// ==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
// @downloadURL https://update.greasyfork.icu/scripts/573913/%F0%9F%9A%80%E4%BA%91%E7%8F%AD%E8%AF%BE%E4%B8%80%E9%94%AE%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E8%B5%84%E6%BA%90%28AI%E4%BF%AE%E5%A4%8D%E7%89%88%29%F0%9F%9A%80.user.js
// @updateURL https://update.greasyfork.icu/scripts/573913/%F0%9F%9A%80%E4%BA%91%E7%8F%AD%E8%AF%BE%E4%B8%80%E9%94%AE%E5%AE%8C%E6%88%90%E6%89%80%E6%9C%89%E8%B5%84%E6%BA%90%28AI%E4%BF%AE%E5%A4%8D%E7%89%88%29%F0%9F%9A%80.meta.js
// ==/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('
');
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;
}
})();