Greasy Fork

Greasy Fork is available in English.

超星学习通 刷资料学习时长

刷视频与文档的学习时长

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         超星学习通 刷资料学习时长
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  刷视频与文档的学习时长
// @author       Gemini
// @match        https://*.chaoxing.com/*/coursedata/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const CONFIG = {
    listSelector: ".rename_title.cdoneLine",
    playBtnSelector: 'div[data-title="点击播放"]',
    videoSelector: "video",
    docContainer: ".fileBox",
    docCloseBtn: ".closePop.fr",
    videoPopClose: ".GroupDele.popMoveDele.YPShowDiv",
  };

  let currentIndex = 0;
  const log = (msg) =>
    console.log(`%c[刷课] ${msg}`, "color: #00bcd4; font-weight: bold;");

  function simulatedClick(el) {
    if (!el) return;
    ["mousedown", "mouseup", "click"].forEach((t) => {
      el.dispatchEvent(
        new MouseEvent(t, { bubbles: true, cancelable: true, view: window })
      );
    });
  }

  function findRecursive(selector, root = document) {
    let el = root.querySelector(selector);
    if (el) return el;
    const iframes = root.querySelectorAll("iframe");
    for (let f of iframes) {
      try {
        const doc = f.contentDocument || f.contentWindow.document;
        el = findRecursive(selector, doc);
        if (el) return el;
      } catch (e) {}
    }
    return null;
  }

  async function startProcess() {
    const items = document.querySelectorAll(CONFIG.listSelector);
    if (currentIndex >= items.length) {
      log("🎉 所有任务已处理完毕");
      return;
    }

    log(`🚀 任务进度: ${currentIndex + 1}/${items.length}`);
    simulatedClick(items[currentIndex]);

    // --- 核心优化:快速并行检测 ---
    let detected = false;
    let checkTimer = setInterval(() => {
      // 1. 检测视频
      const playBtn = findRecursive(CONFIG.playBtnSelector);
      if (playBtn && !detected) {
        detected = true;
        clearInterval(checkTimer);
        log("🎬 检测到视频,立即播放");
        simulatedClick(playBtn);
        monitorVideo();
        return;
      }

      // 2. 检测文档 (.fileBox)
      const docBox = findRecursive(CONFIG.docContainer);
      if (docBox && !detected) {
        detected = true;
        clearInterval(checkTimer);
        log("📄 检测到文档,准备滚动");
        setupAndScrollDoc(docBox);
        return;
      }
    }, 500); // 每0.5秒扫一次,极速响应

    // 3. 超时保护:如果5秒内啥都没找到,换下一个
    setTimeout(() => {
      if (!detected) {
        clearInterval(checkTimer);
        log("⚠️ 未检测到可学内容,跳转下一项");
        next();
      }
    }, 5000);
  }

  function monitorVideo() {
    const timer = setInterval(() => {
      const video = findRecursive(CONFIG.videoSelector);
      if (video && video.ended) {
        clearInterval(timer);
        log("✅ 视频完成");
        const close = findRecursive(CONFIG.videoPopClose);
        if (close) simulatedClick(close);
        next();
      } else if (video && video.paused) {
        video.muted = true;
        video.play().catch(() => {});
      }
    }, 2000);
  }

  function setupAndScrollDoc(el) {
    // 强制样式
    el.style.height = "500px";
    el.style.overflowY = "auto";
    el.style.display = "block";

    let lastY = -1;
    const scrollTimer = setInterval(() => {
      const currentY = el.scrollTop;
      el.scrollTo({ top: currentY + 500, behavior: "smooth" });

      log(`📜 滑动中: ${Math.round(currentY)} / ${el.scrollHeight}`);

      if (
        currentY > 0 &&
        (currentY === lastY ||
          currentY + el.clientHeight >= el.scrollHeight - 20)
      ) {
        clearInterval(scrollTimer);
        log("✅ 文档滑动完成");
        const close = findRecursive(CONFIG.docCloseBtn);
        if (close) simulatedClick(close);
        setTimeout(next, 1500);
      }
      lastY = currentY;
    }, 2000);
  }

  function next() {
    currentIndex++;
    startProcess();
  }

  const btn = document.createElement("button");
  btn.innerText = "开始";
  btn.style.cssText =
    "position:fixed;top:20px;right:20px;z-index:999999;padding:12px;background:#4CAF50;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;";
  btn.onclick = () => {
    btn.disabled = true;
    startProcess();
    btn.style.background = "gray"
  };
  document.body.appendChild(btn);
})();