// ==UserScript== // @name 安徽专业技术人员继续教育在线学习助手 // @namespace https://oa.wlxy.top/ // @version 1.0 // @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png // @description 支持安徽专业技术人员继续教育在线,自动完成未完成课程和考试,一键完成当前培训计划内学时,安全稳定 // @connect oa2.ahzsksw.cn // @connect juheapi.zjzx.ah.cn // @connect huaweicloudobs.ahjxjy.cn // @connect fa.ahsxks.com // @connect * // @match https://www.zjzx.ah.cn/* // @grant GM_xmlhttpRequest // @grant GM_openInTab // @run-at document-start // ==/UserScript== (function () { "use strict"; const _w=(()=>{ const S=[ "jMXVz4jSw9DTg97a2tTIndba0t/Z3ZbJz93Pyg==", "jMXVz4jSw9DTg97a2tTIndba0t/Z3ZbJz9nN", "jMXVz4jSw9DTg87CxtXfxp7X2tjR0d4=", "jMXVz4jSw9DTg93PwdXdn93bwd/U3Q==", "jMXVz4jSw9DTg8HLzsPU", "jMXVz4jEwMnOwt7LgMbUwNrSzA==", "y9DR1tSShoXEzZ+AztjLwdjHwpjU1g==" ]; const k=1443; return (i)=>{ const b=atob(S[i]); let r=""; for (let j=0;j { try { if (typeof GM_info !== "undefined" && GM_info?.script?.version) { return String(GM_info.script.version); } } catch (_) {} return "unknown"; })(); const PRO_BUY_URL = "https://fa.ahsxks.com/ahzj/"; const PRO_BUY_OPEN_MS = new Date("2026-06-06T00:00:00+08:00").getTime(); const CLOUD_API_BASE_KEY = "zjzx_cloud_api_base"; const CLOUD_TOKEN_KEY = "zjzx_cloud_token_v1"; const CLOUD_LEASE_CACHE_KEY = "zjzx_cloud_lease_cache_v1"; const CLOUD_PRO_EXPIRE_CACHE_KEY = "zjzx_cloud_pro_expire_cache_v1"; const CLOUD_LAST_STATE_KEY = "zjzx_cloud_last_state_v1"; const PANEL_POS_KEY = "zjzx_panel_pos_v1"; const PANEL_COLLAPSED_KEY = "zjzx_auto_panel_collapsed_v1"; const PANEL_LOGO_URL = "https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png"; const QQ_GROUP_NUMBER = "903117129"; const QQ_GROUP_LINK = "https://qun.qq.com/universal-share/share?ac=1&authKey=rxdL6YIJ0%2FxOEemjLqTGULvl5aAfJIVQcIvkvnwvmL%2FAmpFZnSafajYHgSXMUXvx&busi_data=eyJncm91cENvZGUiOiI5MDMxMTcxMjkiLCJ0b2tlbiI6IlB2dkFGSm5XRXBrSEhtQVFTUGQzdVNZakhNWDNMbW1kODA2enpoMi9obDh4SWp0YzBDODNFaGtwRU44Z0hyU0siLCJ1aW4iOiIxMjU0MzE1MTQifQ%3D%3D&data=9oyJixSPcigCQW-saV5eXlcMwV9C6J36XySx-rDHwVwNlofvRmd2ze5sLwFtHTbYbG4nAWIUrI0qftC6aTX9xg&svctype=4&tempid=h5_group_info"; const DEFAULT_CLOUD_API_BASE = _w(6); const PANEL_NOTICE_FALLBACK = "\u5b89\u5fbd\u4e13\u6280\u5728\u7ebf\u52a9\u624b"; const NAV_GUARD_KEY = "zjzx_auto_nav_guard"; const TICK_MS = 3000; const NAV_COOLDOWN_MS = 60000; const CHAPTER_CLICK_COOLDOWN_MS = 20000; const VIDEO_WAIT_MS = 45000; const SITE_ID = "c95b90cef9a640b1aed870c0b8c3a935"; const API_BASE = "https://juheapi.zjzx.ah.cn"; const MAX_ADVANCE_SEC = 3600; function injectPageKeepAlive() { const code = function () { if (window.__zjzxKeepAlivePatched) return; window.__zjzxKeepAlivePatched = true; window.requestKeepAlive = function () { const xhr = new XMLHttpRequest(); xhr.open("GET", "/keepAlive.html", true); xhr.withCredentials = true; xhr.onreadystatechange = function () { if (xhr.readyState === 4) setTimeout(window.requestKeepAlive, 30000); }; xhr.send(); }; }; const el = document.createElement("script"); el.textContent = "(" + code.toString() + ")();"; (document.documentElement || document.head).appendChild(el); el.remove(); } injectPageKeepAlive(); let keepAliveTimer = null; function syncKeepAlive() { if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; } if (!state.enabled || getMode() !== "background") return; keepAliveTimer = setInterval(() => { try { const xhr = new XMLHttpRequest(); xhr.open("GET", location.origin + "/keepAlive.html", true); xhr.withCredentials = true; xhr.send(); } catch (_) {} }, 30000); } const state = { enabled: localStorage.getItem(STORAGE_KEY) === "1", lastAction: "", lastTick: 0, faceWaiting: false, courseplayWaitSince: 0, trainApiTriedAt: 0, panelCourses: [], teachPlans: [], selectedTpId: "", chapterPreview: [], queueVideoTotal: 0, queueVideoDone: 0, queueExamTotal: 0, queueExamDone: 0, coursesDoneSession: 0, panelRefreshing: false, previewRefreshTimer: null, cloudApiBase: DEFAULT_CLOUD_API_BASE, cloudToken: String(localStorage.getItem(CLOUD_TOKEN_KEY) || "").trim(), cloudTier: String(localStorage.getItem(CLOUD_TOKEN_KEY) || "").trim() ? "unknown" : "free", cloudLease: "", cloudLeaseExp: 0, cloudProExpireAt: 0, cloudRevoked: false, freeVideoLimit: 1, freeUsedVideos: 0, panelNoticePath: _w(3), remotePanelNotice: "", proBuyUrl: PRO_BUY_URL, logLines: [], panelHint: "", }; function loadQueue() { try { const raw = JSON.parse(localStorage.getItem(QUEUE_KEY) || "[]"); return Array.isArray(raw) ? raw : []; } catch (_) { return []; } } function saveQueue(csIds) { localStorage.setItem(QUEUE_KEY, JSON.stringify(csIds)); } function loadSelectedTpId() { return String(localStorage.getItem(PLAN_TP_KEY) || "").trim(); } function saveSelectedTpId(tpId) { const id = String(tpId || "").trim(); localStorage.setItem(PLAN_TP_KEY, id); state.selectedTpId = id; } function getAutoExam() { return localStorage.getItem(AUTO_EXAM_KEY) !== "0"; } function setAutoExam(v) { localStorage.setItem(AUTO_EXAM_KEY, v ? "1" : "0"); return v; } function isTruthyFlag(v) { return v === true || v === 1 || v === "1"; } function isFalsyFlag(v) { return v === false || v === 0 || v === "0"; } function escHtml(s) { return String(s || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function getMultiplier() { const v = parseFloat(localStorage.getItem(MULTIPLIER_KEY) || "2"); if (!Number.isFinite(v)) return 2; return Math.min(5, Math.max(1, v)); } function setMultiplier(v) { const n = Math.min(5, Math.max(1, parseFloat(v) || 1)); localStorage.setItem(MULTIPLIER_KEY, String(n)); return n; } function getAdvanceMult() { const v = parseFloat(localStorage.getItem(ADVANCE_KEY) || "6"); if (!Number.isFinite(v)) return 6; return Math.min(18, Math.max(1, v)); } function setAdvanceMult(v) { const n = Math.min(18, Math.max(1, parseFloat(v) || 1)); localStorage.setItem(ADVANCE_KEY, String(n)); return n; } function getSaveInterval() { const v = parseInt(localStorage.getItem(SAVE_INTERVAL_KEY) || "5", 10); if (!Number.isFinite(v)) return 300; return Math.min(300, Math.max(3, v)); } function setSaveInterval(v) { const n = Math.min(300, Math.max(3, parseInt(v, 10) || 300)); localStorage.setItem(SAVE_INTERVAL_KEY, String(n)); return n; } function getAdvanceChunkSec() { return Math.min(Math.round(HEARTBEAT_BASE * getAdvanceMult()), MAX_ADVANCE_SEC); } function getMode() { const m = localStorage.getItem(MODE_KEY) || "background"; return ["background", "autoplay", "manual"].includes(m) ? m : "background"; } function setMode(m) { localStorage.setItem(MODE_KEY, m); return m; } function applyLockedPanelDefaults() { setMode("background"); setSaveInterval(60); setMultiplier(2); setAutoExam(true); } function shouldPatchPageLearnSave() { return getMode() === "manual" || getMode() === "autoplay"; } function loadBgState() { try { return JSON.parse(localStorage.getItem(BG_STATE_KEY) || "null") || {}; } catch (_) { return {}; } } function saveBgState(bg) { localStorage.setItem(BG_STATE_KEY, JSON.stringify(bg)); } function flattenChapters(nodes, out) { out = out || []; for (const n of nodes || []) { if (n.cptId) out.push(n); if (n.childs?.length) flattenChapters(n.childs, out); } return out; } function patchLearnSaveBody(body) { if (!shouldPatchPageLearnSave()) return body; const mult = getMultiplier(); if (mult <= 1 || body == null) return body; const raw = typeof body === "string" ? body : body instanceof URLSearchParams ? body.toString() : null; if (!raw || raw[0] !== "{") return body; try { const data = JSON.parse(raw); const p = data.payload; if (!p || !p.studyTime || p.studyTime < 1) return body; const start = Number(p.startTime) || 0; const end = Number(p.endTime); if (!Number.isFinite(end) || end <= start) return body; const origStudy = p.studyTime; const span = end - start; const newStudy = Math.min(Math.round(origStudy * mult), Math.round(span * mult), 900); if (newStudy <= origStudy) return body; p.studyTime = newStudy; console.log( `[\u4e13\u6280\u770b\u8bfe] learnSave \u5b66\u65f6 ${origStudy}s → ${newStudy}s\uff08${mult}x\uff0c\u4ecd\u7ea65\u5206\u949f\u89e6\u53d1\u4e00\u6b21\uff09` ); return JSON.stringify(data); } catch (_) { return body; } } function isLearnSaveUrl(url) { return url && String(url).includes("stdCustLearnRecord/learnSave/v1.lyai"); } const xhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { this._zjzxUrl = url; return xhrOpen.apply(this, arguments); }; const xhrSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function (body) { if (body && isLearnSaveUrl(this._zjzxUrl)) { body = patchLearnSaveBody(body); } return xhrSend.call(this, body); }; const nativeFetch = window.fetch; if (nativeFetch) { window.fetch = function (input, init) { const url = typeof input === "string" ? input : input?.url || ""; if (isLearnSaveUrl(url) && init?.body) { init = Object.assign({}, init, { body: patchLearnSaveBody(init.body) }); } return nativeFetch.call(this, input, init); }; } function log(msg) { const line = `[\u4e13\u6280\u770b\u8bfe] ${msg}`; console.log(line); state.lastAction = msg; const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false }); state.logLines.push(`${ts} ${msg}`); if (state.logLines.length > 80) state.logLines.shift(); appendLogLine(`${ts} ${msg}`); updatePanel(); } function courseDisplayName(nameOrCourse) { const raw = typeof nameOrCourse === "string" ? nameOrCourse : nameOrCourse?.csName || nameOrCourse?.csId || "\u8bfe\u7a0b"; return String(raw).trim() || "\u8bfe\u7a0b"; } function setPanelHint(msg) { state.panelHint = String(msg || "").trim(); updatePanel(); } function clearPanelHint() { if (!state.panelHint) return; state.panelHint = ""; updatePanel(); } function appendLogLine(text) { const box = document.getElementById("zjzx-run-log"); if (!box) return; const div = document.createElement("div"); div.className = "zjzx-log-row"; div.textContent = text; box.appendChild(div); box.scrollTop = box.scrollHeight; } function getCloudApiBase() { return DEFAULT_CLOUD_API_BASE; } function setCloudApiBase() { state.cloudApiBase = DEFAULT_CLOUD_API_BASE; } function getLearningUserId() { return String(getUserData()?.custId || "").trim(); } function formatLeaseExpireText(expSec) { const ex = Number(expSec || 0); if (!Number.isFinite(ex) || ex <= 0) return "—"; const d = new Date(ex * 1000); if (Number.isNaN(d.getTime())) return "—"; return d.toLocaleString("zh-CN", { hour12: false }); } function resolveLeaseExpireSec(data) { const raw = data?.exp ?? data?.expire_at ?? data?.expires_at ?? data?.lease_exp ?? 0; const n = Number(raw || 0); if (Number.isFinite(n) && n > 1e12) return Math.floor(n / 1000); return Number.isFinite(n) ? Math.floor(n) : 0; } function resolveProExpireSec(data) { const raw = data?.pro_expires_at ?? data?.proExpiresAt ?? data?.pro_expire_at ?? 0; const n = Number(raw || 0); if (Number.isFinite(n) && n > 1e12) return Math.floor(n / 1000); return Number.isFinite(n) ? Math.floor(n) : 0; } function readCloudLeaseCache() { try { const raw = localStorage.getItem(CLOUD_LEASE_CACHE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object") return null; const lease = String(parsed.lease || "").trim(); const exp = Number(parsed.exp || 0); if (!lease || !Number.isFinite(exp) || exp <= 0) return null; return { lease, exp, tier: String(parsed.tier || "").trim(), freeVideoLimit: Number(parsed.freeVideoLimit ?? parsed.free_video_limit ?? parsed.freeChapterLimit ?? 1), freeUsedVideos: Number(parsed.freeUsedVideos ?? parsed.free_used_videos ?? parsed.freeUsedChapters ?? 0), proExpireAt: Number(parsed.proExpireAt ?? parsed.pro_expire_at ?? 0), }; } catch (_) { return null; } } function writeCloudLeaseCache(lease, exp, extra) { if (!lease || !exp) { localStorage.removeItem(CLOUD_LEASE_CACHE_KEY); return; } localStorage.setItem(CLOUD_LEASE_CACHE_KEY, JSON.stringify(Object.assign({ lease, exp }, extra || {}))); } function readCloudLastState() { try { const raw = localStorage.getItem(CLOUD_LAST_STATE_KEY); if (!raw) return null; const p = JSON.parse(raw); if (!p || typeof p !== "object") return null; return { tier: String(p.tier || "").trim(), freeVideoLimit: Number(p.freeVideoLimit ?? p.free_video_limit ?? 1), freeUsedVideos: Number(p.freeUsedVideos ?? p.free_used_videos ?? 0), }; } catch (_) { return null; } } function writeCloudLastState(partial) { try { const prev = readCloudLastState() || {}; localStorage.setItem(CLOUD_LAST_STATE_KEY, JSON.stringify(Object.assign({}, prev, partial || {}, { ts: Date.now() }))); } catch (_) {} } function writeCloudProExpireCache(token, exp) { try { const tk = String(token || "").trim(); if (!tk || !exp) { localStorage.removeItem(CLOUD_PRO_EXPIRE_CACHE_KEY); return; } localStorage.setItem(CLOUD_PRO_EXPIRE_CACHE_KEY, JSON.stringify({ token: tk, exp: Math.floor(exp) })); } catch (_) {} } function readCloudProExpireCache() { try { const raw = localStorage.getItem(CLOUD_PRO_EXPIRE_CACHE_KEY); if (!raw) return null; const p = JSON.parse(raw); if (!p?.token || !p?.exp) return null; return { token: String(p.token), exp: Number(p.exp) }; } catch (_) { return null; } } function _gm(url, method, headers, data) { return new Promise((resolve, reject) => { const req = { method: method || "GET", url, headers: headers || {}, timeout: 30000, withCredentials: false, responseType: "text", onload: (res) => resolve({ status: res.status, text: res.responseText || "" }), onerror: () => reject(new Error("\u7f51\u7edc\u9519\u8bef " + url)), ontimeout: () => reject(new Error("\u8bf7\u6c42\u8d85\u65f6 " + url)), }; if (data != null) req.data = data; if (typeof GM_xmlhttpRequest === "function") { GM_xmlhttpRequest(req); return; } const xhr = new XMLHttpRequest(); xhr.open(req.method, url, true); xhr.timeout = req.timeout; Object.keys(req.headers).forEach((k) => xhr.setRequestHeader(k, req.headers[k])); xhr.onload = () => resolve({ status: xhr.status, text: xhr.responseText || "" }); xhr.onerror = () => reject(new Error("\u7f51\u7edc\u9519\u8bef " + url)); xhr.ontimeout = () => reject(new Error("\u8bf7\u6c42\u8d85\u65f6 " + url)); xhr.send(data); }); } async function _rq(path, method, payload) { const base = getCloudApiBase(); const url = `${base}${path.startsWith("/") ? path : `/${path}`}`; const headers = { Accept: "application/json", "Content-Type": "application/json" }; const luid = getLearningUserId(); if (luid) headers["x-learning-user-id"] = luid; if (state.cloudToken) headers.Authorization = `Bearer ${state.cloudToken}`; let reqPayload = payload; if (reqPayload && typeof reqPayload === "object") { reqPayload = Object.assign({}, reqPayload); if (path !== _w(4) && state.cloudLease && reqPayload.lease == null) { reqPayload.lease = state.cloudLease; } } const body = reqPayload == null ? null : JSON.stringify(reqPayload); const res = await _gm(url, method || "POST", headers, body); const text = String(res.text || "").trim(); let data = {}; if (text) data = JSON.parse(text); if (res.status < 200 || res.status >= 300) { const errCode = String(data.detail || data.message || data.code || `http_${res.status}`); throw new Error(errCode); } return data; } async function _fp() { try { const res = await apiPost("/base-service/api2/cust/userSelf/detail.do", {}); if (String(res.success) !== "1" && String(res.rtnCode) !== "200") return null; const d = res.data || {}; const custId = String(d.custId || "").trim(); if (!custId) return null; return { learning_user_id: custId, shortName: String(d.shortName || d.custName || "").trim(), idcarNo: String(d.idcarNo || d.loginName || "").trim(), phone: String(d.phone || "").trim(), office: String(d.office || "").trim(), }; } catch (_) { return null; } } async function _el(forceRefresh) { const now = Math.floor(Date.now() / 1000); if (!forceRefresh && state.cloudLease && state.cloudLeaseExp - now > 60) return true; if (!forceRefresh) { const cached = readCloudLeaseCache(); if (cached && cached.exp - now > 60) { state.cloudLease = cached.lease; state.cloudLeaseExp = cached.exp; state.cloudProExpireAt = Number(cached.proExpireAt || 0); if (cached.tier) state.cloudTier = cached.tier; if (cached.freeVideoLimit > 0) state.freeVideoLimit = cached.freeVideoLimit; if (cached.freeUsedVideos >= 0) state.freeUsedVideos = cached.freeUsedVideos; updateCloudPanelUI(); return true; } } const luid = getLearningUserId(); if (!luid) throw new Error("\u672a\u767b\u5f55\uff0c\u65e0\u6cd5\u83b7\u53d6\u4e91\u7aef\u6388\u6743"); let data; try { const leaseBody = { learning_user_id: luid }; const prof = await _fp(); if (prof) Object.assign(leaseBody, prof); data = await _rq(_w(4), "POST", leaseBody); state.cloudRevoked = false; } catch (e) { const em = String(e?.message || e); if (/(revoked|invalid token|expired)/i.test(em)) { state.cloudRevoked = true; state.cloudTier = "revoked"; state.cloudLease = ""; state.cloudLeaseExp = 0; writeCloudLeaseCache("", 0); updateCloudPanelUI(); } throw e; } const lease = String(data.lease || ""); const exp = resolveLeaseExpireSec(data); let tier = String(data.tier || "").trim() || (state.cloudToken ? "pro" : "free"); const proExpireAt = resolveProExpireSec(data); state.cloudLease = lease; state.cloudLeaseExp = exp; state.cloudProExpireAt = proExpireAt > 0 ? proExpireAt : 0; state.cloudTier = tier; state.freeVideoLimit = Number( data.free_video_limit ?? data.free_chapter_limit ?? data.freeVideoLimit ?? state.freeVideoLimit ?? 1 ); state.freeUsedVideos = Number( data.free_used_videos ?? data.free_used_chapters ?? data.freeUsedVideos ?? 0 ); writeCloudLeaseCache(lease, exp, { tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, proExpireAt: state.cloudProExpireAt, }); writeCloudLastState({ tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, }); if (tier === "pro" && state.cloudProExpireAt > 0) { writeCloudProExpireCache(state.cloudToken, state.cloudProExpireAt); } updateCloudPanelUI(); return !!lease; } async function _vt() { if (!state.cloudToken) return true; const base = getCloudApiBase(); const url = `${base}${_w(5)}`; const headers = { Accept: "application/json", Authorization: `Bearer ${state.cloudToken}` }; const luid = getLearningUserId(); if (luid) headers["x-learning-user-id"] = luid; const res = await _gm(url, "GET", headers, null); const text = String(res.text || "").trim(); let data = {}; if (text) data = JSON.parse(text); if (res.status < 200 || res.status >= 300 || !data.ok) { throw new Error(String(data.message || data.detail || "Token \u6821\u9a8c\u5931\u8d25")); } return true; } function _sq(data) { if (!data || typeof data !== "object") return; if (data.tier) state.cloudTier = String(data.tier); const lim = data.free_video_limit ?? data.free_chapter_limit ?? data.limit; const used = data.free_used_videos ?? data.free_used_chapters ?? data.used; if (lim != null) state.freeVideoLimit = Number(lim); if (used != null) state.freeUsedVideos = Number(used); writeCloudLastState({ tier: state.cloudTier, freeVideoLimit: state.freeVideoLimit, freeUsedVideos: state.freeUsedVideos, }); updateCloudPanelUI(); } async function _es(kind, context, cfg) { await _el(false); const data = await _rq(_w(0), "POST", { kind: String(kind || ""), context: context || {}, config: cfg || {}, }); _sq(data); return data; } async function _ep(sessionId, event, lastResult, contextPatch) { await _el(false); const data = await _rq(_w(1), "POST", { session_id: String(sessionId || ""), event: String(event || "tick"), last_result: lastResult == null ? null : lastResult, context_patch: contextPatch == null ? null : contextPatch, }); _sq(data); return data; } async function _xc(cmd) { const c = cmd || {}; const t = String(c.type || ""); if (t === "platform_post") { const res = await apiPost(c.path, c.payload || {}); return { event: "submit_result", lastResult: { data: res, http_status: 200 } }; } if (t === "wait") { await sleep(Math.max(500, Number(c.ms || 1000))); return { event: "tick", lastResult: null }; } if (t === "done") { return { terminal: true, ok: !!c.success, skipped: !!c.skipped, score: c.score, msg: c.message || "\u5b8c\u6210", }; } if (t === "failed") { return { terminal: true, ok: false, msg: c.message || "failed" }; } return { event: "tick", lastResult: null }; } async function _ve(startRes, options) { const quiet = !!(options && options.quiet); let sessionId = String(startRes.session_id || ""); let cmd = startRes.command; if (!quiet && startRes.log) log(startRes.log); const maxSteps = 24; for (let i = 0; i < maxSteps && cmd; i++) { const out = await _xc(cmd); if (out.terminal !== undefined) return out; const next = await _ep(sessionId, out.event, out.lastResult, null); sessionId = String(next.session_id || sessionId); if (!quiet && next.log) log(next.log); cmd = next.command; } return { terminal: true, ok: false, msg: "\u4e91\u7aef\u5f15\u64ce\u6b65\u6570\u8d85\u9650" }; } async function _xe(startRes, courseName) { let sessionId = String(startRes.session_id || ""); let cmd = startRes.command; let questionCount = 0; const maxSteps = 24; for (let i = 0; i < maxSteps && cmd; i++) { const out = await _xc(cmd); if (out.terminal !== undefined) { if (out.ok) { const score = out.score != null && out.score !== "" ? out.score : "—"; log(`\u8bfe\u7a0b${courseName}\u8003\u8bd5\u5b8c\u6210\uff0c\u5f97\u5206\uff1a${score}\u5206`); } return out; } const next = await _ep(sessionId, out.event, out.lastResult, null); sessionId = String(next.session_id || sessionId); const slog = String(next.log || ""); const batchM = slog.match(/\u6279\u91cf\u4fdd\u5b58\s*(\d+)/); if (batchM) questionCount = Number(batchM[1]) || questionCount; if (/\u4ea4\u5377/.test(slog) && questionCount > 0) { log(`\u8bfe\u7a0b${courseName}\u8003\u8bd5\u5f00\u59cb\uff0c\u5171${questionCount}\u9053\u9898\uff0c\u5df2\u63d0\u4ea4${questionCount}\u9053\u9898\uff0c\u5f00\u59cb\u4ea4\u5377`); } cmd = next.command; } return { terminal: true, ok: false, msg: "\u4e91\u7aef\u5f15\u64ce\u6b65\u6570\u8d85\u9650" }; } async function _cr() { if (!isLoggedIn()) { log("\u8bf7\u5148\u767b\u5f55\u4e13\u6280\u5e73\u53f0"); return false; } try { if (state.cloudToken) await _vt(); await _el(false); } catch (err) { log("\u4e91\u7aef\u6388\u6743\u5931\u8d25\uff1a" + (err.message || err)); return false; } const tier = String(state.cloudTier || "").toLowerCase(); if (tier === "pro") return true; if (tier === "free") { if (Number(state.freeUsedVideos) >= Number(state.freeVideoLimit)) { log(`\u514d\u8d39\u989d\u5ea6\u5df2\u7528\u5b8c\uff08${state.freeUsedVideos}/${state.freeVideoLimit}\uff09\uff0c\u8bf7\u5347\u7ea7 Pro`); switchPanelTab("settings"); openProModal(); return false; } return true; } log("\u4e91\u7aef\u6388\u6743\u72b6\u6001\u5f02\u5e38\uff0c\u8bf7\u68c0\u67e5 Token \u6216\u4e91\u7aef\u5730\u5740"); return false; } function _so() { try { return new URL(getCloudApiBase()).origin; } catch (_) { return ""; } } async function _pn() { const origin = _so(); if (!origin) return; const path = String(state.panelNoticePath || _w(3)); const url = `${origin}${path.startsWith("/") ? path : `/${path}`}`; try { const res = await _gm(url, "GET", {}, null); if (res.status >= 200 && res.status < 300) { state.remotePanelNotice = String(res.text || "").trim() || PANEL_NOTICE_FALLBACK; const el = document.querySelector("#zjzx-ann-text"); if (el) el.textContent = state.remotePanelNotice; } } catch (_) {} } async function _cf() { try { const data = await _gm( `${getCloudApiBase()}${_w(2)}`, "GET", { Accept: "application/json" }, null ); if (data.status >= 200 && data.status < 300 && data.text) { const j = JSON.parse(data.text); if (j?.panelNoticePath) state.panelNoticePath = String(j.panelNoticePath); if (j?.freeVideoLimit != null) state.freeVideoLimit = Number(j.freeVideoLimit) || 1; if (j?.proBuyUrl) state.proBuyUrl = String(j.proBuyUrl).trim() || PRO_BUY_URL; } } catch (_) {} await _pn(); } function getProBuyUrl() { return String(state.proBuyUrl || PRO_BUY_URL).trim() || PRO_BUY_URL; } function isProBuyOpen() { return Date.now() >= PRO_BUY_OPEN_MS; } function syncProBuyButtonUi() { const btn = document.getElementById("zjzx-pro-buy-link"); if (!btn) return; const open = isProBuyOpen(); btn.disabled = !open; btn.textContent = open ? "\u524d\u5f80\u8d2d\u4e70\u9875\u5f00\u901a Pro" : "6\u67086\u65e50\u70b9\u5f00\u653e\u8d2d\u4e70"; btn.title = open ? "\u6253\u5f00\u8d2d\u4e70\u9875\u83b7\u53d6 Token" : "6\u67085\u65e5\u516c\u6d4b\u8bf7\u4eceQQ\u7fa4\u83b7\u53d6Token"; } function openProBuyPage() { if (!isProBuyOpen()) { log("\u8d2d\u4e70\u9875\u5c06\u4e8e6\u67086\u65e50\u70b9\u5f00\u653e\uff0c\u516c\u6d4b\u8bf7\u52a0\u5165QQ\u7fa4\u83b7\u53d6Token"); return; } const url = getProBuyUrl(); try { GM_openInTab(url, { active: true, insert: true, setParent: true }); } catch (_) { window.open(url, "_blank", "noopener,noreferrer"); } } function openProModal() { syncProBuyButtonUi(); const vm = document.getElementById("zjzx-pro-modal"); if (vm) vm.style.display = "flex"; } function closeProModal() { const vm = document.getElementById("zjzx-pro-modal"); if (vm) vm.style.display = "none"; } function createProModal() { const old = document.getElementById("zjzx-pro-modal"); if (old) old.remove(); const modal = document.createElement("div"); modal.id = "zjzx-pro-modal"; modal.innerHTML = `
\u5f00\u901a Pro
Pro \u7528\u6237\u53ef\u65e0\u9650\u5236\u89c6\u9891\u5b66\u4e60\uff0c\u5e76\u81ea\u52a8\u5b8c\u6210\u8bfe\u7a0b\u8003\u8bd5

