// ==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
- \u89e3\u9664\u514d\u8d39 3 \u4e2a\u89c6\u9891\u7ae0\u8282\u9650\u5236
- \u5b66\u5b8c\u540e\u53ef\u81ea\u52a8\u63d0\u4ea4\u6d4b\u9a8c
- Token \u6fc0\u6d3b\u540e 30 \u5929\u6709\u6548\uff08\u7ed1\u5b9a\u5f53\u524d\u767b\u5f55\u8d26\u53f7\uff09
\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 = `
\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
\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();
}
})();