// ==UserScript== // @name 江苏开放全自动化【公益版】 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 江苏开放全自动化【可加速及自动下一页】 // @author 一心向善 // @match *://xuexi.jsou.cn/* // @match *://*.jsou.cn/* // @run-at document-idle // @grant none // @noframes // @license MIT // ==/UserScript== /** * 江苏开放全自动化【公益版】辅助脚本 v1.0.1 * * v1.0→v1.0.1 修复: * 1. 持久化运行状态(localStorage)— 页面跳转后自动恢复"启动中"状态 * 2. 增加最小化/恢复按钮 * 3. 在持久化模式下自动重启 * */ (function () { // 注意:不要 use strict,否则给 XHR prototype 赋值会抛 TypeError // 非严格模式下赋值会静默失败,不影响批量调用原生 sendHeartBeatAjax() var LS_KEY = 'studyHelperV52'; // ============ 配置(从 localStorage 恢复)============ function loadConfig() { try { var saved = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); if (saved) return saved; } catch (e) {} return null; } function saveConfig() { try { localStorage.setItem(LS_KEY, JSON.stringify(CONFIG)); } catch (e) {} } var CONFIG = Object.assign({ docHeartbeats: 600, batchInterval: 600, videoSpeed: 1.0, autoNextDelay: 1500, keepAliveInterval: 10000 }, loadConfig() || {}); var state = { running: false, processing: false, currentResourceType: '', currentActivityId: '', heartbeatCount: 0, successCount: 0, failCount: 0, startTime: null, onlineTimer: null, speedTimer: null, xhrHooked: false, heartbeatSent: 0, heartbeatTarget: 0, waitingForVideo: false, moObserver: null, batchStartTs: 0, minimized: false }; // 持久化运行状态 —— 如果上次是运行中,这次自动恢复 function checkAutoResume() { try { var saved = JSON.parse(localStorage.getItem(LS_KEY + '_running') || 'null'); if (saved && saved.running) { log('🔁 检测到上次运行中,自动恢复...', 'auto'); // 恢复配置 CONFIG = Object.assign(CONFIG, saved.config || {}); CONFIG.videoSpeed = parseFloat(CONFIG.videoSpeed) || 1.0; CONFIG.docHeartbeats = parseInt(CONFIG.docHeartbeats) || 40; // 更新面板控件 setTimeout(function () { var inp = document.getElementById('cfg-doc-hearts'); var sel = document.getElementById('cfg-video-speed'); if (inp) inp.value = CONFIG.docHeartbeats; if (sel) sel.value = String(CONFIG.videoSpeed); updateTimeLabels(); // 启动自动流程 startAuto(true); // autoResume=true 时不重复设置 CONFIG }, 1500); } } catch (e) {} } function persistRunning(running) { try { if (running) { localStorage.setItem(LS_KEY + '_running', JSON.stringify({ running: true, config: CONFIG, startTime: Date.now() })); } else { localStorage.removeItem(LS_KEY + '_running'); } } catch (e) {} } function log(msg, type) { var t = new Date().toLocaleTimeString(); var c = { success:'#52c41a', error:'#f5222d', warn:'#faad14', info:'#1890ff', batch:'#722ed1', auto:'#eb2f96' }[type] || '#333'; console.log('%c[' + t + '] ' + msg, 'color:' + c); } // ============ 核心工具 ============ function getResourceType() { try { if (typeof dataHeart !== 'undefined') { if (dataHeart.type == 21) return '文档'; if (dataHeart.type == 2 || dataHeart.type == 28) return '视频'; if (dataHeart.type == 1) return '资源'; } } catch (e) {} var v = getVideoElement(); if (v) return '视频'; return '资源'; } function getCurrentActivityId() { try { if (typeof dataHeart !== 'undefined' && dataHeart.activityId) return dataHeart.activityId; } catch (e) {} return ''; } function getVideoElement() { var videos = document.querySelectorAll('video'); for (var i = 0; i < videos.length; i++) { if (videos[i].offsetParent !== null || videos[i].duration > 0) return videos[i]; } if (videos.length > 0) return videos[0]; var iframes = document.querySelectorAll('iframe'); for (var j = 0; j < iframes.length; j++) { try { var d = iframes[j].contentDocument || iframes[j].contentWindow.document; if (d) { var v = d.querySelector('video'); if (v) return v; } } catch (e) {} } return null; } // ============ 心跳 ============ function fireOneHeartbeat() { // 🔒 停止后不再发任何心跳 if (!state.running) return false; try { if (typeof dataHeart !== 'undefined') { dataHeart.isResourcePage = true; if ((dataHeart.type == 2 || dataHeart.type == 28) && dataHeart.playStatus === false) { return false; } } if (typeof sendHeartBeatAjax === 'function') { sendHeartBeatAjax(); return true; } if (typeof $ !== 'undefined' && typeof heartbeatUrl !== 'undefined' && heartbeatUrl) { $.ajax({ type:'POST', url:heartbeatUrl, data:dataHeart, cache:false }); return true; } return false; } catch (e) { return false; } } function fireBatch(n, onDone) { var sent = 0, total = n, interval = CONFIG.batchInterval; state.heartbeatTarget = total; state.heartbeatSent = 0; state.batchStartTs = Date.now(); showProgressBar(); updateProgress(0, total); function sendNext() { // 🔒 停止后立刻中断整个批量流程 if (!state.running) { hideProgressBar(); log('🛑 批量心跳已被手动停止', 'warn'); return; } if (sent >= total) { state.heartbeatSent = total; state.heartbeatTarget = 0; hideProgressBar(); log('✅ 本课件心跳 ' + total + '/' + total + ' 完成', 'batch'); updatePanel(); if (typeof onDone === 'function') onDone(); return; } fireOneHeartbeat(); sent++; state.heartbeatCount++; state.heartbeatSent = sent; updateProgress(sent, total); if (sent % 20 === 0 || sent === total) { log('📊 心跳进度 ' + sent + '/' + total, 'info'); } updatePanel(); setTimeout(sendNext, interval); } sendNext(); } // ============ 进度条 ============ function showProgressBar() { var box = document.getElementById('progress-box'); if (box) box.style.display = 'block'; } function hideProgressBar() { var box = document.getElementById('progress-box'); if (box) box.style.display = 'none'; } function updateProgress(done, total) { var bar = document.getElementById('progress-bar'); var pct = document.getElementById('progress-pct'); var dEl = document.getElementById('progress-done'); var tEl = document.getElementById('progress-total'); var eta = document.getElementById('progress-eta'); if (bar) bar.style.width = Math.round(done/total*100) + '%'; if (pct) pct.textContent = Math.round(done/total*100) + '%'; if (dEl) dEl.textContent = done; if (tEl) tEl.textContent = total; if (eta) { var elapsed = (Date.now() - state.batchStartTs) / 1000; var avg = done > 0 ? elapsed / done : 0; var remain = Math.max(0, avg * (total - done)); eta.textContent = '≈ ' + Math.round(remain) + '秒剩余'; } } // ============ XHR 拦截(try/catch 保护)=========== function hookXHR() { if (state.xhrHooked) return; state.xhrHooked = true; try { var origOpen = XMLHttpRequest.prototype.open; var origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (m, u) { this._url = u; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { var self = this; this.addEventListener('load', function () { if (self._url && self._url.indexOf('heartbeat') > -1 && self._url.indexOf('learningBehavior') > -1) { try { var resp = JSON.parse(self.responseText); if (resp && resp.code === 'SUCCESS') { state.successCount++; var te = document.getElementById('stuResourceViewTime'); if (te) { var t = parseInt(te.dataset.time || te.getAttribute('data-time') || '0'); t += 30; te.dataset.time = t; if (typeof formatLength === 'function') te.innerText = formatLength(t); } } else { state.failCount++; } } catch (e) { state.successCount++; } updatePanel(); } }); this.addEventListener('error', function () { if (self._url && self._url.indexOf('heartbeat') > -1) { state.failCount++; updatePanel(); } }); return origSend.apply(this, arguments); }; log('🔌 XHR 拦截器已激活', 'info'); } catch (e) { log('🔌 XHR 拦截器跳过(Chrome 限制),不影响批量发心跳', 'warn'); } } // ============ 保活 ============ function keepAlive() { try { if (typeof inactiveSeconds !== 'undefined') inactiveSeconds = 0; if (typeof mouseIsOn !== 'undefined') mouseIsOn = true; if (typeof isHeartbeatStop !== 'undefined') isHeartbeatStop = false; if (typeof dataHeart !== 'undefined') dataHeart.isResourcePage = true; } catch (e) {} try { var evt = new MouseEvent('mousemove', { clientX: Math.random()*1000, clientY: Math.random()*500, bubbles: true }); document.dispatchEvent(evt); } catch (e) {} } // ============ 视频:自动倍速 + 自动播放 + MutationObserver 持久化 ============ function applyVideoToElement(v) { if (!v || v._helperApplied) return; v._helperApplied = true; v._endedBound = false; v.playbackRate = CONFIG.videoSpeed; try { v.muted = true; } catch (e) {} if (v.paused) { v.play().catch(function(){}); } log('🎬 自动应用倍速 ' + CONFIG.videoSpeed + 'x + 播放', 'success'); // 持续维持速度(每秒检查一次) var speedHold = setInterval(function () { if (!document.body.contains(v)) { clearInterval(speedHold); return; } if (Math.abs(v.playbackRate - CONFIG.videoSpeed) > 0.01) { v.playbackRate = CONFIG.videoSpeed; try { v.muted = true; } catch (e) {} } if (v.paused && state.running) { v.play().catch(function(){}); } }, 2000); // ended 事件:只绑一次 v.addEventListener('ended', function onEnded() { if (!state.running) return; if (v._endedBound) return; v._endedBound = true; log('📺 视频播放完毕,下一节', 'info'); state.waitingForVideo = false; setTimeout(autoNextResource, CONFIG.autoNextDelay); }); // 卡住检测 var lastTime = v.currentTime, stallCount = 0; var stallTimer = setInterval(function () { if (!document.body.contains(v)) { clearInterval(stallTimer); return; } if (v.paused || v.ended) return; if (Math.abs(v.currentTime - lastTime) < 0.5) { stallCount++; if (stallCount > 6) { log('⚠️ 视频长时间没进度,尝试跳过', 'warn'); try { v.currentTime = Math.max(0, v.duration - 2); } catch (e) {} stallCount = 0; } } else { stallCount = 0; } lastTime = v.currentTime; }, 2000); } function startVideoObserver() { if (state.moObserver) { state.moObserver.disconnect(); state.moObserver = null; } state.moObserver = new MutationObserver(function (mutations) { document.querySelectorAll('video').forEach(applyVideoToElement); document.querySelectorAll('iframe').forEach(function (iframe) { try { var d = iframe.contentDocument || iframe.contentWindow.document; if (d) d.querySelectorAll('video').forEach(applyVideoToElement); } catch (e) {} }); }); state.moObserver.observe(document.body, { childList: true, subtree: true }); document.querySelectorAll('video').forEach(applyVideoToElement); log('👀 MutationObserver 已启动,所有视频自动应用倍速 ' + CONFIG.videoSpeed + 'x', 'info'); } function stopVideoObserver() { if (state.moObserver) { state.moObserver.disconnect(); state.moObserver = null; } } function setupVideo() { var v = getVideoElement(); if (!v) { log('❌ 没找到视频元素', 'error'); return false; } applyVideoToElement(v); if (v.duration > 0 && (v.duration - v.currentTime) < 5) { log('⚠️ 视频接近结尾,直接下一节', 'warn'); setTimeout(autoNextResource, 1000); } return true; } function waitForVideoEnd() { if (!state.running) { state.waitingForVideo = false; return; } var v = getVideoElement(); if (!v) { state.waitingForVideo = false; return; } if (!v.paused && v.duration > 0 && v.currentTime < v.duration - 1) { setTimeout(waitForVideoEnd, 3000); return; } if (v.duration > 0 && v.currentTime >= v.duration - 1) { log('📺 视频播完,下一节', 'info'); state.waitingForVideo = false; setTimeout(autoNextResource, CONFIG.autoNextDelay); return; } if (v.paused) { v.play().catch(function(){}); setTimeout(waitForVideoEnd, 2000); return; } setTimeout(waitForVideoEnd, 3000); } // ============ 找下一节 ============ function autoNextResource() { log('🔍 正在查找下一节...', 'auto'); keepAlive(); var selectors = [ '.chapter-item', '.section-item', '.course-item', '.resource-item', '.catalog-item', '.menu-item', '.node-item', '.learn-item', 'li[data-id]', 'li[data-fid]', 'li.activity-item', 'li.node', '.node', '.tree-node', '.slick-current + .slick-slide' ]; var foundActive = false; for (var s = 0; s < selectors.length; s++) { var items = document.querySelectorAll(selectors[s]); if (items.length < 2) continue; for (var j = 0; j < items.length; j++) { var cls = (items[j].className || '') + ' ' + (items[j].getAttribute('class') || ''); var style = items[j].getAttribute('style') || ''; var isCurrent = cls.indexOf('active') > -1 || cls.indexOf('current') > -1 || cls.indexOf('playing') > -1 || cls.indexOf('selected') > -1 || style.indexOf('rgb(24, 144, 255)') > -1 || style.indexOf('color:#1890ff') > -1 || style.indexOf('color: rgb(24, 144, 255)') > -1; if (isCurrent) { foundActive = true; continue; } if (foundActive && items[j].offsetParent !== null && items[j].clientHeight > 0) { log('🎯 点击下一节: ' + (items[j].innerText || '').trim().substring(0, 40), 'auto'); items[j].click(); scheduleHandle(); return true; } } } var btns = document.querySelectorAll('a, button, span, div'); for (var k = 0; k < btns.length; k++) { var t = (btns[k].innerText || '').trim(); if (t === '下一页' || t === '下一节' || t === '下一讲' || t === '下一个' || t === '下一项' || t === '▶' || t === '›') { log('🎯 点击按钮: ' + t, 'auto'); btns[k].click(); scheduleHandle(); return true; } } log('❌ 没找到下一节', 'error'); return false; } function scheduleHandle() { state.processing = false; if (state.speedTimer) { clearInterval(state.speedTimer); state.speedTimer = null; } keepAlive(); setTimeout(function () { var newAct = getCurrentActivityId(); log('🔄 页面加载中... activityId: ' + (newAct ? newAct.substring(0,12)+'...' : '未获取'), 'info'); handleCurrentResource(); }, 3000); } // ============ 主流程 ============ function handleCurrentResource() { if (state.processing) return; state.processing = true; try { if (typeof dataHeart !== 'undefined') { dataHeart.isResourcePage = true; if (typeof isHeartbeatStop !== 'undefined') isHeartbeatStop = false; } } catch (e) {} var type = getResourceType(); state.currentResourceType = type; var actId = getCurrentActivityId(); state.currentActivityId = actId; log('═══════════════════════════════════', 'auto'); log('📚 开始处理: [' + type + '] activityId=' + (actId ? actId.substring(0,12) + '...' : '(页面未加载)'), 'auto'); if (!actId && type === '资源') { log('⏳ 等待页面加载(2秒后重试)...', 'info'); setTimeout(function () { state.processing = false; handleCurrentResource(); }, 2000); return; } if (type === '视频') { log('🎬 视频课件,启动自动播放...', 'auto'); var ok = setupVideo(); if (ok) { state.processing = false; if (!state.waitingForVideo) { state.waitingForVideo = true; waitForVideoEnd(); } } else { log('⚠️ 视频启动失败,10秒后跳到下一节', 'warn'); state.processing = false; setTimeout(autoNextResource, 10000); } } else { log('📄 ' + type + ' 课件,批量发 ' + CONFIG.docHeartbeats + ' 个心跳', 'batch'); fireBatch(CONFIG.docHeartbeats, function () { log('✅ 本节加时完成 → 下一节', 'auto'); state.processing = false; setTimeout(autoNextResource, CONFIG.autoNextDelay); }); } updatePanel(); } // ============ 控制面板 ============ function createPanel() { var old = document.getElementById('study-helper-panel'); if (old) old.remove(); var p = document.createElement('div'); p.id = 'study-helper-panel'; p.innerHTML = '
' + '
' + '🎓 江开自动助手【教务合作】' + '' + '' + '×' + '' + '
' + '
' + '
' + '
🚀 全自动模式
' + '' + '
' + '每个课件加 ' + '' + '个心跳(计算中' + '
' + '' + '
' + '倍速 ' + '' + 'x 自动静音' + '
' + '' + '' + '
' + '' + '
' + '
📊 运行状态
' + '
类型: 检测中
' + '
页面时长: 读取中
' + '
心跳总计: 0 成功 0 / 失败 0
' + '
运行: 00:00:00
' + '
' + '
' + '' + '
' + '
' + '
🔥 答题或教务批量合作 🔥
' + '
联系Q:640105435
' + '
Q群1:949193546  |  Q群2:756253160
' + '
' + '
' + '
'; document.body.appendChild(p); document.getElementById('pc').onclick = function() { stopAll(); p.remove(); }; document.getElementById('pmin').onclick = function() { toggleMinimize(); }; document.getElementById('btn-auto').onclick = function() { startAuto(false); }; document.getElementById('btn-stop-auto').onclick = stopAuto; ['cfg-doc-hearts'].forEach(function(id) { var el = document.getElementById(id); if (el) el.oninput = updateTimeLabels; }); document.getElementById('cfg-video-speed').onchange = function() { CONFIG.videoSpeed = parseFloat(this.value); saveConfig(); if (state.running) { document.querySelectorAll('video').forEach(function(v) { v._helperApplied = false; applyVideoToElement(v); }); } }; document.getElementById('cfg-doc-hearts').onchange = function() { CONFIG.docHeartbeats = parseInt(this.value) || 40; saveConfig(); }; makeDrag(document.getElementById('ph'), p); updateTimeLabels(); updatePanel(); } function toggleMinimize() { var body = document.getElementById('pbody'); var header = document.getElementById('ph'); var p = document.getElementById('study-helper-panel'); var minBtn = document.getElementById('pmin'); if (!body || !p) return; if (!state.minimized) { // 最小化:只保留标题栏 body.style.display = 'none'; p.style.width = '180px'; p.style.height = 'auto'; p.style.top = p.offsetTop + 'px'; p.style.right = 'auto'; minBtn.textContent = '□'; minBtn.title = '展开'; state.minimized = true; } else { // 恢复 body.style.display = 'block'; p.style.width = '340px'; p.style.height = 'auto'; minBtn.textContent = '—'; minBtn.title = '最小化'; state.minimized = false; } } function updateTimeLabels() { var d = parseInt(document.getElementById('cfg-doc-hearts').value) || 0; var dm = d * 30; var el1 = document.getElementById('cfg-doc-time'); if (el1) el1.textContent = dm >= 60 ? Math.floor(dm/60) + '分钟' + (dm%60 > 0 ? dm%60 + '秒' : '') : dm + '秒'; } // ============ 拖拽(终极版:全局 document 监听 + 坐标区域检测)============ // 不依赖 header 的事件冒泡,也不依赖 pointer-events // 在 document 上监听所有鼠标事件,然后判断鼠标位置是否在 header 区域内 function makeDrag(header, panel) { // 确保 header 有可见的 cursor header.style.cursor = 'move'; header.style.userSelect = 'none'; var dragging = false; var dragOffsetX = 0; var dragOffsetY = 0; // 获取 header 在视口中的矩形 function headerRect() { return header.getBoundingClientRect(); } // 判断点 (x,y) 是否在 header 区域内 function isInHeader(x, y) { var r = headerRect(); return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom; } // 判断点 (x,y) 是否在 header 内的 × 或 — 按钮上 function isOnButtons(x, y) { var pc = document.getElementById('pc'), pmin = document.getElementById('pmin'); if (pc) { var r = pc.getBoundingClientRect(); if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return true; } if (pmin) { var r2 = pmin.getBoundingClientRect(); if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true; } return false; } // 全局监听:任何 mousedown 都检查一下 document.addEventListener('mousedown', function (e) { if (isOnButtons(e.clientX, e.clientY)) return; // 点按钮不管 if (isInHeader(e.clientX, e.clientY)) { dragging = true; // 记录偏移量 var panelRect = panel.getBoundingClientRect(); dragOffsetX = e.clientX - panelRect.left; dragOffsetY = e.clientY - panelRect.top; // 清除 right/bottom,改用 left/top 控制 panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.left = panelRect.left + 'px'; panel.style.top = panelRect.top + 'px'; e.preventDefault(); } }, true); // 捕获模式,最顶层 document.addEventListener('mousemove', function (e) { if (!dragging) return; var x = e.clientX - dragOffsetX; var y = e.clientY - dragOffsetY; // 边界限制 x = Math.max(0, Math.min(x, window.innerWidth - panel.offsetWidth - 5)); y = Math.max(0, Math.min(y, window.innerHeight - 40)); panel.style.left = x + 'px'; panel.style.top = y + 'px'; e.preventDefault(); }, true); document.addEventListener('mouseup', function () { dragging = false; }, true); // 触摸端(手机) document.addEventListener('touchstart', function (e) { var p = e.touches[0]; if (!p) return; if (isOnButtons(p.clientX, p.clientY)) return; if (isInHeader(p.clientX, p.clientY)) { dragging = true; var panelRect = panel.getBoundingClientRect(); dragOffsetX = p.clientX - panelRect.left; dragOffsetY = p.clientY - panelRect.top; panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.left = panelRect.left + 'px'; panel.style.top = panelRect.top + 'px'; e.preventDefault(); } }, { passive: false, capture: true }); document.addEventListener('touchmove', function (e) { if (!dragging) return; var p = e.touches[0]; if (!p) return; var x = p.clientX - dragOffsetX; var y = p.clientY - dragOffsetY; x = Math.max(0, Math.min(x, window.innerWidth - panel.offsetWidth - 5)); y = Math.max(0, Math.min(y, window.innerHeight - 40)); panel.style.left = x + 'px'; panel.style.top = y + 'px'; e.preventDefault(); }, { passive: false, capture: true }); document.addEventListener('touchend', function () { dragging = false; }, true); } function updatePanel() { var st = document.getElementById('st-type'), pt = document.getElementById('st-page-time'), su = document.getElementById('st-suc'), fa = document.getElementById('st-fail'), ru = document.getElementById('st-run'), he = document.getElementById('st-heart'), ba = document.getElementById('btn-auto'), bs = document.getElementById('btn-stop-auto'); if (st) st.textContent = state.currentResourceType || getResourceType(); var te = document.getElementById('stuResourceViewTime'); if (pt) pt.textContent = te ? te.innerText : '未找到'; if (su) su.textContent = state.successCount; if (fa) fa.textContent = state.failCount; if (he) he.textContent = state.heartbeatCount; if (ba && bs) { if (state.running) { ba.style.display = 'none'; bs.style.display = 'block'; } else { ba.style.display = 'block'; bs.style.display = 'none'; } } if (state.startTime && ru) { var s = Math.floor((Date.now() - state.startTime) / 1000); ru.textContent = (('0'+Math.floor(s/3600)).slice(-2)) + ':' + (('0'+Math.floor((s%3600)/60)).slice(-2)) + ':' + (('0'+(s%60)).slice(-2)); } } // ============ 运行控制 ============ function enableKeepAlive() { if (state.onlineTimer) return; try { if (typeof isHeartbeatStop !== 'undefined') isHeartbeatStop = false; } catch (e) {} hookXHR(); state.onlineTimer = setInterval(keepAlive, CONFIG.keepAliveInterval); log('🔄 保活已开启(每' + (CONFIG.keepAliveInterval/1000) + '秒模拟在线)', 'info'); } function disableKeepAlive() { if (state.onlineTimer) { clearInterval(state.onlineTimer); state.onlineTimer = null; } } function startAuto(autoResume) { if (state.running && !autoResume) return; var inp = document.getElementById('cfg-doc-hearts'); var sel = document.getElementById('cfg-video-speed'); if (!autoResume) { CONFIG.docHeartbeats = parseInt(inp.value) || 40; CONFIG.videoSpeed = parseFloat(sel.value) || 1.0; } else { // 自动恢复模式:确保控件显示正确值 if (inp) inp.value = CONFIG.docHeartbeats; if (sel) sel.value = String(CONFIG.videoSpeed); } CONFIG.keepAliveInterval = document.getElementById('cfg-keepalive').checked ? 10000 : 999999; state.running = true; if (!autoResume) state.startTime = Date.now(); saveConfig(); // 保存配置 persistRunning(true); // 持久化运行状态 ← 关键! if (CONFIG.keepAliveInterval < 999999) enableKeepAlive(); // 启动 MutationObserver —— 自动给所有新 video 应用倍速/播放 startVideoObserver(); log('═══════════════════════════════════', 'auto'); if (autoResume) { log('🔁 自动恢复运行中...', 'auto'); } else { log('🚀 全自动启动!', 'auto'); } log(' 文档/资源: 每个课件 ' + CONFIG.docHeartbeats + ' 个心跳(' + (CONFIG.docHeartbeats*30) + '秒)', 'auto'); log(' 视频: ' + CONFIG.videoSpeed + 'x 倍速 + 静音 + 自动播放(持久化)', 'auto'); log('═══════════════════════════════════', 'auto'); handleCurrentResource(); updatePanel(); } function stopAuto() { if (!state.running) return; state.running = false; disableKeepAlive(); state.processing = false; state.waitingForVideo = false; stopVideoObserver(); persistRunning(false); // 清除持久化标记 log('🛑 已停止全自动', 'warn'); updatePanel(); } function stopAll() { stopAuto(); } // ============ 定时刷新 ============ setInterval(updatePanel, 1000); // ============ 启动入口 ============ log('🎓 全自动学习 v1.0 已加载!点「▶ 启动全自动」开始', 'auto'); if (document.readyState !== 'loading') { createPanel(); checkAutoResume(); } else { document.addEventListener('DOMContentLoaded', function () { createPanel(); checkAutoResume(); }); } window.__studyHelper = { startAuto: startAuto, stopAuto: stopAuto, goNext: autoNextResource }; })();