// ==UserScript==
// @name 北京市继续医学教育全员必修课培训助手(1分钟完成)
// @namespace https://bjsqypx.haoyisheng.com/
// @version 1.0
// @description 北京市继续医学教育全员必修课培训助手:自动完成学时与章节考试;免费体验1节,Pro不限量10天。速度效率完成,1分钟完成。请在培训首页使用。
// @author 柠檬真酸
// @match https://bjsqypx.haoyisheng.com/*
// @match http://bjsqypx.haoyisheng.com/*
// @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png
// @connect bjsqypx.haoyisheng.com
// @connect oa35.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 API_BASE = location.origin;
const BJ = "/qypx/bj";
const EXAM_MAX_TRIES = 48;
const PANEL_LOGO_URL =
"https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png";
const PANEL_NOTICE_FALLBACK = "\u5317\u4eac\u5168\u5458\u5fc5\u4fee\u8bfe\u57f9\u8bad\u52a9\u624b";
const DEFAULT_PANEL_NOTICE_PATH = "/api/bjqx/panel-notice";
const PINNED_CLOUD_HOST = "oa35.ahzsksw.cn";
const DEFAULT_CLOUD_API_BASE = `https://${PINNED_CLOUD_HOST}`;
const CLOUD_API_PREFIX = "/api/bjqx";
const DEFAULT_FREE_CHAPTER_LIMIT = 1;
const PRO_BUY_URL = "https://card.wlxy.live/details/3C227B2A";
const PRO_DAYS = 10;
const CLOUD_TOKEN_KEY = "bjqx_cloud_token_v1";
const CLOUD_LEASE_CACHE_KEY = "bjqx_cloud_lease_cache_v1";
const CLOUD_FREE_USED_KEY = "bjqx_cloud_free_used_v1";
const QUEUE_KEY = "bjqx_course_queue_v1";
const PANEL_POS_KEY = "bjqx_panel_pos_v1";
const PANEL_COLLAPSED_KEY = "bjqx_panel_collapsed_v1";
const AUTO_EXAM = true;
const KNOWN_COURSE_TITLES = {
"202601016940": "2026\u5e74\u4f20\u67d3\u75c5\u9632\u6cbb\u77e5\u8bc6\u5168\u5458\u57f9\u8bad",
"202601016941": "2026\u5e74\u5317\u4eac\u5e02\u75be\u75c5\u9884\u9632\u63a7\u5236\u5c40\u75ab\u82d7\u7ba1\u7406\u6cd5\u53ca\u75ab\u82d7\u53ef\u9884\u9632\u4f20\u67d3\u75c5\u9632\u63a7\u77e5\u8bc6\u57f9\u8bad",
"202601016942": "2026\u5e74\u9996\u90fd\u536b\u751f\u5065\u5eb7\u6cd5\u5f8b\u8bb2\u5802\u548c“\u533b\u6848\u8bf4\u6cd5”\u6cd5\u5f8b\u6c99\u9f99",
"202601016943": "2026\u5e74\u7c7b\u5668\u5b98\u53ca\u5668\u5b98\u82af\u7247\u7684\u7814\u7a76\u3001\u5e94\u7528\u4e0e\u76d1\u7ba1",
"202601016944": "2026\u5e74\u7cbe\u795e\u536b\u751f\u548c\u5fc3\u7406\u5065\u5eb7\u4fc3\u8fdb\u6280\u80fd\u57f9\u8bad",
};
const state = {
enabled: false,
stopFlag: false,
panelRefreshing: false,
panelCourses: [],
chapterPreview: [],
queueVideoTotal: 0,
queueVideoDone: 0,
queueExamDone: 0,
queueExamTotal: 0,
currentCourseTitle: "",
currentChapterTitle: "",
currentTask: "\u52fe\u9009\u8bfe\u7a0b\u540e\u70b9\u300c\u5f00\u59cb\u300d",
panelHint: "",
userLabel: "",
logs: [],
cloudApiBase: "",
cloudToken: "",
cloudLease: "",
cloudTier: "unknown",
cloudExpireAt: 0,
cloudRevoked: false,
freeChapterLimit: DEFAULT_FREE_CHAPTER_LIMIT,
freeUsed: 0,
remotePanelNotice: "",
panelNoticePath: DEFAULT_PANEL_NOTICE_PATH,
proBuyUrl: PRO_BUY_URL,
cloudReady: false,
cloudLastError: "",
};
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 + BJ + "/" + String(path).replace(/^\.\//, "");
}
function qs(name, url) {
try {
return new URL(url || location.href).searchParams.get(name) || "";
} catch (_) {
return "";
}
}
function log(msg) {
const line = String(msg || "").trim();
if (!line) return;
if (/\u4e91\u7aef\u7f16\u6392|\u672c\u5730\u4ec5\u4ee3\u53d1|\u4ee3\u53d1\u8bf7\u6c42|\u4e91\u7aef\u5904\u7406/.test(line)) return;
const row = `[${new Date().toLocaleTimeString()}] ${line}`;
state.logs.push(row);
if (state.logs.length > 120) state.logs.shift();
const box = document.getElementById("bjqx-run-log");
if (box) {
box.innerHTML = state.logs
.slice(-80)
.map((x) => `
${escHtml(x)}
`)
.join("");
box.scrollTop = box.scrollHeight;
}
}
function friendlyErr(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 (/\u989d\u5ea6|\u514d\u8d39|Pro|\u6388\u6743|lease|token|\u4e91\u7aef|404|\u7f51\u7edc|\u8d85\u65f6|quota|revoked/i.test(m)) {
if (/\u7981\u7528|revoked/i.test(m)) return "\u670d\u52a1\u6682\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
return "\u670d\u52a1\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
}
if (/\u8003\u8bd5/.test(m)) return "\u8003\u8bd5\u672a\u5b8c\u6210\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
if (/\u5b66\u65f6|\u8bfe\u4ef6|\u64ad\u653e|\u7b7e\u540d|saveStudy/i.test(m)) return "\u5b66\u65f6\u63d0\u4ea4\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
return "\u64cd\u4f5c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5";
}
function clearRunLog() {
state.logs = [];
const box = document.getElementById("bjqx-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 " + url));
},
ontimeout() {
reject(new Error("\u8d85\u65f6 " + url));
},
});
});
}
async function httpGet(path, opts) {
const url = absUrl(path);
const o = opts || {};
return gmRequest("GET", url, {
accept: o.accept || "text/html,application/json,*/*",
xhr: !!o.xhr,
headers: o.headers || {},
timeout: o.timeout,
});
}
async function httpPostForm(path, body, opts) {
const url = absUrl(path);
const o = opts || {};
const payload = typeof body === "string" ? body : new URLSearchParams(body).toString();
return gmRequest("POST", url, {
body: payload,
timeout: o.timeout,
headers: Object.assign(
{
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
Accept: "text/html,application/xhtml+xml,*/*",
},
o.headers || {}
),
});
}
function parseDoc(html) {
return new DOMParser().parseFromString(String(html || ""), "text/html");
}
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) {
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 === 404) {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
return;
}
if (status < 200 || status >= 300) {
reject(new Error("\u670d\u52a1\u6821\u9a8c\u5931\u8d25"));
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(`http_${status}`));
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 applyLeasePayload(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_CHAPTER_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_CHAPTER_LIMIT;
}
if (data?.proBuyUrl) {
const buy = String(data.proBuyUrl).trim();
if (buy) state.proBuyUrl = buy;
}
state.cloudApiBase = DEFAULT_CLOUD_API_BASE;
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: getLearningUserId() || "anonymous",
site_id: "bjsqypx",
token: readCloudToken() || undefined,
lease: state.cloudLease || undefined,
});
applyLeasePayload(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 _ccq(meta) {
const data = await _crq(`${CLOUD_API_PREFIX}/study/consume`, "POST", {
lease: state.cloudLease || undefined,
learning_user_id: getLearningUserId() || "anonymous",
course_id: meta?.courseId,
ware_id: meta?.wareId,
site_id: "bjsqypx",
});
applyLeasePayload(data);
return true;
}
async function _acp() {
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 (!isCloudProTier()) {
const used = Math.max(state.freeUsed, readLocalFreeUsed());
const limit = state.freeChapterLimit || DEFAULT_FREE_CHAPTER_LIMIT;
if (used >= limit) {
log("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro");
return false;
}
}
return true;
}
function isCloudProTier() {
return !state.cloudRevoked && String(state.cloudTier || "").toLowerCase() === "pro";
}
function getLearningUserId() {
const card = (document.cookie.match(/(?:^|;\s*)cookie_ic_card=([^;]+)/) || [])[1];
return decodeURIComponent(card || 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("bjqx-cloud-tier");
if (tierEl) tierEl.textContent = formatCloudTierText();
const freeEl = document.getElementById("bjqx-cloud-free");
const freeLabel = document.getElementById("bjqx-cloud-free-label");
const limit = state.freeChapterLimit || DEFAULT_FREE_CHAPTER_LIMIT;
if (freeLabel) freeLabel.textContent = `\u514d\u8d39\u4f53\u9a8c\uff08${limit}\u4e2a\u7ae0\u8282\uff09`;
if (freeEl) {
freeEl.textContent = isCloudProTier()
? "\u4e0d\u9650\u91cf"
: `${state.freeUsed}/${limit} \u8282`;
}
const tokenInput = document.getElementById("bjqx-cloud-token");
if (tokenInput && document.activeElement !== tokenInput) {
tokenInput.value = readCloudToken() || "";
}
}
function renderPanelNotice() {
const el = document.getElementById("bjqx-ann-text");
if (el) {
el.textContent =
String(state.remotePanelNotice || PANEL_NOTICE_FALLBACK).trim() || PANEL_NOTICE_FALLBACK;
}
}
function openBuyPage() {
window.open(state.proBuyUrl || PRO_BUY_URL, "_blank", "noopener");
}
function registerCloudMenus() {
if (typeof GM_registerMenuCommand !== "function") return;
try {
GM_registerMenuCommand("\u7c98\u8d34\u6fc0\u6d3b\u7801", () => {
const t = window.prompt("\u7c98\u8d34\u6fc0\u6d3b\u7801\u540e\u786e\u5b9a", readCloudToken() || "");
if (t == null) return;
void _sct(String(t).trim());
});
GM_registerMenuCommand("\u6e05\u9664\u6fc0\u6d3b\u7801", () => {
void _sct("");
});
GM_registerMenuCommand("\u6253\u5f00\u8d2d\u4e70\u9875", () => openBuyPage());
} catch (_) {}
}
function courseTitleOf(courseId, fallback) {
const id = String(courseId || "");
return KNOWN_COURSE_TITLES[id] || fallback || `\u8bfe\u7a0b ${id}`;
}
function parseIndexCourses(html) {
const doc = parseDoc(html);
const map = new Map();
doc.querySelectorAll('a[href*="zkbd.jsp"]').forEach((a, idx) => {
const href = a.getAttribute("href") || "";
const m = href.match(/course_id=(\d+)/i);
if (!m) return;
const id = m[1];
if (map.has(id)) return;
map.set(id, {
courseId: id,
title: courseTitleOf(id, `\u8bfe\u7a0b${idx + 1}`),
href: absUrl(`${BJ}/zkbd.jsp?course_id=${id}`),
selected: false,
studyDone: 0,
studyTotal: 0,
examDone: 0,
examTotal: 0,
allDone: false,
wares: [],
statusText: "\u672a\u52a0\u8f7d",
});
});
const userM = String(html || "").match(/\u5f53\u524d\u7528\u6237\s*[\uff1a:]\s*([^<&\n]+)/);
if (userM) state.userLabel = userM[1].replace(/\s+/g, " ").trim();
return [...map.values()];
}
function parseZkbdWares(html, courseId) {
const doc = parseDoc(html);
const rows = [...doc.querySelectorAll("table.tables tr, table tr")];
const wares = [];
const seen = new Set();
for (const tr of rows) {
const link = tr.querySelector('a[href*="cc.jsp"][href*="cware_id="]');
if (!link) continue;
const href = link.getAttribute("href") || "";
const wid = (href.match(/cware_id=([^&]+)/i) || [])[1];
if (!wid || seen.has(wid)) continue;
seen.add(wid);
const tds = [...tr.querySelectorAll("td")];
const title = (tds[0] && tds[0].textContent.trim()) || `\u8bfe\u4ef6 ${wid}`;
const teacher = (tds[2] && tds[2].textContent.trim()) || "";
const statusText = (tds[3] && tds[3].textContent.replace(/\s+/g, " ").trim()) || "";
const durM = (tr.textContent || "").match(/\((\d{2}:\d{2}:\d{2})\)/);
const done = /\u5df2\u5b66\u4e60|\u5df2\u5b8c\u6210|\u5df2\u901a\u8fc7|\u901a\u8fc7/.test(statusText);
const studying = /\u5b66\u4e60\u4e2d|\u672a\u8003\u8bd5|\u5f85\u8003\u8bd5/.test(statusText);
wares.push({
courseId: String(courseId || ""),
wareId: String(wid),
title,
teacher,
duration: durM ? durM[1] : "",
statusText: statusText || (done ? "\u5df2\u5b66\u4e60" : "\u672a\u5b66\u4e60"),
done,
studying,
href: absUrl(href.includes("://") || href.startsWith("/") ? href : `${BJ}/${href}`),
});
}
return wares;
}
function summarizeCourse(course) {
const wares = course.wares || [];
course.studyTotal = wares.length;
course.studyDone = wares.filter((w) => w.done).length;
course.examTotal = wares.length;
course.examDone = wares.filter((w) => w.done).length;
course.allDone = wares.length > 0 && wares.every((w) => w.done);
if (!wares.length) course.statusText = "\u672a\u52a0\u8f7d\u7ae0\u8282";
else if (course.allDone) course.statusText = "\u5df2\u5b8c\u6210";
else if (course.studyDone > 0) course.statusText = "\u5b66\u4e60\u4e2d";
else course.statusText = "\u672a\u5b66\u4e60";
return course;
}
async function _fzk(courseId) {
const r = await httpGet(`${BJ}/zkbd.jsp?course_id=${encodeURIComponent(courseId)}`);
if (r.status >= 400) throw new Error("\u7ae0\u8282\u52a0\u8f7d\u5931\u8d25");
return parseZkbdWares(r.text, courseId);
}
function _pcm(html, courseId, wareId) {
const text = String(html || "");
const saveM = text.match(/saveStudy3\.jsp\?[^"'\\\s]+/i);
let saveStudy3 = saveM ? saveM[0].replace(/^['"]|['"]$/g, "") : "";
const slogM = text.match(/slog\.jsp\?[^"'\\\s]+/i);
const examSlog = slogM ? slogM[0].replace(/^['"]|['"]$/g, "") : "";
const userid =
(saveStudy3.match(/userid=([^&]+)/i) || examSlog.match(/user_id=([^&]+)/i) || [])[1] || "";
const paperId = (examSlog.match(/paper_id=([^&]+)/i) || [])[1] || String(wareId || "01");
const lnameM = text.match(/var\s+lname\s*=\s*["']([^"']+)["']/i);
return {
courseId: String(courseId || (saveStudy3.match(/course_id=(\d+)/i) || [])[1] || ""),
wareId: String(wareId || (saveStudy3.match(/cware_id=([^&]+)/i) || [])[1] || ""),
saveStudy3,
examSlog,
userid,
paperId,
lname: lnameM ? lnameM[1] : "",
};
}
async function _fcc2(courseId, wareId) {
const path = `${BJ}/cc.jsp?next=1&course_id=${encodeURIComponent(courseId)}&cware_id=${encodeURIComponent(wareId)}`;
const r = await httpGet(path);
if (r.status >= 400) throw new Error("\u5b66\u65f6\u63d0\u4ea4\u5931\u8d25");
return _pcm(r.text, courseId, wareId);
}
async function _es(kind, context, cfg) {
await _ecl(false);
const data = await _crq(`${CLOUD_API_PREFIX}/study/engine/start`, "POST", {
lease: state.cloudLease,
kind: String(kind || "chapter"),
context: context || {},
config: Object.assign({ auto_exam: false, free_chapter_limit: state.freeChapterLimit || DEFAULT_FREE_CHAPTER_LIMIT }, cfg || {}),
});
applyLeasePayload(data);
return data;
}
async function _ep(sessionId, event, lastResult, contextPatch) {
await _ecl(false);
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,
});
applyLeasePayload(data);
return data;
}
function _iec(cmd) {
const c = cmd || {};
const t = String(c.type || "");
const p = String(c.path || c.url || c.label || "");
return t === "browser_exam" || /exam\.jsp|examDo|slog\.jsp|examQuizFail/i.test(p);
}
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",
userid: c.userid || "",
paper_id: c.paper_id || "",
exam_deferred: true,
study_ok: true,
command: c,
};
}
if (t === "failed") {
return { terminal: true, ok: false, msg: c.message || "failed" };
}
if (_iec(c)) {
return { terminal: true, ok: true, study_ok: true, exam_deferred: true, msg: "\u5b66\u65f6\u5df2\u5b8c\u6210" };
}
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 });
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 };
}
function createExamAnswerState() {
return { byQuesId: Object.create(null), tried: Object.create(null), rotateIdx: 0 };
}
function ensureTried(st, quesId) {
if (!st.tried[quesId]) st.tried[quesId] = new Set();
return st.tried[quesId];
}
function pickAnswer(st, quesId, options) {
const opts = options && options.length ? options.map(String) : ["A", "B", "C", "D"];
const locked = st.byQuesId[quesId];
if (locked && opts.includes(String(locked))) return String(locked);
const tried = ensureTried(st, quesId);
for (const o of opts) if (!tried.has(o)) return o;
return opts[st.rotateIdx++ % opts.length] || "A";
}
function attrUnquoted(tag, name) {
const n = String(name || "");
const m =
tag.match(new RegExp(n + '\\s*=\\s*"([^"]*)"', "i")) ||
tag.match(new RegExp(n + "\\s*=\\s*'([^']*)'", "i")) ||
tag.match(new RegExp(n + "\\s*=\\s*([^\\s>]+)", "i"));
return m ? m[1] : "";
}
function _pef(html) {
const text = String(html || "");
const formM = text.match(/