// ==UserScript== // @name 国家2026年人工智能刷课脚本 // @namespace http://tampermonkey.net/ // @version 3.3 // @description 修复视频完成后无法自动下一个+完美展开+连续播放+防504 // @author ark // @match *://*/* // @grant GM_addStyle // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==================== 样式定义 ==================== GM_addStyle(` #auto-study-panel { position: fixed; top: 20px; right: 20px; width: 340px; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #ffffff; overflow: hidden; transition: all 0.3s ease; user-select: none; border: 1px solid rgba(255, 255, 255, 0.1); } #auto-study-panel.minimized { width: 60px; height: 60px; border-radius: 50%; } #auto-study-panel.minimized .panel-content { display: none; } #auto-study-panel.minimized .panel-header { height: 60px; padding: 0; justify-content: center; } #auto-study-panel.minimized .panel-title { display: none; } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(255, 255, 255, 0.1); cursor: move; } .panel-title { font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .panel-title::before { content: ''; width: 8px; height: 8px; background: #10b981; border-radius: 50%; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .panel-controls { display: flex; gap: 8px; } .panel-btn { width: 28px; height: 28px; border: none; border-radius: 8px; background: rgba(255, 255, 255, 0.1); color: #ffffff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; font-size: 14px; } .panel-btn:hover { background: rgba(255, 255, 255, 0.2); } .panel-btn.minimize-btn { font-size: 18px; } .panel-content { padding: 20px; } .status-section { margin-bottom: 20px; } .status-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 14px; } .status-label { color: #94a3b8; } .status-value { font-weight: 500; color: #ffffff; } .status-value.running { color: #10b981; } .status-value.paused { color: #f59e0b; } .status-value.stopped { color: #ef4444; } .progress-bar { width: 100%; height: 8px; background: rgba(255, 255, 255, 0.1); border-radius: 4px; overflow: hidden; margin-top: 8px; } .progress-fill { height: 100%; background: linear-gradient(90deg, #10b981, #06b6d4); border-radius: 4px; transition: width 0.3s ease; } .settings-section { margin-bottom: 20px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); } .settings-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #94a3b8; } .setting-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .setting-label { font-size: 13px; color: #cbd5e1; } .setting-input { width: 70px; padding: 6px 10px; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 6px; background: rgba(255, 255, 255, 0.05); color: #ffffff; font-size: 13px; text-align: center; } .setting-input:focus { outline: none; border-color: #10b981; } .action-buttons { display: flex; gap: 10px; margin-bottom: 16px; } .action-btn { flex: 1; padding: 12px 0; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; } .action-btn.expand-btn { background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); color: #ffffff; } .action-btn.expand-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4); } .action-btn.start-btn { background: linear-gradient(135deg, #10b981 0%, #06b6d4 100%); color: #0a0a0a; } .action-btn.start-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4); } .action-btn.pause-btn { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: #0a0a0a; } .action-btn.pause-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4); } .action-btn.stop-btn { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: #ffffff; } .action-btn.stop-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); } .action-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; box-shadow: none !important; } .log-section { margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); } .log-title { font-size: 14px; font-weight: 600; margin-bottom: 10px; color: #94a3b8; display: flex; justify-content: space-between; align-items: center; } .clear-log-btn { font-size: 12px; color: #94a3b8; background: none; border: none; cursor: pointer; } .clear-log-btn:hover { color: #ffffff; } .log-container { height: 140px; overflow-y: auto; background: rgba(0, 0, 0, 0.2); border-radius: 8px; padding: 10px; font-size: 12px; font-family: 'Consolas', monospace; color: #e2e8f0; } .log-container::-webkit-scrollbar { width: 4px; } .log-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 2px; } .log-item { margin-bottom: 4px; line-height: 1.4; } .log-item.info { color: #06b6d4; } .log-item.success { color: #10b981; } .log-item.warning { color: #f59e0b; } .log-item.error { color: #ef4444; } `); // ==================== 全局变量 ==================== let isRunning = false; let isPaused = false; let currentIndex = 0; let totalVideos = 0; let videoItems = []; let pauseResolve = null; let stopRequested = false; let isAllExpanded = false; // 默认配置(已针对反检测优化) const config = { scrollDelayMin: 1200, // 滚动后最小等待时间(ms) scrollDelayMax: 1800, // 滚动后最大等待时间(ms) loadDelayMin: 4000, // 点击后视频最小加载时间(ms) loadDelayMax: 6000, // 点击后视频最大加载时间(ms) closeDelayMin: 3000, // 关闭后最小等待时间(ms) closeDelayMax: 4000, // 关闭后最大等待时间(ms) checkInterval: 3000, // 视频进度检查间隔(ms) maxTimeout: 600, // 单个视频最大超时次数(3秒/次 = 30分钟) progressWaitTime: 30000, // 视频播完后等待系统更新进度的时间(ms) expandDelayMin: 400, // 展开章节最小间隔(ms) expandDelayMax: 800 // 展开章节最大间隔(ms) }; // ==================== UI创建 ==================== function createPanel() { const panel = document.createElement('div'); panel.id = 'auto-study-panel'; panel.innerHTML = `
自动挂课助手
运行状态 已停止
播放进度 0 / 0
参数设置
滚动等待(ms)
加载等待(ms)
关闭等待(ms)
进度等待(ms)
运行日志
`; document.body.appendChild(panel); bindEvents(); makeDraggable(panel); log('info', '✅ 终极挂课脚本 V3.3 已加载完成'); log('info', '💡 已修复视频完成后无法自动下一个的Bug'); } // ==================== 事件绑定 ==================== function bindEvents() { document.getElementById('expand-btn').addEventListener('click', expandAllChapters); document.getElementById('start-btn').addEventListener('click', startAutoStudy); document.getElementById('pause-btn').addEventListener('click', togglePause); document.getElementById('stop-btn').addEventListener('click', stopAutoStudy); document.getElementById('minimize-btn').addEventListener('click', toggleMinimize); document.getElementById('clear-log-btn').addEventListener('click', clearLog); // 配置输入框事件 document.getElementById('scroll-delay').addEventListener('change', (e) => { const value = parseInt(e.target.value) || 1500; config.scrollDelayMin = value - 300; config.scrollDelayMax = value + 300; }); document.getElementById('load-delay').addEventListener('change', (e) => { const value = parseInt(e.target.value) || 5000; config.loadDelayMin = value - 1000; config.loadDelayMax = value + 1000; }); document.getElementById('close-delay').addEventListener('change', (e) => { const value = parseInt(e.target.value) || 3500; config.closeDelayMin = value - 500; config.closeDelayMax = value + 500; }); document.getElementById('progress-wait').addEventListener('change', (e) => { config.progressWaitTime = parseInt(e.target.value) || 30000; }); } // ==================== 拖拽功能 ==================== function makeDraggable(element) { const header = document.getElementById('panel-header'); let isDragging = false; let offsetX, offsetY; header.addEventListener('mousedown', (e) => { if (e.target.closest('.panel-controls')) return; isDragging = true; offsetX = e.clientX - element.offsetLeft; offsetY = e.clientY - element.offsetTop; element.style.zIndex = '999999'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - offsetX; const y = e.clientY - offsetY; element.style.left = `${x}px`; element.style.top = `${y}px`; element.style.right = 'auto'; }); document.addEventListener('mouseup', () => { isDragging = false; }); } // ==================== 核心功能:自动展开所有章节 ==================== async function expandAllChapters() { log('info', '🔍 正在扫描所有章节...'); try { // 第一步:展开所有大章节(li.list-item) const mainChapters = document.querySelectorAll('li.list-item'); log('info', `📚 找到 ${mainChapters.length} 个大章节`); for (let i = 0; i < mainChapters.length; i++) { if (stopRequested) break; const chapter = mainChapters[i]; const leftItem = chapter.querySelector('.left-item'); const icon = leftItem?.querySelector('.icon'); // 通过icon的is-expanded类判断是否已经展开 if (leftItem && icon && !icon.classList.contains('is-expanded')) { log('info', `📖 展开大章节 ${i + 1}/${mainChapters.length}`); safeClick(leftItem); await randomSleep(config.expandDelayMin, config.expandDelayMax); } } // 第二步:展开所有小节(ant-collapse-item) const subChapters = document.querySelectorAll('.ant-collapse-item'); log('info', `📑 找到 ${subChapters.length} 个小节`); for (let i = 0; i < subChapters.length; i++) { if (stopRequested) break; const chapter = subChapters[i]; const header = chapter.querySelector('.ant-collapse-header'); if (header && header.getAttribute('aria-expanded') !== 'true') { log('info', `📖 展开小节 ${i + 1}/${subChapters.length}`); safeClick(header); await randomSleep(config.expandDelayMin, config.expandDelayMax); } } // 等待所有内容渲染完成 await sleep(1500); // 第三步:收集所有可见的视频项 videoItems = Array.from(document.querySelectorAll('.child-item')).filter(item => { return item.offsetParent !== null; }); totalVideos = videoItems.length; if (totalVideos === 0) { log('warning', '⚠️ 未找到任何可见的视频项'); } else { log('success', `🎉 所有章节已成功展开,共找到 ${totalVideos} 个可见视频`); } isAllExpanded = true; updateUI(); } catch (error) { log('error', `❌ 展开章节时出错: ${error.message}`); console.error(error); } } // ==================== 核心功能:自动学习 ==================== async function startAutoStudy() { if (isRunning) return; try { // 只有在未展开过的情况下才自动展开 if (!isAllExpanded) { log('info', '🔄 检测到未展开章节,正在自动展开...'); await expandAllChapters(); } if (totalVideos === 0) { log('error', '❌ 没有找到任何视频,请先点击"展开全部"按钮!'); return; } isRunning = true; isPaused = false; stopRequested = false; currentIndex = 0; updateUI(); log('info', `🚀 开始自动学习,共 ${totalVideos} 个视频`); for (let i = 0; i < totalVideos; i++) { if (stopRequested) break; currentIndex = i + 1; updateUI(); const currentItem = videoItems[i]; // 检查进度,已完成则跳过 const processElement = currentItem.querySelector('.process'); const processText = processElement ? processElement.innerText.trim() : ''; if (processText.includes('100%') || processText.includes('已完成')) { log('info', `⏭️ 第 ${currentIndex} 个视频进度 [${processText}],已跳过`); continue; } // 等待暂停 if (isPaused) { log('info', '⏸️ 已暂停,等待继续...'); await new Promise(resolve => { pauseResolve = resolve; }); if (stopRequested) break; log('info', '▶️ 继续学习'); } // 滚动到当前视频 log('info', `▶️ 正在打开第 ${currentIndex} 个视频`); currentItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); await randomSleep(config.scrollDelayMin, config.scrollDelayMax); // 安全点击打开视频 safeClick(currentItem); await randomSleep(config.loadDelayMin, config.loadDelayMax); // 验证视频弹窗是否真的打开了 const videoEl = document.querySelector('video'); if (!videoEl) { log('warning', `⚠️ 第 ${currentIndex} 个视频弹窗未打开,重试一次...`); safeClick(currentItem); await randomSleep(config.loadDelayMin, config.loadDelayMax); } // 尝试点击播放按钮 const playBtn = document.querySelector('.video-play'); if (playBtn) { log('info', '🎬 点击播放按钮'); safeClick(playBtn); await sleep(1000); } // 等待视频播放完成 log('info', '⏳ 正在监测视频播放进度...'); await waitForProcessToEnd(currentItem, currentIndex); // 关闭弹窗 log('info', '🔙 正在关闭视频弹窗...'); forceCloseModal(); await randomSleep(config.closeDelayMin, config.closeDelayMax); } if (stopRequested) { log('info', '⏹️ 用户手动停止了学习'); } else { log('success', '🎉 恭喜!所有视频已全部学习完成!'); } } catch (error) { log('error', `❌ 学习过程中出错: ${error.message}`); console.error(error); } isRunning = false; isPaused = false; updateUI(); } // ==================== 控制功能 ==================== function togglePause() { if (!isRunning) return; isPaused = !isPaused; updateUI(); if (!isPaused && pauseResolve) { pauseResolve(); pauseResolve = null; } } function stopAutoStudy() { if (!isRunning) return; stopRequested = true; if (isPaused && pauseResolve) { pauseResolve(); } } function toggleMinimize() { const panel = document.getElementById('auto-study-panel'); panel.classList.toggle('minimized'); } // ==================== 辅助函数 ==================== function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 随机延时(反检测核心) function randomSleep(min, max) { const ms = Math.floor(Math.random() * (max - min + 1)) + min; return sleep(ms); } // 安全点击函数 function safeClick(element) { if (!element) return; try { // 方法1:原生click()方法(最稳定) element.click(); // 方法2:备用事件触发 setTimeout(() => { const event = document.createEvent('MouseEvents'); event.initMouseEvent( 'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ); element.dispatchEvent(event); }, 50); } catch (error) { log('warning', `⚠️ 点击时出现小问题,已自动处理`); console.error(error); } } // 强制关闭弹窗(多重保障) function forceCloseModal() { try { // 1. 首选:antd 关闭图标 const closeIcon = document.querySelector('.anticon-close'); if (closeIcon && closeIcon.offsetParent !== null) { log('info', '🎯 点击关闭图标'); safeClick(closeIcon); return; } // 2. 备选:视频弹窗关闭按钮 const closeBtn = document.querySelector('.video-modal-close'); if (closeBtn && closeBtn.offsetParent !== null) { log('info', '🎯 点击关闭按钮'); safeClick(closeBtn); return; } // 3. 备选:遮罩层点击 const mask = document.querySelector('.video-modal-mask'); if (mask) { log('info', '🎯 点击遮罩层'); safeClick(mask); return; } // 4. 终极:ESC 键 log('info', '🎯 按下 ESC 键'); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true })); } catch (error) { log('warning', `⚠️ 关闭弹窗时出现小问题`); console.error(error); } } // ✅ 完全重写:监听进度与防卡死逻辑(修复核心Bug) function waitForProcessToEnd(itemNode, index) { return new Promise((resolve) => { let timeoutCount = 0; let mainInterval = null; let progressTimeout = null; let hasVideoEnded = false; // 清理所有定时器的函数 function cleanupAndResolve() { if (mainInterval) clearInterval(mainInterval); if (progressTimeout) clearTimeout(progressTimeout); resolve(); } // 主检查循环 mainInterval = setInterval(() => { if (stopRequested) { cleanupAndResolve(); return; } if (isPaused) return; timeoutCount++; try { // 【条件1】系统进度达到100%(最可靠) const processNode = itemNode.querySelector('.process'); if (processNode) { const processText = processNode.innerText.trim(); if (processText.includes('100%') || processText.includes('已完成')) { log('success', `✅ 第 ${index} 个视频系统进度已更新为 ${processText}`); cleanupAndResolve(); return; } } // 【条件2】视频本体播放完毕 const videoEl = document.querySelector('video'); if (videoEl && !hasVideoEnded) { videoEl.muted = true; // 强制静音 // 自动恢复意外暂停 if (videoEl.paused && videoEl.currentTime < videoEl.duration) { videoEl.play().catch(() => {}); } // 视频播放完成 if (videoEl.ended || (videoEl.duration > 0 && videoEl.duration - videoEl.currentTime <= 1)) { hasVideoEnded = true; log('warning', `⚠️ 第 ${index} 个视频已播完,等待系统更新进度(最多${config.progressWaitTime/1000}秒)...`); // 开始等待系统更新进度 progressTimeout = setTimeout(() => { log('warning', `⏰ 等待进度更新超时,强制进入下一个视频`); cleanupAndResolve(); }, config.progressWaitTime); } } // 【条件3】全局超时保护(防504假死) if (timeoutCount > config.maxTimeout) { log('warning', `🚨 第 ${index} 个视频监测超时(可能服务器504),强行跳过`); cleanupAndResolve(); } } catch (error) { log('warning', `⚠️ 检查进度时出现小问题`); console.error(error); } }, config.checkInterval); }); } // 日志功能 function log(type, message) { try { const container = document.getElementById('log-container'); const item = document.createElement('div'); item.className = `log-item ${type}`; item.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; container.appendChild(item); container.scrollTop = container.scrollHeight; } catch (error) { console.log(message); } } function clearLog() { document.getElementById('log-container').innerHTML = ''; } // 更新UI状态 function updateUI() { try { const statusText = document.getElementById('status-text'); const progressText = document.getElementById('progress-text'); const progressFill = document.getElementById('progress-fill'); const expandBtn = document.getElementById('expand-btn'); const startBtn = document.getElementById('start-btn'); const pauseBtn = document.getElementById('pause-btn'); const stopBtn = document.getElementById('stop-btn'); // 更新状态 if (isRunning) { if (isPaused) { statusText.textContent = '已暂停'; statusText.className = 'status-value paused'; pauseBtn.textContent = '继续'; } else { statusText.textContent = '学习中'; statusText.className = 'status-value running'; pauseBtn.textContent = '暂停'; } } else { statusText.textContent = '已停止'; statusText.className = 'status-value stopped'; } // 更新进度 progressText.textContent = `${currentIndex} / ${totalVideos}`; const progress = totalVideos > 0 ? (currentIndex / totalVideos) * 100 : 0; progressFill.style.width = `${progress}%`; // 更新按钮状态 expandBtn.disabled = isRunning; startBtn.disabled = isRunning; pauseBtn.disabled = !isRunning; stopBtn.disabled = !isRunning; } catch (error) { console.error(error); } } // ==================== 初始化 ==================== if (document.readyState === 'complete' || document.readyState === 'interactive') { createPanel(); } else { window.addEventListener('DOMContentLoaded', createPanel); } })();