// ==UserScript== // @name 重庆高教在线自动刷课助手(增强版) // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 重庆高教在线(cqooc.com)自动刷课脚本。功能:自动播放视频(防暂停、放完自动下一节),PPT课件自动停留120秒后切换,自动跳过作业和测验,支持后台挂机。 // @author 自定义 // @match *://*.cqooc.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @license MIT // ==/UserScript== (function () { 'use strict'; // ==================== 配置区域 ==================== const CONFIG = { PPT_WAIT_TIME: 120000, // PPT停留时间(毫秒)120秒 VIDEO_CHECK_INTERVAL: 1000, // 视频状态检测间隔(毫秒) PAGE_LOAD_DELAY: 2500, // 页面加载等待时间(毫秒) AUTO_START: true, // 是否自动开始(无需手动点击按钮) SKIP_TESTS: true, // 是否跳过作业和测验 PREVENT_PAUSE: true, // 是否开启防暂停 LOG_LEVEL: 'info', // 日志级别: debug|info|warn|error }; // ==================== 日志工具 ==================== const Logger = { prefix: '[高教刷课]', colors: { debug: '#888', info: '#2196F3', warn: '#FF9800', error: '#f44336', success: '#4CAF50' }, log(level, message, data) { const levels = ['debug', 'info', 'warn', 'error']; if (levels.indexOf(level) < levels.indexOf(CONFIG.LOG_LEVEL)) return; const color = this.colors[level] || '#333'; const time = new Date().toLocaleTimeString(); console.log(`%c${this.prefix}[${time}][${level.toUpperCase()}] ${message}`, `color:${color}`, data || ''); // 同时显示在页面控制面板上 addPanelLog(message, level); }, debug(msg, data) { this.log('debug', msg, data); }, info(msg, data) { this.log('info', msg, data); }, warn(msg, data) { this.log('warn', msg, data); }, error(msg, data) { this.log('error', msg, data); }, success(msg, data) { this.log('info', `%c${msg}`, 'color:#4CAF50;font-weight:bold'); } }; // ==================== 页面控制面板 ==================== let panelCreated = false; let panelLogs = []; function createControlPanel() { if (panelCreated) return; panelCreated = true; GM_addStyle(` #cqooc-panel { position: fixed; top: 80px; right: 20px; width: 320px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; transition: all 0.3s ease; } #cqooc-panel.collapsed { width: 180px; height: 50px; } #cqooc-panel.collapsed #cqooc-body { display: none; } #cqooc-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: rgba(0,0,0,0.2); cursor: pointer; user-select: none; } #cqooc-header-title { color: #fff; font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; } #cqooc-header-status { width: 10px; height: 10px; border-radius: 50%; background: #4CAF50; box-shadow: 0 0 8px #4CAF50; animation: pulse 2s infinite; } #cqooc-header-status.stopped { background: #f44336; box-shadow: 0 0 8px #f44336; animation: none; } #cqooc-header-status.paused { background: #FF9800; box-shadow: 0 0 8px #FF9800; animation: pulse 1s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } #cqooc-toggle { color: #fff; font-size: 16px; background: none; border: none; cursor: pointer; } #cqooc-body { padding: 12px; } #cqooc-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; } .cqooc-stat-box { background: rgba(255,255,255,0.15); border-radius: 8px; padding: 8px; text-align: center; } .cqooc-stat-label { color: rgba(255,255,255,0.7); font-size: 11px; margin-bottom: 4px; } .cqooc-stat-value { color: #fff; font-size: 16px; font-weight: 700; } #cqooc-current-task { background: rgba(255,255,255,0.1); border-radius: 8px; padding: 8px; margin-bottom: 10px; color: #fff; font-size: 12px; min-height: 40px; word-break: break-all; } #cqooc-logs { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 8px; height: 150px; overflow-y: auto; font-size: 11px; line-height: 1.5; } .cqooc-log-item { color: rgba(255,255,255,0.9); padding: 1px 0; border-bottom: 1px solid rgba(255,255,255,0.05); } .cqooc-log-debug { color: #aaa; } .cqooc-log-info { color: #64B5F6; } .cqooc-log-warn { color: #FFB74D; } .cqooc-log-error { color: #EF5350; } .cqooc-log-success { color: #81C784; } #cqooc-controls { display: flex; gap: 6px; margin-top: 10px; } .cqooc-btn { flex: 1; padding: 6px 10px; border: none; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .cqooc-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); } .cqooc-btn-start { background: #4CAF50; color: #fff; } .cqooc-btn-stop { background: #f44336; color: #fff; } .cqooc-btn-pause { background: #FF9800; color: #fff; } ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); border-radius: 2px; } `); const panel = document.createElement('div'); panel.id = 'cqooc-panel'; panel.innerHTML = `
高教在线刷课助手 v1.0
总任务
0
已完成
0
当前类型
-
进度
0%
等待开始...
`; document.body.appendChild(panel); // 折叠/展开 let collapsed = false; document.getElementById('cqooc-header').addEventListener('click', (e) => { if (e.target.id === 'cqooc-toggle') { collapsed = !collapsed; panel.classList.toggle('collapsed', collapsed); document.getElementById('cqooc-toggle').textContent = collapsed ? '+' : '−'; } }); // 按钮事件 document.getElementById('cqooc-btn-start').addEventListener('click', () => { if (!isRunning) { startStudying(); } }); document.getElementById('cqooc-btn-pause').addEventListener('click', () => { togglePause(); }); document.getElementById('cqooc-btn-stop').addEventListener('click', () => { stopStudying(); }); } function addPanelLog(message, level = 'info') { panelLogs.push({ message, level, time: new Date().toLocaleTimeString() }); if (panelLogs.length > 100) panelLogs.shift(); const logsContainer = document.getElementById('cqooc-logs'); if (!logsContainer) return; const logItem = document.createElement('div'); logItem.className = `cqooc-log-item cqooc-log-${level}`; logItem.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; logsContainer.appendChild(logItem); logsContainer.scrollTop = logsContainer.scrollHeight; // 限制日志数量 while (logsContainer.children.length > 50) { logsContainer.removeChild(logsContainer.firstChild); } } function updatePanelStats(type, progress) { const typeEl = document.getElementById('cqooc-stat-type'); const progressEl = document.getElementById('cqooc-stat-progress'); if (typeEl) typeEl.textContent = type || '-'; if (progressEl) progressEl.textContent = progress || '0%'; } function updatePanelStatus(status) { const statusEl = document.getElementById('cqooc-header-status'); if (statusEl) { statusEl.className = ''; statusEl.classList.add(status); } } function updateCurrentTask(text) { const el = document.getElementById('cqooc-current-task'); if (el) el.textContent = text; } // ==================== 核心刷课逻辑 ==================== let isRunning = false; let isPaused = false; let shouldStop = false; let taskQueue = []; let completedCount = 0; let totalCount = 0; let currentVideo = null; let pausePreventionInterval = null; function delay(ms) { return new Promise(resolve => { const check = () => { if (shouldStop) { resolve(); return; } if (isPaused) { setTimeout(check, 500); return; } setTimeout(resolve, ms); }; check(); }); } // 等待页面元素加载 function waitForElement(selector, timeout = 15000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { const el = document.querySelector(selector); if (el) { resolve(el); return; } if (Date.now() - startTime > timeout) { reject(new Error(`等待元素 ${selector} 超时`)); return; } setTimeout(check, 500); }; check(); }); } // 获取所有待学习的章节 function getAllTasks() { // 新版高教在线选择器 let tasks = Array.from(document.querySelectorAll('.third-level-box')); // 如果没找到,尝试旧版选择器 if (tasks.length === 0) { const chaptersList = document.querySelector('ul.cont_list#chapters-list'); if (chaptersList) { // 展开所有折叠的章节 const closedItems = chaptersList.querySelectorAll('li.item.one.close'); closedItems.forEach(item => item.click()); // 获取未完成的小节 (s0-s5表示未完成) const regex = /item\s+ref_\d+\s+item\d+\s+two\s+s(0|1|2|3|4|5)/; const allLi = chaptersList.querySelectorAll('li'); tasks = Array.from(allLi).filter(li => regex.test(li.className)); } } // 过滤掉作业和测验 if (CONFIG.SKIP_TESTS) { tasks = tasks.filter(el => !/作业|测验/.test(el.innerText)); } return tasks; } // 点击任务并等待加载 async function clickTaskAndWait(task) { task.click(); await delay(CONFIG.PAGE_LOAD_DELAY); // 等待内容区域加载 try { await Promise.race([ waitForElement('video', 10000), waitForElement('iframe', 10000), waitForElement('.MPreview-box', 10000), waitForElement('#videoPlayer', 10000) ]); } catch (e) { Logger.warn('内容加载等待超时,继续执行'); } } // 判断内容类型 function getContentType() { const hasVideo = document.querySelector('video'); const hasVideoPlayer = document.querySelector('#videoPlayer'); const hasIframe = document.querySelector('iframe'); const hasMPreview = document.querySelector('.MPreview-box'); if (hasVideo || hasVideoPlayer) return 'video'; if (hasIframe || hasMPreview) return 'pdf'; // 通过slide类型判断(旧版) const activeSlide = document.querySelector('ul.list li.active, ul.list li.current'); if (activeSlide) { if (activeSlide.className.includes('v2')) return 'video'; if (activeSlide.className.includes('v1')) return 'pdf'; } return 'unknown'; } // ==================== 视频处理 ==================== async function handleVideo() { Logger.info('开始处理视频任务'); updatePanelStats('视频', '0%'); updateCurrentTask('正在播放视频...'); const video = document.querySelector('video') || document.querySelector('#videoPlayer video'); if (!video) { Logger.error('未找到视频元素'); return; } currentVideo = video; return new Promise((resolve) => { let resolved = false; const safeResolve = () => { if (!resolved) { resolved = true; cleanup(); resolve(); } }; // 清理函数 function cleanup() { video.removeEventListener('ended', onEnded); video.removeEventListener('timeupdate', onTimeUpdate); video.removeEventListener('error', onError); video.onpause = null; if (pausePreventionInterval) { clearInterval(pausePreventionInterval); pausePreventionInterval = null; } currentVideo = null; } // 播放完成监听 function onEnded() { Logger.success('视频播放完成!'); completedCount++; updatePanelStats('视频', '100%'); safeResolve(); } // 进度更新 function onTimeUpdate() { if (video.duration) { const progress = (video.currentTime / video.duration * 100).toFixed(1); updatePanelStats('视频', progress + '%'); updateCurrentTask(`视频播放中: ${formatTime(video.currentTime)} / ${formatTime(video.duration)} (${progress}%)`); } } // 错误处理 function onError(e) { Logger.error('视频播放出错', e); safeResolve(); } // 绑定事件 video.addEventListener('ended', onEnded); video.addEventListener('timeupdate', onTimeUpdate); video.addEventListener('error', onError); // 防暂停处理 if (CONFIG.PREVENT_PAUSE) { video.onpause = function() { if (!isRunning || isPaused || video.ended) return; Logger.debug('视频被暂停,尝试恢复播放'); setTimeout(() => { if (video.paused && !video.ended) { video.play().catch(err => Logger.debug('恢复播放失败:' + err)); } }, 500); }; // 额外保护:定时检查视频状态 pausePreventionInterval = setInterval(() => { if (!isRunning || isPaused) return; if (video.paused && !video.ended && video.readyState >= 2) { Logger.debug('检测到视频暂停,自动恢复'); video.play().catch(() => {}); } }, 3000); } // 尝试播放视频 function tryPlay() { // 先尝试点击播放按钮 const playBtn = document.querySelector('.dplayer-mobile-play') || document.querySelector('.xgplayer-icon-play svg') || document.querySelector('.xgplayer-start') || document.querySelector('[class*="play"]'); if (playBtn) { Logger.debug('点击播放按钮'); playBtn.click(); } // 直接调用play方法 video.play().then(() => { Logger.info('视频开始播放'); }).catch(err => { Logger.warn('自动播放被阻止,尝试静音后播放'); video.muted = true; video.play().catch(err2 => { Logger.error('播放失败', err2); }); }); } // 确保视频可以播放 if (video.readyState >= 2) { tryPlay(); } else { video.addEventListener('canplay', tryPlay, { once: true }); // 超时兜底 setTimeout(tryPlay, 5000); } // 超时保护:如果10分钟还没播放完,强制跳过 setTimeout(() => { if (!resolved) { Logger.warn('视频播放超时,强制进入下一节'); safeResolve(); } }, 600000); }); } // ==================== PPT/PDF处理 ==================== async function handlePDF() { Logger.info(`开始处理PPT/PDF任务,将等待 ${CONFIG.PPT_WAIT_TIME / 1000} 秒`); updatePanelStats('PPT', '等待中'); updateCurrentTask(`PPT/PPT课件学习中,剩余 ${CONFIG.PPT_WAIT_TIME / 1000} 秒...`); const startTime = Date.now(); const totalWait = CONFIG.PPT_WAIT_TIME; return new Promise((resolve) => { let resolved = false; const timer = setInterval(() => { if (shouldStop) { clearInterval(timer); resolve(); return; } if (isPaused) { return; // 暂停时不倒计时 } const elapsed = Date.now() - startTime; const remaining = Math.max(0, totalWait - elapsed); const progress = ((elapsed / totalWait) * 100).toFixed(0); updatePanelStats('PPT', progress + '%'); updateCurrentTask(`PPT课件学习中,剩余 ${Math.ceil(remaining / 1000)} 秒...`); if (remaining <= 0) { clearInterval(timer); if (!resolved) { resolved = true; Logger.success('PPT课件停留时间结束,进入下一节'); completedCount++; resolve(); } } }, 1000); }); } // ==================== 主控制流程 ==================== async function startStudying() { if (isRunning) return; isRunning = true; isPaused = false; shouldStop = false; completedCount = 0; updatePanelStatus('running'); document.getElementById('cqooc-btn-start').textContent = '运行中'; Logger.info('========== 开始自动刷课 =========='); // 获取所有任务 const tasks = getAllTasks(); taskQueue = tasks; totalCount = tasks.length; document.getElementById('cqooc-stat-total').textContent = totalCount; Logger.info(`共找到 ${totalCount} 个学习任务`); if (totalCount === 0) { Logger.warn('未找到可学习的任务,请确认已进入课程学习页面'); isRunning = false; updatePanelStatus('stopped'); return; } // 找到当前正在进行的任务索引 const activeIndex = tasks.findIndex(el => el.classList.contains('active') || el.classList.contains('current') ); const startIndex = activeIndex >= 0 ? activeIndex : 0; Logger.info(`从第 ${startIndex + 1} 个任务开始执行`); // 依次执行每个任务 for (let i = startIndex; i < tasks.length; i++) { if (shouldStop) { Logger.info('刷课已停止'); break; } while (isPaused) { if (shouldStop) break; await delay(500); } const task = tasks[i]; const taskName = task.innerText?.trim() || `任务 ${i + 1}`; Logger.info(`--------------------------------`); Logger.info(`[${i + 1}/${tasks.length}] 开始学习: ${taskName}`); updateCurrentTask(`[${i + 1}/${tasks.length}] ${taskName}`); document.getElementById('cqooc-stat-done').textContent = completedCount; try { // 点击任务并等待加载 await clickTaskAndWait(task); // 等待一下确保内容完全加载 await delay(2000); // 判断内容类型并处理 const contentType = getContentType(); Logger.info(`内容类型: ${contentType}`); switch (contentType) { case 'video': await handleVideo(); break; case 'pdf': await handlePDF(); break; default: Logger.warn('未知内容类型,等待10秒后跳过'); updatePanelStats('未知', '-'); await delay(10000); completedCount++; break; } // 任务完成后等2秒再进入下一个 await delay(2000); } catch (error) { Logger.error(`处理任务时出错: ${error.message}`); await delay(3000); } } isRunning = false; updatePanelStatus('stopped'); document.getElementById('cqooc-btn-start').textContent = '开始刷课'; if (!shouldStop) { Logger.success('========== 所有课程学习完成! =========='); updateCurrentTask('所有课程已完成!'); notify('刷课完成!所有章节已学习完毕', 5000); } } function togglePause() { if (!isRunning) return; isPaused = !isPaused; const btn = document.getElementById('cqooc-btn-pause'); if (isPaused) { btn.textContent = '继续'; updatePanelStatus('paused'); Logger.info('已暂停'); if (currentVideo && !currentVideo.paused) { currentVideo.pause(); } } else { btn.textContent = '暂停'; updatePanelStatus('running'); Logger.info('已恢复'); if (currentVideo && currentVideo.paused) { currentVideo.play().catch(() => {}); } } } function stopStudying() { shouldStop = true; isRunning = false; isPaused = false; updatePanelStatus('stopped'); document.getElementById('cqooc-btn-start').textContent = '开始刷课'; document.getElementById('cqooc-btn-pause').textContent = '暂停'; Logger.info('刷课已停止'); if (pausePreventionInterval) { clearInterval(pausePreventionInterval); pausePreventionInterval = null; } } // ==================== 工具函数 ==================== function formatTime(seconds) { if (!seconds || isNaN(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs < 10 ? '0' + secs : secs}`; } function notify(text, time = 3000) { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 16px 32px; border-radius: 12px; font-size: 14px; font-weight: 600; z-index: 999999; box-shadow: 0 8px 32px rgba(0,0,0,0.3); animation: slideDown 0.3s ease; `; notification.textContent = text; document.body.appendChild(notification); const style = document.createElement('style'); style.textContent = ` @keyframes slideDown { from { opacity: 0; transform: translateX(-50%) translateY(-20px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } `; document.head.appendChild(style); setTimeout(() => { notification.style.animation = 'slideDown 0.3s ease reverse'; setTimeout(() => notification.remove(), 300); }, time); } // ==================== 页面路由处理 ==================== let lastPath = location.pathname; function handlePathChange() { if (lastPath !== location.pathname) { lastPath = location.pathname; onPageChange(); } } function onPageChange() { Logger.info('页面路由变化:', location.pathname); if (location.pathname === '/course/detail/courseStudy' || location.pathname === '/learn/mooc/structure') { // 进入课程学习页面 setTimeout(() => { createControlPanel(); if (CONFIG.AUTO_START) { setTimeout(() => startStudying(), 3000); } }, 2000); } else { stopStudying(); } } // 劫持 history API 监听路由变化 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); handlePathChange(); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); handlePathChange(); }; window.addEventListener('popstate', handlePathChange); // ==================== 初始化入口 ==================== (async function init() { Logger.info('重庆高教在线自动刷课助手已加载'); Logger.info('当前页面:', location.pathname); // 无论哪个页面都创建面板 if (document.body) { createControlPanel(); } else { window.addEventListener('DOMContentLoaded', createControlPanel); } // 如果已经在课程学习页面,自动开始 if (location.pathname === '/course/detail/courseStudy' || location.pathname === '/learn/mooc/structure') { Logger.info('检测到课程学习页面'); if (CONFIG.AUTO_START) { setTimeout(() => { Logger.info('3秒后自动开始刷课...'); startStudying(); }, 3000); } } else { Logger.info('请先进入「在学课程」,然后点击进入课程学习页面'); notify('刷课助手已就绪,请进入课程学习页面后自动开始', 4000); } })(); })();