// ==UserScript== // @name 教育干部网络学院 / 大学生网络党校 · 看课助手(纯净版) // @namespace leemo-enaea-clean-helper // @version 1.2.0 // @description 全自动挂课:静音播放、防暂停、卡死自救、自动答题、自动下一节。 // @author leemo // @copyright 2026, leemo <781783460@qq.com> // @supportURL mailto:781783460@qq.com // @match *://*.enaea.edu.cn/* // @match *://*.uucps.edu.cn/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @run-at document-start // ==/UserScript== (function () { 'use strict'; /* ===================== 配置持久化 ===================== */ var CFG_KEY = 'eas_cfg_v1'; var DEFAULTS = { rate: 1, // 播放倍速 autoNext: true, // 播完自动下一节 blockVerify: true, // 拦截学时验证请求,防止弹验证码打断 autoDialog: true, // 自动关闭"温馨提示"类弹窗 autoAnswer: true, // 自动答题(拿到答案选对,拿不到就轮换穷举,答错继续) autoReload: true, // 长时间无进展 / 找不到下一节时自动刷新自救 autoEnter: true, // 课程列表页自动点"继续学习/开始学习"进入未完成课程 mute: true // 静音 }; var cfg = (function () { var saved = {}; try { saved = GM_getValue(CFG_KEY, {}) || {}; } catch (e) {} var out = {}; for (var k in DEFAULTS) { out[k] = Object.prototype.hasOwnProperty.call(saved, k) ? saved[k] : DEFAULTS[k]; } return out; })(); function saveCfg() { try { GM_setValue(CFG_KEY, cfg); } catch (e) {} } function log() { var a = Array.prototype.slice.call(arguments); console.log('%c[看课助手]', 'color:#2b6cb0', a.join(' ')); } /* ===================== 拦截 XHR / fetch ===================== 目的:掐掉 learnVerify 一类的学时校验/验证码请求,避免播放中途被弹窗打断。 做法:不真正发包,直接伪造一个成功响应返回给页面。 */ function isVerifyUrl(url) { return /learnVerify|learnValid|checkCode|studyValid/i.test(String(url || '')); } function isQuestionUrl(url) { return /getQuestions|validQuestions|questionList|\/questions/i.test(String(url || '')); } // 从拦截到的题目数据里取正确答案;取不到就返回 null,之后随机选 function getCorrectAnswer() { var q = window.__EAS_Q; if (!q) return null; var c = q.correctAnswer || q.answer || q.rightAnswer || q.correct || q.answers; if (c == null) return null; if (Object.prototype.toString.call(c) === '[object Array]') return c.join(''); if (typeof c === 'object') return JSON.stringify(c); return String(c); } function fakeXhr(xhr, text) { setTimeout(function () { try { Object.defineProperty(xhr, 'readyState', { value: 4, configurable: true }); Object.defineProperty(xhr, 'status', { value: 200, configurable: true }); Object.defineProperty(xhr, 'statusText', { value: 'OK', configurable: true }); Object.defineProperty(xhr, 'responseText', { value: text, configurable: true }); Object.defineProperty(xhr, 'response', { value: text, configurable: true }); xhr.dispatchEvent(new Event('readystatechange')); xhr.dispatchEvent(new Event('load')); xhr.dispatchEvent(new Event('loadend')); } catch (e) { log('伪造响应失败', e.message); } }, 0); } function installXHRHook() { var P = window.XMLHttpRequest && window.XMLHttpRequest.prototype; if (!P) return; var origOpen = P.open, origSend = P.send; P.open = function (method, url) { try { this.__easUrl = String(url || ''); } catch (e) {} return origOpen.apply(this, arguments); }; P.send = function () { var self = this; try { if (cfg.blockVerify && isVerifyUrl(this.__easUrl)) { log('已拦截验证请求:', this.__easUrl); fakeXhr(this, '{"code":0,"msg":null,"data":{"status":1}}'); return; } if (isQuestionUrl(this.__easUrl)) { this.addEventListener('load', function () { try { var j = JSON.parse(self.responseText); if (j && j.data) { window.__EAS_Q = j.data; log('已捕获题目数据'); } } catch (e) {} }); } } catch (e) {} return origSend.apply(this, arguments); }; } function installFetchHook() { if (!window.fetch) return; var orig = window.fetch; window.fetch = function (input) { try { var url = typeof input === 'string' ? input : (input && input.url) || ''; if (cfg.blockVerify && isVerifyUrl(url)) { log('已拦截 fetch 验证请求:', url); return Promise.resolve(new Response('{"code":0,"msg":null,"data":{"status":1}}', { status: 200, headers: { 'Content-Type': 'application/json' } })); } } catch (e) {} var p = orig.apply(this, arguments); if (isQuestionUrl(url) && p && typeof p.then === 'function') { p.then(function (res) { try { if (res && typeof res.clone === 'function') { res.clone().json().then(function (j) { if (j && j.data) { window.__EAS_Q = j.data; log('已捕获题目数据(fetch)'); } }).catch(function () {}); } } catch (e) {} return res; }).catch(function () {}); } return p; }; } installXHRHook(); /* ===================== 视频查找(含同域 iframe) ===================== */ function findVideo() { var v = document.querySelector('video'); if (v) return v; var frames = document.querySelectorAll('iframe'); for (var i = 0; i < frames.length; i++) { try { var d = frames[i].contentDocument; if (d) { var iv = d.querySelector('video'); if (iv) return iv; } } catch (e) { /* 跨域 iframe,跳过 */ } } return null; } /* ===================== 目录解析与跳转 ===================== */ var CATALOG_SELECTORS = [ 'ul.lis-content li.lis-inside-content', '.catalog-list li', '.chapter-list li', '.course-catalog li', 'ul.catalog li', '[class*=catalog] li', '[class*=Catalog] li', '[class*=directory] li', '[class*=syllabus] li' ]; function getCatalogItems() { for (var s = 0; s < CATALOG_SELECTORS.length; s++) { var list = document.querySelectorAll(CATALOG_SELECTORS[s]); if (list && list.length > 1) return Array.prototype.slice.call(list); } return []; } function extractUrl(li) { var a = li.querySelector('a[href]'); if (a && a.href && !/^javascript:/i.test(a.href)) return a.href; var node = li.querySelector('h2[onclick], .title[onclick], [onclick]'); var on = node ? node.getAttribute('onclick') : ''; if (!on) return ''; var m = on.match(/location\.href\s*=\s*['"]([^'"]+)['"]/); if (m) return m[1]; m = on.match(/open\s*\(\s*['"](\/[^'"]+)['"]/); if (m) return m[1]; m = on.match(/['"](\/[^'"\s]{4,})['"]/); if (m) return m[1]; return ''; } function isFinished(li) { var txt = (li.innerText || '').replace(/\s+/g, ' '); if (/100\s*%/.test(txt)) return true; if (/已完成|已学习|已看完/.test(txt)) return true; if (li.querySelector('.xxzt_icon3, .icon-finish, .finished, .is-finish')) return true; var btn = li.querySelector('button, .btn, .status'); var bt = btn ? (btn.innerText || '').trim() : ''; if (/已完成|已学习|已看完/.test(bt)) return true; return false; } function currentIndex(items) { for (var i = 0; i < items.length; i++) { if (items[i].querySelector('#top_play, .playing, .current, .active, .on')) return i; } for (var j = 0; j < items.length; j++) { var u = extractUrl(items[j]); if (u && u.indexOf(location.pathname) !== -1 && location.pathname.length > 3) return j; } return -1; } function fallbackNextButton() { var cands = document.querySelectorAll('a, button, .btn, span, div'); for (var i = 0; i < cands.length; i++) { var el = cands[i]; var t = (el.innerText || '').trim(); if (!t || t.length > 10) continue; if (/^下一(节|章|课|个)|下一节|下一章/.test(t)) return { el: el }; } return null; } function nextLesson() { var items = getCatalogItems(); if (items.length) { var cur = currentIndex(items); var start = cur >= 0 ? cur + 1 : 0; for (var i = start; i < items.length; i++) { if (isFinished(items[i])) continue; var url = extractUrl(items[i]); var title = (items[i].innerText || '').replace(/\s+/g, ' ').slice(0, 40); if (url) { log('跳转下一节:', title, url); location.href = url; return true; } var clickable = items[i].querySelector('h2[onclick], .title[onclick], [onclick], a'); if (clickable) { log('点击下一节:', title); clickable.click(); return true; } } // 末尾没找到,从头再找一遍未完成的 for (var j = 0; j < items.length; j++) { if (isFinished(items[j])) continue; var u2 = extractUrl(items[j]); if (u2 && u2 !== location.href) { log('从头跳转未完成项:', u2); location.href = u2; return true; } } } var fb = fallbackNextButton(); if (fb && fb.el) { fb.el.click(); return true; } return false; } /* ===================== 弹窗处理 ===================== */ function closeDialogs() { var dialogs = document.querySelectorAll('div.el-dialog, .el-message-box, .layui-layer, .el-overlay-dialog'); for (var i = 0; i < dialogs.length; i++) { var d = dialogs[i]; if (!d.getBoundingClientRect().height) continue; // 不可见的不处理 var label = (d.getAttribute('aria-label') || '') + ' ' + (d.innerText || ''); if (/练习|考试|答题|测验|验证/.test(label)) continue; // 答题弹窗不碰 if (!/提示|温馨提示|注意|提醒/.test(label)) continue; var btn = d.querySelector('.el-dialog__headerbtn, .el-message-box__btns button, .el-button--primary, .layui-layer-btn0, .el-dialog__footer button'); if (btn) { btn.click(); log('已关闭提示弹窗'); } } } /* ===================== 自动答题 ===================== 策略:接口能拿到正确答案就选对;拿不到就选第一个提交(答错也放行)。 支持 Element UI(el-dialog) / layui / ant-design 三种常见弹窗,radio/checkbox/自定义选项三种题型。 */ var QUIZ_SELECTORS = [ 'div.el-dialog[aria-label="随机练习"]', 'div.el-dialog[aria-label*="练习"]', 'div.el-dialog[aria-label*="答题"]', 'div.el-dialog[aria-label*="测验"]', 'div.el-dialog[aria-label*="考核"]', '.el-dialog', '.el-drawer', '.ant-modal', '.ant-drawer', '.layui-layer', '.modal', '[class*=question]', '[class*=Question]' ]; function findQuizDialog(ignoreFlag) { for (var i = 0; i < QUIZ_SELECTORS.length; i++) { var list = document.querySelectorAll(QUIZ_SELECTORS[i]); for (var j = 0; j < list.length; j++) { var d = list[j]; if (!d.getBoundingClientRect().height) continue; // 不可见 if (!ignoreFlag && d.__easAnswered) continue; // 本轮已处理 var txt = d.innerText || ''; if (/温馨提示|^提示$/.test(txt.slice(0, 20))) continue; // 排除纯提示框 var hasOpt = d.querySelector('input[type=radio], input[type=checkbox], .el-radio, .el-checkbox, .option, .answer-item, .question-option'); if (!hasOpt) continue; // 没有可选项,不是答题框 var hasSubmit = d.querySelector('.submit, .el-button--primary, button'); if (!hasSubmit) continue; return d; } } return null; } function optionText(el) { var v = ''; if (el.value) v += el.value + ' '; var p = el.closest ? el.closest('label, .el-radio, .el-checkbox, li, .option') : null; if (p && p.innerText) v += p.innerText; return (v || '').replace(/\s+/g, ' ').trim(); } // 题目指纹:题干+选项文本,用于记录"这道题已经试过几种选法" // 用 sessionStorage 持久化,跳转到下一节后仍能接着轮换,不会每次从头选第一个 var answerSeq = (function () { try { return JSON.parse(sessionStorage.getItem('eas_seq') || '{}') || {}; } catch (e) { return {}; } })(); var globalTries = (function () { try { return parseInt(sessionStorage.getItem('eas_gt') || '0', 10) || 0; } catch (e) { return 0; } })(); function persistSeq() { try { var keys = Object.keys(answerSeq); if (keys.length > 60) { for (var i = 0; i < keys.length - 60; i++) delete answerSeq[keys[i]]; } sessionStorage.setItem('eas_seq', JSON.stringify(answerSeq)); sessionStorage.setItem('eas_gt', String(globalTries)); } catch (e) {} } function quizText(d) { return (d.innerText || '').replace(/\s+/g, ' ').slice(0, 150); } // 超时强行跳过:优先点关闭,其次隐藏遮罩,最后找"跳过/继续"按钮 function forceCloseQuiz(d) { var closeBtn = d.querySelector('.el-dialog__headerbtn, .el-dialog__close, .layui-layer-close, .ant-modal-close, .close, .dialog-close, .el-drawer__close-btn'); if (closeBtn) { try { closeBtn.click(); } catch (e) {} } var btns = d.querySelectorAll('button, a, span'); for (var j = 0; j < btns.length; j++) { var t = (btns[j].innerText || '').trim(); if (/^(跳过|略过|关闭|继续学习|继续播放|下次再说)$/.test(t)) { try { btns[j].click(); } catch (e) {} break; } } try { d.style.display = 'none'; } catch (e) {} var masks = document.querySelectorAll('.el-overlay, .layui-layer-shade, .ant-modal-mask, .v-modal, .el-loading-mask'); for (var i = 0; i < masks.length; i++) { try { if (masks[i].style.display !== 'none') masks[i].style.display = 'none'; } catch (e) {} } d.__easAnswered = true; d.__easKilled = true; toast('答题超时,已强制跳过'); log('强制关闭答题弹窗'); } function tryAnswer() { if (!cfg.autoAnswer) return false; var d = findQuizDialog(); if (!d) return false; var correct = getCorrectAnswer(); var nodes = d.querySelectorAll('input[type=radio], input[type=checkbox]'); var items = []; if (nodes.length) { for (var i = 0; i < nodes.length; i++) { if (nodes[i].disabled) continue; items.push({ el: nodes[i], text: optionText(nodes[i]), multi: nodes[i].type === 'checkbox' }); } } if (!items.length) { var alt = d.querySelectorAll('.el-radio, .el-checkbox, .option, .answer-item, .question-option, li'); for (var k = 0; k < alt.length; k++) { var t = (alt[k].innerText || '').replace(/\s+/g, ' ').trim(); if (!t || t.length > 200) continue; items.push({ el: alt[k], text: t, multi: false }); } } if (!items.length) return false; // 同一弹窗存在超过 2 分钟仍未消失 -> 强制跳过,绝不停在这里等人工 if (!d.__easFirst) d.__easFirst = Date.now(); if (Date.now() - d.__easFirst > 120000) { forceCloseQuiz(d); return false; } d.__easAnswered = true; setTimeout(function () { d.__easAnswered = false; }, 1800); // 1.8 秒后进入下一轮尝试 var key = quizText(d), n = items.length; var tries = (answerSeq[key] = (answerSeq[key] || 0) + 1); var picked = []; if (correct) { log('参考答案:', correct); for (var m = 0; m < items.length; m++) { if (items[m].text.indexOf(correct) !== -1) picked.push(items[m]); } // 参考答案是 "AB" 这种多选串时,逐字符匹配 if (!picked.length && correct.length > 1) { for (var c = 0; c < correct.length; c++) { var ch = correct[c]; for (var n = 0; n < items.length; n++) { if (items[n].text.indexOf(ch) === 0 || items[n].text.indexOf(ch + '.') === 0) { picked.push(items[n]); break; } } } } } if (!picked.length) { // 没有参考答案:按尝试次数轮换选项,单选题在 n 次内必然命中,全程无需人工 if (!items[0].multi) { var base = tries - 1; if (tries === 1) base = globalTries++; // 题目每次随机变时也能持续轮换 var idx = ((base % n) + n) % n; picked.push(items[idx]); log('盲选第 ' + tries + ' 次:选第 ' + (idx + 1) + '/' + n + ' 项'); } else if (tries <= n) { picked.push(items[(tries - 1) % n]); log('多选盲选第 ' + tries + ' 次:只选第 ' + tries + ' 项'); } else if (tries === n + 1) { picked = items.slice(); log('多选盲选:全选'); } else { var cnt = 1 + Math.floor(Math.random() * n); var pool = items.slice(); for (var q = 0; q < cnt && pool.length; q++) { picked.push(pool.splice(Math.floor(Math.random() * pool.length), 1)[0]); } log('多选盲选:随机 ' + picked.length + ' 项'); } } persistSeq(); // 去重:同一 checkbox 点两次会取消勾选 var seen = []; picked = picked.filter(function (it) { if (seen.indexOf(it.el) !== -1) return false; seen.push(it.el); return true; }); for (var p = 0; p < picked.length; p++) { try { picked[p].el.click(); } catch (e) {} } var submit = d.querySelector('.submit') || d.querySelector('.el-dialog__footer .el-button--primary') || d.querySelector('.el-button--primary') || d.querySelector('.el-dialog__footer button') || d.querySelector('.dialog-footer button') || d.querySelector('.layui-layer-btn0') || d.querySelector('.ant-modal-footer button'); if (submit) { setTimeout(function () { try { submit.click(); log('已提交答案'); } catch (e) {} setTimeout(closeResult, 900); }, 300); toast(correct ? '已自动答题(参考答案 ' + correct + ')' : '已自动答题(盲选第 ' + tries + ' 轮)'); return true; } return false; } function closeResult() { var boxes = document.querySelectorAll('.el-message-box, .el-dialog, .ant-modal, .layui-layer'); for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (!b.getBoundingClientRect().height) continue; var txt = (b.innerText || '').replace(/\s+/g, ' '); var isResult = b.classList.contains('el-message-box') || /回答正确|回答错误|答对|答错|恭喜|很遗憾|正确答案|本题/.test(txt); if (!isResult) continue; var btn = b.querySelector('.el-message-box__btns button') || b.querySelector('.el-button--primary') || b.querySelector('.el-dialog__footer button') || b.querySelector('.layui-layer-btn0') || b.querySelector('.ant-modal-footer button'); if (btn) { try { btn.click(); log('已关闭答题结果框'); } catch (e) {} return true; } } return false; } /* ===================== 播放守护 ===================== */ var video = null, lastT = -1, stall = 0, handled = false; function bindVideo(v) { video = v; v.__easSrc = v.currentSrc || v.src || ''; lastT = -1; stall = 0; handled = false; v.addEventListener('ended', function () { onEnded(); }, { once: true }); v.addEventListener('pause', function () { setTimeout(function () { if (video && !video.ended && video.paused) video.play().catch(function () {}); }, 500); }); log('已接管视频'); } function onEnded() { if (handled) return; handled = true; setState('本节已完成', '#276749'); if (!cfg.autoNext) return; toast('本节完成,3 秒后进入下一节'); setTimeout(function () { goNext(0); }, 3000); } function findLinkByText(re) { var cands = document.querySelectorAll('a, button, .btn, span, div'); for (var i = 0; i < cands.length; i++) { var el = cands[i]; if (el.children.length > 2) continue; var t = (el.innerText || '').trim(); if (!t || t.length > 14) continue; if (re.test(t)) return el; } return null; } // 下一节:目录解析重试 3 次 -> 兜底点"返回课程列表" -> 最后自动刷新 function goNext(attempt) { if (nextLesson()) return; if (attempt < 2) { setState('未找到下一节,重试 ' + (attempt + 1) + '/3', '#854F0B'); setTimeout(function () { goNext(attempt + 1); }, 2500); return; } var back = findLinkByText(/返回课程列表|返回列表|我的课程|课程目录|继续学习|返回课程/); if (back) { log('兜底:点击返回课程列表'); back.click(); return; } if (cfg.autoReload) { autoReload('未找到下一节'); return; } setState('已到最后一节', '#854F0B'); toast('本页目录已学完'); } var reloading = false; function autoReload(reason) { if (reloading) return; var cnt = 0; try { cnt = parseInt(sessionStorage.getItem('eas_reload') || '0', 10) || 0; } catch (e) {} if (cnt >= 3) { reloading = true; setState('刷新 3 次仍失败,已停手', '#A32D2D'); toast('本页已自动刷新 3 次仍未恢复,停止自救(可手动刷新继续)'); return; } reloading = true; try { sessionStorage.setItem('eas_reload', String(cnt + 1)); } catch (e) {} toast(reason + ',自动刷新 ' + (cnt + 1) + '/3'); setTimeout(function () { location.reload(); }, 1500); } // 列表页闭环:没有视频时,8 秒后自动点"继续学习"进入未完成课程 var pageLoadAt = Date.now(), enteredOnce = false, lastEnterTry = 0; function tryEnterCourse() { if (!cfg.autoEnter || enteredOnce) return; if (Date.now() - pageLoadAt < 8000) return; if (Date.now() - lastEnterTry < 5000) return; lastEnterTry = Date.now(); var el = findLinkByText(/^(继续学习|开始学习|去学习|进入学习|继续观看|继续听课)$/); if (el) { enteredOnce = true; log('当前页无视频,自动进入未完成课程'); toast('自动进入未完成课程'); try { el.click(); } catch (e) {} } } function tick() { // 答题优先:弹窗可能覆盖视频,甚至整页没有 video if (cfg.autoAnswer) { try { tryAnswer(); } catch (e) { log('答题异常', e.message); } } var v = findVideo(); if (!v) { tryEnterCourse(); setState('未检测到视频', '#5F5E5A'); setProg('--'); return; } // 换视频(新元素或换源)时重新接管,避免 SPA 切课后 handled 未复位 if (v !== video || v.__easSrc !== (v.currentSrc || v.src || '')) bindVideo(v); if (cfg.mute) { v.muted = true; v.volume = 0; } if (v.playbackRate !== cfg.rate) { try { v.playbackRate = cfg.rate; } catch (e) {} } if (cfg.autoDialog) closeDialogs(); var dur = v.duration || 0, ct = v.currentTime || 0; setProg(dur ? fmt(ct) + ' / ' + fmt(dur) + ' (' + Math.floor(ct / dur * 100) + '%)' : fmt(ct)); if (!v.ended && dur && ct >= dur - 1.2) { onEnded(); return; } if (v.ended) { onEnded(); return; } if (v.paused) { // 答题弹窗打开时视频本就该暂停,别去抢 if (cfg.autoAnswer && findQuizDialog(true)) { setState('答题中,等待提交', '#185FA5'); lastT = ct; return; } setState('已暂停,正在恢复', '#854F0B'); v.play().catch(function () {}); return; } if (Math.abs(ct - lastT) < 0.05) { stall++; if (stall === 15) { try { v.currentTime = ct + 0.5; } catch (e) {} log('播放停滞,尝试续播'); } if (stall === 40) { try { v.currentTime = ct + 2; v.play().catch(function () {}); } catch (e) {} log('二次自救:向前跳跃续播'); } if (stall > 45) setState('卡住,准备自动刷新', '#A32D2D'); if (stall >= 90 && cfg.autoReload) autoReload('视频长时间无进展'); } else { stall = 0; if (ct > 30) { try { sessionStorage.removeItem('eas_reload'); } catch (e) {} } } lastT = ct; setState('播放中', '#276749'); } function fmt(sec) { sec = Math.max(0, Math.floor(sec || 0)); var h = Math.floor(sec / 3600), m = Math.floor(sec % 3600 / 60), s = sec % 60; var p = function (n) { return n < 10 ? '0' + n : '' + n; }; return (h ? h + ':' : '') + p(m) + ':' + p(s); } /* ===================== 面板 UI ===================== */ var elState = null, elProg = null, panel = null; function setState(text, color) { if (!elState) return; elState.textContent = text; elState.style.color = color || '#2C2C2A'; } function setProg(text) { if (elProg) elProg.textContent = text; } function toast(msg) { var box = document.getElementById('eas-toast'); if (!box) { box = document.createElement('div'); box.id = 'eas-toast'; document.body.appendChild(box); } box.textContent = msg; box.className = 'eas-toast eas-toast-show'; clearTimeout(box.__t); box.__t = setTimeout(function () { box.className = 'eas-toast'; }, 3200); } function buildPanel() { GM_addStyle([ '.eas-panel{position:fixed;right:24px;bottom:24px;z-index:2147483000;width:248px;background:#fff;', 'border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 6px 24px rgba(0,0,0,.12);', 'font:13px/1.6 -apple-system,"Segoe UI","Microsoft YaHei",sans-serif;color:#2C2C2A;user-select:none}', '.eas-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;', 'border-bottom:1px solid #edf2f7;cursor:move;font-weight:500;font-size:14px}', '.eas-fold{cursor:pointer;color:#888780;padding:0 4px}', '.eas-body{padding:12px 14px}', '.eas-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}', '.eas-row span{color:#5F5E5A;font-size:12px}', '.eas-row b{font-weight:500;font-size:12px;text-align:right;max-width:150px}', '.eas-panel select{padding:3px 6px;border:1px solid #e2e8f0;border-radius:6px;font-size:12px}', '.eas-check{display:block;margin:6px 0;font-size:12px;color:#444441;cursor:pointer}', '.eas-check input{margin-right:6px;vertical-align:-1px}', '.eas-btns{display:flex;gap:8px;margin-top:10px}', '.eas-btns button{flex:1;padding:7px 0;border:none;border-radius:7px;background:#3182ce;color:#fff;', 'font-size:12px;cursor:pointer}', '.eas-btns button.ghost{background:#edf2f7;color:#444441}', '.eas-btns button:hover{opacity:.88}', '.eas-tip{margin:10px 0 0;font-size:11px;color:#888780;line-height:1.5}', '.eas-toast{position:fixed;left:50%;bottom:80px;transform:translate(-50%,12px);z-index:2147483001;', 'background:rgba(26,32,44,.92);color:#fff;padding:10px 18px;border-radius:8px;font-size:13px;', 'opacity:0;pointer-events:none;transition:all .25s}', '.eas-toast-show{opacity:1;transform:translate(-50%,0)}' ].join('')); panel = document.createElement('div'); panel.className = 'eas-panel'; panel.id = 'eas-panel'; panel.innerHTML = [ '
看课助手—
', '
', '
状态启动中…
', '
进度--
', '
倍速
', '', '', '', '', '', '', '', '
', '
', '

