NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name c**c半自动刷课工具
// @namespace https://openuserjs.org/Users/aaserera
// @version 4.9.0
// @description 从选定开头自动播放本章各小节课程
// @author aaserera (generate by DeepSeek & ChatGPT)
// @match https://zypt.cauc.edu.cn/OCEDU/action/studyFile/toStudySpoc
// @match https://zypt-443.webvpn.cauc.edu.cn/OCEDU/action/studyFile/toStudySpoc
// @icon https://www.google.com/s2/favicons?sz=64&domain=cauc.edu.cn
// @grant none
// @license MIT
// @homepageURL https://openuserjs.org/scripts/aaserera/c**c半自动刷课工具
// @supportURL https://openuserjs.org/scripts/aaserera/c**c半自动刷课工具/discussions
// ==/UserScript==
// ==OpenUserJS==
// @author aaserera
// ==/OpenUserJS==
//4.8版本更新:修复 monitorElementChanges 未定义 & 代码优化
//4.8.1版本单纯是为了测试更新功能
//4.9.0更新:增加vpn登录情况下的作用域
(function () {
'use strict';
const config = {
checkInterval: 1000, // 资源检查间隔(1 秒)
nextDelay: 4000, // 资源切换延迟(4 秒)
resumeDelay: 1000, // 关闭弹窗后检测视频(1 秒)
playRetry: 3, // 播放重试次数
completeThreshold: 99.9, // 进度完成阈值
minDuration: 10, // 最短有效时长(秒)
logLevel: 'DEBUG', // 默认日志级别
};
let videoChecked = false;
let lastChapterId = "";
function monitorElementChanges() {
const observer = new MutationObserver(() => checkCompletion());
const fileList = document.querySelector('#ul_fileList');
const chapterList = document.querySelector('.kjslist');
if (fileList) observer.observe(fileList, {
childList: true,
subtree: true
});
if (chapterList) observer.observe(chapterList, {
childList: true,
subtree: true
});
log('👀 监听 DOM 变化', 'DEBUG');
}
/*********************
* 监听文件列表变化
*********************/
function monitorFileListChanges() {
const fileList = document.querySelector('#ul_fileList');
if (fileList) {
const observer = new MutationObserver(() => {
if (!isVideoPlaying()) {
log('🔄 检测到学习资源变化,重新尝试播放新视频', 'DEBUG');
waitForVideoToLoad();
}
else {
log('🎥 视频正在播放,跳过自动播放逻辑', 'DEBUG');
}
});
observer.observe(fileList, {
childList: true,
subtree: true
});
}
}
function init() {
log('📌 脚本初始化', 'DEBUG');
monitorElementChanges();
monitorFileListChanges();
initPopupHandler();
initCompletionDialogHandler();
waitForVideoToLoad();
log('🚀 脚本已启动', 'DEBUG');
}
/*********************
* 关闭所有弹窗
*********************/
function closeAllPopups() {
const popups = document.querySelectorAll('.layui-layer');
popups.forEach(popup => {
const closeButton = popup.querySelector('.layui-layer-close') || popup.querySelector('.layui-layer-btn0');
if (closeButton) {
closeButton.click();
log('✅ 关闭弹窗', 'INFO');
}
});
// 在短时间后检测视频状态
setTimeout(checkVideoPaused, config.resumeDelay);
}
function checkVideoPaused() {
const video = document.querySelector('video');
if (video && video.paused) {
log('🔄 检测到视频暂停,尝试恢复播放', 'DEBUG');
tryPlayVideo(video);
}
}
/*********************
* 监听学习完成 & 时间提醒弹窗
*********************/
function initCompletionDialogHandler() {
setInterval(closeAllPopups, config.checkInterval);
}
function initPopupHandler() {
setInterval(closeAllPopups, config.checkInterval);
}
/*********************
* 进度完成验证
*********************/
function checkCompletion() {
const progress = getCurrentProgress();
if (progress >= config.completeThreshold && !videoChecked) {
videoChecked = true;
log(`🎯 进度达标: ${progress}%`, 'INFO');
handleCourseComplete();
}
}
function getCurrentProgress() {
try {
const titleProgress = parseFloat(document.querySelector('#div_studyProgressBar').title.match(/[\d.]+/)[0]);
const widthProgress = parseFloat(document.querySelector('.bfcurrentbg').style.width);
return Math.max(titleProgress, widthProgress);
}
catch {
return 0;
}
}
function handleCourseComplete() {
log('⏭️ 达到完成条件,准备跳转', 'INFO');
setTimeout(() => {
clickNextResource();
videoChecked = false;
}, config.nextDelay);
}
/*********************
* 章节跳转
*********************/
function clickNextResource() {
const currentItem = document.querySelector('#ul_fileList li.current');
const allResources = [...document.querySelectorAll('#ul_fileList li')].filter(li => li.textContent);
const currentIndex = allResources.indexOf(currentItem);
if (currentIndex < allResources.length - 1) {
const nextId = allResources[currentIndex + 1].getAttribute('moocstufileid');
studyMoocSpoc.clickFile(nextId);
log(`📂 跳转到:${allResources[currentIndex + 1].textContent.trim()}`, 'INFO');
}
else {
log('📖 当前章节学习完成,尝试寻找下一个章节', 'DEBUG');
setTimeout(clickNextChapter, 5000);
}
}
function clickNextChapter() {
const currentChapter = document.querySelector('.kjslist li a.currentChapterNode');
const currentChapterItem = currentChapter?.closest('li');
const nextChapterItem = currentChapterItem ? currentChapterItem.nextElementSibling : null;
if (nextChapterItem) {
const nextChapterId = nextChapterItem.getAttribute('chapterid');
if (nextChapterId !== lastChapterId) {
lastChapterId = nextChapterId;
nextChapterItem.click();
log('📖 跳转到下一个章节', 'INFO');
}
}
else {
log('🔄 当前已是最后一个章节,但仍然保持脚本运行', 'DEBUG');
}
}
/*********************
* 视频加载优化(减少启动延迟)
*********************/
function waitForVideoToLoad() {
const video = document.querySelector('video');
if (!video) {
log('⏳ 未找到视频,等待 0.5 秒后重试...', 'DEBUG');
setTimeout(waitForVideoToLoad, 500);
return;
}
video.addEventListener('canplay', () => {
log('✅ 视频可以播放,尝试播放', 'DEBUG');
tryPlayVideo(video);
});
tryPlayVideo(video);
}
function tryPlayVideo(video) {
if (isVideoPlaying()) {
log('🎥 视频已经在播放,跳过自动播放', 'DEBUG');
return;
}
let attempts = 0;
function attemptPlay() {
video.play().then(() => {
log('✅ 视频自动播放成功', 'INFO');
}).catch(() => {
if (attempts < config.playRetry) {
log(`❌ 播放失败,重试 ${attempts + 1}/${config.playRetry} 次`, 'DEBUG');
attempts++;
setTimeout(attemptPlay, 500);
}
else {
log('❌ 自动播放失败,请手动点击播放', 'ERROR');
}
});
}
attemptPlay();
}
function isVideoPlaying() {
const video = document.querySelector('video');
return video && !video.paused && !video.ended && video.readyState > 2;
}
function log(message, level = 'DEBUG') {
console.log(`[刷课助手] ${new Date().toLocaleTimeString()} [${level}] ${message}`);
}
if (document.readyState === 'complete') {
init();
}
else {
window.addEventListener('load', init);
}
})();