Greasy Fork

Greasy Fork is available in English.

🚀云班课一键完成所有资源(AI修复版)🚀

修复由于蓝墨云班课页面更新导致的按钮消失和统计崩溃问题

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         🚀云班课一键完成所有资源(AI修复版)🚀
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  修复由于蓝墨云班课页面更新导致的按钮消失和统计崩溃问题
// @author       Key77 ( With Gemini3Pro ) Handsomedog
// @match        https://www.mosoteach.cn/web*/index.php?c=res&m=index&clazz_course_id=*
// @icon
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // 1. 获取课程ID
  var courseIdElem = document.getElementsByName("clazz_course_id")[0];
  if (!courseIdElem) return; // 如果不在资源页面则安全退出
  var courseId = courseIdElem.value;

  var pendingTasks = []; // 待完成的任务列表

  // 2. 遍历所有资源,动态、健壮地提取数据
  $('div[data-mime]').each(function() {
    var $res = $(this);
    var type = $res.attr("data-mime");
    var value = $res.attr("data-value");

    // 查找当前资源特有的完成状态(拖拽状态)
    var $finishSpan = $res.find('span[data-is-drag]').first();
    var finish = $finishSpan.attr("data-is-drag");

    // 如果状态为 N (未完成),则加入任务队列
    if (finish === 'N') {
      var task = { type: type, value: value };

      if (type === 'video') {
        // 使用正则提取视频时间,避免以前脆弱的下标定位 (children[4])
        var boxText = $res.find('.create-box.manual-order-hide-part').text();
        var timeMatch = boxText.match(/(\d{2}):(\d{2}):(\d{2})/);
        if (timeMatch) {
          var hour = Number(timeMatch[1]);
          var minute = Number(timeMatch[2]);
          var second = Number(timeMatch[3]);
          task.alltime = (hour * 60 * 60) + (minute * 60) + second;
        } else {
          task.alltime = 300; // 匹配不到时间时,默认给5分钟容错时长
        }
      }
      pendingTasks.push(task);
    }
  });

  var leaveres = pendingTasks.length;
  var deepen = 0;
  var nowdeep = 0;

  // 3. 插入按钮 (增加 z-index 确保显示在最上层)
  $("#cc-main").append('<div class="progress"><div class="progressBar"><span class="progressBar-value">一键完成所有资源</span></div></div>');
  GM_addStyle(".progress {position: absolute; right: 20px; top: 75px; width: 180px; height: 25px; background: #e5e5e5; border-radius: 4px; overflow: hidden; cursor: pointer; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.2);}");
  GM_addStyle(".progressBar { width: 180px; height: 100%; display: flex; justify-content: center; align-items: center; background: cornflowerblue; background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-size: 40px 40px; transition: width .6s ease; border-radius: 4px; animation: progressBar 2s linear infinite;}");
  GM_addStyle(".progressBar-value { font-size: 13px; font-weight: bold; color: white; margin-right: 0px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);}");
  GM_addStyle("@keyframes progressBar { from { background-position: 40px 0; } to { background-position: 0 0; }}");

  var progressBar = document.getElementsByClassName("progressBar")[0];
  var progressBarValue = document.getElementsByClassName("progressBar-value")[0];

  if (leaveres === 0) {
    progressBarValue.innerHTML = `很厉害哦,都完成啦!!!`;
    progressBar.style.cursor = "default";
  } else {
    progressBarValue.innerHTML = `剩余 ${leaveres} 个任务 (点击开始)`;
  }

  // 4. 点击事件与请求派发
  $(".progress").click(function () {
    if (leaveres > 0) {
      deepen = (100 / leaveres).toFixed(1);
      progressBarValue.innerHTML = `进度:0%`;
      progressBar.style.width = '80px';
      progressBar.style.justifyContent = 'flex-end';
      progressBarValue.style.marginRight = '5px';
    } else {
      return;
    }

    // 错峰发送请求,防止同一时间并发过高被服务器拦截
    pendingTasks.forEach(function(task, index) {
      setTimeout(function() {
        if (task.type === 'video') {
          getVideo(task.value, task.alltime);
        } else {
          getResource(task.value);
        }
      }, index * 400); // 每个请求间隔 400 毫秒
    });
  });

  function updateProgress() {
    leaveres -= 1;
    if (leaveres <= 0) {
      progressBarValue.innerHTML = `进度:100%`;
      progressBar.style.width = '180px';
      setTimeout(() => location.reload(), 1000); // 跑完后延迟1秒刷新页面
    } else {
      nowdeep = getNowDeep(nowdeep);
      progressBar.style.width = Number(nowdeep) + 80 + 'px';
      progressBarValue.innerHTML = `进度:${nowdeep}%`;
    }
  }

  function getResource(value) {
    $.ajax({
      type: 'head',
      url: 'index.php?c=res&m=online_preview&clazz_course_id=' + courseId + '&file_id=' + value,
      complete: function () {
        updateProgress();
      }
    });
  }

  function getVideo(value, alltime) {
    $.ajax({
      type: 'post',
      dataType: 'json',
      url: 'index.php?c=res&m=save_watch_to',
      data: {
        clazz_course_id: courseId,
        res_id: value,
        watch_to: alltime,
        duration: alltime,
        current_watch_to: 0
      },
      success: function () {
        updateProgress();
      },
      error: function () {
        console.log("视频保存失败, ID: " + value);
        updateProgress(); // 即使个别失败也推进进度条,避免卡死
      }
    });
  }

  function getNowDeep(nowdeep) {
    let nextdeep = (Number(nowdeep) + Number(deepen)).toFixed(1);
    if (nextdeep >= 100 - deepen) {
      nextdeep = 100;
    } else if (nextdeep - Math.floor(nextdeep) >= 0.5) {
      nextdeep = Math.ceil(nextdeep);
    }
    return nextdeep;
  }
})();