// ==UserScript== // @name 重庆高校在线课程平台-刷课脚本 // @namespace https://www.cqooc.com/ // @version 1.0 // @description 自动检测课程页面、播放视频/等待iframe、自动跳转下一课 // @author nekods // @match *://www.cqooc.com/course/detail/courseStudy?id=* // @grant none // @tag 网课,刷课 // ==/UserScript== (function() { 'use strict'; // 全局状态管理 const globalState = { isRunning: false, // 脚本运行状态 currentActiveThirdDom: null, // 当前激活的三级菜单DOM allThirdLevelMenus: [], // 所有有效三级菜单(过滤空节点) firstLevelMenus: [], // 一级菜单列表 isProcessing: false, // 是否正在处理当前页面 iframeTimer: null // iframe等待定时器 }; // ====================== 1. 界面构建(新增手动跳转按钮) ====================== function createControlPanel() { // 主面板容器 const panel = document.createElement('div'); panel.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 99999; background: #fff; border-radius: 8px; box-shadow: 0 2px 20px rgba(0,0,0,0.2); padding: 20px; width: 380px; font-family: sans-serif; `; // 标题 const title = document.createElement('h3'); title.style.cssText = ` margin: 0 0 15px 0; font-size: 18px; color: #333; text-align: center; border-bottom: 1px solid #eee; padding-bottom: 10px; `; title.textContent = '课程自动学习控制器'; // 状态显示 const statusDisplay = document.createElement('div'); statusDisplay.style.cssText = ` margin: 10px 0; padding: 8px; border-radius: 4px; background: #f5f7fa; color: #666; font-size: 14px; min-height: 40px; line-height: 1.6; `; statusDisplay.id = 'auto-learn-status'; statusDisplay.textContent = '状态:未运行\n当前进度:未检测到课程'; // 按钮容器(新增手动跳转按钮) const btnContainer = document.createElement('div'); btnContainer.style.cssText = ` display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; `; // 启动按钮 const startBtn = document.createElement('button'); startBtn.style.cssText = ` flex: 1; padding: 10px; background: #52c41a; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background 0.3s; min-width: 120px; `; startBtn.textContent = '启动自动学习'; startBtn.onmouseover = () => startBtn.style.background = '#73d13d'; startBtn.onmouseout = () => startBtn.style.background = '#52c41a'; startBtn.onclick = async () => { globalState.isRunning = true; updateStatus('已启动自动学习,正在加载课程菜单...', '#52c41a'); startBtn.disabled = true; stopBtn.disabled = false; jumpBtn.disabled = false; // 启动后启用手动跳转按钮 // 初始化菜单数据(适配真实DOM结构) try { await loadAllMenuData(); // 定位当前激活的三级DOM globalState.currentActiveThirdDom = document.querySelector('.third-level-box.active'); // 若未激活,默认选第一个有效三级菜单 if (!globalState.currentActiveThirdDom && globalState.allThirdLevelMenus.length > 0) { globalState.currentActiveThirdDom = globalState.allThirdLevelMenus[0]; } updateStatus(`课程菜单加载完成,共${globalState.allThirdLevelMenus.length}个有效三级课程`, '#52c41a'); } catch (e) { updateStatus(`菜单加载失败:${e.message}`, '#ff4d4f'); startBtn.disabled = false; stopBtn.disabled = true; jumpBtn.disabled = true; globalState.isRunning = false; return; } initCourseDetection(); }; // 停止按钮 const stopBtn = document.createElement('button'); stopBtn.style.cssText = ` flex: 1; padding: 10px; background: #ff4d4f; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background 0.3s; min-width: 120px; `; stopBtn.textContent = '停止自动学习'; stopBtn.disabled = true; stopBtn.onmouseover = () => stopBtn.style.background = '#ff7875'; stopBtn.onmouseout = () => stopBtn.style.background = '#ff4d4f'; stopBtn.onclick = () => { globalState.isRunning = false; globalState.isProcessing = false; if (globalState.iframeTimer) clearTimeout(globalState.iframeTimer); updateStatus('已停止自动学习', '#ff4d4f'); startBtn.disabled = false; stopBtn.disabled = true; jumpBtn.disabled = true; }; // 新增:手动跳转下一课按钮 const jumpBtn = document.createElement('button'); jumpBtn.style.cssText = ` flex: 1; padding: 10px; background: #1890ff; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: background 0.3s; min-width: 120px; `; jumpBtn.textContent = '手动跳转下一课'; jumpBtn.disabled = true; jumpBtn.onmouseover = () => jumpBtn.style.background = '#40a9ff'; jumpBtn.onmouseout = () => jumpBtn.style.background = '#1890ff'; jumpBtn.onclick = async () => { if (!globalState.currentActiveThirdDom) { updateStatus('无可用课程可跳转', '#ff4d4f'); return; } updateStatus('手动触发跳转下一课...', '#409eff'); await jumpToNextCourse(); // 直接调用跳转逻辑 }; // 组装按钮 btnContainer.appendChild(startBtn); btnContainer.appendChild(stopBtn); btnContainer.appendChild(jumpBtn); // 添加手动跳转按钮 // 组装面板 panel.appendChild(title); panel.appendChild(statusDisplay); panel.appendChild(btnContainer); document.body.appendChild(panel); } // 更新状态显示 function updateStatus(text, color = '#666') { const statusEl = document.getElementById('auto-learn-status'); if (statusEl) { statusEl.style.color = color; statusEl.textContent = `状态:${text}`; } console.log(`[自动学习] ${text}`); } // ====================== 2. 核心:适配真实DOM结构的菜单加载 ====================== // 等待元素加载 function waitForElement(selector, timeout = 15000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkInterval = 300; function check() { if (Date.now() - startTime > timeout) { reject(new Error(`超时(${timeout/1000}秒)未找到元素: ${selector}`)); return; } const el = document.querySelector(selector); if (el) resolve(el); else setTimeout(check, checkInterval); } check(); }); } // 等待页面加载完成 function waitForPageLoad() { return new Promise(resolve => { if (document.readyState === 'complete') resolve(); else window.addEventListener('load', resolve); }); } // 核心:加载菜单数据(适配真实DOM结构) async function loadAllMenuData() { // 第一步:等待菜单容器加载 await waitForElement('.menu-box', 10000); // 第二步:加载所有一级菜单(适配.menu-item > .first-level-box结构) globalState.firstLevelMenus = []; const menuItems = Array.from(document.querySelectorAll('.menu-box .menu-item')); for (const [firstIndex, menuItem] of menuItems.entries()) { const firstDom = menuItem.querySelector('.first-level-box'); if (!firstDom) continue; // 一级菜单信息 const firstTitle = firstDom.querySelector('.title-big')?.textContent.trim() || `一级菜单${firstIndex+1}`; const firstExpandBtn = firstDom.querySelector('.right-icon'); const isFirstExpanded = !!firstDom.querySelector('.right-icon .first-icon.anticon-down'); // 找一级菜单下的inner-box(.first-level-inner-box) const firstInnerBox = menuItem.querySelector('.first-level-inner-box'); const secondLevelBoxes = []; if (firstInnerBox) { // 遍历一级inner-box下的所有二级菜单 const secondDoms = Array.from(firstInnerBox.querySelectorAll('.second-level-box')); for (const [secondIndex, secondDom] of secondDoms.entries()) { // 二级菜单信息 const secondTitle = secondDom.querySelector('.title-big')?.textContent.trim() || `二级菜单${secondIndex+1}`; // 找二级菜单下的inner-box(.second-level-inner-box) const secondInnerBox = secondDom.nextElementSibling; const thirdLevelBoxes = []; if (secondInnerBox && secondInnerBox.classList.contains('second-level-inner-box')) { // 遍历二级inner-box下的所有三级菜单(过滤空节点) const thirdDoms = Array.from(secondInnerBox.querySelectorAll('.third-level-box')); for (const thirdDom of thirdDoms) { // 过滤空的三级菜单节点(
) const hasContent = thirdDom.querySelector('.third-level-inner-box') && thirdDom.querySelector('.title'); if (hasContent) { thirdLevelBoxes.push(thirdDom); } } } secondLevelBoxes.push({ dom: secondDom, title: secondTitle, thirdLevelBoxes: thirdLevelBoxes }); } } globalState.firstLevelMenus.push({ dom: firstDom, index: firstIndex, title: firstTitle, expandBtn: firstExpandBtn, isExpanded: isFirstExpanded, secondLevelBoxes: secondLevelBoxes, menuItem: menuItem // 关联所属的.menu-item }); } // 第三步:构建扁平的有效三级菜单列表(核心:过滤空节点) globalState.allThirdLevelMenus = []; for (const firstMenu of globalState.firstLevelMenus) { for (const secondMenu of firstMenu.secondLevelBoxes) { globalState.allThirdLevelMenus.push(...secondMenu.thirdLevelBoxes); } } // 去重+排序(保证顺序) globalState.allThirdLevelMenus = [...new Set(globalState.allThirdLevelMenus)]; } // 展开指定一级菜单 async function expandFirstLevelMenu(firstIndex) { if (firstIndex >= globalState.firstLevelMenus.length) return false; const firstMenu = globalState.firstLevelMenus[firstIndex]; if (!firstMenu.expandBtn || firstMenu.isExpanded) return false; // 点击展开按钮 firstMenu.expandBtn.click(); updateStatus(`展开一级菜单:${firstMenu.title}`, '#409eff'); // 延长等待时间,确保inner-box加载完成 await new Promise(resolve => setTimeout(resolve, 1000)); firstMenu.isExpanded = true; // 重新加载菜单数据(展开后更新三级列表) await loadAllMenuData(); return true; } // 获取当前三级DOM的扁平索引 function getCurrentThirdIndex(currentThirdDom) { return globalState.allThirdLevelMenus.findIndex(dom => dom === currentThirdDom); } // 获取菜单层级标题(适配真实DOM) function getMenuHierarchyTitle(thirdDom) { if (!thirdDom) return '未知课程'; // 找三级标题 const thirdTitle = thirdDom.querySelector('.title')?.textContent.trim() || '未知三级课程'; // 找二级菜单(三级→二级inner-box→二级) const secondInnerBox = thirdDom.closest('.second-level-inner-box'); const secondDom = secondInnerBox?.previousElementSibling; const secondTitle = secondDom?.querySelector('.title-big')?.textContent.trim() || '未知二级菜单'; // 找一级菜单(二级→一级inner-box→一级→menu-item→一级) const firstInnerBox = secondInnerBox?.closest('.first-level-inner-box'); const firstDom = firstInnerBox?.previousElementSibling; const firstTitle = firstDom?.querySelector('.title-big')?.textContent.trim() || '未知一级菜单'; return `${firstTitle} > ${secondTitle} > ${thirdTitle}`; } // ====================== 3. 课程核心逻辑 ====================== // 初始化课程检测 async function initCourseDetection() { if (!globalState.isRunning) return; try { await waitForPageLoad(); // 重新定位当前激活的三级DOM(页面跳转后更新) const activeDom = document.querySelector('.third-level-box.active'); if (activeDom) { globalState.currentActiveThirdDom = activeDom; } // 显示当前课程信息 if (globalState.currentActiveThirdDom) { const currentIndex = getCurrentThirdIndex(globalState.currentActiveThirdDom) + 1; const currentTitle = getMenuHierarchyTitle(globalState.currentActiveThirdDom); updateStatus(`当前课程:${currentTitle} (第${currentIndex}/${globalState.allThirdLevelMenus.length}节)`, '#409eff'); } else { updateStatus('未检测到当前激活的课程', '#faad14'); return; } await processCurrentPage(); } catch (error) { updateStatus(`检测失败:${error.message}`, '#ff4d4f'); console.error('[自动学习] 初始化失败:', error); } } // 处理当前页面(视频/iframe) async function processCurrentPage() { if (!globalState.isRunning || globalState.isProcessing || !globalState.currentActiveThirdDom) return; globalState.isProcessing = true; const currentTitle = getMenuHierarchyTitle(globalState.currentActiveThirdDom); updateStatus(`开始处理:${currentTitle}`, '#409eff'); try { await new Promise(resolve => setTimeout(resolve, 1500)); // 等待页面稳定 // 检测视频 const hasVideo = await checkVideoElement(); if (hasVideo) { await handleVideoPlay(); return; } // 检测iframe const hasIframe = await checkIframeElement(); if (hasIframe) { await handleIframeWait(); return; } // 无内容直接跳转 updateStatus('当前页面无视频/iframe,准备自动跳转', '#666'); setTimeout(jumpToNextCourse, 2000); } catch (error) { updateStatus(`处理失败:${error.message}`, '#ff4d4f'); console.error('[自动学习] 处理失败:', error); setTimeout(jumpToNextCourse, 3000); } finally { globalState.isProcessing = false; } } // 检测视频元素 async function checkVideoElement() { try { await waitForElement('.dplayer-video-wrap', 5000); return !!document.querySelector('.dplayer-video.dplayer-video-current'); } catch (e) { return false; } } // 播放视频(二倍速) async function handleVideoPlay() { updateStatus('检测到视频,2倍速播放中...', '#409eff'); const videoEl = document.querySelector('.dplayer-video.dplayer-video-current'); if (!videoEl) throw new Error('未找到视频元素'); // 播放+二倍速 try { await videoEl.play(); videoEl.playbackRate = 2; } catch (e) { // 模拟点击播放(解决自动播放限制) videoEl.click(); await new Promise(resolve => setTimeout(resolve, 1000)); if (videoEl.paused) { updateStatus('浏览器限制自动播放,请手动点击视频', '#faad14'); return; } videoEl.playbackRate = 2; } // 监听播放结束 videoEl.onended = () => { updateStatus('视频播放完成,准备自动跳转', '#52c41a'); jumpToNextCourse(); }; // 进度更新 const progressInterval = setInterval(() => { if (!globalState.isRunning || videoEl.paused) { clearInterval(progressInterval); return; } const progress = (videoEl.currentTime / videoEl.duration * 100).toFixed(1); updateStatus(`视频播放中(2倍速):${progress}%`, '#409eff'); }, 2000); } // 检测iframe元素 async function checkIframeElement() { try { await waitForElement('.ifrema', 5000); return !!document.querySelector('.ifrema #myframe'); } catch (e) { return false; } } // 处理iframe(25秒等待) async function handleIframeWait() { updateStatus('检测到iframe,等待25秒后自动跳转...', '#409eff'); if (globalState.iframeTimer) clearTimeout(globalState.iframeTimer); // 倒计时显示 let countdown = 25; const countdownInterval = setInterval(() => { if (!globalState.isRunning) return clearInterval(countdownInterval); countdown--; updateStatus(`iframe等待中:剩余${countdown}秒`, '#409eff'); if (countdown <= 0) clearInterval(countdownInterval); }, 1000); // 25秒后跳转 globalState.iframeTimer = setTimeout(() => { clearInterval(countdownInterval); updateStatus('iframe等待完成,准备自动跳转', '#52c41a'); jumpToNextCourse(); }, 25 * 1000); } // 核心跳转逻辑(适配真实DOM+手动/自动通用) async function jumpToNextCourse() { if (!globalState.currentActiveThirdDom || globalState.allThirdLevelMenus.length === 0) { updateStatus('无可用课程可跳转', '#ff4d4f'); return; } // 1. 获取当前索引,计算下一个索引 const currentIndex = getCurrentThirdIndex(globalState.currentActiveThirdDom); const nextIndex = currentIndex + 1; // 2. 最后一课处理 if (nextIndex >= globalState.allThirdLevelMenus.length) { updateStatus('已完成所有课程学习!', '#52c41a'); globalState.isRunning = false; const stopBtn = document.querySelector('button:not([disabled])'); if (stopBtn) stopBtn.disabled = true; const startBtn = document.querySelector('button[disabled]'); if (startBtn) startBtn.disabled = false; const jumpBtn = document.querySelector('button[style*="background: #1890ff"]'); if (jumpBtn) jumpBtn.disabled = true; return; } // 3. 获取下一课DOM(从扁平列表取,100%有效) const nextThirdDom = globalState.allThirdLevelMenus[nextIndex]; // 4. 判断是否需要展开一级菜单(跨一级时) const currentFirstTitle = getMenuHierarchyTitle(globalState.currentActiveThirdDom).split(' > ')[0]; const nextFirstTitle = getMenuHierarchyTitle(nextThirdDom).split(' > ')[0]; if (currentFirstTitle !== nextFirstTitle) { // 找到下一个一级菜单并展开 const nextFirstMenu = globalState.firstLevelMenus.find(menu => menu.title === nextFirstTitle); if (nextFirstMenu) { updateStatus(`当前是【${currentFirstTitle}】最后一课,展开下一级菜单【${nextFirstTitle}】`, '#409eff'); await expandFirstLevelMenu(nextFirstMenu.index); } } // 5. 执行跳转(适配真实DOM的标题元素) const nextTitleEl = nextThirdDom.querySelector('.title'); // 三级菜单的.title元素 const nextTitle = getMenuHierarchyTitle(nextThirdDom); if (nextTitleEl) { updateStatus(`跳转到下一课:${nextTitle} (第${nextIndex+1}/${globalState.allThirdLevelMenus.length}节)`, '#409eff'); nextTitleEl.click(); // 点击三级标题跳转 } else { updateStatus(`跳转:${nextTitle}(无标题元素,直接点击三级DOM)`, '#faad14'); nextThirdDom.click(); // 兜底:点击三级DOM本身 } // 6. 更新当前DOM,等待页面加载后重新检测 globalState.currentActiveThirdDom = nextThirdDom; setTimeout(() => { globalState.isProcessing = false; if (globalState.isRunning) { initCourseDetection(); // 自动模式下继续检测 } }, 2000); } // ====================== 4. 初始化 ====================== function init() { // 创建控制面板(含手动跳转按钮) createControlPanel(); // 初始状态更新 updateStatus('未运行,点击启动按钮开始(支持手动跳转测试)', '#666'); } // 页面加载完成后初始化 if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { document.addEventListener('DOMContentLoaded', init); } })();