// ==UserScript== // @name 浙江省药品继续教育学习平台(mpsc.zjfda-tc.org.cn)学习助手 // @namespace https://mpsc.zjfda-tc.org.cn/ // @version 1.1 // @author 柠檬真酸 // @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png // @description 浙江省药品监督管理局继续教育平台:勾选项目自动更新视频学时、Pro 可自动提交测验,支持效率与 1:1 学习模式 // @antifeature payment 免费体验3个视频章节,升级Pro不限;测验需Pro // @antifeature membership 需云端授权 // @connect oa45.ahzsksw.cn // @connect oa888.ahzsksw.cn // @connect mapi.cloudsymbol.cn // @connect mpsc.zjfda-tc.org.cn // @connect huaweicloudobs.ahjxjy.cn // @connect card.wlxy.live // @match https://mpsc.zjfda-tc.org.cn/* // @match http://mpsc.zjfda-tc.org.cn/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @grant GM_openInTab // @grant unsafeWindow // @run-at document-idle // @license All Rights Reserved // ==/UserScript== (function bindOa888GlobalImportantNotice() { const GLOBAL_NOTICE_JSON = "https://oa888.ahzsksw.cn/api/global/important-notice.json"; const HOST_ID = "oa888-global-bubble-host-mpsc"; const DISMISS_KEY = "oa888_global_notice_dismiss_mpsc_v1"; const LEGACY_DISMISS_KEYS = [ "oa888_global_notice_dismiss_v2", "oa888_global_notice_dismiss_v1", ]; function gmGet(url) { return new Promise((resolve, reject) => { const g = typeof GM_xmlhttpRequest === "function" ? GM_xmlhttpRequest : typeof GM !== "undefined" && GM.xmlHttpRequest ? GM.xmlHttpRequest.bind(GM) : null; if (!g) { fetch(url, { cache: "no-store" }) .then((r) => r.text()) .then(resolve) .catch(reject); return; } g({ method: "GET", url, anonymous: true, onload: (res) => { if (res.status >= 200 && res.status < 300) resolve(String(res.responseText || "")); else reject(new Error("HTTP " + res.status)); }, onerror: () => reject(new Error("network")), }); }); } function sanitizeHtml(html) { const tpl = document.createElement("template"); tpl.innerHTML = String(html || ""); const allowed = new Set([ "A", "B", "STRONG", "EM", "I", "U", "BR", "P", "SPAN", "DIV", "UL", "OL", "LI", "H3", "H4", "SMALL", "CODE", ]); const walk = (node) => { const kids = Array.from(node.childNodes); for (const child of kids) { if (child.nodeType === 1) { const el = child; if (!allowed.has(el.tagName)) { while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el); el.remove(); continue; } for (const attr of Array.from(el.attributes)) { const n = attr.name.toLowerCase(); if (n.startsWith("on") || n === "style" || n === "src" || n === "srcdoc") { el.removeAttribute(attr.name); continue; } if (el.tagName === "A") { if (n === "href") { const href = String(el.getAttribute("href") || "").trim(); if (!/^https?:\/\//i.test(href) && !/^mailto:/i.test(href)) { el.removeAttribute("href"); } else { el.setAttribute("target", "_blank"); el.setAttribute("rel", "noopener noreferrer"); } } else if (n !== "target" && n !== "rel") { el.removeAttribute(attr.name); } } else { el.removeAttribute(attr.name); } } walk(el); } else if (child.nodeType === 8) { child.remove(); } } }; walk(tpl.content); return tpl.innerHTML; } function hashText(s) { let h = 0; const str = String(s || ""); for (let i = 0; i < str.length; i++) h = ((h << 5) - h + str.charCodeAt(i)) | 0; return String(h); } function findPanelRoot() { const preferred = document.getElementById("mpsc-auto-panel"); if (preferred) return preferred; const anchors = document.querySelectorAll( "[id$='-ann-text'], .qingshu-ann, .gs-ann, .hbut-ann, .rjpx-ann, .zkzx-ann, .jxjy-ann, .mpsc-ann, [class*='-ann'], [id*='panel'], [class*='panel']" ); for (const a of anchors) { let el = a; for (let i = 0; i < 8 && el; i++) { const st = window.getComputedStyle(el); if ( (st.position === "fixed" || st.position === "absolute") && el.offsetWidth > 180 && el.offsetHeight > 80 ) { return el; } el = el.parentElement; } } const fixed = Array.from(document.querySelectorAll("div")).find((d) => { const st = window.getComputedStyle(d); return ( st.position === "fixed" && d.offsetWidth > 220 && d.offsetHeight > 120 && d.offsetWidth < 520 ); }); return fixed || document.body; } function removeOldBanner() { const old = document.getElementById("oa888-global-ann"); if (old) old.remove(); } function showBubble(html, opts) { removeOldBanner(); const clean = sanitizeHtml(html).trim(); if (!clean) return; const sig = hashText(clean); const force = !!(opts && opts.force); try { if (!force && sessionStorage.getItem(DISMISS_KEY) === sig) return; } catch (_) {} try { document.getElementById("oa888-global-bubble-host")?.remove(); } catch (_) {} let host = document.getElementById(HOST_ID); if (!host) { host = document.createElement("div"); host.id = HOST_ID; const root = findPanelRoot(); if (!root) return; const st = window.getComputedStyle(root); if (st.position === "static") root.style.position = "relative"; root.appendChild(host); } host.innerHTML = ""; host.style.setProperty("position", "absolute", "important"); host.style.setProperty("inset", "0", "important"); host.style.setProperty("z-index", "2147483000", "important"); host.style.setProperty("pointer-events", "none", "important"); host.style.setProperty("display", "flex", "important"); host.style.setProperty("align-items", "flex-start", "important"); host.style.setProperty("justify-content", "center", "important"); host.style.setProperty("padding", "14px 12px 0", "important"); host.style.setProperty("margin", "0", "important"); host.style.setProperty("box-sizing", "border-box", "important"); host.style.setProperty("width", "auto", "important"); host.style.setProperty("height", "auto", "important"); host.style.setProperty("float", "none", "important"); const mask = document.createElement("div"); mask.style.cssText = "position:absolute !important;inset:0 !important;background:rgba(15,23,42,.28);pointer-events:auto;border-radius:inherit;float:none !important;"; const card = document.createElement("div"); card.style.cssText = "pointer-events:auto;position:relative !important;z-index:1;width:min(100%,320px);margin-top:28px;" + "background:linear-gradient(165deg,#fffef8 0%,#fff7e6 55%,#ffefd2 100%);" + "border:1px solid #f0c36a;border-radius:16px;box-shadow:0 18px 40px rgba(120,70,10,.22);" + "padding:14px 14px 12px;color:#5c3b0a;font:13px/1.55 'PingFang SC','Microsoft YaHei',sans-serif;" + "animation:oa888pop .28s ease-out;float:none !important;"; if (!document.getElementById("oa888-global-bubble-style")) { const sty = document.createElement("style"); sty.id = "oa888-global-bubble-style"; sty.textContent = "@keyframes oa888pop{from{opacity:0;transform:translateY(-10px) scale(.96)}to{opacity:1;transform:none}}" + "#mpsc-auto-panel #" + HOST_ID + ",#" + HOST_ID + "{position:absolute !important;inset:0 !important;z-index:2147483000 !important;pointer-events:none !important;display:flex !important;float:none !important;}" + "#" + HOST_ID + " a{color:#b45309;text-decoration:underline;font-weight:600}" + "#" + HOST_ID + " p{margin:0 0 .55em}" + "#" + HOST_ID + " p:last-child{margin:0}"; document.head.appendChild(sty); } const head = document.createElement("div"); head.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;"; head.innerHTML = '
' + '!' + '\u91cd\u8981\u516c\u544a
'; const close = document.createElement("button"); close.type = "button"; close.textContent = "×"; close.setAttribute("aria-label", "\u5173\u95ed"); close.style.cssText = "border:0;background:transparent;color:#92400e;font-size:20px;line-height:1;cursor:pointer;padding:0 2px"; const dismiss = () => { try { sessionStorage.setItem(DISMISS_KEY, sig); } catch (_) {} host.remove(); }; close.onclick = dismiss; mask.onclick = dismiss; head.appendChild(close); const body = document.createElement("div"); body.innerHTML = clean; const foot = document.createElement("div"); foot.style.cssText = "margin-top:10px;display:flex;justify-content:flex-end"; const ok = document.createElement("button"); ok.type = "button"; ok.textContent = "\u77e5\u9053\u4e86"; ok.style.cssText = "border:0;border-radius:999px;padding:6px 14px;cursor:pointer;font-size:12px;font-weight:700;" + "background:linear-gradient(135deg,#f59e0b,#ea580c);color:#fff"; ok.onclick = dismiss; foot.appendChild(ok); card.appendChild(head); card.appendChild(body); card.appendChild(foot); host.appendChild(mask); host.appendChild(card); } async function run(opts) { try { const force = !!(opts && opts.force); if (!force && document.getElementById(HOST_ID)) return; const raw = await gmGet(GLOBAL_NOTICE_JSON); let data = null; try { data = JSON.parse(raw); } catch (_) { data = null; } let html = ""; if (data && data.ok !== false) { if (!data.enabled) return; html = String(data.text || data.html || ""); } else { html = String(raw || ""); } html = html.trim(); if (!html) return; if (!/[<>]/.test(html)) { html = html .replace(/&/g, "&") .replace(//g, ">") .replace(/\n/g, "
"); } showBubble(html, { force }); } catch (_) {} } const boot = () => { void run(); setTimeout(() => run(), 1600); setTimeout(() => run(), 4200); setTimeout(() => run(), 7000); }; window.__MPSC_OA888_NOTICE_RERUN__ = (force) => { try { document.getElementById(HOST_ID)?.remove(); document.getElementById("oa888-global-bubble-host")?.remove(); if (force) { sessionStorage.removeItem(DISMISS_KEY); LEGACY_DISMISS_KEYS.forEach((k) => { try { sessionStorage.removeItem(k); } catch (_) {} }); } } catch (_) {} void run({ force: !!force }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot, { once: true }); } else { boot(); } window.addEventListener("mpsc-panel-ready", () => { setTimeout(() => window.__MPSC_OA888_NOTICE_RERUN__?.(true), 300); }); })(); (function () { "use strict"; try { if (window.top !== window.self) return; } catch (_) { return; } const HOST = String(location.hostname || "").replace(/^www\./, ""); if (!/mpsc\.zjfda-tc\.org\.cn$/i.test(HOST)) return; const PAGE = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; const BOOT_KEY = "__MPSC_STUDY_HELPER_V103__"; try { if (PAGE[BOOT_KEY]) return; PAGE[BOOT_KEY] = true; } catch (_) { if (window[BOOT_KEY]) return; window[BOOT_KEY] = true; } const SCRIPT_VERSION = (() => { try { if (typeof GM_info !== "undefined" && GM_info?.script?.version) { return String(GM_info.script.version); } } catch (_) {} return "1.0.8"; })(); const PRODUCT_NAME = "\u6d59\u836f\u7ee7\u7eed\u6559\u80b2\u5b66\u4e60\u52a9\u624b"; const SITE_ORIGIN = "https://mpsc.zjfda-tc.org.cn"; const PC_API = "https://mapi.cloudsymbol.cn/educationPCServer"; const H5_API = "https://mapi.cloudsymbol.cn/educationH5Server"; const PANEL_LOGO_URL = "https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png"; const PANEL_NOTICE_FALLBACK = PRODUCT_NAME; const DEFAULT_CLOUD_API_BASE = "https://oa45.ahzsksw.cn"; const PRO_BUY_URL = "https://card.wlxy.live/details/4825E266"; const PRO_BUY_OPEN_MS = 0; const QQ_GROUP_NUMBER = "903117129"; const QQ_GROUP_LINK = "https://qun.qq.com/universal-share/share?ac=1&authKey=rxdL6YIJ0%2FxOEemjLqTGULvl5aAfJIVQcIvkvnwvmL%2FAmpFZnSafajYHgSXMUXvx&busi_data=eyJncm91cENvZGUiOiI5MDMxMTcxMjkiLCJ0b2tlbiI6IlB2dkFGSm5XRXBrSEhtQVFTUGQzdVNZakhNWDNMbW1kODA2enpoMi9obDh4SWp0YzBDODNFaGtwRU44Z0hyU0siLCJ1aW4iOiIxMjU0MzE1MTQifQ%3D%3D&data=9oyJixSPcigCQW-saV5eXlcMwV9C6J36XySx-rDHwVwNlofvRmd2ze5sLwFtHTbYbG4nAWIUrI0qftC6aTX9xg&svctype=4&tempid=h5_group_info"; const QUEUE_KEY = "mpsc_project_queue_v1"; const PROJECTS_CACHE_KEY = "mpsc_projects_cache_v1"; const PANEL_POS_KEY = "mpsc_panel_pos_v1"; const PANEL_COLLAPSED_KEY = "mpsc_panel_collapsed_v1"; const STUDY_MODE_KEY = "mpsc_study_mode_v1"; const ONLY_INCOMPLETE_KEY = "mpsc_only_incomplete_v2"; const AUTO_EXAM_KEY = "mpsc_auto_exam_v1"; const CLOUD_TOKEN_KEY = "mpsc_cloud_token_v1"; const CLOUD_LEASE_CACHE_KEY = "mpsc_cloud_lease_cache_v1"; const CLOUD_LAST_STATE_KEY = "mpsc_cloud_last_state_v1"; const CLOUD_PRO_EXPIRE_CACHE_KEY = "mpsc_cloud_pro_expire_cache_v1"; const state = { enabled: false, stopFlag: false, studyMode: "", projects: [], selected: new Set(), chapters: [], chapterLoading: false, chapterPreviewKey: "", panelRefreshing: false, panelHint: "", logLines: [], lastUserAction: "", currentProject: "", currentCourse: "", userId: "", userLabel: "", token: "", remotePanelNotice: "", queueVideoTotal: 0, queueVideoDone: 0, queueExamTotal: 0, queueExamDone: 0, cloudToken: "", cloudTier: "free", cloudLease: "", cloudLeaseExp: 0, cloudProExpireAt: 0, cloudRevoked: false, freeVideoLimit: 3, freeUsedVideos: 0, panelNoticePath: "/api/mpsc/panel-notice", proBuyUrl: PRO_BUY_URL, studyModeHintTimer: null, }; state.cloudToken = String(localStorage.getItem(CLOUD_TOKEN_KEY) || "").trim(); if (state.cloudToken) state.cloudTier = "unknown"; function gmGet(key, fallback) { try { const v = GM_getValue(key, fallback); return v === undefined || v === null ? fallback : v; } catch (_) { try { const ls = localStorage.getItem(key); return ls == null ? fallback : ls; } catch (e2) { return fallback; } } } function gmSet(key, val) { try { GM_setValue(key, val); } catch (_) {} try { localStorage.setItem(key, typeof val === "string" ? val : JSON.stringify(val)); } catch (_) {} } function loadQueue() { try { const raw = gmGet(QUEUE_KEY, "[]"); const arr = typeof raw === "string" ? JSON.parse(raw) : raw; return Array.isArray(arr) ? arr.map(String) : []; } catch (_) { return []; } } function saveQueue(ids) { gmSet(QUEUE_KEY, JSON.stringify(Array.from(ids || []).map(String))); } state.selected = new Set(loadQueue()); function getOnlyIncomplete() { return gmGet(ONLY_INCOMPLETE_KEY, "0") === "1"; } function setOnlyIncomplete(v) { gmSet(ONLY_INCOMPLETE_KEY, v ? "1" : "0"); } function getAutoExam() { return gmGet(AUTO_EXAM_KEY, "1") !== "0"; } function setAutoExam(v) { gmSet(AUTO_EXAM_KEY, v ? "1" : "0"); } function readStudyModeSelect() { const sel = document.getElementById("mpsc-study-mode"); const v = String(sel?.value || "").toLowerCase(); return v === "realtime" || v === "efficiency" ? v : ""; } function hasStudyModeSelected() { const ui = readStudyModeSelect(); if (ui) { state.studyMode = ui; return true; } if (state.enabled && (state.studyMode === "realtime" || state.studyMode === "efficiency")) { return true; } state.studyMode = ""; return false; } function getStudyMode() { const ui = readStudyModeSelect(); if (ui) return ui; const m = String(state.studyMode || gmGet(STUDY_MODE_KEY, "") || ""); if (m === "realtime" || m === "efficiency") return m; return ""; } function setStudyMode(mode) { const raw = String(mode || "").toLowerCase(); if (raw !== "realtime" && raw !== "efficiency") { state.studyMode = ""; syncStudyModeUi(""); return ""; } state.studyMode = raw; gmSet(STUDY_MODE_KEY, raw); syncStudyModeUi(raw); return raw; } function clearStudyModeSelection() { state.studyMode = ""; hideStudyModeHint(); if (state.studyModeHintTimer) { clearTimeout(state.studyModeHintTimer); state.studyModeHintTimer = null; } syncStudyModeUi(""); } function studyModeLabel(mode) { const m = mode !== undefined ? String(mode) : getStudyMode(); if (m === "realtime") return "1:1 \u5b66\u4e60\u6a21\u5f0f"; if (m === "efficiency") return "\u6548\u7387\u6a21\u5f0f"; return "\u672a\u9009\u62e9"; } function escHtml(s) { return String(s ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } function projectKey(p) { return `${p.projectId}_${p.orderNo || ""}`; } function _fch(v) { if (v == null || v === "") return "-\u5b66\u65f6"; const n = Number(v); if (Number.isFinite(n)) return `${n}\u5b66\u65f6`; return `${String(v).trim() || "-"}\u5b66\u65f6`; } function getCookie(name) { try { const m = document.cookie.match( new RegExp("(?:^|; )" + name.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&") + "=([^;]*)") ); return m ? decodeURIComponent(m[1]) : ""; } catch (_) { return ""; } } function _rat() { for (const k of ["admin-token", "adminToken", "token"]) { const v = getCookie(k); if (v) return v; } try { const ls = localStorage.getItem("admin-token") || localStorage.getItem("adminToken") || ""; if (ls) return String(ls); } catch (_) {} return ""; } function readUserId() { for (const k of ["admin-userId", "userId"]) { const v = getCookie(k); if (v) return String(v); } try { const ls = localStorage.getItem("admin-userId") || localStorage.getItem("userId") || ""; if (ls) return String(ls); } catch (_) {} return ""; } function _glu() { return String(state.userId || readUserId() || "").trim(); } function isLoggedIn() { state.token = _rat(); if (!state.token) return false; if (!state.userId) state.userId = readUserId(); state.userLabel = state.userId ? `\u7528\u6237 ${state.userId}` : "\u5df2\u767b\u5f55"; return true; } function _eauth() { if (!_rat()) throw new Error("\u672a\u68c0\u6d4b\u5230\u767b\u5f55 token\uff08admin-token\uff09\uff0c\u8bf7\u5148\u767b\u5f55\u5e73\u53f0"); isLoggedIn(); } function log(msg) { const text = String(msg || ""); const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false }); state.logLines.push(`${ts} ${text}`); if (state.logLines.length > 120) state.logLines.shift(); appendLogLine(`${ts} ${text}`); console.log(`[${PRODUCT_NAME}] ${text}`); } function appendLogLine(text) { const box = document.getElementById("mpsc-run-log"); if (!box) return; const div = document.createElement("div"); div.className = "mpsc-log-row"; div.textContent = text; box.appendChild(div); box.scrollTop = box.scrollHeight; } function clearRunLog() { state.logLines = []; const box = document.getElementById("mpsc-run-log"); if (box) box.innerHTML = ""; } function setPanelHint(msg) { state.panelHint = String(msg || "").trim(); _upn(); } function clearPanelHint() { if (!state.panelHint) return; state.panelHint = ""; _upn(); } function _gr(opts) { return new Promise((resolve, reject) => { const g = typeof GM_xmlhttpRequest === "function" ? GM_xmlhttpRequest : typeof GM !== "undefined" && GM.xmlHttpRequest ? GM.xmlHttpRequest.bind(GM) : null; if (!g) { reject(new Error("\u5f53\u524d\u73af\u5883\u4e0d\u652f\u6301 GM_xmlhttpRequest")); return; } g({ method: opts.method || "GET", url: opts.url, headers: opts.headers || {}, data: opts.data, responseType: opts.responseType || "text", timeout: opts.timeout || 60000, onload: (res) => resolve(res), onerror: () => reject(new Error("\u7f51\u7edc\u9519\u8bef")), ontimeout: () => reject(new Error("\u8bf7\u6c42\u8d85\u65f6")), }); }); } function _grs(url, method, headers, data) { return new Promise((resolve, reject) => { _gr({ method, url, headers, data }) .then((res) => resolve({ status: res.status, text: res.responseText || "" })) .catch(reject); }); } async function _aj(base, method, path, body) { _eauth(); const url = path.startsWith("http") ? path : `${base}${path}`; const headers = { adminToken: state.token, Accept: "application/json, text/plain, */*", Origin: SITE_ORIGIN, Referer: SITE_ORIGIN + "/", }; let data; if (method !== "GET" && body !== undefined) { headers["Content-Type"] = "application/json;charset=UTF-8"; data = typeof body === "string" ? body : JSON.stringify(body); } const res = await _gr({ method, url, headers, data }); let json = null; const text = String(res.responseText || ""); try { json = text ? JSON.parse(text) : null; } catch (_) { json = null; } return { status: res.status, text, data: json }; } async function _pcp(path, body) { return _aj(PC_API, "POST", path, body); } async function _h5p(path, body) { return _aj(H5_API, "POST", path, body); } function _aok(r, label) { if (r.status >= 400) throw new Error(`${label} HTTP ${r.status}`); const code = r.data?.code; if (code !== undefined && Number(code) !== 200) { throw new Error(`${label} \u5931\u8d25\uff1a${r.data?.msg || r.data?.message || code}`); } return r.data; } function projectStatusText(st) { const map = { 1: "\u5168\u90e8\u5b8c\u6210", 3: "\u5b66\u4e60\u4e2d", 4: "\u5b8c\u6210\u57f9\u8bad", 5: "\u672a\u5b66\u4e60/\u672a\u901a\u8fc7", 6: "\u91cd\u65b0\u5b66\u4e60", 7: "\u91cd\u65b0\u5b66\u4e60\u901a\u8fc7", 8: "\u91cd\u65b0\u5b66\u4e60\u672a\u901a\u8fc7", 9: "\u8bf7\u5199\u8bba\u6587", 10: "\u8865\u4fee\u901a\u8fc7", }; return map[Number(st)] || `\u72b6\u6001${st}`; } function courseStatusText(st) { const map = { 1: "\u89c6\u9891+\u6d4b\u9a8c\u5b8c\u6210", 3: "\u89c6\u9891\u5b66\u4e60\u4e2d", 4: "\u89c6\u9891\u5b8c\u6210·\u672a\u6d4b\u9a8c", 5: "\u672a\u5b66\u4e60", }; return map[Number(st)] || `\u72b6\u6001${st}`; } const VIDEO_DONE_OVERSHOOT_MIN = 5; function isCourseVideoDone(c) { const st = Number(c.learnStatus); if (st === 1) return true; if (st === 5) return false; const need = Number(c.videoMinMinutes) || 0; const got = Number(c.studyTime) || 0; if (need > 0) return got >= need + VIDEO_DONE_OVERSHOOT_MIN; return st === 4; } function canTakeExam(c) { return Number(c.learnStatus) === 4; } function isCourseFullyDone(c) { return Number(c.learnStatus) === 1; } function courseProgressPct(c) { const need = Number(c.videoMinMinutes) || 0; const got = Number(c.studyTime) || 0; if (need <= 0) { const st = Number(c.learnStatus); if (st === 1 || st === 4) return 100; return 0; } return Math.min(100, Math.round((got / need) * 100)); } async function _fup() { const pageSize = 10; const all = []; let curPage = 1; let total = Infinity; while (all.length < total && curPage <= 50) { const r = await _pcp("/order/queryUserProject", { curPage, maxPage: pageSize, yearNum: null, }); const data = _aok(r, "queryUserProject"); const page = data?.data || {}; total = Number(page.total) || 0; const rows = Array.isArray(page.data) ? page.data : []; all.push(...rows); if (!rows.length || rows.length < pageSize) break; curPage += 1; } return all; } async function _fpd(projectId, orderNo) { const r = await _pcp("/personal/queryProjectDetails", { projectId: Number(projectId) || projectId, orderNo: orderNo || "", }); return _aok(r, "queryProjectDetails")?.data || {}; } async function _fvm(projectId, courseId) { const r = await _aj( PC_API, "GET", `/project/getVideoUrl?projectId=${encodeURIComponent(projectId)}&courseId=${encodeURIComponent(courseId)}`, undefined ); return _aok(r, "getVideoUrl")?.data || {}; } async function editLearnState(courseId, projectId) { const body = { courseId: Number(courseId) || courseId, projectId: Number(projectId) || projectId, userId: state.userId || readUserId(), }; try { const r = await _h5p("/study/editLearnState", body); if (r.data && Number(r.data.code) !== 200) { log(`\u8fdb\u5165\u5b66\u4e60\u72b6\u6001\uff1a${r.data.msg || r.data.message || r.data.code}`); } return r.data; } catch (e) { log(`\u8fdb\u5165\u5b66\u4e60\u72b6\u6001\u5931\u8d25\uff1a${e.message || e}`); return null; } } async function _lpc(project) { const detail = await _fpd(project.projectId, project.orderNo); const required = Array.isArray(detail.requiredCourses) ? detail.requiredCourses : []; const elective = Array.isArray(detail.electiveCourses) ? detail.electiveCourses : []; const courses = [...required, ...elective].map((c, idx) => ({ ...c, courseId: c.courseId, projectId: c.projectId || project.projectId, courseName: c.courseName || `\u8bfe\u7a0b${idx + 1}`, studyTime: Number(c.studyTime) || 0, videoMinMinutes: Number(c.videoMinMinutes) || 0, learnStatus: c.learnStatus, giveType: c.giveType, isRequired: idx < required.length, orderNo: project.orderNo, })); return { detail, courses }; } function chaptersFromCourses(project, courses) { return courses.map((c) => ({ projectName: project.projectName, projectId: project.projectId, name: c.courseName, courseId: c.courseId, pct: courseProgressPct(c), status: courseStatusText(c.learnStatus), learnStatus: c.learnStatus, studyTime: c.studyTime, videoMinMinutes: c.videoMinMinutes, done: isCourseVideoDone(c), canExam: canTakeExam(c), })); } function getCloudApiBase() { return DEFAULT_CLOUD_API_BASE; } function _flet(expSec) { const ex = Number(expSec || 0); if (!Number.isFinite(ex) || ex <= 0) return "—"; const d = new Date(ex * 1000); if (Number.isNaN(d.getTime())) return "—"; return d.toLocaleString("zh-CN", { hour12: false }); } function _rles(data) { const raw = data?.exp ?? data?.expire_at ?? data?.expires_at ?? 0; const n = Number(raw || 0); if (Number.isFinite(n) && n > 1e12) return Math.floor(n / 1000); return Number.isFinite(n) ? Math.floor(n) : 0; } function _rpes(data) { const raw = data?.pro_expires_at ?? data?.proExpiresAt ?? 0; const n = Number(raw || 0); if (Number.isFinite(n) && n > 1e12) return Math.floor(n / 1000); return Number.isFinite(n) ? Math.floor(n) : 0; } function _rlc() { try { const raw = localStorage.getItem(CLOUD_LEASE_CACHE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!parsed?.lease) return null; return parsed; } catch (_) { return null; } } function _wlc(lease, exp, extra) { if (!lease || !exp) { localStorage.removeItem(CLOUD_LEASE_CACHE_KEY); return; } localStorage.setItem(CLOUD_LEASE_CACHE_KEY, JSON.stringify(Object.assign({ lease, exp }, extra || {}))); } function _wls(partial) { try { localStorage.setItem( CLOUD_LAST_STATE_KEY, JSON.stringify(Object.assign({}, partial || {}, { ts: Date.now() })) ); } catch (_) {} } async function _rq(path, method, payload) { const base = getCloudApiBase(); const url = `${base}${path.startsWith("/") ? path : `/${path}`}`; const headers = { Accept: "application/json", "Content-Type": "application/json" }; const luid = _glu(); if (luid) headers["x-learning-user-id"] = luid; if (state.cloudToken) headers.Authorization = `Bearer ${state.cloudToken}`; let reqPayload = payload; if (reqPayload && typeof reqPayload === "object") { reqPayload = Object.assign({}, reqPayload); if (path !== "/api/mpsc/lease" && state.cloudLease && reqPayload.lease == null) { reqPayload.lease = state.cloudLease; } } const body = reqPayload == null ? null : JSON.stringify(reqPayload); const res = await _grs(url, method || "POST", headers, body); const text = String(res.text || "").trim(); let data = {}; if (text) { try { data = JSON.parse(text); } catch (_) { data = { message: text }; } } if (res.status < 200 || res.status >= 300) { throw new Error(String(data.detail || data.message || data.code || `http_${res.status}`)); } return data; } async function _el(forceRefresh) { const now = Math.floor(Date.now() / 1000); if (!forceRefresh && state.cloudLease && state.cloudLeaseExp - now > 60) return true; if (!forceRefresh) { const cached = _rlc(); if (cached && cached.exp - now > 60) { state.cloudLease = cached.lease; state.cloudLeaseExp = cached.exp; state.cloudProExpireAt = Number(cached.proExpireAt || 0); if (cached.tier) state.cloudTier = cached.tier; if (cached.freeVideoLimit > 0) state.freeVideoLimit = cached.freeVideoLimit; if (cached.freeUsedVideos >= 0) state.freeUsedVideos = cached.freeUsedVideos; _ucu(); return true; } } const luid = _glu(); if (!luid) throw new Error("\u672a\u767b\u5f55\uff0c\u65e0\u6cd5\u83b7\u53d6\u4e91\u7aef\u6388\u6743"); let data; try { data = await _rq("/api/mpsc/lease", "POST", { learning_user_id: luid }); state.cloudRevoked = false; } catch (e) { const em = String(e?.message || e); if (/(revoked|invalid token|expired)/i.test(em)) { state.cloudRevoked = true; state.cloudTier = "revoked"; state.cloudLease = ""; state.cloudLeaseExp = 0; _wlc("", 0); _ucu(); } throw e; } const lease = String(data.lease || ""); const exp = _rles(data); const tier = String(data.tier || "").trim() || (state.cloudToken ? "pro" : "free"); const proExpireAt = _rpes(data); state.cloudLease = lease; state.cloudLeaseExp = exp; state.cloudProExpireAt = proExpireAt > 0 ? proExpireAt : 0; state.cloudTier = tier; state.freeVideoLimit = Number( data.free_video_limit ?? data.free_chapter_limit ?? state.freeVideoLimit ?? 3 ); state.freeUsedVideos = Number(data.free_used_videos ?? data.free_used_chapters ?? 0); _wlc(lease, exp, { tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, proExpireAt: state.cloudProExpireAt, }); _wls({ tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, }); _ucu(); return !!lease; } async function _cvt() { if (!state.cloudToken) return true; const url = `${getCloudApiBase()}/api/license/verify`; const headers = { Accept: "application/json", Authorization: `Bearer ${state.cloudToken}` }; const luid = _glu(); if (luid) headers["x-learning-user-id"] = luid; const res = await _grs(url, "GET", headers, null); let data = {}; if (res.text) { try { data = JSON.parse(res.text); } catch (_) {} } if (res.status < 200 || res.status >= 300 || !data.ok) { throw new Error(String(data.message || data.detail || "Token \u6821\u9a8c\u5931\u8d25")); } return true; } function _scq(data) { if (!data || typeof data !== "object") return; if (data.tier) state.cloudTier = String(data.tier); const lim = data.free_video_limit ?? data.free_chapter_limit ?? data.limit; const used = data.free_used_videos ?? data.free_used_chapters ?? data.used; if (lim != null) state.freeVideoLimit = Number(lim); if (used != null) state.freeUsedVideos = Number(used); _wls({ tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, }); _ucu(); } async function _mes(kind, context, cfg) { await _el(false); const data = await _rq("/api/mpsc/study/engine/start", "POST", { kind: String(kind || ""), context: context || {}, config: cfg || {}, }); _scq(data); return data; } async function _mep(sessionId, event, lastResult) { await _el(false); const data = await _rq("/api/mpsc/study/engine/step", "POST", { session_id: String(sessionId || ""), event: String(event || "tick"), last_result: lastResult == null ? null : lastResult, }); _scq(data); return data; } async function _xmc(cmd) { const c = cmd || {}; const t = String(c.type || ""); if (t === "platform_h5_post") { const path = String(c.path || ""); const payload = c.payload || {}; const r = await _h5p(path, payload); const body = r.data || {}; return { event: "submit_result", lastResult: { data: body, http_status: r.status } }; } if (t === "wait") { await sleep(Math.max(500, Number(c.ms || 1000))); return { event: "tick", lastResult: null }; } if (t === "done") { return { terminal: true, ok: !!c.success, skipped: !!c.skipped, msg: c.message || "\u5b8c\u6210", }; } if (t === "failed") { return { terminal: true, ok: false, msg: c.message || "failed" }; } return { event: "tick", lastResult: null }; } async function _rml(startRes, options) { const quiet = !!(options && options.quiet); let sessionId = String(startRes.session_id || ""); let cmd = startRes.command; if (!quiet && startRes.log) log(startRes.log); const maxSteps = Number(options?.maxSteps) || 200; for (let i = 0; i < maxSteps && cmd; i++) { const out = await _xmc(cmd); if (out.terminal !== undefined) return out; let lastResult = out.lastResult; if (out.event === "submit_result" && lastResult && options?.afterPlatform) { try { const patch = await options.afterPlatform(lastResult, cmd); if (patch && typeof patch === "object") { lastResult = Object.assign({}, lastResult, patch); } } catch (_) {} } const next = await _mep(sessionId, out.event, lastResult); sessionId = String(next.session_id || sessionId); if (!quiet && next.log) log(next.log); cmd = next.command; if (state.stopFlag) return { terminal: true, ok: false, msg: "\u5df2\u505c\u6b62" }; } return { terminal: true, ok: false, msg: "\u4e91\u7aef\u5f15\u64ce\u6b65\u6570\u8d85\u9650" }; } async function _rcr(course) { try { const { courses } = await _lpc({ projectId: course.projectId, orderNo: course.orderNo || "", }); const fresh = courses.find((x) => String(x.courseId) === String(course.courseId)); if (fresh) Object.assign(course, fresh); } catch (_) {} return course; } function _ecfg() { return { study_mode: getStudyMode() || "efficiency", free_video_limit: state.freeVideoLimit }; } async function _soc(course, projectName) { const title = course.courseName || `\u8bfe\u7a0b ${course.courseId}`; state.currentProject = projectName; state.currentCourse = title; state.lastUserAction = `${projectName} / ${title}`; _upn(); if (isCourseVideoDone(course)) { log(`\u8df3\u8fc7\u5df2\u5b66\u5b8c\uff1a${title}`); state.queueVideoDone += 1; _upn(); return; } let meta = {}; try { meta = await _fvm(course.projectId, course.courseId); } catch (e) { log(`getVideoUrl\uff1a${e.message || e}`); } await editLearnState(course.courseId, course.projectId); const needMin = Number(meta.videoMinMinutes) || Number(course.videoMinMinutes) || 0; const historyTime = Math.floor(Number(meta.progressBar) || 0); const durationSec = Math.max(60, Math.round(needMin * 60) || 1800); const ctx = { course_id: course.courseId, project_id: course.projectId, order_no: course.orderNo || "", course_name: title, need_min: needMin, study_time: Number(course.studyTime) || 0, history_time: historyTime, duration_sec: durationSec, }; let startRes; try { startRes = await _mes("video", ctx, _ecfg()); } catch (e) { log(`\u4e91\u7aef\u89c6\u9891\u5f15\u64ce\u542f\u52a8\u5931\u8d25\uff1a${e.message || e}`); return; } const afterPlatform = async (lastResult, cmd) => { if (String(cmd?.path || "") !== "/recordViewingTime") return null; await sleep(1200); await _rcr(course); return { study_time_after: Number(course.studyTime) || 0 }; }; const result = await _rml(startRes, { afterPlatform, maxSteps: 220 }); await _rcr(course); if (result.ok || result.skipped) { state.queueVideoDone += 1; log(`\u89c6\u9891\u5b66\u4e60\u5b8c\u6210\uff1a${title}`); } else if (result.msg === "free_quota_exhausted" || /free_quota/.test(String(result.msg))) { log(`\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro`); _opm(); } else if (result.msg) { log(`\u89c6\u9891\u5b66\u4e60\u7ed3\u675f\uff1a${result.msg}`); } _upn(); } async function _tec(course, projectName) { const title = course.courseName || `\u8bfe\u7a0b ${course.courseId}`; state.lastUserAction = `\u6d4b\u9a8c · ${projectName} / ${title}`; state.currentCourse = title; _upn(); if (String(state.cloudTier || "").toLowerCase() !== "pro") { log("\u6d4b\u9a8c\u4ec5 Pro \u53ef\u7528\uff0c\u514d\u8d39\u4f53\u9a8c\u4e0d\u5305\u542b\u6d4b\u9a8c"); _opm(); return { ok: false }; } const ctx = { course_id: course.courseId, project_id: course.projectId, order_no: course.orderNo || "", course_name: title, }; let startRes; try { startRes = await _mes("exam", ctx, _ecfg()); } catch (e) { log(`\u4e91\u7aef\u6d4b\u9a8c\u5f15\u64ce\u542f\u52a8\u5931\u8d25\uff1a${e.message || e}`); return { ok: false }; } const result = await _rml(startRes, { maxSteps: 80 }); await _rcr(course); if (result.ok) { state.queueExamDone += 1; log(`\u6d4b\u9a8c\u5b8c\u6210\uff1a${title}`); } else if (result.msg === "exam_requires_pro") { log("\u6d4b\u9a8c\u9700 Pro\uff0c\u8bf7\u5347\u7ea7\u540e\u91cd\u8bd5"); _opm(); } else if (result.msg) { log(`\u6d4b\u9a8c\u7ed3\u675f\uff1a${result.msg}`); } _upn(); return result; } async function _mec(course, projectName) { if (!getAutoExam() || state.stopFlag) return; await _rcr(course); if (isCourseFullyDone(course)) return; if (!canTakeExam(course) && !isCourseVideoDone(course)) return; await _tec(course, projectName); } async function _sop(project) { state.currentProject = project.projectName; state.lastUserAction = project.projectName; _upn(); log(`\u8fdb\u5165\u9879\u76ee\uff1a${project.projectName}`); const { courses, detail } = await _lpc(project); const allCourses = courses.map((c) => { c.orderNo = project.orderNo; c.projectId = c.projectId || project.projectId; return c; }); const skipVideo = Number(detail.needVideo) === 1; const videoCourses = skipVideo ? [] : allCourses.filter((c) => Number(c.giveType) === 0 || Number(c.videoMinMinutes) > 0); const examFirst = getAutoExam() ? allCourses.filter((c) => { if (isCourseFullyDone(c)) return false; if (canTakeExam(c)) return true; return skipVideo && !isCourseFullyDone(c); }) : []; const videoTargets = videoCourses.filter((c) => !isCourseVideoDone(c)); state.chapters = chaptersFromCourses( project, examFirst.length || videoTargets.length ? [...examFirst, ...videoTargets] : allCourses ); state.queueExamTotal = Math.max( state.queueExamTotal, state.queueExamDone + examFirst.length + (getAutoExam() ? videoTargets.length : 0) ); state.queueVideoTotal = Math.max(state.queueVideoTotal, state.queueVideoDone + videoTargets.length); _upn(); if (!examFirst.length && !videoTargets.length) { log(`\u9879\u76ee\u65e0\u5f85\u529e\uff1a${project.projectName}`); return; } if (examFirst.length) { log(`\u4f18\u5148\u6d4b\u9a8c ${examFirst.length} \u95e8`); for (const c of examFirst) { if (state.stopFlag) break; await _tec(c, project.projectName); await sleep(600); } } if (videoTargets.length && !state.stopFlag) { log(`\u5f85\u5b66 ${videoTargets.length} \u95e8\u89c6\u9891 · ${studyModeLabel()}${getAutoExam() ? " · \u5b66\u5b8c\u5373\u6d4b\u9a8c" : ""}`); for (const c of videoTargets) { if (state.stopFlag) break; await _soc(c, project.projectName); if (!state.stopFlag && getAutoExam()) await _mec(c, project.projectName); await sleep(800); } } } async function _rfp() { _eauth(); state.panelRefreshing = true; _upn(); log("\u6b63\u5728\u62c9\u53d6\u9879\u76ee\u5217\u8868…"); const list = await _fup(); if (list[0]?.userId) { state.userId = String(list[0].userId); state.userLabel = `\u7528\u6237 ${state.userId}`; } state.projects = list.map((p) => { const learnStatus = Number(p.learnStatus); const incomplete = ![1, 4, 7, 10].includes(learnStatus); return { projectId: p.projectId, orderNo: p.orderNo || "", projectName: p.projectName || `\u9879\u76ee ${p.projectId}`, creditHour: p.creditHour, trainTime: p.trainTime, learnStatus, statusText: projectStatusText(learnStatus), incomplete, raw: p, }; }); gmSet(PROJECTS_CACHE_KEY, JSON.stringify(state.projects)); const valid = new Set(state.projects.map((p) => projectKey(p))); for (const id of Array.from(state.selected)) { if (!valid.has(id)) state.selected.delete(id); } saveQueue(state.selected); state.panelRefreshing = false; log(`\u5df2\u52a0\u8f7d ${state.projects.length} \u4e2a\u9879\u76ee`); _upn(); if (state.selected.size) _psc({ force: true }).catch((e) => log(String(e.message || e))); } async function _psc(opts) { const force = !!(opts && opts.force); if (state.enabled) return; const selected = state.projects.filter((p) => state.selected.has(projectKey(p))); if (!selected.length) { state.chapters = []; state.chapterPreviewKey = ""; _rcp(); return; } const key = selected.map((p) => projectKey(p)).sort().join("|"); if (!force && key === state.chapterPreviewKey && state.chapters.length) { _rcp(); return; } state.chapterLoading = true; _rcp(); _upn(); const all = []; try { for (const p of selected) { const { courses, detail } = await _lpc(p); const list = Number(detail.needVideo) === 1 ? [] : courses.filter((c) => Number(c.giveType) === 0 || Number(c.videoMinMinutes) > 0); all.push(...chaptersFromCourses(p, list.length ? list : courses)); } state.chapters = all; state.chapterPreviewKey = key; } finally { state.chapterLoading = false; _upn(); } } async function _ecr() { if (!isLoggedIn()) { log("\u8bf7\u5148\u767b\u5f55\u7ee7\u7eed\u6559\u80b2\u5e73\u53f0"); return false; } try { if (state.cloudToken) await _cvt(); await _el(false); } catch (err) { log("\u4e91\u7aef\u6388\u6743\u5931\u8d25\uff1a" + (err.message || err)); return false; } const tier = String(state.cloudTier || "").toLowerCase(); if (tier === "pro") return true; if (tier === "free") { if (Number(state.freeUsedVideos) >= Number(state.freeVideoLimit)) { log(`\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff08${state.freeUsedVideos}/${state.freeVideoLimit}\uff09\uff0c\u8bf7\u5347\u7ea7 Pro`); switchPanelTab("settings"); _opm(); return false; } return true; } log("\u4e91\u7aef\u6388\u6743\u72b6\u6001\u5f02\u5e38\uff0c\u8bf7\u68c0\u67e5 Token \u6216\u7f51\u7edc"); return false; } async function _rqe() { if (state.enabled) return; const picked = readStudyModeSelect(); if (!picked) { setPanelHint("\u8bf7\u5148\u9009\u62e9\u5b66\u4e60\u6a21\u5f0f\u518d\u70b9\u5f00\u59cb"); log("\u8bf7\u5148\u9009\u62e9\u5b66\u4e60\u6a21\u5f0f"); _upn(); return; } setStudyMode(picked); const selected = state.projects.filter((p) => state.selected.has(projectKey(p))); if (!selected.length) { setPanelHint("\u8bf7\u5148\u52fe\u9009\u81f3\u5c11\u4e00\u4e2a\u9879\u76ee"); log("\u8bf7\u5148\u52fe\u9009\u9879\u76ee"); _upn(); return; } if (!(await _ecr())) { _upn(); return; } clearPanelHint(); state.enabled = true; state.stopFlag = false; state.queueVideoDone = 0; state.queueVideoTotal = selected.length; state.queueExamDone = 0; state.queueExamTotal = 0; _upn(); log(`\u5f00\u59cb\u5b66\u4e60\uff1a${selected.length} \u4e2a\u9879\u76ee · ${studyModeLabel()}`); try { for (const p of selected) { if (state.stopFlag) break; try { await _sop(p); } catch (e) { log(`${p.projectName}\uff1a${e.message || e}`); } await sleep(600); } log(state.stopFlag ? "\u5df2\u505c\u6b62" : "\u961f\u5217\u5df2\u5b8c\u6210"); } finally { state.enabled = false; state.lastUserAction = state.stopFlag ? "\u5df2\u505c\u6b62" : "\u7a7a\u95f2"; clearStudyModeSelection(); _upn(); } } function _stq() { state.stopFlag = true; log("\u6b63\u5728\u505c\u6b62…"); _upn(); } function _vpr() { return getOnlyIncomplete() ? state.projects.filter((p) => p.incomplete) : state.projects; } function _rpl() { const box = document.getElementById("mpsc-course-list"); if (!box) return; const list = _vpr(); if (!list.length) { box.innerHTML = `
${state.projects.length ? "\u7b5b\u9009\u4e0b\u65e0\u9879\u76ee" : "\u70b9\u5237\u65b0\u52a0\u8f7d\u9879\u76ee"}
`; return; } box.innerHTML = list .map((p) => { const id = projectKey(p); const checked = state.selected.has(id) ? "checked" : ""; return ``; }) .join(""); box.querySelectorAll("input[type=checkbox]").forEach((el) => { el.addEventListener("change", () => { const id = el.getAttribute("data-id"); if (el.checked) state.selected.add(id); else state.selected.delete(id); saveQueue(state.selected); state.chapterPreviewKey = ""; _upn(); _psc({ force: true }).catch((e) => log(String(e.message || e))); }); }); } function _rcp() { const box = document.getElementById("mpsc-chapter-preview"); if (!box) return; if (state.chapterLoading) { box.innerHTML = `
\u6b63\u5728\u52a0\u8f7d\u8bfe\u7a0b…
`; return; } if (!state.selected.size) { box.innerHTML = `
\u8bf7\u5148\u5728\u8bfe\u7a0b\u5217\u8868\u52fe\u9009\u9879\u76ee
`; return; } if (!state.chapters.length) { box.innerHTML = `
\u6682\u65e0\u8bfe\u7a0b\u9884\u89c8
`; return; } let last = ""; const html = []; for (const ch of state.chapters) { if (ch.projectName !== last) { last = ch.projectName; html.push(`
${escHtml(ch.projectName)}
`); } html.push( `
${escHtml(ch.pct)}%${escHtml( ch.name )}
${escHtml(ch.status)} · ${escHtml(ch.studyTime)}/${escHtml( ch.videoMinMinutes )} \u5206\u949f
` ); } box.innerHTML = html.join(""); } function formatCloudTierText(tier) { if (state.cloudRevoked) return "\u5df2\u7981\u7528"; const t = String(tier || "").toLowerCase(); if (t === "pro") return "Pro \u4f1a\u5458"; if (t === "free") return "\u514d\u8d39\u4f53\u9a8c"; return tier ? String(tier) : "\u672a\u6821\u9a8c"; } function _ucu() { const tierEl = document.querySelector("#mpsc-cloud-tier"); const freeEl = document.querySelector("#mpsc-cloud-free"); const freeLabelEl = document.querySelector("#mpsc-cloud-free-label"); const tokenInput = document.querySelector("#mpsc-cloud-token"); if (tierEl) tierEl.textContent = formatCloudTierText(state.cloudTier); if (state.cloudRevoked) { if (freeLabelEl) freeLabelEl.textContent = "\u6388\u6743\u72b6\u6001"; if (freeEl) freeEl.textContent = "Token \u5df2\u7981\u7528"; } else if (String(state.cloudTier || "").toLowerCase() === "pro") { if (freeLabelEl) freeLabelEl.textContent = "Pro \u5230\u671f"; if (freeEl) freeEl.textContent = _flet(state.cloudProExpireAt || state.cloudLeaseExp); } else { if (freeLabelEl) freeLabelEl.textContent = "\u514d\u8d39\u4f53\u9a8c\u89c6\u9891\u7ae0\u8282"; if (freeEl) freeEl.textContent = `${state.freeUsedVideos}/${state.freeVideoLimit}`; } if (tokenInput && tokenInput !== document.activeElement) tokenInput.value = state.cloudToken || ""; } function updateQueueSummary() { const doneEl = document.getElementById("mpsc-queue-done"); const totalEl = document.getElementById("mpsc-queue-total"); const pctEl = document.getElementById("mpsc-queue-percent"); const bar = document.getElementById("mpsc-queue-progress"); const examEl = document.getElementById("mpsc-queue-exam"); const total = Math.max(state.queueVideoTotal, state.selected.size || 0, 1); const done = state.queueVideoDone; const pct = total ? Math.round((done / total) * 100) : 0; if (doneEl) doneEl.textContent = String(done); if (totalEl) totalEl.textContent = String(Math.max(state.queueVideoTotal, state.selected.size || 0)); if (pctEl) pctEl.textContent = `${pct}%`; if (bar) bar.style.width = `${pct}%`; if (examEl) { const et = Math.max(0, Number(state.queueExamTotal) || 0); const ed = Math.max(0, Number(state.queueExamDone) || 0); examEl.textContent = getAutoExam() ? (et > 0 ? `${ed}/${et}` : String(ed)) : "\u5173"; } const qtext = document.getElementById("mpsc-queue-text"); if (qtext) { qtext.textContent = state.selected.size ? `\u5df2\u9009 ${state.selected.size} \u4e2a\u9879\u76ee` : "\u672a\u9009\u62e9"; } } function updateCurrentMetaPanel() { const u = document.getElementById("mpsc-current-user"); if (u) u.textContent = state.userLabel || "\u672a\u68c0\u6d4b"; const cp = document.getElementById("mpsc-current-course"); if (cp) cp.textContent = state.currentProject || "\u65e0"; const cc = document.getElementById("mpsc-current-chapter"); if (cc) cc.textContent = state.currentCourse || "\u65e0"; const tk = document.getElementById("mpsc-current-task"); if (tk) tk.textContent = state.lastUserAction || "\u52fe\u9009\u9879\u76ee\u540e\u70b9\u5f00\u59cb"; const ann = document.getElementById("mpsc-ann-text"); if (ann) ann.textContent = state.remotePanelNotice || PANEL_NOTICE_FALLBACK; } function syncStartStopButtons() { const running = !!state.enabled; const needMode = !hasStudyModeSelected(); const startBtn = document.getElementById("mpsc-start"); const stopBtn = document.getElementById("mpsc-stop"); if (startBtn) { const off = running || needMode; startBtn.disabled = off; startBtn.classList.toggle("mpsc-btn-off", off); } if (stopBtn) { stopBtn.disabled = !running; stopBtn.classList.toggle("mpsc-btn-off", !running); } } function switchPanelTab(name) { document.querySelectorAll(".mpsc-tab-btn").forEach((el) => { el.classList.toggle("active", el.dataset.tab === name); }); document.querySelectorAll(".mpsc-pane").forEach((el) => { el.classList.toggle("active", el.dataset.pane === name); }); if (name === "chapter") _psc().catch((e) => log(String(e.message || e))); } function hideStudyModeHint() { const box = document.getElementById("mpsc-study-mode-hint"); if (box) box.style.display = "none"; } function syncStudyModeHintContent() { const m = getStudyMode(); if (!m) return; const box = document.getElementById("mpsc-study-mode-hint"); if (!box) return; const title = box.querySelector(".mpsc-study-mode-hint-title"); const body = box.querySelector(".mpsc-study-mode-hint-body"); const rt = m === "realtime"; box.classList.toggle("mpsc-study-mode-hint-efficiency", !rt); box.classList.toggle("mpsc-study-mode-hint-realtime", rt); if (title) title.textContent = rt ? "1:1 \u5b66\u4e60\u6a21\u5f0f" : "\u6548\u7387\u6a21\u5f0f"; if (body) { body.textContent = rt ? "\u6309\u5e73\u53f0\u8282\u594f\u7ea6\u6bcf 300 \u79d2\u66f4\u65b0\u4e00\u6b21\u8fdb\u5ea6\uff0c\u5faa\u73af\u81f3\u5b66\u5b8c\u3002" : "\u5feb\u901f\u5b8c\u6210\u89c6\u9891\u5b66\u65f6\u66f4\u65b0\uff0c\u5b66\u5b8c\u4e00\u95e8\u540e\u81ea\u52a8\u8fdb\u5165\u4e0b\u4e00\u95e8\u3002"; } } function showStudyModeHintTemporary() { const box = document.getElementById("mpsc-study-mode-hint"); if (!box) return; syncStudyModeHintContent(); box.style.display = ""; if (state.studyModeHintTimer) clearTimeout(state.studyModeHintTimer); state.studyModeHintTimer = setTimeout(hideStudyModeHint, 5000); } function syncStudyModeUi(mode) { const sel = document.getElementById("mpsc-study-mode"); const m = mode !== undefined && mode !== null ? String(mode) : getStudyMode(); if (sel) sel.value = m || ""; if (m) syncStudyModeHintContent(); } function _gpu() { return String(state.proBuyUrl || PRO_BUY_URL).trim() || PRO_BUY_URL; } function _opb() { const url = _gpu(); try { GM_openInTab(url, { active: true, insert: true, setParent: true }); } catch (_) { window.open(url, "_blank", "noopener,noreferrer"); } } function _opm() { const vm = document.getElementById("mpsc-pro-modal"); if (vm) vm.style.display = "flex"; } function _clpm() { const vm = document.getElementById("mpsc-pro-modal"); if (vm) vm.style.display = "none"; } function _cpm() { const old = document.getElementById("mpsc-pro-modal"); if (old) old.remove(); const modal = document.createElement("div"); modal.id = "mpsc-pro-modal"; modal.innerHTML = `
\u5f00\u901a Pro
Pro \u53ef\u4e0d\u9650\u89c6\u9891\u7ae0\u8282\u5b66\u4e60\uff0c\u5e76\u652f\u6301\u81ea\u52a8\u63d0\u4ea4\u6d4b\u9a8c

