// ==UserScript== // @name 人工智能2026观看脚本V1.6 -仅供学习 // @namespace http://tampermonkey.net/ // @version 1.6 // @description 优化Bug // @author 诺亚 // @match *://*.smartedu.cn/* // @match https://higher.smartedu.cn/* // @match https://service.icourses.cn/* // @grant GM_addStyle // @grant GM_openInTab // @run-at document-end // ==/UserScript== (function() { 'use strict'; try { const blockEvents = ['visibilitychange', 'webkitvisibilitychange', 'blur', 'pagehide', 'focusout', 'freeze', 'resume']; blockEvents.forEach(eventName => { window.addEventListener(eventName, e => e.stopImmediatePropagation(), true); document.addEventListener(eventName, e => e.stopImmediatePropagation(), true); }); Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true }); Object.defineProperty(document, 'hidden', { get: () => false, configurable: true }); Object.defineProperty(window, 'hasFocus', { value: () => true, configurable: true }); // 拦截 document.hasFocus() 调用 if (document.hasFocus) { Object.defineProperty(document, 'hasFocus', { value: () => true, configurable: true }); } // 拦截页面可见性API的媒体查询 try { const originalMatchMedia = window.matchMedia; window.matchMedia = function(query) { if (query && query.includes('prefers-reduced-motion')) { return originalMatchMedia.call(this, query); } if (query && (query.includes('display-mode') || query.includes('prefers-color-scheme'))) { return originalMatchMedia.call(this, query); } const result = originalMatchMedia.call(this, query); if (query && query.includes('prefers-reduced-transparency')) { return result; } // 对可见性相关查询返回伪造结果 return { matches: false, media: query, addListener: function(){}, removeListener: function(){}, addEventListener: function(){}, removeEventListener: function(){}, dispatchEvent: function(){ return true; }, onchange: null }; }; } catch(e) {} console.log("🚀 终极挂课脚本 V1.5 已启动!切屏/最小化/后台冻结检测已完全拦截"); } catch (e) { console.warn("⚠️ 部分防切屏功能受限,但核心播放不受影响"); } 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: 99999999; 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; flex-wrap: wrap; } .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.feedback-btn { background: linear-gradient(135deg, #ec4899 0%, #db2777 100%); color: #ffffff; flex: 1 1 100%; } .action-btn.feedback-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(236, 72, 153, 0.4); } .action-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; box-shadow: none !important; } .recommend-section { margin-bottom: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); } .recommend-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #94a3b8; } .recommend-buttons { display: flex; flex-direction: column; gap: 8px; } .recommend-btn { width: 100%; padding: 10px 0; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; text-align: center; } .recommend-btn.general { background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); color: #ffffff; } .recommend-btn.ai-x { background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: #ffffff; } .recommend-btn.llm { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: #0a0a0a; } .recommend-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3); } .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; } .platform-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 11px; font-weight: 600; margin-left: 6px; background: #10b981; color: #0a0a0a; } .detect-section { margin-bottom: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); } .detect-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #94a3b8; display: flex; justify-content: space-between; align-items: center; } .detect-summary { display: flex; gap: 10px; margin-bottom: 12px; } .detect-stat { flex: 1; padding: 8px; border-radius: 8px; text-align: center; font-size: 12px; } .detect-stat.completed { background: rgba(16, 185, 129, 0.15); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); } .detect-stat.incomplete { background: rgba(239, 68, 68, 0.15); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.3); } .detect-stat-num { font-size: 20px; font-weight: 700; display: block; margin-bottom: 2px; } .detect-stat-label { font-size: 11px; opacity: 0.8; } .detect-list { max-height: 200px; overflow-y: auto; background: rgba(0, 0, 0, 0.2); border-radius: 8px; padding: 8px; margin-bottom: 12px; } .detect-list::-webkit-scrollbar { width: 4px; } .detect-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 2px; } .detect-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; border-radius: 6px; margin-bottom: 4px; font-size: 12px; transition: background 0.2s; } .detect-item:hover { background: rgba(255, 255, 255, 0.05); } .detect-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e2e8f0; margin-right: 8px; } .detect-item-progress { font-size: 11px; font-weight: 600; white-space: nowrap; padding: 2px 8px; border-radius: 4px; } .detect-item-progress.done { background: rgba(16, 185, 129, 0.2); color: #10b981; } .detect-item-progress.partial { background: rgba(245, 158, 11, 0.2); color: #f59e0b; } .detect-item-progress.none { background: rgba(239, 68, 68, 0.2); color: #ef4444; } .detect-btn-row { display: flex; gap: 8px; } .action-btn.detect-btn { background: linear-gradient(135deg, #06b6d4 0%, #0284c7 100%); color: #ffffff; } .action-btn.detect-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(6, 182, 212, 0.4); } .action-btn.continue-incomplete-btn { background: linear-gradient(135deg, #f97316 0%, #ea580c 100%); color: #ffffff; } .action-btn.continue-incomplete-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4); } .detect-empty { text-align: center; color: #64748b; font-size: 12px; padding: 16px 0; } `); let isRunning = false; let isPaused = false; let currentIndex = 0; let totalVideos = 0; let videoItems = []; let pauseResolve = null; let stopRequested = false; let isAllExpanded = false; let isSmartEduPlatform = false; const config = { scrollDelay: 1500, loadDelay: 5000, closeDelay: 3000, checkInterval: 2000, maxTimeout: 900 }; const recommendCourses = [ { name: "人工智能应用导论(通识课)", url: "https://higher.smartedu.cn/course/agc3/69ba53600976b58e126c4d8e?type=training", class: "general" }, { name: "自动驾驶场景设计(AI+X)", url: "https://higher.smartedu.cn/course/agc3/69b56a1ff74ce762ce352749?type=training", class: "ai-x" }, { name: "DeepSeek大模型前沿进展", url: "https://higher.smartedu.cn/course/lmc/67d7cf98625fca5f6cf9076e", class: "llm" } ]; function createPanel() { if (document.getElementById('auto-study-panel')) { console.log("⚠️ 面板已存在,跳过创建"); return; } const panel = document.createElement('div'); panel.id = 'auto-study-panel'; panel.innerHTML = `
自动挂课助手 V5.4.1 ${isSmartEduPlatform ? '诺亚NOYA' : ''}
运行状态 已停止
播放进度 0 / 0
参数设置(仅通用模式生效)
滚动等待(ms)
加载等待(ms)
关闭等待(ms)
🔥 推荐课程快速跳转
${recommendCourses.map(course => `` ).join('')}
运行日志
`; document.body.appendChild(panel); bindEvents(); makeDraggable(panel); log('info', '✅ 终极挂课脚本 V5.4.1 已加载完成'); log('success', '🛡️ 切屏/最小化检测已完全拦截,后台挂机零中断'); if (isSmartEduPlatform) { log('success', '🎯 检测到高等教育智慧教育平台,已强制启用figure标签专属脚本'); log('info', 'ℹ️ 完全按照你提供的脚本逻辑执行,无需展开章节'); log('info', '💡 已完成(100%)的视频自动跳过,中间进度从上次位置续播'); } else { log('info', 'ℹ️ 通用模式已启用,请先点击"展开全部"再开始学习'); log('info', '💡 已完成(100%)的视频自动跳过,中间进度从上次位置续播'); } console.log("✅ UI面板创建成功,开始按钮已绑定事件"); } function bindEvents() { const startBtn = document.getElementById('start-btn'); const newStartBtn = startBtn.cloneNode(true); startBtn.parentNode.replaceChild(newStartBtn, startBtn); newStartBtn.addEventListener('click', () => { console.log("🔘 开始学习按钮被点击!"); log('info', '🔘 开始学习按钮被点击'); startAutoStudy(); }); document.getElementById('expand-btn').addEventListener('click', expandAllChapters); 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('feedback-btn').addEventListener('click', () => { GM_openInTab('tencent://message/?uin=723167066&Site=&Menu=yes', { active: true }); log('info', '🔗 正在打开QQ反馈窗口'); }); document.querySelectorAll('.recommend-btn').forEach(btn => { btn.addEventListener('click', () => { const url = btn.getAttribute('data-url'); GM_openInTab(url, { active: true }); log('info', `🔗 正在打开推荐课程: ${btn.textContent}`); }); }); document.getElementById('scroll-delay').addEventListener('change', (e) => { if (!isSmartEduPlatform) config.scrollDelay = parseInt(e.target.value) || 1500; }); document.getElementById('load-delay').addEventListener('change', (e) => { if (!isSmartEduPlatform) config.loadDelay = parseInt(e.target.value) || 5000; }); document.getElementById('close-delay').addEventListener('change', (e) => { if (!isSmartEduPlatform) config.closeDelay = parseInt(e.target.value) || 3000; }); document.getElementById('detect-btn').addEventListener('click', detectCourseProgress); document.getElementById('continue-incomplete-btn').addEventListener('click', continueIncomplete); document.getElementById('detect-close-btn').addEventListener('click', () => { document.getElementById('detect-section').style.display = 'none'; }); console.log("✅ 所有事件绑定完成"); } 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 = '99999999'; }); 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() { if (isSmartEduPlatform) { log('warning', '⚠️ 智慧教育平台专属模式无需展开章节'); return; } log('info', '🔍 正在扫描所有章节...'); try { 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'); if (leftItem && icon && !icon.classList.contains('is-expanded')) { log('info', `📖 展开大章节 ${i + 1}/${mainChapters.length}`); safeClick(leftItem); await randomSleep(400, 800); } } 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(400, 800); } } await sleep(1500); videoItems = Array.from(document.querySelectorAll('.name, .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); } } // 使用Web Worker保持后台定时器精度(防止浏览器降频setInterval) let keepAliveWorker = null; function startKeepAliveWorker() { try { const workerCode = ` let intervalId = null; self.onmessage = function(e) { if (e.data === 'start') { intervalId = setInterval(() => { self.postMessage('tick'); }, 1000); } else if (e.data === 'stop') { if (intervalId) clearInterval(intervalId); self.close(); } }; `; const blob = new Blob([workerCode], { type: 'application/javascript' }); keepAliveWorker = new Worker(URL.createObjectURL(blob)); keepAliveWorker.onmessage = function(e) { if (e.data === 'tick') { // Worker每秒发送tick,保持主线程定时器活跃 } }; keepAliveWorker.postMessage('start'); console.log("🛡️ 后台保活Worker已启动,防止定时器降频"); } catch(e) { console.warn("⚠️ Web Worker启动失败,后台定时器可能被降频:", e); } } function stopKeepAliveWorker() { if (keepAliveWorker) { keepAliveWorker.postMessage('stop'); keepAliveWorker = null; } } async function startAutoStudy() { console.log("🚀 startAutoStudy函数被调用"); if (isRunning) { log('warning', '⚠️ 脚本已经在运行中'); console.log("⚠️ 脚本已经在运行中,isRunning =", isRunning); return; } startKeepAliveWorker(); try { if (isSmartEduPlatform) { await startSmartEduExclusiveAutoStudy(); } else { await startIntegratedAutoStudy(); } } catch (error) { log('error', `❌ 学习过程中出错: ${error.message}`); console.error("❌ 学习过程中出错:", error); } isRunning = false; isPaused = false; stopKeepAliveWorker(); updateUI(); console.log("🏁 学习流程结束,isRunning已重置为false"); } // 存储检测结果 let detectedItems = []; async function detectCourseProgress() { if (isRunning) { log('warning', '⚠️ 脚本运行中,请先停止再检测'); return; } log('info', '🔍 正在扫描课程进度...'); detectedItems = []; if (isSmartEduPlatform) { // 智慧教育平台:兼容多种元素结构扫描视频 let foundItems = null; for (let retry = 0; retry < 3; retry++) { foundItems = findAllVideoItems(); if (foundItems.length > 0) break; log('warning', `⚠️ 第 ${retry + 1} 次未找到视频,等待2秒后重试...`); await sleep(2000); } if (!foundItems || foundItems.length === 0) { log('error', '❌ 未找到视频,请确认页面已完全加载'); return; } foundItems.forEach((item, idx) => { const name = getItemName(item, idx); const progress = getItemProgressText(item); const isComplete = checkSmartEduItemCompleted(item); detectedItems.push({ name, progress, isComplete, element: item, index: idx }); }); } else { // 通用模式:需要先展开 if (!isAllExpanded) { log('info', '🔄 先展开章节...'); await expandAllChapters(); } videoItems.forEach((item, idx) => { const processElement = item.querySelector('.process'); const processText = processElement ? (processElement.innerText || '').trim() : ''; const parentText = item.parentElement ? item.parentElement.innerText : item.innerText; const name = getItemName(item, idx); const progress = processText || getItemProgressText(item); // 只有"已观看100%"或"已完成"才算完成 const isComplete = processText.includes('已观看100%') || processText.includes('已完成') || (processText === '' && (parentText.includes('已观看100%') || parentText.includes('已完成'))); detectedItems.push({ name, progress, isComplete, element: item, index: idx }); }); } const completedCount = detectedItems.filter(i => i.isComplete).length; const incompleteCount = detectedItems.length - completedCount; // 显示检测区域 const detectSection = document.getElementById('detect-section'); detectSection.style.display = 'block'; document.getElementById('detect-completed-num').textContent = completedCount; document.getElementById('detect-incomplete-num').textContent = incompleteCount; // 渲染列表 const listEl = document.getElementById('detect-list'); if (detectedItems.length === 0) { listEl.innerHTML = '
未找到视频
'; } else { listEl.innerHTML = detectedItems.map((item, idx) => { const statusClass = item.isComplete ? 'done' : (item.progress ? 'partial' : 'none'); const statusText = item.isComplete ? '✅ 已完成' : (item.progress || '❌ 未观看'); return `
${idx + 1}. ${item.name} ${statusText}
`; }).join(''); } // 启用/禁用"续播未完成"按钮 document.getElementById('continue-incomplete-btn').disabled = incompleteCount === 0; if (incompleteCount === 0) { log('success', `🎉 检测完成:共 ${detectedItems.length} 个视频,全部已完成!`); } else { log('success', `📊 检测完成:共 ${detectedItems.length} 个视频,已完成 ${completedCount} 个,未完成 ${incompleteCount} 个`); log('info', `💡 点击"续播未完成"按钮只播放未完成的视频`); } } async function continueIncomplete() { if (isRunning) { log('warning', '⚠️ 脚本运行中,请先停止'); return; } const incompleteItems = detectedItems.filter(i => !i.isComplete); if (incompleteItems.length === 0) { log('info', '✅ 没有未完成的视频'); return; } log('info', `▶️ 开始续播 ${incompleteItems.length} 个未完成视频`); isRunning = true; isPaused = false; stopRequested = false; startKeepAliveWorker(); updateUI(); try { if (isSmartEduPlatform) { await continueSmartEduIncomplete(incompleteItems); } else { await continueGenericIncomplete(incompleteItems); } } catch (error) { log('error', `❌ 续播出错: ${error.message}`); console.error(error); } isRunning = false; isPaused = false; stopKeepAliveWorker(); updateUI(); } async function continueSmartEduIncomplete(items) { totalVideos = items.length; currentIndex = 0; for (let i = 0; i < items.length; i++) { if (stopRequested) break; currentIndex = i + 1; updateUI(); const { element, name } = items[i]; if (isPaused) { log('info', '⏸️ 已暂停,等待继续...'); await new Promise(resolve => { pauseResolve = resolve; }); if (stopRequested) break; log('info', '▶️ 继续学习'); } log('info', `▶️ [续播] ${i + 1}/${items.length} - ${name}`); element.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(1500); safeClick(element); await sleep(4000); await forcePlayVideo(); await watchSmartEduVideoUntilEnd(i + 1); // 关闭弹窗 let closeBtn = document.querySelector('button[class*="bg-white"][class*="h-10"]'); if (!closeBtn) closeBtn = document.querySelector('button[class*="close"]'); if (!closeBtn) closeBtn = document.querySelector('.anticon-close'); if (!closeBtn) closeBtn = document.querySelector('.video-modal-close'); if (closeBtn) { safeClick(closeBtn); } else { document.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Escape', 'bubbles': true})); document.dispatchEvent(new KeyboardEvent('keyup', {'key': 'Escape', 'bubbles': true})); } await sleep(3000); } if (stopRequested) { log('info', '⏹️ 用户手动停止了续播'); } else { log('success', '🎉 所有未完成视频已续播完毕!'); } } async function continueGenericIncomplete(items) { totalVideos = items.length; currentIndex = 0; for (let i = 0; i < items.length; i++) { if (stopRequested) break; currentIndex = i + 1; updateUI(); const { element, name } = items[i]; if (isPaused) { log('info', '⏸️ 已暂停,等待继续...'); await new Promise(resolve => { pauseResolve = resolve; }); if (stopRequested) break; log('info', '▶️ 继续学习'); } log('info', `▶️ [续播] ${i + 1}/${items.length} - ${name}`); element.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(config.scrollDelay); safeClick(element); await sleep(config.loadDelay); await forcePlayVideo(); await waitForProcessToEnd(element, i + 1); forceCloseModal(); await sleep(config.closeDelay); } if (stopRequested) { log('info', '⏹️ 用户手动停止了续播'); } else { log('success', '🎉 所有未完成视频已续播完毕!'); } } async function startSmartEduExclusiveAutoStudy() { isRunning = true; isPaused = false; stopRequested = false; currentIndex = 0; updateUI(); log('info', '🚀 启动高等教育智慧教育平台专属自动学习'); console.log("🚀 启动智慧教育平台专属自动学习"); let videoItems = null; for (let retry = 0; retry < 3; retry++) { videoItems = findAllVideoItems(); if (videoItems.length > 0) break; log('warning', `⚠️ 第 ${retry + 1} 次未找到视频,等待2秒后重试...`); console.log(`⚠️ 第 ${retry + 1} 次未找到视频,等待2秒后重试...`); await sleep(2000); } if (!videoItems || videoItems.length === 0) { log('error', '❌ 经过3次重试仍未找到视频封面,请确认:1. 页面是否完全加载 2. 是否在课程播放页面'); console.error("❌ 经过3次重试仍未找到视频封面"); isRunning = false; updateUI(); return; } totalVideos = videoItems.length; let skippedCount = 0; log('info', `共识别到 ${totalVideos} 个视频,准备开始按顺序播放...`); console.log(`共识别到 ${videoItems.length} 个视频,准备开始无脑按顺序播放...`); updateUI(); for (let i = 0; i < videoItems.length; i++) { if (stopRequested) break; currentIndex = i + 1; updateUI(); const currentItem = videoItems[i]; // 检查视频是否已完成 const isCompleted = checkSmartEduItemCompleted(currentItem); if (isCompleted) { skippedCount++; const progress = getItemProgressText(currentItem); log('info', `⏭️ 第 ${i + 1} 个视频 [${progress || '100%'}] 已完成,自动跳过`); continue; } // 识别中间进度 const progress = getItemProgressText(currentItem); if (progress && !progress.includes('100%') && !progress.includes('已完成')) { log('info', `📖 第 ${i + 1} 个视频进度 [${progress}],从上次位置继续播放`); } else { log('info', `📖 第 ${i + 1} 个视频未观看,开始播放`); } if (isPaused) { log('info', '⏸️ 已暂停,等待继续...'); await new Promise(resolve => { pauseResolve = resolve; }); if (stopRequested) break; log('info', '▶️ 继续学习'); } log('info', `▶️ 正在打开第 ${currentIndex} 个视频...`); console.log(`▶️ 正在打开第 ${i + 1} 个视频...`); currentItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(1500); safeClick(currentItem); log('info', "⏳ 等待弹窗和视频加载..."); console.log("⏳ 等待 4 秒钟让弹窗和播放器完全渲染..."); await sleep(4000); log('info', '🎬 正在激活视频播放...'); await forcePlayVideo(); log('info', "⏳ 正在等待视频播放完毕,请挂机..."); console.log("⏳ 正在等待视频播放完毕,请挂机..."); await watchSmartEduVideoUntilEnd(i + 1); log('info', `✅ 第 ${currentIndex} 个视频结束,尝试关闭弹窗...`); console.log(`✅ 第 ${i + 1} 个视频结束,尝试关闭弹窗...`); let closeBtn = document.querySelector('button[class*="bg-white"][class*="h-10"]'); if (!closeBtn) closeBtn = document.querySelector('button[class*="close"]'); if (!closeBtn) closeBtn = document.querySelector('.anticon-close'); if (!closeBtn) closeBtn = document.querySelector('.video-modal-close'); if (closeBtn) { log('info', "🎯 找到关闭按钮,点击关闭"); console.log("找到关闭按钮,点击关闭..."); safeClick(closeBtn); } else { log('info', "🎯 未找到任何关闭按钮,尝试按 ESC 关闭..."); console.log("未找到任何关闭按钮,尝试按 ESC 关闭..."); document.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Escape', 'bubbles': true})); document.dispatchEvent(new KeyboardEvent('keyup', {'key': 'Escape', 'bubbles': true})); } await sleep(3000); } if (stopRequested) { log('info', '⏹️ 用户手动停止了学习'); } else { if (skippedCount > 0) { log('success', `🎉 视频轮询完毕!其中跳过已完成 ${skippedCount} 个,实际播放 ${totalVideos - skippedCount} 个`); } else { log('success', '🎉 所有界面的视频已全部轮询播放完毕!'); } console.log("🎉 所有界面的视频已全部轮询播放完毕!"); } } function checkSmartEduItemCompleted(item) { try { // 优先检查 .process 元素,这是最准确的进度标识 const processEl = item.querySelector('.process'); if (processEl) { const processText = (processEl.innerText || '').trim(); // 只有"已观看100%"才算完成 if (processText === '已观看100%' || processText.includes('已观看100%') || processText.includes('已完成')) { return true; } // 其他任何进度(已观看10%、已观看50%等)都不算完成 return false; } // 兜底:检查整体文本 const allText = item.innerText || ''; if (allText.includes('已观看100%') || allText.includes('已完成')) { return true; } } catch (e) { console.warn("检查完成状态出错:", e); } return false; } // 获取视频的观看进度文本(用于日志展示) function getItemProgressText(item) { try { // 优先从 .process 元素获取 const processEl = item.querySelector('.process'); if (processEl) { const processText = (processEl.innerText || '').trim(); if (processText) return processText; } const allText = item.innerText || ''; // 尝试匹配"已观看XX%"格式 const match = allText.match(/已观看\s*\d+%/); if (match) return match[0]; // 尝试匹配"XX%"格式 const pctMatch = allText.match(/\d+%/); if (pctMatch) return pctMatch[0]; if (allText.includes('已完成')) return '已完成'; } catch (e) {} return ''; } // 智慧教育平台:获取视频名称 function getItemName(item, idx) { try { // 优先从 .tag-txt 获取(child-item 结构) const tagTxt = item.querySelector('.tag-txt'); if (tagTxt) { const title = tagTxt.getAttribute('title') || tagTxt.innerText; if (title) return title.trim(); } // 尝试 figcaption const figcaption = item.querySelector('figcaption'); if (figcaption) return (figcaption.innerText || '').trim(); // 尝试 .title const titleEl = item.querySelector('.title'); if (titleEl) return (titleEl.innerText || '').trim(); } catch (e) {} return `视频 ${idx + 1}`; } // 智慧教育平台:查找所有视频项(兼容多种元素结构) function findAllVideoItems() { // 优先查找 .child-item(课程列表页的视频项) let items = document.querySelectorAll('.child-item'); if (items.length > 0) { // 过滤出包含视频标签的项 const videoItems = Array.from(items).filter(item => { const tagEl = item.querySelector('.tag-video, .tag'); return tagEl !== null; }); if (videoItems.length > 0) return videoItems; } // 其次查找 figure 元素(封面卡片模式) items = document.querySelectorAll('figure[class*="cursor-pointer"]'); if (items.length > 0) return Array.from(items); // 兜底查找 .name 元素 items = document.querySelectorAll('.name'); if (items.length > 0) return Array.from(items); return []; } async function startIntegratedAutoStudy() { isRunning = true; isPaused = false; stopRequested = false; currentIndex = 0; updateUI(); console.log("🚀 终极挂课脚本 V5.4.1 通用模式已启动!"); if (!isAllExpanded) { log('info', '🔄 检测到未展开章节,正在自动展开...'); await expandAllChapters(); } if (totalVideos === 0) { log('error', '❌ 没有找到任何视频,请先点击"展开全部"按钮!'); isRunning = false; updateUI(); return; } 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() : ''; // 只有"已观看100%"或"已完成"才算完成,其他进度(已观看10%等)不算完成 const isComplete = processText.includes('已观看100%') || processText.includes('已完成'); if (isComplete) { log('info', `⏭️ 第 ${i + 1} 个视频进度为 [${processText}],自动跳过`); continue; } // 识别中间进度,从上次位置继续 if (processText && processText.includes('%') && !processText.includes('100%')) { log('info', `📖 第 ${i + 1} 个视频进度 [${processText}],从上次位置继续播放`); } if (isPaused) { log('info', '⏸️ 已暂停,等待继续...'); await new Promise(resolve => { pauseResolve = resolve; }); if (stopRequested) break; log('info', '▶️ 继续学习'); } log('info', `▶️ 正在滚动并打开第 ${i + 1} 个未完成的视频...`); currentItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(config.scrollDelay); safeClick(currentItem); await sleep(config.loadDelay); log('info', "🎬 正在激活视频播放..."); await forcePlayVideo(); log('info', "⏳ 正在监测视频播放状态..."); await waitForProcessToEnd(currentItem, i + 1); log('info', `☑️ 第 ${i + 1} 个视频流程结束,尝试强制关闭弹窗...`); forceCloseModal(); await sleep(config.closeDelay); } if (stopRequested) { log('info', '⏹️ 用户手动停止了学习'); } else { log('success', '🎉 太棒了,所有任务脚本运行结束!'); } } async function forcePlayVideo() { const video = document.querySelector('video'); if (video) { video.muted = true; // 不重置currentTime,从上次位置继续播放 const savedTime = video.currentTime; await sleep(500); // 确保没有因加载重置了进度 if (savedTime > 0 && video.currentTime < savedTime) { log('info', `⏩ 恢复到上次播放位置 ${formatTime(savedTime)}`); video.currentTime = savedTime; } } const targets = [ document.querySelector('.video-play-img img'), document.querySelector('.video-play-img'), document.querySelector('.video-play'), document.querySelector('.vjs-big-play-button'), document.querySelector('button[class*="play"]'), video ]; for (let target of targets) { if (target && target.offsetParent !== null) { log('info', `👉 尝试物理点击播放按钮...`); safeClick(target); await sleep(800); if (video && !video.paused) { log('success', '✅ 播放成功激活!'); return; } } } if (video && video.paused) { log('info', '👉 物理点击无效,强制调用底层播放API...'); video.play().catch((err) => { log('warning', `⚠️ 底层API被拦截: ${err.message},将继续轮询恢复`); console.log("底层API被拦截:", err); }); } } 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 formatTime(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; return `${m}:${String(s).padStart(2, '0')}`; } function randomSleep(min, max) { const ms = Math.floor(Math.random() * (max - min + 1)) + min; return sleep(ms); } function safeClick(element) { if (!element) return; try { const realClick = new MouseEvent('click', { bubbles: true, cancelable: true, clientX: element.getBoundingClientRect().left + element.offsetWidth / 2, clientY: element.getBoundingClientRect().top + element.offsetHeight / 2 }); element.dispatchEvent(realClick); console.log("✅ 安全点击成功:", element); } catch (error) { log('warning', `⚠️ 点击时出现小问题,已自动处理`); console.error("点击错误:", error); try { element.click(); } catch (e) {} } } function forceCloseModal() { const realClick = new MouseEvent('click', { bubbles: true, cancelable: true }); const spanIcon = document.querySelector('.anticon-close'); if (spanIcon && spanIcon.offsetParent !== null) { log('info', "🎯 使用真实鼠标事件点击了关闭图标!"); spanIcon.dispatchEvent(realClick); return; } const divContainer = document.querySelector('.video-modal-close'); if (divContainer && divContainer.offsetParent !== null) { log('info', "🎯 使用真实鼠标事件点击了关闭容器!"); divContainer.dispatchEvent(realClick); return; } const modalMask = document.querySelector('.video-modal-mask'); if (modalMask) { log('info', "🎯 使用真实鼠标事件点击了遮罩层!"); modalMask.dispatchEvent(realClick); return; } log('info', "🎯 尝试模拟按 ESC 键!"); document.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Escape', 'bubbles': true})); document.dispatchEvent(new KeyboardEvent('keyup', {'key': 'Escape', 'bubbles': true})); } function waitForProcessToEnd(itemNode, index) { return new Promise((resolve) => { let maxTimeout = 0; let mainInterval = null; let hasVideoEnded = false; let endedConfirmCount = 0; const ENDED_CONFIRM_THRESHOLD = 3; const videoEl = document.querySelector('video'); if (videoEl) { videoEl.muted = true; videoEl.addEventListener('pause', async function() { if (videoEl.currentTime < videoEl.duration - 1 && !isPaused && !stopRequested) { log('warning', "⚡ 平台尝试暂停视频!自动重新激活播放..."); await forcePlayVideo(); } }); } function cleanupAndResolve() { if (mainInterval) clearInterval(mainInterval); resolve(); } mainInterval = setInterval(async () => { if (stopRequested) { cleanupAndResolve(); return; } if (isPaused) return; maxTimeout++; const processNode = itemNode.querySelector('.process'); if (processNode && (processNode.innerText.includes('已观看100%') || processNode.innerText.includes('已完成'))) { log('success', `✅ 第 ${index} 个视频系统进度已更新为完成!`); cleanupAndResolve(); return; } const currentVideo = document.querySelector('video'); if (currentVideo && !hasVideoEnded) { currentVideo.muted = true; if (currentVideo.paused && currentVideo.currentTime < currentVideo.duration) { log('warning', '⚡ 检测到视频暂停,强制恢复播放...'); await forcePlayVideo(); } currentVideo.play().catch(() => {}); if (currentVideo.ended || (currentVideo.duration > 0 && currentVideo.duration - currentVideo.currentTime <= 1)) { endedConfirmCount++; if (endedConfirmCount >= ENDED_CONFIRM_THRESHOLD) { hasVideoEnded = true; log('warning', `⚠️ 第 ${index} 个视频本体已播完(连续${ENDED_CONFIRM_THRESHOLD}次确认),跳过平台服务器响应判定。`); cleanupAndResolve(); return; } } else { endedConfirmCount = 0; } } if (maxTimeout > config.maxTimeout) { log('warning', `🚨 第 ${index} 个视频监测超时(可能服务器504),强行跳过。`); cleanupAndResolve(); } }, config.checkInterval); }); } function watchSmartEduVideoUntilEnd(index) { return new Promise((resolve) => { let notFoundCount = 0; let endedConfirmCount = 0; const ENDED_CONFIRM_THRESHOLD = 3; const checkInterval = setInterval(async () => { if (stopRequested) { clearInterval(checkInterval); resolve(); return; } if (isPaused) return; const videoEl = document.querySelector('video'); if (videoEl) { notFoundCount = 0; videoEl.muted = true; if (videoEl.paused && videoEl.currentTime < videoEl.duration) { log('warning', '⚡ 检测到视频暂停,强制恢复播放...'); // 优先点击播放按钮,再兜底API调用 const playBtn = document.querySelector('.video-play-img img') || document.querySelector('.video-play-img') || document.querySelector('.vjs-big-play-button') || document.querySelector('button[class*="play"]'); if (playBtn && playBtn.offsetParent !== null) { safeClick(playBtn); await sleep(500); if (!videoEl.paused) { return; // 播放成功,跳过本次检查 } } videoEl.play().catch(() => console.log("尝试自动播放被拦截")); } // 连续多次确认视频已结束,避免误判 if (videoEl.ended || (videoEl.duration > 0 && videoEl.currentTime >= videoEl.duration - 1)) { endedConfirmCount++; if (endedConfirmCount >= ENDED_CONFIRM_THRESHOLD) { log('success', `✅ 第 ${index} 个视频已确认播放完毕(连续${ENDED_CONFIRM_THRESHOLD}次检测)`); clearInterval(checkInterval); resolve(); } } else { endedConfirmCount = 0; } } else { notFoundCount++; if (notFoundCount >= 5) { log('warning', `⚠️ 第 ${index} 个视频似乎无法加载视频流,准备跳过...`); console.warn(`⚠️ 第 ${index} 个视频似乎无法加载视频流,准备跳过...`); clearInterval(checkInterval); resolve(); } } }, 3000); }); } 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 = ''; } 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 || isSmartEduPlatform; startBtn.disabled = isRunning; pauseBtn.disabled = !isRunning; stopBtn.disabled = !isRunning; const detectBtn = document.getElementById('detect-btn'); const continueBtn = document.getElementById('continue-incomplete-btn'); if (detectBtn) detectBtn.disabled = isRunning; if (continueBtn) { if (isRunning) { continueBtn.disabled = true; } // 不运行时保留之前的启用/禁用状态(由detectCourseProgress设置) } } catch (error) { console.error("更新UI错误:", error); } } // 全局视频暂停监控:无论何时视频被暂停,自动恢复 function setupVideoPauseGuard() { // 使用MutationObserver持续监听新出现的video元素 const observer = new MutationObserver(() => { const videos = document.querySelectorAll('video'); videos.forEach(video => { if (video._pauseGuardInstalled) return; video._pauseGuardInstalled = true; // 拦截video.pause()调用 const originalPause = video.pause.bind(video); video.pause = function() { // 允许用户手动暂停(isPaused)或脚本停止(stopRequested) if (isRunning && !isPaused && !stopRequested) { console.log("🛡️ 拦截了平台对video.pause()的调用,自动恢复播放"); return; // 直接忽略暂停请求 } return originalPause(); }; // 监听pause事件,防止非脚本触发的暂停 video.addEventListener('pause', function() { if (isRunning && !isPaused && !stopRequested && video.currentTime < video.duration - 1) { console.log("🛡️ 检测到视频被暂停,300ms后自动恢复..."); setTimeout(() => { if (isRunning && !isPaused && !stopRequested) { video.play().catch(() => {}); } }, 300); } }); // 监听视频被移除或替换后重新挂载守卫 video.addEventListener('loadstart', function() { video._pauseGuardInstalled = false; // 延迟重新安装,避免在加载过程中干扰 setTimeout(() => setupVideoPauseGuard(), 1000); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } function init() { console.log("🔧 开始初始化脚本..."); isSmartEduPlatform = window.location.href.includes('higher.smartedu.cn/course/'); console.log("📱 平台检测结果:", isSmartEduPlatform ? "智慧教育平台" : "通用平台"); setupVideoPauseGuard(); if (document.readyState === 'complete') { createPanel(); } else { window.addEventListener('load', createPanel); } } if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(init, 1000); } else { window.addEventListener('DOMContentLoaded', () => { setTimeout(init, 1000); }); } })();