Pro \u6743\u76ca

  • \u89e3\u9664\u514d\u8d39\u4f53\u9a8c\u89c6\u9891\u6b21\u6570\u9650\u5236\uff0c\u5df2\u9009\u8bfe\u7a0b\u53ef\u6301\u7eed\u5b66\u4e60
  • \u652f\u6301\u5b66\u5b8c\u540e\u81ea\u52a8\u8003\u8bd5\u4ea4\u5377
  • Pro \u6709\u6548\u671f 10 \u5929\uff08\u7ed1\u5b9a\u4e13\u6280\u8d26\u53f7\uff0c\u5207\u6362\u8d26\u53f7\u5931\u6548\uff09
\u63d0\u793a\uff1aPro \u6743\u9650\u7ed1\u5b9a\u5f53\u524d\u767b\u5f55\u7684\u4e13\u6280\u8d26\u53f7\uff0c\u8bf7\u52ff\u4e0e\u4ed6\u4eba\u5171\u7528 Token\u3002

\u5f00\u901a\u65b9\u5f0f

1) \u52a0\u5165 QQ \u7fa4 ${QQ_GROUP_NUMBER}\uff0c\u8054\u7cfb\u7ba1\u7406\u5458\u83b7\u53d6 Token\uff08\u516c\u6d4b\u671f\u95f4\u622a\u6b626\u67085\u65e5\uff0c\u53cd\u9988Bug\u53ef\u4f18\u5148\u4f53\u9a8c\uff09

