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); } })();