// ==UserScript== // @name 广东省干部培训网络学院 - 自动学习助手 // @namespace https://scriptcat.org/gdce-auto-study // @version 0.8.0 // @description 广东省干部培训网络学院自动学习脚本:视频监控、自动选课、自动切换课程、自动播放恢复 // @author ScriptCat Developer // @match https://gbpx.gd.gov.cn/gdceportal/study/* // @match https://gbpx.gd.gov.cn/gdceportal/Study/* // @match https://wcs1.shawcoder.xyz/gdcecw/play_pc/* // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // @grant GM_addValueChangeListener // @grant GM_closeTab // @grant unsafeWindow // @run-at document-start // @license MIT // ==/UserScript== (function () { var pageWin = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; var _origAlert = pageWin.alert; var _origConfirm = pageWin.confirm; var _origPrompt = pageWin.prompt; pageWin.alert = function (msg) { console.log("[GDCE自动学习] 拦截alert: " + (msg || "").substring(0, 100)); }; pageWin.confirm = function (msg) { console.log("[GDCE自动学习] 拦截confirm: " + (msg || "").substring(0, 100) + " -> 自动返回true"); return true; }; pageWin.prompt = function (msg, def) { console.log("[GDCE自动学习] 拦截prompt: " + (msg || "").substring(0, 100)); return def || ""; }; var IS_VIDEO_PAGE = /shawcoder\.xyz/.test(location.hostname); var IS_STUDY_CENTER = /gbpx\.gd\.gov\.cn/.test(location.hostname); var _origWindowOpen = pageWin.open; function openInTab(url) { if (typeof GM_openInTab === "function") { console.log("[GDCE自动学习] 通过GM_openInTab打开: " + (url || "").substring(0, 80)); try { var tab = GM_openInTab(url, { active: true }); return tab || true; } catch (e) { console.log("[GDCE自动学习] GM_openInTab失败,回退到window.open: " + e.message); } } var win = _origWindowOpen.call(pageWin, url, "_blank"); if (!win) { console.log("[GDCE自动学习] window.open被弹窗拦截器阻止"); return false; } return win; } if (IS_VIDEO_PAGE) { var _lastAlertStatusTime = 0; var _layerAlertWatch = setInterval(function () { if (pageWin.layer && pageWin.layer.alert) { var origLayerAlert = pageWin.layer.alert; pageWin.layer.alert = function (msg, opts, callback) { console.log("[GDCE自动学习] 拦截layer.alert: " + (msg || "").substring(0, 100)); var idx = origLayerAlert.call(pageWin.layer, msg, opts, callback); setTimeout(function () { pageWin.layer.close(idx); }, 1500); var msgStr = String(msg || ""); var isCourseDone = msgStr.indexOf("不在学习中") !== -1 || msgStr.indexOf("已完成") !== -1; var alertStatus = isCourseDone ? "ended" : "error"; try { var now = Date.now(); if (typeof GM_setValue === "function" && now - _lastAlertStatusTime > 30000) { _lastAlertStatusTime = now; GM_setValue("gdce_video_status", { status: alertStatus, msg: msgStr, ts: now }); console.log("[GDCE自动学习] 已发送视频状态: " + alertStatus + " (layer.alert)"); } else { console.log("[GDCE自动学习] 忽略重复layer.alert状态发送(30秒防抖)"); } var msgType = isCourseDone ? "gdce-video-ended" : "gdce-video-error"; if (pageWin.parent && pageWin.parent !== pageWin.self) { pageWin.parent.postMessage({ type: msgType, msg: msgStr }, "*"); } if (pageWin.opener && !pageWin.opener.closed) { pageWin.opener.postMessage({ type: msgType, msg: msgStr }, "*"); } if (pageWin.top && pageWin.top !== pageWin.self) { pageWin.top.postMessage({ type: msgType, msg: msgStr }, "*"); } if (isCourseDone) { setTimeout(function () { console.log("[GDCE自动学习] 课程不在学习中,尝试关闭视频标签页(安全回退)"); if (typeof GM_closeTab === "function") { try { GM_closeTab(); } catch (e) {} } }, 10000); } } catch (e) {} return idx; }; clearInterval(_layerAlertWatch); } }, 500); setTimeout(function () { clearInterval(_layerAlertWatch); }, 30000); pageWin.open = function (url) { if (url && typeof url === "string") { if (url.indexOf("http") !== 0 && url.indexOf("//") !== 0) { try { url = new URL(url, location.href).href; } catch (e) {} } if (location.href.indexOf("playverif") !== -1 && url.indexOf("playdo") !== -1) { console.log("[GDCE自动学习] playverif页拦截window.open,在当前标签页导航到: " + url.substring(0, 80)); location.href = url; return null; } console.log("[GDCE自动学习] 视频页拦截window.open,通过GM_openInTab打开: " + url.substring(0, 80)); if (openInTab(url)) return null; } return _origWindowOpen.apply(this, arguments); }; } if (IS_STUDY_CENTER && window.self !== window.top) { pageWin.open = function (url) { if (url && typeof url === "string") { if (url.indexOf("http") !== 0 && url.indexOf("//") !== 0) { try { url = new URL(url, location.href).href; } catch (e) {} } console.log("[GDCE自动学习] iframe中拦截window.open,通过GM_openInTab打开: " + url.substring(0, 80)); if (openInTab(url)) return null; } return _origWindowOpen.apply(this, arguments); }; return; } var _isPopupLike = (pageWin.opener && !pageWin.opener.closed) || (window.self === window.top && /CourseDetail/i.test(location.href)); if (IS_STUDY_CENTER && _isPopupLike) { console.log("[GDCE自动学习] 检测到弹窗窗口/新标签页,URL: " + location.href.substring(0, 80)); function onReady() { var layerBtn = document.querySelector(".layui-layer-btn0, .layui-layer-close1"); if (layerBtn) layerBtn.click(); var btn = document.querySelector("#btnConfirm"); if (!btn) btn = document.querySelector("input[type='submit'][value='进入学习']"); if (btn) { console.log("[GDCE自动学习] 弹窗窗口:点击'进入学习'按钮"); btn.click(); } if (/CourseDetail/i.test(location.href)) { setTimeout(function () { console.log("[GDCE自动学习] CourseDetail页面,3秒后自动关闭"); pageWin.close(); }, 3000); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", onReady); } else { onReady(); } return; } var CONFIG = { CHECK_INTERVAL: 3000, PAUSE_CHECK_INTERVAL: 1500, STUCK_THRESHOLD: 15, MAX_RETRIES: 3, MAX_LOGS: 80, ACTION_DELAY: [800, 2000], SELECTORS: { secondIframe: "#secondIframe", thirdIframe: "#thirdIframe", dataMainIframe: "#dataMainIframe", courseTable: "#gvList", courseRow: "#gvList tr", continueStudyLink: "a.courseware-reed", completedCourseLink: "a.courseware-selected", courseNameLink: "a.courseName", enterStudyBtn: "#btnConfirm", navMyCourse: ".secondRouterLink", firstRouterLink: "span.firstRouterLink", childHrefItem: "span.childHref-item", childHrefContainer: "div.childHref", leftHrefChild: "div.leftHrefChild", creditedHours: "#ctl00_CPHMain_lblCredited_Num", requiredHours: "#ctl00_CPHMain_lblRequired_Num", layerConfirm: ".layui-layer-btn0", layerCancel: ".layui-layer-btn1", layerClose: ".layui-layer-close1", videoPlayer: "video" }, URL_PATTERNS: { studyCenter: /studyCenter\.aspx/, courseDetail: /CourseDetail\.aspx/, courseList: /thirdMain|myCourse|LearningCourse/, videoPlay: /shawcoder\.xyz|playverif|playdo/ }, STORAGE_PREFIX: "gdce_auto_study_" }; var Utils = { _logs: [], log: function (level, message) { var ts = new Date().toLocaleTimeString("zh-CN", { hour12: false }); this._logs.push({ timestamp: ts, level: level, message: message }); if (this._logs.length > CONFIG.MAX_LOGS) this._logs.shift(); var p = "[GDCE自动学习]"; if (level === "error") console.error(p + " [" + ts + "] " + message); else if (level === "warn") console.warn(p + " [" + ts + "] " + message); else console.log(p + " [" + ts + "] " + message); if (typeof StatusPanel !== "undefined" && StatusPanel._panel) StatusPanel.updateLogs(); }, info: function (m) { this.log("info", m); }, warn: function (m) { this.log("warn", m); }, error: function (m) { this.log("error", m); }, getLogs: function () { return this._logs.slice(); }, qsIframes: function (sel, root) { root = root || document; var el = root.querySelector(sel); if (el) return el; var frames = root.querySelectorAll("iframe"); for (var i = 0; i < frames.length; i++) { try { var doc = frames[i].contentDocument || (frames[i].contentWindow && frames[i].contentWindow.document); if (!doc) continue; var found = this.qsIframes(sel, doc); if (found) return found; } catch (e) { } } return null; }, qsaIframes: function (sel, root) { root = root || document; var results = Array.prototype.slice.call(root.querySelectorAll(sel)); var frames = root.querySelectorAll("iframe"); for (var i = 0; i < frames.length; i++) { try { var doc = frames[i].contentDocument || (frames[i].contentWindow && frames[i].contentWindow.document); if (!doc) continue; results = results.concat(this.qsaIframes(sel, doc)); } catch (e) { } } return results; }, getIframeDoc: function (iframeSelector) { var iframe = document.querySelector(iframeSelector); if (!iframe) return null; try { return iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); } catch (e) { this.warn("无法访问iframe: " + iframeSelector); return null; } }, waitForElement: function (selector, timeout, root) { timeout = timeout || 30000; root = root || document; return new Promise(function (resolve, reject) { var el = root.querySelector(selector); if (el) return resolve(el); var observer = new MutationObserver(function () { var found = root.querySelector(selector); if (found) { observer.disconnect(); resolve(found); } }); observer.observe(root, { childList: true, subtree: true }); setTimeout(function () { observer.disconnect(); reject(new Error("等待元素超时: " + selector)); }, timeout); }); }, waitForElementIframes: function (selector, timeout) { timeout = timeout || 60000; var self = this; return new Promise(function (resolve, reject) { var el = self.qsIframes(selector); if (el) return resolve(el); var start = Date.now(); var timer = setInterval(function () { var found = self.qsIframes(selector); if (found) { clearInterval(timer); resolve(found); } else if (Date.now() - start > timeout) { clearInterval(timer); reject(new Error("等待元素超时(iframe穿透): " + selector)); } }, 1000); }); }, waitForElementDisappearIframes: function (selector, timeout) { timeout = timeout || 15000; var self = this; return new Promise(function (resolve) { var el = self.qsIframes(selector); if (!el) return resolve(true); var start = Date.now(); var timer = setInterval(function () { var found = self.qsIframes(selector); if (!found) { clearInterval(timer); resolve(true); } else if (Date.now() - start > timeout) { clearInterval(timer); resolve(false); } }, 500); }); }, findByText: function (tagName, text, root) { root = root || document; var els = root.querySelectorAll(tagName); for (var i = 0; i < els.length; i++) { if (els[i].textContent.indexOf(text) !== -1) return els[i]; } return null; }, findByTextIframes: function (tagName, text) { var all = this.qsaIframes(tagName); for (var i = 0; i < all.length; i++) { if (all[i].textContent.indexOf(text) !== -1) return all[i]; } return null; }, safeClick: function (element) { if (!element) return false; try { element.click(); return true; } catch (e) { this.error("点击元素失败: " + e.message); return false; } }, observeDOM: function (target, options, callback) { var observer = new MutationObserver(callback); observer.observe(target, options); return observer; }, sleep: function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); }, randomDelay: function () { var min = CONFIG.ACTION_DELAY[0], max = CONFIG.ACTION_DELAY[1]; var ms = Math.floor(Math.random() * (max - min) + min); return this.sleep(ms); }, getConfig: function (key, defaultValue) { try { var raw = localStorage.getItem(CONFIG.STORAGE_PREFIX + key); return raw !== null ? JSON.parse(raw) : defaultValue; } catch (e) { return defaultValue; } }, setConfig: function (key, value) { try { localStorage.setItem(CONFIG.STORAGE_PREFIX + key, JSON.stringify(value)); } catch (e) { this.error("保存配置失败: " + e.message); } }, formatTime: function (seconds) { if (!seconds || isNaN(seconds)) return "00:00"; var h = Math.floor(seconds / 3600); var m = Math.floor((seconds % 3600) / 60); var s = Math.floor(seconds % 60); if (h > 0) return h + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2); return ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2); } }; var PageDetector = { _currentPage: null, _isInIframe: false, init: function () { this._isInIframe = window.self !== window.top; this._currentPage = this.detectPageType(); Utils.info("页面检测 -> 类型: " + this._currentPage + ", iframe: " + this._isInIframe + ", URL: " + location.pathname); }, detectPageType: function () { var path = location.pathname; if (CONFIG.URL_PATTERNS.studyCenter.test(path)) return "studyCenter"; if (CONFIG.URL_PATTERNS.courseDetail.test(path)) return "courseDetail"; if (CONFIG.URL_PATTERNS.videoPlay.test(path)) return "videoPlay"; if (CONFIG.URL_PATTERNS.courseList.test(path)) return "courseList"; if (Utils.qsIframes(CONFIG.SELECTORS.videoPlayer)) return "videoPlay"; if (Utils.qsIframes(CONFIG.SELECTORS.enterStudyBtn)) return "courseDetail"; if (Utils.qsIframes(CONFIG.SELECTORS.courseTable)) return "courseList"; return "unknown"; }, get currentPage() { return this._currentPage; }, get isInIframe() { return this._isInIframe; }, isTopFrame: function () { return window.self === window.top; }, onPageChange: function (callback) { var lastHref = location.href; var self = this; var origPush = history.pushState; history.pushState = function () { origPush.apply(this, arguments); if (location.href !== lastHref) { lastHref = location.href; self._currentPage = self.detectPageType(); callback(self._currentPage); } }; window.addEventListener("popstate", function () { if (location.href !== lastHref) { lastHref = location.href; self._currentPage = self.detectPageType(); callback(self._currentPage); } }); var observer = new MutationObserver(function () { var newType = self.detectPageType(); if (newType !== self._currentPage && newType !== "unknown") { self._currentPage = newType; callback(newType); } }); observer.observe(document, { childList: true, subtree: true }); } }; var PageAnalyzer = { analyze: function () { Utils.info("========== 页面结构分析开始 =========="); this._dumpIframeTree(document, 0); this._findKeyElements(); this._dumpAllIframeContent(); this._dumpTableDetail(); Utils.info("========== 页面结构分析结束 =========="); }, _dumpIframeTree: function (doc, depth) { var indent = ""; for (var d = 0; d < depth; d++) indent += " "; var iframes = doc.querySelectorAll("iframe"); Utils.info(indent + "iframe数量: " + iframes.length); for (var i = 0; i < iframes.length; i++) { var iframe = iframes[i]; var src = iframe.src || iframe.getAttribute("src") || "(无src)"; var id = iframe.id || "(无id)"; var cls = iframe.className || "(无class)"; Utils.info(indent + " iframe[" + i + "] id=" + id + " class=" + cls + " src=" + src); try { var childDoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); if (childDoc) { this._dumpIframeTree(childDoc, depth + 1); } else { Utils.info(indent + " (无法访问 - 可能跨域)"); } } catch (e) { Utils.info(indent + " (无法访问: " + e.message + ")"); } } }, _findKeyElements: function () { var selectors = { "secondIframe": CONFIG.SELECTORS.secondIframe, "thirdIframe": CONFIG.SELECTORS.thirdIframe, "courseItem": CONFIG.SELECTORS.courseItem, "enterStudyBtn": CONFIG.SELECTORS.enterStudyBtn, "nextSectionBtn": CONFIG.SELECTORS.nextSectionBtn, "creditedHours": CONFIG.SELECTORS.creditedHours, "requiredHours": CONFIG.SELECTORS.requiredHours, "layerConfirm": CONFIG.SELECTORS.layerConfirm, "videoPlayer": CONFIG.SELECTORS.videoPlayer }; Utils.info("--- 选择器匹配结果 ---"); var keys = Object.keys(selectors); for (var i = 0; i < keys.length; i++) { var name = keys[i]; var sel = selectors[name]; var found = Utils.qsIframes(sel); var count = Utils.qsaIframes(sel).length; Utils.info(" " + name + " (" + sel + "): " + (found ? "找到" : "未找到") + (count > 0 ? " (共" + count + "个)" : "")); } Utils.info("--- 文本搜索结果 ---"); var textSearches = ["进入学习", "继续学习", "我的课程", "课程", "下一节", "下一课", "开始学习", "学习", "操作"]; for (var j = 0; j < textSearches.length; j++) { var text = textSearches[j]; var el = Utils.findByTextIframes("a, button, span, div, li, p, input", text); Utils.info(" 文本\"" + text + "\": " + (el ? "找到 " + this._describeElement(el) : "未找到")); } }, _dumpAllIframeContent: function () { Utils.info("--- 所有iframe详细内容 ---"); this._dumpDocContent(document, "顶层", 0); }, _dumpDocContent: function (doc, label, depth) { if (depth > 5) return; var indent = ""; for (var d = 0; d < depth; d++) indent += " "; try { Utils.info(indent + "[" + label + "] URL: " + (doc.location || doc.defaultView.location).href); } catch (e) { Utils.info(indent + "[" + label + "] URL: (无法获取)"); } var body = doc.body; if (!body) { Utils.info(indent + " (无body)"); return; } this._dumpElementTree(body, indent + " ", 0, 3); var iframes = doc.querySelectorAll("iframe"); for (var i = 0; i < iframes.length; i++) { try { var childDoc = iframes[i].contentDocument || (iframes[i].contentWindow && iframes[i].contentWindow.document); if (childDoc) { this._dumpDocContent(childDoc, "iframe[" + i + "] #" + (iframes[i].id || ""), depth + 1); } } catch (e) { Utils.info(indent + " iframe[" + i + "]: 无法访问"); } } }, _dumpElementTree: function (el, indent, depth, maxDepth) { if (depth > maxDepth) return; var children = el.children; for (var i = 0; i < children.length; i++) { var child = children[i]; var tag = child.tagName.toLowerCase(); if (tag === "script" || tag === "style" || tag === "link") continue; var id = child.id ? "#" + child.id : ""; var cls = child.className && typeof child.className === "string" ? "." + child.className.split(" ").join(".") : ""; var text = (child.textContent || "").trim().replace(/\s+/g, " ").substring(0, 80); var childCount = child.children.length; Utils.info(indent + tag + id + cls + (childCount > 0 ? " [" + childCount + "子元素]" : "") + " \"" + text + "\""); if (tag === "a" || tag === "button" || tag === "input" || tag === "video") { var outer = child.outerHTML.substring(0, 300); Utils.info(indent + " -> HTML: " + outer); } this._dumpElementTree(child, indent + " ", depth + 1, maxDepth); } }, _dumpTableDetail: function () { Utils.info("--- 表格详细分析(课程列表) ---"); var tables = Utils.qsaIframes("table"); Utils.info("找到 " + tables.length + " 个table元素"); for (var t = 0; t < tables.length; t++) { var table = tables[t]; var tid = table.id ? "#" + table.id : ""; var tcls = table.className ? "." + table.className.split(" ").join(".") : ""; Utils.info("table[" + t + "]" + tid + tcls); var rows = table.querySelectorAll("tr"); Utils.info(" 共 " + rows.length + " 行"); for (var r = 0; r < Math.min(rows.length, 10); r++) { var row = rows[r]; var cells = row.querySelectorAll("td, th"); var rowInfo = " 行[" + r + "]: "; for (var c = 0; c < cells.length; c++) { var cell = cells[c]; var cellText = (cell.textContent || "").trim().replace(/\s+/g, " ").substring(0, 40); rowInfo += "[" + cellText + "] "; } Utils.info(rowInfo); var links = row.querySelectorAll("a, button, input"); for (var l = 0; l < links.length; l++) { Utils.info(" 链接/按钮: " + links[l].outerHTML.substring(0, 300)); } } } }, _describeElement: function (el) { if (!el) return "(null)"; var tag = el.tagName || "?"; var id = el.id ? "#" + el.id : ""; var cls = el.className && typeof el.className === "string" ? "." + el.className.split(" ").join(".") : ""; var text = (el.textContent || "").trim().substring(0, 40); return tag + id + cls + " \"" + text + "\""; } }; function des(beinetkey, message, encrypt, mode, iv) { var spfunction1 = new Array(0x1010400, 0, 0x10000, 0x1010404, 0x1010004, 0x10404, 0x4, 0x10000, 0x400, 0x1010400, 0x1010404, 0x400, 0x1000404, 0x1010004, 0x1000000, 0x4, 0x404, 0x1000400, 0x1000400, 0x10400, 0x10400, 0x1010000, 0x1010000, 0x1000404, 0x10004, 0x1000004, 0x1000004, 0x10004, 0, 0x404, 0x10404, 0x1000000, 0x10000, 0x1010404, 0x4, 0x1010000, 0x1010400, 0x1000000, 0x1000000, 0x400, 0x1010004, 0x10000, 0x10400, 0x1000004, 0x400, 0x4, 0x1000404, 0x10404, 0x1010404, 0x10004, 0x1010000, 0x1000404, 0x1000004, 0x404, 0x10404, 0x1010400, 0x404, 0x1000400, 0x1000400, 0, 0x10004, 0x10400, 0, 0x1010004); var spfunction2 = new Array(-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0, -0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0, 0x108000, 0x100020, -0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020, -0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, -0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0, 0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, -0x7fff8000, 0x20, -0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0, 0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000, -0x7fefffe0, -0x7fef7fe0, 0x108000); var spfunction3 = new Array(0x208, 0x8020200, 0, 0x8020008, 0x8000200, 0, 0x20208, 0x8000200, 0x20008, 0x8000008, 0x8000008, 0x20000, 0x8020208, 0x20008, 0x8020000, 0x208, 0x8000000, 0x8, 0x8020200, 0x200, 0x20200, 0x8020000, 0x8020008, 0x20208, 0x8000208, 0x20200, 0x20000, 0x8000208, 0x8, 0x8020208, 0x200, 0x8000000, 0x8020200, 0x8000000, 0x20008, 0x208, 0x20000, 0x8020200, 0x8000200, 0, 0x200, 0x20008, 0x8020208, 0x8000200, 0x8000008, 0x200, 0, 0x8020008, 0x8000208, 0x20000, 0x8000000, 0x8020208, 0x8, 0x20208, 0x20200, 0x8000008, 0x8020000, 0x8000208, 0x208, 0x8020000, 0x20208, 0x8, 0x8020008, 0x20200); var spfunction4 = new Array(0x802001, 0x2081, 0x2081, 0x80, 0x802080, 0x800081, 0x800001, 0x2001, 0, 0x802000, 0x802000, 0x802081, 0x81, 0, 0x800080, 0x800001, 0x1, 0x2000, 0x800000, 0x802001, 0x80, 0x800000, 0x2001, 0x2080, 0x800081, 0x1, 0x2080, 0x800080, 0x2000, 0x802080, 0x802081, 0x81, 0x800080, 0x800001, 0x802000, 0x802081, 0x81, 0, 0, 0x802000, 0x2080, 0x800080, 0x800081, 0x1, 0x802001, 0x2081, 0x2081, 0x80, 0x802081, 0x81, 0x1, 0x2000, 0x800001, 0x2001, 0x802080, 0x800081, 0x2001, 0x2080, 0x800000, 0x802001, 0x80, 0x800000, 0x2000, 0x802080); var spfunction5 = new Array(0x100, 0x2080100, 0x2080000, 0x42000100, 0x80000, 0x100, 0x40000000, 0x2080000, 0x40080100, 0x80000, 0x2000100, 0x40080100, 0x42000100, 0x42080000, 0x80100, 0x40000000, 0x2000000, 0x40080000, 0x40080000, 0, 0x40000100, 0x42080100, 0x42080100, 0x2000100, 0x42080000, 0x40000100, 0, 0x42000000, 0x2080100, 0x2000000, 0x42000000, 0x80100, 0x80000, 0x42000100, 0x100, 0x2000000, 0x40000000, 0x2080000, 0x42000100, 0x40080100, 0x2000100, 0x40000000, 0x42080000, 0x2080100, 0x40080100, 0x100, 0x2000000, 0x42080000, 0x42080100, 0x80100, 0x42000000, 0x42080100, 0x2080000, 0, 0x40080000, 0x42000000, 0x80100, 0x2000100, 0x40000100, 0x80000, 0, 0x40080000, 0x2080100, 0x40000100); var spfunction6 = new Array(0x20000010, 0x20400000, 0x4000, 0x20404010, 0x20400000, 0x10, 0x20404010, 0x400000, 0x20004000, 0x404010, 0x400000, 0x20000010, 0x400010, 0x20004000, 0x20000000, 0x4010, 0, 0x400010, 0x20004010, 0x4000, 0x404000, 0x20004010, 0x10, 0x20400010, 0x20400010, 0, 0x404010, 0x20404000, 0x4010, 0x404000, 0x20404000, 0x20000000, 0x20004000, 0x10, 0x20400010, 0x404000, 0x20404010, 0x400000, 0x4010, 0x20000010, 0x400000, 0x20004000, 0x20000000, 0x4010, 0x20000010, 0x20404010, 0x404000, 0x20400000, 0x404010, 0x20404000, 0, 0x20400010, 0x10, 0x4000, 0x20400000, 0x404010, 0x4000, 0x400010, 0x20004010, 0, 0x20404000, 0x20000000, 0x400010, 0x20004010); var spfunction7 = new Array(0x200000, 0x4200002, 0x4000802, 0, 0x800, 0x4000802, 0x200802, 0x4200800, 0x4200802, 0x200000, 0, 0x4000002, 0x2, 0x4000000, 0x4200002, 0x802, 0x4000800, 0x200802, 0x200002, 0x4000800, 0x4000002, 0x4200000, 0x4200800, 0x200002, 0x4200000, 0x800, 0x802, 0x4200802, 0x200800, 0x2, 0x4000000, 0x200800, 0x4000000, 0x200800, 0x200000, 0x4000802, 0x4000802, 0x4200002, 0x4200002, 0x2, 0x200002, 0x4000000, 0x4000800, 0x200000, 0x4200800, 0x802, 0x200802, 0x4200800, 0x802, 0x4000002, 0x4200802, 0x4200000, 0x200800, 0, 0x2, 0x4200802, 0, 0x200802, 0x4200000, 0x800, 0x4000002, 0x4000800, 0x800, 0x200002); var spfunction8 = new Array(0x10001040, 0x1000, 0x40000, 0x10041040, 0x10000000, 0x10001040, 0x40, 0x10000000, 0x40040, 0x10040000, 0x10041040, 0x41000, 0x10041000, 0x41040, 0x1000, 0x40, 0x10040000, 0x10000040, 0x10001000, 0x1040, 0x41000, 0x40040, 0x10040040, 0x10041000, 0x1040, 0, 0, 0x10040040, 0x10000040, 0x10001000, 0x41040, 0x40000, 0x41040, 0x40000, 0x10041000, 0x1000, 0x40, 0x10040040, 0x1000, 0x41040, 0x10001000, 0x40, 0x10000040, 0x10040000, 0x10040040, 0x10000000, 0x40000, 0x10001040, 0, 0x10041040, 0x40040, 0x10000040, 0x10040000, 0x10001000, 0x10001040, 0, 0x10041040, 0x41000, 0x41000, 0x1040, 0x1040, 0x40040, 0x10000000, 0x10041000); var keys = des_createKeys(beinetkey); var m = 0, i, j, temp, temp2, right1, right2, left, right, looping; var cbcleft, cbcleft2, cbcright, cbcright2 var endloop, loopinc; var len = message.length; var chunk = 0; var iterations = keys.length == 32 ? 3 : 9; if (iterations == 3) { looping = encrypt ? new Array(0, 32, 2) : new Array(30, -2, -2); } else { looping = encrypt ? new Array(0, 32, 2, 62, 30, -2, 64, 96, 2) : new Array(94, 62, -2, 32, 64, 2, 30, -2, -2); } message += '\0\0\0\0\0\0\0\0'; result = ''; tempresult = ''; if (mode == 1) { cbcleft = (iv.charCodeAt(m++) << 24) | (iv.charCodeAt(m++) << 16) | (iv.charCodeAt(m++) << 8) | iv.charCodeAt(m++); cbcright = (iv.charCodeAt(m++) << 24) | (iv.charCodeAt(m++) << 16) | (iv.charCodeAt(m++) << 8) | iv.charCodeAt(m++); m = 0; } while (m < len) { if (encrypt) { left = (message.charCodeAt(m++) << 16) | message.charCodeAt(m++); right = (message.charCodeAt(m++) << 16) | message.charCodeAt(m++); } else { left = (message.charCodeAt(m++) << 24) | (message.charCodeAt(m++) << 16) | (message.charCodeAt(m++) << 8) | message.charCodeAt(m++); right = (message.charCodeAt(m++) << 24) | (message.charCodeAt(m++) << 16) | (message.charCodeAt(m++) << 8) | message.charCodeAt(m++); } if (mode == 1) { if (encrypt) { left ^= cbcleft; right ^= cbcright; } else { cbcleft2 = cbcleft; cbcright2 = cbcright; cbcleft = left; cbcright = right; } } temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4); temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16); temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2); temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8); temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1); left = ((left << 1) | (left >>> 31)); right = ((right << 1) | (right >>> 31)); for (j = 0; j < iterations; j += 3) { endloop = looping[j + 1]; loopinc = looping[j + 2]; for (i = looping[j]; i != endloop; i += loopinc) { right1 = right ^ keys[i]; right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1]; temp = left; left = right; right = temp ^ (spfunction2[(right1 >>> 24) & 0x3f] | spfunction4[(right1 >>> 16) & 0x3f] | spfunction6[(right1 >>> 8) & 0x3f] | spfunction8[right1 & 0x3f] | spfunction1[(right2 >>> 24) & 0x3f] | spfunction3[(right2 >>> 16) & 0x3f] | spfunction5[(right2 >>> 8) & 0x3f] | spfunction7[right2 & 0x3f]); } temp = left; left = right; right = temp; } left = ((left >>> 1) | (left << 31)); right = ((right >>> 1) | (right << 31)); temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1); temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8); temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2); temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16); temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4); if (mode == 1) { if (encrypt) { cbcleft = left; cbcright = right; } else { left ^= cbcleft2; right ^= cbcright2; } } if (encrypt) { tempresult += String.fromCharCode((left >>> 24), ((left >>> 16) & 0xff), ((left >>> 8) & 0xff), (left & 0xff), (right >>> 24), ((right >>> 16) & 0xff), ((right >>> 8) & 0xff), (right & 0xff)); } else { if (((left >>> 16) & 0xffff) != 0) { tempresult += String.fromCharCode(((left >>> 16) & 0xffff)); } if ((left & 0xffff) != 0) { tempresult += String.fromCharCode((left & 0xffff)); } if (((right >>> 16) & 0xffff) != 0) { tempresult += String.fromCharCode(((right >>> 16) & 0xffff)); } if ((right & 0xffff) != 0) { tempresult += String.fromCharCode((right & 0xffff)); } } encrypt ? chunk += 16 : chunk += 8; if (chunk == 512) { result += tempresult; tempresult = ''; chunk = 0; } } return result + tempresult; } function des_createKeys(beinetkey) { var pc2bytes0 = new Array(0, 0x4, 0x20000000, 0x20000004, 0x10000, 0x10004, 0x20010000, 0x20010004, 0x200, 0x204, 0x20000200, 0x20000204, 0x10200, 0x10204, 0x20010200, 0x20010204); var pc2bytes1 = new Array(0, 0x1, 0x100000, 0x100001, 0x4000000, 0x4000001, 0x4100000, 0x4100001, 0x100, 0x101, 0x100100, 0x100101, 0x4000100, 0x4000101, 0x4100100, 0x4100101); var pc2bytes2 = new Array(0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808, 0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808); var pc2bytes3 = new Array(0, 0x200000, 0x8000000, 0x8200000, 0x2000, 0x202000, 0x8002000, 0x8202000, 0x20000, 0x220000, 0x8020000, 0x8220000, 0x22000, 0x222000, 0x8022000, 0x8222000); var pc2bytes4 = new Array(0, 0x40000, 0x10, 0x40010, 0, 0x40000, 0x10, 0x40010, 0x1000, 0x41000, 0x1010, 0x41010, 0x1000, 0x41000, 0x1010, 0x41010); var pc2bytes5 = new Array(0, 0x400, 0x20, 0x420, 0, 0x400, 0x20, 0x420, 0x2000000, 0x2000400, 0x2000020, 0x2000420, 0x2000000, 0x2000400, 0x2000020, 0x2000420); var pc2bytes6 = new Array(0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002, 0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002); var pc2bytes7 = new Array(0, 0x10000, 0x800, 0x10800, 0x20000000, 0x20010000, 0x20000800, 0x20010800, 0x20000, 0x30000, 0x20800, 0x30800, 0x20020000, 0x20030000, 0x20020800, 0x20030800); var pc2bytes8 = new Array(0, 0x40000, 0, 0x40000, 0x2, 0x40002, 0x2, 0x40002, 0x2000000, 0x2040000, 0x2000000, 0x2040000, 0x2000002, 0x2040002, 0x2000002, 0x2040002); var pc2bytes9 = new Array(0, 0x10000000, 0x8, 0x10000008, 0, 0x10000000, 0x8, 0x10000008, 0x400, 0x10000400, 0x408, 0x10000408, 0x400, 0x10000400, 0x408, 0x10000408); var pc2bytes10 = new Array(0, 0x20, 0, 0x20, 0x100000, 0x100020, 0x100000, 0x100020, 0x2000, 0x2020, 0x2000, 0x2020, 0x102000, 0x102020, 0x102000, 0x102020); var pc2bytes11 = new Array(0, 0x1000000, 0x200, 0x1000200, 0x200000, 0x1200000, 0x200200, 0x1200200, 0x4000000, 0x5000000, 0x4000200, 0x5000200, 0x4200000, 0x5200000, 0x4200200, 0x5200200); var pc2bytes12 = new Array(0, 0x1000, 0x8000000, 0x8001000, 0x80000, 0x81000, 0x8080000, 0x8081000, 0x10, 0x1010, 0x8000010, 0x8001010, 0x80010, 0x81010, 0x8080010, 0x8081010); var pc2bytes13 = new Array(0, 0x4, 0x100, 0x104, 0, 0x4, 0x100, 0x104, 0x1, 0x5, 0x101, 0x105, 0x1, 0x5, 0x101, 0x105); var iterations = beinetkey.length >= 24 ? 3 : 1; var keys = new Array(32 * iterations); var shifts = new Array(0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0); var lefttemp, righttemp, m = 0, n = 0, temp; for (var j = 0; j < iterations; j++) { left = (beinetkey.charCodeAt(m++) << 24) | (beinetkey.charCodeAt(m++) << 16) | (beinetkey.charCodeAt(m++) << 8) | beinetkey.charCodeAt(m++); right = (beinetkey.charCodeAt(m++) << 24) | (beinetkey.charCodeAt(m++) << 16) | (beinetkey.charCodeAt(m++) << 8) | beinetkey.charCodeAt(m++); temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4); temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16); temp = ((left >>> 2) ^ right) & 0x33333333; right ^= temp; left ^= (temp << 2); temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16); temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1); temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8); temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1); temp = (left << 8) | ((right >>> 20) & 0x000000f0); left = (right << 24) | ((right << 8) & 0xff0000) | ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0); right = temp; for (i = 0; i < shifts.length; i++) { if (shifts[i]) { left = (left << 2) | (left >>> 26); right = (right << 2) | (right >>> 26); } else { left = (left << 1) | (left >>> 27); right = (right << 1) | (right >>> 27); } left &= -0xf; right &= -0xf; lefttemp = pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf] | pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf] | pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf] | pc2bytes6[(left >>> 4) & 0xf]; righttemp = pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf] | pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf] | pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf] | pc2bytes13[(right >>> 4) & 0xf]; temp = ((righttemp >>> 16) ^ lefttemp) & 0x0000ffff; keys[n++] = lefttemp ^ temp; keys[n++] = righttemp ^ (temp << 16); } } return keys; } function stringToHex(s) { var r = ''; var hexes = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); for (var i = 0; i < (s.length); i++) { r += hexes[s.charCodeAt(i) >> 4] + hexes[s.charCodeAt(i) & 0xf]; } return r; } function hexToString(s) { var r = ""; for (var i = 0; i < s.length; i += 2) { var sxx = parseInt(s.substring(i, i + 2), 16); r += String.fromCharCode(sxx); } return r; } function encMe(s, k) { return stringToHex(des(k, s, 1, 0)); } function uncMe(s, k) { return des(k, hexToString(s), 0, 0); } var VideoMonitor = { _video: null, _isMonitoring: false, _monitorTimer: null, _pauseCheckTimer: null, _lastProgress: 0, _stuckCount: 0, _isRateLocked: false, _onVideoEnd: null, _onError: null, _handlers: {}, _userInteracted: false, _autoplayRetries: 0, _maxAutoplayRetries: 30, _noSourceRetries: 0, _maxNoSourceRetries: 10, _lastHeartbeatTs: 0, HEARTBEAT_INTERVAL: 50000, init: function (onVideoEnd, onError) { this._onVideoEnd = onVideoEnd; this._onError = onError; Utils.info("视频监控模块初始化"); var self = this; var events = ["click", "keydown", "touchstart"]; events.forEach(function (evt) { document.addEventListener(evt, function () { self._userInteracted = true; }, { once: false, passive: true }); }); }, findVideo: function (timeout) { timeout = timeout || 60000; var self = this; Utils.info("正在查找视频元素..."); var video = Utils.qsIframes(CONFIG.SELECTORS.videoPlayer); if (video) { Utils.info("找到视频元素(即时)"); return Promise.resolve(video); } return Utils.waitForElementIframes(CONFIG.SELECTORS.videoPlayer, timeout) .then(function (v) { Utils.info("等待到视频元素出现"); return v; }) .catch(function () { Utils.warn("未找到视频元素"); return null; }); }, lockPlaybackRate: function (video) { if (this._isRateLocked) return; try { video.playbackRate = 1; var desc = Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, "playbackRate"); if (desc && desc.set) { var originalSetter = desc.set; Object.defineProperty(video, "playbackRate", { get: function () { return 1; }, set: function (val) { if (val !== 1) Utils.warn("检测到尝试修改播放速率为 " + val + "x,已阻止"); originalSetter.call(this, 1); }, configurable: true }); } this._isRateLocked = true; Utils.info("已锁定播放速率为1倍速"); } catch (e) { Utils.error("锁定播放速率失败: " + e.message); } }, unlockPlaybackRate: function (video) { if (!this._isRateLocked || !video) return; try { delete video.playbackRate; this._isRateLocked = false; Utils.info("已解锁播放速率"); } catch (e) { Utils.error("解锁播放速率失败: " + e.message); } }, startMonitoring: function () { if (this._isMonitoring) { Utils.info("视频监控已在运行中,跳过重复启动"); return; } var self = this; this.findVideo().then(function (video) { if (!video) { Utils.error("无法找到视频元素,监控未启动"); return; } self._video = video; self._isMonitoring = true; self._lastProgress = video.currentTime; self._stuckCount = 0; self._autoplayRetries = 0; self._noSourceRetries = 0; self.lockPlaybackRate(video); self._bindEvents(); self._monitorTimer = setInterval(function () { self._checkStatus(); }, CONFIG.CHECK_INTERVAL); self._pauseCheckTimer = setInterval(function () { self._checkAndResume(); }, CONFIG.PAUSE_CHECK_INTERVAL); Utils.info("视频监控已启动"); StatusPanel.updateVideoStatus("monitoring"); self._tryAutoplay(); }); }, stopMonitoring: function () { if (!this._isMonitoring) return; this._isMonitoring = false; clearInterval(this._monitorTimer); this._monitorTimer = null; clearInterval(this._pauseCheckTimer); this._pauseCheckTimer = null; if (this._video) { this.unlockPlaybackRate(this._video); this._unbindEvents(); } this._video = null; Utils.info("视频监控已停止"); StatusPanel.updateVideoStatus("stopped"); }, _tryAutoplay: function () { if (!this._video) return; if (!this._hasValidSource()) { Utils.warn("视频源无效,跳过自动播放"); return; } var self = this; if (this._userInteracted) { if (this._video.paused) { this._video.play().then(function () { Utils.info("自动播放成功"); }).catch(function (e) { Utils.warn("自动播放失败: " + e.message); }); } return; } if (this._video.paused && this._autoplayRetries < this._maxAutoplayRetries) { this._autoplayRetries++; var wasMuted = this._video.muted; this._video.muted = true; var playPromise = this._video.play(); if (playPromise && playPromise.then) { playPromise.then(function () { Utils.info("静音自动播放成功,等待用户交互后恢复声音"); self._waitForInteractionAndUnmute(); }).catch(function (e) { if (e.message && e.message.indexOf("no supported sources") !== -1) { if (self._handleNoSource()) return; } Utils.warn("静音自动播放也失败 (尝试 " + self._autoplayRetries + "/" + self._maxAutoplayRetries + "): " + e.message); self._video.muted = wasMuted; }); } } }, _waitForInteractionAndUnmute: function () { var self = this; if (this._userInteracted) { this._video.muted = false; Utils.info("已恢复视频声音"); return; } Utils.info("等待用户交互以恢复声音(请点击页面任意位置)..."); StatusPanel.showAlert("请点击页面任意位置以恢复视频声音"); var handler = function () { setTimeout(function () { if (self._video) { self._video.muted = false; Utils.info("用户已交互,恢复视频声音"); StatusPanel.hideAlert(); } }, 500); }; document.addEventListener("click", handler, { once: true }); document.addEventListener("keydown", handler, { once: true }); }, _bindEvents: function () { var v = this._video; if (!v) return; var self = this; var handlers = { play: function () { Utils.info("视频开始播放"); StatusPanel.updateVideoStatus("playing"); }, pause: function () { Utils.info("视频已暂停"); StatusPanel.updateVideoStatus("paused"); }, ended: function () { Utils.info("视频播放完毕"); StatusPanel.updateVideoStatus("ended"); self.stopMonitoring(); if (self._onVideoEnd) self._onVideoEnd(); }, error: function (e) { Utils.error("视频播放错误"); StatusPanel.updateVideoStatus("error"); if (self._onError) self._onError(e); }, waiting: function () { Utils.info("视频缓冲中..."); StatusPanel.updateVideoStatus("buffering"); }, playing: function () { Utils.info("视频恢复播放"); StatusPanel.updateVideoStatus("playing"); } }; var keys = Object.keys(handlers); for (var i = 0; i < keys.length; i++) { v.addEventListener(keys[i], handlers[keys[i]]); self._handlers[keys[i]] = handlers[keys[i]]; } }, _unbindEvents: function () { var v = this._video; if (!v) return; var keys = Object.keys(this._handlers); for (var i = 0; i < keys.length; i++) { v.removeEventListener(keys[i], this._handlers[keys[i]]); } this._handlers = {}; }, _isVideoCompleted: function () { if (!this._video) return false; var duration = this._video.duration; if (!duration || !isFinite(duration) || duration <= 0) return false; return this._video.currentTime >= duration - 1; }, _hasValidSource: function () { if (!this._video) return false; if (this._video.networkState === 3) return false; var hasSrc = !!this._video.src || this._video.querySelector("source"); if (!hasSrc) return false; var dur = this._video.duration; if (dur === 0 || (dur && !isFinite(dur))) return false; return true; }, _handleNoSource: function () { this._noSourceRetries++; if (this._noSourceRetries >= this._maxNoSourceRetries) { Utils.error("视频源无效(no supported sources),已重试" + this._noSourceRetries + "次,跳过此课程"); StatusPanel.showAlert("视频源无效,跳过此课程..."); this.stopMonitoring(); if (this._onVideoEnd) this._onVideoEnd(); return true; } return false; }, _sendHeartbeat: function () { var v = this._video; if (!v) return; this._lastHeartbeatTs = Date.now(); try { if (typeof GM_setValue === "function") { GM_setValue("gdce_video_heartbeat", { currentTime: v.currentTime, duration: v.duration, paused: v.paused, ts: this._lastHeartbeatTs }); } } catch (e) {} }, _checkStatus: function () { if (!this._video || !this._isMonitoring) return; if (Date.now() - this._lastHeartbeatTs > this.HEARTBEAT_INTERVAL) { this._sendHeartbeat(); } var currentTime = this._video.currentTime; var duration = this._video.duration; var paused = this._video.paused; var ended = this._video.ended; if (!this._hasValidSource() && !this._isVideoCompleted()) { if (this._handleNoSource()) return; } if (duration > 0) { var pct = Math.round((currentTime / duration) * 100); StatusPanel.updateProgress(pct, currentTime, duration); } if (this._isVideoCompleted() && !ended) { Utils.info("检测到视频已播放完毕(currentTime=" + currentTime.toFixed(2) + ", duration=" + duration.toFixed(2) + "),手动触发ended流程"); this.stopMonitoring(); if (this._onVideoEnd) this._onVideoEnd(); return; } if (!paused && !ended) { if (Math.abs(currentTime - this._lastProgress) < 0.5) { this._stuckCount++; if (this._stuckCount >= CONFIG.STUCK_THRESHOLD / (CONFIG.CHECK_INTERVAL / 1000)) { if (this._isVideoCompleted()) { Utils.info("视频已播完但卡住,直接触发ended"); this.stopMonitoring(); if (this._onVideoEnd) this._onVideoEnd(); return; } Utils.warn("视频可能卡住,尝试刷新恢复"); this._stuckCount = 0; this._tryRecover(); } } else { this._stuckCount = 0; } } this._lastProgress = currentTime; }, _checkAndResume: function () { if (!this._video || !this._isMonitoring) return; if (this._isVideoCompleted()) { Utils.info("视频已播放完毕,跳过恢复播放检查"); return; } if (!this._hasValidSource()) { if (this._handleNoSource()) return; Utils.warn("视频源可能无效(第" + this._noSourceRetries + "次检测),等待中..."); return; } if (this._noSourceRetries > 0) { this._noSourceRetries = 0; } if (this._video.paused && !this._video.ended) { Utils.info("检测到视频暂停,尝试恢复播放"); var self = this; var playPromise = this._video.play(); if (playPromise && playPromise.catch) { playPromise.then(function () { Utils.info("自动恢复播放成功"); }).catch(function (e) { if (e.message && e.message.indexOf("no supported sources") !== -1) { if (self._handleNoSource()) return; return; } if (e.name === "NotAllowedError" && !self._video.muted) { Utils.info("自动播放被阻止,尝试静音播放"); self._video.muted = true; self._video.play().then(function () { Utils.info("静音播放成功,等待用户交互恢复声音"); self._waitForInteractionAndUnmute(); }).catch(function (e2) { if (e2.message && e2.message.indexOf("no supported sources") !== -1) { if (self._handleNoSource()) return; return; } Utils.warn("静音播放也失败: " + e2.message); }); } else { Utils.warn("自动恢复播放失败: " + e.message); } }); } } }, _tryRecover: function () { if (!this._video) return; if (this._isVideoCompleted()) { Utils.info("视频已播放完毕,跳过恢复操作"); return; } if (!this._hasValidSource()) { if (this._handleNoSource()) return; Utils.warn("视频源无效,跳过恢复操作"); return; } try { this._video.pause(); var self = this; setTimeout(function () { if (!self._video) return; self._video.play().catch(function (e) { if (e.message && e.message.indexOf("no supported sources") !== -1) { if (self._handleNoSource()) return; } Utils.error("恢复播放失败: " + e.message); }); }, 1000); } catch (e) { Utils.error("恢复操作失败: " + e.message); } }, get isMonitoring() { return this._isMonitoring; } };  var CourseAutomation = { _isRunning: false, _retryCount: 0, TARGET_HOURS: 50, TARGET_REQUIRED_HOURS: 25, _targets: null, _creditedRequired: 0, _myCoursesRequiredHours: 0, _neededRequired: 0, _learnStartCredited: 0, _learnStartRequired: 0, _sessionGained: null, COURSE_CATEGORIES: [ "党的理论", "党性教育", "履职能力", "知识培训", "厅处长讲业务", "形势与政策", "红色广东", "新时代广东实践" ], _currentCategoryIndex: 0, _completedHours: 0, _myCourses: [], _myCoursesHours: 0, _neededHours: 0, _selectedCourses: [], _currentLearnIndex: 0, _phase: "idle", init: function () { Utils.info("课程自动化模块初始化(目标学时: " + this.TARGET_HOURS + ")"); this._startPopupWatcher(); }, _startPopupWatcher: function () { var self = this; setInterval(function () { self._tryDismissPopup(); }, 1500); }, _tryDismissPopup: function () { var selectors = [ CONFIG.SELECTORS.layerConfirm, CONFIG.SELECTORS.layerClose, ".layui-layer-btn0", ".layui-layer-btn a", ".layui-layer-close1" ]; for (var i = 0; i < selectors.length; i++) { try { var btn = Utils.qsIframes(selectors[i]); if (btn) { Utils.safeClick(btn); return; } btn = document.querySelector(selectors[i]); if (btn) { Utils.safeClick(btn); return; } } catch (e) { } } }, _handleLayerPopup: function () { setTimeout(function () { var confirmBtn = Utils.qsIframes(CONFIG.SELECTORS.layerConfirm); if (confirmBtn) { Utils.safeClick(confirmBtn); } var closeBtn = Utils.qsIframes(CONFIG.SELECTORS.layerClose); if (closeBtn) { Utils.safeClick(closeBtn); } confirmBtn = document.querySelector(CONFIG.SELECTORS.layerConfirm); if (confirmBtn) { Utils.safeClick(confirmBtn); } closeBtn = document.querySelector(CONFIG.SELECTORS.layerClose); if (closeBtn) { Utils.safeClick(closeBtn); } }, 1500); }, _dismissPopupAndContinue: function (waitMs) { waitMs = waitMs || 2000; var self = this; return new Promise(function (resolve) { var checkCount = 0; var maxChecks = Math.ceil(waitMs / 500) + 4; var timer = setInterval(function () { checkCount++; var dismissed = self._tryDismissPopupNow(); if (dismissed || checkCount >= maxChecks) { clearInterval(timer); setTimeout(resolve, 800); } }, 500); }); }, _tryDismissPopupNow: function () { var selectors = [ CONFIG.SELECTORS.layerConfirm, CONFIG.SELECTORS.layerClose, ".layui-layer-btn0", ".layui-layer-btn a", ".layui-layer-close1" ]; for (var i = 0; i < selectors.length; i++) { try { var btn = Utils.qsIframes(selectors[i]); if (btn) { Utils.safeClick(btn); return true; } btn = document.querySelector(selectors[i]); if (btn) { Utils.safeClick(btn); return true; } } catch (e) { } } return false; }, _extractCourseDetailUrl: function (link) { if (!link) return null; var href = link.getAttribute("href") || ""; var match = href.match(/javascript:w\(["']([^"']+)["']\)/); if (match && match[1]) { var url = match[1]; if (url.indexOf("CourseDetail") !== -1) { if (url.indexOf("http") !== 0) { try { var doc = link.ownerDocument; var baseURL = doc.location.href; url = new URL(url, baseURL).href; } catch (e) { url = "https://gbpx.gd.gov.cn/gdceportal/Study/" + url; } } return url; } } return null; }, _selectCourseViaFetch: function (course) { var self = this; var link = course.selectLink || course.continueLink; if (!link) return Promise.reject(new Error("无可用链接")); var courseDetailUrl = self._extractCourseDetailUrl(link); if (!courseDetailUrl) { var href = link.getAttribute("href") || ""; var pbMatch = href.match(/__doPostBack\(['"]([^'"]+)['"],\s*['"]([^'"]*)['"]\)/); if (pbMatch) { return self._selectCourseViaPostBack(link, pbMatch[1], pbMatch[2], course); } Utils.info("[选课] 链接格式无法解析,回退到直接点击: " + href.substring(0, 60)); Utils.safeClick(link); return self._dismissPopupAndContinue(3000); } Utils.info("[选课] 课程详情: " + courseDetailUrl); return fetch(courseDetailUrl, { method: "GET", credentials: "include" }).then(function (response) { if (!response.ok) throw new Error("请求失败: HTTP " + response.status); return response.text(); }).then(function (html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, "text/html"); var viewstate = doc.querySelector("#__VIEWSTATE"); var viewstateGen = doc.querySelector("#__VIEWSTATEGENERATOR"); var eventVal = doc.querySelector("#__EVENTVALIDATION"); var btnConfirm = doc.querySelector("#btnConfirm"); if (!viewstate || !btnConfirm) { Utils.warn("[选课] 课程详情页缺少表单字段(可能已选过),跳过"); return "already_selected"; } var formData = new FormData(); formData.set("__VIEWSTATE", viewstate.value); formData.set("__VIEWSTATEGENERATOR", viewstateGen ? viewstateGen.value : ""); formData.set("__EVENTVALIDATION", eventVal ? eventVal.value : ""); formData.set("btnConfirm", "进入学习"); Utils.info("[选课] 提交选课到: " + courseDetailUrl); return fetch(courseDetailUrl, { method: "POST", body: formData, credentials: "include" }).then(function (postResponse) { if (postResponse.ok) { Utils.info("[选课] 选课请求已提交成功(HTTP " + postResponse.status + ")"); self._applySelected(course); return "success"; } else { Utils.warn("[选课] 提交返回非200状态: " + postResponse.status); return "fallback"; } }); }).then(function (result) { if (result === "fallback") { Utils.safeClick(link); self._applySelected(course); return self._dismissPopupAndContinue(3000); } return Utils.sleep(1500); }).catch(function (e) { Utils.warn("[选课] 请求失败: " + e.message + ",回退到直接点击"); Utils.safeClick(link); self._applySelected(course); return self._dismissPopupAndContinue(3000); }); }, _selectCourseViaPostBack: function (link, eventTarget, eventArgument, course) { var self = this; var doc = link.ownerDocument; var form = doc ? doc.querySelector("form") : null; if (!form) { Utils.safeClick(link); return self._dismissPopupAndContinue(3000); } var formData = new FormData(form); formData.set("__EVENTTARGET", eventTarget); formData.set("__EVENTARGUMENT", eventArgument); var actionUrl = form.getAttribute("action") || doc.location.href; if (actionUrl.indexOf("http") !== 0) { try { actionUrl = new URL(actionUrl, doc.location.href).href; } catch (e) { } } Utils.info("[选课] 提交到: " + actionUrl.substring(0, 80)); return fetch(actionUrl, { method: "POST", body: formData, credentials: "include" }).then(function (response) { if (response.ok) { Utils.info("[选课] 请求成功"); self._applySelected(course); return Utils.sleep(2000); } Utils.warn("[选课] 请求失败: HTTP " + response.status); Utils.safeClick(link); self._applySelected(course); return self._dismissPopupAndContinue(3000); }).catch(function (e) { Utils.warn("[选课] 请求失败: " + e.message); Utils.safeClick(link); self._applySelected(course); return self._dismissPopupAndContinue(3000); }); }, _extractVideoUrl: function (link) { if (!link) return null; var href = link.getAttribute("href") || ""; var match = href.match(/javascript:w\(["']([^"']+)["']\)/); if (match && match[1]) { var url = match[1].replace(/&/g, "&"); if (url.indexOf("shawcoder.xyz") !== -1 || url.indexOf("playverif") !== -1) { if (url.indexOf("http") !== 0) { try { var doc = link.ownerDocument; var baseURL = doc.location.href; url = new URL(url, baseURL).href; } catch (e) { url = "https://wcs1.shawcoder.xyz/gdcecw/play_pc/" + url; } } Utils.info("提取到视频URL: " + url.substring(0, 80) + "..."); return url; } return null; } if (href.indexOf("http") === 0 && (href.indexOf("shawcoder") !== -1 || href.indexOf("playverif") !== -1)) return href; return null; }, _parseCourseRow: function (row) { var cells = row.querySelectorAll("td"); if (cells.length < 5) return null; var numCols = cells.length; var nameCell = cells[0]; var hoursCell = cells[1]; var typeCell = cells[2]; var actionCellIndex = (numCols >= 7) ? 5 : 4; var actionCell = cells[actionCellIndex]; var name = (nameCell.textContent || "").trim(); var hours = parseFloat((hoursCell.textContent || "0").trim()) || 0; var type = (typeCell.textContent || "").trim(); var completedLink = row.querySelector(CONFIG.SELECTORS.completedCourseLink); var actionText = (actionCell.textContent || "").trim(); var isCompleted = !!completedLink || actionText.indexOf("已学") !== -1 || actionText.indexOf("已完成") !== -1; var continueLink = null; var videoUrl = null; var courseDetailLink = null; var allLinks = row.querySelectorAll("a, input[type='button'], input[type='submit']"); for (var j = 0; j < allLinks.length; j++) { var link = allLinks[j]; var linkText = (link.textContent || link.value || "").trim(); if (linkText.indexOf("继续学习") !== -1 || linkText.indexOf("开始学习") !== -1 || linkText.indexOf("进入学习") !== -1) { continueLink = link; videoUrl = this._extractVideoUrl(link); } else if (linkText.indexOf("进入选课") !== -1) { courseDetailLink = link; } else if (!continueLink && !courseDetailLink) { var detailUrl = this._extractCourseDetailUrl(link); if (detailUrl) { courseDetailLink = link; } } } var selectLink = null; if (courseDetailLink) { selectLink = courseDetailLink; } if (!selectLink && actionText.indexOf("进入选课") !== -1) { var actionLinks = actionCell.querySelectorAll("a"); if (actionLinks.length > 0) selectLink = actionLinks[0]; } var isRequired = type.indexOf("必修") !== -1; return { name: name, hours: hours, type: type, isRequired: isRequired, progress: isCompleted ? 100 : 0, isCompleted: isCompleted, continueLink: continueLink, videoUrl: videoUrl, selectLink: selectLink, row: row }; }, _scanCourseTable: function () { var table = Utils.qsIframes(CONFIG.SELECTORS.courseTable); if (!table) { Utils.warn("未找到课程表格 #gvList"); return []; } var rows = table.querySelectorAll("tr"); var courses = []; for (var i = 1; i < rows.length; i++) { var course = this._parseCourseRow(rows[i]); if (course) courses.push(course); } return courses; }, _waitForCourseTableAndScan: function (timeout) { timeout = timeout || 30000; var self = this; var selector = CONFIG.SELECTORS.courseTable; Utils.info("等待课程表格加载..."); return Utils.waitForElementDisappearIframes(selector, 8000).then(function (disappeared) { if (disappeared) Utils.info("旧课程表格已消失,等待新表格加载..."); else Utils.info("旧表格未消失,继续等待表格出现..."); return Utils.waitForElementIframes(selector, timeout); }).then(function (table) { Utils.info("课程表格已加载,开始扫描"); return Utils.sleep(1500).then(function () { return self._scanCourseTable(); }); }).catch(function (e) { Utils.warn("等待课程表格超时: " + e.message); return self._scanCourseTable(); }); }, _getPaginationInfo: function () { var info = { total: 0, currentPage: 0, totalPages: 0 }; var totalEl = Utils.qsIframes("#lblTotal"); var currentPageEl = Utils.qsIframes("#lblCurrentPage"); var pageEl = Utils.qsIframes("#lblPage"); if (totalEl) info.total = parseInt(totalEl.textContent.trim()) || 0; if (currentPageEl) info.currentPage = parseInt(currentPageEl.textContent.trim()) || 1; if (pageEl) info.totalPages = parseInt(pageEl.textContent.trim()) || 1; return info; }, _clickNextPage: function () { var nextBtn = Utils.qsIframes("#btnNextPage"); if (!nextBtn) nextBtn = Utils.qsIframes("input[type='submit'][name='btnNextPage']"); if (nextBtn) { Utils.info("点击下一页按钮"); Utils.safeClick(nextBtn); return true; } Utils.warn("未找到下一页按钮"); return false; }, _scanAllPages: function (timeout) { var self = this; var allCourses = []; function goToFirstPage() { var firstBtn = Utils.qsIframes("#btnFirstPage"); if (!firstBtn) firstBtn = Utils.qsIframes("input[type='submit'][name='btnFirstPage']"); if (firstBtn) { Utils.info("跳转到第1页"); Utils.safeClick(firstBtn); return Utils.sleep(3000); } return Utils.sleep(500); } function scanPage() { return self._waitForCourseTableAndScan(timeout).then(function (courses) { allCourses = allCourses.concat(courses); var pageInfo = self._getPaginationInfo(); Utils.info("当前第 " + pageInfo.currentPage + "/" + pageInfo.totalPages + " 页,本页 " + courses.length + " 门,累计 " + allCourses.length + " 门"); if (pageInfo.currentPage < pageInfo.totalPages) { Utils.info("还有下一页,翻页继续扫描..."); if (self._clickNextPage()) { return Utils.sleep(3000).then(function () { return scanPage(); }); } } return allCourses; }); } return goToFirstPage().then(function () { return scanPage(); }); }, getStudyHours: function () { var creditedEl = document.querySelector(CONFIG.SELECTORS.creditedHours); var requiredEl = document.querySelector(CONFIG.SELECTORS.requiredHours); if (!creditedEl) creditedEl = Utils.qsIframes(".courseware-des span, .courseware-des"); return { credited: creditedEl ? creditedEl.textContent.trim() : "0", required: requiredEl ? requiredEl.textContent.trim() : "50" }; }, _readTargets: function () { var total = this.TARGET_HOURS, reqMin = this.TARGET_REQUIRED_HOURS; try { var txt = document.body.innerText || ""; var m1 = txt.match(/总学时要求[::]?\s*([0-9.]+)\s*学时/); var m2 = txt.match(/必修课程至少\s*([0-9.]+)\s*学时/); if (m1) total = parseFloat(m1[1]) || total; if (m2) reqMin = parseFloat(m2[1]) || reqMin; var m3 = txt.match(/已完成学时[::]?\s*([0-9.]+)\s*学时(其中必修课程\s*([0-9.]+)\s*学时/); if (m3 && m3[2]) { var reqFromText = parseFloat(m3[2]); if (!isNaN(reqFromText) && reqFromText >= 0) { this._creditedRequired = reqFromText; this._creditedRequiredFromText = true; } } } catch (e) { } this._targets = { total: total, reqMin: reqMin }; Utils.info("[目标] 总学时 " + total + "(必修至少 " + reqMin + "),已获必修 " + (this._creditedRequired || 0) + (this._creditedRequiredFromText ? "(页面文案)" : "(待列表统计)")); return this._targets; }, hasReachedTargets: function () { var t = this._targets || this._readTargets(); var hours = this.getStudyHours(); var credited = parseFloat(hours.credited) || 0; var creditedReq = this._creditedRequired || 0; var totalOk = credited >= t.total; var reqOk = creditedReq >= t.reqMin; Utils.info("[达标检查] 总学时 " + credited + "/" + t.total + (totalOk ? "(已达标)" : "") + ",必修 " + creditedReq + "/" + t.reqMin + (reqOk ? "(已达标)" : "")); return { totalOk: totalOk, reqOk: reqOk, allOk: totalOk && reqOk }; }, hasReachedTargetHours: function () { return this.hasReachedTargets().allOk; }, _ensureCourseTab: function (tabName) { var candidates = Utils.qsaIframes("div,span,a"); var best = null; for (var i = 0; i < candidates.length; i++) { var txt = (candidates[i].textContent || "").trim(); if (txt === tabName) { best = candidates[i]; break; } if (!best && txt.indexOf(tabName) !== -1 && txt.length <= tabName.length + 2) best = candidates[i]; } if (best) { Utils.info("[导航] 切换到「" + tabName + "」标签"); Utils.safeClick(best); return Utils.sleep(2500).then(function () { return true; }); } Utils.warn("[导航] 未找到「" + tabName + "」标签,按当前列表继续"); return Utils.sleep(300); }, _scanLearnedHours: function () { var self = this; if (this._creditedRequiredFromText) { Utils.info("必修已获学时已从页面文案读取: " + this._creditedRequired + ",跳过已学课程扫描"); return Promise.resolve(this._creditedRequired); } Utils.info("=== 第2.5步:统计'已学课程'(计算必修已获学时) ==="); return this._ensureCourseTab("已学课程").then(function () { return self._scanAllPages(12000); }).then(function (courses) { var req = 0, total = 0; for (var i = 0; i < courses.length; i++) { total += courses[i].hours; if (courses[i].isRequired) req += courses[i].hours; } self._creditedRequired = Math.round(req * 100) / 100; self._creditedTotalLearned = Math.round(total * 100) / 100; Utils.info("已学课程统计: 必修 " + self._creditedRequired + " 学时,合计 " + self._creditedTotalLearned + " 学时(" + courses.length + " 门)"); return self._creditedRequired; }).catch(function (e) { Utils.warn("已学课程统计失败: " + e.message + "(必修已获按 " + (self._creditedRequired || 0) + " 处理)"); return self._creditedRequired || 0; }); }, _applySelected: function (course) { this._neededHours = Math.max(0, (this._neededHours || 0) - course.hours); if (course.isRequired) this._neededRequired = Math.max(0, (this._neededRequired || 0) - course.hours); Utils.info("[选课] 剩余缺口: 总 " + this._neededHours + " / 必修 " + this._neededRequired); }, _sortCoursesRequiredFirst: function (courses) { var arr = (courses || []).slice(); arr.sort(function (a, b) { return (b.isRequired ? 1 : 0) - (a.isRequired ? 1 : 0); }); return arr; }, _clickTopLevelMyCourse: function () { var links = document.querySelectorAll("span.firstRouterLink"); for (var i = 0; i < links.length; i++) { var text = (links[i].textContent || "").trim(); if (text.indexOf("我的课程") !== -1) { Utils.info("[导航] 点击顶层'我的课程'标签"); Utils.safeClick(links[i]); return true; } } Utils.warn("[导航] 未找到顶层'我的课程'标签"); return false; }, _clickTopLevelCategory: function (categoryName) { var links = document.querySelectorAll("span.firstRouterLink"); for (var i = 0; i < links.length; i++) { var text = (links[i].textContent || "").trim(); if (text.indexOf(categoryName) !== -1 || categoryName.indexOf(text) !== -1) { Utils.info("[导航] 点击顶层分类标签: " + text); Utils.safeClick(links[i]); return links[i]; } } return null; }, _readNavigationStructure: function () { var structure = []; var firstLinks = document.querySelectorAll("span.firstRouterLink"); if (firstLinks.length === 0) { firstLinks = Utils.qsaIframes(CONFIG.SELECTORS.firstRouterLink); } Utils.info("[导航] 找到 " + firstLinks.length + " 个一级分类链接"); for (var i = 0; i < firstLinks.length; i++) { var link = firstLinks[i]; var name = (link.textContent || "").trim(); var routerUrl = link.getAttribute("routerlink") || ""; Utils.info("[导航] 一级分类[" + i + "]: name='" + name + "' routerlink='" + routerUrl + "'"); var parent = link.parentElement; var childContainer = parent ? parent.querySelector(CONFIG.SELECTORS.childHrefContainer) : null; var children = []; if (childContainer) { var items = childContainer.querySelectorAll(CONFIG.SELECTORS.childHrefItem); for (var j = 0; j < items.length; j++) { var childName = (items[j].textContent || "").trim(); var childUrl = items[j].getAttribute("item") || ""; children.push({ name: childName, url: childUrl, element: items[j] }); Utils.info("[导航] 子分组[" + j + "]: name='" + childName + "' item='" + childUrl + "'"); } } structure.push({ name: name, url: routerUrl, element: link, children: children }); } return structure; }, _navigateToUrl: function (relativeUrl) { if (!relativeUrl) return false; var fullUrl = relativeUrl; if (relativeUrl.indexOf("http") !== 0) { fullUrl = "https://gbpx.gd.gov.cn/gdceportal/study/" + relativeUrl; } Utils.info("[导航] 导航到: " + fullUrl); var secondIframe = document.querySelector(CONFIG.SELECTORS.secondIframe); if (secondIframe) { secondIframe.src = fullUrl; return true; } secondIframe = Utils.qsIframes(CONFIG.SELECTORS.secondIframe); if (secondIframe) { secondIframe.src = fullUrl; return true; } Utils.warn("[导航] 未找到可导航的iframe"); return false; }, _clickNavAndNavigate: function (navElement, relativeUrl) { var self = this; var beforeSrc = this._getSecondIframeSrc(); Utils.safeClick(navElement); return Utils.sleep(3000).then(function () { var afterSrc = self._getSecondIframeSrc(); if (beforeSrc !== afterSrc) { Utils.info("[导航] 点击后secondIframe src已变化: " + (afterSrc || "").substring(0, 80)); return true; } Utils.info("[导航] 点击后secondIframe src未变化,尝试直接导航"); if (relativeUrl) { return self._navigateToUrl(relativeUrl); } return false; }); }, _getSecondIframeSrc: function () { var iframe = document.querySelector(CONFIG.SELECTORS.secondIframe); if (iframe) return iframe.src || ""; iframe = Utils.qsIframes(CONFIG.SELECTORS.secondIframe); if (iframe) return iframe.src || ""; return ""; }, autoSelectAndEnterCourse: function () { var self = this; var targets = this._readTargets(); if (!this._sessionGained) this._sessionGained = { total: 0, required: 0 }; this._phase = "scanning"; this._completedHours = parseFloat(this.getStudyHours().credited) || 0; Utils.info("=== 第1步:读取已完成学时: 总 " + this._completedHours + ",必修 " + (this._creditedRequired || 0) + " ==="); Utils.info("=== 第2步:扫描'我的课程'中未完成的课程(含分页) ==="); this._clickMyCourse().then(function () { return self._ensureCourseTab("在学课程"); }).then(function () { return self._scanAllPages(20000); }).then(function (courses) { self._myCourses = []; self._myCoursesHours = 0; self._myCoursesRequiredHours = 0; for (var i = 0; i < courses.length; i++) { var c = courses[i]; if (!c.isCompleted) { self._myCourses.push(c); self._myCoursesHours += c.hours; if (c.isRequired) self._myCoursesRequiredHours += c.hours; } } self._myCoursesHours = Math.round(self._myCoursesHours * 100) / 100; self._myCoursesRequiredHours = Math.round(self._myCoursesRequiredHours * 100) / 100; Utils.info("我的课程中未完成: " + self._myCourses.length + " 门,共 " + self._myCoursesHours + " 学时(必修 " + self._myCoursesRequiredHours + " 学时)"); return self._scanLearnedHours().then(function () { var creditedReq = self._creditedRequired || 0; var check = self.hasReachedTargets(); if (check.allOk) { Utils.info("总学时和必修学时均已达标,停止自动学习"); StatusPanel.showAlert("已达标:总 " + self._completedHours + "/" + targets.total + ",必修 " + creditedReq + "/" + targets.reqMin); self._phase = "idle"; return; } Utils.info("=== 第3步:学时缺口分析(总学时+必修双口径) ==="); Utils.info(" 目标: 总 " + targets.total + " 学时(必修至少 " + targets.reqMin + ")"); Utils.info(" 已获: 总 " + self._completedHours + " / 必修 " + creditedReq); Utils.info(" 在学未完成: 总 " + self._myCoursesHours + " / 必修 " + self._myCoursesRequiredHours); var needReq = Math.max(0, targets.reqMin - creditedReq - self._myCoursesRequiredHours); var needTotal = Math.max(0, targets.total - self._completedHours - self._myCoursesHours); self._neededRequired = Math.round(needReq * 100) / 100; self._neededHours = Math.round(Math.max(needTotal, needReq) * 100) / 100; Utils.info(" 修完在学后仍缺: 必修 " + self._neededRequired + " / 总计 " + Math.max(0, targets.total - self._completedHours - self._myCoursesHours) + "(选课按 " + self._neededHours + " 执行)"); if (self._neededHours <= 0 && self._neededRequired <= 0) { Utils.info("在学课程已足够达标,直接开始学习(必修优先)"); self._startLearningMyCourses(); } else { Utils.info("=== 第4步:从分类列表选课凑学时(必修优先) ==="); self._selectCoursesToFillGap(0); } }); }).catch(function (e) { Utils.error("扫描我的课程失败: " + e.message); self._selectCoursesToFillGap(0); }); }, _clickMyCourse: function () { var self = this; return new Promise(function (resolve) { Utils.info("点击'我的课程'标签..."); var clicked = self._clickTopLevelMyCourse(); if (clicked) { Utils.info("已点击顶层'我的课程',等待secondIframe加载..."); setTimeout(function () { var subTab = Utils.qsIframes(CONFIG.SELECTORS.navMyCourse); if (subTab) { Utils.info("点击secondIframe中的'我的课程'子标签"); Utils.safeClick(subTab); } setTimeout(resolve, 2000); }, 3000); } else { var myCourseTab = Utils.qsIframes(CONFIG.SELECTORS.navMyCourse); if (!myCourseTab) myCourseTab = Utils.findByTextIframes("span.firstRouterLink, a, span, div", "我的课程"); if (myCourseTab) { Utils.safeClick(myCourseTab); Utils.info("已点击iframe中的'我的课程'"); } else { Utils.warn("未找到'我的课程'标签"); } setTimeout(resolve, 2000); } }); }, _selectCoursesToFillGap: function (categoryIndex) { if (this._neededHours <= 0 && this._neededRequired <= 0) { Utils.info("已选够学时(总/必修双达标),回到我的课程开始学习"); this._goBackToMyCoursesAndLearn(); return; } if (categoryIndex >= this.COURSE_CATEGORIES.length) { Utils.warn("所有分类都已检查,仍未凑够学时(还差 " + this._neededHours + " 学时)"); this._goBackToMyCoursesAndLearn(); return; } this._currentCategoryIndex = categoryIndex; var categoryName = this.COURSE_CATEGORIES[categoryIndex]; Utils.info("=== 检查分类: " + categoryName + " (还差 " + this._neededHours + " 学时) ==="); var topEl = this._clickTopLevelCategory(categoryName); var self = this; if (topEl) { Utils.sleep(3000).then(function () { var navStructure = self._readNavigationStructure(); var navItem = null; for (var i = 0; i < navStructure.length; i++) { if (navStructure[i].name.indexOf(categoryName) !== -1 || categoryName.indexOf(navStructure[i].name) !== -1) { navItem = navStructure[i]; break; } } if (!navItem) { Utils.warn("未在导航中找到分类: " + categoryName); self._selectCoursesToFillGap(categoryIndex + 1); return; } if (navItem.children.length > 0) { self._selectFromSubCategoryToFillGap(categoryIndex, 0, navItem, navStructure); } else { self._selectCoursesFromCurrentCategory(categoryIndex); } }); } else { var navStructure = this._readNavigationStructure(); var navItem = null; for (var i = 0; i < navStructure.length; i++) { if (navStructure[i].name.indexOf(categoryName) !== -1 || categoryName.indexOf(navStructure[i].name) !== -1) { navItem = navStructure[i]; break; } } if (!navItem) { Utils.warn("未在导航中找到分类: " + categoryName); this._selectCoursesToFillGap(categoryIndex + 1); return; } this._clickNavAndNavigate(navItem.element, navItem.url).then(function () { if (navItem.children.length > 0) { self._selectFromSubCategoryToFillGap(categoryIndex, 0, navItem, navStructure); } else { self._selectCoursesFromCurrentCategory(categoryIndex); } }); } }, _selectFromSubCategoryToFillGap: function (categoryIndex, subIndex, navItem, navStructure) { if (this._neededHours <= 0 && this._neededRequired <= 0) { this._goBackToMyCoursesAndLearn(); return; } var self = this; var categoryName = this.COURSE_CATEGORIES[categoryIndex]; if (subIndex >= navItem.children.length) { Utils.info("分类'" + categoryName + "'的所有子分组已遍历完毕"); this._selectCoursesToFillGap(categoryIndex + 1); return; } var subItem = navItem.children[subIndex]; Utils.info("--- 子分组[" + subIndex + "/" + navItem.children.length + "]: " + subItem.name + " (还差 " + this._neededHours + " 学时) ---"); this._clickNavAndNavigate(subItem.element, subItem.url).then(function () { return self._selectCoursesFromCurrentCategory(categoryIndex); }).then(function () { self._selectFromSubCategoryToFillGap(categoryIndex, subIndex + 1, navItem, navStructure); }).catch(function (e) { Utils.error("子分组'" + subItem.name + "'选课失败: " + e.message); self._selectFromSubCategoryToFillGap(categoryIndex, subIndex + 1, navItem, navStructure); }); }, _selectCoursesFromCurrentCategory: function (categoryIndex) { var self = this; return this._scanAllPages(20000).then(function (courses) { var requiredCourses = []; var electiveCourses = []; for (var i = 0; i < courses.length; i++) { var c = courses[i]; if (c.isCompleted) continue; if (c.isRequired) requiredCourses.push(c); else electiveCourses.push(c); } var toSelect = requiredCourses.concat(electiveCourses); var selectedCount = 0; function needMore() { return self._neededHours > 0 || self._neededRequired > 0; } function selectNext(index) { if (index >= toSelect.length || !needMore()) { Utils.info("本分类选课完成,共选 " + selectedCount + " 门(剩余缺口: 总 " + Math.max(0, self._neededHours) + " / 必修 " + Math.max(0, self._neededRequired) + ")"); return Utils.sleep(2000); } var course = toSelect[index]; var wantThis = false; if (course.isRequired && self._neededRequired > 0) wantThis = true; else if (self._neededHours > 0 && self._neededRequired <= 0) wantThis = true; if (!wantThis) return selectNext(index + 1); if (course.selectLink) { Utils.info("选课: " + course.name + " (" + course.hours + "学时, " + course.type + ")"); selectedCount++; return self._selectCourseViaFetch(course).then(function () { return selectNext(index + 1); }).catch(function (e) { Utils.warn("选课失败,回退到点击方式: " + e.message); Utils.safeClick(course.selectLink); self._applySelected(course); return self._dismissPopupAndContinue(3000).then(function () { return selectNext(index + 1); }); }); } else if (course.continueLink || course.videoUrl) { Utils.info("课程已选但未完成,已在学习队列中: " + course.name); return selectNext(index + 1); } return selectNext(index + 1); } return selectNext(0); }); }, _goBackToMyCoursesAndLearn: function () { var self = this; this._phase = "learning"; Utils.info("=== 第5步:回到我的课程,开始逐个学习(必修优先) ==="); this._clickMyCourse().then(function () { return self._ensureCourseTab("在学课程"); }).then(function () { return self._scanAllPages(20000); }).then(function (courses) { self._selectedCourses = []; for (var i = 0; i < courses.length; i++) { if (!courses[i].isCompleted) { self._selectedCourses.push(courses[i]); } } self._selectedCourses = self._sortCoursesRequiredFirst(self._selectedCourses); Utils.info("我的课程中未完成共 " + self._selectedCourses.length + " 门(必修优先排序),开始学习"); self._currentLearnIndex = 0; self._learnStartCredited = parseFloat(self.getStudyHours().credited) || 0; self._learnStartRequired = self._creditedRequired || 0; self._sessionGained = { total: 0, required: 0 }; self._learnNextCourse(); }).catch(function (e) { Utils.error("回到我的课程失败: " + e.message); }); }, _startLearningMyCourses: function () { this._phase = "learning"; this._selectedCourses = this._sortCoursesRequiredFirst(this._myCourses); this._currentLearnIndex = 0; this._learnStartCredited = this._completedHours || (parseFloat(this.getStudyHours().credited) || 0); this._learnStartRequired = this._creditedRequired || 0; this._sessionGained = { total: 0, required: 0 }; Utils.info("我的课程中未完成共 " + this._selectedCourses.length + " 门(必修优先排序),开始学习"); this._learnNextCourse(); }, _learnNextCourse: function () { if (this._currentLearnIndex >= this._selectedCourses.length) { Utils.info("所有课程已学习完毕!"); StatusPanel.showAlert("所有课程已学习完毕!"); MainController._videoFinishedLock = false; MainController._videoPlaying = false; return; } if (MainController._videoPlaying) { Utils.info("上一个视频仍在播放中,10秒后重试..."); var self = this; setTimeout(function () { self._learnNextCourse(); }, 10000); return; } var course = this._selectedCourses[this._currentLearnIndex]; Utils.info("=== 学习课程[" + (this._currentLearnIndex + 1) + "/" + this._selectedCourses.length + "]: " + course.name + " (" + course.hours + "学时) ==="); var videoUrl = course.videoUrl; if (!videoUrl && course.continueLink) { videoUrl = this._extractVideoUrl(course.continueLink); } if (videoUrl) { Utils.info("通过openInTab打开视频: " + videoUrl.substring(0, 80)); MainController._videoPlaying = true; MainController._videoFinishedLock = false; var tabResult = openInTab(videoUrl); if (!tabResult) { Utils.warn("openInTab失败,回退到点击链接"); MainController._videoPlaying = false; Utils.safeClick(course.continueLink); } else { MainController._videoTab = (typeof tabResult === "object" && tabResult !== true) ? tabResult : null; MainController._lastVideoActivityTs = Date.now(); MainController._lastHeartbeatSeenTs = 0; MainController._startVideoWindowCheck(); } } else if (course.continueLink) { Utils.info("点击课程学习链接: " + course.name); Utils.safeClick(course.continueLink); } else if (course.selectLink) { var courseDetailUrl = this._extractCourseDetailUrl(course.selectLink); if (courseDetailUrl) { Utils.info("课程无直接视频链接,通过CourseDetail进入: " + courseDetailUrl.substring(0, 80)); var secondIframe = document.querySelector(CONFIG.SELECTORS.secondIframe); if (!secondIframe) secondIframe = Utils.qsIframes(CONFIG.SELECTORS.secondIframe); if (secondIframe) { secondIframe.src = courseDetailUrl; var self = this; Utils.info("已设置secondIframe.src为CourseDetail页,等待加载后点击'进入学习'..."); setTimeout(function () { self._clickEnterStudyInIframe(secondIframe); }, 5000); } else { Utils.warn("未找到secondIframe,跳过课程: " + course.name); this._currentLearnIndex++; this._learnNextCourse(); } } else { Utils.warn("课程没有可用的学习链接: " + course.name + ",跳过"); this._currentLearnIndex++; this._learnNextCourse(); } } else { Utils.warn("课程没有可用的学习链接: " + course.name + ",跳过"); this._currentLearnIndex++; this._learnNextCourse(); } }, _clickEnterStudyInIframe: function (iframe) { var self = this; try { var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); if (!doc) { Utils.warn("无法访问iframe内容,3秒后重试"); setTimeout(function () { self._clickEnterStudyInIframe(iframe); }, 3000); return; } var confirmBtn = doc.querySelector(CONFIG.SELECTORS.layerConfirm); if (confirmBtn) { Utils.info("iframe内检测到弹窗确认按钮,自动点击"); confirmBtn.click(); } var closeBtn = doc.querySelector(CONFIG.SELECTORS.layerClose); if (closeBtn) { Utils.info("iframe内检测到弹窗关闭按钮,自动关闭"); closeBtn.click(); } var btn = doc.querySelector(CONFIG.SELECTORS.enterStudyBtn); if (!btn) btn = doc.querySelector("input[type='submit'][value='进入学习']"); if (!btn) btn = doc.querySelector("input[type='submit'][name='btnConfirm']"); if (btn) { Utils.info("在iframe内找到'进入学习'按钮,点击"); btn.click(); Utils.info("已点击'进入学习'按钮,等待视频页面加载..."); setTimeout(function () { VideoMonitor.stopMonitoring(); var videoEl = Utils.qsIframes(CONFIG.SELECTORS.videoPlayer); if (videoEl) { Utils.info("找到同域视频元素,启动监控"); VideoMonitor.startMonitoring(); } else { Utils.info("视频在跨域iframe中播放,等待postMessage通知播放完毕"); } }, 5000); } else { Utils.warn("iframe内未找到'进入学习'按钮,3秒后重试"); setTimeout(function () { self._clickEnterStudyInIframe(iframe); }, 3000); } } catch (e) { Utils.warn("访问iframe内容失败: " + e.message + ",3秒后重试"); setTimeout(function () { self._clickEnterStudyInIframe(iframe); }, 3000); } }, onCourseLearned: function () { var doneCourse = this._selectedCourses[this._currentLearnIndex]; if (doneCourse && this._sessionGained) { this._sessionGained.total += doneCourse.hours; if (doneCourse.isRequired) this._sessionGained.required += doneCourse.hours; } this._currentLearnIndex++; Utils.info("课程学习完毕,准备学习下一门(" + this._currentLearnIndex + "/" + this._selectedCourses.length + ")"); var t = this._targets || this._readTargets(); var labelNow = parseFloat(this.getStudyHours().credited) || 0; var estTotal = Math.max(labelNow, (this._learnStartCredited || 0) + (this._sessionGained ? this._sessionGained.total : 0)); var estReq = (this._learnStartRequired || 0) + (this._sessionGained ? this._sessionGained.required : 0); if (estTotal >= t.total && estReq >= t.reqMin) { Utils.info("预计已达标(总 " + estTotal + "/" + t.total + ",必修 " + Math.round(estReq * 100) / 100 + "/" + t.reqMin + "),停止学习。以后续服务端统计为准,可刷新页面复核。"); StatusPanel.showAlert("已达标:总 " + estTotal + "/" + t.total + "(必修 " + Math.round(estReq * 100) / 100 + "/" + t.reqMin + ")"); MainController._videoFinishedLock = false; MainController._videoPlaying = false; this._phase = "idle"; return; } var self = this; setTimeout(function () { self._learnNextCourse(); }, 3000); }, clickNextSection: function () { var self = this; Utils.info("正在查找'下一节'按钮..."); var btn = Utils.findByTextIframes("a, button, span, div", "下一节"); if (!btn) btn = Utils.findByTextIframes("a, button, span, div", "下一课"); if (!btn) { Utils.warn("未找到'下一节'按钮"); return false; } Utils.randomDelay().then(function () { Utils.safeClick(btn); Utils.info("已点击'下一节'按钮"); self._handleLayerPopup(); }); return true; }, _findCourseInList: function (courses, name) { for (var i = 0; i < courses.length; i++) { if (courses[i].name.indexOf(name) !== -1 || name.indexOf(courses[i].name) !== -1) return courses[i]; } return null; }, checkMultipleCourses: function () { var videos = Utils.qsaIframes(CONFIG.SELECTORS.videoPlayer); if (videos.length > 1) { Utils.warn("检测到多个视频同时播放!"); StatusPanel.showAlert("警告:检测到多课程同时打开,请关闭多余课程!"); return true; } return false; }, start: function () { this._isRunning = true; Utils.info("课程自动化已启动"); }, stop: function () { this._isRunning = false; Utils.info("课程自动化已停止"); }, get isRunning() { return this._isRunning; } }; var StatusPanel = { _panel: null, _isMinimized: false, _isPaused: false, _videoStatus: "stopped", _progress: 0, _currentTime: 0, _duration: 0, _courseName: "", _alertMsg: "", _mode: "studyCenter", init: function () { if (typeof IS_VIDEO_PAGE !== "undefined" && IS_VIDEO_PAGE) { this._mode = "videoPlayer"; } else { this._mode = "studyCenter"; } this._createPanel(); this._bindPanelEvents(); Utils.info("状态面板已创建(模式: " + this._mode + ")"); }, _createPanel: function () { var style = document.createElement("style"); style.textContent = [ "#gdce-panel{position:fixed;top:20px;right:20px;width:320px;background:#1a1a2e;color:#e0e0e0;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.4);z-index:999999;font-family:'Microsoft YaHei',sans-serif;font-size:13px;transition:all 0.3s ease;overflow:hidden}", "#gdce-panel.minimized #gdce-panel-body{display:none!important}", "#gdce-panel.minimized #gdce-panel-controls{display:none!important}", "#gdce-panel.minimized{width:auto;height:auto;border-radius:8px;cursor:pointer}", "#gdce-panel-header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:linear-gradient(135deg,#16213e,#0f3460);border-radius:12px 12px 0 0;cursor:move}", "#gdce-panel-title{font-weight:bold;font-size:14px;color:#e94560;line-height:1.4}", "#gdce-panel-title .gdce-subtitle{font-size:11px;color:#888;font-weight:normal}", "#gdce-panel-title a{color:#58a6ff;text-decoration:none}", "#gdce-panel-title a:hover{text-decoration:underline}", "#gdce-panel-controls{display:flex;gap:6px}", ".gdce-btn{background:none;border:1px solid #444;color:#ccc;border-radius:6px;padding:2px 8px;cursor:pointer;font-size:12px;transition:all 0.2s}", ".gdce-btn:hover{background:#e94560;color:#fff;border-color:#e94560}", ".gdce-btn.active{background:#e94560;color:#fff;border-color:#e94560}", "#gdce-panel-body{padding:12px 14px;max-height:400px;overflow-y:auto}", ".gdce-section{margin-bottom:10px}", ".gdce-section-title{font-size:11px;color:#888;text-transform:uppercase;margin-bottom:4px;letter-spacing:1px}", ".gdce-status-row{display:flex;justify-content:space-between;align-items:center;padding:3px 0}", ".gdce-status-value{color:#e94560;font-weight:bold}", "#gdce-progress-bar{width:100%;height:6px;background:#2a2a4a;border-radius:3px;overflow:hidden;margin-top:4px}", "#gdce-progress-fill{height:100%;background:linear-gradient(90deg,#e94560,#ff6b6b);border-radius:3px;transition:width 0.5s ease;width:0%}", "#gdce-log-container{max-height:150px;overflow-y:auto;background:#0d1117;border-radius:6px;padding:6px 8px;font-size:11px;line-height:1.6}", ".gdce-log-entry{border-bottom:1px solid #1a1a2e;padding:1px 0}", ".gdce-log-info{color:#58a6ff}", ".gdce-log-warn{color:#d29922}", ".gdce-log-error{color:#f85149}", "#gdce-alert{background:#f8514922;border:1px solid #f85149;border-radius:6px;padding:6px 10px;margin-top:8px;color:#f85149;font-size:12px;display:none}", "#gdce-panel::-webkit-scrollbar{width:4px}", "#gdce-panel::-webkit-scrollbar-thumb{background:#444;border-radius:2px}" ].join("\n"); document.head.appendChild(style); var panel = document.createElement("div"); panel.id = "gdce-panel"; var videoSection = ''; var hoursSection = ''; if (this._mode === "videoPlayer") { videoSection = [ '
', '
视频状态
', '
', ' 状态', ' 未启动', '
', '
', ' 进度', ' 0%', '
', '
', '
', ' 时间', ' 00:00 / 00:00', '
', '
' ].join("\n"); } else { hoursSection = [ '
', '
学时信息
', '
', ' 已获总学时', ' --', '
', '
', ' 其中必修学时', ' --', '
', '
', ' 要求总学时', ' 50', '
', '
', ' 必修至少', ' 25', '
', '
' ].join("\n"); } panel.innerHTML = [ '
', ' 学习助手「免费」
交流QQ群:882909961
', '
', ' ', ' ', '
', '
', '
', '
', '
', ' 项目地址', ' ScriptCat', '
', '
', videoSection, hoursSection, '
', '
操作日志
', '
', '
', '
', '
' ].join("\n"); document.body.appendChild(panel); this._panel = panel; }, _bindPanelEvents: function () { var self = this; document.getElementById("gdce-btn-pause").addEventListener("click", function () { self._isPaused = !self._isPaused; this.textContent = self._isPaused ? "继续" : "暂停"; this.classList.toggle("active", self._isPaused); if (self._isPaused) { Utils.info("用户暂停了自动学习"); if (typeof MainController !== "undefined") MainController.pause(); } else { Utils.info("用户恢复了自动学习"); if (typeof MainController !== "undefined") MainController.resume(); } }); document.getElementById("gdce-btn-minimize").addEventListener("click", function () { self._isMinimized = !self._isMinimized; self._panel.classList.toggle("minimized", self._isMinimized); this.textContent = self._isMinimized ? "展开" : "收起"; }); this._panel.addEventListener("click", function (e) { if (self._isMinimized) { self._isMinimized = false; self._panel.classList.remove("minimized"); document.getElementById("gdce-btn-minimize").textContent = "收起"; } }); this._makeDraggable(); }, _makeDraggable: function () { var header = document.getElementById("gdce-panel-header"); var panel = this._panel; var isDragging = false, startX, startY, origLeft, origTop; header.addEventListener("mousedown", function (e) { if (e.target.tagName === "BUTTON") return; isDragging = true; startX = e.clientX; startY = e.clientY; origLeft = panel.offsetLeft; origTop = panel.offsetTop; e.preventDefault(); }); document.addEventListener("mousemove", function (e) { if (!isDragging) return; panel.style.left = (origLeft + e.clientX - startX) + "px"; panel.style.top = (origTop + e.clientY - startY) + "px"; panel.style.right = "auto"; }); document.addEventListener("mouseup", function () { isDragging = false; }); }, updateVideoStatus: function (status) { this._videoStatus = status; var el = document.getElementById("gdce-video-status"); if (!el) return; var map = { stopped: "已停止", monitoring: "监控中", playing: "播放中", paused: "已暂停", ended: "已结束", buffering: "缓冲中", error: "错误" }; el.textContent = map[status] || status; }, updateProgress: function (pct, current, duration) { this._progress = pct; this._currentTime = current; this._duration = duration; var fill = document.getElementById("gdce-progress-fill"); var text = document.getElementById("gdce-progress-text"); var time = document.getElementById("gdce-time-text"); if (fill) fill.style.width = pct + "%"; if (text) text.textContent = pct + "%"; if (time) time.textContent = Utils.formatTime(current) + " / " + Utils.formatTime(duration); }, updateStudyHours: function (credited, required, creditedReq, requiredMin) { var cEl = document.getElementById("gdce-credited"); var rEl = document.getElementById("gdce-required"); var crEl = document.getElementById("gdce-credited-req"); var rmEl = document.getElementById("gdce-required-min"); if (cEl) cEl.textContent = credited; if (rEl) rEl.textContent = required; if (crEl && typeof creditedReq === "number") crEl.textContent = Math.round(creditedReq * 100) / 100; if (rmEl && typeof requiredMin === "number") rmEl.textContent = requiredMin; }, updateLogs: function () { var container = document.getElementById("gdce-log-container"); if (!container) return; var logs = Utils.getLogs(); var html = ""; for (var i = Math.max(0, logs.length - 20); i < logs.length; i++) { html += '
[' + logs[i].timestamp + '] ' + logs[i].message + '
'; } container.innerHTML = html; container.scrollTop = container.scrollHeight; }, showAlert: function (msg) { this._alertMsg = msg; var el = document.getElementById("gdce-alert"); if (el) { el.textContent = msg; el.style.display = "block"; } }, hideAlert: function () { this._alertMsg = ""; var el = document.getElementById("gdce-alert"); if (el) el.style.display = "none"; }, get isPaused() { return this._isPaused; } }; var MainController = { _isRunning: false, _isPaused: false, _initTimer: null, _hoursTimer: null, _mode: "studyCenter", _lastDispatchedType: null, _dispatchTimer: null, _nextCourseTimer: null, _videoWindow: null, _videoWindowCheckTimer: null, _statusCheckTimer: null, _videoFinishedLock: false, _videoPlaying: false, _videoTab: null, _lastVideoActivityTs: 0, _lastHeartbeatSeenTs: 0, VIDEO_WATCHDOG_TIMEOUT: 300000, _reloginShown: false, showReloginBox: function () { if (this._reloginShown || document.getElementById('__gdce-relogin')) return; this._reloginShown = true; var USER = '17817120523', PASS = 'bangbang123!'; var self = this; function refreshCaptcha() { return new Promise(function (resolve) { var img = new Image(); img.onload = img.onerror = function () { var c = document.createElement('canvas'); c.width = img.naturalWidth; c.height = img.naturalHeight; c.getContext('2d').drawImage(img, 0, 0); resolve(c.toDataURL('image/png')); }; img.src = '/gdceportal/VerifyChar.aspx?id=' + Date.now(); }); } function tryLogin(cap) { return fetch('/gdceportal/LoginAjaxServer.ashx?sign=GetKey&rand=' + Math.random(), { method: 'POST', credentials: 'include' }) .then(function (r) { return r.text(); }) .then(function (key) { var body = new URLSearchParams({ username: encMe(USER, key.trim()), password: encMe(PASS, key.trim()), validCode: cap, rememberMe: '1' }).toString(); return fetch('/gdceportal/LoginAjaxServer.ashx?sign=LoginWEB&rand=' + Math.random(), { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: body }).then(function (r) { return r.text(); }); }) .then(function (txt) { var ok = txt.indexOf('result:true') !== -1 || txt.indexOf('"result":true') !== -1; if (ok) { try { var obj = eval('(' + txt + ')'); var ks = ['live_token', 'spec_token', 'sz_token', 'st_token', 'fs_token', 'zq_token', 'course_auth', 'ssoNational_token']; for (var i = 0; i < ks.length; i++) { if (obj[ks[i]]) localStorage.setItem(ks[i], obj[ks[i]]); } } catch (e) {} } else { Utils.warn('登录返回: ' + txt.substring(0, 120)); } return ok; }); } refreshCaptcha().then(function (capData) { var box = document.createElement('div'); box.id = '__gdce-relogin'; box.style.cssText = 'position:fixed;z-index:2147483647;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:28px 38px;border:6px solid #e74c3c;border-radius:12px;box-shadow:0 0 40px rgba(0,0,0,.5);text-align:center;font-family:Microsoft YaHei,sans-serif'; var title = document.createElement('div'); title.textContent = '授权码已过期,请输入验证码重新登录'; title.style.cssText = 'font-size:20px;font-weight:bold;color:#c0392b;margin-bottom:14px'; var img = new Image(); img.src = capData; img.style.cssText = 'width:464px;height:208px;display:block;margin:0 auto;border:2px solid #ccc;border-radius:6px;cursor:pointer'; img.title = '看不清?点击换一张'; img.onclick = function () { refreshCaptcha().then(function (d) { img.src = d; }); }; var inp = document.createElement('input'); inp.placeholder = '输入图中4个字符'; inp.style.cssText = 'display:block;margin:14px auto 0;width:320px;font-size:30px;text-align:center;letter-spacing:12px;padding:8px;border:2px solid #3498db;border-radius:6px;outline:none'; var btn = document.createElement('button'); btn.textContent = '登 录'; btn.style.cssText = 'display:block;margin:14px auto 0;font-size:24px;padding:10px 60px;background:#27ae60;color:#fff;border:none;border-radius:8px;cursor:pointer'; var st = document.createElement('div'); st.style.cssText = 'margin-top:10px;font-size:15px;color:#7f8c8d'; st.textContent = '登录成功后自动刷新页面并继续自动学习'; btn.onclick = function () { st.textContent = '登录中...'; st.style.color = '#7f8c8d'; tryLogin(inp.value.trim()).then(function (ok) { if (ok) { st.textContent = '登录成功!正在刷新...'; st.style.color = '#27ae60'; setTimeout(function () { location.reload(); }, 1000); } else { st.textContent = '验证码错误,已换新图,请重试'; st.style.color = '#e74c3c'; refreshCaptcha().then(function (d2) { img.src = d2; }); } }).catch(function (e) { st.textContent = '登录出错: ' + e.message; st.style.color = '#e74c3c'; Utils.error('重新登录失败: ' + e.message); }); }; box.appendChild(title); box.appendChild(img); box.appendChild(inp); box.appendChild(btn); box.appendChild(st); document.body.appendChild(box); inp.focus(); Utils.info('已弹出重新登录框,请输入验证码'); }).catch(function (e) { Utils.error('弹出登录框失败: ' + e.message); StatusPanel.showAlert('登录框加载失败,请手动重新登录'); self._reloginShown = false; }); }, init: function () { var self = this; if (typeof IS_VIDEO_PAGE !== "undefined" && IS_VIDEO_PAGE) { this._mode = "videoPlayer"; } else { this._mode = "studyCenter"; } Utils.info("主控制器初始化... 模式: " + this._mode); function onDOMReady(callback) { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", callback); } else { callback(); } } onDOMReady(function () { PageDetector.init(); StatusPanel.init(); if (self._mode === "studyCenter") { CourseAutomation.init(); pageWin.alert = function (msg) { Utils.info("[拦截alert] " + (msg || "").substring(0, 100)); }; pageWin.confirm = function (msg) { Utils.info("[拦截confirm] " + (msg || "").substring(0, 100) + " -> 自动返回true"); return true; }; pageWin.prompt = function (msg, def) { Utils.info("[拦截prompt] " + (msg || "").substring(0, 100)); return def || ""; }; var _lastPostMsgTime = {}; window.addEventListener("message", function (e) { if (!e.data || !e.data.type) return; var now = Date.now(); var msgType = e.data.type; if (_lastPostMsgTime[msgType] && now - _lastPostMsgTime[msgType] < 30000) { Utils.info("忽略重复postMessage(" + msgType + "),30秒防抖中"); return; } _lastPostMsgTime[msgType] = now; if (e.data.type === "gdce-video-ended") { Utils.info("收到视频播放完毕消息(postMessage),准备选择下一门课程"); StatusPanel.hideAlert(); self._videoWindow = null; self._onVideoFinished(); } else if (e.data.type === "gdce-video-error") { var errMsg = (e.data.msg || "未知错误"); var isCourseDone = errMsg.indexOf("不在学习中") !== -1 || errMsg.indexOf("已完成") !== -1; if (errMsg.indexOf("过期") !== -1) { Utils.error("授权码已过期,自动学习已暂停:请在弹出的登录框中输入验证码"); StatusPanel.showAlert("授权码已过期:请在登录框中输入验证码"); self.pause(); self.showReloginBox(); } else if (isCourseDone) { Utils.info("收到课程结束消息(postMessage): " + errMsg.substring(0, 50)); self._videoWindow = null; self._videoPlaying = false; self._onVideoFinished(); } else { Utils.warn("收到视频错误消息(postMessage): " + errMsg.substring(0, 50) + "(不自动跳过)"); StatusPanel.showAlert("视频错误: " + errMsg.substring(0, 50)); } } else if (e.data.type === "gdce-navigate-iframe") { var url = e.data.url || ""; if (url) { Utils.info("收到iframe导航请求,在secondIframe中加载: " + url.substring(0, 80)); var secondIframe = document.querySelector(CONFIG.SELECTORS.secondIframe); if (secondIframe) { secondIframe.src = url; if (/CourseDetail/i.test(url)) { Utils.info("检测到CourseDetail页面,3秒后自动点击'进入学习'..."); setTimeout(function () { self._clickEnterStudyInIframe(secondIframe); }, 3000); } } else { Utils.warn("未找到secondIframe,无法加载视频页面"); } } } }); var _lastGMStatusTime = 0; if (typeof GM_addValueChangeListener === "function") { GM_addValueChangeListener("gdce_video_status", function (key, oldVal, newVal, remote) { if (!newVal || !remote) return; var now = Date.now(); if (now - _lastGMStatusTime < 60000) { Utils.info("忽略GM状态(60秒全局防抖中): " + JSON.stringify(newVal).substring(0, 80)); return; } _lastGMStatusTime = now; Utils.info("收到GM跨域视频状态变化: " + JSON.stringify(newVal).substring(0, 100)); self._lastVideoActivityTs = Date.now(); var msgStr = String(newVal.msg || ""); var isCourseDone = msgStr.indexOf("不在学习中") !== -1 || msgStr.indexOf("已完成") !== -1; if (newVal.status === "ended" || (newVal.status === "error" && isCourseDone)) { Utils.info("视频播放完毕(GM跨域通知" + (isCourseDone ? ",课程已结束" : "") + "),准备选择下一门课程"); try { GM_setValue("gdce_video_status", null); } catch (e) {} StatusPanel.hideAlert(); self._videoWindow = null; self._videoPlaying = false; self._onVideoFinished(); } else if (newVal.status === "error") { if (msgStr.indexOf("过期") !== -1) { Utils.error("授权码已过期,自动学习已暂停:请在弹出的登录框中输入验证码"); StatusPanel.showAlert("授权码已过期:请在登录框中输入验证码"); self.pause(); self.showReloginBox(); } else { Utils.warn("视频错误(GM跨域通知): " + msgStr.substring(0, 80) + "(不自动跳过,等待人工处理或超时)"); StatusPanel.showAlert("视频错误: " + msgStr.substring(0, 50)); } } }); Utils.info("已注册GM跨域视频状态监听器"); } var origOpen = pageWin.open; pageWin.open = function () { var win = origOpen.apply(pageWin, arguments); if (win) { var url = arguments[0] || ""; Utils.info("检测到新窗口打开: " + url.substring(0, 80)); try { win.alert = function (msg) { Utils.info("[新窗口拦截alert] " + (msg || "").substring(0, 100)); }; win.confirm = function (msg) { Utils.info("[新窗口拦截confirm] " + (msg || "").substring(0, 100) + " -> 自动返回true"); return true; }; Utils.info("已在新窗口中注入alert/confirm拦截器"); } catch (e) { Utils.warn("注入新窗口拦截器失败: " + e.message); } if (url.indexOf("CourseDetail") !== -1) { Utils.info("检测到课程详情弹窗窗口,自动处理"); self._handleCourseDetailPopup(win); } else { Utils.info("记录为视频窗口"); self._videoWindow = win; self._videoPlaying = true; self._startVideoWindowCheck(); Utils.setConfig("videoStatus", "playing"); } } return win; }; self._statusCheckTimer = setInterval(function () { self._checkVideoStatus(); }, 10000); } VideoMonitor.init( function () { self._onVideoEnd(); }, function (e) { self._onVideoError(e); } ); if (self._mode === "studyCenter") { setTimeout(function () { PageAnalyzer.analyze(); }, 3000); } PageDetector.onPageChange(function (pageType) { Utils.info("页面类型变化: " + pageType); self._dispatchByPageType(pageType); }); self._initTimer = setTimeout(function () { self.start(); }, self._mode === "videoPlayer" ? 2000 : 5000); if (self._mode === "studyCenter") { self._hoursTimer = setInterval(function () { self._updateStudyHours(); }, 30000); } document.addEventListener("keydown", function (e) { if (e.ctrlKey && e.shiftKey && e.key === "A") { e.preventDefault(); Utils.info("手动触发页面分析"); PageAnalyzer.analyze(); } }); Utils.info("主控制器初始化完成(Ctrl+Shift+A 可手动触发页面分析)"); }); }, _checkVideoStatus: function () { var status = Utils.getConfig("videoStatus", null); if (typeof GM_getValue === "function") { try { var gmStatus = GM_getValue("gdce_video_status", null); if (gmStatus && gmStatus.status === "ended") { Utils.info("检测到视频状态为'ended'(GM跨域),准备选择下一门课程"); GM_setValue("gdce_video_status", null); StatusPanel.hideAlert(); this._videoPlaying = false; this._lastVideoActivityTs = Date.now(); this._onVideoFinished(); return; } if (this._videoPlaying && this._isRunning && !this._isPaused && typeof GM_addValueChangeListener === "function") { var hb = null; try { hb = GM_getValue("gdce_video_heartbeat", null); } catch (e) {} if (hb && hb.ts && hb.ts !== this._lastHeartbeatSeenTs) { this._lastHeartbeatSeenTs = hb.ts; this._lastVideoActivityTs = Date.now(); } else if (Date.now() - this._lastVideoActivityTs > this.VIDEO_WATCHDOG_TIMEOUT) { Utils.warn("看门狗:视频播放期间超过" + Math.round(this.VIDEO_WATCHDOG_TIMEOUT / 60000) + "分钟无心跳,判定视频标签页已死亡,跳到下一门课程"); StatusPanel.showAlert("视频标签页无响应,自动切换下一门课程"); this._videoPlaying = false; this._lastVideoActivityTs = Date.now(); this._closeVideoTab(); this._onVideoFinished(); return; } } } catch (e) {} } if (status === "ended") { Utils.info("检测到视频状态为'ended'(localStorage),准备选择下一门课程"); Utils.setConfig("videoStatus", "idle"); StatusPanel.hideAlert(); this._videoPlaying = false; this._onVideoFinished(); } }, _closeVideoTab: function () { if (this._videoTab) { try { if (typeof this._videoTab.close === "function") { this._videoTab.close(); Utils.info("已通过tab.close()关闭旧视频标签页"); } else { Utils.warn("tab对象没有close()方法,无法从学习中心侧关闭视频标签页,等待视频页自行关闭"); } } catch (e) { Utils.warn("关闭旧视频标签页失败: " + e.message); } this._videoTab = null; } if (this._videoWindow) { try { if (!this._videoWindow.closed) this._videoWindow.close(); } catch (e) {} this._videoWindow = null; } }, _onVideoFinished: function () { if (this._videoFinishedLock) { Utils.info("_onVideoFinished已被调用,跳过重复调用(锁保护中)"); return; } this._videoFinishedLock = true; this._videoPlaying = false; this._lastVideoActivityTs = Date.now(); var self = this; clearTimeout(this._nextCourseTimer); this._closeVideoTab(); this._updateStudyHours(); setTimeout(function () { if (self._videoFinishedLock) { Utils.warn("_videoFinishedLock 120秒超时,强制释放"); self._videoFinishedLock = false; } }, 120000); this._nextCourseTimer = setTimeout(function () { if (!self._isRunning || self._isPaused) return; if (CourseAutomation._phase === "learning") { CourseAutomation.onCourseLearned(); } else { CourseAutomation.autoSelectAndEnterCourse(); } }, 3000); }, _startVideoWindowCheck: function () { var self = this; clearInterval(this._videoWindowCheckTimer); this._videoWindowCheckTimer = setInterval(function () { if (!self._videoWindow && !self._videoTab) { clearInterval(self._videoWindowCheckTimer); return; } if (self._videoWindow) { try { if (self._videoWindow.closed) { Utils.info("检测到视频窗口已关闭,准备选择下一门课程"); self._videoWindow = null; self._videoPlaying = false; clearInterval(self._videoWindowCheckTimer); self._onVideoFinished(); return; } } catch (e) { } } if (self._videoTab) { try { if (self._videoTab.closed) { Utils.info("检测到视频标签页(GM)已关闭,准备选择下一门课程"); self._videoTab = null; self._videoPlaying = false; clearInterval(self._videoWindowCheckTimer); self._onVideoFinished(); return; } } catch (e) { } } }, 5000); }, _handleCourseDetailPopup: function (popupWin) { var self = this; setTimeout(function () { try { var confirmBtn = popupWin.document.querySelector(CONFIG.SELECTORS.layerConfirm); if (confirmBtn) { Utils.info("弹窗中找到确认按钮,点击"); confirmBtn.click(); } var closeBtn = popupWin.document.querySelector(CONFIG.SELECTORS.layerClose); if (closeBtn) { Utils.info("弹窗中找到关闭按钮,点击"); closeBtn.click(); } } catch (e) { Utils.warn("无法访问弹窗内容: " + e.message); } try { popupWin.close(); Utils.info("已关闭课程详情弹窗窗口"); } catch (e) { Utils.warn("关闭弹窗窗口失败: " + e.message); } }, 2000); }, start: function () { this._isRunning = true; this._isPaused = false; Utils.info("自动学习已启动"); if (this._mode === "videoPlayer") { Utils.setConfig("videoStatus", "playing"); this._handleVideoPlay(); } else { this._dispatchByPageType(PageDetector.currentPage); } }, pause: function () { this._isPaused = true; VideoMonitor.stopMonitoring(); Utils.info("自动学习已暂停"); }, resume: function () { this._isPaused = false; Utils.info("自动学习已恢复"); if (this._mode === "videoPlayer") { this._handleVideoPlay(); } else { this._dispatchByPageType(PageDetector.currentPage); } }, stop: function () { this._isRunning = false; VideoMonitor.stopMonitoring(); CourseAutomation.stop(); clearTimeout(this._initTimer); clearInterval(this._hoursTimer); clearTimeout(this._dispatchTimer); clearTimeout(this._nextCourseTimer); clearInterval(this._videoWindowCheckTimer); clearInterval(this._statusCheckTimer); Utils.info("自动学习已停止"); }, _dispatchByPageType: function (pageType) { if (this._isPaused || !this._isRunning) return; if (pageType === this._lastDispatchedType) { Utils.info("页面类型未变化,跳过重复调度: " + pageType); return; } this._lastDispatchedType = pageType; clearTimeout(this._dispatchTimer); switch (pageType) { case "studyCenter": this._dispatchTimer = setTimeout(function () { MainController._handleStudyCenter(); }, 1000); break; case "courseDetail": this._dispatchTimer = setTimeout(function () { MainController._handleCourseDetail(); }, 1000); break; case "courseList": this._dispatchTimer = setTimeout(function () { MainController._handleCourseList(); }, 1000); break; case "videoPlay": this._dispatchTimer = setTimeout(function () { MainController._handleVideoPlay(); }, 1000); break; default: Utils.info("未知页面类型,等待页面变化..."); break; } }, _handleStudyCenter: function () { Utils.info("当前页面:学习中心主页,开始选课流程"); var self = this; setTimeout(function () { if (self._isPaused) return; CourseAutomation.autoSelectAndEnterCourse(); }, 3000); }, _handleCourseList: function () { Utils.info("当前页面:课程列表页"); var self = this; setTimeout(function () { if (self._isPaused) return; CourseAutomation.autoSelectAndEnterCourse(); CourseAutomation.checkMultipleCourses(); }, 3000); }, _handleCourseDetail: function () { Utils.info("当前页面:课程详情页"); var self = this; function dismissPopups() { var confirmBtn = document.querySelector(CONFIG.SELECTORS.layerConfirm); if (confirmBtn) { Utils.info("检测到弹窗确认按钮,自动点击"); Utils.safeClick(confirmBtn); } var closeBtn = document.querySelector(CONFIG.SELECTORS.layerClose); if (closeBtn) { Utils.info("检测到弹窗关闭按钮,自动关闭"); Utils.safeClick(closeBtn); } } dismissPopups(); setTimeout(function () { if (self._isPaused) return; dismissPopups(); var btn = document.querySelector(CONFIG.SELECTORS.enterStudyBtn); if (!btn) btn = Utils.qsIframes(CONFIG.SELECTORS.enterStudyBtn); if (!btn) btn = Utils.findByTextIframes("input[type='submit'], button, a", "进入学习"); if (btn) { Utils.info("找到'进入学习'按钮,准备点击"); Utils.safeClick(btn); Utils.info("已点击'进入学习'按钮"); } else { Utils.warn("未找到'进入学习'按钮,3秒后重试"); setTimeout(function () { dismissPopups(); var retryBtn = document.querySelector(CONFIG.SELECTORS.enterStudyBtn); if (!retryBtn) retryBtn = Utils.qsIframes(CONFIG.SELECTORS.enterStudyBtn); if (!retryBtn) retryBtn = Utils.findByTextIframes("input[type='submit'], button, a", "进入学习"); if (retryBtn) { Utils.info("重试找到'进入学习'按钮,点击"); Utils.safeClick(retryBtn); } else { Utils.error("重试仍未找到'进入学习'按钮"); } }, 3000); } }, 2000); }, _handleVideoPlay: function () { var self = this; Utils.info("当前页面:视频播放页"); if (this._mode === "studyCenter") { var videoEl = Utils.qsIframes(CONFIG.SELECTORS.videoPlayer); if (videoEl) { Utils.info("找到同域视频元素,启动监控"); VideoMonitor.stopMonitoring(); setTimeout(function () { VideoMonitor.startMonitoring(); }, 3000); } else { Utils.info("视频在跨域iframe中播放,等待iframe中脚本postMessage通知播放完毕"); } return; } VideoMonitor.stopMonitoring(); setTimeout(function () { VideoMonitor.startMonitoring(); }, 3000); var videoTimeout = setTimeout(function () { if (VideoMonitor._isMonitoring) return; Utils.warn("视频查找超时(90秒),跳过此课程"); self._onVideoEnd(); }, 90000); var layerCheckTimer = setInterval(function () { var errDialog = document.querySelector(".layui-layer-content"); if (errDialog) { var msg = errDialog.textContent || ""; var isExpired = msg.indexOf("过期") !== -1; if (isExpired) { clearInterval(layerCheckTimer); clearTimeout(videoTimeout); var expClose = document.querySelector(".layui-layer-close1, .layui-layer-btn0"); if (expClose) expClose.click(); Utils.error("授权码已过期!请重新登录学习中心,然后刷新页面恢复自动学习"); StatusPanel.showAlert("授权码已过期:请重新登录学习中心并刷新页面"); try { if (typeof GM_setValue === "function") { GM_setValue("gdce_video_status", { status: "error", msg: "授权码已过期,请重新登录", ts: Date.now() }); } } catch (e) {} try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ type: "gdce-video-error", msg: "授权码已过期,请重新登录" }, "*"); } } catch (e) {} return; } if (msg.indexOf("无法访问") !== -1 || msg.indexOf("不存在") !== -1 || msg.indexOf("错误") !== -1) { Utils.warn("检测到视频错误对话框: " + msg.substring(0, 50)); clearInterval(layerCheckTimer); clearTimeout(videoTimeout); var closeBtn = document.querySelector(".layui-layer-close1, .layui-layer-btn0"); if (closeBtn) closeBtn.click(); setTimeout(function () { self._onVideoEnd(); }, 2000); } } }, 3000); setTimeout(function () { clearInterval(layerCheckTimer); }, 120000); }, _onVideoEnd: function () { Utils.info("视频播放完毕"); if (this._mode === "videoPlayer") { Utils.setConfig("videoStatus", "ended"); if (typeof GM_setValue === "function") { try { GM_setValue("gdce_video_status", { status: "ended", ts: Date.now() }); Utils.info("已通过GM_setValue发送视频完毕状态"); } catch (e) { Utils.warn("GM_setValue失败: " + e.message); } } try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ type: "gdce-video-ended" }, "*"); Utils.info("已发送postMessage到opener窗口"); } } catch (e) { Utils.warn("postMessage到opener失败: " + e.message); } try { if (window.parent && window.parent !== window.self) { window.parent.postMessage({ type: "gdce-video-ended" }, "*"); Utils.info("已发送postMessage到parent窗口"); } } catch (e) { Utils.warn("postMessage到parent失败: " + e.message); } try { if (window.top && window.top !== window.self) { window.top.postMessage({ type: "gdce-video-ended" }, "*"); Utils.info("已发送postMessage到top窗口"); } } catch (e) { Utils.warn("postMessage到top失败: " + e.message); } StatusPanel.showAlert("视频播放完毕!等待学习中心关闭此窗口..."); setTimeout(function () { Utils.info("学习中心未关闭此标签页,尝试自行关闭"); if (typeof GM_closeTab === "function") { try { GM_closeTab(); return; } catch (e) { Utils.warn("GM_closeTab失败: " + e.message); } } try { window.close(); } catch (e) {} try { location.href = "about:blank"; } catch (e) {} }, 10000); return; } var self = this; Utils.randomDelay().then(function () { var success = CourseAutomation.clickNextSection(); if (!success) { Utils.info("没有下一节了,学习下一门课程"); setTimeout(function () { CourseAutomation.onCourseLearned(); }, 3000); } }); }, _onVideoError: function (e) { Utils.error("视频播放出错,尝试恢复"); setTimeout(function () { VideoMonitor.stopMonitoring(); setTimeout(function () { VideoMonitor.startMonitoring(); }, 3000); }, 2000); }, _updateStudyHours: function () { var self = this; var hours = CourseAutomation.getStudyHours(); var targets = (typeof CourseAutomation._readTargets === "function" && CourseAutomation._targets) ? CourseAutomation._targets : null; StatusPanel.updateStudyHours(hours.credited, hours.required, typeof CourseAutomation._creditedRequired === "number" ? CourseAutomation._creditedRequired : null, targets ? targets.reqMin : null); fetch("https://gbpx.gd.gov.cn/gdceportal/study/studyCenter.aspx", { method: "GET", credentials: "include" }).then(function (response) { if (!response.ok) return; return response.text(); }).then(function (html) { if (!html) return; var parser = new DOMParser(); var doc = parser.parseFromString(html, "text/html"); var creditedEl = doc.querySelector(CONFIG.SELECTORS.creditedHours); var requiredEl = doc.querySelector(CONFIG.SELECTORS.requiredHours); if (creditedEl) { var newCredited = creditedEl.textContent.trim(); var newRequired = requiredEl ? requiredEl.textContent.trim() : hours.required; var mReq = html.match(/必修课程至少\s*([0-9.]+)\s*学时/); var reqMin = mReq ? parseFloat(mReq[1]) : (targets ? targets.reqMin : null); Utils.info("[学时刷新] 正在从其它标签页中获取学时: 已获 " + newCredited + " / 要求 " + newRequired + "(必修已获 " + (CourseAutomation._creditedRequired || 0) + " / 至少 " + reqMin + ")"); StatusPanel.updateStudyHours(newCredited, newRequired, typeof CourseAutomation._creditedRequired === "number" ? CourseAutomation._creditedRequired : null, typeof reqMin === "number" ? reqMin : null); var localCredited = document.querySelector(CONFIG.SELECTORS.creditedHours); if (localCredited) localCredited.textContent = newCredited; var localRequired = document.querySelector(CONFIG.SELECTORS.requiredHours); if (localRequired) localRequired.textContent = newRequired; } }).catch(function (e) { Utils.warn("[学时刷新] 获取学时失败: " + e.message); }); }, get isRunning() { return this._isRunning; }, get isPaused() { return this._isPaused; } }; MainController.init(); })();