Pro \u6743\u76ca

\u5f00\u901a\u65b9\u5f0f

1) \u52a0\u5165 QQ \u7fa4 ${QQ_GROUP_NUMBER} \u8054\u7cfb\u7ba1\u7406\u5458\u83b7\u53d6 Token

\u5728\u8bbe\u7f6e\u9875\u7c98\u8d34 Token \u540e\u70b9\u300c\u4fdd\u5b58\u5e76\u6821\u9a8c\u300d
`; document.body.appendChild(modal); modal.addEventListener("click", (e) => { if (e.target === modal) _clpm(); }); modal.querySelector("#mpsc-pro-close")?.addEventListener("click", _clpm); modal.querySelector("#mpsc-pro-buy-link")?.addEventListener("click", _opb); } function readPanelPos() { try { return JSON.parse(gmGet(PANEL_POS_KEY, "null")); } catch (_) { return null; } } function writePanelPos(left, top) { gmSet(PANEL_POS_KEY, JSON.stringify({ left, top })); } function readPanelCollapsed() { return gmGet(PANEL_COLLAPSED_KEY, "0") === "1"; } function writePanelCollapsed(v) { gmSet(PANEL_COLLAPSED_KEY, v ? "1" : "0"); } function applyPanelCollapsed(panel, collapsed) { const btnMin = panel.querySelector("#mpsc-btn-min"); const btnMax = panel.querySelector("#mpsc-btn-max"); panel.classList.toggle("mpsc-panel-min", !!collapsed); panel.classList.toggle("mpsc-panel-max", !collapsed); if (btnMin) btnMin.style.display = collapsed ? "none" : ""; if (btnMax) btnMax.style.display = collapsed ? "" : "none"; } function enablePanelDrag(panel) { const header = panel.querySelector("#mpsc-panel-header"); if (!header) return; let dragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; header.addEventListener("mousedown", (e) => { if (e.target?.closest("#mpsc-panel-controls")) return; dragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; panel.style.right = "auto"; e.preventDefault(); }); document.addEventListener("mousemove", (e) => { if (!dragging) return; panel.style.left = `${Math.max(0, startLeft + (e.clientX - startX))}px`; panel.style.top = `${Math.max(0, startTop + (e.clientY - startY))}px`; }); document.addEventListener("mouseup", () => { if (!dragging) return; dragging = false; const rect = panel.getBoundingClientRect(); writePanelPos(rect.left, rect.top); }); } function _ips() { const id = "mpsc-panel-style-v3"; document.getElementById("mpsc-panel-style-v1")?.remove(); document.getElementById("mpsc-panel-style-v2")?.remove(); if (document.getElementById(id) || !document.head) return; const st = document.createElement("style"); st.id = id; st.textContent = ` #mpsc-auto-panel,#mpsc-auto-panel *:not(#oa888-global-bubble-host):not(#oa888-global-bubble-host *):not(#oa888-global-bubble-host-mpsc):not(#oa888-global-bubble-host-mpsc *), #mpsc-auto-panel *::before,#mpsc-auto-panel *::after{box-sizing:border-box !important;} #mpsc-auto-panel{position:fixed !important;right:20px !important;top:80px !important;z-index:999999 !important;width:412px !important;max-width:calc(100vw - 24px) !important;display:flex !important;flex-direction:column !important;background:#f4f7fb !important;border:1px solid #dbe4f0 !important;border-radius:16px !important;box-shadow:0 16px 40px rgba(15,23,42,.17) !important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif !important;font-size:12px !important;line-height:1.4 !important;color:#0f172a !important;overflow:hidden !important;float:none !important;clear:both !important;margin:0 !important;padding:0 !important;text-align:left !important;} #mpsc-auto-panel.mpsc-panel-hidden{display:none !important;} #mpsc-auto-panel.mpsc-panel-max{max-height:min(92vh,780px) !important;} #mpsc-auto-panel.mpsc-panel-min{max-height:none !important;box-shadow:0 10px 28px rgba(15,23,42,.14) !important;} #mpsc-auto-panel.mpsc-panel-min #mpsc-panel-header{border-bottom:none !important;} #mpsc-auto-panel.mpsc-panel-min #mpsc-panel-body, #mpsc-auto-panel.mpsc-panel-min #mpsc-panel-footer, #mpsc-auto-panel.mpsc-panel-min .mpsc-footer-extra{display:none !important;} #mpsc-auto-panel > *:not(#oa888-global-bubble-host):not(#oa888-global-bubble-host-mpsc), #mpsc-auto-panel #mpsc-panel-body, #mpsc-auto-panel #mpsc-panel-body *:not(#oa888-global-bubble-host):not(#oa888-global-bubble-host *):not(#oa888-global-bubble-host-mpsc):not(#oa888-global-bubble-host-mpsc *), #mpsc-auto-panel .mpsc-footer-extra, #mpsc-auto-panel .mpsc-footer-extra *, #mpsc-auto-panel #mpsc-panel-footer, #mpsc-auto-panel #mpsc-panel-header, #mpsc-auto-panel #mpsc-panel-header *{ float:none !important;max-width:100% !important; } #mpsc-auto-panel #mpsc-panel-body *:not(input):not(select):not(button):not(img):not(span):not(em):not(b):not(strong):not(#oa888-global-bubble-host):not(#oa888-global-bubble-host *):not(#oa888-global-bubble-host-mpsc):not(#oa888-global-bubble-host-mpsc *), #mpsc-auto-panel #mpsc-panel-header *:not(img):not(span):not(button), #mpsc-auto-panel .mpsc-footer-extra *:not(span):not(button), #mpsc-auto-panel #mpsc-panel-footer{ position:static !important;transform:none !important; } #mpsc-auto-panel #oa888-global-bubble-host, #mpsc-auto-panel #oa888-global-bubble-host-mpsc, #mpsc-auto-panel #oa888-global-bubble-host *, #mpsc-auto-panel #oa888-global-bubble-host-mpsc *{ float:none !important;max-width:none !important; } #mpsc-auto-panel #oa888-global-bubble-host, #mpsc-auto-panel #oa888-global-bubble-host-mpsc{ position:absolute !important;inset:0 !important;z-index:2147483000 !important;pointer-events:none !important; display:flex !important;align-items:flex-start !important;justify-content:center !important;padding:14px 12px 0 !important; margin:0 !important;width:auto !important;height:auto !important;box-sizing:border-box !important; } #mpsc-auto-panel label{display:inline-flex !important;width:auto !important;height:auto !important;margin:0 !important;padding:0 !important;font-weight:inherit !important;cursor:pointer !important;position:static !important;float:none !important;} #mpsc-auto-panel button{display:inline-flex !important;align-items:center !important;justify-content:center !important;width:auto !important;height:auto !important;margin:0 !important;appearance:none !important;-webkit-appearance:none !important;position:static !important;float:none !important;} #mpsc-auto-panel input[type=checkbox]{position:static !important;width:14px !important;height:14px !important;margin:0 !important;flex:0 0 auto !important;accent-color:#2563eb;float:none !important;} #mpsc-auto-panel select,#mpsc-auto-panel input[type=text]{display:block !important;width:100% !important;float:none !important;position:static !important;} #mpsc-auto-panel #mpsc-panel-header{flex:0 0 auto !important;padding:8px 11px !important;background:linear-gradient(180deg,#f8ecd5,#f4e8cf) !important;border-bottom:1px solid #e5dbc6 !important;display:flex !important;justify-content:space-between !important;align-items:center !important;cursor:move !important;user-select:none !important;gap:8px !important;position:relative !important;} #mpsc-auto-panel #mpsc-panel-body{flex:1 1 auto !important;min-height:0 !important;overflow-x:hidden !important;overflow-y:auto !important;padding:8px !important;display:block !important;position:relative !important;} #mpsc-auto-panel .mpsc-footer-extra{flex:0 0 auto !important;padding:6px 9px !important;background:#f8fafc !important;border-top:1px solid #e2e8f0 !important;position:relative !important;} #mpsc-auto-panel #mpsc-panel-footer{flex:0 0 auto !important;padding:7px 11px !important;background:#eef2f7 !important;border-top:1px solid #dbe4f0 !important;font-size:12px !important;} #mpsc-auto-panel #mpsc-panel-brand{display:flex !important;align-items:center !important;gap:9px !important;min-width:0 !important;flex:1 1 auto !important;} #mpsc-auto-panel #mpsc-panel-logo{width:30px !important;height:30px !important;border-radius:9px !important;object-fit:cover !important;border:1px solid rgba(148,163,184,.45) !important;background:#fff !important;flex:0 0 auto !important;} #mpsc-auto-panel #mpsc-panel-title{font-size:13px !important;font-weight:900 !important;color:#9a3412 !important;line-height:1.26 !important;display:flex !important;align-items:flex-start !important;gap:5px !important;flex-wrap:wrap !important;} #mpsc-auto-panel .mpsc-panel-title-text{flex:1 1 12em !important;min-width:0 !important;} #mpsc-auto-panel #mpsc-panel-sub{display:flex !important;flex-wrap:wrap !important;align-items:center !important;gap:3px 5px !important;margin-top:3px !important;} #mpsc-auto-panel .mpsc-sub-chip{font-size:11px !important;color:#7c2d12 !important;background:rgba(255,255,255,.62) !important;padding:2px 7px !important;border-radius:999px !important;border:1px solid rgba(180,83,9,.18) !important;font-weight:700 !important;} #mpsc-auto-panel .mpsc-sub-chip-em{color:#0f766e !important;background:rgba(236,253,245,.9) !important;border-color:rgba(15,118,110,.28) !important;} #mpsc-auto-panel .mpsc-sub-dot{color:#d6d3d1 !important;font-size:10px !important;} #mpsc-auto-panel .mpsc-panel-version{font-size:11px !important;font-weight:900 !important;color:#64748b !important;padding:2px 7px !important;border-radius:999px !important;background:#f1f5f9 !important;border:1px solid #e2e8f0 !important;} #mpsc-auto-panel #mpsc-panel-controls{display:flex !important;align-items:center !important;gap:5px !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-panel-ctl{border:none !important;background:#fff !important;color:#64748b !important;width:28px !important;height:28px !important;border-radius:999px !important;cursor:pointer !important;box-shadow:0 1px 2px rgba(15,23,42,.1) !important;padding:0 !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-ctl-min{display:block !important;width:10px !important;height:2px !important;background:#64748b !important;border-radius:1px !important;margin:0 auto !important;} #mpsc-auto-panel .mpsc-ctl-plus{font-size:16px !important;line-height:1 !important;font-weight:700 !important;color:#64748b !important;} #mpsc-auto-panel .mpsc-card{display:block !important;background:#fff !important;border:1px solid #d9e2ee !important;border-radius:12px !important;padding:7px 9px !important;margin:0 0 7px !important;overflow:visible !important;clear:both !important;position:relative !important;height:auto !important;min-height:0 !important;} #mpsc-auto-panel .mpsc-card-status{padding:6px 8px !important;margin-bottom:6px !important;} #mpsc-auto-panel .mpsc-status-row{display:flex !important;justify-content:space-between !important;align-items:center !important;gap:7px !important;line-height:1.28 !important;} #mpsc-auto-panel .mpsc-status-label{font-size:11px !important;color:#64748b !important;font-weight:700 !important;} #mpsc-auto-panel .mpsc-status-main{margin-top:3px !important;} #mpsc-auto-panel .mpsc-status-metrics{display:flex !important;align-items:center !important;gap:5px !important;font-size:12px !important;color:#475569 !important;min-width:0 !important;flex:1 1 auto !important;} #mpsc-auto-panel .mpsc-status-metrics em{font-style:normal !important;font-weight:900 !important;color:#0f172a !important;} #mpsc-auto-panel .mpsc-metric-div{color:#cbd5e1 !important;} #mpsc-auto-panel .mpsc-progress-pct,#mpsc-auto-panel #mpsc-queue-percent{font-size:14px !important;font-weight:900 !important;color:#0369a1 !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-progress-bar{height:5px !important;border-radius:999px !important;background:#e2e8f0 !important;overflow:hidden !important;margin-top:4px !important;position:relative !important;} #mpsc-auto-panel .mpsc-progress-bar>span{display:block !important;height:100% !important;width:0;background:linear-gradient(90deg,#22d3ee,#2563eb) !important;position:static !important;} #mpsc-auto-panel #mpsc-auto-status{padding:2px 7px !important;border-radius:999px !important;font-weight:900 !important;font-size:11px !important;background:#fff !important;border:1px solid #cbd5e1 !important;} #mpsc-auto-panel .mpsc-tabbar{display:flex !important;gap:6px !important;margin-bottom:7px !important;} #mpsc-auto-panel .mpsc-tab-btn{flex:1 1 0 !important;border:1px solid #cbd5e1 !important;background:#f8fafc !important;color:#475569 !important;padding:4px 2px !important;border-radius:9px !important;cursor:pointer !important;font-weight:700 !important;font-size:11px !important;min-width:0 !important;} #mpsc-auto-panel .mpsc-tab-btn.active{background:linear-gradient(135deg,#1d4ed8,#0ea5e9) !important;color:#fff !important;border-color:transparent !important;} #mpsc-auto-panel .mpsc-pane{display:none !important;height:auto !important;min-height:0 !important;overflow:visible !important;} #mpsc-auto-panel .mpsc-pane.active{display:block !important;} #mpsc-auto-panel .mpsc-list-head{display:flex !important;align-items:center !important;justify-content:space-between !important;margin-bottom:6px !important;gap:8px !important;} #mpsc-auto-panel .mpsc-list-title{font-size:12px !important;color:#64748b !important;font-weight:700 !important;} #mpsc-auto-panel .mpsc-list-tag{font-size:11px !important;color:#92400e !important;background:#ffedd5 !important;border:1px solid #fdba74 !important;border-radius:999px !important;padding:2px 7px !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-list-toolbar{display:flex !important;flex-wrap:wrap !important;gap:6px !important;margin-bottom:6px !important;align-items:center !important;} #mpsc-auto-panel .mpsc-list-toolbar .mpsc-tool-btn{ flex:0 0 auto !important;border:1px solid #cbd5e1 !important;background:#fff !important;color:#0f172a !important; padding:3px 9px !important;border-radius:8px !important;font-size:11px !important;font-weight:700 !important;cursor:pointer !important; min-width:0 !important;height:auto !important;line-height:1.35 !important; } #mpsc-auto-panel .mpsc-list-toolbar label{font-size:11px !important;display:inline-flex !important;align-items:center !important;gap:4px !important;color:#475569 !important;flex:0 0 auto !important;white-space:nowrap !important;} #mpsc-auto-panel #mpsc-course-list,#mpsc-auto-panel #mpsc-chapter-preview,#mpsc-auto-panel #mpsc-run-log{ display:block !important;min-height:120px !important;max-height:200px !important;height:auto !important; overflow-x:hidden !important;overflow-y:auto !important; background:#f8fafc !important;border:1px solid #dbe4f0 !important;border-radius:11px !important;padding:5px !important; clear:both !important;position:relative !important; } #mpsc-auto-panel #mpsc-course-list::after{content:"" !important;display:table !important;clear:both !important;} #mpsc-auto-panel .mpsc-empty-state{padding:12px 7px !important;text-align:center !important;color:#94a3b8 !important;font-size:12px !important;font-weight:900 !important;} #mpsc-auto-panel label.mpsc-item,#mpsc-auto-panel .mpsc-item{ display:flex !important;flex-direction:row !important;align-items:flex-start !important;gap:8px !important; padding:7px 8px !important;border:1px solid #dbe4f0 !important;border-radius:9px !important;margin:0 0 5px !important; background:#fff !important;cursor:pointer !important;width:100% !important;float:none !important;clear:both !important; position:relative !important;left:auto !important;top:auto !important;right:auto !important;bottom:auto !important; height:auto !important; } #mpsc-auto-panel .mpsc-item.done{opacity:.78 !important;background:#f8fafc !important;} #mpsc-auto-panel .mpsc-item>div{flex:1 1 auto !important;min-width:0 !important;} #mpsc-auto-panel .mpsc-item-name{font-weight:700 !important;font-size:12px !important;color:#0f172a !important;word-break:break-word !important;} #mpsc-auto-panel .mpsc-item-meta{font-size:11px !important;color:#64748b !important;margin-top:2px !important;line-height:1.35 !important;} #mpsc-auto-panel .mpsc-ch-group{margin:6px 0 3px !important;padding:4px 7px !important;background:#eaf1ff !important;border-radius:7px !important;font-size:11px !important;font-weight:700 !important;color:#1d4ed8 !important;} #mpsc-auto-panel .mpsc-ch-row{display:flex !important;gap:6px !important;padding:5px 0 !important;border-bottom:1px dashed #dbe4f0 !important;font-size:11px !important;align-items:flex-start !important;} #mpsc-auto-panel .mpsc-ch-mark{min-width:38px !important;font-weight:800 !important;color:#0369a1 !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-ch-sub{color:#64748b !important;font-size:10px !important;} #mpsc-auto-panel .mpsc-log-clear-btn{border:1px solid #cbd5e1 !important;background:#fff !important;color:#64748b !important;padding:1px 7px !important;border-radius:999px !important;font-size:10px !important;font-weight:700 !important;cursor:pointer !important;} #mpsc-auto-panel .mpsc-log-row{padding:4px 6px !important;border-bottom:1px dashed #d4deea !important;font-size:12px !important;line-height:1.42 !important;word-break:break-all !important;} #mpsc-auto-panel .mpsc-meta-row{display:flex !important;justify-content:space-between !important;gap:7px !important;font-size:12px !important;margin:0 0 5px !important;align-items:flex-start !important;} #mpsc-auto-panel .mpsc-meta-label{color:#64748b !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-meta-value{font-weight:700 !important;text-align:right !important;flex:1 1 auto !important;min-width:0 !important;word-break:break-all !important;} #mpsc-auto-panel .mpsc-card-current{padding:5px 8px !important;margin-bottom:6px !important;} #mpsc-auto-panel .mpsc-card-current .mpsc-meta-row{font-size:11px !important;margin-bottom:2px !important;} #mpsc-auto-panel .mpsc-card-current .mpsc-meta-label{font-size:10px !important;white-space:nowrap !important;} #mpsc-auto-panel .mpsc-card-current .mpsc-meta-value{font-size:11px !important;font-weight:600 !important;} #mpsc-auto-panel #mpsc-cloud-tier{border:none !important;border-radius:0 !important;padding:0 !important;background:transparent !important;box-shadow:none !important;font-weight:800 !important;} #mpsc-auto-panel .mpsc-card-actions{padding:6px 8px !important;} #mpsc-auto-panel .mpsc-study-mode-block{margin-bottom:5px !important;display:block !important;} #mpsc-auto-panel .mpsc-plan-select,#mpsc-auto-panel #mpsc-study-mode{ width:100% !important;border:1px solid #cbd5e1 !important;border-radius:8px !important;padding:6px 9px !important; font-size:12px !important;background:#fff !important;color:#0f172a !important;height:auto !important;line-height:1.35 !important; } #mpsc-auto-panel .mpsc-study-mode-hint{margin-top:5px !important;padding:7px 9px !important;border-radius:9px !important;border:1px solid #e2e8f0 !important;font-size:11px !important;line-height:1.42 !important;display:block !important;} #mpsc-auto-panel .mpsc-study-mode-hint-title{display:block !important;font-weight:800 !important;margin-bottom:3px !important;} #mpsc-auto-panel .mpsc-study-mode-hint-body{margin:0 !important;color:#475569 !important;} #mpsc-auto-panel .mpsc-study-mode-hint-efficiency{background:linear-gradient(135deg,#eff6ff,#f0f9ff) !important;border-color:#bfdbfe !important;color:#1e3a8a !important;} #mpsc-auto-panel .mpsc-study-mode-hint-realtime{background:linear-gradient(135deg,#fffbeb,#fef3c7) !important;border-color:#f59e0b !important;color:#78350f !important;} #mpsc-auto-panel .mpsc-start-hint{font-size:11px !important;color:#b45309 !important;background:#fffbeb !important;border:1px solid #fcd34d !important;border-radius:7px !important;padding:5px 7px !important;margin-bottom:5px !important;text-align:center !important;font-weight:600 !important;} #mpsc-auto-panel .mpsc-btn-row{display:flex !important;gap:7px !important;margin-bottom:6px !important;flex-wrap:nowrap !important;} #mpsc-auto-panel .mpsc-btn-row-nowrap{flex-wrap:nowrap !important;} #mpsc-auto-panel .mpsc-btn{flex:1 1 0 !important;border:none !important;color:#fff !important;padding:7px 9px !important;border-radius:10px !important;cursor:pointer !important;font-weight:800 !important;font-size:12px !important;min-width:0 !important;} #mpsc-auto-panel .mpsc-btn-action{min-width:0 !important;flex:1 1 0 !important;} #mpsc-auto-panel .mpsc-btn-label{white-space:nowrap !important;} #mpsc-auto-panel .mpsc-btn-start{background:#16a34a !important;} #mpsc-auto-panel .mpsc-btn-stop{background:#ef4444 !important;} #mpsc-auto-panel .mpsc-btn-start.mpsc-btn-off,#mpsc-auto-panel .mpsc-btn-start:disabled{background:#cbd5e1 !important;color:#64748b !important;cursor:not-allowed !important;pointer-events:none !important;} #mpsc-auto-panel .mpsc-btn-stop.mpsc-btn-off,#mpsc-auto-panel .mpsc-btn-stop:disabled{background:#e2e8f0 !important;color:#94a3b8 !important;cursor:not-allowed !important;pointer-events:none !important;} #mpsc-auto-panel .mpsc-btn-ghost{border:1px solid #cbd5e1 !important;background:#fff !important;color:#0f172a !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-btn-pro{flex:0 0 auto !important;background:linear-gradient(135deg,#f59e0b,#ef4444) !important;color:#fff !important;border:none !important;padding:4px 8px !important;font-size:12px !important;border-radius:10px !important;cursor:pointer !important;font-weight:800 !important;} #mpsc-auto-panel .mpsc-ann{position:relative !important;font-size:12px !important;color:#334155 !important;line-height:1.42 !important;background:linear-gradient(180deg,#fffdf5,#fff7e6) !important;border:1px solid #fcd34d !important;border-radius:11px !important;padding:8px 9px 8px 11px !important;} #mpsc-auto-panel .mpsc-ann-text{display:block !important;word-break:break-word !important;} #mpsc-auto-panel .mpsc-ann::before{content:"" !important;position:absolute !important;left:0 !important;top:0 !important;bottom:0 !important;width:3px !important;background:linear-gradient(180deg,#f59e0b,#ef4444) !important;border-top-left-radius:11px !important;border-bottom-left-radius:11px !important;} #mpsc-auto-panel .mpsc-qq-row{display:flex !important;align-items:center !important;justify-content:space-between !important;gap:9px !important;margin-top:5px !important;} #mpsc-auto-panel .mpsc-qq-text{font-size:12px !important;color:#475569 !important;} #mpsc-auto-panel .mpsc-qq-title{font-size:13px !important;font-weight:800 !important;color:#0f172a !important;} #mpsc-auto-panel .mpsc-qq-btn{border:none !important;background:linear-gradient(135deg,#1d4ed8,#0ea5e9) !important;color:#fff !important;padding:5px 12px !important;border-radius:999px !important;font-size:12px !important;font-weight:800 !important;cursor:pointer !important;flex:0 0 auto !important;} #mpsc-auto-panel .mpsc-hidden{display:none !important;} #mpsc-auto-panel #mpsc-cloud-token{flex:1 1 auto !important;min-width:100px !important;border:1px solid #cbd5e1 !important;border-radius:8px !important;padding:4px 7px !important;font-size:12px !important;width:auto !important;display:inline-block !important;} #mpsc-pro-modal{position:fixed !important;inset:0 !important;background:rgba(15,23,42,.45) !important;z-index:1000001 !important;display:none;align-items:center !important;justify-content:center !important;padding:20px !important;} #mpsc-pro-modal .mpsc-pro-card{width:min(560px,92vw) !important;max-height:88vh !important;overflow:auto !important;background:#fff !important;border-radius:18px !important;border:1px solid #dbe4f0 !important;box-shadow:0 18px 46px rgba(15,23,42,.25) !important;padding:16px !important;position:relative !important;} #mpsc-pro-modal .mpsc-pro-title{font-size:24px !important;font-weight:900 !important;color:#0f172a !important;} #mpsc-pro-modal .mpsc-pro-sub{font-size:13px !important;color:#64748b !important;margin-top:4px !important;} #mpsc-pro-modal .mpsc-pro-sec{margin-top:12px !important;border:1px solid #dbe4f0 !important;border-radius:12px !important;padding:12px !important;background:#f8fafc !important;} #mpsc-pro-modal .mpsc-pro-buy-btn,#mpsc-pro-modal .mpsc-pro-close{border:none !important;background:linear-gradient(135deg,#1d4ed8,#0ea5e9) !important;color:#fff !important;padding:10px 16px !important;border-radius:12px !important;font-size:14px !important;font-weight:800 !important;cursor:pointer !important;} #mpsc-pro-modal .mpsc-pro-actions{margin-top:14px !important;display:flex !important;justify-content:flex-end !important;} `; document.head.appendChild(st); } function _cpn() { const old = document.getElementById("mpsc-auto-panel"); if (old) old.remove(); try { document.getElementById("oa888-global-bubble-host")?.remove(); document.getElementById("oa888-global-bubble-host-mpsc")?.remove(); } catch (_) {} _ips(); _cpm(); const panel = document.createElement("div"); panel.id = "mpsc-auto-panel"; panel.className = "mpsc-panel-max"; panel.innerHTML = `
${PRODUCT_NAME}v${SCRIPT_VERSION}
\u66f4\u65b0\u5b66\u65f6·\u81ea\u52a8\u6d4b\u9a8c·\u4e00\u952e\u5b8c\u6210
\u8fd0\u884c\u72b6\u6001\u5df2\u505c\u6b62
0/0 \u89c6\u9891\u5df2\u5b66\u4e60 · \u6d4b\u9a8c —
0%
\u9879\u76ee\u9009\u62e9\u8bfe\u7a0b\u5217\u8868
\u767b\u5f55\u540e\u70b9\u5237\u65b0\u52a0\u8f7d
\u7ae0\u8282\u9884\u89c8\u5b9e\u65f6\u8fdb\u5ea6
\u8bf7\u52fe\u9009\u9879\u76ee
\u8fd0\u884c\u65e5\u5fd7
\u6388\u6743\u4e0e\u9009\u9879\u8bbe\u7f6e
\u7528\u6237\u7c7b\u578b\u672a\u6821\u9a8c
\u514d\u8d39\u4f53\u9a8c\u89c6\u9891\u7ae0\u82820/3
Token
\u5f53\u524d\u9879\u76ee\u65e0
\u5f53\u524d\u8bfe\u7a0b\u65e0
\u5f53\u524d\u4efb\u52a1\u52fe\u9009\u9879\u76ee\u540e\u70b9\u5f00\u59cb
\u767b\u5f55\u7528\u6237—
`; const saved = readPanelPos(); if (saved && saved.left != null && saved.top != null) { panel.style.right = "auto"; panel.style.left = `${saved.left}px`; panel.style.top = `${saved.top}px`; } document.body.appendChild(panel); applyPanelCollapsed(panel, readPanelCollapsed()); enablePanelDrag(panel); try { window.dispatchEvent(new CustomEvent("mpsc-panel-ready")); } catch (_) { try { window.__MPSC_OA888_NOTICE_RERUN__?.(true); } catch (__) {} } panel.querySelector("#mpsc-btn-min")?.addEventListener("click", (e) => { e.stopPropagation(); writePanelCollapsed(true); applyPanelCollapsed(panel, true); }); panel.querySelector("#mpsc-btn-max")?.addEventListener("click", (e) => { e.stopPropagation(); writePanelCollapsed(false); applyPanelCollapsed(panel, false); }); panel.querySelectorAll(".mpsc-tab-btn").forEach((btn) => { btn.addEventListener("click", () => switchPanelTab(btn.dataset.tab)); }); document.getElementById("mpsc-clear-log")?.addEventListener("click", clearRunLog); document.getElementById("mpsc-refresh")?.addEventListener("click", () => { _rfp().catch((e) => log(String(e.message || e))); }); document.getElementById("mpsc-select-all")?.addEventListener("click", () => { _vpr().forEach((p) => state.selected.add(projectKey(p))); saveQueue(state.selected); state.chapterPreviewKey = ""; _upn(); _psc({ force: true }).catch((e) => log(String(e.message || e))); }); document.getElementById("mpsc-select-none")?.addEventListener("click", () => { state.selected.clear(); saveQueue(state.selected); state.chapters = []; _upn(); }); document.getElementById("mpsc-start")?.addEventListener("click", () => { _rqe().catch((e) => log(String(e.message || e))); }); document.getElementById("mpsc-stop")?.addEventListener("click", _stq); document.getElementById("mpsc-join-qq")?.addEventListener("click", () => { try { GM_openInTab(QQ_GROUP_LINK, { active: true, insert: true, setParent: true }); } catch (_) { window.open(QQ_GROUP_LINK, "_blank"); } }); document.getElementById("mpsc-open-pro")?.addEventListener("click", _opm); document.getElementById("mpsc-cloud-save")?.addEventListener("click", async () => { const input = document.getElementById("mpsc-cloud-token"); state.cloudToken = String(input?.value || "").trim(); state.cloudRevoked = false; localStorage.setItem(CLOUD_TOKEN_KEY, state.cloudToken); _wlc("", 0); state.cloudLease = ""; try { if (state.cloudToken) await _cvt(); await _el(true); log("\u4e91\u7aef\u6388\u6743\u5df2\u66f4\u65b0"); } catch (err) { log("\u4e91\u7aef\u6821\u9a8c\u5931\u8d25\uff1a" + (err.message || err)); } _ucu(); }); const studySel = document.getElementById("mpsc-study-mode"); if (studySel) { studySel.addEventListener("change", () => { if (state.enabled) { log("\u8bf7\u5148\u505c\u6b62\u540e\u518d\u5207\u6362\u5b66\u4e60\u6a21\u5f0f"); studySel.value = getStudyMode() || ""; return; } const picked = String(studySel.value || ""); if (!picked) { clearStudyModeSelection(); _upn(); return; } setStudyMode(picked); showStudyModeHintTemporary(); log(`\u5df2\u9009\u62e9 ${studyModeLabel(picked)}`); _upn(); }); } const bindOnly = (id) => { document.getElementById(id)?.addEventListener("change", (e) => { setOnlyIncomplete(!!e.target.checked); const other = id === "mpsc-only-incomplete-bar" ? "mpsc-only-incomplete" : "mpsc-only-incomplete-bar"; const o = document.getElementById(other); if (o) o.checked = !!e.target.checked; _upn(); }); }; bindOnly("mpsc-only-incomplete-bar"); bindOnly("mpsc-only-incomplete"); document.getElementById("mpsc-auto-exam")?.addEventListener("change", (e) => { setAutoExam(!!e.target.checked); _upn(); }); const onlyBar = document.getElementById("mpsc-only-incomplete-bar"); const onlySet = document.getElementById("mpsc-only-incomplete"); if (onlyBar) onlyBar.checked = getOnlyIncomplete(); if (onlySet) onlySet.checked = getOnlyIncomplete(); const autoExam = document.getElementById("mpsc-auto-exam"); if (autoExam) autoExam.checked = getAutoExam(); _ucu(); _upn(); } function _upn() { syncStartStopButtons(); const statusEl = document.getElementById("mpsc-auto-status"); if (statusEl) { if (state.panelRefreshing) statusEl.textContent = "\u5237\u65b0\u4e2d…"; else if (state.enabled) statusEl.textContent = "\u8fd0\u884c\u4e2d"; else statusEl.textContent = "\u5df2\u505c\u6b62"; } _ucu(); updateCurrentMetaPanel(); updateQueueSummary(); _rpl(); _rcp(); const hintEl = document.getElementById("mpsc-start-hint"); if (hintEl && !state.enabled) { if (state.panelHint) { hintEl.textContent = state.panelHint; hintEl.style.display = "block"; } else if (!hasStudyModeSelected()) { hintEl.textContent = "\u8bf7\u5148\u9009\u62e9\u5b66\u4e60\u6a21\u5f0f\u518d\u70b9\u5f00\u59cb"; hintEl.style.display = "block"; } else { hintEl.style.display = "none"; } } const studyModeSel = document.getElementById("mpsc-study-mode"); if (studyModeSel) studyModeSel.disabled = !!state.enabled; } async function fetchPanelNotice() { try { const origin = new URL(getCloudApiBase()).origin; const url = `${origin}${state.panelNoticePath}`; const res = await _grs(url, "GET", {}, null); if (res.status >= 200 && res.status < 300) { state.remotePanelNotice = String(res.text || "").trim() || PANEL_NOTICE_FALLBACK; updateCurrentMetaPanel(); } } catch (_) {} } async function fetchClientConfig() { try { const res = await _grs( `${getCloudApiBase()}/api/mpsc/client-config`, "GET", { Accept: "application/json" }, null ); if (res.status >= 200 && res.status < 300 && res.text) { const j = JSON.parse(res.text); if (j?.panelNoticePath) state.panelNoticePath = String(j.panelNoticePath); if (j?.freeVideoLimit != null) state.freeVideoLimit = Number(j.freeVideoLimit) || 3; if (j?.proBuyUrl) state.proBuyUrl = String(j.proBuyUrl).trim() || PRO_BUY_URL; if (j?.cloudApiBase) { } } } catch (_) {} await fetchPanelNotice(); } function onRouteMaybeChanged() { if (!document.getElementById("mpsc-auto-panel")) _cpn(); else _upn(); } function boot() { try { const cached = gmGet(PROJECTS_CACHE_KEY, "[]"); const arr = typeof cached === "string" ? JSON.parse(cached) : cached; if (Array.isArray(arr) && arr.length) state.projects = arr; } catch (_) {} _cpn(); log(`${PRODUCT_NAME} v${SCRIPT_VERSION} \u5df2\u52a0\u8f7d`); if (isLoggedIn()) { _rfp().catch((e) => log(String(e.message || e))); _el(false).catch(() => {}); } else { log("\u8bf7\u5148\u767b\u5f55\u5e73\u53f0"); } void fetchClientConfig(); window.addEventListener("hashchange", onRouteMaybeChanged); window.addEventListener("popstate", onRouteMaybeChanged); setInterval(() => { if (!document.getElementById("mpsc-auto-panel")) _cpn(); }, 2000); setInterval(() => { if (isLoggedIn()) _el(false).catch(() => {}); }, 10 * 60 * 1000); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();