全自动:拿不到答案时按选项轮换穷举,不必人工干预。倍速可能被判无效学时,建议先 1.0x 跑通。

', '
' ].join(''); document.body.appendChild(panel); elState = document.getElementById('eas-state'); elProg = document.getElementById('eas-prog'); var rate = document.getElementById('eas-rate'); rate.value = String(cfg.rate); rate.onchange = function () { cfg.rate = parseFloat(rate.value) || 1; saveCfg(); if (video) { try { video.playbackRate = cfg.rate; } catch (e) {} } }; bindCheck('eas-next', 'autoNext'); bindCheck('eas-answer', 'autoAnswer'); bindCheck('eas-verify', 'blockVerify'); bindCheck('eas-dialog', 'autoDialog'); bindCheck('eas-reload', 'autoReload'); bindCheck('eas-enter', 'autoEnter'); bindCheck('eas-mute', 'mute'); document.getElementById('eas-play').onclick = function () { var v = findVideo(); if (!v) { toast('当前页面没有视频'); return; } if (cfg.mute) { v.muted = true; v.volume = 0; } v.play().then(function () { toast('已开始播放'); }).catch(function () { toast('播放被浏览器拦截,请点一下页面再试'); }); }; document.getElementById('eas-skip').onclick = function () { if (!nextLesson()) toast('没找到下一节'); }; var body = document.getElementById('eas-body'); document.getElementById('eas-fold').onclick = function (e) { e.stopPropagation(); var hidden = body.style.display === 'none'; body.style.display = hidden ? '' : 'none'; this.textContent = hidden ? '—' : '+'; }; // 拖动 var head = document.getElementById('eas-head'); var dragging = false, sx = 0, sy = 0, ox = 0, oy = 0; head.addEventListener('mousedown', function (e) { if (e.target.id === 'eas-fold') return; dragging = true; sx = e.clientX; sy = e.clientY; var r = panel.getBoundingClientRect(); ox = r.left; oy = r.top; panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.left = ox + 'px'; panel.style.top = oy + 'px'; e.preventDefault(); }); document.addEventListener('mousemove', function (e) { if (!dragging) return; panel.style.left = Math.max(0, Math.min(window.innerWidth - 60, ox + e.clientX - sx)) + 'px'; panel.style.top = Math.max(0, Math.min(window.innerHeight - 40, oy + e.clientY - sy)) + 'px'; }); document.addEventListener('mouseup', function () { dragging = false; }); } function bindCheck(id, key) { var el = document.getElementById(id); el.checked = !!cfg[key]; el.onchange = function () { cfg[key] = el.checked; saveCfg(); }; } /* ===================== 启动 ===================== */ function boot() { installFetchHook(); buildPanel(); setState('等待视频…', '#5F5E5A'); tick(); setInterval(tick, 1000); log('已启动,无广告 / 无授权 / 无外部请求'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot, { once: true }); } else { boot(); } })();