// ==UserScript==
// @name 【效率版】河南专技在线·高辉教育科技平台学习助手
// @namespace https://card.wlxy.live/details/BAC51F30
// @version 1.0
// @description 河南高辉教育科技举办(hnzj.ghlearning.com)河南专技继续教育:公需课/专业课勾选自动排队看课,免费3视频章节,Pro激活30天不限。
// @antifeature payment 免费体验3个视频章节,升级Pro不限
// @antifeature membership 需云端授权
// @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png
// @author 柠檬真酸
// @match https://hnzj.user.ghlearning.com/*
// @match https://hnzj.ghlearning.com/*
// @match *://www.hnzjgl.gov.cn/*
// @match *://hnzjgl.gov.cn/*
// @match *://jxjy.henu.edu.cn/*
// @match *://www.jxjy.henu.edu.cn/*
// @match *://hnpihn.newzhihui.cn/*
// @match *://www.hnpihn.newzhihui.cn/*
// @match *://zdkj.v.zzu.edu.cn/*
// @match *://zyjs.lypt.edu.cn/*
// @match *://www.zyjs.lypt.edu.cn/*
// @match *://www.jxjyedu.org.cn/*
// @match *://jxjyedu.org.cn/*
// @match *://nypx.jxjyedu.org.cn/*
// @match *://www.ly.fnhzj.com/*
// @match *://ly.fnhzj.com/*
// @match *://www.huayuzj.com/*
// @match *://huayuzj.com/*
// @match *://testplugservice-v3.ylxue.net/*
// @match *://plugservice-v3.ylxue.net/*
// @noframes
// @connect hnzj.user.ghlearning.com
// @connect hnzj.ghlearning.com
// @connect ghlearning.com
// @connect oa26.ahzsksw.cn
// @connect card.wlxy.live
// @connect huaweicloudobs.ahjxjy.cn
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// @tag 河南高辉
// @tag 河南专技
// @tag 继续教育
// @tag ghlearning
// @license All Rights Reserved
// ==/UserScript==
(function () {
"use strict";
const SCRIPT_VERSION = "1.0";
try {
if (window.top !== window.self) return;
} catch (_) {
return;
}
const SITE_HOST = String(location.hostname || "")
.replace(/^www\./i, "")
.toLowerCase();
const GHLEARNING_SUPPORTED_HOSTS = new Set([
"hnzj.ghlearning.com",
"hnzj.user.ghlearning.com",
]);
const HN_REDIRECT_BOOT_KEY = "__HN_ZJ_PORTAL_REDIRECT_V1__";
const HN_SCRIPT_JXJY_URL = "https://scriptcat.org/zh-CN/script-show-page/7693";
const HN_SCRIPT_LYLG_URL = "https://scriptcat.org/zh-CN/script-show-page/7691";
const HN_SCRIPT_GHLEARNING_URL = "https://card.wlxy.live/details/BAC51F30";
const HN_ADAPTED_PLATFORMS = [
{
name: "\u6cb3\u5357\u7701\u7ee7\u7eed\u6559\u80b2\u5b66\u4f1a",
url: "https://www.jxjyedu.org.cn/",
modeLabel: "\u6548\u7387\u5feb\u901f\u5b8c\u6210",
modeClass: "hn-zj-badge-fast",
scriptUrl: HN_SCRIPT_JXJY_URL,
scriptLabel: "\u5b89\u88c5\u5b66\u4e60\u52a9\u624b\uff08ScriptCat #7693\uff09",
},
{
name: "\u5357\u9633\u7406\u5de5\u5b66\u9662",
url: "https://nypx.jxjyedu.org.cn/",
modeLabel: "\u6548\u7387\u5feb\u901f\u5b8c\u6210",
modeClass: "hn-zj-badge-fast",
scriptUrl: HN_SCRIPT_JXJY_URL,
scriptLabel: "\u5b89\u88c5\u5b66\u4e60\u52a9\u624b\uff08ScriptCat #7693\uff09",
},
{
name: "\u6d1b\u9633\u7406\u5de5\u5b66\u9662",
url: "https://www.ly.fnhzj.com/",
modeLabel: "1:1 \u591a\u8bfe\u540c\u5237",
modeClass: "hn-zj-badge-multi",
scriptUrl: HN_SCRIPT_LYLG_URL,
scriptLabel: "\u5b89\u88c5\u5b66\u4e60\u52a9\u624b\uff08ScriptCat #7691\uff09",
},
{
name: "\u4e2d\u539f\u5de5\u5b66\u9662",
url: "https://www.huayuzj.com/",
modeLabel: "1:1 \u591a\u8bfe\u540c\u5237",
modeClass: "hn-zj-badge-multi",
scriptUrl: HN_SCRIPT_LYLG_URL,
scriptLabel: "\u5b89\u88c5\u5b66\u4e60\u52a9\u624b\uff08ScriptCat #7691\uff09",
},
{
name: "\u6cb3\u5357\u9ad8\u8f89\u6559\u80b2\u79d1\u6280\u6709\u9650\u516c\u53f8",
url: "https://hnzj.ghlearning.com/",
modeLabel: "\u81ea\u52a8\u770b\u8bfe",
modeClass: "hn-zj-badge-fast",
scriptUrl: HN_SCRIPT_GHLEARNING_URL,
scriptLabel: "\u5b89\u88c5\u5b66\u4e60\u52a9\u624b\uff08\u6cb3\u5357\u9ad8\u8f89\u6559\u80b2\uff09",
},
];
const REDIRECT_SOURCE_LABELS = {
"hnzjgl.gov.cn": "\u6cb3\u5357\u4e13\u6280\u7ba1\u7406\u5e73\u53f0",
"jxjy.henu.edu.cn": "\u6cb3\u5357\u5927\u5b66",
"hnpihn.newzhihui.cn": "\u6cb3\u5357\u5de5\u4e1a\u804c\u4e1a\u6280\u672f\u5927\u5b66",
"zdkj.v.zzu.edu.cn": "\u90d1\u5dde\u5927\u5b66",
"zyjs.lypt.edu.cn": "\u6d1b\u9633\u804c\u4e1a\u6280\u672f\u5b66\u9662",
"jxjyedu.org.cn": "\u6cb3\u5357\u7701\u7ee7\u7eed\u6559\u80b2\u5b66\u4f1a",
"nypx.jxjyedu.org.cn": "\u5357\u9633\u7406\u5de5\u5b66\u9662",
"ly.fnhzj.com": "\u6d1b\u9633\u7406\u5de5\u5b66\u9662",
"huayuzj.com": "\u4e2d\u539f\u5de5\u5b66\u9662",
"testplugservice-v3.ylxue.net": "ylxue \u5b66\u4e60\u63d2\u4ef6",
"plugservice-v3.ylxue.net": "ylxue \u5b66\u4e60\u63d2\u4ef6",
};
function resolveHostRole(host) {
if (GHLEARNING_SUPPORTED_HOSTS.has(host)) return "ghlearning";
return "redirect";
}
function injectHnRedirectStyles() {
if (document.getElementById("hn-zj-redirect-style")) return;
const st = document.createElement("style");
st.id = "hn-zj-redirect-style";
st.textContent = `
#hn-zj-redirect-panel{position:fixed;right:20px;top:80px;z-index:999999;width:390px;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;}
#hn-zj-redirect-header{padding:10px 12px;background:linear-gradient(180deg,#f8ecd5,#f4e8cf);border-bottom:1px solid #e5dbc6;cursor:move;user-select:none;display:flex;align-items:center;gap:9px;}
#hn-zj-redirect-logo{width:28px;height:28px;border-radius:8px;object-fit:cover;background:#fff;border:1px solid rgba(148,163,184,.45);}
#hn-zj-redirect-title{font-size:13px;font-weight:900;color:#9a3412;line-height:1.3;flex:1;}
#hn-zj-redirect-body{padding:10px 12px 12px;}
.hn-zj-redirect-lead{font-size:12px;color:#475569;line-height:1.55;margin:0 0 8px;}
.hn-zj-redirect-lead strong{color:#9a3412;}
.hn-zj-redirect-list{display:flex;flex-direction:column;gap:7px;margin:0;padding:0;list-style:none;}
.hn-zj-redirect-item{background:#fff;border:1px solid #dbe4f0;border-radius:11px;padding:8px 9px;}
.hn-zj-redirect-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px;}
.hn-zj-redirect-link{font-size:13px;font-weight:800;color:#1d4ed8;text-decoration:none;}
.hn-zj-redirect-link:hover{text-decoration:underline;}
.hn-zj-redirect-item-foot{margin-top:5px;}
.hn-zj-redirect-script{font-size:11px;font-weight:700;color:#7c2d12;text-decoration:none;}
.hn-zj-redirect-script:hover{text-decoration:underline;color:#9a3412;}
.hn-zj-badge{font-size:10px;font-weight:800;padding:2px 7px;border-radius:999px;white-space:nowrap;flex:0 0 auto;}
.hn-zj-badge-fast{color:#0f766e;background:#ecfdf5;border:1px solid #99f6e4;}
.hn-zj-badge-multi{color:#1d4ed8;background:#eff6ff;border:1px solid #bfdbfe;}
.hn-zj-redirect-hint{margin:9px 0 0;font-size:11px;color:#64748b;line-height:1.45;background:linear-gradient(180deg,#fffdf5,#fff7e6);border:1px solid #fcd34d;border-radius:10px;padding:7px 9px;}
`;
document.head.appendChild(st);
}
function bindHnRedirectDrag(panel) {
const header = panel.querySelector("#hn-zj-redirect-header");
if (!header) return;
let sx = 0,
sy = 0,
ox = 0,
oy = 0,
dragging = false;
header.addEventListener("mousedown", (e) => {
dragging = true;
sx = e.clientX;
sy = e.clientY;
const rect = panel.getBoundingClientRect();
ox = rect.left;
oy = rect.top;
e.preventDefault();
});
window.addEventListener("mousemove", (e) => {
if (!dragging) return;
panel.style.left = `${ox + e.clientX - sx}px`;
panel.style.top = `${oy + e.clientY - sy}px`;
panel.style.right = "auto";
});
window.addEventListener("mouseup", () => {
dragging = false;
});
}
function _hnEsc(s) {
return String(s || "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function renderHnRedirectPlatformItems() {
return HN_ADAPTED_PLATFORMS.map(
(p) =>
`
`
).join("");
}
function mountHnRedirectPanel() {
try {
const root =
(typeof unsafeWindow !== "undefined" && unsafeWindow.top) ||
window.top ||
window;
if (root[HN_REDIRECT_BOOT_KEY]) return;
root[HN_REDIRECT_BOOT_KEY] = SCRIPT_VERSION;
} catch (_) {
if (window[HN_REDIRECT_BOOT_KEY]) return;
window[HN_REDIRECT_BOOT_KEY] = SCRIPT_VERSION;
}
injectHnRedirectStyles();
document.querySelectorAll("#hn-zj-redirect-panel").forEach((el) => el.remove());
const sourceLabel = REDIRECT_SOURCE_LABELS[SITE_HOST] || "\u5f53\u524d\u5e73\u53f0";
const panel = document.createElement("div");
panel.id = "hn-zj-redirect-panel";
panel.innerHTML = `
\u60a8\u5f53\u524d\u8bbf\u95ee\u7684\u662f ${_hnEsc(sourceLabel)}\uff0c\u6682\u672a\u63a5\u5165\u81ea\u52a8\u5b66\u4e60\u3002
\u5efa\u8bae\u4f18\u5148\u524d\u5f80\u4ee5\u4e0b 5 \u4e2a\u5df2\u9002\u914d\u5e73\u53f0 \u5b66\u4e60\uff1a
${renderHnRedirectPlatformItems()}
\u524d\u5f80\u5e73\u53f0\u524d\u8bf7\u5148\u5b89\u88c5\u5bf9\u5e94 ScriptCat \u811a\u672c\u3002\u6cb3\u5357\u7701\u7ee7\u7eed\u6559\u80b2\u5b66\u4f1a\u3001\u5357\u9633\u7406\u5de5\u5b66\u9662\u652f\u6301\u6548\u7387\u5feb\u901f\u5b8c\u6210\uff08#7693\uff09\uff1b\u6d1b\u9633\u7406\u5de5\u5b66\u9662\u3001\u4e2d\u539f\u5de5\u5b66\u9662\u652f\u6301 1:1 \u65f6\u957f\u591a\u8bfe\u540c\u65f6\u5b66\u4e60\uff08#7691\uff09\uff1b\u6cb3\u5357\u9ad8\u8f89\u6559\u80b2\u79d1\u6280\u652f\u6301\u516c\u9700\u8bfe/\u4e13\u4e1a\u8bfe\u81ea\u52a8\u770b\u8bfe\uff08\u65e0\u8003\u8bd5\uff09\u3002
`;
document.body.appendChild(panel);
bindHnRedirectDrag(panel);
}
function runHnRedirectPanelOnly() {
const show = () => mountHnRedirectPanel();
if (document.body) show();
else document.addEventListener("DOMContentLoaded", show, { once: true });
}
if (resolveHostRole(SITE_HOST) !== "ghlearning") {
runHnRedirectPanelOnly();
return;
}
const PAGE = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
const PANEL_LOGO_URL =
"https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png";
const FALLBACK_ORIGIN = "https://hnzj.user.ghlearning.com";
const API_PREFIX = "/train";
const PATH_ACCOUNT_INFO = `${API_PREFIX}/login/get-account-info.gson`;
const PATH_STUDY_LIST = `${API_PREFIX}/cms/study/find-my-study-info.gson`;
const PATH_STUDY_YEARS = `${API_PREFIX}/cms/study/find-my-study-info-yearlist.gson`;
const PATH_CLASS_YEARS = `${API_PREFIX}/cms/study/get-class-year-info.gson`;
const PATH_MY_CLASS = `${API_PREFIX}/class/my-class.gson`;
const PATH_MY_CLASS_BOOKS = `${API_PREFIX}/class/my-class-books.gson`;
const PATH_VIDEO_SV = `${API_PREFIX}/cms/my-video/sv.gson`;
const PATH_VIDEO_CV = `${API_PREFIX}/cms/my-video/cv.gson`;
const PATH_UPDATE_LAST_VIDEO = `${API_PREFIX}/cms/my-video/update-last-video.gson`;
const PATH_EXAM_LIST = `${API_PREFIX}/cms/paper/find-paper-list.gson`;
const PATH_EXAM_DETAIL = `${API_PREFIX}/cms/paper/get-paper-detail.gson`;
const PATH_EXAM_CHECK_STATUS = `${API_PREFIX}/cms/paper/check-my-exam-status.gson`;
const PATH_EXAM_SAVE_RECORD = `${API_PREFIX}/cms/paper/save-my-exam-record.gson`;
const PATH_EXAM_START = `${API_PREFIX}/cms/paper/start-do-paper-or-test.gson`;
const PATH_EXAM_SUBMIT = `${API_PREFIX}/cms/paper/submit-paper.gson`;
const CMS_ACCOUNT_KEY = "cmsAccountInfo";
const PINNED_CLOUD_HOST = "oa26.ahzsksw.cn";
const DEFAULT_CLOUD_API_BASE = "https://oa26.ahzsksw.cn";
const CLOUD_TOKEN_KEY = "hnzj_cloud_token_v1";
const CLOUD_LEASE_CACHE_KEY = "hnzj_cloud_lease_cache_v1";
const CLOUD_API_BASE_KEY = "hnzj_cloud_api_base_v1";
const DEFAULT_FREE_CHAPTER_LIMIT = 3;
const PRO_BUY_URL = "https://card.wlxy.live/details/BAC51F30";
const PRO_DAYS = 30;
const PANEL_NOTICE_FALLBACK = "\u6cb3\u5357\u4e13\u6280\u5728\u7ebf·\u9ad8\u8f89\u6559\u80b2\u79d1\u6280\u5e73\u53f0\u5b66\u4e60\u52a9\u624b";
const DEFAULT_PANEL_NOTICE_PATH = "/api/hnzj/panel-notice";
const QUEUE_KEY = "hnzj_project_queue_v1";
const PANEL_POS_KEY = "hnzj_panel_pos_v1";
const PANEL_COLLAPSED_KEY = "hnzj_panel_collapsed_v1";
const AUTH_CACHE_KEY = "hnzj_auth_cache_v1";
const CLASS_TYPE_KEY = "hnzj_class_type_v1";
const CLASS_TYPE_PUBLIC = 0;
const CLASS_TYPE_MAJOR = 2;
const EFFICIENCY_JUMP_RATIO = 0.99;
const CV_INTERVAL_MS = 120000;
const CV_STEP_SECONDS = 120;
const SV_BEFORE_CV_MS = 5000;
const WATCH_MAX_ROUNDS = 200;
const ENGINE_TICK_MS = 1200;
const EXAM_PASS_RETRY = 2;
const CV_INTERVAL_MINUTES = CV_INTERVAL_MS / 60000;
const state = {
enabled: false,
engineRunning: false,
panelProjects: [],
panelRefreshing: false,
chapterPreview: [],
chapterPreviewLoading: false,
queueWareTotal: 0,
queueWareDone: 0,
userProfile: null,
panelHint: "",
lastUserAction: "",
runLogs: [],
studyClassType: normalizeStudyClassType(
(typeof unsafeWindow !== "undefined" ? unsafeWindow : window).localStorage.getItem(CLASS_TYPE_KEY)
),
cloudToken: String(
(typeof unsafeWindow !== "undefined" ? unsafeWindow : window).localStorage.getItem(CLOUD_TOKEN_KEY) || ""
).trim(),
cloudLease: "",
cloudLeaseExp: 0,
cloudTier: "unknown",
cloudProExpireAt: 0,
cloudRevoked: false,
freeChapterLimit: DEFAULT_FREE_CHAPTER_LIMIT,
freeUsedChapters: 0,
proBuyUrl: PRO_BUY_URL,
cloudApiBase: "",
panelNoticePath: DEFAULT_PANEL_NOTICE_PATH,
remotePanelNotice: "",
_lastProErr: "",
_bootLoaded: false,
_capturedLists: { learning: [], completed: [] },
_accountId: "",
};
state.cloudApiBase = resolvePinnedCloudApiBase();
const PLATFORM_HAS_EXAM = false;
function trace(msg) {
try {
console.log(`[\u6cb3\u5357\u9ad8\u8f89\u52a9\u624b] ${String(msg ?? "")}`);
} catch (_) {}
}
function normalizeStudyClassType(v) {
const n = Number(v);
return n === CLASS_TYPE_PUBLIC ? CLASS_TYPE_PUBLIC : CLASS_TYPE_MAJOR;
}
function getStudyClassType() {
return normalizeStudyClassType(state.studyClassType);
}
function setStudyClassType(classType) {
const ct = normalizeStudyClassType(classType);
state.studyClassType = ct;
try {
pageStorage().setItem(CLASS_TYPE_KEY, String(ct));
} catch (_) {}
}
function studyClassTypeLabel(classType) {
return Number(classType) === CLASS_TYPE_MAJOR ? "\u4e13\u4e1a\u8bfe" : "\u516c\u9700\u8bfe";
}
function studyClassTypeShortLabel(classType) {
return Number(classType) === CLASS_TYPE_MAJOR ? "\u4e13\u4e1a" : "\u516c\u9700";
}
function formatTsDate(ms) {
const n = Number(ms);
if (!n || !Number.isFinite(n)) return "";
const d = new Date(n);
if (Number.isNaN(d.getTime())) return "";
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function formatProgressDisplay(progress) {
const v = Number(progress);
if (!Number.isFinite(v) || v <= 0) return "0";
if (v >= 100) return "\u5df2\u5b8c\u6210";
return String(Math.round(v * 10) / 10);
}
function isProjectExpired(p) {
if (!p) return false;
if (Number(p.courseStatus) === -1) return true;
const lim = Number(p.limitDataTime || 0);
if (lim > 0 && Date.now() > lim) return true;
const last = String(p.lastTime || "").trim();
if (last) {
const t = new Date(last.replace(/-/g, "/")).getTime();
if (t > 0 && Date.now() > t) return true;
}
return false;
}
function formatCourseDeadline(p) {
if (!p) return "";
const start = String(p.enrollTime || "").trim() || formatTsDate(p.startTime);
const endRaw = String(p.lastTime || "").trim();
const end = endRaw ? endRaw.slice(0, 10) : formatTsDate(p.limitDataTime);
if (start && end) return `\u5b66\u4e60\u622a\u6b62\uff1a${start}~${end}`;
if (end) return `\u5b66\u4e60\u622a\u6b62\uff1a${end}`;
return "";
}
function projectActionLabel(p) {
if (isProjectExpired(p)) return "\u5df2\u8fc7\u671f";
if (isProjectFullyFinished(p)) return "\u5df2\u5b8c\u6210";
return "\u7ee7\u7eed\u5b66\u4e60";
}
function renderProgressRing(p) {
const done = isProjectFullyFinished(p);
if (done) {
return `\u5df2\u5b8c\u6210
`;
}
const pct = Math.min(100, Math.max(0, Number(p.studyProgress) || 0));
const display = formatProgressDisplay(pct);
const r = 18;
const c = 2 * Math.PI * r;
const offset = c * (1 - pct / 100);
return `
${escHtml(display)}
`;
}
function visiblePanelProjects() {
const ct = getStudyClassType();
return (state.panelProjects || []).filter((p) => {
if (isProjectExpired(p)) return false;
if (p.classType == null) return true;
return Number(p.classType) === ct;
});
}
function pruneHiddenQueue() {
const visible = new Set(visiblePanelProjects().map((p) => String(p.projectId)));
const q = loadQueue().map(String).filter((id) => visible.has(id));
if (q.length !== loadQueue().length) saveQueue(q);
}
function escHtml(s) {
return String(s ?? "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function pageStorage() {
try {
return PAGE.localStorage;
} catch (_) {
return localStorage;
}
}
function readJson(key, fallback) {
try {
const raw = pageStorage().getItem(key);
return raw ? JSON.parse(raw) : fallback;
} catch (_) {
return fallback;
}
}
function writeJson(key, val) {
pageStorage().setItem(key, JSON.stringify(val));
}
function detectLogKind(msg, kind) {
if (kind) return String(kind);
const s = String(msg || "");
if (/^\u5f00\u59cb\uff1a/.test(s)) return "start";
if (/^\u5b66\u4e60\uff1a/.test(s)) return "study";
if (/^\u5b8c\u6210\uff1a/.test(s)) return "done";
if (/^\u8003\u8bd5\uff1a/.test(s) || /\u8003\u8bd5.*\u5df2\u63d0\u4ea4/.test(s)) return "exam";
if (/^\u5df2\u505c\u6b62/.test(s)) return "stop";
if (/\u6bcf.*\u5206\u949f|\u8010\u5fc3\u7b49\u5f85|\u4ecd\u5728\u8fd0\u884c/.test(s)) return "hint";
if (/\u5931\u8d25|\u9519\u8bef|\u4e0a\u9650|\u8df3\u8fc7|\u65e0\u89c6\u9891\u6e90|\u7535\u5b50\u4e66|\u8bf7\u5148\u52fe\u9009/.test(s)) return "warn";
return "info";
}
function _rrl() {
const el = document.getElementById("hnzj-run-log");
if (!el) return;
if (!state.runLogs.length) {
el.innerHTML = `\u6682\u65e0\u65e5\u5fd7
`;
return;
}
el.innerHTML = state.runLogs
.map((item) => {
const row = typeof item === "string" ? { time: "", msg: item, kind: detectLogKind(item) } : item;
const time = row.time ? `${escHtml(row.time)}` : "";
const kind = escHtml(row.kind || "info");
return `${time}${escHtml(row.msg || "")}
`;
})
.join("");
}
function log(msg, kind) {
const text = String(msg || "");
const row = {
time: new Date().toLocaleTimeString(),
msg: text,
kind: detectLogKind(text, kind),
};
state.runLogs.unshift(row);
if (state.runLogs.length > 200) state.runLogs.length = 200;
_rrl();
trace(text);
}
function _lwh() {
if (state._studyWaitHintShown) return;
state._studyWaitHintShown = true;
log(`\u770b\u8bfe\u8fdb\u5ea6\u7ea6\u6bcf ${CV_INTERVAL_MINUTES} \u5206\u949f\u66f4\u65b0\u4e00\u6b21\uff0c\u7b49\u5f85\u671f\u95f4\u811a\u672c\u4ecd\u5728\u8fd0\u884c`, "hint");
}
function resolvePinnedCloudApiBase() {
try {
const raw = String(pageStorage().getItem(CLOUD_API_BASE_KEY) || DEFAULT_CLOUD_API_BASE)
.trim()
.replace(/\/+$/, "");
if (!raw || !new RegExp(PINNED_CLOUD_HOST.replace(/\./g, "\\."), "i").test(raw)) {
try {
pageStorage().setItem(CLOUD_API_BASE_KEY, DEFAULT_CLOUD_API_BASE);
} catch (_) {}
return DEFAULT_CLOUD_API_BASE;
}
return raw;
} catch (_) {
return DEFAULT_CLOUD_API_BASE;
}
}
function getCloudApiBase() {
return String(state.cloudApiBase || DEFAULT_CLOUD_API_BASE).trim().replace(/\/+$/, "");
}
function getPanelNoticeText() {
return String(state.remotePanelNotice || PANEL_NOTICE_FALLBACK).trim() || PANEL_NOTICE_FALLBACK;
}
function renderPanelNotice() {
const el = document.getElementById("hnzj-ann-text");
if (el) el.textContent = getPanelNoticeText();
}
function getApiOrigin() {
try {
const host = String(location.hostname || "").toLowerCase();
if (host === "hnzj.user.ghlearning.com") return location.origin;
if (host === "hnzj.ghlearning.com" || /\.ghlearning\.com$/i.test(host)) {
return "https://hnzj.user.ghlearning.com";
}
} catch (_) {}
return FALLBACK_ORIGIN;
}
function uuidv4() {
try {
if (PAGE.crypto && typeof PAGE.crypto.randomUUID === "function") return PAGE.crypto.randomUUID();
} catch (_) {}
const s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
return s;
}
function toQuery(params) {
const parts = [];
Object.keys(params || {}).forEach((k) => {
const v = params[k];
if (v === undefined || v === null) return;
parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
});
return parts.join("&");
}
function toFormBody(params) {
return toQuery(params);
}
function readCmsAccountInfo() {
try {
const raw = pageStorage().getItem(CMS_ACCOUNT_KEY);
if (!raw || raw === "null") return null;
const obj = JSON.parse(raw);
return obj && typeof obj === "object" ? obj : null;
} catch (_) {
return null;
}
}
function rememberAccountId(id) {
const aid = String(id || "").trim();
if (!aid || aid.length < 8) return;
if (state._accountId === aid && PAGE.__hnzjAccountId === aid) return;
state._accountId = aid;
PAGE.__hnzjAccountId = aid;
writeAuthCache({ accountId: aid });
}
function getAccountId() {
if (state._accountId) return state._accountId;
if (PAGE.__hnzjAccountId) {
state._accountId = String(PAGE.__hnzjAccountId);
return state._accountId;
}
const cms = readCmsAccountInfo();
if (cms?.accountId) {
rememberAccountId(cms.accountId);
return state._accountId;
}
const cache = readAuthCache();
if (cache.accountId) {
state._accountId = String(cache.accountId);
return state._accountId;
}
return "";
}
function readAuthCache() {
return readJson(AUTH_CACHE_KEY, {}) || {};
}
function writeAuthCache(partial) {
const prev = readAuthCache();
try {
pageStorage().setItem(AUTH_CACHE_KEY, JSON.stringify(Object.assign({}, prev, partial || {})));
} catch (_) {}
}
function hasPlatformAuth() {
return !!getAccountId() || !!readCmsAccountInfo();
}
function unwrapPlatformJson(json) {
if (!json || typeof json !== "object") {
return { code: -1, msg: "empty", data: null, raw: json };
}
if (json.respCode != null && json.attribute !== undefined) {
return {
code: Number(json.respCode),
msg: String(json.respDesc || json.msg || ""),
data: json.attribute,
raw: json,
};
}
return {
code: Number(json.code != null ? json.code : json.respCode),
msg: String(json.msg || json.respDesc || ""),
data: json.data !== undefined ? json.data : json.attribute,
raw: json,
};
}
function pickList(data) {
if (Array.isArray(data)) return data;
if (!data || typeof data !== "object") return [];
if (Array.isArray(data.list)) return data.list;
if (data.listPage && Array.isArray(data.listPage.list)) return data.listPage.list;
if (data.data && Array.isArray(data.data.list)) return data.data.list;
if (Array.isArray(data.myClassCourseRPList)) return data.myClassCourseRPList;
if (Array.isArray(data.data)) return data.data;
if (Array.isArray(data.records)) return data.records;
if (Array.isArray(data.content)) return data.content;
if (Array.isArray(data.rows)) return data.rows;
if (Array.isArray(data.courseData)) return data.courseData;
if (data.data && typeof data.data === "object") return pickList(data.data);
return [];
}
function parseClassTypeFromStudyUrl(url) {
try {
const m = String(url || "").match(/[?&]classType=(\d+)/i);
if (m) return normalizeStudyClassType(Number(m[1]));
} catch (_) {}
return null;
}
function ingestStudyListPayload(url, json) {
try {
if (!/find-my-study-info\.gson/i.test(url)) return;
const un = unwrapPlatformJson(json);
if (Number(un.code) !== 200) return;
const rows = pickList(un.data);
if (!rows.length) return;
const ctHint = parseClassTypeFromStudyUrl(url);
const learning = [];
const completed = [];
for (const row of rows) {
const p = mapProjectRow(row, ctHint);
if (p.listSource === "completed") completed.push(p);
else learning.push(p);
}
const bag = state._capturedLists || { learning: [], completed: [] };
const merge = (src, arr) => {
const byId = new Map((bag[src] || []).map((x) => [x.projectId, x]));
for (const p of arr) byId.set(p.projectId, p);
bag[src] = Array.from(byId.values());
};
merge("learning", learning);
merge("completed", completed);
state._capturedLists = bag;
if (document.getElementById("hnzj-auto-panel") && !state.panelRefreshing) {
mergeCapturedIntoPanel(false);
}
} catch (_) {}
}
function mergeCapturedIntoPanel(preferApi) {
const bag = state._capturedLists || { learning: [], completed: [] };
if (!bag.learning.length && !bag.completed.length) return false;
const byId = new Map();
for (const p of bag.learning || []) byId.set(p.projectId, p);
for (const p of bag.completed || []) byId.set(p.projectId, p);
const merged = Array.from(byId.values());
for (const p of merged) syncProjectFinishedFlags(p);
if (!preferApi || !state.panelProjects.length) {
state.panelProjects = merged;
_upn();
}
return state.panelProjects.length > 0;
}
function installAuthHooks() {
if (PAGE.__hnzjNetHooked) return;
PAGE.__hnzjNetHooked = true;
try {
const XHR = PAGE.XMLHttpRequest;
const open = XHR.prototype.open;
const send = XHR.prototype.send;
const setHeader = XHR.prototype.setRequestHeader;
XHR.prototype.open = function (method, url) {
try {
this.__hnzjUrl = String(url || "");
this.__hnzjMethod = String(method || "");
} catch (_) {}
return open.apply(this, arguments);
};
XHR.prototype.setRequestHeader = function (k, v) {
try {
if (/^accountid$/i.test(String(k || ""))) rememberAccountId(v);
} catch (_) {}
return setHeader.apply(this, arguments);
};
XHR.prototype.send = function () {
try {
this.addEventListener("load", function () {
try {
const url = String(this.__hnzjUrl || "");
const text = String(this.responseText || "");
if (!text || text.length > 5e6) return;
const json = JSON.parse(text);
if (/get-account-info\.gson/i.test(url)) {
const un = unwrapPlatformJson(json);
const attr = un.data || null;
const info = attr?.cmsAccountInfo || attr?.data || attr || null;
if (info?.accountId) rememberAccountId(info.accountId);
if (info) {
state.userProfile = normalizeUserProfile(info);
}
}
if (/yearlist|get-class-year-info/i.test(url)) {
const years = extractYears(unwrapPlatformJson(json).data);
if (years.length) state.yearOptions = years;
}
ingestStudyListPayload(url, json);
} catch (_) {}
});
} catch (_) {}
return send.apply(this, arguments);
};
} catch (_) {}
try {
const rawFetch = PAGE.fetch;
if (typeof rawFetch === "function" && !rawFetch.__hnzjPatched) {
const wrapped = function (input, init) {
try {
const h = init && init.headers;
let aid = "";
if (h && typeof h.get === "function") aid = h.get("accountId") || h.get("AccountId") || "";
else if (h && typeof h === "object") aid = h.accountId || h.AccountId || "";
if (aid) rememberAccountId(aid);
} catch (_) {}
const url = typeof input === "string" ? input : input && input.url;
return rawFetch.apply(PAGE, arguments).then((res) => {
try {
const u = String(url || "");
if (/find-my-study-info\.gson|get-account-info\.gson/i.test(u)) {
res
.clone()
.json()
.then((json) => {
if (/get-account-info/i.test(u)) {
const un = unwrapPlatformJson(json);
const attr = un.data || null;
const info = attr?.cmsAccountInfo || attr?.data || attr || null;
if (info?.accountId) rememberAccountId(info.accountId);
}
ingestStudyListPayload(u, json);
})
.catch(() => {});
}
} catch (_) {}
return res;
});
};
wrapped.__hnzjPatched = true;
PAGE.fetch = wrapped;
}
} catch (_) {}
}
function apiUrl(path) {
if (/^https?:\/\//i.test(path)) return path;
return getApiOrigin() + (path.startsWith("/") ? path : `/${path}`);
}
async function _prq(method, path, body, opt) {
const accountId = getAccountId();
if (!accountId) throw new Error("\u672a\u83b7\u53d6 accountId\uff0c\u8bf7\u5148\u767b\u5f55\u5b98\u7f51\u540e\u70b9\u300c\u5237\u65b0\u300d");
const url = apiUrl(path);
const headers = Object.assign(
{
Accept: "application/json, text/plain, */*",
accountId: accountId,
},
(opt && opt.headers) || {}
);
let payload = undefined;
const m = String(method || "GET").toUpperCase();
if (body !== undefined && body !== null && m !== "GET") {
if (opt && opt.form) {
headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8";
payload = typeof body === "string" ? body : toFormBody(body);
} else {
headers["Content-Type"] = "application/json;charset=UTF-8";
payload = typeof body === "string" ? body : JSON.stringify(body);
}
}
const parseOk = (json, status) => {
if (status < 200 || status >= 300) {
throw new Error(json?.msg || json?.respDesc || json?.message || `HTTP ${status}`);
}
const un = unwrapPlatformJson(json);
if (Number(un.code) !== 200) {
throw new Error(un.msg || `code_${un.code}`);
}
return un;
};
try {
const pageFetch = PAGE.fetch || fetch;
const res = await pageFetch.call(PAGE, url, {
method: m,
headers,
body: payload,
credentials: "include",
});
const json = await res.json().catch(() => ({}));
return parseOk(json, res.status);
} catch (e) {
const msg = String((e && e.message) || e || "");
if (!/Failed to fetch|NetworkError|CORS/i.test(msg) && /\u672a\u83b7\u53d6|HTTP |code_|accountId/i.test(msg)) {
throw e;
}
trace(`\u9875\u9762 fetch \u5931\u8d25\uff0c\u6539 GM\uff1a${msg}`);
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(e);
return;
}
GM_xmlhttpRequest({
method: m,
url,
headers,
data: payload,
anonymous: false,
onload(resp) {
let json = {};
try {
json = JSON.parse(resp.responseText || "{}");
} catch (_) {}
try {
resolve(parseOk(json, resp.status));
} catch (err) {
reject(err);
}
},
onerror() {
reject(new Error("GM \u7f51\u7edc\u9519\u8bef"));
},
});
});
}
}
function apiGet(path) {
return _prq("GET", path, null);
}
function apiPostForm(path, body) {
return _prq("POST", path, body, { form: true });
}
function apiPostJson(path, body) {
return _prq("POST", path, body == null ? {} : body, { form: false });
}
function normalizeUserProfile(info) {
if (!info || typeof info !== "object") return null;
return {
accountId: String(info.accountId || ""),
name: String(info.realName || info.name || info.nickName || info.userName || "").trim(),
loginId: String(info.loginName || info.userName || info.account || info.mobile || "").trim(),
phone: String(info.mobile || info.phone || "").trim(),
raw: info,
};
}
function extractYears(data) {
const arr = pickList(data);
const years = [];
for (const item of arr) {
if (typeof item === "number" || /^\d{4}$/.test(String(item))) {
years.push(Number(item));
} else if (item && typeof item === "object") {
const y = Number(item.year || item.studyYear || item.classYear || item.value || 0);
if (y > 2000) years.push(y);
}
}
return Array.from(new Set(years.filter((n) => n > 2000))).sort((a, b) => b - a);
}
function getLearningUserId() {
return String(
getAccountId() ||
state.userProfile?.accountId ||
readAuthCache().accountId ||
""
).trim();
}
function formatCloudTierText(tier) {
if (state.cloudRevoked) return "\u5df2\u505c\u7528";
const t = String(tier || "").toLowerCase();
if (t === "pro" || t === "local") return "\u5df2\u5f00\u901a";
if (t === "free") return "\u6807\u51c6\u7528\u6237";
if (t === "revoked") return "\u5df2\u505c\u7528";
if (t === "unknown") return "—";
return tier ? String(tier) : "—";
}
function formatExpireText(ts) {
const n = Number(ts || 0);
if (!n) return "—";
const d = new Date(n * 1000);
return Number.isNaN(d.getTime()) ? "—" : d.toLocaleString();
}
function isCloudProTier() {
return !state.cloudRevoked && String(state.cloudTier || "").toLowerCase() === "pro";
}
function readCloudLeaseCache() {
try {
const raw = pageStorage().getItem(CLOUD_LEASE_CACHE_KEY);
if (!raw) return null;
const p = JSON.parse(raw);
const lease = String(p.lease || "").trim();
const exp = Number(p.exp || 0);
if (!lease || !Number.isFinite(exp) || exp <= 0) return null;
return {
lease,
exp,
tier: p.tier || "",
proExpireAt: Number(p.proExpireAt || 0),
};
} catch (_) {
return null;
}
}
function writeCloudLeaseCache(lease, exp, extra) {
if (!lease || !exp) {
pageStorage().removeItem(CLOUD_LEASE_CACHE_KEY);
return;
}
pageStorage().setItem(
CLOUD_LEASE_CACHE_KEY,
JSON.stringify(Object.assign({ lease, exp }, extra || {}))
);
}
function _crq(path, method, body, headersExtra) {
const url = `${getCloudApiBase()}${path}`;
const headers = Object.assign(
{
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
headersExtra || {}
);
if (state.cloudToken) headers.Authorization = `Bearer ${state.cloudToken}`;
const luid = getLearningUserId();
if (luid) headers["x-learning-user-id"] = luid;
const payload = body == null ? null : JSON.stringify(body);
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== "function") {
reject(new Error("\u4e91\u7aef\u8bf7\u6c42\u4e0d\u53ef\u7528"));
return;
}
GM_xmlhttpRequest({
method: method || "POST",
url,
headers,
data: payload,
onload(resp) {
let data = {};
try {
data = JSON.parse(String(resp.responseText || "").trim() || "{}");
} catch (_) {
data = {};
}
const status = Number(resp.status || 0);
if (status === 404) {
reject(new Error("cloud_404"));
return;
}
if (status < 200 || status >= 300) {
reject(new Error(String(data.detail || data.message || data.msg || `http_${status}`)));
return;
}
resolve(data);
},
onerror() {
reject(new Error("\u4e91\u7aef\u7f51\u7edc\u9519\u8bef"));
},
ontimeout() {
reject(new Error("\u4e91\u7aef\u8bf7\u6c42\u8d85\u65f6"));
},
});
});
}
async function _fpn() {
try {
const text = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `${getCloudApiBase()}${state.panelNoticePath || DEFAULT_PANEL_NOTICE_PATH}`,
headers: { Accept: "text/plain, */*" },
onload(resp) {
if (resp.status >= 200 && resp.status < 300) resolve(String(resp.responseText || "").trim());
else reject(new Error("notice_fail"));
},
onerror() {
reject(new Error("notice_net"));
},
});
});
if (text) state.remotePanelNotice = text;
renderPanelNotice();
} catch (_) {}
}
async function _fcc() {
try {
const data = await _crq("/api/hnzj/client-config", "GET", null, {
Accept: "application/json",
});
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 = resolvePinnedCloudApiBase();
} catch (_) {}
await _fpn();
}
function formatCloudAuthError(err) {
const em = String(err?.message || err || "");
if (/\u672a\u90e8\u7f72|404|cloud_404/i.test(em)) return "\u4e91\u7aef\u670d\u52a1\u6682\u4e0d\u53ef\u7528";
if (/invalid token/i.test(em)) return "Token \u65e0\u6548";
if (/revoked/i.test(em)) return "Token \u5df2\u88ab\u7981\u7528";
if (/expired/i.test(em)) return "Token \u5df2\u8fc7\u671f";
return em || "\u6821\u9a8c\u5931\u8d25";
}
function _acd(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 (Number.isFinite(exp) && exp > 0) state.cloudLeaseExp = Math.floor(exp);
if (data.tier) state.cloudTier = String(data.tier).trim().toLowerCase();
const lim = data.free_chapter_limit ?? data.freeChapterLimit ?? data.free_video_limit;
const used = data.free_used_chapters ?? data.freeUsedChapters ?? data.free_used_videos;
if (lim != null) state.freeChapterLimit = Number(lim) || DEFAULT_FREE_CHAPTER_LIMIT;
if (used != null) state.freeUsedChapters = Number(used) || 0;
const proExp = Number(data.pro_expires_at ?? data.proExpiresAt ?? data.pro_expire_at ?? 0);
if (Number.isFinite(proExp) && proExp > 0) state.cloudProExpireAt = Math.floor(proExp);
if (!state.cloudTier) state.cloudTier = state.cloudToken ? "pro" : "free";
writeCloudLeaseCache(state.cloudLease, state.cloudLeaseExp, {
tier: state.cloudTier,
proExpireAt: state.cloudProExpireAt,
});
}
async function _ecl(force) {
const now = Math.floor(Date.now() / 1000);
if (!force && state.cloudLease && state.cloudLeaseExp - now > 60) return true;
const cached = readCloudLeaseCache();
if (!force && cached && cached.exp - now > 60) {
state.cloudLease = cached.lease;
state.cloudLeaseExp = cached.exp;
state.cloudTier = cached.tier || state.cloudTier;
state.cloudProExpireAt = cached.proExpireAt || state.cloudProExpireAt;
updateCloudPanelUI();
return true;
}
const luid = getLearningUserId();
if (!luid) {
state.cloudTier = state.cloudToken ? "pro" : "free";
updateCloudPanelUI();
return false;
}
try {
const u = state.userProfile || {};
const data = await _crq("/api/hnzj/lease", "POST", {
learning_user_id: luid,
lease: state.cloudLease || undefined,
site: "hnzj",
script_version: SCRIPT_VERSION,
name: String(u.name || "").trim(),
realName: String(u.name || "").trim(),
userName: String(u.loginId || "").trim(),
phone: String(u.phone || "").trim(),
});
state.cloudRevoked = false;
_acd(data);
return !!state.cloudLease;
} catch (e) {
const em = String(e?.message || e);
if (/revoked|invalid token|expired/i.test(em)) {
state.cloudRevoked = true;
state.cloudTier = "revoked";
state.cloudLease = "";
writeCloudLeaseCache("", 0);
updateCloudPanelUI();
throw e;
}
if (em === "cloud_404") {
state.panelHint = "";
updateCloudPanelUI();
return false;
}
if (cached && cached.lease && cached.exp - now > -300 && isCloudProTier()) {
state.cloudLease = cached.lease;
state.cloudLeaseExp = cached.exp;
state.cloudTier = cached.tier || "pro";
updateCloudPanelUI();
return true;
}
trace(`\u4e91\u7aef\u6388\u6743\u5931\u8d25\uff1a${em || "\u7f51\u7edc\u9519\u8bef"}`);
updateCloudPanelUI();
return false;
}
}
async function _sct(token) {
const tk = String(token || "").trim();
state.cloudToken = tk;
if (tk) pageStorage().setItem(CLOUD_TOKEN_KEY, tk);
else pageStorage().removeItem(CLOUD_TOKEN_KEY);
if (!tk) {
state.cloudTier = "free";
state.cloudProExpireAt = 0;
state.cloudRevoked = false;
try {
await _ecl(true);
} catch (_) {}
updateCloudPanelUI();
trace("\u5df2\u6e05\u9664\u6388\u6743\u7801");
return true;
}
try {
await _crq("/api/hnzj/license/verify", "GET", null);
await _ecl(true);
if (!isCloudProTier()) state.cloudTier = "pro";
state.cloudRevoked = false;
updateCloudPanelUI();
trace("\u4f1a\u5458\u72b6\u6001\u5df2\u66f4\u65b0");
return true;
} catch (e) {
const tip = formatCloudAuthError(e);
if (state._lastProErr !== tip) {
state._lastProErr = tip;
trace(`\u6388\u6743\u6821\u9a8c\u5931\u8d25\uff1a${tip}`);
}
updateCloudPanelUI();
return false;
}
}
async function _acp() {
try {
await _ecl(true);
} catch (e) {
log(`\u4e91\u7aef\u6388\u6743\u5931\u8d25\uff1a${formatCloudAuthError(e)}`, "warn");
return false;
}
if (!state.cloudLease) {
log("\u672a\u83b7\u53d6\u4e91\u7aef\u6388\u6743\uff0c\u8bf7\u786e\u8ba4\u5df2\u767b\u5f55\u5b98\u7f51\u540e\u518d\u8bd5", "warn");
return false;
}
return true;
}
async function _rcq() {
try {
const data = await _crq("/api/hnzj/study/consume", "POST", {
lease: state.cloudLease || undefined,
learning_user_id: getLearningUserId(),
kind: "chapter",
});
if (data && data.ok === false) {
throw new Error(String(data.message || "quota_denied"));
}
_acd(data);
return true;
} catch (e) {
const em = String(e?.message || e);
if (/free_quota_exhausted|quota|403|exam_requires_pro/i.test(em)) {
openProModal();
} else {
trace(`\u4e91\u7aef\uff1a${em || "\u7f51\u7edc\u9519\u8bef"}`);
}
return false;
}
}
async function _ate() {
try {
await _ecl(true);
} catch (_) {
return false;
}
if (!state.cloudLease) return false;
try {
const data = await _crq("/api/hnzj/study/can-exam", "POST", {
lease: state.cloudLease || undefined,
learning_user_id: getLearningUserId(),
});
if (data && data.ok !== false && data.success !== false) return true;
} catch (e) {
trace(`\u8003\u8bd5\u6821\u9a8c\uff1a${e?.message || e}`);
}
openProModal();
return false;
}
function openProBuyPage() {
try {
PAGE.open(state.proBuyUrl || PRO_BUY_URL, "_blank");
} catch (_) {
location.href = state.proBuyUrl || PRO_BUY_URL;
}
}
function closeProModal() {
document.getElementById("hnzj-pro-modal")?.remove();
}
function openProModal() {
closeProModal();
const modal = document.createElement("div");
modal.id = "hnzj-pro-modal";
modal.innerHTML = `
\u5f00\u901a Pro \u4f1a\u5458
\u514d\u8d39\u7528\u6237\u53ef\u4f53\u9a8c ${DEFAULT_FREE_CHAPTER_LIMIT} \u4e2a\u89c6\u9891\u7ae0\u8282\uff1b\u5f00\u901a Pro \u540e\u4e0d\u9650\u7ae0\u8282\uff0c\u6fc0\u6d3b\u540e ${PRO_DAYS} \u5929\u6709\u6548\u3002
\u4f1a\u5458\u6743\u76ca
- \u516c\u9700\u8bfe / \u4e13\u4e1a\u8bfe\u81ea\u52a8\u770b\u8bfe\u4e0d\u9650\u7ae0\u8282
- \u672c\u5e73\u53f0\u65e0\u8003\u8bd5\uff0c\u4e13\u6ce8\u770b\u8bfe\u8fdb\u5ea6
- \u6fc0\u6d3b\u540e ${PRO_DAYS} \u5929\u6709\u6548
\u5f00\u901a\u65b9\u5f0f
\u524d\u5f80\u8d2d\u4e70\u9875\u83b7\u53d6\u6388\u6743\u7801\uff0c\u5728\u300c\u8bbe\u7f6e\u300d\u4e2d\u7c98\u8d34\u5e76\u4fdd\u5b58\u3002
`;
document.body.appendChild(modal);
modal.querySelector("#hnzj-pro-buy")?.addEventListener("click", () => openProBuyPage());
modal.querySelector("#hnzj-pro-close")?.addEventListener("click", () => closeProModal());
modal.addEventListener("click", (e) => {
if (e.target === modal) closeProModal();
});
}
function showToast(msg) {
let el = document.getElementById("hnzj-toast");
if (!el) {
el = document.createElement("div");
el.id = "hnzj-toast";
document.body.appendChild(el);
}
el.textContent = String(msg || "");
el.classList.add("show");
clearTimeout(showToast._t);
showToast._t = setTimeout(() => el.classList.remove("show"), 2600);
}
function loadQueue() {
return readJson(QUEUE_KEY, []);
}
function saveQueue(list) {
writeJson(QUEUE_KEY, Array.isArray(list) ? list : []);
}
function isEbookVideo(v) {
return Number(v?.type) === 2;
}
function videoTotalSeconds(v) {
const n = Number(v?.duration || v?.videoDuration || v?.totalDuration || 0);
return Math.max(1, Math.ceil(n));
}
function videoStudySeconds(v) {
const total = videoTotalSeconds(v);
const watched = Math.max(0, Number(v?.watchTimeLength || 0));
const rate = Number(v?.learnSpeed || 0);
const fromRate = rate > 0 ? Math.floor((total * rate) / 100) : 0;
return Math.max(watched, fromRate);
}
function studiedSecondsFromRate(total, rate) {
const t = Math.max(1, Number(total) || 1);
const r = Math.max(0, Math.min(100, Number(rate) || 0));
return Math.min(t, Math.floor((t * r) / 100));
}
function _ppp(projectId, videoId, videoName, rate, total) {
const pid = String(projectId || "");
const vid = String(videoId || "");
const studied = studiedSecondsFromRate(total, rate);
const done = Number(rate) >= 100;
let hit = false;
for (const c of state.chapterPreview) {
if (String(c.projectId) !== pid) continue;
const same =
(vid && String(c.videoId || "") === vid) ||
(!vid && videoName && String(c.name || "") === String(videoName));
if (!same) continue;
c.studied = studied;
c.total = Math.max(1, Number(total) || c.total || 1);
c.done = done;
hit = true;
}
if (!hit && pid && videoName) {
const p = state.panelProjects.find((x) => String(x.projectId) === pid);
state.chapterPreview.push({
projectId: pid,
projectName: p?.projectName || pid,
videoId: vid,
name: videoName,
studied,
total: Math.max(1, Number(total) || 1),
done,
kind: "ware",
});
}
_rsp(pid);
}
function _rsp(projectId) {
const pid = String(projectId || "");
const p = state.panelProjects.find((x) => String(x.projectId) === pid);
if (!p) return;
const items = state.chapterPreview.filter(
(c) => String(c.projectId) === pid && c.kind !== "exam" && Math.max(0, Number(c.total) || 0) > 0
);
if (!items.length) return;
let studied = 0;
let total = 0;
for (const c of items) {
studied += Math.max(0, Number(c.studied) || 0);
total += Math.max(0, Number(c.total) || 0);
}
if (total <= 0) return;
const progress = Math.min(100, (studied / total) * 100);
p.studyProgress = Number(progress.toFixed(1));
if (progress >= 100) {
p.videoFinishedStatus = true;
if (!isProjectFullyFinished(p)) p.studyLabel = formatProgressDisplay(100);
} else if (progress > 0) {
p.studyLabel = formatProgressDisplay(p.studyProgress);
} else {
p.studyLabel = "\u672a\u5b66\u4e60";
}
syncProjectFinishedFlags(p);
}
function videoLearnRateOf(v) {
return Number(v?.learnSpeed ?? v?.videoLearnRate ?? v?.studySpeed ?? v?.percent ?? 0);
}
function isVideoDone(v) {
if (!v) return true;
if (isEbookVideo(v)) return true;
return videoLearnRateOf(v) >= 100;
}
function videoDisplayStudiedSeconds(v) {
const total = videoTotalSeconds(v);
const raw = videoStudySeconds(v);
if (isVideoDone(v)) return Math.max(raw, total);
return Math.min(raw, total);
}
function formatStudyProgressLog(name, rate, total) {
const studiedSec = studiedSecondsFromRate(total, rate);
return `\u5b66\u4e60\uff1a${name}\u5df2\u5b66${formatMinutes(studiedSec)}/${formatMinutes(total)}`;
}
function formatMinutes(sec) {
const s = Math.max(0, Number(sec) || 0);
const m = s / 60;
if (m >= 100) return `${Math.round(m)} \u5206\u949f`;
if (m >= 10) return `${m.toFixed(0)} \u5206\u949f`;
return `${m.toFixed(1)} \u5206\u949f`;
}
function wareProgressPct(studied, total, done) {
if (done) return 100;
const t = Math.max(1, Number(total) || 1);
return Math.min(100, Math.round((Math.max(0, Number(studied) || 0) / t) * 100));
}
function videoNameOf(v) {
const video = String(v?.videoName || v?.name || v?.title || "").trim();
const course = String(v?.courseName || v?.myClassCourseName || "").trim();
if (video && course && !video.includes(course)) return `${course} · ${video}`;
return video || course || String(v?.myClassCourseVideoId || "\u89c6\u9891");
}
function videoSourceOf(v) {
return String(v?.baiJiaYunVideoSource || v?.ccVideoSource || v?.videoSource || "").trim();
}
function flattenVideosFromBooks(booksData, myClassId) {
let courseData = [];
if (Array.isArray(booksData)) courseData = booksData;
else if (Array.isArray(booksData?.myClassCourseRPList)) courseData = booksData.myClassCourseRPList;
else if (Array.isArray(booksData?.courseData)) courseData = booksData.courseData;
else if (Array.isArray(booksData?.data?.myClassCourseRPList)) courseData = booksData.data.myClassCourseRPList;
else if (Array.isArray(booksData?.data?.courseData)) courseData = booksData.data.courseData;
else if (Array.isArray(booksData?.data)) courseData = booksData.data;
else courseData = pickList(booksData);
const out = [];
for (const course of courseData || []) {
const courseName = String(course?.courseName || course?.name || course?.title || "").trim();
const vids = Array.isArray(course?.videoRPs) ? course.videoRPs : [];
for (const v of vids) {
out.push({
...v,
myClassId: myClassId || v.myClassId,
myClassCourseId: v.myClassCourseId || course.myClassCourseId || course.id,
courseType: course.type,
courseName: courseName || v.courseName || "",
});
}
}
return out;
}
function isVideoFinished(p) {
if (!p) return false;
if (p.videoFinishedStatus === true) return true;
if (Number(p.studyStatus) === 2) return true;
if (Number(p.studyProgress) >= 100) return true;
return false;
}
function isProjectFullyFinished(p) {
if (!p) return false;
if (Number(p.isPass) === 1) return true;
if (Number(p.studyStatus) === 2) return true;
if (Number(p.studyProgress) >= 100) return true;
if (!PLATFORM_HAS_EXAM) return false;
if (!isVideoFinished(p) && p.listSource !== "completed" && !p.projectFinishedStatus) return false;
if (p.examRequired && !p.examDone) return false;
if (String(p.examLabel || "").includes("\u5f85\u8003\u8bd5")) return false;
if (p._examEnriched) {
return !!(p.examDone || String(p.examLabel || "").includes("\u5df2\u901a\u8fc7") || p.examLabel === "\u65e0\u8bd5\u5377" || p.examLabel === "\u65e0\u5f85\u8003");
}
if (isVideoFinished(p) || p.listSource === "completed") return false;
return false;
}
function isProjectSelectable(p) {
if (!p) return false;
if (isProjectExpired(p)) return false;
return !isProjectFullyFinished(p);
}
function projectListBadge(p) {
if (isProjectExpired(p)) {
return `\u5df2\u8fc7\u671f`;
}
if (isProjectFullyFinished(p)) {
return `\u5df2\u5b8c\u6210`;
}
if (Number(p.studyProgress) > 0) {
return `\u5b66\u4e60\u4e2d`;
}
return `\u672a\u5b66\u4e60`;
}
function syncProjectFinishedFlags(p) {
if (!p) return;
const apiDone =
Number(p.isPass) === 1 || Number(p.studyStatus) === 2 || Number(p.studyProgress) >= 100;
if (apiDone) {
p.videoFinishedStatus = true;
p.listSource = "completed";
p.sourceLabel = "\u5df2\u5b8c\u6210";
p.studyLabel = "\u5df2\u5b8c\u6210";
p.projectFinishedStatus = true;
return;
}
p.projectFinishedStatus = false;
if (p.listSource === "completed") p.listSource = "learning";
if (Number(p.studyProgress) >= 100) {
p.videoFinishedStatus = true;
} else if (Number(p.studyProgress) > 0) {
p.videoFinishedStatus = false;
p.sourceLabel = "\u5b66\u4e60\u4e2d";
p.studyLabel = formatProgressDisplay(p.studyProgress);
} else if (isProjectExpired(p)) {
p.videoFinishedStatus = false;
p.sourceLabel = "\u5df2\u8fc7\u671f";
p.studyLabel = "\u672a\u5b66\u4e60";
} else {
p.videoFinishedStatus = false;
p.sourceLabel = "\u672a\u5b66\u4e60";
p.studyLabel = "\u672a\u5b66\u4e60";
}
if (!PLATFORM_HAS_EXAM) return;
if (isProjectFullyFinished(p)) {
p.listSource = "completed";
p.sourceLabel = "\u5df2\u5b8c\u6210";
p.studyLabel = "\u5df2\u5b8c\u6210";
p.projectFinishedStatus = true;
} else if (isVideoFinished(p) && (p.examRequired || String(p.examLabel || "").includes("\u5f85\u8003\u8bd5"))) {
p.projectFinishedStatus = false;
p.sourceLabel = "\u5f85\u8003\u8bd5";
p.listSource = "learning";
}
}
function classifyStudyRow(row) {
const status = Number(row?.studyStatus);
const speed = Number(
row?.studySpeed ?? row?.studyProgress ?? row?.learnSpeed ?? row?.percent ?? row?.classEntity?.studySpeed ?? 0
);
if (Number(row?.isPass) === 1 || status === 2 || speed >= 100) return "completed";
return "learning";
}
function mapProjectRow(row, classTypeHint) {
const entity = row?.classEntity && typeof row.classEntity === "object" ? row.classEntity : {};
const info = row?.classInfo && typeof row.classInfo === "object" ? row.classInfo : {};
const src = classifyStudyRow(row);
const progress = Number(
row.studySpeed ?? row.studyProgress ?? row.learnSpeed ?? row.percent ?? entity.studySpeed ?? info.studySpeed ?? 0
);
const myClassId = String(row.myClassId || row.id || entity.myClassId || info.myClassId || "");
const classId = String(row.classId || entity.classId || info.classId || myClassId);
const finishedHint = src === "completed" || progress >= 100 || Number(row.isPass) === 1;
const classType = normalizeStudyClassType(
classTypeHint != null
? classTypeHint
: row.classType ?? entity.classType ?? entity.attribute ?? info.attribute ?? CLASS_TYPE_PUBLIC
);
const name =
row.title ||
row.className ||
entity.className ||
info.className ||
row.myClassName ||
row.name ||
"";
return {
projectId: myClassId,
projectName: name || `\u8bfe\u7a0b${myClassId}`,
classType,
classTypeLabel: studyClassTypeLabel(classType),
creditType:
studyClassTypeLabel(classType) ||
row.subjectName ||
row.classTypeName ||
row.creditType ||
entity.subjectName ||
info.subjectName ||
"",
projectCredit:
row.classHour ?? row.credit ?? row.projectCredit ?? row.period ?? entity.classHour ?? info.classHour,
studyProgress: progress,
studyStatus: Number(row.studyStatus || 0),
isPass: Number(row.isPass || 0),
courseStatus: Number(row.status ?? 0),
year: String(entity.year || row.year || info.year || ""),
enrollTime: String(row.enrollTime || ""),
lastTime: String(row.lastTime || ""),
startTime: Number(row.startTime || entity.startTime || 0),
limitDataTime: Number(entity.limitDataTime || row.limitDataTime || 0),
studyTime: Number(row.studyTime || 0),
classId,
listSource: src,
sourceLabel: src === "completed" ? "\u5df2\u5b8c\u6210" : progress > 0 ? "\u5b66\u4e60\u4e2d" : "\u672a\u5b66\u4e60",
studyLabel: finishedHint
? "\u5df2\u5b8c\u6210"
: progress > 0
? formatProgressDisplay(progress)
: "\u672a\u5b66\u4e60",
examId: "",
examName: "",
examDone: false,
examRequired: false,
examScore: null,
examPassLine: 60,
examLabel: "",
videoFinishedStatus: finishedHint,
projectFinishedStatus: false,
raw: row,
};
}
async function _eup(force) {
if (!force && state.userProfile?.accountId) return state.userProfile;
const cms = readCmsAccountInfo();
if (cms?.accountId) {
rememberAccountId(cms.accountId);
state.userProfile = normalizeUserProfile(cms);
if (!force) return state.userProfile;
}
try {
const j = await apiGet(PATH_ACCOUNT_INFO);
const attr = j.data || null;
const info = attr?.cmsAccountInfo || attr?.data || attr || null;
if (info?.accountId) rememberAccountId(info.accountId);
state.userProfile = normalizeUserProfile(info || cms || {});
if (state.userProfile?.accountId) {
writeAuthCache({ accountId: state.userProfile.accountId });
}
} catch (e) {
if (cms) state.userProfile = normalizeUserProfile(cms);
else throw e;
}
return state.userProfile;
}
async function fetchStudyListPage(pageNo, studyStatus, classType) {
const qs = toQuery({
searchYear: "",
studyStatus: studyStatus == null ? "" : studyStatus,
mylearnTaskId: "",
attribute: "",
orderBy: 2,
mathRandom: Math.random(),
pageNo: pageNo || 1,
subject: "",
classType: classType == null ? getStudyClassType() : classType,
pageSize: 50,
});
const j = await apiGet(`${PATH_STUDY_LIST}?${qs}`);
return pickList(j.data);
}
async function _fap() {
const ct = getStudyClassType();
let allRows = [];
try {
allRows = await fetchStudyListPage(1, "", ct);
} catch (e) {
trace(`${studyClassTypeLabel(ct)}\u5217\u8868\u5931\u8d25: ${e.message || e}`);
}
if (!allRows.length) {
try {
const learning = await fetchStudyListPage(1, 1, ct);
const done = await fetchStudyListPage(1, 2, ct);
allRows.push(...learning, ...done);
} catch (_) {}
}
const mapped = allRows.map((row) => mapProjectRow(row, ct));
const learning = mapped.filter((p) => p.listSource === "learning");
const completed = mapped.filter((p) => p.listSource === "completed");
state._capturedLists = { learning, completed };
const byId = new Map();
for (const p of learning) byId.set(p.projectId, p);
for (const p of completed) byId.set(p.projectId, p);
const merged = Array.from(byId.values());
for (const p of merged) syncProjectFinishedFlags(p);
return merged;
}
async function fetchClassDetail(myClassId) {
const j = await apiGet(`${PATH_MY_CLASS}?${toQuery({ myClassId })}`);
const raw = j.data?.data || j.data || null;
if (!raw || typeof raw !== "object") return raw;
if (raw.classInfo && typeof raw.classInfo === "object") {
if (!raw.classId && raw.classInfo.classId) raw.classId = raw.classInfo.classId;
if (!raw.className && raw.classInfo.className) raw.className = raw.classInfo.className;
}
return raw;
}
async function fetchClassBooks(myClassId) {
const j = await apiGet(`${PATH_MY_CLASS}?${toQuery({ myClassId })}`);
const raw = j.data?.data || j.data || null;
if (!raw || typeof raw !== "object") return { courseData: [] };
if (Array.isArray(raw.myClassCourseRPList) && raw.myClassCourseRPList.length) {
return raw;
}
try {
const bj = await apiGet(`${PATH_MY_CLASS_BOOKS}?${toQuery({ myClassId })}`);
const bro = bj.data?.data != null ? bj.data.data : bj.data;
if (Array.isArray(bro)) return { courseData: bro };
if (bro && typeof bro === "object") return bro;
} catch (_) {}
return raw;
}
async function enrichProjectNames(projects, limit) {
const list = (projects || []).slice(0, Math.max(1, limit || 20));
for (const p of list) {
if (!p || !p.projectId) continue;
if (p.projectName && !/^\u8bfe\u7a0b[0-9a-f-]{30,}$/i.test(p.projectName)) continue;
try {
const detail = await fetchClassDetail(p.projectId);
const name =
detail?.classInfo?.className ||
detail?.className ||
detail?.classInfo?.title ||
"";
if (name) {
p.projectName = name;
if (detail?.classId) p.classId = String(detail.classId);
}
} catch (_) {}
}
}
function buildWatchInfo(video, playduration, pid, timestamp) {
const vid = videoSourceOf(video);
return JSON.stringify({
vid,
pid: pid || `BAIJIAYUN_${uuidv4()}`,
playduration: Math.max(0, Math.floor(Number(playduration) || 0)),
timestamp: Number(timestamp) > 0 ? Math.floor(Number(timestamp)) : Date.now(),
});
}
function formatVideoRate(rate) {
const n = Number(rate);
if (!Number.isFinite(n)) return "0";
if (n >= 100) return "100";
return n.toFixed(1);
}
function initialPlaySeconds(video, total) {
const watched = Math.floor(Number(video?.watchTimeLength) || 0);
if (watched > 0) return Math.min(watched, Math.max(0, total - 1));
const speed = videoLearnRateOf(video);
if (speed > 0) return Math.min(Math.floor((total * speed) / 100), Math.max(0, total - 1));
return 1;
}
function videoInnerResult(un) {
const attr = un?.data;
if (!attr) return null;
if (attr.data && typeof attr.data === "object") return attr.data;
return attr;
}
async function _psw(myClassId, video, watchInfo) {
return apiPostForm(PATH_VIDEO_SV, {
myClassId,
myClassCourseId: video.myClassCourseId,
myClassCourseVideoId: video.myClassCourseVideoId,
watchInfo,
});
}
async function _pcw(myClassId, video, watchInfo) {
return apiPostForm(PATH_VIDEO_CV, {
myClassId,
myClassCourseId: video.myClassCourseId,
myClassCourseVideoId: video.myClassCourseVideoId,
watchInfo,
isCalculateclassHourFlag: true,
});
}
async function postUpdateLastVideo(classId, myClassId, video) {
try {
await apiPostForm(PATH_UPDATE_LAST_VIDEO, {
classId: classId || myClassId,
myClassId,
myClassCourseId: video.myClassCourseId,
myClassCourseVideoId: video.myClassCourseVideoId,
});
} catch (e) {
trace(`update-last-video: ${e.message || e}`);
}
}
function paperNeedsExam(row) {
if (!row) return false;
if (Number(row.isPass) === 1 || row.passFlag === true || row.pass === true) return false;
const pass = Number(row.passScore ?? row.passMark ?? row.passLine ?? 60);
const score = Number(row.score ?? row.myHighestScore ?? row.highestScore ?? NaN);
if (Number.isFinite(score) && score >= pass) return false;
const studySpeed = Number(row.studySpeed ?? 0);
if (studySpeed < 100) return false;
const statusName = String(row.statusName || "");
if (statusName.includes("\u672a\u5b8c\u6210")) return false;
if (statusName.includes("\u5df2\u901a\u8fc7")) return false;
return true;
}
function paperUnfinished(row) {
return paperNeedsExam(row);
}
async function fetchPaperList(cmsType) {
const qs = toQuery({ pageNo: 1, pageSize: 20, cmsType: cmsType == null ? 9 : cmsType });
const j = await apiGet(`${PATH_EXAM_LIST}?${qs}`);
return pickList(j.data);
}
async function checkExamStatus(myExamId) {
if (!myExamId) return;
try {
await apiGet(`${PATH_EXAM_CHECK_STATUS}?${toQuery({ myExamId })}`);
} catch (e) {
trace(`check-my-exam-status\uff1a${e.message || e}`);
}
}
async function saveExamRecord(paperRow) {
const qs = toQuery({
myExamId: paperRow.myExamId,
isMakeUp: paperRow.isMakeUpStatus ?? paperRow.isMakeUp ?? 0,
examType: paperRow.examType ?? paperRow.type ?? 0,
paperId: paperRow.paperId,
paperSource: paperRow.paperSource ?? 1,
});
const j = await apiGet(`${PATH_EXAM_SAVE_RECORD}?${qs}`);
const rid = j.data?.data ?? j.data;
return rid ? String(rid) : "";
}
async function startPaper(paperId, myExamRecordId) {
const qs = toQuery({
paperId: paperId || "",
myExamRecordId: myExamRecordId || "",
});
const j = await apiGet(`${PATH_EXAM_START}?${qs}`);
return j.data?.data || j.data || null;
}
function isStandardOption(opt) {
if (!opt) return false;
const v = opt.standardAnswer;
return v === true || v === 1 || v === "1" || Number(opt.isStandardAnswer) === 1 || opt.isCorrect === true;
}
function buildExamAnswerList(paperDetail) {
const stems = paperDetail?.questionStemRPS || paperDetail?.listQuestionStemRP || [];
if (!Array.isArray(stems) || !stems.length) return null;
const answerList = [];
for (const stem of stems) {
const questions = stem.listPaperQuestionRP || stem.paperQuestionRPS || [];
if (!Array.isArray(questions) || !questions.length) return null;
for (const q of questions) {
const qid = q.paperQuestionId || q.questionId || q.id;
if (!qid) return null;
const opts = q.paperOptionRPS || q.paperOptionList || q.options || [];
const std = (Array.isArray(opts) ? opts : []).filter(isStandardOption);
if (!std.length) return null;
answerList.push({
paperQuestionId: String(qid),
answers: std.map((o) => ({ paperOptionId: String(o.paperOptionId || o.id) })),
});
}
}
return answerList.length ? answerList : null;
}
async function submitPaper(payload) {
const body = {
myExamId: payload.myExamId,
isMakeUp: payload.isMakeUp != null ? payload.isMakeUp : false,
myExamRecordId: payload.myExamRecordId,
answerList:
typeof payload.answerList === "string"
? payload.answerList
: JSON.stringify(payload.answerList || []),
};
try {
return await apiPostForm(PATH_EXAM_SUBMIT, body);
} catch (_) {
return await apiPostJson(PATH_EXAM_SUBMIT, body);
}
}
async function _roe(paperRow) {
const myExamId = paperRow.myExamId;
const paperId = paperRow.paperId;
const name = paperRow.paperName || paperRow.className || paperId;
if (!myExamId || !paperId) throw new Error("\u7f3a\u5c11 myExamId \u6216 paperId");
await checkExamStatus(myExamId);
const myExamRecordId = await saveExamRecord(paperRow);
if (!myExamRecordId) throw new Error("save-my-exam-record \u672a\u8fd4\u56de recordId");
const detail = await startPaper(paperId, myExamRecordId);
if (!detail) throw new Error("start-do-paper \u65e0\u9898\u76ee");
const answerList = buildExamAnswerList(detail);
if (!answerList) throw new Error("\u9898\u76ee\u65e0 standardAnswer\uff0c\u5df2\u8df3\u8fc7");
await submitPaper({
myExamId,
isMakeUp: paperRow.isMakeUpStatus ?? paperRow.isMakeUp ?? false,
myExamRecordId,
answerList,
});
log(`\u8003\u8bd5\uff1a${name} \u5df2\u63d0\u4ea4`);
return true;
}
async function _tea(project) {
if (!(await _ate())) return false;
let papers = [];
try {
papers = await fetchPaperList(9);
} catch (e) {
trace(`\u8bd5\u5377\u5217\u8868(cmsType=9)\uff1a${e.message || e}`);
}
if (!papers.length) {
try {
papers = await fetchPaperList(1);
} catch (e) {
trace(`\u8bd5\u5377\u5217\u8868(cmsType=1)\uff1a${e.message || e}`);
}
}
let pending = papers.filter(paperNeedsExam);
if (project?.projectId) {
const mine = pending.filter((p) => String(p.myClassId || "") === String(project.projectId));
if (mine.length) pending = mine;
}
if (!pending.length) {
if (project) {
const st = examLabelForProject(project, papers);
project.examRequired = st.examRequired;
project.examDone = st.examDone;
project.examLabel = st.examLabel;
project.examScore = st.examScore;
project._examEnriched = true;
syncProjectFinishedFlags(project);
}
return false;
}
if (project) {
project.examRequired = true;
project.examLabel = `\u5f85\u8003\u8bd5 ${pending.length} \u4efd`;
}
let did = false;
for (const paper of pending.slice(0, 3)) {
if (!state.enabled) break;
try {
await _roe(paper);
did = true;
if (project) {
project.examDone = true;
project.examRequired = false;
project.examLabel = "\u5df2\u63d0\u4ea4\uff0c\u5237\u65b0\u6210\u7ee9\u4e2d…";
project._examEnriched = false;
}
await sleep(1500);
try {
let papers2 = await fetchPaperList(9);
if (!papers2.length) papers2 = await fetchPaperList(1);
if (project) {
const st = examLabelForProject(project, papers2);
project.examRequired = st.examRequired;
project.examDone = st.examDone;
project.examLabel = st.examLabel;
project.examScore = st.examScore;
project._examEnriched = true;
syncProjectFinishedFlags(project);
_upn();
}
} catch (_) {
if (project) {
project.examLabel = "\u5df2\u63d0\u4ea4";
project.examDone = true;
project.examRequired = false;
}
}
await sleep(800);
} catch (e) {
const msg = e.message || e;
log(`\u8003\u8bd5\u300c${paper.paperName || paper.className || paper.paperId}\u300d\u5931\u8d25\uff1a${msg}`);
if (project && String(msg).includes("standardAnswer")) {
project.examLabel = "\u7f3a\u6807\u51c6\u7b54\u6848\uff0c\u5df2\u8df3\u8fc7";
}
trace(`\u8003\u8bd5\u5931\u8d25\uff1a${msg}`);
}
}
return did;
}
async function _sw(project, video) {
const myClassId = project.projectId;
const name = videoNameOf(video);
const total = videoTotalSeconds(video);
const goalSec = Math.min(total, Math.ceil(total * EFFICIENCY_JUMP_RATIO));
const vid = videoSourceOf(video);
if (!vid) {
log(`\u89c6\u9891\u300c${name}\u300d\u65e0\u89c6\u9891\u6e90\uff0c\u8df3\u8fc7`);
return;
}
if (isEbookVideo(video)) {
log(`\u300c${name}\u300d\u4e3a\u7535\u5b50\u4e66\uff0c\u8df3\u8fc7\u8ba1\u65f6`);
return;
}
state.lastUserAction = name;
_upn();
const pid = `BAIJIAYUN_${uuidv4()}`;
let playduration = initialPlaySeconds(video, total);
let rate = videoLearnRateOf(video);
let lastReportMs = Date.now();
await postUpdateLastVideo(project.classId || myClassId, myClassId, video);
let svOk = false;
try {
const watchInfo0 = buildWatchInfo(video, playduration, pid, lastReportMs);
const svRes = await _psw(myClassId, video, watchInfo0);
const inner = videoInnerResult(svRes);
if (inner && String(inner.respCode) !== "SUCCESS" && inner.respCode != null) {
throw new Error(inner.respDesc || inner.respCode || "sv_fail");
}
svOk = true;
if (inner?.videoLearnRate != null) rate = Number(inner.videoLearnRate);
video.learnSpeed = rate;
_ppp(myClassId, video.myClassCourseVideoId, name, rate, total);
_upn();
log(formatStudyProgressLog(name, rate, total));
_lwh();
} catch (e) {
trace(`sv\uff1a${e.message || e}`);
await sleep(2000);
}
if (!svOk) {
log(`\u8df3\u8fc7\uff1a${name}`);
return;
}
await sleep(SV_BEFORE_CV_MS);
const needSteps = Math.ceil(Math.max(0, goalSec - playduration) / CV_STEP_SECONDS);
const maxRounds = Math.min(WATCH_MAX_ROUNDS, needSteps + 2);
for (let round = 0; round < maxRounds && state.enabled; round++) {
if (rate >= 100) break;
if (playduration >= goalSec) break;
const now = Date.now();
const elapsedSec = Math.max(1, Math.floor((now - lastReportMs) / 1000));
const step = Math.min(CV_STEP_SECONDS, elapsedSec, Math.max(1, goalSec - playduration));
playduration = Math.min(goalSec, playduration + step);
lastReportMs = now;
const watchInfo = buildWatchInfo(video, playduration, pid, lastReportMs);
let cvOk = false;
try {
const cvRes = await _pcw(myClassId, video, watchInfo);
const inner = videoInnerResult(cvRes);
if (cvRes.msg && String(cvRes.msg).includes("today class hour limit")) {
log(`\u4eca\u65e5\u5b66\u65f6\u5df2\u8fbe\u4e0a\u9650`);
break;
}
if (inner && String(inner.respCode) === "INVALID_PARAM") {
trace(`cv\uff1a${inner.respDesc || inner.respCode}`);
playduration = Math.max(1, playduration - step);
await sleep(CV_INTERVAL_MS);
continue;
}
if (inner && String(inner.respCode) === "SUCCESS") {
if (inner.videoLearnRate != null) rate = Number(inner.videoLearnRate);
video.learnSpeed = rate;
_ppp(myClassId, video.myClassCourseVideoId, name, rate, total);
_upn();
log(formatStudyProgressLog(name, rate, total));
if (rate >= 100 || playduration >= goalSec) break;
} else if (inner && inner.respCode != null) {
throw new Error(inner.respDesc || inner.respCode);
}
} catch (e) {
trace(`cv\uff1a${e.message || e}`);
playduration = Math.max(1, playduration - step);
await sleep(CV_INTERVAL_MS);
continue;
}
if (rate >= 100 || playduration >= goalSec) break;
await sleep(CV_INTERVAL_MS);
}
video.learnSpeed = Math.max(Number(video.learnSpeed || 0), rate);
if (rate >= 100 || playduration >= goalSec) video.learnSpeed = 100;
_ppp(myClassId, video.myClassCourseVideoId, name, video.learnSpeed, total);
log(formatStudyProgressLog(name, video.learnSpeed, total).replace(/^\u5b66\u4e60\uff1a/, "\u5b8c\u6210\uff1a"));
}
async function _sp(project) {
const projectId = project.projectId;
state.lastUserAction = project.projectName;
_upn();
log(`\u5f00\u59cb\uff1a${project.projectName}`);
let detail = null;
try {
detail = await fetchClassDetail(projectId);
if (detail?.classId) project.classId = String(detail.classId);
if (detail?.studyStatus != null) project.studyStatus = Number(detail.studyStatus);
if (detail?.studySpeed != null) project.studyProgress = Number(detail.studySpeed);
} catch (e) {
trace(`\u8bfe\u7a0b\u8be6\u60c5\uff1a${e.message || e}`);
}
let books = null;
try {
books = await fetchClassBooks(projectId);
} catch (e) {
throw new Error(`\u8bfe\u4ef6\u5217\u8868\u5931\u8d25\uff1a${e.message || e}`);
}
const videos = flattenVideosFromBooks(books, projectId).filter((v) => !isEbookVideo(v));
const pending = videos.filter((v) => !isVideoDone(v));
if (pending.length) {
if (!(await _acp())) {
state.enabled = false;
return;
}
for (const video of pending) {
if (!state.enabled) return;
if (!(await _acp())) {
state.enabled = false;
return;
}
if (!(await _rcq())) {
log("\u514d\u8d39\u989d\u5ea6\u5df2\u7528\u5b8c\u6216\u672a\u6388\u6743\uff0c\u8bf7\u5f00\u901a Pro", "warn");
state.enabled = false;
return;
}
await _sw(project, video);
updateCloudPanelUI();
}
} else {
log(`${project.projectName} \u89c6\u9891\u5df2\u5168\u90e8\u8fbe\u6807`);
}
try {
const books2 = await fetchClassBooks(projectId);
const vids2 = flattenVideosFromBooks(books2, projectId).filter((v) => !isEbookVideo(v));
const allDone = vids2.length > 0 && vids2.every(isVideoDone);
project.videoFinishedStatus = allDone;
if (allDone) {
project.studyProgress = Math.max(Number(project.studyProgress || 0), 100);
} else if (detail?.studySpeed != null) {
project.studyProgress = Number(detail.studySpeed);
}
} catch (_) {}
if (PLATFORM_HAS_EXAM && state.enabled) {
try {
for (let i = 0; i < EXAM_PASS_RETRY && state.enabled; i++) {
const did = await _tea(project);
if (!did) break;
await sleep(800);
}
} catch (e) {
trace(`\u8003\u8bd5\u6d41\u7a0b\uff1a${e.message || e}`);
}
}
if (project.videoFinishedStatus && Number(project.studyProgress) >= 100) {
project.projectFinishedStatus = true;
project.listSource = "completed";
}
syncProjectFinishedFlags(project);
await _rcp();
_upn();
}
async function courseHasPendingWork(project) {
try {
const books = await fetchClassBooks(project.projectId);
const videos = flattenVideosFromBooks(books, project.projectId).filter((v) => !isEbookVideo(v));
if (videos.some((v) => !isVideoDone(v))) return true;
if (PLATFORM_HAS_EXAM) {
try {
const papers = await fetchPaperList(9);
if (papers.some(paperUnfinished)) return true;
} catch (_) {}
}
return false;
} catch (_) {
return true;
}
}
async function _rse() {
if (state.engineRunning) return;
state.engineRunning = true;
try {
while (state.enabled) {
const queue = loadQueue().filter(Boolean);
if (!queue.length) {
state.enabled = false;
break;
}
let progressed = false;
for (const projectId of queue) {
if (!state.enabled) break;
const project = state.panelProjects.find((p) => p.projectId === projectId);
if (!project) continue;
if (isProjectFullyFinished(project)) continue;
const hasWork = await courseHasPendingWork(project);
if (!hasWork) {
if (
Number(project.studyProgress) >= 100 ||
Number(project.isPass) === 1 ||
Number(project.studyStatus) === 2
) {
project.projectFinishedStatus = true;
syncProjectFinishedFlags(project);
}
continue;
}
if (!(await _acp())) {
state.enabled = false;
break;
}
progressed = true;
try {
await _sp(project);
} catch (e) {
const msg = `${project.projectName} \u5f02\u5e38\uff1a${e.message || e}`;
log(msg, "warn");
trace(msg);
await sleep(3000);
}
}
if (!progressed) {
let anyWork = false;
for (const projectId of queue) {
if (!state.enabled) break;
const project = state.panelProjects.find((p) => p.projectId === projectId);
if (!project || isProjectFullyFinished(project)) continue;
try {
if (await courseHasPendingWork(project)) {
anyWork = true;
break;
}
} catch (_) {
anyWork = true;
break;
}
}
if (!anyWork) {
state.enabled = false;
break;
}
}
await sleep(ENGINE_TICK_MS);
}
} finally {
state.engineRunning = false;
state.lastUserAction = "";
_upn();
if (!state.enabled) log("\u5df2\u505c\u6b62");
}
}
function readPanelPos() {
return readJson(PANEL_POS_KEY, null);
}
function writePanelPos(pos) {
writeJson(PANEL_POS_KEY, pos);
}
function readPanelCollapsed() {
return pageStorage().getItem(PANEL_COLLAPSED_KEY) === "1";
}
function writePanelCollapsed(v) {
pageStorage().setItem(PANEL_COLLAPSED_KEY, v ? "1" : "0");
}
function applyPanelCollapsed(panel, collapsed) {
const body = panel.querySelector("#hnzj-panel-body");
const footer = panel.querySelector("#hnzj-panel-footer");
const extra = panel.querySelector(".hnzj-footer-extra");
const btnMin = panel.querySelector("#hnzj-btn-min");
const btnMax = panel.querySelector("#hnzj-btn-max");
if (collapsed) {
if (body) body.style.display = "none";
if (footer) footer.style.display = "none";
if (extra) extra.style.display = "none";
if (btnMin) btnMin.style.display = "none";
if (btnMax) btnMax.style.display = "";
} else {
if (body) body.style.display = "";
if (footer) footer.style.display = "";
if (extra) extra.style.display = "";
if (btnMin) btnMin.style.display = "";
if (btnMax) btnMax.style.display = "none";
}
}
function enablePanelDrag(panel) {
const hd = panel.querySelector("#hnzj-panel-header");
if (!hd) return;
let ox = 0;
let oy = 0;
let dragging = false;
hd.addEventListener("mousedown", (e) => {
if (e.target.closest("button")) return;
dragging = true;
const rect = panel.getBoundingClientRect();
ox = e.clientX - rect.left;
oy = e.clientY - rect.top;
e.preventDefault();
});
window.addEventListener("mousemove", (e) => {
if (!dragging) return;
const left = Math.max(0, e.clientX - ox);
const top = Math.max(0, e.clientY - oy);
panel.style.left = `${left}px`;
panel.style.top = `${top}px`;
panel.style.right = "auto";
});
window.addEventListener("mouseup", () => {
if (!dragging) return;
dragging = false;
writePanelPos({
left: parseInt(panel.style.left || "0", 10),
top: parseInt(panel.style.top || "0", 10),
});
});
}
function switchPanelTab(tab) {
document.querySelectorAll("#hnzj-auto-panel .hnzj-tab-btn").forEach((b) => {
b.classList.toggle("active", b.dataset.tab === tab);
});
document.querySelectorAll("#hnzj-auto-panel .hnzj-pane").forEach((p) => {
p.classList.toggle("active", p.dataset.pane === tab);
});
if (tab === "chapter" && loadQueue().length && !state.chapterPreview.length && !state.chapterPreviewLoading) {
state.chapterPreviewLoading = true;
renderChapterPreview();
void _rcp()
.catch((e) => trace(`\u7ae0\u8282\u9884\u89c8\u5931\u8d25\uff1a${e.message || e}`))
.finally(() => {
state.chapterPreviewLoading = false;
_upn();
});
}
}
function updateCloudPanelUI() {
const tierEl = document.getElementById("hnzj-cloud-tier");
const freeEl = document.getElementById("hnzj-cloud-free");
const freeLabelEl = document.getElementById("hnzj-cloud-free-label");
const freeRow = document.getElementById("hnzj-cloud-free-row");
const tokenInput = document.getElementById("hnzj-cloud-token");
if (tokenInput) tokenInput.disabled = false;
if (tierEl) {
tierEl.textContent = formatCloudTierText(state.cloudTier);
tierEl.style.color =
state.cloudRevoked || String(state.cloudTier).toLowerCase() === "revoked" ? "#dc2626" : "#0f172a";
}
if (state.cloudRevoked) {
if (freeRow) freeRow.style.display = "none";
} else if (isCloudProTier()) {
if (freeRow) freeRow.style.display = "";
if (freeLabelEl) freeLabelEl.textContent = "\u6709\u6548\u671f";
if (freeEl) freeEl.textContent = formatExpireText(state.cloudProExpireAt || state.cloudLeaseExp);
} else {
if (freeRow) freeRow.style.display = "";
if (freeLabelEl) freeLabelEl.textContent = "\u514d\u8d39\u989d\u5ea6";
const lim = Math.max(1, Number(state.freeChapterLimit) || DEFAULT_FREE_CHAPTER_LIMIT);
const used = Math.max(0, Number(state.freeUsedChapters) || 0);
if (freeEl) freeEl.textContent = `${used}/${lim} \u89c6\u9891\u7ae0\u8282`;
}
if (tokenInput && tokenInput !== document.activeElement) tokenInput.value = state.cloudToken || "";
}
function recomputeQueueStats() {
const q = loadQueue();
let total = 0;
let done = 0;
let allDone = 0;
for (const id of q) {
const p = state.panelProjects.find((x) => x.projectId === id);
if (!p) continue;
const preview = state.chapterPreview.filter((c) => c.projectId === id);
if (preview.length) {
total += preview.length;
done += preview.filter((c) => c.done).length;
} else {
total += 1;
if (isProjectFullyFinished(p)) done += 1;
}
if (isProjectFullyFinished(p)) allDone += 1;
}
state.queueWareTotal = total;
state.queueWareDone = done;
return { total, done, allDone, selected: q.length };
}
function _upn() {
const status = document.getElementById("hnzj-auto-status");
if (status) {
status.textContent = state.enabled ? `\u8fd0\u884c\u4e2d · ${CV_INTERVAL_MINUTES}\u5206\u949f/\u6b21` : "\u5df2\u505c\u6b62";
status.style.color = state.enabled ? "#15803d" : "#64748b";
}
const stats = recomputeQueueStats();
const doneEl = document.getElementById("hnzj-queue-done");
const totalEl = document.getElementById("hnzj-queue-total");
const examEl = document.getElementById("hnzj-queue-exam");
const pctEl = document.getElementById("hnzj-queue-percent");
const bar = document.getElementById("hnzj-queue-progress");
const pct = stats.total ? Math.round((stats.done / stats.total) * 100) : 0;
if (doneEl) doneEl.textContent = String(stats.done);
if (totalEl) totalEl.textContent = String(stats.total);
if (examEl) examEl.textContent = stats.selected ? `${stats.allDone}/${stats.selected}` : "—";
if (pctEl) pctEl.textContent = `${pct}%`;
if (bar) bar.style.width = `${pct}%`;
const userEl = document.getElementById("hnzj-current-user");
if (userEl) {
const u = state.userProfile;
const name = String(u?.name || "").trim();
userEl.textContent = name || (getAccountId() ? "\u5df2\u767b\u5f55" : "—");
}
const courseEl = document.getElementById("hnzj-current-course");
const chapterEl = document.getElementById("hnzj-current-chapter");
const taskEl = document.getElementById("hnzj-current-task");
if (taskEl) taskEl.textContent = state.lastUserAction || "\u52fe\u9009\u8bfe\u7a0b\u540e\u70b9\u300c\u5f00\u59cb\u300d";
if (courseEl && !state.enabled) courseEl.textContent = "\u65e0";
if (chapterEl && !state.enabled) chapterEl.textContent = "\u65e0";
const startBtn = document.getElementById("hnzj-start");
const stopBtn = document.getElementById("hnzj-stop");
if (startBtn) startBtn.disabled = !!state.enabled;
if (stopBtn) {
stopBtn.disabled = !state.enabled;
stopBtn.classList.toggle("hnzj-btn-off", !state.enabled);
}
const qText = document.getElementById("hnzj-queue-text");
if (qText) qText.textContent = stats.selected ? `\u5df2\u9009 ${stats.selected} \u9879` : "\u672a\u9009\u62e9";
_rcl();
renderChapterPreview();
updateCloudPanelUI();
renderPanelNotice();
}
function _rcl() {
const box = document.getElementById("hnzj-course-list");
if (!box) return;
if (state.panelRefreshing) {
box.innerHTML = `\u52a0\u8f7d\u4e2d…
`;
return;
}
const q = new Set(loadQueue().map(String));
if (!state.panelProjects.length) {
const ok = hasPlatformAuth();
box.innerHTML = `${
ok
? `\u6682\u65e0${studyClassTypeLabel(getStudyClassType())}\u8bfe\u7a0b\uff0c\u8bf7\u5207\u6362\u7c7b\u578b\u6216\u5230\u5b98\u7f51\u300c\u6211\u7684\u5b66\u4e60\u300d\u540e\u518d\u5237\u65b0\u3002`
: "\u8bf7\u5148\u767b\u5f55\u5b98\u7f51\uff0c\u6253\u5f00\u300c\u6211\u7684\u5b66\u4e60\u300d\uff0c\u518d\u70b9\u5237\u65b0\u3002"
}
`;
return;
}
const projects = visiblePanelProjects();
if (!projects.length) {
box.innerHTML = `\u5f53\u524d\u300c${studyClassTypeLabel(getStudyClassType())}\u300d\u4e0b\u6682\u65e0\u8bfe\u7a0b\uff0c\u8bf7\u5207\u6362\u7c7b\u578b\u540e\u5237\u65b0\u3002
`;
return;
}
box.innerHTML = projects
.map((p) => {
const checked = q.has(String(p.projectId)) ? "checked" : "";
const disabled = !isProjectSelectable(p) ? "disabled" : "";
const expired = isProjectExpired(p);
const done = isProjectFullyFinished(p);
const action = projectActionLabel(p);
const actionCls = expired ? "hnzj-action-expired" : done ? "hnzj-action-done" : "hnzj-action-go";
const deadline = formatCourseDeadline(p);
const credit =
p.projectCredit != null && p.projectCredit !== ""
? `${escHtml(String(p.projectCredit))}\u5b66\u65f6`
: "";
const yearTag = p.year ? `${escHtml(p.year)}` : "";
const typeTag = `${escHtml(studyClassTypeShortLabel(p.classType))}`;
const itemCls = [
"hnzj-course-item",
expired ? "hnzj-course-expired" : "",
done ? "hnzj-course-done" : "",
]
.filter(Boolean)
.join(" ");
return ``;
})
.join("");
box.querySelectorAll("input[type=checkbox]").forEach((inp) => {
inp.addEventListener("change", () => {
const id = String(inp.getAttribute("data-pid") || "");
let list = loadQueue().map(String);
if (inp.checked) {
if (!list.includes(id)) list.push(id);
} else {
list = list.filter((x) => x !== id);
}
saveQueue(list);
state.chapterPreviewLoading = true;
renderChapterPreview();
void _rcp()
.catch((e) => trace(`\u7ae0\u8282\u9884\u89c8\u5931\u8d25\uff1a${e.message || e}`))
.finally(() => {
state.chapterPreviewLoading = false;
_upn();
});
});
});
}
function renderChapterPreview() {
const box = document.getElementById("hnzj-chapter-preview");
if (!box) return;
const q = loadQueue().map(String).filter(Boolean);
if (!q.length) {
box.innerHTML = `\u8bf7\u52fe\u9009\u8bfe\u7a0b
`;
return;
}
if (state.chapterPreviewLoading && !state.chapterPreview.length) {
box.innerHTML = `\u6b63\u5728\u52a0\u8f7d\u89c6\u9891\u7ae0\u8282…
`;
return;
}
if (!state.chapterPreview.length) {
box.innerHTML = `\u5df2\u52fe\u9009 ${q.length} \u9879\uff0c\u4f46\u672a\u8bfb\u5230\u89c6\u9891\u3002\u8bf7\u70b9\u300c\u5237\u65b0\u300d\u540e\u91cd\u8bd5\u3002
`;
return;
}
const groups = new Map();
for (const c of state.chapterPreview) {
const pid = String(c.projectId);
if (!groups.has(pid)) groups.set(pid, []);
groups.get(pid).push(c);
}
const html = [];
for (const [pid, items] of groups) {
const p = state.panelProjects.find((x) => String(x.projectId) === pid);
const title = p?.projectName || items[0]?.projectName || pid;
const wareDone = items.filter((x) => x.done).length;
html.push(`
${escHtml(title)}
\u89c6\u9891 ${wareDone}/${items.length}
${items
.map((c) => {
const pct = wareProgressPct(c.studied, c.total, c.done);
return `
${escHtml(c.name)}
${c.done ? "\u5df2\u8fbe\u6807" : "\u672a\u5b8c\u6210"} · ${formatMinutes(c.studied)} / ${formatMinutes(c.total)}\uff08${pct}%\uff09
`;
})
.join("")}
`);
}
box.innerHTML = html.join("") || `\u6682\u65e0\u89c6\u9891
`;
}
async function _rcp() {
const q = loadQueue().map(String).filter(Boolean);
const rows = [];
if (!q.length) {
state.chapterPreview = [];
return;
}
for (const id of q) {
const p = state.panelProjects.find((x) => String(x.projectId) === String(id));
if (!p) {
rows.push({
projectId: id,
projectName: id,
name: "\uff08\u5217\u8868\u4e2d\u627e\u4e0d\u5230\u8be5\u8bfe\u7a0b\uff0c\u8bf7\u5148\u5237\u65b0\uff09",
studied: 0,
total: 0,
done: false,
kind: "ware",
});
continue;
}
try {
const books = await fetchClassBooks(id);
const videos = flattenVideosFromBooks(books, id).filter((v) => !isEbookVideo(v));
if (!videos.length) {
rows.push({
projectId: id,
projectName: p.projectName,
name: "\u672a\u83b7\u53d6\u5230\u89c6\u9891\u5217\u8868",
studied: 0,
total: 0,
done: false,
kind: "ware",
});
} else {
for (const v of videos) {
const done = isVideoDone(v);
rows.push({
projectId: id,
projectName: p.projectName,
videoId: String(v.myClassCourseVideoId || ""),
name: videoNameOf(v),
studied: videoDisplayStudiedSeconds(v),
total: videoTotalSeconds(v),
done,
kind: "ware",
});
}
const allDone = videos.every(isVideoDone);
if (allDone) {
p.videoFinishedStatus = true;
p.studyProgress = Math.max(Number(p.studyProgress || 0), 100);
}
syncProjectFinishedFlags(p);
}
} catch (e) {
trace(`preview ${id}: ${e.message || e}`);
rows.push({
projectId: id,
projectName: p.projectName,
name: `\u52a0\u8f7d\u5931\u8d25\uff1a${e.message || e}`,
studied: 0,
total: 0,
done: false,
kind: "ware",
});
}
}
state.chapterPreview = rows;
}
function formatExamScore(score) {
const n = Number(score);
if (!Number.isFinite(n)) return "";
return Number.isInteger(n) ? `${n}\u5206` : `${Number(n.toFixed(1))}\u5206`;
}
function examLabelForProject(project, papers) {
const pid = String(project?.projectId || "");
const mine = (papers || []).filter((row) => String(row.myClassId || "") === pid);
if (!mine.length) {
return { examRequired: false, examDone: true, examLabel: "\u65e0\u8bd5\u5377", examScore: null };
}
const pending = mine.filter(paperNeedsExam);
if (pending.length) {
const lastScore = Number(pending[0].score);
const scoreHint =
Number.isFinite(lastScore) && lastScore > 0
? `\uff08\u4e0a\u6b21${formatExamScore(lastScore)}\uff09`
: "";
return {
examRequired: true,
examDone: false,
examScore: Number.isFinite(lastScore) ? lastScore : null,
examLabel:
(pending.length === 1 ? "\u5f85\u8003\u8bd5" : `\u5f85\u8003\u8bd5 ${pending.length} \u4efd`) + scoreHint,
};
}
const passed = mine.filter(
(row) =>
Number(row.isPass) === 1 ||
String(row.statusName || "").includes("\u5df2\u901a\u8fc7") ||
(Number.isFinite(Number(row.score)) &&
Number(row.score) >= Number(row.passScore ?? 60))
);
if (passed.length) {
const best = Math.max(...passed.map((r) => Number(r.score) || 0));
const scoreText = formatExamScore(best);
return {
examRequired: false,
examDone: true,
examScore: best,
examLabel: scoreText ? `\u5df2\u901a\u8fc7 ${scoreText}` : "\u5df2\u901a\u8fc7",
};
}
const blocked = mine.filter(
(row) =>
Number(row.studySpeed || 0) < 100 || String(row.statusName || "").includes("\u672a\u5b8c\u6210\u5b66\u4e60")
);
if (blocked.length) {
return { examRequired: false, examDone: false, examScore: null, examLabel: "\u9700\u5148\u5b66\u5b8c" };
}
const anyScore = Math.max(...mine.map((r) => Number(r.score) || 0), 0);
if (anyScore > 0) {
return {
examRequired: false,
examDone: true,
examScore: anyScore,
examLabel: `\u6210\u7ee9 ${formatExamScore(anyScore)}`,
};
}
return { examRequired: false, examDone: true, examScore: null, examLabel: "\u65e0\u5f85\u8003" };
}
async function enrichExamStatusForProjects(list, limit) {
const targets = (list || []).slice(0, Math.max(1, limit || 12));
let papers = null;
try {
papers = await fetchPaperList(9);
if (!papers.length) papers = await fetchPaperList(1);
} catch (_) {
papers = [];
}
for (const p of targets) {
if (!p || p._examEnriched) continue;
const st = examLabelForProject(p, papers);
p.examRequired = st.examRequired;
p.examDone = st.examDone;
p.examLabel = st.examLabel;
p.examScore = st.examScore;
p._examEnriched = true;
syncProjectFinishedFlags(p);
_upn();
await sleep(60);
}
}
async function _rpp(opt) {
const silent = !!opt?.silent;
state.panelRefreshing = true;
_upn();
try {
for (let i = 0; i < 8 && !getAccountId(); i++) {
const cms = readCmsAccountInfo();
if (cms?.accountId) rememberAccountId(cms.accountId);
await sleep(400);
}
if (!getAccountId()) {
if (mergeCapturedIntoPanel(true)) {
if (!silent) trace(`\u5df2\u7528\u9875\u9762\u7f13\u5b58\u5217\u8868\uff1a${state.panelProjects.length} \u9879`);
return;
}
throw new Error("\u672a\u83b7\u53d6 accountId\uff0c\u8bf7\u786e\u8ba4\u5df2\u767b\u5f55\u540e\u70b9\u5237\u65b0");
}
try {
await _eup(false);
} catch (e) {
trace(`\u7528\u6237\u4fe1\u606f: ${e.message || e}`);
}
state.panelProjects = await _fap();
await enrichProjectNames(state.panelProjects, 50);
if (!state.panelProjects.length) {
mergeCapturedIntoPanel(true);
await enrichProjectNames(state.panelProjects, 50);
}
await _rcp();
if (PLATFORM_HAS_EXAM) {
void enrichExamStatusForProjects(
[
...state.panelProjects.filter((p) => loadQueue().map(String).includes(String(p.projectId))),
...state.panelProjects.filter((p) => p.listSource === "learning"),
...state.panelProjects.filter((p) => p.listSource === "completed"),
],
20
);
}
const visible = visiblePanelProjects();
const nLearn = visible.filter((p) => p.listSource === "learning").length;
const nDone = visible.filter((p) => p.listSource === "completed").length;
if (!silent) trace(`\u5df2\u5237\u65b0\uff1a${studyClassTypeLabel(getStudyClassType())} ${nLearn + nDone} \u9879\uff08\u6b63\u5728\u5b66 ${nLearn} · \u5df2\u5b8c\u6210 ${nDone}\uff09`);
} catch (e) {
if (mergeCapturedIntoPanel(true)) {
trace(`\u63a5\u53e3\u5f02\u5e38\uff0c\u5df2\u663e\u793a\u62e6\u622a\u5217\u8868\uff1a${e.message || e}`);
} else {
trace(`\u5237\u65b0\u5931\u8d25\uff1a${e.message || e}`);
}
} finally {
state.panelRefreshing = false;
pruneHiddenQueue();
_upn();
}
}
function selectUnfinishedProjects() {
const list = visiblePanelProjects().filter(isProjectSelectable).map((p) => p.projectId);
saveQueue(list);
state.chapterPreviewLoading = true;
renderChapterPreview();
void _rcp()
.catch((e) => log(`\u7ae0\u8282\u9884\u89c8\u5931\u8d25\uff1a${e.message || e}`))
.finally(() => {
state.chapterPreviewLoading = false;
_upn();
});
trace(`\u5df2\u52fe\u9009\u672a\u5b8c\u6210 ${list.length} \u9879`);
}
function clearProjectSelection() {
saveQueue([]);
state.chapterPreview = [];
_upn();
}
function injectPanelStyles() {
const id = "hnzj-panel-style-v3";
if (document.getElementById(id)) return;
document.getElementById("hnzj-panel-style-v2")?.remove();
document.getElementById("hnzj-panel-style-v1")?.remove();
const style = document.createElement("style");
style.id = id;
style.textContent = `
#hnzj-auto-panel{position:fixed;right:20px;top:80px;z-index:999999;width:412px;max-width:calc(100vw - 24px);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;max-height:min(92vh,780px);}
#hnzj-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;}
#hnzj-panel-brand{display:flex;align-items:center;gap:9px;min-width:0;flex:1;}
#hnzj-panel-logo{width:30px;height:30px;border-radius:9px;object-fit:cover;display:block;flex:0 0 auto;background:#e2e8f0;}
#hnzj-panel-title{font-size:13px;font-weight:900;color:#9a3412;line-height:1.26;}
#hnzj-panel-sub{margin-top:3px;font-size:11px;color:#7c2d12;font-weight:700;}
#hnzj-panel-controls{display:flex;gap:5px;}
.hnzj-panel-ctl{border:none;background:#fff;color:#64748b;width:28px;height:28px;border-radius:999px;cursor:pointer;font-size:15px;font-weight:900;}
#hnzj-panel-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;}
#hnzj-panel-footer{padding:7px 11px;background:#eef2f7;border-top:1px solid #dbe4f0;font-size:12px;}
.hnzj-footer-extra{padding:6px 9px;background:#f8fafc;border-top:1px solid #e2e8f0;}
.hnzj-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;}
.hnzj-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;}
.hnzj-card{background:#fff;border:1px solid #d9e2ee;border-radius:12px;padding:7px 9px;margin-bottom:7px;}
.hnzj-status-row{display:flex;justify-content:space-between;align-items:center;gap:7px;}
.hnzj-status-metrics{font-size:12px;color:#475569;}
.hnzj-status-metrics em{font-style:normal;font-weight:900;color:#0f172a;}
.hnzj-progress-pct{font-size:14px;font-weight:900;color:#0369a1;}
.hnzj-progress-bar{height:5px;border-radius:999px;background:#e2e8f0;overflow:hidden;margin-top:4px;}
.hnzj-progress-bar>span{display:block;height:100%;width:0;background:linear-gradient(90deg,#22d3ee,#2563eb);transition:width .2s ease;}
.hnzj-tabbar{display:flex;gap:6px;margin-bottom:7px;}
.hnzj-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;}
.hnzj-tab-btn.active{background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;border-color:transparent;}
.hnzj-pane{display:none;}.hnzj-pane.active{display:block;}
.hnzj-list-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;gap:6px;flex-wrap:wrap;}
.hnzj-list-title{font-size:12px;color:#64748b;font-weight:700;}
.hnzj-list-tag{font-size:11px;color:#92400e;background:#ffedd5;border:1px solid #fdba74;border-radius:999px;padding:2px 7px;}
.hnzj-list-head-actions{display:flex;gap:6px;align-items:center;flex-wrap:nowrap;justify-content:flex-start;flex:1 1 100%;}
.hnzj-list-head-actions .hnzj-btn{flex:0 0 auto !important;width:auto;min-width:0;white-space:nowrap;padding:4px 8px;font-size:11px;line-height:1.2;}
.hnzj-btn{border:none;color:#fff;padding:7px 9px;border-radius:10px;cursor:pointer;font-weight:800;font-size:12px;}
.hnzj-btn-ghost{background:#fff !important;color:#0f172a !important;border:1px solid #cbd5e1;}
.hnzj-btn-start{flex:1;background:#16a34a;}
.hnzj-btn-stop{flex:1;background:#ef4444;}
.hnzj-btn-off,.hnzj-btn:disabled{background:#cbd5e1 !important;color:#64748b !important;cursor:not-allowed;}
.hnzj-btn-row{display:flex;gap:7px;}
.hnzj-btn-pro{border:none;background:linear-gradient(135deg,#f59e0b,#ea580c);color:#fff;border-radius:8px;padding:5px 10px;font-weight:800;cursor:pointer;font-size:12px;}
#hnzj-course-list,#hnzj-chapter-preview,#hnzj-run-log{max-height:240px;overflow-y:auto;background:#f8fafc;border:1px solid #dbe4f0;border-radius:11px;padding:5px;}
.hnzj-empty-state{padding:12px 7px;text-align:center;color:#94a3b8;font-size:12px;font-weight:700;}
.hnzj-course-item{display:flex;gap:8px;align-items:center;border:1px solid #e2e8f0;border-radius:10px;padding:8px;margin-bottom:6px;cursor:pointer;background:#fff;}
.hnzj-course-item.hnzj-course-expired{opacity:.78;background:#f8fafc;}
.hnzj-course-item.hnzj-course-done{background:#fafafa;}
.hnzj-course-item input[type=checkbox]{margin-top:2px;flex:0 0 auto;}
.hnzj-course-main{flex:1;min-width:0;}
.hnzj-course-side{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:4px;width:62px;}
.hnzj-course-name{font-size:12px;line-height:1.38;font-weight:700;color:#0f172a;}
.hnzj-course-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px;}
.hnzj-tag{font-size:10px;font-weight:700;line-height:1.2;padding:2px 6px;border-radius:999px;border:1px solid;}
.hnzj-tag-hour{color:#92400e;background:#fef9c3;border-color:#fde047;}
.hnzj-tag-year{color:#475569;background:#f1f5f9;border-color:#cbd5e1;}
.hnzj-tag-type{color:#b91c1c;background:#fef2f2;border-color:#fecaca;}
.hnzj-course-deadline{margin-top:4px;font-size:10px;color:#94a3b8;line-height:1.35;}
.hnzj-ring{position:relative;width:44px;height:44px;}
.hnzj-ring svg{width:44px;height:44px;transform:rotate(-90deg);}
.hnzj-ring-bg{fill:none;stroke:#f1f5f9;stroke-width:3;}
.hnzj-ring-fg{fill:none;stroke:#ef4444;stroke-width:3;stroke-linecap:round;transition:stroke-dashoffset .2s ease;}
.hnzj-ring-num,.hnzj-ring-done span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:900;color:#ef4444;line-height:1;}
.hnzj-ring-done span{font-size:10px;color:#ef4444;}
.hnzj-course-action{font-size:10px;font-weight:800;line-height:1.2;text-align:center;white-space:nowrap;}
.hnzj-action-go{color:#ef4444;}
.hnzj-action-expired{color:#94a3b8;}
.hnzj-action-done{color:#64748b;}
.hnzj-chapter-item{display:flex;gap:7px;align-items:flex-start;border:1px solid #e2e8f0;border-radius:8px;padding:7px;margin-bottom:6px;cursor:pointer;background:#f8fafc;}
.hnzj-course-name{font-size:12px;line-height:1.38;font-weight:700;display:flex;align-items:flex-start;gap:6px;flex-wrap:wrap;}
.hnzj-course-meta{font-size:11px;color:#64748b;margin-top:2px;line-height:1.4;}
.hnzj-badge{font-size:10px;font-weight:800;padding:2px 6px;border-radius:999px;border:1px solid;line-height:1.3;}
.hnzj-badge-learn{color:#1d4ed8;background:#eff6ff;border-color:#93c5fd;}
.hnzj-badge-expired{color:#64748b;background:#f1f5f9;border-color:#cbd5e1;}
.hnzj-badge-done{color:#047857;background:#ecfdf5;border-color:#6ee7b7;}
.hnzj-preview-group{margin-bottom:7px;border:1px solid #dbe4f0;border-radius:9px;background:#fff;padding:0;}
.hnzj-preview-hd{padding:6px 9px;background:#eaf1ff;border-bottom:1px solid #dbe4f0;color:#1d4ed8;font-size:12px;font-weight:700;}
.hnzj-preview-exam{margin:6px 8px;font-size:11px;font-weight:700;color:#9a3412;background:#fff7ed;border:1px solid #fed7aa;border-radius:8px;padding:5px 7px;line-height:1.4;}
.hnzj-preview-group .hnzj-chapter-item{margin:0 6px 6px;background:#f8fafc;}
.hnzj-meta-row{display:flex;justify-content:space-between;gap:7px;font-size:11px;margin-bottom:4px;}
.hnzj-meta-label{color:#64748b;}.hnzj-meta-value{font-weight:700;text-align:right;max-width:68%;word-break:break-all;}
.hnzj-log-line{padding:4px 6px;border-bottom:1px dashed #d4deea;font-size:12px;line-height:1.42;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap;}
.hnzj-log-time{color:#94a3b8;font-size:11px;font-weight:700;flex:0 0 auto;}
.hnzj-log-text{font-weight:700;word-break:break-word;}
.hnzj-log-start .hnzj-log-text{color:#15803d;}
.hnzj-log-study .hnzj-log-text{color:#1d4ed8;}
.hnzj-log-done .hnzj-log-text{color:#047857;}
.hnzj-log-exam .hnzj-log-text{color:#c2410c;}
.hnzj-log-warn .hnzj-log-text{color:#b45309;}
.hnzj-log-stop .hnzj-log-text{color:#64748b;}
.hnzj-log-info .hnzj-log-text{color:#334155;}
.hnzj-log-hint{background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;margin:2px 0;padding:5px 7px !important;border-bottom:none;}
.hnzj-log-hint .hnzj-log-text{color:#0369a1;}
.hnzj-log-tip{margin:0 0 6px;padding:6px 8px;border-radius:8px;background:#eff6ff;border:1px solid #bfdbfe;color:#0369a1;font-size:11px;font-weight:700;line-height:1.45;}
#hnzj-class-type-select{border:1px solid #cbd5e1;border-radius:8px;padding:3px 6px;font-size:12px;margin-left:4px;min-width:88px;}
#hnzj-pro-modal{position:fixed;inset:0;z-index:1000000;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;padding:16px;}
.hnzj-pro-card{width:min(420px,94vw);background:#fff;border-radius:14px;padding:16px;box-shadow:0 20px 50px rgba(0,0,0,.25);}
.hnzj-pro-title{font-size:18px;font-weight:900;color:#9a3412;}
.hnzj-pro-sub{margin-top:6px;font-size:12px;color:#78716c;line-height:1.45;}
.hnzj-pro-sec{margin-top:12px;}
.hnzj-pro-sec h4{margin:0 0 6px;font-size:13px;color:#0f172a;}
.hnzj-pro-sec ul{margin:0;padding-left:18px;font-size:12px;color:#334155;line-height:1.55;}
.hnzj-pro-sec p{margin:0;font-size:12px;color:#475569;line-height:1.5;}
.hnzj-pro-buy-btn{margin-top:8px;border:none;background:linear-gradient(135deg,#f59e0b,#ea580c);color:#fff;border-radius:8px;padding:8px 12px;font-weight:800;cursor:pointer;width:100%;}
.hnzj-pro-actions{margin-top:14px;display:flex;justify-content:flex-end;}
.hnzj-pro-close{border:1px solid #cbd5e1;background:#f8fafc;border-radius:8px;padding:7px 12px;cursor:pointer;font-weight:700;}
#hnzj-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(20px);z-index:1000001;background:rgba(15,23,42,.92);color:#fff;padding:10px 14px;border-radius:10px;font-size:12px;font-weight:700;opacity:0;pointer-events:none;transition:.2s ease;max-width:86vw;}
#hnzj-toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
`;
(document.head || document.documentElement).appendChild(style);
}
function _cp() {
if (document.getElementById("hnzj-auto-panel")) return;
injectPanelStyles();
const panel = document.createElement("div");
panel.id = "hnzj-auto-panel";
panel.innerHTML = `
\u8fd0\u884c\u72b6\u6001
\u5df2\u505c\u6b62
\u8bfe\u4ef6 0/0 · \u8bfe\u7a0b\u5b8c\u6210 —
0%
\u8bfe\u7a0b\u7c7b\u578b
\u767b\u5f55\u540e\u5c06\u81ea\u52a8\u52a0\u8f7d
\u7ae0\u8282\u9884\u89c8\u89c6\u9891
\u8bf7\u52fe\u9009\u8bfe\u7a0b
\u8fd0\u884c\u65e5\u5fd7\u8fdb\u5ea6
⏱ \u770b\u8bfe\u8fdb\u5ea6\u7ea6\u6bcf ${CV_INTERVAL_MINUTES} \u5206\u949f\u66f4\u65b0\u4e00\u6b21\uff0c\u7b49\u5f85\u671f\u95f4\u811a\u672c\u4ecd\u5728\u8fd0\u884c
\u4f1a\u5458\u8bbe\u7f6e
\u4f1a\u5458\u72b6\u6001—
\u514d\u8d39\u989d\u5ea6—
\u6388\u6743\u7801
\u5f53\u524d\u7528\u6237—
\u5f53\u524d\u4efb\u52a1\u52fe\u9009\u8bfe\u7a0b\u540e\u70b9\u300c\u5f00\u59cb\u300d
\u5f53\u524d\u8bfe\u7a0b\u65e0
\u5f53\u524d\u8bfe\u4ef6\u65e0
`;
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("#hnzj-btn-min")?.addEventListener("click", (e) => {
e.stopPropagation();
writePanelCollapsed(true);
applyPanelCollapsed(panel, true);
});
panel.querySelector("#hnzj-btn-max")?.addEventListener("click", (e) => {
e.stopPropagation();
writePanelCollapsed(false);
applyPanelCollapsed(panel, false);
});
panel.querySelectorAll(".hnzj-tab-btn").forEach((btn) => {
btn.addEventListener("click", () => switchPanelTab(btn.dataset.tab));
});
const classTypeSel = panel.querySelector("#hnzj-class-type-select");
if (classTypeSel) {
classTypeSel.value = String(getStudyClassType());
classTypeSel.addEventListener("change", () => {
setStudyClassType(Number(classTypeSel.value));
pruneHiddenQueue();
void _rpp({ reason: "classType" });
});
}
document.getElementById("hnzj-refresh")?.addEventListener("click", () => {
void _rpp({ reason: "manual" });
});
document.getElementById("hnzj-select-unfinished")?.addEventListener("click", () => selectUnfinishedProjects());
document.getElementById("hnzj-clear-select")?.addEventListener("click", () => clearProjectSelection());
document.getElementById("hnzj-cloud-save")?.addEventListener("click", () => {
const input = document.getElementById("hnzj-cloud-token");
void _sct(input?.value || "").then((ok) => {
updateCloudPanelUI();
showToast(ok ? "\u5df2\u4fdd\u5b58" : "\u4fdd\u5b58\u5931\u8d25");
});
});
document.getElementById("hnzj-open-pro")?.addEventListener("click", () => openProModal());
document.getElementById("hnzj-start")?.addEventListener("click", () => {
const q = loadQueue().filter((id) => {
const p = state.panelProjects.find((x) => x.projectId === id);
return p && isProjectSelectable(p);
});
saveQueue(q);
if (!q.length) {
log("\u8bf7\u5148\u52fe\u9009\u8bfe\u7a0b");
showToast("\u8bf7\u5148\u52fe\u9009\u8bfe\u7a0b");
_upn();
return;
}
void (async () => {
try {
await _ecl(true);
} catch (_) {}
if (!(await _acp())) {
_upn();
return;
}
state.enabled = true;
state.lastUserAction = "";
state._studyWaitHintShown = false;
_upn();
void _rse();
})();
});
document.getElementById("hnzj-stop")?.addEventListener("click", () => {
state.enabled = false;
state.lastUserAction = "";
state._studyWaitHintShown = false;
_upn();
});
_upn();
void _fcc();
}
async function _bst() {
const host = String(location.hostname || "");
if (!/ghlearning\.com$/i.test(host)) return;
const cms = readCmsAccountInfo();
if (cms?.accountId) rememberAccountId(cms.accountId);
installAuthHooks();
const waitBody = () =>
new Promise((r) => {
if (document.body) r();
else document.addEventListener("DOMContentLoaded", r, { once: true });
});
await waitBody();
await sleep(600);
_cp();
await _rpp({ reason: "boot", silent: true });
try {
await _ecl(false);
} catch (_) {}
if (!state.panelProjects.length) {
for (let i = 0; i < 6 && !state.panelProjects.length; i++) {
await sleep(800);
if (mergeCapturedIntoPanel(true)) break;
if (getAccountId()) {
await _rpp({ reason: "retry", silent: true });
break;
}
}
}
const panel = document.getElementById("hnzj-auto-panel");
state._bootLoaded = true;
if (!getAccountId()) {
trace("\u672a\u767b\u5f55\uff0c\u8bf7\u6253\u5f00\u6211\u7684\u5b66\u4e60\u540e\u5237\u65b0");
} else if (!state.panelProjects.length) {
trace("\u5217\u8868\u4e3a\u7a7a\uff0c\u8bf7\u6253\u5f00\u6211\u7684\u5b66\u4e60\u9875\u540e\u518d\u5237\u65b0");
}
}
_bst().catch((e) => trace(e));
})();