// ==UserScript==
// @name 放射卫生培训考试平台助手(1分钟完成)
// @namespace http://27.128.236.246:8070/FSPX/
// @version 1.0
// @description 放射卫生培训考试助手:自动看课与考试;免费体验1个课程,Pro不限课程与考试。请登录平台主页使用。
// @author 柠檬真酸
// @match http://27.128.236.246:8070/FSPX*
// @match http://27.128.236.246:8070/FSPX/*
// @match *://*/FSPX/*
// @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png
// @connect 27.128.236.246
// @connect oa36.ahzsksw.cn
// @connect huaweicloudobs.ahjxjy.cn
// @connect card.wlxy.live
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @run-at document-idle
// @antifeature payment 免费体验1个课程,升级Pro不限课程与考试
// @antifeature membership 需云端授权
// @license All Rights Reserved
// ==/UserScript==
(function () {
"use strict";
const SCRIPT_VERSION = "1.0";
const ROOT = "/FSPX";
const API_BASE = location.origin;
const PANEL_LOGO_URL =
"https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png";
const PANEL_NOTICE_FALLBACK =
"\u514d\u8d39\u4f53\u9a8c1\u4e2a\u8bfe\u7a0b\uff1bPro\u6fc0\u6d3b\u540e10\u5929\u6709\u6548\u53ef\u5b8c\u6210\u6240\u6709\u8bfe\u7a0b\u548c\u8003\u8bd5\u3002\u672a\u9002\u914d\u5e73\u53f0\u548c\u53cd\u9988\u8054\u7cfbQQ125431514\uff01";
const DEFAULT_PANEL_NOTICE_PATH = "/api/fspx/panel-notice";
const PINNED_CLOUD_HOST = "oa36.ahzsksw.cn";
const DEFAULT_CLOUD_API_BASE = `https://${PINNED_CLOUD_HOST}`;
const CLOUD_API_PREFIX = "/api/fspx";
const DEFAULT_FREE_COURSE_LIMIT = 1;
const PRO_BUY_URL = "https://card.wlxy.live/details/D9736B60";
const PRO_DAYS = 10;
const AUTO_EXAM = true;
const _LDQ = false;
const CLOUD_TOKEN_KEY = "fspx_cloud_token_v1";
const CLOUD_LEASE_CACHE_KEY = "fspx_cloud_lease_cache_v1";
const CLOUD_FREE_USED_KEY = "fspx_cloud_free_used_v1";
const QUEUE_KEY = "fspx_video_queue_v1";
const PANEL_POS_KEY = "fspx_panel_pos_v1";
const PANEL_COLLAPSED_KEY = "fspx_panel_collapsed_v1";
const SITE_ID = "fspx";
const state = {
enabled: false,
stopFlag: false,
panelRefreshing: false,
panelVideos: [],
queueDone: 0,
queueTotal: 0,
examGrade: null,
examLabel: "",
currentVideoTitle: "",
currentTask: "\u52fe\u9009\u89c6\u9891\u540e\u70b9\u300c\u5f00\u59cb\u300d",
panelHint: "",
userLabel: "",
learningUserId: "",
loginUserId: "",
domain: "",
logs: [],
cloudApiBase: "",
cloudToken: "",
cloudLease: "",
cloudTier: "unknown",
cloudExpireAt: 0,
cloudRevoked: false,
freeChapterLimit: DEFAULT_FREE_COURSE_LIMIT,
freeUsed: 0,
remotePanelNotice: "",
panelNoticePath: DEFAULT_PANEL_NOTICE_PATH,
proBuyUrl: PRO_BUY_URL,
cloudReady: false,
cloudLastError: "",
};
function isFspxPage() {
const href = String(location.href || "");
const path = String(location.pathname || "");
return /\/FSPX(\/|$|\?)/i.test(href) || /^\/FSPX/i.test(path);
}
function isTopWindow() {
try {
return window.top === window;
} catch (_) {
return true;
}
}
function escHtml(s) {
return String(s || "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function readJson(key, fallback) {
try {
const raw = localStorage.getItem(key);
if (!raw) return fallback;
return JSON.parse(raw);
} catch (_) {
return fallback;
}
}
function writeJson(key, val) {
localStorage.setItem(key, JSON.stringify(val));
}
function absUrl(path) {
if (!path) return API_BASE;
if (/^https?:\/\//i.test(path)) return path;
if (path.startsWith("/")) return API_BASE + path;
return API_BASE + ROOT + "/" + String(path).replace(/^\.\//, "");
}
function _slg(msg) {
let line = String(msg || "").trim();
if (!line) return "";
if (
/\u4e91\u7aef|\u7f16\u6392|\u4ee3\u53d1|lease|token|engine|insertExam|createPaper|recordAction|selectList|GM_|oa\d+|ahzsksw|\u6388\u6743\u57df|HMAC|session_id/i.test(
line
)
) {
return "";
}
line = line
.replace(/https?:\/\/\S+/gi, "")
.replace(/\/api\/\S+/gi, "")
.replace(/\s{2,}/g, " ")
.trim();
return line;
}
function log(msg) {
const line = _slg(msg);
if (!line) return;
const row = `[${new Date().toLocaleTimeString()}] ${line}`;
state.logs.push(row);
if (state.logs.length > 100) state.logs.shift();
const box = document.getElementById("fspx-run-log");
if (box) {
box.innerHTML = state.logs
.slice(-60)
.map((x) => `
${escHtml(x)}
`)
.join("");
box.scrollTop = box.scrollHeight;
}
}
function _ferr(err) {
const m = String(err?.message || err || "");
if (/\u505c\u6b62|\u5df2\u505c\u6b62/.test(m)) return "\u5df2\u505c\u6b62";
if (/\u767b\u5f55|\u672a\u767b\u5f55|cookie|\u4f1a\u8bdd/i.test(m)) return "\u8bf7\u5148\u767b\u5f55\u5e73\u53f0\u8d26\u53f7";
if (/free_quota|\u989d\u5ea6|\u514d\u8d39.*\u7528\u5b8c/i.test(m)) return "\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro";
if (/\u7981\u7528|revoked/i.test(m)) return "\u670d\u52a1\u6682\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
if (/lease|token|\u4e91\u7aef|404|\u7f51\u7edc|\u8d85\u65f6|quota|\u6821\u9a8c|\u6388\u6743/i.test(m)) return "\u670d\u52a1\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
if (/\u8003\u8bd5|\u4ea4\u5377|\u7ec4\u5377|\u5f00\u8003/i.test(m)) return m.length < 40 ? m : "\u8003\u8bd5\u672a\u5b8c\u6210\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
if (/\u5b66\u4e60|\u89c6\u9891|\u63d0\u4ea4/i.test(m)) return "\u5b66\u4e60\u63d0\u4ea4\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
if (m.length && m.length < 40 && !/Error|TypeError|undefined/i.test(m)) return m;
return "\u64cd\u4f5c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
}
function clearRunLog() {
state.logs = [];
const box = document.getElementById("fspx-run-log");
if (box) box.innerHTML = `\u6682\u65e0\u65e5\u5fd7
`;
}
function gmRequest(method, url, opts) {
opts = opts || {};
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(new Error("\u9700\u8981 Tampermonkey / ScriptCat"));
return;
}
const headers = Object.assign({ Accept: opts.accept || "*/*" }, opts.headers || {});
if (opts.xhr) headers["X-Requested-With"] = "XMLHttpRequest";
GM_xmlhttpRequest({
method: method || "GET",
url,
headers,
data: opts.body || undefined,
responseType: "text",
timeout: opts.timeout || 45000,
anonymous: false,
onload(r) {
resolve({
status: r.status,
text: r.responseText || "",
url: r.finalUrl || r.responseURL || url,
headers: r.responseHeaders || "",
});
},
onerror() {
reject(new Error("\u7f51\u7edc\u9519\u8bef"));
},
ontimeout() {
reject(new Error("\u8d85\u65f6"));
},
});
});
}
async function httpGet(path, opts) {
const url = absUrl(path);
const r = await gmRequest("GET", url, opts);
if (r.status === 401 || r.status === 403) throw new Error("\u672a\u767b\u5f55");
if (/logon\.html|misssession/i.test(r.url)) throw new Error("\u672a\u767b\u5f55");
return r;
}
async function httpPostForm(path, body, opts) {
opts = opts || {};
const url = absUrl(path);
const r = await gmRequest("POST", url, {
...opts,
xhr: opts.xhr !== false,
accept: opts.accept || "application/json, text/javascript, */*; q=0.01",
headers: Object.assign(
{ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
opts.headers || {}
),
body: typeof body === "string" ? body : new URLSearchParams(body || {}).toString(),
});
if (r.status === 401 || r.status === 403) throw new Error("\u672a\u767b\u5f55");
return r;
}
function parseJsonSafe(text) {
try {
return JSON.parse(String(text || ""));
} catch (_) {
return null;
}
}
function durationToSec(s) {
const raw = String(s || "").trim();
if (!raw) return 0;
if (/^\d+$/.test(raw)) return Math.max(0, Number(raw) || 0);
const p = raw.split(":").map((x) => Number(x) || 0);
if (p.length === 3) return p[0] * 3600 + p[1] * 60 + p[2];
if (p.length === 2) return p[0] * 60 + p[1];
return 0;
}
function isVideoDone(v) {
return String(v?.isEnd || "").trim() === "\u662f";
}
function getCloudApiBase() {
state.cloudApiBase = DEFAULT_CLOUD_API_BASE;
return DEFAULT_CLOUD_API_BASE;
}
function readCloudToken() {
return String(localStorage.getItem(CLOUD_TOKEN_KEY) || state.cloudToken || "").trim();
}
function writeCloudToken(token) {
const t = String(token || "").trim();
state.cloudToken = t;
if (t) localStorage.setItem(CLOUD_TOKEN_KEY, t);
else localStorage.removeItem(CLOUD_TOKEN_KEY);
}
function readLocalFreeUsed() {
return Math.max(0, Number(localStorage.getItem(CLOUD_FREE_USED_KEY) || 0) || 0);
}
function writeLocalFreeUsed(n) {
localStorage.setItem(CLOUD_FREE_USED_KEY, String(Math.max(0, Number(n) || 0)));
state.freeUsed = readLocalFreeUsed();
}
function readCloudLeaseCache() {
try {
const p = JSON.parse(localStorage.getItem(CLOUD_LEASE_CACHE_KEY) || "null");
if (!p || !p.lease) return null;
return p;
} catch (_) {
return null;
}
}
function writeCloudLeaseCache(lease, exp, extra) {
if (!lease || !exp) {
localStorage.removeItem(CLOUD_LEASE_CACHE_KEY);
return;
}
writeJson(CLOUD_LEASE_CACHE_KEY, Object.assign({ lease, exp }, extra || {}));
}
function _crq(path, method, body, headerExtra) {
if (_LDQ) {
return Promise.reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
}
const p = String(path || "").trim();
const url = `${getCloudApiBase()}${p.startsWith("/") ? p : `/${p}`}`;
const headers = Object.assign(
{ Accept: "application/json", "Content-Type": "application/json" },
headerExtra || {}
);
const token = readCloudToken();
if (token) headers.Authorization = `Bearer ${token}`;
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
return;
}
GM_xmlhttpRequest({
method: method || "GET",
url,
headers,
data: body != null ? JSON.stringify(body) : undefined,
responseType: "text",
timeout: 20000,
anonymous: true,
onload(resp) {
const status = Number(resp.status || 0);
const text = String(resp.responseText || "");
if (status < 200 || status >= 300) {
let msg = "\u670d\u52a1\u6821\u9a8c\u5931\u8d25";
try {
const j = JSON.parse(text);
if (j && (j.message || j.msg)) msg = String(j.message || j.msg);
} catch (_) {}
if (/free_quota/i.test(msg)) msg = "\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro";
reject(new Error(msg));
return;
}
try {
resolve(text ? JSON.parse(text) : {});
} catch (_) {
resolve({ raw: text });
}
},
onerror() {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
},
ontimeout() {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
},
});
});
}
function _crt(path) {
const p = String(path || "").trim() || DEFAULT_PANEL_NOTICE_PATH;
const url = `${getCloudApiBase()}${p.startsWith("/") ? p : `/${p}`}`;
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
return;
}
GM_xmlhttpRequest({
method: "GET",
url,
headers: { Accept: "text/plain, */*" },
timeout: 15000,
anonymous: true,
onload(resp) {
const status = Number(resp.status || 0);
if (status < 200 || status >= 300) {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
return;
}
resolve(String(resp.responseText || "").trim());
},
onerror() {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
},
ontimeout() {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
},
});
});
}
function _alp(data) {
if (!data || typeof data !== "object") return;
if (data.lease) state.cloudLease = String(data.lease);
const exp = Number(data.exp ?? data.expire_at ?? data.expires_at ?? data.lease_exp ?? 0);
if (exp > 0) state.cloudExpireAt = exp;
if (data.tier) state.cloudTier = String(data.tier);
if (data.revoked) state.cloudRevoked = true;
const freeUsed = data.freeUsed ?? data.free_used_chapters ?? data.used;
const freeLimit = data.freeLimit ?? data.free_chapter_limit ?? data.limit;
if (freeUsed != null) writeLocalFreeUsed(freeUsed);
if (freeLimit != null) state.freeChapterLimit = Number(freeLimit) || DEFAULT_FREE_COURSE_LIMIT;
if (state.cloudLease && state.cloudExpireAt) {
writeCloudLeaseCache(state.cloudLease, state.cloudExpireAt, { tier: state.cloudTier });
}
state.cloudReady = true;
}
async function _fpn() {
try {
const text = await _crt(state.panelNoticePath || DEFAULT_PANEL_NOTICE_PATH);
if (text) state.remotePanelNotice = text;
renderPanelNotice();
} catch (_) {}
}
async function _fcc() {
try {
const data = await _crq(`${CLOUD_API_PREFIX}/client-config`, "GET", null);
if (data?.panelNoticePath) {
const p = String(data.panelNoticePath).trim();
if (p) state.panelNoticePath = p.startsWith("/") ? p : `/${p}`;
}
if (data?.freeChapterLimit != null) {
state.freeChapterLimit = Number(data.freeChapterLimit) || DEFAULT_FREE_COURSE_LIMIT;
}
if (data?.proBuyUrl) {
const buy = String(data.proBuyUrl).trim();
if (buy) state.proBuyUrl = buy;
}
state.cloudLastError = "";
} catch (e) {
state.cloudLastError = String(e.message || e);
}
await _fpn();
}
async function _ecl(force) {
const cached = readCloudLeaseCache();
const now = Math.floor(Date.now() / 1000);
if (!force && cached?.lease && cached.exp - now > 60) {
state.cloudLease = cached.lease;
state.cloudExpireAt = cached.exp;
if (cached.tier) state.cloudTier = cached.tier;
state.cloudReady = true;
return true;
}
const data = await _crq(`${CLOUD_API_PREFIX}/lease`, "POST", {
learning_user_id: _glu() || "anonymous",
site_id: SITE_ID,
token: readCloudToken() || undefined,
lease: state.cloudLease || undefined,
});
_alp(data);
state.cloudLastError = "";
return true;
}
async function _vct() {
try {
await _crq(`${CLOUD_API_PREFIX}/license/verify`, "GET", null);
await _ecl(true);
state.cloudLastError = "";
return true;
} catch (e) {
state.cloudLastError = String(e.message || e);
state.cloudReady = false;
return false;
}
}
async function _acp() {
if (_LDQ) {
log("\u670d\u52a1\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return false;
}
try {
await _ecl(true);
} catch (_) {
log("\u670d\u52a1\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return false;
}
if (state.cloudRevoked) {
log("\u670d\u52a1\u6682\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return false;
}
if (!_ipt()) {
const used = Math.max(state.freeUsed, readLocalFreeUsed());
const limit = state.freeChapterLimit || DEFAULT_FREE_COURSE_LIMIT;
if (used >= limit) {
log("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro");
return false;
}
}
return true;
}
function _ipt() {
return !state.cloudRevoked && String(state.cloudTier || "").toLowerCase() === "pro";
}
function _glu() {
return String(state.learningUserId || state.userLabel || "").trim();
}
async function _sct(token) {
writeCloudToken(token);
const ok = await _vct();
if (ok) log(token ? "\u5df2\u751f\u6548" : "\u5df2\u6e05\u9664");
else log("\u6fc0\u6d3b\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u540e\u91cd\u8bd5");
updatePanel();
return ok;
}
function formatCloudTierText() {
if (state.cloudRevoked) return "\u5df2\u7981\u7528";
const t = String(state.cloudTier || "").toLowerCase();
if (t === "pro") return "Pro \u4f1a\u5458";
if (t === "free") return "\u514d\u8d39\u4f53\u9a8c";
return "\u672a\u6821\u9a8c";
}
function _ucu() {
state.freeUsed = readLocalFreeUsed();
const tierEl = document.getElementById("fspx-cloud-tier");
if (tierEl) tierEl.textContent = formatCloudTierText();
const freeEl = document.getElementById("fspx-cloud-free");
const freeLabel = document.getElementById("fspx-cloud-free-label");
const limit = state.freeChapterLimit || DEFAULT_FREE_COURSE_LIMIT;
if (freeLabel) freeLabel.textContent = `\u514d\u8d39\u4f53\u9a8c\uff08${limit}\u4e2a\u8bfe\u7a0b\uff09`;
if (freeEl) {
freeEl.textContent = _ipt()
? "\u4e0d\u9650\u91cf"
: `${Math.max(state.freeUsed, readLocalFreeUsed())}/${limit} \u4e2a`;
}
const tokenInput = document.getElementById("fspx-cloud-token");
if (tokenInput && document.activeElement !== tokenInput) {
tokenInput.value = readCloudToken();
}
}
function renderPanelNotice() {
const el = document.getElementById("fspx-ann-text");
if (!el) return;
el.textContent = state.remotePanelNotice || PANEL_NOTICE_FALLBACK;
}
function openBuyPage() {
window.open(state.proBuyUrl || PRO_BUY_URL, "_blank");
}
function registerCloudMenus() {
if (typeof GM_registerMenuCommand !== "function") return;
try {
GM_registerMenuCommand("\u653e\u5c04\u536b\u751f\u52a9\u624b\uff1a\u6253\u5f00\u8d2d\u4e70\u9875", () => openBuyPage());
} catch (_) {}
}
async function _ruc() {
const topHtml = (await httpGet(`${ROOT}/page/main_top_page`)).text;
let loginId = "";
const m1 = topHtml.match(/SysUser!findByUserId["']?\s*,\s*\{\s*["']id["']\s*:\s*["']([^"']+)["']/);
const m2 = topHtml.match(/findByUserId[\s\S]{0,80}?["']id["']\s*:\s*["']([^"']+)["']/);
const m3 = topHtml.match(/\{\s*"id"\s*:\s*"(\d{10,})"\s*\}/);
loginId = (m1 || m2 || m3 || [])[1] || "";
if (!loginId) throw new Error("\u672a\u767b\u5f55");
state.loginUserId = loginId;
const r = await httpPostForm(`${ROOT}/json/SysUser!findByUserId`, { id: loginId });
const data = parseJsonSafe(r.text);
const row = data?.data?.[0];
if (!row?.id) throw new Error("\u672a\u767b\u5f55");
state.learningUserId = String(row.id);
state.domain = String(row.userDomain || state.domain || "").trim() || "131081";
state.userLabel = String(row.userName || row.userId || row.id || "").trim();
return row;
}
async function _fav() {
const pageSize = 50;
let start = 0;
let total = Infinity;
const all = [];
while (start < total) {
const body = `filter=${encodeURIComponent("[]")}&start=${start}&limit=${pageSize}`;
const r = await httpPostForm(`${ROOT}/json/videoAction!selectList`, body, {
headers: { Referer: absUrl(`${ROOT}/page/video_list_page`) },
});
const data = parseJsonSafe(r.text);
if (!data || data.success === false) throw new Error("\u89c6\u9891\u5217\u8868\u52a0\u8f7d\u5931\u8d25");
const rows = Array.isArray(data.data) ? data.data : [];
total = Number(data.total != null ? data.total : rows.length) || rows.length;
for (const row of rows) {
all.push({
videoId: String(row.id || ""),
title: String(row.videoName || row.id || "\u672a\u547d\u540d\u89c6\u9891"),
duration: String(row.duration || ""),
durationSec: durationToSec(row.duration),
grade: Number(row.grade != null ? row.grade : 1) || 1,
isEnd: String(row.isEnd || ""),
selected: false,
});
}
if (!rows.length) break;
start += pageSize;
if (all.length >= total) break;
}
return all;
}
async function _es(kind, context, cfg) {
await _ecl(false);
if (!state.cloudLease) throw new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25");
const data = await _crq(`${CLOUD_API_PREFIX}/study/engine/start`, "POST", {
lease: state.cloudLease,
kind: String(kind || "video"),
context: context || {},
config: Object.assign(
{ free_chapter_limit: state.freeChapterLimit || DEFAULT_FREE_COURSE_LIMIT },
cfg || {}
),
});
_alp(data);
return data;
}
async function _ep(sessionId, event, lastResult, contextPatch) {
await _ecl(false);
if (!state.cloudLease) throw new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25");
const data = await _crq(`${CLOUD_API_PREFIX}/study/engine/step`, "POST", {
lease: state.cloudLease,
session_id: String(sessionId || ""),
event: String(event || "tick"),
last_result: lastResult == null ? null : lastResult,
context_patch: contextPatch == null ? null : contextPatch,
});
_alp(data);
return data;
}
async function _xc(cmd) {
const c = cmd || {};
const t = String(c.type || "");
if (t === "done") {
return {
terminal: true,
ok: c.success !== false,
skipped: !!c.skipped,
msg: c.message || "\u5b8c\u6210",
grade: c.grade,
study_ok: !!c.study_ok,
exam_ok: !!c.exam_ok,
command: c,
};
}
if (t === "failed") {
return { terminal: true, ok: false, msg: c.message || "\u5931\u8d25" };
}
if (t === "platform_get" || t === "platform_fetch" || t === "document_fetch") {
const path = c.path || c.url || "";
const r = await httpGet(path, {
xhr: !!c.xhr,
headers: c.headers || {},
accept: (c.headers && (c.headers.Accept || c.headers.accept)) || undefined,
});
return {
event: c.event || "submit_result",
lastResult: { text: r.text, status: r.status, url: r.url },
};
}
if (t === "platform_post") {
const headers = Object.assign({}, c.headers || {});
const body =
c.body != null
? c.body
: typeof c.payload === "string"
? c.payload
: new URLSearchParams(c.payload || {}).toString();
const r = await httpPostForm(c.path || c.url || "", body, {
headers,
accept: headers.Accept || headers.accept || "*/*",
});
let data;
try {
data = JSON.parse(r.text);
} catch (_) {
data = { text: r.text };
}
return {
event: "submit_result",
lastResult: { data, http_status: r.status, text: r.text, url: r.url },
};
}
if (t === "wait") {
await sleep(Math.max(300, Number(c.ms || 800)));
return { event: "tick", lastResult: null };
}
return { event: "tick", lastResult: null };
}
async function _rel(startPayload) {
let sid = String(startPayload.session_id || "");
let cmd = startPayload.command;
if (startPayload.log) log(String(startPayload.log));
for (let i = 0; i < 40; i++) {
if (state.stopFlag) throw new Error("\u5df2\u505c\u6b62");
if (!cmd) throw new Error("\u64cd\u4f5c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
const out = await _xc(cmd);
if (out.terminal) {
if (!out.ok) throw new Error(out.msg || "\u64cd\u4f5c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return out;
}
const next = await _ep(sid, out.event, out.lastResult, null);
if (next.session_id) sid = String(next.session_id);
if (next.log) log(String(next.log));
cmd = next.command;
}
throw new Error("\u64cd\u4f5c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
}
async function _rve(video) {
const start = await _es("video", {
video_id: video.videoId,
videoId: video.videoId,
user_id: state.learningUserId,
userId: state.learningUserId,
domain: state.domain,
grade: video.grade || 1,
duration_sec: video.durationSec || durationToSec(video.duration),
duration: video.duration,
title: video.title,
});
return _rel(start);
}
async function _ree() {
const start = await _es("exam", {
user_id: state.learningUserId,
userId: state.learningUserId,
domain: state.domain,
});
return _rel(start);
}
function loadQueue() {
const arr = readJson(QUEUE_KEY, []);
return Array.isArray(arr) ? arr.map(String) : [];
}
function saveQueue(ids) {
writeJson(QUEUE_KEY, Array.from(new Set((ids || []).map(String))));
}
function isVideoSelectable(v) {
return v && v.videoId && !isVideoDone(v);
}
async function _rpv(opts) {
opts = opts || {};
if (state.panelRefreshing) return;
state.panelRefreshing = true;
try {
if (!opts.silent) log("\u6b63\u5728\u5237\u65b0…");
await _ruc();
const list = await _fav();
const q = new Set(loadQueue());
for (const v of list) v.selected = q.has(v.videoId) && isVideoSelectable(v);
state.panelVideos = list;
renderVideoList();
updateQueueSummary();
updateCurrentMeta();
_ucu();
if (!opts.silent) log(`\u5df2\u52a0\u8f7d ${list.length} \u4e2a\u89c6\u9891`);
} catch (e) {
if (!opts.silent) log(_ferr(e));
state.panelHint = _ferr(e);
renderVideoList();
} finally {
state.panelRefreshing = false;
updatePanel();
}
}
function selectUnfinishedVideos() {
const ids = state.panelVideos.filter(isVideoSelectable).map((v) => v.videoId);
saveQueue(ids);
for (const v of state.panelVideos) v.selected = ids.includes(v.videoId);
renderVideoList();
updateQueueSummary();
log(ids.length ? `\u5df2\u52fe\u9009\u672a\u5b8c\u6210 ${ids.length} \u4e2a` : "\u6ca1\u6709\u672a\u5b8c\u6210\u89c6\u9891");
}
function clearVideoSelection() {
saveQueue([]);
for (const v of state.panelVideos) v.selected = false;
renderVideoList();
updateQueueSummary();
}
function renderVideoList() {
const box = document.getElementById("fspx-video-list");
if (!box) return;
if (!state.panelVideos.length) {
box.innerHTML = `${escHtml(state.panelHint || "\u767b\u5f55\u540e\u5c06\u81ea\u52a8\u52a0\u8f7d")}
`;
return;
}
const q = new Set(loadQueue());
box.innerHTML = state.panelVideos
.map((v) => {
const done = isVideoDone(v);
const checked = !done && (q.has(v.videoId) || v.selected);
const cls = done ? "fspx-video-done" : checked ? "fspx-video-study" : "";
const badge = done
? `\u5df2\u5b8c\u6210`
: `\u672a\u5b8c\u6210`;
return ``;
})
.join("");
box.querySelectorAll("input[type=checkbox][data-vid]").forEach((input) => {
input.addEventListener("change", () => {
const id = input.getAttribute("data-vid");
const cur = loadQueue().filter((x) => x !== id);
if (input.checked) cur.push(id);
saveQueue(cur);
const hit = state.panelVideos.find((x) => x.videoId === id);
if (hit) hit.selected = !!input.checked;
updateQueueSummary();
});
});
}
function updateQueueSummary() {
const q = loadQueue().filter((id) => {
const v = state.panelVideos.find((x) => x.videoId === id);
return v && isVideoSelectable(v);
});
saveQueue(q);
const unfinished = state.panelVideos.filter((v) => !isVideoDone(v)).length;
const done = state.panelVideos.filter(isVideoDone).length;
state.queueTotal = state.enabled ? state.queueTotal : q.length;
const pct =
state.queueTotal > 0 ? Math.min(100, Math.round((state.queueDone / state.queueTotal) * 100)) : 0;
const set = (id, text) => {
const el = document.getElementById(id);
if (el) el.textContent = text;
};
set("fspx-queue-done", String(state.queueDone || 0));
set("fspx-queue-total", String(state.queueTotal || q.length || 0));
set("fspx-queue-percent", `${pct}%`);
set("fspx-exam-status", state.examLabel || (state.examGrade != null ? `${state.examGrade}\u5206` : "—"));
const bar = document.getElementById("fspx-queue-progress");
if (bar) bar.style.width = `${pct}%`;
set(
"fspx-queue-text",
q.length
? `\u5df2\u9009 ${q.length} \u4e2a\u672a\u5b8c\u6210 · \u5168\u7ad9\u5b8c\u6210 ${done}/${state.panelVideos.length}\uff08\u5269 ${unfinished}\uff09`
: `\u672a\u9009\u62e9 · \u5168\u7ad9\u5b8c\u6210 ${done}/${state.panelVideos.length || 0}`
);
}
function updateCurrentMeta() {
const set = (id, text) => {
const el = document.getElementById(id);
if (el) el.textContent = text;
};
set("fspx-current-user", state.userLabel || "—");
set("fspx-current-video", state.currentVideoTitle || "\u65e0");
set("fspx-current-task", state.currentTask || "—");
}
function updatePanel() {
updateQueueSummary();
updateCurrentMeta();
_ucu();
renderPanelNotice();
const status = document.getElementById("fspx-auto-status");
if (status) status.textContent = state.enabled ? "\u8fd0\u884c\u4e2d" : "\u5df2\u505c\u6b62";
const start = document.getElementById("fspx-start");
const stop = document.getElementById("fspx-stop");
const examBtn = document.getElementById("fspx-exam-only");
if (start) {
start.disabled = !!state.enabled;
start.classList.toggle("fspx-btn-off", !!state.enabled);
}
if (examBtn) {
examBtn.disabled = !!state.enabled;
examBtn.classList.toggle("fspx-btn-off", !!state.enabled);
}
if (stop) {
stop.disabled = !state.enabled;
stop.classList.toggle("fspx-btn-off", !state.enabled);
}
}
async function _pv(video) {
if (state.stopFlag) throw new Error("\u5df2\u505c\u6b62");
state.currentVideoTitle = video.title;
state.currentTask = "\u63d0\u4ea4\u5b66\u4e60…";
updatePanel();
log(`\u5f00\u59cb\uff1a${video.title}`);
if (!(await _acp())) throw new Error("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro");
const out = await _rve(video);
if (!out.ok) throw new Error(out.msg || "\u5b66\u4e60\u63d0\u4ea4\u5931\u8d25");
video.isEnd = "\u662f";
log(`\u5b8c\u6210\uff1a${video.title}`);
state.currentTask = "\u672c\u89c6\u9891\u5df2\u5b8c\u6210";
updatePanel();
}
async function _rse() {
const q = loadQueue().filter((id) => {
const v = state.panelVideos.find((x) => x.videoId === id);
return v && isVideoSelectable(v);
});
saveQueue(q);
state.queueTotal = q.length;
state.queueDone = 0;
updatePanel();
try {
for (const id of q) {
if (state.stopFlag) break;
const video = state.panelVideos.find((x) => x.videoId === id);
if (!video || !isVideoSelectable(video)) continue;
try {
await _pv(video);
state.queueDone += 1;
} catch (e) {
log(`${video.title}\uff1a${_ferr(e)}`);
if (/\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c|\u5df2\u505c\u6b62/.test(String(e.message || e))) break;
}
updatePanel();
await sleep(400);
}
await _rpv({ silent: true });
if (!state.stopFlag && AUTO_EXAM) {
const unfinished = state.panelVideos.filter((v) => !isVideoDone(v)).length;
if (unfinished === 0) {
try {
state.currentTask = "\u5f00\u59cb\u8003\u8bd5…";
updatePanel();
log("\u5f00\u59cb\u8003\u8bd5");
const out = await _ree();
if (out.ok) {
const g = out.grade != null ? out.grade : out.command?.grade;
state.examGrade = g != null ? Number(g) : state.examGrade;
state.examLabel = state.examGrade != null ? `${state.examGrade}\u5206` : "\u5df2\u5b8c\u6210";
log(out.msg || "\u8003\u8bd5\u5df2\u5b8c\u6210");
}
} catch (e) {
log(`\u8003\u8bd5\uff1a${_ferr(e)}`);
state.currentTask = _ferr(e);
}
} else {
log(`\u4ecd\u6709 ${unfinished} \u4e2a\u672a\u5b8c\u6210\uff0c\u8df3\u8fc7\u8003\u8bd5`);
}
}
state.currentTask = state.stopFlag ? "\u5df2\u505c\u6b62" : state.examLabel || "\u5168\u90e8\u5b8c\u6210";
if (!state.stopFlag) log("\u5168\u90e8\u5b8c\u6210");
} catch (e) {
log(_ferr(e));
state.currentTask = _ferr(e);
} finally {
state.enabled = false;
state.stopFlag = false;
updatePanel();
}
}
function switchPanelTab(tab) {
document.querySelectorAll(".fspx-tab-btn").forEach((b) => {
b.classList.toggle("active", b.dataset.tab === tab);
});
document.querySelectorAll(".fspx-pane").forEach((p) => {
p.classList.toggle("active", p.dataset.pane === tab);
});
}
function readPanelCollapsed() {
return localStorage.getItem(PANEL_COLLAPSED_KEY) === "1";
}
function writePanelCollapsed(v) {
localStorage.setItem(PANEL_COLLAPSED_KEY, v ? "1" : "0");
}
function readPanelPos() {
return readJson(PANEL_POS_KEY, null);
}
function writePanelPos(left, top) {
writeJson(PANEL_POS_KEY, { left, top });
}
function applyPanelCollapsed(panel, collapsed) {
panel.classList.toggle("fspx-panel-min", !!collapsed);
panel.classList.toggle("fspx-panel-max", !collapsed);
const min = panel.querySelector("#fspx-btn-min");
const max = panel.querySelector("#fspx-btn-max");
if (min) min.style.display = collapsed ? "none" : "";
if (max) max.style.display = collapsed ? "" : "none";
}
function enablePanelDrag(panel) {
const header = panel.querySelector("#fspx-panel-header");
if (!header) return;
let dragging = false;
let sx = 0;
let sy = 0;
let ol = 0;
let ot = 0;
header.addEventListener("mousedown", (e) => {
if (e.target.closest("button")) return;
dragging = true;
const rect = panel.getBoundingClientRect();
sx = e.clientX;
sy = e.clientY;
ol = rect.left;
ot = rect.top;
panel.style.right = "auto";
panel.style.left = `${ol}px`;
panel.style.top = `${ot}px`;
e.preventDefault();
});
window.addEventListener("mousemove", (e) => {
if (!dragging) return;
panel.style.left = `${Math.max(0, ol + e.clientX - sx)}px`;
panel.style.top = `${Math.max(0, ot + e.clientY - sy)}px`;
});
window.addEventListener("mouseup", () => {
if (!dragging) return;
dragging = false;
const rect = panel.getBoundingClientRect();
writePanelPos(rect.left, rect.top);
});
}
function injectPanelStyles() {
const id = "fspx-panel-style-v2";
if (document.getElementById(id) || !document.head) return;
document.getElementById("fspx-panel-style-v1")?.remove();
const st = document.createElement("style");
st.id = id;
st.textContent = `
#fspx-auto-panel{position:fixed;right:20px;top:80px;z-index:999999;width:412px;display:flex;flex-direction:column;background:#f4f7fb;border:1px solid #dbe4f0;border-radius:16px;box-shadow:0 16px 40px rgba(15,23,42,.17);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;font-size:12px;color:#0f172a;overflow:hidden;}
#fspx-auto-panel.fspx-panel-max{max-height:min(92vh,780px);}
#fspx-auto-panel.fspx-panel-min #fspx-panel-body,#fspx-auto-panel.fspx-panel-min #fspx-panel-footer,#fspx-auto-panel.fspx-panel-min .fspx-footer-extra{display:none !important;}
#fspx-panel-header{padding:8px 11px;background:linear-gradient(180deg,#f8ecd5,#f4e8cf);border-bottom:1px solid #e5dbc6;display:flex;justify-content:space-between;align-items:center;cursor:move;user-select:none;}
#fspx-panel-brand{display:flex;align-items:center;gap:9px;min-width:0;flex:1;}
#fspx-panel-logo{width:30px;height:30px;border-radius:9px;object-fit:cover;display:block;flex:0 0 auto;background:#e2e8f0;}
#fspx-panel-title{font-size:13px;font-weight:900;color:#9a3412;line-height:1.26;}
#fspx-panel-sub{margin-top:3px;font-size:11px;color:#7c2d12;font-weight:700;}
#fspx-panel-controls{display:flex;gap:5px;}
.fspx-panel-ctl{border:none;background:#fff;color:#64748b;width:28px;height:28px;border-radius:999px;cursor:pointer;font-size:15px;font-weight:900;}
#fspx-panel-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;}
#fspx-panel-footer{padding:7px 11px;background:#eef2f7;border-top:1px solid #dbe4f0;font-size:12px;}
.fspx-footer-extra{padding:6px 9px;background:#f8fafc;border-top:1px solid #e2e8f0;}
.fspx-ann{position:relative;font-size:12px;color:#334155;line-height:1.42;background:linear-gradient(180deg,#fffdf5,#fff7e6);border:1px solid #fcd34d;border-radius:11px;padding:8px 9px 8px 11px;}
.fspx-ann::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,#f59e0b,#ef4444);border-top-left-radius:11px;border-bottom-left-radius:11px;}
.fspx-card{background:#fff;border:1px solid #d9e2ee;border-radius:12px;padding:7px 9px;margin-bottom:7px;}
.fspx-status-row{display:flex;justify-content:space-between;align-items:center;gap:7px;}
.fspx-status-metrics{font-size:12px;color:#475569;}
.fspx-status-metrics em{font-style:normal;font-weight:900;color:#0f172a;}
.fspx-progress-pct{font-size:14px;font-weight:900;color:#0369a1;}
.fspx-progress-bar{height:5px;border-radius:999px;background:#e2e8f0;overflow:hidden;margin-top:4px;}
.fspx-progress-bar>span{display:block;height:100%;width:0;background:linear-gradient(90deg,#22d3ee,#2563eb);transition:width .2s ease;}
.fspx-tabbar{display:flex;gap:6px;margin-bottom:7px;}
.fspx-tab-btn{flex:1;border:1px solid #cbd5e1;background:#f8fafc;color:#475569;padding:4px;border-radius:9px;cursor:pointer;font-weight:700;font-size:11px;}
.fspx-tab-btn.active{background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;border-color:transparent;}
.fspx-pane{display:none;}.fspx-pane.active{display:block;}
.fspx-list-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;gap:6px;flex-wrap:wrap;}
.fspx-list-title{font-size:12px;color:#64748b;font-weight:700;}
.fspx-list-tag{font-size:11px;color:#92400e;background:#ffedd5;border:1px solid #fdba74;border-radius:999px;padding:2px 7px;}
#fspx-video-list,#fspx-run-log{max-height:220px;overflow-y:auto;background:#f8fafc;border:1px solid #dbe4f0;border-radius:11px;padding:5px;}
.fspx-empty-state{padding:12px 7px;text-align:center;color:#94a3b8;font-size:12px;font-weight:700;}
.fspx-list-head-actions{display:flex;gap:6px;align-items:center;flex-wrap:nowrap;justify-content:flex-start;flex:1 1 100%;}
.fspx-list-head-actions .fspx-btn{flex:0 0 auto !important;width:auto;min-width:0;white-space:nowrap;padding:4px 8px;font-size:11px;line-height:1.2;}
.fspx-video-item{display:flex;gap:8px;align-items:flex-start;border:1px solid #e2e8f0;border-radius:10px;padding:8px;margin-bottom:6px;cursor:pointer;background:#f8fafc;}
.fspx-video-item.fspx-video-study{background:#fffbeb;border-color:#fcd34d;}
.fspx-video-item.fspx-video-done{background:#e2e8f0;border-color:#cbd5e1;opacity:.85;cursor:not-allowed;}
.fspx-video-item>input[type=checkbox]{margin-top:2px;flex:0 0 auto;}
.fspx-video-body{flex:1;min-width:0;}
.fspx-video-title{display:block;font-size:12px;line-height:1.4;font-weight:700;}
.fspx-status-badges{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px;}
.fspx-badge{font-size:10px;font-weight:800;padding:2px 6px;border-radius:999px;border:1px solid;line-height:1.3;}
.fspx-badge-study-done{color:#047857;background:#ecfdf5;border-color:#6ee7b7;}
.fspx-badge-study-todo{color:#64748b;background:#f8fafc;border-color:#cbd5e1;}
.fspx-log-row{padding:4px 6px;border-bottom:1px dashed #d4deea;font-size:12px;line-height:1.42;}
.fspx-meta-row{display:flex;justify-content:space-between;gap:7px;font-size:11px;margin-bottom:4px;}
.fspx-meta-label{color:#64748b;}.fspx-meta-value{font-weight:700;text-align:right;max-width:68%;word-break:break-all;}
.fspx-btn-row{display:flex;gap:7px;flex-wrap:wrap;}
.fspx-btn{flex:1;border:none;color:#fff;padding:7px 9px;border-radius:10px;cursor:pointer;font-weight:800;font-size:12px;min-width:68px;}
.fspx-btn-start{background:#16a34a;}.fspx-btn-stop{background:#ef4444;}
.fspx-btn-off,.fspx-btn:disabled{background:#cbd5e1 !important;color:#64748b !important;cursor:not-allowed;}
.fspx-btn-ghost{flex:0 0 auto;background:#fff;color:#0f172a;border:1px solid #cbd5e1;}
.fspx-btn-pro{flex:0 0 auto;background:linear-gradient(135deg,#ea580c,#f59e0b);color:#fff;border:none;border-radius:8px;padding:4px 8px;font-size:12px;font-weight:800;cursor:pointer;}
`;
document.head.appendChild(st);
}
function _cp() {
const old = document.getElementById("fspx-auto-panel");
if (old) old.remove();
injectPanelStyles();
const panel = document.createElement("div");
panel.id = "fspx-auto-panel";
panel.className = "fspx-panel-max";
panel.innerHTML = `
\u8fd0\u884c\u72b6\u6001
\u5df2\u505c\u6b62
\u89c6\u9891 0/0 · \u8003\u8bd5 —
0%
\u5728\u7ebf\u57f9\u8bad\u89c6\u9891
\u52fe\u9009\u672a\u5b8c\u6210\u89c6\u9891\u540e\u70b9\u300c\u5f00\u59cb\u300d\uff1b\u5168\u90e8\u5b8c\u6210\u540e\u81ea\u52a8\u8003\u8bd5\u3002\u4e5f\u53ef\u5355\u72ec\u70b9\u300c\u4ec5\u8003\u8bd5\u300d\u3002
\u767b\u5f55\u540e\u5c06\u81ea\u52a8\u52a0\u8f7d
\u8fd0\u884c\u65e5\u5fd7
\u6388\u6743\u4e0e\u9009\u9879\u4f1a\u5458
\u7528\u6237\u7c7b\u578b\u672a\u6821\u9a8c
\u514d\u8d39\u4f53\u9a8c\uff081\u4e2a\u8bfe\u7a0b\uff090/1 \u4e2a
Token
\u514d\u8d39\u4f53\u9a8c1\u4e2a\u8bfe\u7a0b\uff1bPro\u6fc0\u6d3b\u540e10\u5929\u6709\u6548\u53ef\u5b8c\u6210\u6240\u6709\u8bfe\u7a0b\u548c\u8003\u8bd5\u3002\u672a\u9002\u914d\u5e73\u53f0\u548c\u53cd\u9988\u8054\u7cfbQQ125431514\uff01
\u5f53\u524d\u7528\u6237—
\u5f53\u524d\u89c6\u9891\u65e0
\u5f53\u524d\u4efb\u52a1\u52fe\u9009\u89c6\u9891\u540e\u70b9\u300c\u5f00\u59cb\u300d
`;
document.body.appendChild(panel);
const savedPos = readPanelPos();
if (savedPos?.left != null && savedPos?.top != null) {
panel.style.right = "auto";
panel.style.left = `${savedPos.left}px`;
panel.style.top = `${savedPos.top}px`;
}
applyPanelCollapsed(panel, readPanelCollapsed());
enablePanelDrag(panel);
panel.querySelector("#fspx-btn-min")?.addEventListener("click", (e) => {
e.stopPropagation();
writePanelCollapsed(true);
applyPanelCollapsed(panel, true);
});
panel.querySelector("#fspx-btn-max")?.addEventListener("click", (e) => {
e.stopPropagation();
writePanelCollapsed(false);
applyPanelCollapsed(panel, false);
});
panel.querySelectorAll(".fspx-tab-btn").forEach((btn) => {
btn.addEventListener("click", () => switchPanelTab(btn.dataset.tab));
});
document.getElementById("fspx-refresh")?.addEventListener("click", () => {
void _rpv({ reason: "manual" });
});
document.getElementById("fspx-select-unfinished")?.addEventListener("click", () => selectUnfinishedVideos());
document.getElementById("fspx-clear-select")?.addEventListener("click", () => clearVideoSelection());
document.getElementById("fspx-clear-log")?.addEventListener("click", () => clearRunLog());
document.getElementById("fspx-cloud-save")?.addEventListener("click", () => {
const input = document.getElementById("fspx-cloud-token");
void _sct(input?.value || "");
});
document.getElementById("fspx-open-pro")?.addEventListener("click", () => openBuyPage());
document.getElementById("fspx-start")?.addEventListener("click", () => {
const q = loadQueue().filter((id) => {
const v = state.panelVideos.find((x) => x.videoId === id);
return v && isVideoSelectable(v);
});
saveQueue(q);
if (!q.length) {
log("\u8bf7\u5148\u52fe\u9009\u672a\u5b8c\u6210\u7684\u89c6\u9891");
updatePanel();
return;
}
void (async () => {
if (!(await _acp())) {
updatePanel();
return;
}
state.enabled = true;
state.stopFlag = false;
state.currentTask = "\u6b63\u5728\u5f00\u59cb…";
log("\u5df2\u5f00\u59cb");
updatePanel();
void _rse();
})();
});
document.getElementById("fspx-exam-only")?.addEventListener("click", () => {
void (async () => {
if (state.enabled) return;
try {
await _ecl(true);
} catch (_) {
log("\u670d\u52a1\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return;
}
if (state.cloudRevoked) {
log("\u670d\u52a1\u6682\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5");
return;
}
state.enabled = true;
state.stopFlag = false;
state.currentTask = "\u5f00\u59cb\u8003\u8bd5…";
log("\u5f00\u59cb\u8003\u8bd5");
updatePanel();
try {
if (!state.learningUserId) await _ruc();
const out = await _ree();
if (out.ok) {
const g = out.grade != null ? out.grade : out.command?.grade;
state.examGrade = g != null ? Number(g) : null;
state.examLabel = state.examGrade != null ? `${state.examGrade}\u5206` : "\u5df2\u5b8c\u6210";
state.currentTask = state.examLabel;
log(out.msg || "\u8003\u8bd5\u5df2\u5b8c\u6210");
}
} catch (e) {
log(`\u8003\u8bd5\uff1a${_ferr(e)}`);
state.currentTask = _ferr(e);
} finally {
state.enabled = false;
state.stopFlag = false;
updatePanel();
}
})();
});
document.getElementById("fspx-stop")?.addEventListener("click", () => {
state.stopFlag = true;
state.enabled = false;
state.currentTask = "\u6b63\u5728\u505c\u6b62…";
log("\u5df2\u505c\u6b62");
updatePanel();
});
}
async function boot() {
if (!isFspxPage() || !isTopWindow()) return;
if (!document.body) {
setTimeout(() => void boot(), 300);
return;
}
state.cloudToken = readCloudToken();
state.freeUsed = readLocalFreeUsed();
state.cloudApiBase = getCloudApiBase();
registerCloudMenus();
_cp();
updatePanel();
log("\u52a9\u624b\u5df2\u5c31\u7eea");
void _fcc().then(() => _ucu());
void _vct().then(() => _ucu());
await _rpv({ silent: true });
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => void boot());
} else {
void boot();
}
})();