// ==UserScript== // @name 广东省干部培训网络学院 - 自动学习助手 // @namespace https://scriptcat.org/gdce-auto-study // @version 0.6.1 // @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 unsafeWindow // @run-at document-start // @license MIT // ==/UserScript== (function () { "use strict"; 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 { GM_openInTab(url, { active: true }); return 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 true; } if (IS_VIDEO_PAGE) { 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); try { if (typeof GM_setValue === "function") { GM_setValue("gdce_video_status", { status: "error", msg: String(msg), ts: Date.now() }); } if (pageWin.parent && pageWin.parent !== pageWin.self) { pageWin.parent.postMessage({ type: "gdce-video-error", msg: String(msg) }, "*"); } if (pageWin.opener && !pageWin.opener.closed) { pageWin.opener.postMessage({ type: "gdce-video-error", msg: String(msg) }, "*"); } if (pageWin.top && pageWin.top !== pageWin.self) { pageWin.top.postMessage({ type: "gdce-video-error", msg: String(msg) }, "*"); } } 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/ }, 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 + "\""; } }; 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, 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.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; 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) { 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"); 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 = {}; }, _checkStatus: function () { if (!this._video || !this._isMonitoring) return; var currentTime = this._video.currentTime; var duration = this._video.duration; var paused = this._video.paused; var ended = this._video.ended; if (duration > 0) { var pct = Math.round((currentTime / duration) * 100); StatusPanel.updateProgress(pct, currentTime, duration); } if (!paused && !ended) { if (Math.abs(currentTime - this._lastProgress) < 0.5) { this._stuckCount++; if (this._stuckCount >= CONFIG.STUCK_THRESHOLD / (CONFIG.CHECK_INTERVAL / 1000)) { Utils.warn("视频可能卡住,尝试刷新恢复"); this._stuckCount = 0; this._tryRecover(); } } else { this._stuckCount = 0; } } this._lastProgress = currentTime; }, _checkAndResume: function () { if (!this._video || !this._isMonitoring) return; 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.name === "NotAllowedError" && !self._video.muted) { Utils.info("自动播放被阻止,尝试静音播放"); self._video.muted = true; self._video.play().then(function () { Utils.info("静音播放成功,等待用户交互恢复声音"); self._waitForInteractionAndUnmute(); }).catch(function (e2) { Utils.warn("静音播放也失败: " + e2.message); }); } else { Utils.warn("自动恢复播放失败: " + e.message); } }); } } }, _tryRecover: function () { if (!this._video) return; try { this._video.pause(); var self = this; setTimeout(function () { self._video.play().catch(function (e) { 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, 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._neededHours -= course.hours; Utils.info("[选课] 剩余需选: " + Math.max(0, self._neededHours) + " 学时"); return "success"; } else { Utils.warn("[选课] 提交返回非200状态: " + postResponse.status); return "fallback"; } }); }).then(function (result) { if (result === "fallback") { Utils.safeClick(link); self._neededHours -= course.hours; return self._dismissPopupAndContinue(3000); } return Utils.sleep(1500); }).catch(function (e) { Utils.warn("[选课] 请求失败: " + e.message + ",回退到直接点击"); Utils.safeClick(link); self._neededHours -= course.hours; 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._neededHours -= course.hours; return Utils.sleep(2000); } Utils.warn("[选课] 请求失败: HTTP " + response.status); Utils.safeClick(link); self._neededHours -= course.hours; return self._dismissPopupAndContinue(3000); }).catch(function (e) { Utils.warn("[选课] 请求失败: " + e.message); Utils.safeClick(link); self._neededHours -= course.hours; 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" }; }, hasReachedTargetHours: function () { var hours = this.getStudyHours(); var credited = parseFloat(hours.credited) || 0; if (credited >= this.TARGET_HOURS) { Utils.info("已达到目标学时 " + this.TARGET_HOURS + "(当前: " + credited + ")"); return true; } Utils.info("当前学时: " + credited + " / 目标: " + this.TARGET_HOURS); return false; }, _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; if (this.hasReachedTargetHours()) { Utils.info("已达到目标学时,停止自动选课"); StatusPanel.showAlert("已达到 " + this.TARGET_HOURS + " 学时目标!"); return; } this._phase = "scanning"; this._completedHours = parseFloat(this.getStudyHours().credited) || 0; Utils.info("=== 第1步:读取已完成学时: " + this._completedHours + " ==="); Utils.info("=== 第2步:扫描'我的课程'中未完成的课程(含分页) ==="); this._clickMyCourse().then(function () { return self._scanAllPages(20000); }).then(function (courses) { self._myCourses = []; self._myCoursesHours = 0; for (var i = 0; i < courses.length; i++) { var c = courses[i]; if (!c.isCompleted) { self._myCourses.push(c); self._myCoursesHours += c.hours; } } Utils.info("我的课程中未完成: " + self._myCourses.length + " 门,共 " + self._myCoursesHours + " 学时"); var remaining = self.TARGET_HOURS - self._completedHours; self._neededHours = remaining - self._myCoursesHours; Utils.info("=== 第3步:学时缺口分析 ==="); Utils.info(" 目标学时: " + self.TARGET_HOURS); Utils.info(" 已完成: " + self._completedHours); Utils.info(" 我的课程中未完成: " + self._myCoursesHours + " 学时"); Utils.info(" 还需选课: " + Math.max(0, self._neededHours) + " 学时"); if (self._neededHours <= 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) { 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._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 selectNext(index) { if (index >= toSelect.length || self._neededHours <= 0) { Utils.info("本分类选课完成,共选 " + selectedCount + " 门"); return Utils.sleep(2000); } var course = toSelect[index]; 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._neededHours -= course.hours; return self._dismissPopupAndContinue(3000).then(function () { return selectNext(index + 1); }); }); } else if (course.continueLink || course.videoUrl) { self._neededHours -= course.hours; 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._scanAllPages(20000); }).then(function (courses) { self._selectedCourses = []; for (var i = 0; i < courses.length; i++) { if (!courses[i].isCompleted) { self._selectedCourses.push(courses[i]); } } Utils.info("我的课程中未完成共 " + self._selectedCourses.length + " 门,开始学习"); self._currentLearnIndex = 0; self._learnNextCourse(); }).catch(function (e) { Utils.error("回到我的课程失败: " + e.message); }); }, _startLearningMyCourses: function () { this._phase = "learning"; this._selectedCourses = this._myCourses; this._currentLearnIndex = 0; Utils.info("我的课程中未完成共 " + this._selectedCourses.length + " 门,开始学习"); this._learnNextCourse(); }, _learnNextCourse: function () { if (this._currentLearnIndex >= this._selectedCourses.length) { Utils.info("所有课程已学习完毕!"); StatusPanel.showAlert("所有课程已学习完毕!"); 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)); if (!openInTab(videoUrl)) { Utils.warn("openInTab失败,回退到点击链接"); Utils.safeClick(course.continueLink); } } 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 () { this._currentLearnIndex++; Utils.info("课程学习完毕,准备学习下一门(" + this._currentLearnIndex + "/" + this._selectedCourses.length + ")"); 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', '
', '
' ].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) { var cEl = document.getElementById("gdce-credited"); var rEl = document.getElementById("gdce-required"); if (cEl) cEl.textContent = credited; if (rEl) rEl.textContent = required; }, 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, 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 || ""; }; window.addEventListener("message", function (e) { if (!e.data || !e.data.type) return; 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") { Utils.warn("收到视频错误消息: " + (e.data.msg || "未知错误")); StatusPanel.showAlert("视频加载失败: " + (e.data.msg || "").substring(0, 50) + ",3秒后跳过..."); self._videoWindow = null; setTimeout(function () { self._onVideoFinished(); }, 3000); } 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,无法加载视频页面"); } } } }); if (typeof GM_addValueChangeListener === "function") { GM_addValueChangeListener("gdce_video_status", function (key, oldVal, newVal, remote) { if (!newVal || !remote) return; Utils.info("收到GM跨域视频状态变化: " + JSON.stringify(newVal).substring(0, 100)); if (newVal.status === "ended") { Utils.info("视频播放完毕(GM跨域通知),准备选择下一门课程"); StatusPanel.hideAlert(); self._videoWindow = null; self._onVideoFinished(); } else if (newVal.status === "error") { Utils.warn("视频错误(GM跨域通知): " + (newVal.msg || "未知错误")); StatusPanel.showAlert("视频加载失败: " + (newVal.msg || "").substring(0, 50) + ",3秒后跳过..."); self._videoWindow = null; setTimeout(function () { self._onVideoFinished(); }, 3000); } }); 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._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._onVideoFinished(); return; } } catch (e) {} } if (status === "ended") { Utils.info("检测到视频状态为'ended'(localStorage),准备选择下一门课程"); Utils.setConfig("videoStatus", "idle"); StatusPanel.hideAlert(); this._onVideoFinished(); } }, _onVideoFinished: function () { var self = this; clearTimeout(this._nextCourseTimer); this._updateStudyHours(); 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) { clearInterval(self._videoWindowCheckTimer); return; } try { if (self._videoWindow.closed) { Utils.info("检测到视频窗口已关闭,准备选择下一门课程"); self._videoWindow = null; clearInterval(self._videoWindowCheckTimer); self._onVideoFinished(); } } 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 || ""; 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("视频播放完毕!3秒后自动关闭此窗口..."); setTimeout(function () { Utils.info("关闭视频播放窗口"); window.close(); setTimeout(function () { StatusPanel.showAlert("视频播放完毕!请手动关闭此窗口并返回学习中心"); }, 1000); }, 3000); 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(); StatusPanel.updateStudyHours(hours.credited, hours.required); 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; Utils.info("[学时刷新] 正在从其它标签页中获取学时: 已获 " + newCredited + " / 要求 " + newRequired); StatusPanel.updateStudyHours(newCredited, newRequired); 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(); })();