// ==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 = `
\u653e\u5c04\u536b\u751f\u57f9\u8bad\u8003\u8bd5\u52a9\u624b v${SCRIPT_VERSION}
\u81ea\u52a8\u770b\u8bfe · \u81ea\u52a8\u8003\u8bd5
\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
\u6682\u65e0\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(); } })();