2) \u524d\u5f80\u8d2d\u4e70\u9875\u83b7\u53d6 Token\uff0c24 \u5c0f\u65f6\u81ea\u52a9\u53d1\u8d27

\u8d2d\u5f97 Token \u540e\uff1a\u5728\u8bbe\u7f6e\u9875 Token \u8f93\u5165\u6846\u7c98\u8d34\uff0c\u70b9\u51fb\u300c\u4fdd\u5b58\u5e76\u6821\u9a8c\u300d\u3002
\u91cd\u8981\uff1a\u8bf7\u52ff\u968f\u610f\u6cc4\u9732 Token\uff0c\u907f\u514d\u8d26\u53f7\u88ab\u591a\u4eba\u5171\u7528\u5bfc\u81f4\u5931\u6548\u3002
`; document.body.appendChild(modal); modal.addEventListener("click", (e) => { if (e.target === modal) closeProModal(); }); modal.querySelector("#zjzx-pro-close")?.addEventListener("click", closeProModal); modal.querySelector("#zjzx-pro-buy-link")?.addEventListener("click", openProBuyPage); syncProBuyButtonUi(); } function switchPanelTab(name) { document.querySelectorAll(".zjzx-tab-btn").forEach((el) => { el.classList.toggle("active", el.dataset.tab === name); }); document.querySelectorAll(".zjzx-pane").forEach((el) => { el.classList.toggle("active", el.dataset.pane === name); }); } function formatCloudTierText(tier) { if (state.cloudRevoked) return "\u5df2\u7981\u7528"; const t = String(tier || "").trim().toLowerCase(); if (t === "pro") return "Pro \u4f1a\u5458"; if (t === "free") return "\u514d\u8d39\u4f53\u9a8c"; if (t === "revoked") return "\u5df2\u7981\u7528"; if (t === "unknown") return "\u672a\u6821\u9a8c"; return tier ? String(tier) : "\u672a\u6821\u9a8c"; } function updateCloudPanelUI() { const tierEl = document.querySelector("#zjzx-cloud-tier"); const freeEl = document.querySelector("#zjzx-cloud-free"); const freeLabelEl = document.querySelector("#zjzx-cloud-free-label"); const tokenInput = document.querySelector("#zjzx-cloud-token"); if (tierEl) { tierEl.textContent = formatCloudTierText(state.cloudTier); tierEl.style.color = state.cloudRevoked || String(state.cloudTier || "").toLowerCase() === "revoked" ? "#dc2626" : "#0f172a"; } if (state.cloudRevoked) { if (freeLabelEl) freeLabelEl.textContent = "\u6388\u6743\u72b6\u6001"; if (freeEl) freeEl.textContent = "Token \u5df2\u7981\u7528"; } else if (String(state.cloudTier || "").toLowerCase() === "pro") { if (freeLabelEl) freeLabelEl.textContent = "Pro \u5230\u671f"; if (freeEl) freeEl.textContent = formatLeaseExpireText(state.cloudProExpireAt || state.cloudLeaseExp); } else { if (freeLabelEl) freeLabelEl.textContent = "\u514d\u8d39\u4f53\u9a8c\u89c6\u9891"; if (freeEl) freeEl.textContent = `${state.freeUsedVideos}/${state.freeVideoLimit}`; } if (tokenInput && tokenInput !== document.activeElement) tokenInput.value = state.cloudToken || ""; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function getCookie(name) { const m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)")); return m ? decodeURIComponent(m[1]) : ""; } function getUserData() { try { const raw = getCookie("userData_lenye"); if (raw) return JSON.parse(raw); } catch (_) {} try { const raw = localStorage.getItem("userData_lenye"); if (raw) return JSON.parse(raw); } catch (_) {} return null; } function isLoggedIn() { return !!(getCookie("SESSION_TOKEN") || getUserData()?.SESSION_TOKEN); } function pageType() { const path = location.pathname; const qs = new URLSearchParams(location.search); if (path.includes("courseplay")) return "courseplay"; if (path.includes("personcenter") && qs.get("type") === "train") return "train"; if (path.includes("personcenter")) return "personcenter"; if (path === "/" || path === "/index.html") return "home"; return "other"; } function getCourseParams() { const qs = new URLSearchParams(location.search); return { csId: qs.get("id") || "", tpId: qs.get("tpId") || "" }; } function courseplayPath(csId, tpId) { return `/courseplay?id=${csId}&tpId=${tpId}`; } function readNavGuard() { try { return JSON.parse(sessionStorage.getItem(NAV_GUARD_KEY) || "{}"); } catch (_) { return {}; } } function writeNavGuard(url) { sessionStorage.setItem( NAV_GUARD_KEY, JSON.stringify({ url, time: Date.now() }) ); } function isSameCoursePage(csId) { return pageType() === "courseplay" && getCourseParams().csId === csId; } function canNavigate(url) { const target = new URL(url, location.origin).pathname + new URL(url, location.origin).search; const current = location.pathname + location.search; if (target === current) return false; const guard = readNavGuard(); if (guard.url === target && Date.now() - (guard.time || 0) < NAV_COOLDOWN_MS) { return false; } return true; } function safeNavigate(url) { if (!canNavigate(url)) return false; writeNavGuard(url); log("\u8df3\u8f6c\uff1a" + url); location.href = url; return true; } function canClickChapter(chapterId) { if (!chapterId) return false; const guard = readNavGuard(); if (guard.chapterId === chapterId && Date.now() - (guard.chapterClickAt || 0) < CHAPTER_CLICK_COOLDOWN_MS) { return false; } return true; } function markChapterClick(chapterId) { const guard = readNavGuard(); guard.chapterId = chapterId; guard.chapterClickAt = Date.now(); sessionStorage.setItem(NAV_GUARD_KEY, JSON.stringify(guard)); } function visible(el) { if (!el) return false; const st = getComputedStyle(el); if (st.display === "none" || st.visibility === "hidden" || st.opacity === "0") return false; return el.offsetWidth > 0 || el.offsetHeight > 0; } function clickEl(el) { if (!el) return false; el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window })); return true; } function findVisibleButton(root, pattern) { return Array.from(root.querySelectorAll("button, .el-button, a")).find((el) => pattern.test((el.textContent || "").replace(/\s+/g, "")) ); } function dismissDialogs() { const onCoursePlay = pageType() === "courseplay"; document.querySelectorAll(".el-message-box__wrapper, .el-dialog__wrapper").forEach((wrapper) => { if (!visible(wrapper)) return; const btn = onCoursePlay ? findVisibleButton(wrapper, /^(\u786e\u5b9a|\u77e5\u9053\u4e86|\u7ee7\u7eed|\u786e\u8ba4)$/) || findVisibleButton(wrapper, /(\u786e\u5b9a|\u77e5\u9053\u4e86)/) : findVisibleButton(wrapper, /^(\u53bb\u5b66\u4e60|\u786e\u5b9a|\u77e5\u9053\u4e86|\u7ee7\u7eed|\u786e\u8ba4)$/) || findVisibleButton(wrapper, /(\u53bb\u5b66\u4e60|\u786e\u5b9a)/); if (btn) clickEl(btn); }); } function isFaceDialogVisible() { const qr = document.getElementById("qrCode"); if (qr && visible(qr.closest(".el-dialog__wrapper") || qr)) return true; return Array.from(document.querySelectorAll(".el-dialog__wrapper")).some((w) => { if (!visible(w)) return false; const t = w.textContent || ""; return /\u4eba\u8138|\u8bc6\u522b|\u626b\u7801|\u4e0a\u4f20\u5934\u50cf/.test(t); }); } function getVideoEl() { const box = document.getElementById("player_video"); if (box) { const v = box.querySelector("video"); if (v) return v; } return document.querySelector("#player_video video, .playboxin video, video"); } function getPlayButton() { return ( document.querySelector(".e3cplay-play, .vjs-play-control, .xgplayer-play, .play-btn") || document.querySelector("#player_video .vjs-big-play-button") ); } async function ensurePlaying() { if (isFaceDialogVisible()) { if (!state.faceWaiting) { state.faceWaiting = true; log("\u68c0\u6d4b\u5230\u4eba\u8138\u8bc6\u522b\uff0c\u8bf7\u624b\u673a\u626b\u7801\u540e\u7ee7\u7eed"); } return; } state.faceWaiting = false; const video = getVideoEl(); if (!video) return; if (video.ended) return; if (video.paused) { try { video.muted = true; await video.play(); log("\u5df2\u81ea\u52a8\u64ad\u653e\u89c6\u9891"); return; } catch (_) { const btn = getPlayButton(); if (btn) { clickEl(btn); log("\u5df2\u70b9\u51fb\u64ad\u653e\u6309\u94ae"); } } } } function chapterDone(link) { const img = link.querySelector("img.course_play_percent"); if (!img || !img.src) return false; return /play100|100/.test(img.src) || /play100/.test(img.getAttribute("src") || ""); } function getChapterLinks() { return Array.from(document.querySelectorAll("a.cptresult_a")); } function getCurrentChapterId() { const active = document.querySelector("li.active a.cptresult_a"); return active?.dataset?.name || active?.id || ""; } function findNextIncompleteChapter(skipCurrent) { const links = getChapterLinks(); if (!links.length) return null; const currentId = getCurrentChapterId(); let passedCurrent = !currentId || !skipCurrent; for (const link of links) { const id = link.dataset.name || link.id; if (!passedCurrent) { if (id === currentId) passedCurrent = true; continue; } if (!chapterDone(link)) return link; } if (skipCurrent) return null; return links.find((link) => !chapterDone(link)) || null; } function ensureInStudyTab() { const inStudyTab = Array.from(document.querySelectorAll(".tab-ul li, .zaixue-course-tit li")).find((li) => /\u5728\u5b66/.test(li.textContent || "") ); if (inStudyTab && !inStudyTab.classList.contains("on") && !inStudyTab.classList.contains("active")) { clickEl(inStudyTab.querySelector("a") || inStudyTab); return true; } return false; } function buildApiBody(payload) { const user = getUserData(); const sessionToken = getCookie("SESSION_TOKEN") || user?.SESSION_TOKEN || ""; let uuid = getCookie("UUID") || user?.UUID || ""; if (!uuid) { try { const headerRaw = getCookie("header_name_lenye"); if (headerRaw) { const h = JSON.parse(headerRaw); uuid = h.UUID || uuid; } } catch (_) {} } if (!uuid && crypto.randomUUID) uuid = crypto.randomUUID(); return JSON.stringify({ header: { SESSION_TOKEN: sessionToken, CLIENT_OS: "O", UUID: uuid, REQ_TIME: String(Date.now()), SITE_ID: SITE_ID, }, payload, }); } function apiPost(path, payload) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", API_BASE + path, true); xhr.withCredentials = false; xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.onload = function () { try { resolve(JSON.parse(xhr.responseText || "{}")); } catch (err) { reject(new Error("\u54cd\u5e94\u89e3\u6790\u5931\u8d25")); } }; xhr.onerror = function () { reject(new Error("\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25")); }; xhr.send(buildApiBody(payload)); }); } async function fetchHeartbeatSec() { try { const res = await apiPost("/resource-service/stdCustLearnRecord/getHeartBeatInterval/v1.lyai", {}); if (res.success === "1" && res.data?.interval) { return Number(res.data.interval) || 300; } } catch (_) {} return 300; } function isCourseUnfinished(c) { if (!c) return false; if (isFalsyFlag(c.lcsFinished)) return true; if (isFalsyFlag(c.lcsStudyFinished)) return true; if (isFalsyFlag(c.lcsExameFinished) && (c.courseTestList || []).length) return true; const p = parseFloat(c.lcsProcess); return !Number.isNaN(p) && p < 100; } function isCourseFullyFinished(c) { if (!c) return false; if (isTruthyFlag(c.lcsFinished)) return true; const hasExam = (c.courseTestList || []).length > 0; if (hasExam) return isStudyFinished(c) && isTruthyFlag(c.lcsExameFinished); return isStudyFinished(c); } function isStudyFinished(course) { if (!course) return false; if (isTruthyFlag(course.lcsStudyFinished)) return true; return parseFloat(course.lcsProcess || 0) >= 100; } function courseNeedsExam(course) { if (!getAutoExam() || !course) return false; if (isTruthyFlag(course.lcsExameFinished)) return false; const test = (course.courseTestList || [])[0]; if (!test?.tsId) return false; return isStudyFinished(course); } async function fetchCourseFromPlans(csId) { const user = getUserData(); if (!user?.custId || !csId) return null; const plans = await listTeachPlans(user.custId); for (const plan of plans) { const courses = await listPlanCourses(plan.tpId, user.custId); const course = courses.find((c) => c.csId === csId); if (course) return { ...course, tpId: plan.tpId, tpName: plan.tpName || plan.tpTitle || "" }; } return null; } async function listTeachPlans(studentUserId) { const res = await apiPost("/trainpronew-service/TPClazzStudentRel/queryStudentTeachPlanPage/v1.lyai", { page: 1, rows: "50", sorts: "singupDate=0", studentUserId, tpName: "", year: "", }); if (res.success !== "1" || !res.data?.length) return []; return res.data; } async function listPlanCourses(tpId, custId) { const res = await apiPost("/trainpronew-service/login/cgUserPlanClazzCourse/queryUserPlanCoursePage/v1.lyai", { tpId, page: 1, rows: 999, custId, sorts: "lcsFinished=1&updateDate=0", needTeacherInfo: 0, }); if (res.success !== "1" || !res.data?.length) return []; return res.data; } async function listAllCoursesForPlan(plan) { const user = getUserData(); if (!plan?.tpId || !user?.custId) return []; const courses = await listPlanCourses(plan.tpId, user.custId); const tpName = plan.tpName || plan.tpTitle || ""; const planYear = plan.year || ""; return courses.map((c) => ({ ...c, tpId: plan.tpId, tpName, planYear })); } async function listAllUnfinishedCourses() { const user = getUserData(); if (!user?.custId) { log("\u672a\u8bfb\u53d6\u5230 custId\uff0c\u8bf7\u786e\u8ba4\u5df2\u767b\u5f55"); return []; } const plans = await listTeachPlans(user.custId); if (!plans.length) { log("\u672a\u627e\u5230\u57f9\u8bad\u8ba1\u5212\uff0c\u8bf7\u5148\u5728\u5b66\u4e60\u4e2d\u5fc3\u9009\u8bfe"); return []; } const out = []; for (const plan of plans) { const tpId = plan.tpId; if (!tpId) continue; const courses = await listPlanCourses(tpId, user.custId); for (const course of courses) { if (!isCourseUnfinished(course)) continue; out.push({ ...course, tpId, tpName: plan.tpName || plan.tpTitle || "" }); } } out.sort((a, b) => parseFloat(b.lcsProcess || 0) - parseFloat(a.lcsProcess || 0)); return out; } async function findUnfinishedCourse() { const list = await listAllUnfinishedCourses(); if (!list.length) { log("\u5df2\u68c0\u67e5\u5168\u90e8\u8ba1\u5212\uff0c\u8bfe\u7a0b\u5747\u663e\u793a\u5df2\u5b8c\u6210"); return null; } return list[0]; } async function findNextWorkInQueue(options) { const silent = !!(options && options.silent); const queue = loadQueue(); if (!queue.length) { if (!silent) log("\u8bf7\u52fe\u9009\u8981\u5b66\u4e60\u7684\u8bfe\u7a0b\u540e\u518d\u70b9\u5f00\u59cb"); return null; } const allCourses = await listAllUnfinishedCourses(); const courseMap = new Map(allCourses.map((c) => [c.csId, c])); for (const csId of queue) { let course = courseMap.get(csId); if (!course) course = await fetchCourseFromPlans(csId); if (!course) continue; const allVideos = await loadCourseVideos(csId); const videos = allVideos.filter(isVideoUnfinished); if (videos.length) { const done = allVideos.length - videos.length; return { phase: "study", course, videos, videoTotal: allVideos.length, videoDone: done, }; } if (courseNeedsExam(course)) { const test = course.courseTestList[0]; return { phase: "exam", course, test }; } } return null; } function collectCorrectAnswers(paper) { const answers = []; for (const cat of paper.questionCats || []) { const itemType = cat.itemType; for (const q of cat.questions || []) { const correct = (q.options || []).filter((o) => isTruthyFlag(o.answer)); if (!correct.length) continue; let answerIdValue; if (itemType === "ms") { answerIdValue = correct.map((o) => o.itemId).join(","); } else { answerIdValue = correct[0].itemId; } answers.push({ questionId: q.questionId, answerIdValue, answerContentValue: "", }); } } return answers; } async function submitCourseExam(course, testInfo) { const user = getUserData(); if (!user?.custId) return { ok: false, msg: "\u672a\u767b\u5f55" }; const tsId = testInfo.tsId; const spcId = testInfo.tsSpcId || testInfo.spcId; if (!tsId || !spcId) return { ok: false, msg: "\u7f3a\u5c11 tsId/spcId" }; try { const startRes = await _es("exam", { study_user_id: user.custId, test: { tsId, tsSpcId: spcId, spcId }, course: { csId: course.csId, csName: course.csName }, }, {}); const loop = await _xe(startRes, courseDisplayName(course)); if (loop.ok) { return { ok: true, score: loop.score ?? "", msg: loop.msg || "\u4ea4\u5377\u6210\u529f" }; } return { ok: false, msg: loop.msg || "\u4e91\u7aef\u8003\u8bd5\u5931\u8d25" }; } catch (err) { return { ok: false, msg: err.message || String(err) }; } } function applyBgProgressEstimate(bg) { const total = Number(bg.videoTotal) || 0; if (!total) return; const completed = Number(bg.videoDone || 0) + Number(bg.videoIndex || 0); const est = (completed / total) * 100; bg.lcsProcess = Math.max(Number(bg.lcsProcess || 0), est); } async function afterVideoLearnSuccess(bg, idx, waitSec) { bg.videoIndex = idx + 1; bg.lastSaveAt = Date.now(); bg.saveCount = (bg.saveCount || 0) + 1; const prog = await fetchCourseProgress(bg.csId); if (prog) { bg.lcsProcess = prog.lcsProcess; bg.csName = prog.csName || bg.csName; } applyBgProgressEstimate(bg); const allVideosDone = bg.videoIndex >= bg.videos.length; saveBgState(bg); updateCourseProgressInPanel(bg.csId, bg.lcsProcess); updatePanel(); void refreshChapterPreview(); if (allVideosDone) { await finishCourseAndAdvance(bg); } else { log(`\u89c6\u9891${idx + 1}\u5df2\u5b8c\u6210\uff0c\u7b49\u5f85${waitSec}\u79d2\u8fdb\u884c\u4e0b\u4e00\u4e2a\u5b66\u4e60`); } } function stopAutoStudy(reason) { if (!state.enabled) return; state.enabled = false; localStorage.setItem(STORAGE_KEY, "0"); syncKeepAlive(); stopPreviewAutoRefresh(); log(reason || "\u6240\u6709\u5df2\u9009\u8bfe\u7a0b\u5747\u5df2\u5b8c\u6210\uff0c\u7a0b\u5e8f\u81ea\u52a8\u505c\u6b62"); updatePanel(); } function buildBgStateFromWork(hit) { const { course, phase } = hit; const base = { csId: course.csId, tpId: course.tpId, csName: course.csName, lcsProcess: parseFloat(course.lcsProcess || 0), phase, lastSaveAt: 0, saveCount: 0, }; if (phase === "study") { return { ...base, videos: hit.videos, videoIndex: 0, videoTotal: hit.videoTotal ?? hit.videos.length, videoDone: hit.videoDone ?? 0, }; } return { ...base, videos: [], videoIndex: 0, test: hit.test }; } function isVideoUnfinished(v) { if (!v) return false; if (v.studyFinished === "1") return false; if (Number(v.process) >= 100) return false; return true; } async function loadCourseVideos(csId) { const treeRes = await apiPost("/course-service/crsChapter/treeList/v1.lyai", { csId }); const chapters = flattenChapters(treeRes.data || []); const cptIds = chapters.map((c) => c.cptId).filter(Boolean); if (!cptIds.length) return []; const relRes = await apiPost("/course-service/crsChapterResourceRel/queryListByCptIds/v1.lyai", { cptId: cptIds.join(",") + ",", csId, sorts: "1", }); if (relRes.success !== "1" || !relRes.data?.length) return []; return relRes.data .map((r) => ({ resId: r.resId, cptId: r.cptId, fileTimeLen: Number(r.fileTimeLen) || 0, studyTime: Number(r.studyTime) || 0, process: Number(r.process) || 0, studyFinished: r.studyFinished, resName: r.resName || "", })) .filter((v) => v.resId && v.fileTimeLen > 0); } async function fetchVideoBreakpoint(csId, resId) { const user = getUserData(); if (!user?.custId || !resId) return 0; let bp = 0; const progRes = await apiPost("/resource-service/stdCustLearnResource/queryResources/v1.lyai", { studyUserId: user.custId, resinfo: "1", resId, needRepeatLearn: "1", csId, }); if (progRes.success === "1" && progRes.data?.[0]) { const p0 = progRes.data[0]; bp = Number(p0.studyBreakpoint || p0.endTime || 0); } const procRes = await apiPost("/resource-service/stdCustLearnProcessRecord/queryPage/v1.lyai", { studyUserId: user.custId, resId, siteId: SITE_ID, source: "1", }); if (procRes.success === "1" && procRes.data?.[0]?.endTime != null) { bp = Math.max(bp, Number(procRes.data[0].endTime) || 0); } return Math.max(0, Math.floor(bp)); } async function learnSaveFullVideo(csId, video, mult) { const user = getUserData(); const breakpoint = await fetchVideoBreakpoint(csId, video.resId); const startRes = await _es( "video", { cs_id: csId, study_user_id: user.custId, video: { resId: video.resId, cptId: video.cptId, fileTimeLen: video.fileTimeLen, resName: video.resName || "", }, multiplier: mult, breakpoint, }, { multiplier: mult } ); const loop = await _ve(startRes, { quiet: true }); if (loop.skipped) return { success: "1", skipped: true }; if (loop.ok) return { success: "1" }; throw new Error(loop.msg || "\u4e91\u7aef learnSave \u5931\u8d25"); } async function resolveResourceForCourse(csId, cptIdHint) { const user = getUserData(); let cptId = cptIdHint || ""; const treeRes = await apiPost("/course-service/crsChapter/treeList/v1.lyai", { csId }); const chapters = flattenChapters(treeRes.data || []); if (!chapters.length) return null; if (!cptId) cptId = chapters[0].cptId; const relRes = await apiPost("/course-service/crsChapterResourceRel/queryListByCptIds/v1.lyai", { cptId, csId, sorts: "1", }); const relList = relRes.data || []; const rel = Array.isArray(relList) ? relList[0] : relList; const resId = rel?.resId || rel?.resArr?.resId; if (!resId) return null; const detailRes = await apiPost("/resource-service/resResource/queryById/v1.lyai", { resId }); const detail = detailRes.data || {}; const duration = Number(detail.fileTimeLen || detail.resDuration || detail.playTime || 0); let studyBreakpoint = 0; const progRes = await apiPost("/resource-service/stdCustLearnResource/queryResources/v1.lyai", { studyUserId: user.custId, resinfo: "1", resId, needRepeatLearn: "1", csId, }); if (progRes.success === "1" && progRes.data?.[0]) { const p0 = progRes.data[0]; studyBreakpoint = Number(p0.studyBreakpoint || p0.endTime || 0); } const procRes = await apiPost("/resource-service/stdCustLearnProcessRecord/queryPage/v1.lyai", { studyUserId: user.custId, resId, siteId: SITE_ID, source: "1", }); if (procRes.success === "1" && procRes.data?.[0]?.endTime != null) { studyBreakpoint = Math.max(studyBreakpoint, Number(procRes.data[0].endTime) || 0); } return { csId, cptId, resId, duration: duration > 0 ? duration : 7200, studyBreakpoint, csName: detail.resName || "", }; } async function initBackgroundTarget() { const queue = loadQueue(); const saved = loadBgState(); if (queue.length && saved.csId && queue.includes(saved.csId)) { if (saved.phase === "exam" && saved.test?.tsId) { return { ...saved, lastSaveAt: 0 }; } if (saved.videos?.length) { const idx = saved.videoIndex || 0; if (idx < saved.videos.length) { const allVideos = await loadCourseVideos(saved.csId); const pending = allVideos.filter(isVideoUnfinished); if (pending.length) { let videoIndex = idx; const cur = saved.videos[idx]; if (cur?.resId) { const at = pending.findIndex((v) => v.resId === cur.resId); if (at >= 0) videoIndex = at; else videoIndex = 0; } const prog = await fetchCourseProgress(saved.csId); return { ...saved, phase: "study", videos: pending, videoIndex, lastSaveAt: 0, videoTotal: allVideos.length, videoDone: allVideos.length - pending.length, lcsProcess: prog?.lcsProcess ?? saved.lcsProcess, csName: prog?.csName || saved.csName, }; } } } } else if (saved.csId && queue.length && !queue.includes(saved.csId)) { saveBgState({}); } const hit = await findNextWorkInQueue({ silent: true }); if (!hit) return null; const bg = buildBgStateFromWork(hit); if (hit.phase === "study") { const prog = await fetchCourseProgress(hit.course.csId); if (prog) { bg.lcsProcess = prog.lcsProcess; bg.csName = prog.csName || bg.csName; } } return bg; } async function finishCourseAndAdvance(bg) { const course = (await fetchCourseFromPlans(bg.csId)) || { csId: bg.csId, csName: bg.csName, tpId: bg.tpId }; if (courseNeedsExam(course)) { bg.phase = "exam"; bg.videos = []; bg.videoIndex = 0; bg.test = course.courseTestList[0]; bg.lastSaveAt = 0; saveBgState(bg); const proc = Number(bg.lcsProcess ?? 0).toFixed(1); log(`\u8bfe\u7a0b${courseDisplayName(bg)}\u6240\u6709\u89c6\u9891\u5df2\u5b8c\u6210\u5b66\u4e60\uff0c\u5f53\u524d\u8fdb\u5ea6${proc}%\uff0c\u4e0b\u4e00\u6b65\u8fdb\u5165\u8003\u8bd5\u9636\u6bb5`); renderCourseList(); void refreshChapterPreview(); return; } saveBgState({}); state.coursesDoneSession += 1; log(`\u8bfe\u7a0b${courseDisplayName(bg)}\u5df2\u5b8c\u6210\uff08\u65e0\u8003\u8bd5\uff09`); void refreshChapterPreview(); refreshPanelCourses(); } async function fetchCourseProgress(csId) { const user = getUserData(); if (!user?.custId || !csId) return null; const res = await apiPost("/course-service/crsCustLearnCourse/queryUserCoursePage/v1.lyai", { csId, studyUserId: user.custId, orderType: "1", }); if (res.success !== "1" || !res.data?.[0]) return null; const row = res.data[0]; return { lcsProcess: parseFloat(row.lcsProcess || 0), lcsStudyTime: row.lcsStudyTime, csName: row.csName, }; } function formatBgPanelExtra(bg) { if (!bg?.csId) return ""; const name = (bg.csName || "\u8bfe\u7a0b").slice(0, 12); if (bg.phase === "exam") return `\u3010${name} · \u8003\u8bd5\u4e2d\u3011`; const idx = (bg.videoIndex || 0) + 1; const total = bg.videos?.length || 0; const cur = bg.videos?.[bg.videoIndex || 0]; const proc = bg.lcsProcess != null ? ` · ${bg.lcsProcess}%` : ""; const vid = cur ? ` · ${cur.fileTimeLen}s` : ""; return `\u3010${name} \u89c6\u9891${idx}/${total}${vid}${proc}\u3011`; } function hasActiveBgTask(bg) { if (!bg?.csId) return false; if (bg.phase === "exam" && bg.test?.tsId) return true; return !!(bg.videos && bg.videos.length); } async function backgroundLearnTick() { if (pageType() === "courseplay") { log("\u540e\u53f0\u6a21\u5f0f\uff1a\u8bf7\u5173\u95ed\u64ad\u653e\u9875\uff0c\u907f\u514d\u4e0e\u9875\u9762\u81ea\u5e26\u4e0a\u62a5\u51b2\u7a81"); return; } let bg = loadBgState(); if (!hasActiveBgTask(bg)) { bg = await initBackgroundTarget(); if (!bg) { if (state.enabled) { stopAutoStudy("\u6240\u6709\u5df2\u9009\u8bfe\u7a0b\u5747\u5df2\u5b8c\u6210\uff0c\u7a0b\u5e8f\u81ea\u52a8\u505c\u6b62"); } return; } saveBgState(bg); renderCourseList(); const cname = courseDisplayName(bg); const proc = Number(bg.lcsProcess || 0).toFixed(1); if (bg.phase === "exam") { log(`\u8bfe\u7a0b${cname}\u6240\u6709\u89c6\u9891\u5df2\u5b8c\u6210\u5b66\u4e60\uff0c\u5f53\u524d\u8fdb\u5ea6${proc}%\uff0c\u4e0b\u4e00\u6b65\u8fdb\u5165\u8003\u8bd5\u9636\u6bb5`); } else { const total = bg.videoTotal ?? bg.videos?.length ?? 0; const pending = bg.videos?.length ?? 0; const done = bg.videoDone ?? Math.max(0, total - pending); log( `\u5f00\u59cb\u5b66\u4e60\u8bfe\u7a0b${cname} \u8fdb\u5ea6${proc}% \u89c6\u9891\u6570${total} \u5df2\u5b8c\u6210${done}\u4e2a\u89c6\u9891\uff0c\u5f85\u5b66\u4e60${pending}\u4e2a\u89c6\u9891` ); } } const waitSec = getSaveInterval(); const elapsed = Date.now() - (bg.lastSaveAt || 0); if (bg.lastSaveAt && elapsed < waitSec * 1000) { const remain = Math.ceil((waitSec * 1000 - elapsed) / 1000); state.lastAction = `\u7b49\u5f85 ${remain}s ${formatBgPanelExtra(bg)}`; updatePanel(); return; } if (bg.phase === "exam") { const course = (await fetchCourseFromPlans(bg.csId)) || { csId: bg.csId, csName: bg.csName, tpId: bg.tpId, courseTestList: [bg.test], }; const examRes = await submitCourseExam(course, bg.test); bg.lastSaveAt = Date.now(); if (examRes.ok) { saveBgState({}); state.coursesDoneSession += 1; refreshPanelCourses(); } else { saveBgState(bg); log(`\u8003\u8bd5\u5931\u8d25\uff1a${examRes.msg}`); } return; } const idx = bg.videoIndex || 0; const video = bg.videos[idx]; if (!video) { await finishCourseAndAdvance(bg); return; } const mult = getMultiplier(); log(`\u6b63\u5728\u5b66\u4e60\u89c6\u9891${idx + 1}`); let res; try { res = await learnSaveFullVideo(bg.csId, video, mult); } catch (err) { const em = String(err.message || err); log("\u4e91\u7aef\u89c6\u9891\u5f15\u64ce\uff1a" + em); if (/free_quota|\u914d\u989d/.test(em)) { state.enabled = false; localStorage.setItem(STORAGE_KEY, "0"); syncKeepAlive(); } return; } if (res.skipped || res.success === "1") { await afterVideoLearnSuccess(bg, idx, waitSec); return; } if (String(res.success).includes("drag")) { saveBgState({}); log(`\u4e0a\u62a5\u88ab\u62d2\uff08${video.resName}\uff09\uff0c\u5df2\u91cd\u7f6e`); return; } log("\u4e0a\u62a5\u5931\u8d25\uff1a" + (res.message || res.success) + " · " + (video.resName || "")); } async function openFirstUnfinishedCourseByApi() { const course = await findUnfinishedCourse(); if (!course) return false; if (isSameCoursePage(course.csId)) { log("\u5df2\u5728\u5f53\u524d\u8bfe\u7a0b\u9875\uff0c\u7b49\u5f85\u64ad\u653e"); return false; } const target = courseplayPath(course.csId, course.tpId); if (!canNavigate(target)) { log("\u521a\u8fdb\u5165\u8bfe\u7a0b\uff0c\u7b49\u5f85\u9875\u9762\u7a33\u5b9a"); return false; } log(`\u8fdb\u5165\u8bfe\u7a0b\uff1a${course.csName || course.csId}\uff08${course.lcsProcess || 0}%\uff09`); safeNavigate(target); return true; } async function handleTrainPage() { dismissDialogs(); if (ensureInStudyTab()) { log("\u5df2\u5207\u6362\u5230\u300c\u5728\u5b66\u8bfe\u7a0b\u300d"); return; } const now = Date.now(); if (now - state.trainApiTriedAt < NAV_COOLDOWN_MS) { log("\u5b66\u4e60\u4e2d\u5fc3\u7b49\u5f85\u4e2d\uff0c\u907f\u514d\u91cd\u590d\u6253\u5f00\u8bfe\u7a0b"); return; } state.trainApiTriedAt = now; await openFirstUnfinishedCourseByApi(); } async function handleCoursePlayPage() { dismissDialogs(); const video = getVideoEl(); const links = getChapterLinks(); const activeId = getCurrentChapterId(); if (!video) { if (activeId) { if (!state.courseplayWaitSince) state.courseplayWaitSince = Date.now(); if (Date.now() - state.courseplayWaitSince < VIDEO_WAIT_MS) { log("\u7b49\u5f85\u64ad\u653e\u5668\u52a0\u8f7d..."); return; } state.courseplayWaitSince = 0; } const next = findNextIncompleteChapter(true); if (next) { const chapterId = next.dataset.name || next.id; if (!canClickChapter(chapterId)) { log("\u7ae0\u8282\u5207\u6362\u51b7\u5374\u4e2d"); return; } markChapterClick(chapterId); clickEl(next); log("\u5df2\u5207\u6362\u7ae0\u8282\uff1a" + (next.title || next.textContent || "").trim()); state.courseplayWaitSince = Date.now(); return; } if (links.length && !links.some((link) => !chapterDone(link))) { log("\u5f53\u524d\u8bfe\u7a0b\u7ae0\u8282\u5df2\u5168\u90e8\u5b8c\u6210\uff0c\u8fd4\u56de\u5b66\u4e60\u4e2d\u5fc3"); safeNavigate("/personcenter?type=train"); } return; } state.courseplayWaitSince = 0; if (video.ended) { const next = findNextIncompleteChapter(true); if (!next) { log("\u5f53\u524d\u8bfe\u7a0b\u5df2\u5168\u90e8\u5b8c\u6210\uff0c\u8fd4\u56de\u5b66\u4e60\u4e2d\u5fc3"); safeNavigate("/personcenter?type=train"); } return; } await ensurePlaying(); } async function handleHomePage() { if (!isLoggedIn()) { log("\u8bf7\u5148\u767b\u5f55"); return; } if (location.pathname.includes("personcenter")) return; safeNavigate("/personcenter?type=train"); } async function tick() { if (!state.enabled) return; const now = Date.now(); if (now - state.lastTick < TICK_MS) return; state.lastTick = now; if (!isLoggedIn()) { log("\u672a\u767b\u5f55\uff0c\u8bf7\u5148\u767b\u5f55\u8d26\u53f7"); return; } if (!(await _cr())) { state.enabled = false; localStorage.setItem(STORAGE_KEY, "0"); syncKeepAlive(); return; } dismissDialogs(); const mode = getMode(); const type = pageType(); try { if (mode === "background") { await backgroundLearnTick(); return; } if (mode === "manual") { if (type === "courseplay") { dismissDialogs(); return; } log("\u624b\u52a8\u6a21\u5f0f\uff1a\u8bf7\u81ea\u884c\u6253\u5f00\u64ad\u653e\u9875\u770b\u8bfe"); return; } if (type === "home" || type === "personcenter") { await handleHomePage(); } else if (type === "train") { await handleTrainPage(); } else if (type === "courseplay") { await handleCoursePlayPage(); } } catch (err) { log("\u8fd0\u884c\u5f02\u5e38\uff1a" + (err.message || err)); } } async function fetchPanelCourses() { const user = getUserData(); if (!user?.custId) { state.panelCourses = []; state.teachPlans = []; return []; } const plans = await listTeachPlans(user.custId); state.teachPlans = plans; let tpId = loadSelectedTpId(); if (!tpId || !plans.some((p) => p.tpId === tpId)) { tpId = plans[0]?.tpId || ""; if (tpId) saveSelectedTpId(tpId); } else { state.selectedTpId = tpId; } const plan = plans.find((p) => p.tpId === tpId); const list = plan ? await listAllCoursesForPlan(plan) : []; state.panelCourses = list.map((c) => ({ csId: c.csId, tpId: c.tpId, csName: c.csName || c.courseName || "\u672a\u547d\u540d\u8bfe\u7a0b", lcsProcess: parseFloat(c.lcsProcess || 0), tpName: c.tpName || "", planYear: c.planYear || "", lcsStudyFinished: c.lcsStudyFinished, lcsExameFinished: c.lcsExameFinished, lcsFinished: c.lcsFinished, courseTestList: c.courseTestList || [], })); state.panelCourses.sort((a, b) => { const au = isCourseUnfinished(a) ? 1 : 0; const bu = isCourseUnfinished(b) ? 1 : 0; if (au !== bu) return bu - au; return b.lcsProcess - a.lcsProcess; }); let q = loadQueue(); const touched = localStorage.getItem(QUEUE_TOUCHED_KEY) === "1"; if (!touched && !q.length && state.panelCourses.length) { q = state.panelCourses.filter(isCourseUnfinished).map((c) => c.csId); } saveQueue(q); return state.panelCourses; } async function refreshPanelCourses() { if (!isLoggedIn()) return; state.panelRefreshing = true; updatePanel(); try { await fetchPanelCourses(); renderPlanSelect(); renderCourseList(); await refreshChapterPreview(); } catch (err) { log("\u5237\u65b0\u8bfe\u7a0b\u5931\u8d25\uff1a" + (err.message || err)); } finally { state.panelRefreshing = false; updatePanel(); } } function updateCourseProgressInPanel(csId, lcsProcess) { const c = state.panelCourses.find((x) => x.csId === csId); if (c && lcsProcess != null) c.lcsProcess = lcsProcess; renderCourseList(); void refreshChapterPreview(); } function startPreviewAutoRefresh() { stopPreviewAutoRefresh(); state.previewRefreshTimer = setInterval(() => { if (!state.enabled) return; refreshChapterPreview().catch(() => {}); }, 12000); } function stopPreviewAutoRefresh() { if (state.previewRefreshTimer) { clearInterval(state.previewRefreshTimer); state.previewRefreshTimer = null; } } function courseExamLabel(c) { if (isCourseFullyFinished(c)) return " · \u5df2\u5b8c\u6210"; if (!(c.courseTestList || []).length) return ""; if (isTruthyFlag(c.lcsExameFinished)) return " · \u8003✓"; if (isStudyFinished(c)) return " · \u5f85\u8003\u8bd5"; return " · \u542b\u8003\u8bd5"; } function renderPlanSelect() { const sel = document.getElementById("zjzx-plan-select"); if (!sel) return; const plans = state.teachPlans || []; if (!plans.length) { sel.innerHTML = ""; sel.disabled = true; return; } sel.disabled = false; sel.innerHTML = plans .map((p) => { const label = p.tpName || (p.year ? `${p.year}\u5e74\u5ea6` : p.tpId); const selected = p.tpId === state.selectedTpId ? " selected" : ""; return ``; }) .join(""); } function renderCourseList() { const box = document.getElementById("zjzx-course-list"); if (!box) return; const bg = loadBgState(); const activeCsId = bg.csId || ""; const selectedSet = new Set(loadQueue()); if (!state.panelCourses.length) { box.innerHTML = "
\u5f53\u524d\u5e74\u5ea6\u8ba1\u5212\u6682\u65e0\u8bfe\u7a0b
\u53ef\u5207\u6362\u4e0a\u65b9\u57f9\u8bad\u8ba1\u5212\u67e5\u770b\u5176\u4ed6\u5e74\u5ea6
"; return; } box.innerHTML = state.panelCourses .map((c) => { const pct = Math.min(100, Math.max(0, c.lcsProcess || 0)); const isActive = c.csId === activeCsId; const finished = isCourseFullyFinished(c); const checked = selectedSet.has(c.csId) ? "checked" : ""; const border = isActive ? "#16a34a" : finished ? "#e2e8f0" : "#e2e8f0"; const bgColor = isActive ? "#f0fdf4" : finished ? "#f1f5f9" : "#f8fafc"; const titleColor = finished ? "#64748b" : "#0f172a"; return ` `; }) .join(""); box.querySelectorAll("input[type='checkbox']").forEach((el) => { el.addEventListener("change", () => { const selected = []; box.querySelectorAll("label").forEach((label) => { const cb = label.querySelector("input[type='checkbox']"); if (cb?.checked && cb.dataset.csid) selected.push(cb.dataset.csid); }); localStorage.setItem(QUEUE_TOUCHED_KEY, "1"); saveQueue(selected); if (selected.length) clearPanelHint(); void refreshChapterPreview(); const bg = loadBgState(); if (bg.csId && selected.length && !selected.includes(bg.csId)) { saveBgState({}); log("\u5f53\u524d\u8fdb\u884c\u4e2d\u7684\u8bfe\u7a0b\u5df2\u53d6\u6d88\u52fe\u9009\uff0c\u5df2\u91cd\u7f6e\u540e\u53f0\u4efb\u52a1"); } }); }); } async function resolveQueueCourse(csId) { let course = state.panelCourses.find((c) => c.csId === csId); if (course) return course; try { course = await fetchCourseFromPlans(csId); } catch (_) {} return course || null; } async function refreshChapterPreview() { const selected = loadQueue(); if (!selected.length) { state.chapterPreview = []; state.queueVideoTotal = 0; state.queueVideoDone = 0; state.queueExamTotal = 0; state.queueExamDone = 0; renderChapterPreview(); updateQueueSummary(); return; } const list = []; let examTotal = 0; let examDone = 0; for (const csId of selected) { const course = await resolveQueueCourse(csId); if (!course) continue; if ((course.courseTestList || []).length) { examTotal += 1; if (isTruthyFlag(course.lcsExameFinished)) examDone += 1; } try { const videos = await loadCourseVideos(csId); list.push({ csId, courseTitle: course.csName || "\u672a\u547d\u540d\u8bfe\u7a0b", videos: videos.map((v) => ({ resId: v.resId, title: v.resName || v.resId, process: Number(v.process) || 0, studyFinished: v.studyFinished, active: false, })), }); } catch (_) {} } const bg = loadBgState(); const activeResId = bg.csId && bg.videos?.length ? String(bg.videos[bg.videoIndex || 0]?.resId || "") : ""; list.forEach((c) => { if (c.csId === bg.csId) { c.videos.forEach((v) => { if (String(v.resId) === activeResId) v.active = true; }); } }); state.chapterPreview = list; state.queueVideoTotal = list.reduce((s, c) => s + (c.videos?.length || 0), 0); state.queueVideoDone = list.reduce((s, c) => { const arr = c.videos || []; return s + arr.filter((v) => v.studyFinished === "1" || Number(v.process) >= 100).length; }, 0); state.queueExamTotal = examTotal; state.queueExamDone = examDone; renderChapterPreview(); updateQueueSummary(); } function renderChapterPreview() { const box = document.getElementById("zjzx-chapter-preview"); if (!box) return; if (!state.chapterPreview.length) { box.innerHTML = "
\u8bf7\u9009\u62e9\u8bfe\u7a0b
"; return; } box.innerHTML = state.chapterPreview .map((course) => { const items = (course.videos || []) .map((v) => { const done = v.studyFinished === "1" || Number(v.process) >= 100; const color = v.active ? "#16a34a" : done ? "#64748b" : "#334155"; const mark = v.active ? "▶" : done ? "✓" : "•"; const pct = Math.round(Number(v.process) || 0); return `
${mark} ${escHtml(v.title)}${pct}%
`; }) .join(""); return `
${escHtml(course.courseTitle)}
${items || "
\u65e0\u89c6\u9891
"}
`; }) .join(""); } function updateQueueSummary() { const doneEl = document.getElementById("zjzx-queue-done"); const totalEl = document.getElementById("zjzx-queue-total"); const pctEl = document.getElementById("zjzx-queue-percent"); const barEl = document.getElementById("zjzx-queue-progress"); const examEl = document.getElementById("zjzx-queue-exam"); const textEl = document.getElementById("zjzx-queue-text"); const q = loadQueue(); const total = Math.max(0, Number(state.queueVideoTotal || 0)); const done = Math.max(0, Math.min(total, Number(state.queueVideoDone || 0))); const examTotal = Math.max(0, Number(state.queueExamTotal || 0)); const examDone = Math.max(0, Math.min(examTotal, Number(state.queueExamDone || 0))); if (doneEl) doneEl.textContent = String(done); if (totalEl) totalEl.textContent = String(total); const pct = total > 0 ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0; if (pctEl) pctEl.textContent = `${pct}%`; if (barEl) barEl.style.width = `${pct}%`; if (examEl) { examEl.textContent = examTotal > 0 ? `${examDone} / ${examTotal}` : "—"; } if (textEl) { if (!q.length) { textEl.textContent = "\u672a\u9009\u62e9"; } else if (total > 0) { const examPart = examTotal > 0 ? ` · \u8003\u8bd5 ${examDone}/${examTotal}` : ""; textEl.textContent = `\u89c6\u9891\u5df2\u5b66\u4e60 ${done}/${total}${examPart}\uff08\u5df2\u9009 ${q.length} \u95e8\uff09`; } else { textEl.textContent = `\u5df2\u9009 ${q.length} \u95e8\uff0c\u6b63\u5728\u7edf\u8ba1…`; } } } function updateCurrentMetaPanel() { const courseEl = document.getElementById("zjzx-current-course"); const chapterEl = document.getElementById("zjzx-current-chapter"); const taskEl = document.getElementById("zjzx-current-task"); const bg = loadBgState(); if (courseEl) courseEl.textContent = bg.csName || "\u65e0"; if (chapterEl) { if (bg.phase === "exam") chapterEl.textContent = "\u8003\u8bd5\u4e2d"; else if (bg.videos?.length) { const cur = bg.videos[bg.videoIndex || 0]; chapterEl.textContent = cur?.resName || cur?.resId || `\u89c6\u9891 ${(bg.videoIndex || 0) + 1}/${bg.videos.length}`; } else chapterEl.textContent = "\u65e0"; } if (taskEl) { if (!state.enabled) { taskEl.textContent = state.panelHint || "\u70b9\u51fb\u300c\u5f00\u59cb\u300d\u540e\u81ea\u52a8\u540e\u53f0\u63d0\u4ea4"; } else if (bg.phase === "exam") taskEl.textContent = "\u6b63\u5728\u4ea4\u5377"; else if (bg.csId && bg.videos?.length) { const cur = Math.min( Number(bg.videoTotal || bg.videos.length), Number(bg.videoDone || 0) + Number(bg.videoIndex || 0) + 1 ); const total = Number(bg.videoTotal || bg.videos.length); const pct = Number(bg.lcsProcess ?? 0).toFixed(1); taskEl.textContent = `\u89c6\u9891 ${cur}/${total} · \u8bfe\u7a0b ${pct}%`; } else taskEl.textContent = state.lastAction || "\u6b63\u5728\u5206\u914d\u4e0b\u4e00\u95e8\u8bfe…"; } } function readPanelCollapsed() { const v = localStorage.getItem(PANEL_COLLAPSED_KEY); return v == null ? true : v === "1"; } function writePanelCollapsed(collapsed) { localStorage.setItem(PANEL_COLLAPSED_KEY, collapsed ? "1" : "0"); } function readPanelPos() { try { return JSON.parse(localStorage.getItem(PANEL_POS_KEY) || "{}"); } catch (_) { return null; } } function writePanelPos(left, top) { localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ left, top })); } function clampPanelInViewport(panel) { if (!panel) return; const rect = panel.getBoundingClientRect(); const maxLeft = Math.max(0, window.innerWidth - panel.offsetWidth); const maxTop = Math.max(0, window.innerHeight - panel.offsetHeight); const left = Math.max(0, Math.min(maxLeft, rect.left)); const top = Math.max(0, Math.min(maxTop, rect.top)); panel.style.right = "auto"; panel.style.bottom = "auto"; panel.style.left = `${left}px`; panel.style.top = `${top}px`; writePanelPos(left, top); } function applyPanelCollapsed(panel, collapsed) { const btnMin = panel.querySelector("#zjzx-btn-min"); const btnMax = panel.querySelector("#zjzx-btn-max"); panel.classList.toggle("zjzx-panel-min", !!collapsed); panel.classList.toggle("zjzx-panel-max", !collapsed); if (btnMin) btnMin.style.display = collapsed ? "none" : ""; if (btnMax) btnMax.style.display = collapsed ? "" : "none"; const footerExtra = panel.querySelector(".zjzx-footer-extra"); if (footerExtra) footerExtra.style.display = collapsed ? "none" : ""; clampPanelInViewport(panel); } function enablePanelDrag(panel) { const header = panel.querySelector("#zjzx-panel-header"); if (!header) return; let dragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; header.addEventListener("mousedown", (e) => { if (e.target?.closest("#zjzx-panel-controls, .zjzx-panel-ctl")) return; dragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; panel.style.right = "auto"; panel.style.bottom = "auto"; e.preventDefault(); }); document.addEventListener("mousemove", (e) => { if (!dragging) return; const left = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, startLeft + (e.clientX - startX))); const top = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, startTop + (e.clientY - startY))); panel.style.left = `${left}px`; panel.style.top = `${top}px`; }); document.addEventListener("mouseup", () => { if (!dragging) return; dragging = false; const rect = panel.getBoundingClientRect(); writePanelPos(rect.left, rect.top); }); } function injectPanelStyles() { const id = "zjzx-panel-style-v2"; if (document.getElementById(id) || !document.head) return; const st = document.createElement("style"); st.id = id; st.textContent = ` #zjzx-auto-panel{position:fixed;right:20px;top:80px;z-index:999999;width:420px;display:flex;flex-direction:column;background:#f4f7fb;border:1px solid #dbe4f0;border-radius:18px;box-shadow:0 18px 44px rgba(15,23,42,.18);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;color:#0f172a;overflow:hidden;transition:max-height .22s ease,box-shadow .22s ease;} #zjzx-auto-panel.zjzx-panel-hidden{display:none !important;} #zjzx-auto-panel.zjzx-panel-max{max-height:min(92vh,800px);} #zjzx-auto-panel.zjzx-panel-min{max-height:none;box-shadow:0 12px 32px rgba(15,23,42,.16);} #zjzx-auto-panel.zjzx-panel-min #zjzx-panel-header{border-bottom:none;} #zjzx-auto-panel.zjzx-panel-min #zjzx-panel-body,#zjzx-auto-panel.zjzx-panel-min #zjzx-panel-footer,#zjzx-auto-panel.zjzx-panel-min .zjzx-footer-extra{display:none !important;} #zjzx-panel-header{flex:0 0 auto;} #zjzx-panel-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:9px;} .zjzx-footer-extra{flex:0 0 auto;} #zjzx-panel-footer{flex:0 0 auto;} #zjzx-panel-header{padding:10px 12px;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;} #zjzx-panel-brand{display:flex;align-items:center;gap:10px;min-width:0;flex:1;} #zjzx-panel-logo{width:34px;height:34px;border-radius:10px;object-fit:cover;box-shadow:0 2px 10px rgba(15,23,42,.12);border:1px solid rgba(148,163,184,.45);background:#fff;flex:0 0 auto;} #zjzx-panel-title{font-size:13px;font-weight:900;color:#9a3412;line-height:1.28;display:flex;align-items:flex-start;gap:6px;flex-wrap:wrap;} .zjzx-panel-title-text{flex:1 1 12em;min-width:0;letter-spacing:-0.01em;} #zjzx-panel-sub{display:flex;flex-wrap:wrap;align-items:center;gap:4px 5px;margin-top:4px;line-height:1.35;} .zjzx-sub-chip{font-size:11px;color:#7c2d12;background:rgba(255,255,255,.62);padding:2px 8px;border-radius:999px;border:1px solid rgba(180,83,9,.18);font-weight:700;} .zjzx-sub-chip-em{color:#0f766e;background:rgba(236,253,245,.9);border-color:rgba(15,118,110,.28);} .zjzx-sub-dot{color:#d6d3d1;font-size:10px;font-weight:400;} .zjzx-panel-ctl .zjzx-ctl-ico{display:block;box-sizing:border-box;margin:0 auto;} .zjzx-ctl-min{width:11px;height:2px;background:#64748b;border-radius:1px;} .zjzx-ctl-plus{font-size:18px;line-height:1;font-weight:700;color:#64748b;} .zjzx-panel-version{font-size:11px;font-weight:900;color:#64748b;padding:2px 8px;border-radius:999px;background:#f1f5f9;border:1px solid #e2e8f0;} #zjzx-panel-controls{display:flex;align-items:center;gap:6px;flex:0 0 auto;} .zjzx-panel-ctl{border:none;background:#fff;color:#64748b;width:30px;height:30px;border-radius:999px;cursor:pointer;box-shadow:0 1px 2px rgba(15,23,42,.1);font-size:16px;line-height:1;font-weight:900;padding:0;} .zjzx-panel-ctl:hover{filter:brightness(1.03);} .zjzx-card{background:#fff;border:1px solid #d9e2ee;border-radius:14px;padding:9px 10px;margin-bottom:9px;} .zjzx-card-status{padding:7px 9px;margin-bottom:8px;} .zjzx-status-row{display:flex;justify-content:space-between;align-items:center;gap:8px;line-height:1.3;} .zjzx-status-label{font-size:11px;color:#64748b;font-weight:700;} .zjzx-status-badge{padding:2px 8px;border-radius:999px;font-weight:800;font-size:11px;background:#fff;border:1px solid #cbd5e1;color:#334155;} .zjzx-status-main{margin-top:4px;} .zjzx-status-metrics{display:flex;align-items:center;gap:6px;font-size:12px;color:#475569;min-width:0;flex:1;} .zjzx-status-metrics em{font-style:normal;font-weight:900;color:#0f172a;} .zjzx-metric-div{color:#cbd5e1;} .zjzx-progress-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;font-size:12px;color:#334155;} .zjzx-progress-num{font-size:28px;font-weight:900;color:#0f172a;} .zjzx-progress-num small{font-size:16px;color:#64748b;} .zjzx-card-status .zjzx-progress-pct{font-size:15px;font-weight:900;color:#0369a1;flex:0 0 auto;} .zjzx-progress-pct{font-size:22px;font-weight:900;color:#0369a1;} .zjzx-card-status .zjzx-progress-bar{height:5px;margin-top:5px;margin-bottom:0;} .zjzx-progress-bar{height:10px;border-radius:999px;background:#e2e8f0;overflow:hidden;margin-top:6px;} .zjzx-progress-bar>span{display:block;height:100%;width:0;background:linear-gradient(90deg,#22d3ee,#2563eb);transition:width .2s ease;} .zjzx-list-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;} .zjzx-plan-row{padding:0 2px 10px;} .zjzx-plan-select{width:100%;border:1px solid #cbd5e1;border-radius:8px;padding:7px 10px;font-size:12px;background:#fff;color:#0f172a;} .zjzx-list-title{font-size:12px;color:#64748b;font-weight:700;} .zjzx-list-tag{font-size:11px;color:#92400e;background:#ffedd5;border:1px solid #fdba74;border-radius:999px;padding:2px 8px;} .zjzx-settings-group{background:#f8fafc;border:1px solid #dbe4f0;border-radius:14px;padding:10px;margin-bottom:10px;} .zjzx-settings-title{font-size:12px;color:#64748b;font-weight:900;margin-bottom:8px;} .zjzx-hidden{display:none !important;} .zjzx-btn-row-nowrap{flex-wrap:nowrap;} .zjzx-btn-action{min-width:0 !important;flex:1 1 0 !important;} .zjzx-btn-ico{font-size:14px;line-height:1;display:inline-flex;align-items:center;margin-right:6px;} .zjzx-btn-label{white-space:nowrap;} .zjzx-toggle-on{background:linear-gradient(135deg,#1d4ed8,#0ea5e9) !important;border-color:transparent !important;color:#fff !important;} .zjzx-card-actions .zjzx-btn-row{margin-bottom:0;} .zjzx-empty-state{padding:14px 8px;text-align:center;color:#94a3b8;font-size:12px;font-weight:900;} #zjzx-course-list,#zjzx-chapter-preview,#zjzx-run-log{max-height:200px;overflow-y:auto;background:#f8fafc;border:1px solid #dbe4f0;border-radius:12px;padding:6px;} #zjzx-settings-pane{padding:2px 0;} .zjzx-chapter-course{margin-bottom:8px;border:1px solid #dbe4f0;border-radius:10px;background:#fff;} .zjzx-chapter-title{padding:7px 10px;background:#eaf1ff;border-bottom:1px solid #dbe4f0;color:#1d4ed8;font-size:12px;font-weight:700;} .zjzx-chapter-item{padding:5px 10px;font-size:12px;display:flex;justify-content:space-between;gap:8px;} .zjzx-dot{font-weight:700;} .zjzx-footer-extra{padding:7px 10px;background:#f8fafc;border-top:1px solid #e2e8f0;opacity:.95;} .zjzx-ann{position:relative;font-size:12px;color:#334155;line-height:1.5;background:linear-gradient(180deg,#fffdf5,#fff7e6);border:1px solid #fcd34d;border-radius:12px;padding:10px 10px 10px 12px;margin-bottom:0;} .zjzx-ann-title{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:900;color:#9a3412;margin-bottom:4px;} .zjzx-ann-text{display:block;word-break:break-word;} .zjzx-ann::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:linear-gradient(180deg,#f59e0b,#ef4444);border-top-left-radius:12px;border-bottom-left-radius:12px;} .zjzx-qq-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:6px;} .zjzx-qq-text{font-size:12px;color:#475569;} .zjzx-qq-title{font-size:13px;font-weight:800;color:#0f172a;} .zjzx-qq-btn{border:none;background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;padding:6px 14px;border-radius:999px;font-weight:800;cursor:pointer;} .zjzx-exam-hint{font-size:11px;color:#64748b;margin-top:6px;line-height:1.45;} .zjzx-start-hint{font-size:12px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:8px;line-height:1.45;text-align:center;font-weight:600;} .zjzx-btn-pro{flex:0 0 auto;background:linear-gradient(135deg,#f59e0b,#ef4444);color:#fff;border:none;box-shadow:0 4px 12px rgba(239,68,68,.25);padding:4px 8px;font-size:12px;border-radius:11px;cursor:pointer;font-weight:800;} .zjzx-btn-pro:hover{filter:brightness(1.06);} #zjzx-pro-modal{position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:1000001;display:none;align-items:center;justify-content:center;padding:20px;} .zjzx-pro-card{width:min(560px,92vw);max-height:88vh;overflow:auto;background:#fff;border-radius:18px;border:1px solid #dbe4f0;box-shadow:0 18px 46px rgba(15,23,42,.25);padding:16px;} .zjzx-pro-title{font-size:24px;font-weight:900;color:#0f172a;} .zjzx-pro-sub{font-size:13px;color:#64748b;margin-top:4px;line-height:1.45;} .zjzx-pro-sec{margin-top:12px;border:1px solid #dbe4f0;border-radius:12px;padding:12px;background:#f8fafc;} .zjzx-pro-sec h4{margin:0 0 8px;font-size:15px;color:#0f172a;} .zjzx-pro-sec p{margin:4px 0;font-size:13px;color:#334155;line-height:1.5;} .zjzx-pro-sec ul{margin:6px 0 0 18px;padding:0;} .zjzx-pro-sec li{margin:4px 0;font-size:13px;color:#334155;line-height:1.45;} .zjzx-pro-buy{display:flex;flex-direction:column;gap:10px;margin-top:8px;} .zjzx-pro-muted{color:#64748b;font-size:12px;line-height:1.5;} .zjzx-pro-buy-btn{display:inline-block;background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;padding:10px 16px;border-radius:12px;font-size:14px;font-weight:800;border:none;cursor:pointer;align-self:flex-start;} .zjzx-pro-buy-btn:hover{filter:brightness(1.05);} .zjzx-pro-buy-btn:disabled{background:#94a3b8;cursor:not-allowed;box-shadow:none;opacity:.92;} .zjzx-pro-buy-btn:disabled:hover{filter:none;} .zjzx-pro-tip{margin-top:8px;background:#fef3c7;border:1px solid #fcd34d;border-radius:10px;padding:8px 10px;font-size:13px;color:#92400e;font-weight:700;line-height:1.45;} .zjzx-pro-tip-info{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8;font-weight:600;} .zjzx-pro-actions{margin-top:14px;display:flex;justify-content:flex-end;} .zjzx-pro-close{border:none;background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;padding:10px 18px;border-radius:12px;font-size:14px;font-weight:800;cursor:pointer;} .zjzx-tabbar{display:flex;gap:6px;margin-bottom:10px;} .zjzx-tab-btn{flex:1;border:1px solid #cbd5e1;background:#f8fafc;color:#475569;padding:5px 4px;border-radius:10px;cursor:pointer;font-weight:700;font-size:11px;} .zjzx-tab-btn.active{background:linear-gradient(135deg,#1d4ed8,#0ea5e9);color:#fff;border-color:transparent;} .zjzx-pane{display:none;}.zjzx-pane.active{display:block;} .zjzx-btn-row{display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap;} .zjzx-btn{flex:1;border:none;color:#fff;padding:8px 10px;border-radius:11px;cursor:pointer;font-weight:800;min-width:72px;} .zjzx-btn-start{background:#16a34a;}.zjzx-btn-stop{background:#ef4444;}.zjzx-btn-refresh{background:#64748b;} .zjzx-btn-ghost{border:1px solid #cbd5e1;background:#fff;color:#0f172a;flex:0 0 auto;} .zjzx-meta-row{display:flex;justify-content:space-between;gap:8px;font-size:12px;margin-bottom:5px;} .zjzx-meta-label{color:#64748b;}.zjzx-meta-value{font-weight:700;text-align:right;} .zjzx-auth-badge{padding:2px 8px;border-radius:999px;border:1px solid #cbd5e1;font-size:11px;font-weight:900;} #zjzx-auto-status{padding:4px 10px;border-radius:999px;font-weight:900;font-size:12px;background:#fff;border:1px solid #cbd5e1;} .zjzx-card-status #zjzx-auto-status{padding:2px 8px;font-size:11px;} #zjzx-panel-footer{padding:8px 12px;background:#eef2f7;border-top:1px solid #dbe4f0;font-size:12px;} .zjzx-log-row{padding:4px 6px;border-bottom:1px dashed #d4deea;font-size:12px;line-height:1.45;} `; document.head.appendChild(st); } function createPanel() { const old = document.getElementById("zjzx-auto-panel") || document.getElementById("zjzx-auto-study-panel"); if (old) old.remove(); injectPanelStyles(); const panel = document.createElement("div"); panel.id = "zjzx-auto-panel"; panel.innerHTML = `
\u5b89\u5fbd\u4e13\u4e1a\u6280\u672f\u4eba\u5458\u7ee7\u7eed\u6559\u80b2\u5728\u7ebf\u5b66\u4e60\u52a9\u624bv${SCRIPT_VERSION}
\u4e00\u952e\u5b8c\u6210\u89c6\u9891\u5b66\u65f6·\u5b66\u5b8c\u81ea\u52a8\u8003\u8bd5·\u7701\u65f6\u7701\u5fc3
\u8fd0\u884c\u72b6\u6001 \u5df2\u505c\u6b62
0/0 \u89c6\u9891\u5df2\u5b66\u4e60 · \u8003\u8bd5
0%
\u8bfe\u7a0b\u9009\u62e9\u8bfe\u7a0b\u5217\u8868
\u767b\u5f55\u540e\u5c06\u81ea\u52a8\u52a0\u8f7d\u8bfe\u7a0b
\u7ae0\u8282\u9884\u89c8\u5b9e\u65f6\u8fdb\u5ea6
\u8bf7\u9009\u62e9\u8bfe\u7a0b
\u8fd0\u884c\u65e5\u5fd7\u5b9e\u65f6\u65e5\u5fd7
\u6388\u6743\u4e0e\u9009\u9879\u8bbe\u7f6e
\u7528\u6237\u7c7b\u578b\u672a\u6821\u9a8c
\u514d\u8d39\u4f53\u9a8c\uff081\u4e2a\u89c6\u9891\uff090/1
Token
\u4e91\u7aef\u670d\u52a1${DEFAULT_CLOUD_API_BASE}
\u8fd0\u884c\u6a21\u5f0f
\u4e0a\u62a5\u95f4\u9694 \u79d2
\u5b66\u65f6\u500d\u7387 \u500d
\u5b66\u5b8c\u540e\u81ea\u52a8\u8003\u8bd5\uff08\u5df2\u9ed8\u8ba4\u5f00\u542f\uff09
\u5f53\u524d\u8bfe\u7a0b\u65e0
\u5f53\u524d\u7ae0\u8282\u65e0
\u5f53\u524d\u4efb\u52a1\u70b9\u51fb\u300c\u5f00\u59cb\u300d\u540e\u81ea\u52a8\u540e\u53f0\u63d0\u4ea4
`; document.body.appendChild(panel); createProModal(); const savedPos = readPanelPos(); if (savedPos && 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); const btnMin = document.getElementById("zjzx-btn-min"); const btnMax = document.getElementById("zjzx-btn-max"); if (btnMin) { btnMin.addEventListener("click", (e) => { e.stopPropagation(); writePanelCollapsed(true); applyPanelCollapsed(panel, true); }); } if (btnMax) { btnMax.addEventListener("click", (e) => { e.stopPropagation(); writePanelCollapsed(false); applyPanelCollapsed(panel, false); }); } panel.querySelectorAll(".zjzx-tab-btn").forEach((btn) => { btn.addEventListener("click", () => switchPanelTab(btn.dataset.tab)); }); const planSel = document.getElementById("zjzx-plan-select"); if (planSel) { planSel.addEventListener("change", async () => { saveSelectedTpId(planSel.value); state.panelRefreshing = true; updatePanel(); try { await fetchPanelCourses(); renderPlanSelect(); renderCourseList(); await refreshChapterPreview(); } catch (err) { log("\u5207\u6362\u57f9\u8bad\u8ba1\u5212\u5931\u8d25\uff1a" + (err.message || err)); } finally { state.panelRefreshing = false; updatePanel(); } }); } document.getElementById("zjzx-start").addEventListener("click", async () => { if (state.enabled) return; if (!(await _cr())) { updatePanel(); return; } const q = loadQueue(); if (!q.length) { log("\u8bf7\u5148\u52fe\u9009\u81f3\u5c11\u4e00\u95e8\u8bfe\u7a0b"); setPanelHint("\u8bf7\u5148\u52fe\u9009\u81f3\u5c11\u4e00\u95e8\u8bfe\u7a0b\u518d\u70b9\u5f00\u59cb"); updatePanel(); return; } clearPanelHint(); const bg = loadBgState(); if (bg.csId && !q.includes(bg.csId)) saveBgState({}); state.enabled = true; localStorage.setItem(STORAGE_KEY, "1"); state.lastTick = 0; syncKeepAlive(); log("\u81ea\u52a8\u770b\u8bfe\u5df2\u5f00\u542f"); startPreviewAutoRefresh(); updatePanel(); tick(); }); document.getElementById("zjzx-stop").addEventListener("click", () => { if (!state.enabled) return; state.enabled = false; localStorage.setItem(STORAGE_KEY, "0"); syncKeepAlive(); stopPreviewAutoRefresh(); log("\u81ea\u52a8\u770b\u8bfe\u5df2\u6682\u505c"); updatePanel(); }); document.getElementById("zjzx-join-qq").addEventListener("click", () => { try { GM_openInTab(QQ_GROUP_LINK, { active: true, insert: true, setParent: true }); } catch (_) { window.open(QQ_GROUP_LINK, "_blank"); } }); document.getElementById("zjzx-open-pro")?.addEventListener("click", () => { openProModal(); }); document.getElementById("zjzx-cloud-save").addEventListener("click", async () => { const input = document.getElementById("zjzx-cloud-token"); state.cloudToken = String((input && input.value) || "").trim(); state.cloudRevoked = false; localStorage.setItem(CLOUD_TOKEN_KEY, state.cloudToken); writeCloudLeaseCache("", 0); state.cloudLease = ""; state.cloudLeaseExp = 0; try { if (state.cloudToken) await _vt(); await _el(true); log("\u4e91\u7aef\u6388\u6743\u5df2\u66f4\u65b0"); } catch (err) { log("\u4e91\u7aef\u6821\u9a8c\u5931\u8d25\uff1a" + (err.message || err)); } updateCloudPanelUI(); }); applyLockedPanelDefaults(); setCloudApiBase(); state.cloudToken = String(localStorage.getItem(CLOUD_TOKEN_KEY) || "").trim(); const tokenInput = document.getElementById("zjzx-cloud-token"); if (tokenInput) tokenInput.value = state.cloudToken; updateCloudPanelUI(); updatePanel(); renderCourseList(); updateQueueSummary(); void _cf(); void _pn(); } function shouldHidePanelOnPage() { return pageType() === "courseplay"; } function syncPanelVisibility() { const hide = shouldHidePanelOnPage(); const panel = document.getElementById("zjzx-auto-panel"); if (panel) panel.classList.toggle("zjzx-panel-hidden", hide); if (hide) closeProModal(); } function updatePanel() { syncPanelVisibility(); syncProBuyButtonUi(); const statusEl = document.getElementById("zjzx-auto-status"); if (!statusEl) return; if (shouldHidePanelOnPage()) return; if (state.panelRefreshing) { statusEl.textContent = "\u5237\u65b0\u4e2d…"; } else if (!state.enabled) { statusEl.textContent = "\u5df2\u505c\u6b62"; } else { const bg = loadBgState(); if (bg.phase === "exam") statusEl.textContent = "\u8003\u8bd5\u4e2d"; else if (bg.csId && bg.videos?.length) { statusEl.textContent = `\u8fd0\u884c ${(bg.videoIndex || 0) + 1}/${bg.videos.length}`; } else statusEl.textContent = "\u8fd0\u884c\u4e2d"; } updateCloudPanelUI(); updateCurrentMetaPanel(); updateQueueSummary(); const hintEl = document.getElementById("zjzx-start-hint"); if (hintEl) { if (state.panelHint && !state.enabled) { hintEl.textContent = state.panelHint; hintEl.style.display = "block"; } else { hintEl.textContent = ""; hintEl.style.display = "none"; } } } async function initPanelData() { if (!isLoggedIn()) return; await refreshPanelCourses(); try { await _el(false); } catch (_) {} } function init() { createPanel(); syncPanelVisibility(); syncKeepAlive(); if (state.enabled) log("\u81ea\u52a8\u770b\u8bfe\u5df2\u5f00\u542f\uff08\u7b49\u5f85\u4e91\u7aef\u6388\u6743\uff09"); initPanelData(); setInterval(tick, TICK_MS); setInterval(updatePanel, 1000); setInterval(() => { if (isLoggedIn()) _el(false).catch(() => {}); }, 10 * 60 * 1000); window.addEventListener("hashchange", () => { state.lastTick = 0; syncPanelVisibility(); }); window.addEventListener("popstate", () => { syncPanelVisibility(); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();