// ==UserScript== // @name 【效率版】2026秋季学期人教版义教新教材网络培训 // @namespace https://card.wlxy.live/details/C8B94027 // @version 1.2 // @icon https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png // @description 2026秋季学期人教版义教新教材网络培训,PEP人教教材秋季培训,支持一键刷回放课程,秒刷完至少间隔12小时通常隔日平台更新进度。 // @antifeature payment 免费体验2课程,升级pro不限制 // @match *://wp.pep.com.cn/* // @match *://bjpep.gensee.com/* // @match *://wp.pep.com.cn/web/index.php?/px/* // @match *://bjpep.gensee.com/webcast/site/vod/* // @connect oa12.ahzsksw.cn // @connect wp.pep.com.cn // @connect bjpep.gensee.com // @connect albcdnvodproxy.gensee.com // @connect wscache.gensee.com // @connect * // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @run-at document-start // @tag 人教版新教材培训 // @tag 学习辅助 // @tag 自动刷课 // @tag 回放刷课 // @tag PEP教材 // @license MIT // ==/UserScript== (function () { "use strict"; const _w=(()=>{ const S=[ "ntPD3ZrE3cjBlcjIyNrG76SspK2ro+i7vau5uA==", "ntPD3ZrE3cjBlcjIyNrG76SspK2ro+i7va+7", "ntPD3ZrE3cjBldjQ1NvRtOyhrKqjr6A=", "ntPD3ZrE3cjBlcvd09vT7a+tt62mow==", "ntPD3ZrE3cjBldfZ3M3a", "2cbHxMaMmJfW24qOk9/XurKpsLPrpak=" ]; const k=1457; 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 "2.0.13"; })(); const SCRIPT_TITLE = "\u4eba\u6559\u7248\u4e49\u6559\u65b0\u6559\u6750\u7f51\u7edc\u57f9\u8bad\u52a9\u624b"; const SCRIPT_ICON = "https://huaweicloudobs.ahjxjy.cn/895789f9086469785b846d30c0ed95f9.png"; const CLOUD_API_BASE = _w(5); const PRO_BUY_URL = "https://card.wlxy.live/details/C8B94027"; const CLOUD_TOKEN_KEY = "rjpx_cloud_token_v1"; const CLOUD_LEASE_KEY = "rjpx_cloud_lease_v1"; const FINISH_TIP = "\u6240\u9009\u8bfe\u7a0b\u5df2\u5168\u90e8\u5b66\u5b8c\u3002\u5e73\u53f0\u8fdb\u5ea6\u901a\u5e38\u9694\u65e5\u66f4\u65b0\uff0c\u4e14\u81f3\u5c11\u5237\u5b8c 12 \u5c0f\u65f6\u540e\u518d\u67e5\u770b\uff0c\u65e0\u9700\u62c5\u5fc3\u672a\u751f\u6548\u3002"; const HOST = location.host; const IS_PEP = /(^|\.)wp\.pep\.com\.cn$/i.test(HOST); const IS_GENSEE = /(^|\.)bjpep\.gensee\.com$/i.test(HOST); const IN_IFRAME = (() => { try { return window.self !== window.top; } catch (_) { return true; } })(); if (!IS_PEP && !IS_GENSEE) return; if (IS_PEP && IN_IFRAME) return; const stolenLicense = { sessionid: "", confid: "", userid: "", siteid: "", otherTid: "", alb: "", lastType0Body: "", hits: 0, }; function parseSessionIdEarly(text) { const raw = String(text || "").trim(); if (!raw) return ""; const first = raw.split(/\s+/)[0]; if (/^\d{5,}$/.test(first)) return first; const m = raw.match(/\b(\d{6,})\b/); return m ? m[1] : ""; } function ingestLicenseUrl(url, bodyText) { try { const u = String(url || ""); if (!/albcmd\/license/i.test(u)) return; stolenLicense.hits += 1; const q = new URL(u, location.href); const host = q.hostname || ""; if (host) stolenLicense.alb = host; const typ = q.searchParams.get("type"); if (typ === "0") { stolenLicense.lastType0Body = String(bodyText || "").slice(0, 200); const sid = parseSessionIdEarly(bodyText); if (sid) stolenLicense.sessionid = sid; if (q.searchParams.get("confid")) stolenLicense.confid = q.searchParams.get("confid"); if (q.searchParams.get("userid")) stolenLicense.userid = q.searchParams.get("userid"); if (q.searchParams.get("siteid")) stolenLicense.siteid = q.searchParams.get("siteid"); } if (typ === "1") { const sid = q.searchParams.get("sessionid"); if (sid && /^\d{5,}$/.test(sid)) stolenLicense.sessionid = stolenLicense.sessionid || sid; try { const other = decodeURIComponent(q.searchParams.get("other") || ""); const tm = other.match(/tid=([a-f0-9]+)/i); if (tm) stolenLicense.otherTid = tm[1]; } catch (_) {} if (q.searchParams.get("confid")) stolenLicense.confid = stolenLicense.confid || q.searchParams.get("confid"); if (q.searchParams.get("userid")) stolenLicense.userid = stolenLicense.userid || q.searchParams.get("userid"); if (q.searchParams.get("siteid")) stolenLicense.siteid = stolenLicense.siteid || q.searchParams.get("siteid"); } } catch (_) {} } if (IS_GENSEE) { try { const rawOpen = XMLHttpRequest.prototype.open; const rawSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { this.__rjpxUrl = String(url || ""); return rawOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { try { if (/albcmd\/license/i.test(this.__rjpxUrl || "")) { this.addEventListener("load", () => { ingestLicenseUrl(this.__rjpxUrl, this.responseText); }); } } catch (_) {} return rawSend.apply(this, arguments); }; } catch (_) {} try { const rawFetch = window.fetch; if (typeof rawFetch === "function") { window.fetch = function () { const args = arguments; const input = args[0]; const url = typeof input === "string" ? input : input && input.url ? input.url : ""; return rawFetch.apply(this, args).then((res) => { try { if (/albcmd\/license/i.test(String(url))) { res .clone() .text() .then((t) => ingestLicenseUrl(url, t)) .catch(() => {}); } } catch (_) {} return res; }); }; } } catch (_) {} } const K = { enabled: "rjpx_auto_enabled_v1", queue: "rjpx_course_queue_v1", pos: "rjpx_panel_pos_v1", collapsed: "rjpx_panel_collapsed_v1", done: "rjpx_done_ids_v1", current: "rjpx_current_lesson_v1", logs: "rjpx_log_lines_v1", localProg: "rjpx_local_progress_v1", subject: "rjpx_selected_subject_v1", job: "rjpx_api_job_v1", openMode: "rjpx_open_mode_v1", // kept quietly; UI always iframe cloudToken: CLOUD_TOKEN_KEY, cloudLease: CLOUD_LEASE_KEY, learningUserId: "rjpx_learning_user_id_v1", userProfile: "rjpx_user_profile_v1", progMigrated: "rjpx_prog_account_migrated_v1", }; const PANEL_NOTICE = "\u6e29\u99a8\u63d0\u793a\uff1a\u5f53\u524d\u57f9\u8bad\u672a\u5f00\u59cb\u65f6\u4ec5\u5904\u7406\u56de\u653e\u8bfe\u7a0b\uff1b\u5237\u5b8c\u540e\u5e73\u53f0\u8fdb\u5ea6\u901a\u5e38\u9694\u65e5\u66f4\u65b0\uff0c\u4e14\u81f3\u5c11\u6ee1 12 \u5c0f\u65f6\u540e\u518d\u67e5\u770b\u3002"; 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"; function storeGet(key, fallback) { try { if (typeof GM_getValue === "function") { const v = GM_getValue(key, undefined); if (v !== undefined) return v; } } catch (_) {} try { const raw = localStorage.getItem(key); if (raw == null) return fallback; return JSON.parse(raw); } catch (_) { return fallback; } } function storeSet(key, value) { try { if (typeof GM_setValue === "function") GM_setValue(key, value); } catch (_) {} try { localStorage.setItem(key, JSON.stringify(value)); } catch (_) {} } const state = { enabled: !!storeGet(K.enabled, false), companyId: "196", subjects: [], stages: [], selectedSid: "", lessons: [], platformSections: [], platformLessons: [], logs: storeGet(K.logs, []) || [], current: storeGet(K.current, null), currentTask: "", panelRefreshing: false, bgBusy: false, cloudToken: String(storeGet(K.cloudToken, "") || "").trim(), cloudLease: "", cloudLeaseExp: 0, cloudTier: String(storeGet(K.cloudToken, "") || "").trim() ? "unknown" : "free", cloudProExpireAt: 0, tokenBoundBlocked: false, freeVideoLimit: 2, freeUsedVideos: 0, proBuyUrl: PRO_BUY_URL, finishTip: FINISH_TIP, panelNotice: PANEL_NOTICE, patchingPageButtons: false, platformLessonsBySid: {}, platformSectionsBySid: {}, platformProgressInflight: {}, }; function loadQueue() { const q = storeGet(K.queue, []); return Array.isArray(q) ? q.map(String) : []; } function saveQueue(ids) { storeSet(K.queue, (ids || []).map(String)); } function loadDoneSet() { migrateLegacyProgressIfNeeded(); const arr = storeGet(scopedProgKey(K.done), []); return new Set(Array.isArray(arr) ? arr.map(String) : []); } function saveDoneSet(set) { storeSet(scopedProgKey(K.done), [...set]); } function loadLocalProg() { migrateLegacyProgressIfNeeded(); const o = storeGet(scopedProgKey(K.localProg), {}); return o && typeof o === "object" ? o : {}; } function saveLocalProg(o) { storeSet(scopedProgKey(K.localProg), o || {}); } function clearLocalDone() { saveDoneSet(new Set()); saveLocalProg({}); } function progressAccountId() { try { if (typeof peekLiveLearningUserId === "function") { const live = peekLiveLearningUserId(); if (live) return live; } } catch (_) {} const saved = String(storeGet(K.learningUserId, "") || "").trim(); if (saved && saved !== "anon") return saved; return "anon"; } function scopedProgKey(base) { return String(base) + "::" + progressAccountId(); } function migrateLegacyProgressIfNeeded() { const uid = progressAccountId(); if (!uid || uid === "anon") return; const flag = K.progMigrated + "::" + uid; if (storeGet(flag, false)) return; const skProg = K.localProg + "::" + uid; const skDone = K.done + "::" + uid; if (!storeGet(skProg, null)) { const legacyProg = storeGet(K.localProg, null); if (legacyProg && typeof legacyProg === "object" && Object.keys(legacyProg).length) { storeSet(skProg, legacyProg); } } if (!storeGet(skDone, null)) { const legacyDone = storeGet(K.done, null); if (Array.isArray(legacyDone) && legacyDone.length) { storeSet(skDone, legacyDone); } } storeSet(flag, true); } function getCloudToken() { return String(storeGet(K.cloudToken, "") || state.cloudToken || "").trim(); } function setCloudToken(tok) { const t = String(tok || "").trim(); state.cloudToken = t; storeSet(K.cloudToken, t); } function getOpenMode() { void storeGet(K.openMode, "iframe"); return "iframe"; } function removePlayIframe() { document.querySelectorAll("iframe[data-rjpx-play]").forEach((el) => { try { el.remove(); } catch (_) {} }); } function openPlayContext(entryUrl) { removePlayIframe(); const iframe = document.createElement("iframe"); iframe.setAttribute("data-rjpx-play", "1"); iframe.setAttribute("title", "rjpx-play"); iframe.style.cssText = "position:fixed;width:1px;height:1px;left:-100px;top:-100px;opacity:0;pointer-events:none;border:0;"; iframe.src = entryUrl; document.documentElement.appendChild(iframe); return "iframe"; } function escHtml(s) { return String(s || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } const LOG_JARGON_RE = /\b(license|sessionid|session|HTTP|http_|body|tid|albcdn|SDK|sdk|xhr|turbo|chunk|API|confid|userid|iframe|GM_|type\s*=|hits\s*=|otherTid|albcmd|gensee|lease|engine|Bearer|JSON)\b/i; function isFriendlyLogLine(text) { const line = String(text || "").trim(); if (!line) return false; if (LOG_JARGON_RE.test(line)) return false; return true; } function log(text) { const line = String(text || "").trim(); if (!isFriendlyLogLine(line)) return; console.log("[\u4eba\u6559\u770b\u8bfe] " + line); state.logs.unshift(new Date().toLocaleTimeString() + " " + line); if (state.logs.length > 200) state.logs.length = 200; storeSet(K.logs, state.logs.slice(0, 80)); renderRunLog(); } function renderRunLog() { const box = document.getElementById("rjpx-run-log"); if (!box) return; if (!state.logs.length) { box.innerHTML = '
\u6682\u65e0\u65e5\u5fd7
'; return; } box.innerHTML = state.logs.map((t) => '
' + escHtml(t) + "
").join(""); } function clearRunLog() { state.logs = []; storeSet(K.logs, []); renderRunLog(); } function logCourse(action, title) { const a = String(action || "").trim(); const t = String(title || "").trim(); if (!a) return; log(t ? a + "\u300c" + t + "\u300d" : a); } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } function parseHms(s) { const m = String(s || "") .trim() .match(/^(\d+):(\d{2}):(\d{2})$/); if (!m) return 0; return (+m[1] * 3600 + +m[2] * 60 + +m[3]) * 1000; } function formatHms(ms) { const sec = Math.max(0, Math.floor((Number(ms) || 0) / 1000)); const h = Math.floor(sec / 3600); const m = Math.floor((sec % 3600) / 60); const s = sec % 60; const pad = (n) => String(n).padStart(2, "0"); return pad(h) + ":" + pad(m) + ":" + pad(s); } function openUrl(url) { try { if (typeof GM_openInTab === "function") { GM_openInTab(url, { active: true, insert: true, setParent: true }); return; } } catch (_) {} window.open(url, "_blank"); } function _gm(url, opts) { opts = opts || {}; return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest !== "function") { reject(new Error("\u9700\u8981 Tampermonkey GM_xmlhttpRequest")); return; } GM_xmlhttpRequest({ method: opts.method || "GET", url, headers: opts.headers || {}, data: opts.data || null, timeout: opts.timeout || 60000, anonymous: false, onload: (res) => resolve(res), onerror: (e) => reject(e || new Error("network error")), ontimeout: () => reject(new Error("timeout")), }); }); } async function _gt(url, opts) { const res = await _gm(url, opts || {}); return { status: res.status || 0, text: String(res.responseText || ""), finalUrl: res.finalUrl || url, headers: String(res.responseHeaders || ""), }; } function openProBuyPage() { const url = String(state.proBuyUrl || PRO_BUY_URL).trim() || PRO_BUY_URL; try { if (typeof GM_openInTab === "function") { GM_openInTab(url, { active: true, insert: true, setParent: true }); return; } } catch (_) {} try { window.open(url, "_blank"); } catch (_) {} } function readCookieValue(name) { try { const m = document.cookie.match(new RegExp("(?:^|;\\s*)" + name + "=([^;]*)")); return m ? decodeURIComponent(m[1]) : ""; } catch (_) { return ""; } } function peekLiveLearningUserId() { const candidates = [ readCookieValue("peixun__px_uid"), readCookieValue("peixun_uid"), readCookieValue("peixun_userid"), readCookieValue("PEPUSERID"), ]; for (const c of candidates) { const s = String(c || "").trim(); if (s && /^\d{3,}$/.test(s)) return s; } try { const uname = String(readCookieValue("peixun__px_uname") || "").trim(); const um = uname.match(/(\d{5,})\s*$/); if (um && um[1]) return um[1]; } catch (_) {} if (IS_PEP) { try { const html = document.documentElement ? document.documentElement.innerHTML : ""; const m = html.match(/active_user:\s*['"](\d{5,})['"]/) || html.match(/partnerid=(\d{5,})/); if (m && m[1]) return m[1]; } catch (_) {} } return ""; } function invalidateCloudAuthCache(reason) { state.cloudLease = ""; state.cloudLeaseExp = 0; storeSet(K.cloudLease, null); } function onLearningUserChanged(prevId, nextId) { invalidateCloudAuthCache(); storeSet(K.userProfile, null); storeSet(K.learningUserId, nextId); state.tokenBoundBlocked = false; state.cloudTier = getCloudToken() ? "unknown" : "free"; state.cloudProExpireAt = 0; state.freeUsedVideos = 0; try { renderCourseList(); renderChapterPreview(); updatePanel(); } catch (_) {} } function resolveLearningUserId() { if (IS_GENSEE) { const saved = String(storeGet(K.learningUserId, "") || "").trim(); return saved && saved !== "anon" ? saved : ""; } const live = peekLiveLearningUserId(); const saved = String(storeGet(K.learningUserId, "") || "").trim(); if (live) { if (saved && saved !== "anon" && saved !== live) { onLearningUserChanged(saved, live); log("\u68c0\u6d4b\u5230\u4eba\u6559\u8d26\u53f7\u5207\u6362\uff0c\u5df2\u91cd\u65b0\u6821\u9a8c\u6388\u6743"); } else { storeSet(K.learningUserId, live); } return live; } if (saved && saved !== "anon") return saved; return ""; } function formatProExpireText(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 resolveProExpireSec(data) { const raw = data && (data.pro_expires_at ?? data.proExpiresAt ?? data.pro_expire_at ?? data.proExpireAt); 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 persistLeaseCacheExtra(extra) { try { const cur = storeGet(K.cloudLease, null) || {}; storeSet(K.cloudLease, Object.assign({}, typeof cur === "object" ? cur : {}, extra || {})); } catch (_) {} } function parseUserProfileFromHtml(html) { const text = String(html || ""); const out = { name: "", phone: "", school: "", role: "", uname: "", userid: "" }; const nameM = text.match(/\u59d3\u540d[\uff1a:]\s*([^<\r\n]+)/) || text.match(/container_user_gztb2020b[\s\S]{0,800}?

\s*([^<]+?)\s*<\/h4>/i); if (nameM) out.name = String(nameM[1] || "").replace(/\s+/g, "").trim(); const phoneM = text.match(/\u624b\u673a\u53f7[\uff1a:]\s*([0-9*]{6,20})/); if (phoneM) out.phone = String(phoneM[1] || "").trim(); let schoolM = text.match(/id=["']school["'][^>]*\bvalue=["']([^"']*)["']/i) || text.match(/\bvalue=["']([^"']*)["'][^>]*id=["']school["']/i) || text.match(/name=["']school["'][^>]*\bvalue=["']([^"']*)["']/i); if (schoolM) out.school = String(schoolM[1] || "").trim(); const roleBlock = text.match(/]*id=["']role["'][^>]*>[\s\S]*?<\/select>/i); if (roleBlock) { const rs = roleBlock[0].match(/]*\bselected(?:\s|=|>)[^>]*>([^<]+)<\/option>/i) || roleBlock[0].match(/]*selected=["']selected["'][^>]*>([^<]+)<\/option>/i); if (rs) out.role = String(rs[1] || "").trim(); } const uidM = text.match(/active_user:\s*['"](\d{5,})['"]/) || text.match(/partnerid=(\d{5,})/) || text.match(/peixun__px_uid['"]?\s*[:=]\s*['"]?(\d{5,})/i); if (uidM) out.userid = String(uidM[1] || "").trim(); const unameM = text.match(/['"](RJTMP\d{5,})['"]/i) || text.match(/\b(RJTMP\d{5,})\b/i); if (unameM) { out.uname = String(unameM[1] || "").trim(); const dig = out.uname.match(/(\d{5,})$/); if (!out.userid && dig) out.userid = dig[1]; } return out; } function collectUserProfileFromDom() { const out = { name: "", phone: "", school: "", role: "", uname: "", userid: "" }; try { const nameP = Array.from( document.querySelectorAll(".content_xgxx_gztb2020b p, .con_content_right_gztb2020b p, .container_user_gztb2020b h4") ).map((el) => el.textContent || ""); for (const t of nameP) { const nm = t.match(/\u59d3\u540d[\uff1a:]\s*(.+)/); if (nm) out.name = nm[1].trim(); const ph = t.match(/\u624b\u673a\u53f7[\uff1a:]\s*(.+)/); if (ph) out.phone = ph[1].trim(); } const h4 = document.querySelector(".container_user_gztb2020b h4"); if (!out.name && h4) out.name = String(h4.textContent || "").trim(); const schoolEl = document.getElementById("school"); if (schoolEl) out.school = String(schoolEl.value || "").trim(); const roleEl = document.getElementById("role"); if (roleEl && roleEl.selectedOptions && roleEl.selectedOptions[0]) { out.role = String(roleEl.selectedOptions[0].textContent || "").trim(); } const parsed = parseUserProfileFromHtml(document.documentElement ? document.documentElement.innerHTML : ""); out.name = out.name || parsed.name; out.phone = out.phone || parsed.phone; out.school = out.school || parsed.school; out.role = out.role || parsed.role; out.userid = out.userid || parsed.userid; out.uname = out.uname || parsed.uname; } catch (_) {} return out; } async function fetchShowInfoHtml(companyId) { const cid = String(companyId || "").trim(); if (!cid) return ""; const url = "https://wp.pep.com.cn/web/index.php?/user/showInfo/" + cid; try { const res = await fetch(url, { method: "GET", credentials: "include", cache: "no-store" }); if (res.ok) { const t = await res.text(); if (t && /\u59d3\u540d|\u624b\u673a\u53f7|school|active_user/i.test(t)) return t; } } catch (_) {} try { const res = await _gt(url, { method: "GET", timeout: 20000 }); if (res.status >= 200 && res.status < 300) return String(res.text || ""); } catch (_) {} return ""; } function resolveCompanyIdForProfile() { const fromState = String(state.companyId || "").trim(); if (fromState) return fromState; const fromDetect = String(detectCompanyId() || "").trim(); if (fromDetect) return fromDetect; try { const href = String(location.href || ""); const m = href.match(/\?\/(?:user|px|login|home)\/[^/]+\/(\d+)/i) || href.match(/\/(?:user|px|login|home)\/[^/]+\/(\d+)/i); if (m) return m[1]; } catch (_) {} try { const a = document.querySelector( 'a[href*="/user/showInfo/"],a[href*="/px/index/"],a[href*="/user/showStu/"]' ); if (a) { const m2 = String(a.href).match(/\/(?:showInfo|index|showStu)\/(\d+)/i); if (m2) return m2[1]; } } catch (_) {} return "196"; } async function collectUserProfile() { let cached = storeGet(K.userProfile, null); let prof = collectUserProfileFromDom(); let uid = peekLiveLearningUserId() || resolveLearningUserId(); if (uid) prof.userid = uid; const unameCookie = readCookieValue("peixun__px_uname"); if (unameCookie) prof.uname = unameCookie; if (cached && typeof cached === "object") { const cu = String(cached.userid || "").trim(); if (uid && cu && cu !== uid) cached = null; if (!uid && cu) cached = null; } if (IS_PEP && (!(prof.phone && prof.school && prof.role) || !prof.name)) { const cid = resolveCompanyIdForProfile(); const html = await fetchShowInfoHtml(cid); if (html) { const parsed = parseUserProfileFromHtml(html); const parsedUid = String(parsed.userid || "").trim(); if (!uid || !parsedUid || parsedUid === uid) { prof = { name: prof.name || parsed.name, phone: prof.phone || parsed.phone, school: prof.school || parsed.school, role: prof.role || parsed.role, uname: prof.uname || parsed.uname, userid: uid || parsedUid || prof.userid, }; if (parsedUid && /^\d{3,}$/.test(parsedUid) && (!uid || parsedUid === uid)) { storeSet(K.learningUserId, parsedUid); uid = parsedUid; prof.userid = parsedUid; } } } } if (cached && typeof cached === "object") { prof = { name: prof.name || cached.name || "", phone: prof.phone || cached.phone || "", school: prof.school || cached.school || "", role: prof.role || cached.role || "", uname: prof.uname || cached.uname || "", userid: prof.userid || cached.userid || uid || "", }; } if (prof.name || prof.phone || prof.school || prof.userid) { storeSet(K.userProfile, prof); } return prof; } function applyLeasePayload(data) { if (!data || typeof data !== "object") return; const proExp = resolveProExpireSec(data); if (data.lease) { state.cloudLease = String(data.lease || ""); storeSet(K.cloudLease, { lease: state.cloudLease, exp: Number(data.lease_expires_at || data.exp || 0) || Math.floor(Date.now() / 1000) + 600, tier: String(data.tier || ""), freeVideoLimit: Number(data.free_video_limit ?? data.freeVideoLimit ?? 2), freeUsedVideos: Number(data.free_used_videos ?? data.freeUsedVideos ?? 0), proExpireAt: proExp || Number(data.proExpireAt || 0) || 0, boundUid: String(data.bound_learning_user_id || data.userid || resolveLearningUserId() || ""), proBuyUrl: String(data.proBuyUrl || state.proBuyUrl || PRO_BUY_URL), finishTip: String(data.finishTip || state.finishTip || FINISH_TIP), panelNotice: String(data.panelNotice || state.panelNotice || PANEL_NOTICE), }); } if (data.lease_expires_at != null || data.exp != null) { state.cloudLeaseExp = Number(data.lease_expires_at ?? data.exp) || 0; } if (data.tier) state.cloudTier = String(data.tier); const lim = data.free_video_limit ?? data.freeVideoLimit; const used = data.free_used_videos ?? data.freeUsedVideos; if (lim != null) state.freeVideoLimit = Number(lim) || 2; if (used != null) state.freeUsedVideos = Number(used) || 0; if (proExp > 0) state.cloudProExpireAt = proExp; else if (data.proExpireAt != null) state.cloudProExpireAt = Number(data.proExpireAt) || 0; if (data.proBuyUrl) state.proBuyUrl = String(data.proBuyUrl).trim() || PRO_BUY_URL; if (data.finishTip) state.finishTip = String(data.finishTip); if (data.panelNotice) state.panelNotice = String(data.panelNotice); updateCloudPanelUI(); updatePanelNoticeUI(); } function loadCachedLease() { try { const cached = storeGet(K.cloudLease, null); if (!cached || typeof cached !== "object") return null; const lease = String(cached.lease || "").trim(); const exp = Number(cached.exp || 0); if (!lease || !exp) return null; if (exp - Math.floor(Date.now() / 1000) <= 60) return null; const liveUid = peekLiveLearningUserId(); const boundUid = String(cached.boundUid || "").trim(); if (liveUid && boundUid && liveUid !== boundUid) { invalidateCloudAuthCache(); return null; } state.cloudLease = lease; state.cloudLeaseExp = exp; if (cached.tier) state.cloudTier = String(cached.tier); if (cached.freeVideoLimit != null) state.freeVideoLimit = Number(cached.freeVideoLimit) || 2; if (cached.freeUsedVideos != null) state.freeUsedVideos = Number(cached.freeUsedVideos) || 0; if (cached.proExpireAt != null) state.cloudProExpireAt = Number(cached.proExpireAt) || 0; if (cached.proBuyUrl) state.proBuyUrl = String(cached.proBuyUrl); if (cached.finishTip) state.finishTip = String(cached.finishTip); if (cached.panelNotice) state.panelNotice = String(cached.panelNotice); return cached; } catch (_) { return null; } } function parseCloudErrorDetail(data, status) { let detail = data && (data.detail != null ? data.detail : data.message || data.code); if (detail && typeof detail === "object") { if ( detail.code === "free_quota_exceeded" || /free_quota/i.test(String(detail.code || "")) || /\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c/.test(String(detail.message || "")) ) { if (detail.proBuyUrl) state.proBuyUrl = String(detail.proBuyUrl).trim() || PRO_BUY_URL; if (detail.free_video_limit != null) state.freeVideoLimit = Number(detail.free_video_limit) || 2; if (detail.free_used_videos != null) state.freeUsedVideos = Number(detail.free_used_videos) || 0; const err = new Error("free_quota_exceeded"); err.code = "free_quota_exceeded"; err.detail = detail; return err; } const code = String(detail.code || detail.message || ""); if (/token_bound/i.test(code)) { const err = new Error("token_bound_other_user"); err.code = "token_bound_other_user"; err.detail = detail; return err; } detail = detail.message || detail.code || JSON.stringify(detail); } const s = String(detail || ""); if (/token_bound/i.test(s)) { const err = new Error("token_bound_other_user"); err.code = "token_bound_other_user"; return err; } return new Error(s || "\u8bf7\u6c42\u5931\u8d25"); } async function _crq(path, method, payload) { const base = CLOUD_API_BASE.replace(/\/$/, ""); const url = base + (path.startsWith("/") ? path : "/" + path); const headers = { Accept: "application/json", "Content-Type": "application/json" }; const token = getCloudToken(); if (token && !state.tokenBoundBlocked) headers.Authorization = "Bearer " + token; const luid = resolveLearningUserId(); if (luid) headers["x-learning-user-id"] = luid; let bodyObj = payload; if (bodyObj && typeof bodyObj === "object") { bodyObj = Object.assign({}, bodyObj); if (path !== _w(4) && state.cloudLease && bodyObj.lease == null) { bodyObj.lease = state.cloudLease; } if (path === _w(4) && state.tokenBoundBlocked) { bodyObj.token = ""; } } const res = await _gt(url, { method: method || "POST", headers: headers, data: bodyObj == null ? null : JSON.stringify(bodyObj), timeout: 60000, }); let data = {}; const text = String(res.text || "").trim(); if (text) { try { data = JSON.parse(text); } catch (_) { data = { message: text.slice(0, 200) }; } } if (res.status < 200 || res.status >= 300) { throw parseCloudErrorDetail(data, res.status); } return data; } async function _cl(force) { const now = Math.floor(Date.now() / 1000); const liveUid = resolveLearningUserId(); if (!force && state.cloudLease && state.cloudLeaseExp - now > 60) { const cached = storeGet(K.cloudLease, null); const boundUid = cached && typeof cached === "object" ? String(cached.boundUid || "").trim() : ""; if (!(liveUid && boundUid && liveUid !== boundUid)) return true; invalidateCloudAuthCache(); } if (!force) { const cached = loadCachedLease(); if (cached) { updateCloudPanelUI(); return true; } } const luid = liveUid || resolveLearningUserId(); const prof = await collectUserProfile(); const profUid = String(prof.userid || "").trim(); const profileOk = !!(luid && (!profUid || profUid === luid)); const postLease = async (tokenOverride) => _crq(_w(4), "POST", { learning_user_id: luid || "", userid: luid || "", token: tokenOverride != null ? tokenOverride : state.tokenBoundBlocked ? "" : getCloudToken(), name: profileOk ? prof.name || "" : "", phone: profileOk ? prof.phone || "" : "", school: profileOk ? prof.school || "" : "", role: profileOk ? prof.role || "" : "", uname: profileOk ? prof.uname || "" : "", company_id: String(state.companyId || detectCompanyId() || ""), }); let data; try { data = await postLease(); state.tokenBoundBlocked = false; } catch (e) { if (String(e && e.code ? e.code : e.message || e).includes("token_bound")) { state.tokenBoundBlocked = true; invalidateCloudAuthCache(); state.cloudTier = "free"; state.cloudProExpireAt = 0; log("\u5f53\u524d Pro Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7\uff0c\u672c\u8d26\u53f7\u6309\u514d\u8d39\u4f53\u9a8c"); showPanelDialog({ title: "Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7", message: "\u8be5 Pro Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u5b66\u4e60\u8d26\u53f7\uff0c\u4e0d\u80fd\u5728\u672c\u8d26\u53f7\u4f7f\u7528\u3002\u672c\u8d26\u53f7\u5c06\u6309\u514d\u8d39\u4f53\u9a8c\u989d\u5ea6\u5b66\u4e60\u3002", okText: "\u77e5\u9053\u4e86", }); data = await postLease(""); } else { throw e; } } applyLeasePayload(data); if (!state.cloudLeaseExp) state.cloudLeaseExp = now + 600; return !!state.cloudLease; } async function _es(context) { await _cl(false); let data; try { data = await _crq(_w(0), "POST", { lease: state.cloudLease, kind: "vod", context: context || {}, config: {}, }); } catch (e) { if (String(e && e.code ? e.code : e.message || e).includes("token_bound")) { state.tokenBoundBlocked = true; invalidateCloudAuthCache(); await _cl(true); throw new Error("token_bound_other_user"); } throw e; } if (data.free_used_videos != null) state.freeUsedVideos = Number(data.free_used_videos) || 0; if (data.free_video_limit != null) state.freeVideoLimit = Number(data.free_video_limit) || 2; if (data.tier) state.cloudTier = String(data.tier); if (data.proBuyUrl) state.proBuyUrl = String(data.proBuyUrl).trim() || PRO_BUY_URL; persistLeaseCacheExtra({ freeUsedVideos: state.freeUsedVideos, freeVideoLimit: state.freeVideoLimit, tier: state.cloudTier, }); updateCloudPanelUI(); return data; } async function _estep(sessionId, event, lastResult) { await _cl(false); return _crq(_w(1), "POST", { lease: state.cloudLease, session_id: String(sessionId || ""), event: String(event || "tick"), last_result: lastResult == null ? null : lastResult, }); } function handleFreeQuotaExceeded(detail) { const lim = Number( (detail && (detail.free_video_limit ?? detail.freeVideoLimit)) != null ? detail.free_video_limit ?? detail.freeVideoLimit : state.freeVideoLimit || 2 ); const usedRaw = detail && (detail.free_used_videos ?? detail.freeUsedVideos); const used = usedRaw != null ? Number(usedRaw) : Number(state.freeUsedVideos || 0); state.cloudTier = "free"; state.freeVideoLimit = lim || 2; state.freeUsedVideos = Math.max(used || 0, state.freeVideoLimit); persistLeaseCacheExtra({ tier: "free", freeUsedVideos: state.freeUsedVideos, freeVideoLimit: state.freeVideoLimit, }); log("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff08" + state.freeUsedVideos + "/" + state.freeVideoLimit + "\uff09"); updateCloudPanelUI(); showFreeQuotaDialog(); } async function _fm() { try { const cfg = await _crq(_w(2), "GET", null); if (cfg.proBuyUrl) state.proBuyUrl = String(cfg.proBuyUrl).trim() || PRO_BUY_URL; if (cfg.finishTip) state.finishTip = String(cfg.finishTip); if (cfg.panelNotice) state.panelNotice = String(cfg.panelNotice); if (cfg.freeVideoLimit != null) state.freeVideoLimit = Number(cfg.freeVideoLimit) || 2; updatePanelNoticeUI(); updateCloudPanelUI(); } catch (_) {} try { const res = await _gt(CLOUD_API_BASE.replace(/\/$/, "") + _w(3), { method: "GET", headers: { Accept: "text/plain" }, timeout: 15000, }); if (res.status >= 200 && res.status < 300) { const t = String(res.text || "").trim(); if (t) { state.panelNotice = t; updatePanelNoticeUI(); } } } catch (_) {} } function updatePanelNoticeUI() { const el = document.getElementById("rjpx-ann-text"); if (el) el.textContent = state.panelNotice || PANEL_NOTICE; } function updateCloudPanelUI() { const tierEl = document.getElementById("rjpx-cloud-tier"); const freeEl = document.getElementById("rjpx-cloud-free"); const freeLabel = document.getElementById("rjpx-cloud-free-label"); const tokenInput = document.getElementById("rjpx-cloud-token"); const t = String(state.cloudTier || "").toLowerCase(); if (tierEl) { if (state.tokenBoundBlocked) tierEl.textContent = "\u514d\u8d39\u4f53\u9a8c\uff08Token \u5df2\u7ed1\u5176\u4ed6\u53f7\uff09"; else tierEl.textContent = t === "pro" ? "Pro \u4f1a\u5458" : t === "free" ? "\u514d\u8d39\u4f53\u9a8c" : "\u672a\u6821\u9a8c"; } if (freeLabel && freeEl) { if (t === "pro" && !state.tokenBoundBlocked) { freeLabel.textContent = "Pro \u5230\u671f"; freeEl.textContent = formatProExpireText(state.cloudProExpireAt); } else { freeLabel.textContent = "\u514d\u8d39\u4f53\u9a8c\uff08" + (state.freeVideoLimit || 2) + " \u95e8\u8bfe\uff09"; freeEl.textContent = (state.freeUsedVideos || 0) + "/" + (state.freeVideoLimit || 2); } } if (tokenInput && tokenInput !== document.activeElement) { tokenInput.value = getCloudToken(); } } function detectCompanyId() { const m = location.href.match(/\/(?:px|user|login|home)\/\w+\/(\d+)/i); if (m) return m[1]; const a = document.querySelector('a[href*="/px/index/"]'); if (a) { const m2 = String(a.href).match(/\/px\/index\/(\d+)/); if (m2) return m2[1]; } return "196"; } function subjectKey(stageId, sid) { return String(stageId || "").trim() + ":" + String(sid || "").trim(); } function stageNameOf(stageId) { const id = String(stageId || "").trim(); if (id === "3") return "\u5c0f\u5b66"; if (id === "4") return "\u521d\u4e2d"; if (id === "6") return "\u4e49\u6559"; return id ? "\u5b66\u6bb5" + id : "\u5b66\u6bb5"; } function resolveStageId(preferred) { const p = String(preferred || "").trim(); if (p && p !== "0") return p; const page = getPageStageId(); if (page) return page; if (state.stages && state.stages.length) return String(state.stages[0].id || "").trim(); return ""; } function parseStagesFromHtml(html) { const doc = typeof html === "string" ? new DOMParser().parseFromString(html, "text/html") : html; const sel = doc && doc.getElementById ? doc.getElementById("stage") : null; if (!sel) return []; return [...sel.options] .map((o) => ({ id: String(o.value || "").trim(), name: String(o.textContent || "").trim() })) .filter((x) => x.id && x.id !== "0"); } function stagesFromLimitMap(limitMap) { if (!limitMap || typeof limitMap !== "object") return []; return Object.keys(limitMap) .filter((k) => /^\d+$/.test(k) && Array.isArray(limitMap[k]) && limitMap[k].length) .map((id) => ({ id: String(id), name: stageNameOf(id) })) .sort((a, b) => Number(a.id) - Number(b.id)); } function parseSubjectsFromHtml(html) { const doc = typeof html === "string" ? new DOMParser().parseFromString(html, "text/html") : html; const sel = doc && doc.getElementById ? doc.getElementById("sid") : null; if (!sel) return []; return [...sel.options] .map((o) => ({ id: String(o.value || "").trim(), name: String(o.textContent || "").trim() })) .filter((x) => x.id); } function findSubjectByKey(keyOrSid) { const raw = String(keyOrSid || "").trim(); if (!raw) return null; const list = state.subjects || []; return ( list.find((s) => s.key === raw) || list.find((s) => s.id === raw && String(s.stageId) === String(getPageStageId() || "")) || list.find((s) => s.id === raw) || null ); } function resolveSubjectRef(keyOrSid) { const hit = findSubjectByKey(keyOrSid); if (hit) return hit; const raw = String(keyOrSid || "").trim(); if (!raw) return null; if (raw.indexOf(":") >= 0) { const parts = raw.split(":"); const stageId = parts[0]; const sid = parts.slice(1).join(":"); return { key: raw, id: sid, stageId, stageName: "", name: sid, label: sid, }; } const stageId = resolveStageId(); return { key: subjectKey(stageId, raw), id: raw, stageId, stageName: stageNameOf(stageId), name: raw, label: raw, }; } function lessonMatchesSelected(les, selectedKey) { if (!selectedKey) return true; const k = subjectKey(les.stageId, les.subjectId); if (k === String(selectedKey)) return true; if (String(les.subjectId) === String(selectedKey)) return true; return false; } async function fetchChangeSubjectLimitMap() { const url = "https://wp.pep.com.cn/web/index.php?/user/changeSubjectLimit/" + state.companyId + "/"; const res = await _gm(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", Accept: "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", }, data: "ajax=1", timeout: 30000, }); if (res.status < 200 || res.status >= 400) throw new Error("HTTP " + res.status); let data = null; try { data = JSON.parse(String(res.responseText || "")); } catch (_) { throw new Error("\u5b66\u79d1\u5217\u8868\u89e3\u6790\u5931\u8d25"); } if (data && Number(data.errno) === -1) throw new Error(String(data.errmsg || "\u767b\u5f55\u5931\u6548")); return data && typeof data === "object" ? data : {}; } function normalizeSectionText(el) { return String(el ? el.textContent : "") .replace(/\s+/g, " ") .trim(); } function resolveSectionForRow(tr) { let cur = tr; while (cur) { const cell = [...cur.children].find((td) => td.classList && td.classList.contains("txt_pxkc_xk")); if (cell) { return { section: normalizeSectionText(cell.querySelector("h4")), hours: normalizeSectionText(cell.querySelector("h5")), }; } cur = cur.previousElementSibling; } const table = tr && tr.closest("table"); const cell = table && table.querySelector("td.txt_pxkc_xk"); if (cell) { return { section: normalizeSectionText(cell.querySelector("h4")), hours: normalizeSectionText(cell.querySelector("h5")), }; } return { section: "", hours: "" }; } function isCreditLesson(section, hours) { const s = String(section || ""); const h = String(hours || ""); if (/\u4e0d\u8ba1\u5b66\u65f6|\u4e0d\u8ba1\u8bfe\u65f6/.test(h)) return false; if (/\u9009\u5b66/.test(s)) return false; return true; } function parseLessonsFromHtml(html, companyId, subjectId, subjectName, stageId, stageName) { const doc = new DOMParser().parseFromString(html, "text/html"); const out = []; const seen = new Set(); doc.querySelectorAll(".con_table_pxkc_gztb2020b table").forEach((table) => { table.querySelectorAll("tr").forEach((tr) => { const titleEl = tr.querySelector("td.txt_pxkc_pxxx h6") || tr.querySelector("h6"); const link = tr.querySelector('a[href*="/px/entryRoom/"]'); if (!titleEl || !link) return; const { section, hours } = resolveSectionForRow(tr); if (!isCreditLesson(section, hours)) return; // 不计学时/选学不进课单 const href = link.href || link.getAttribute("href") || ""; const m = href.match(/entryRoom\/(\d+)\/(\d+)\/(\d)/); if (!m) return; const lessonId = m[2]; if (seen.has(lessonId)) return; seen.add(lessonId); const spans = [...tr.querySelectorAll("td.txt_pxkc_pxxx p span")]; const teacherEl = spans.filter((s) => !s.classList.contains("showtime_lesson")).pop() || spans[spans.length - 1]; const roomType = m[3]; const img = link.querySelector("img"); const imgSrc = img ? img.getAttribute("src") || "" : ""; let status = "\u53ef\u5b66"; if (/btn_wks/i.test(imgSrc)) status = "\u672a\u5f00\u59cb"; else if (/btn_gkhf/i.test(imgSrc)) status = "\u56de\u653e"; else if (/btn_jrpx/i.test(imgSrc)) status = "\u8fdb\u5165"; const isReplay = roomType === "2" || /btn_gkhf/i.test(imgSrc); out.push({ id: String(lessonId), companyId: String(m[1] || companyId), subjectId: String(subjectId || ""), subjectName: subjectName || "", stageId: String(stageId || ""), stageName: stageName || "", roomType: String(roomType), title: titleEl.textContent.trim() || "\u8bfe\u65f6 " + lessonId, section: section, hours: hours, teacher: teacherEl ? teacherEl.textContent.trim() : "", href: href.startsWith("http") ? href : "https://wp.pep.com.cn" + href, status, isReplay, countsCredit: true, }); }); }); return out; } function parseShowStuSections(html) { const doc = new DOMParser().parseFromString(html, "text/html"); const sections = []; doc.querySelectorAll('a[href*="/user/showStuOne/"]').forEach((a) => { const href = a.href || ""; const m = href.match(/showStuOne\/(\d+)\/(\d+)\/(\d)/); if (!m) return; const ul = a.closest("ul"); const titleLi = ul ? ul.querySelector("li[title], li.ell") : null; const title = (titleLi && (titleLi.getAttribute("title") || titleLi.textContent)) || ""; const timeText = (a.textContent || "").trim(); const pctMatch = (ul ? ul.textContent : "").match(/(\d+)%/); sections.push({ stuOneId: m[2], kind: m[3], title: String(title).replace(/\s+/g, " ").trim(), watchedText: timeText, watchedMs: parseHms(timeText), percent: pctMatch ? +pctMatch[1] : 0, href, }); }); const remainders = []; doc.querySelectorAll(".con_xxsj_gztb2020b p").forEach((p) => { const t = p.textContent.replace(/\s+/g, " ").trim(); if (t) remainders.push(t); }); return { sections, remainders }; } function parseShowStuOneLessons(html, stuOneId) { const doc = new DOMParser().parseFromString(html, "text/html"); const lessons = []; doc.querySelectorAll(".list_hftj_xstj_wp2022 ul.clearfix").forEach((ul) => { const lis = [...ul.querySelectorAll("li")].map((li) => li.textContent.trim()); if (lis.length < 2) return; const mm = lis[1].match(/([\d:]+)\s*\/\s*([\d:]+)/); const watchedMs = mm ? parseHms(mm[1]) : 0; const durMs = mm ? parseHms(mm[2]) : 0; lessons.push({ stuOneId: String(stuOneId || ""), title: lis[0], watchedMs, totalMs: durMs, percent: durMs ? Math.min(100, Math.round((watchedMs / durMs) * 100)) : 0, }); }); return lessons; } async function fetchHtml(pathOrUrl) { let url = pathOrUrl.startsWith("http") ? pathOrUrl : "https://wp.pep.com.cn/web/index.php?" + (pathOrUrl.startsWith("/") ? pathOrUrl : "/" + pathOrUrl); url += (url.indexOf("?") >= 0 ? "&" : "?") + "_=" + Date.now(); const res = await _gt(url, { headers: { "Cache-Control": "no-cache", Pragma: "no-cache", }, }); if (res.status < 200 || res.status >= 400) throw new Error("HTTP " + res.status); return res.text; } async function loadAllSubjectsCourses() { state.companyId = detectCompanyId(); const indexHtml = await fetchHtml("/px/index/" + state.companyId); let limitMap = null; try { limitMap = await fetchChangeSubjectLimitMap(); } catch (e) { log("\u62c9\u53d6\u5b66\u6bb5\u5b66\u79d1\u6620\u5c04\u5931\u8d25\uff0c\u6539\u7528\u9875\u9762\u5b66\u79d1"); } let stages = parseStagesFromHtml(document.documentElement.outerHTML); if (!stages.length) stages = parseStagesFromHtml(indexHtml); const mapStages = stagesFromLimitMap(limitMap); if (mapStages.length) { const byId = new Map(stages.map((s) => [s.id, s])); mapStages.forEach((s) => { if (!byId.has(s.id)) byId.set(s.id, s); else if (!byId.get(s.id).name) byId.set(s.id, s); }); stages = [...byId.values()].sort((a, b) => Number(a.id) - Number(b.id)); } if (!stages.length) { const m = String(location.href || "").match(/\/(?:px\/index|user\/showStu)\/\d+(?:\/\d+)?\/\d+\/(\d+)/); const stageId = (m && m[1]) || getPageStageId(); if (stageId) stages = [{ id: String(stageId), name: stageNameOf(stageId) }]; } state.stages = stages; const pageStageId = resolveStageId(getPageStageId()); const pageSubjects = parseSubjectsFromHtml(document.documentElement.outerHTML); const pageSubjectsIndex = parseSubjectsFromHtml(indexHtml); const subjects = []; const seenKey = new Set(); for (const st of stages) { let rows = []; const bag = limitMap && limitMap[st.id]; if (Array.isArray(bag) && bag.length) { rows = bag .map((ee) => ({ id: String((ee && (ee.sid != null ? ee.sid : ee.id)) || "").trim(), name: String((ee && (ee.subjectname || ee.name)) || "").trim(), })) .filter((x) => x.id); } else if (String(st.id) === String(pageStageId) || stages.length === 1) { const src = pageSubjects.length ? pageSubjects : pageSubjectsIndex; rows = src.map((x) => ({ id: x.id, name: x.name })); } if (!rows.length) { log("\u5b66\u6bb5\u300c" + st.name + "\u300d\u6682\u65e0\u5b66\u79d1"); continue; } for (const row of rows) { const key = subjectKey(st.id, row.id); if (seenKey.has(key)) continue; seenKey.add(key); subjects.push({ id: row.id, stageId: String(st.id), stageName: st.name || stageNameOf(st.id), name: row.name || row.id, key, label: stages.length > 1 ? (st.name || stageNameOf(st.id)) + " · " + (row.name || row.id) : row.name || row.id, }); } } if (!subjects.length) { const fallback = pageSubjects.length ? pageSubjects : pageSubjectsIndex; const st = stages[0] || { id: pageStageId || "", name: stageNameOf(pageStageId) }; fallback.forEach((x) => { const key = subjectKey(st.id, x.id); subjects.push({ id: x.id, stageId: String(st.id), stageName: st.name, name: x.name, key, label: x.name, }); }); } if (!subjects.length) { const st = resolveStageId(pageStageId); subjects.push({ id: "1", stageId: st, stageName: stageNameOf(st), name: "\u5f53\u524d", key: subjectKey(st, "1"), label: "\u5f53\u524d", }); } state.subjects = subjects; const savedSid = String(storeGet(K.subject, "") || ""); const pageKey = getPageSubjectKey(); const pick = findSubjectByKey(savedSid) || findSubjectByKey(pageKey) || subjects[0]; state.selectedSid = pick ? pick.key : subjects[0].key; const all = []; for (const sub of subjects) { const stageId = resolveStageId(sub.stageId); if (!stageId) { log("\u52a0\u8f7d\u300c" + sub.label + "\u300d\u5931\u8d25\uff1a\u5b66\u6bb5\u672a\u77e5"); continue; } try { const html = await fetchHtml( "/px/index/" + state.companyId + "/" + sub.id + "/" + stageId ); const lessons = parseLessonsFromHtml( html, state.companyId, sub.id, sub.name, stageId, sub.stageName || stageNameOf(stageId) ); all.push(...lessons); log("\u5df2\u52a0\u8f7d\u300c" + sub.label + "\u300d" + lessons.length + " \u95e8\u8bfe"); } catch (e) { log("\u52a0\u8f7d\u300c" + sub.label + "\u300d\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"); } await sleep(200); } state.lessons = all; const allow = new Set(all.map((x) => String(x.id))); saveQueue(loadQueue().filter((id) => allow.has(String(id)))); await _rp({}); pruneQueueLocked(); const markN = Object.keys(loadLocalProg()).length; const doneN = loadDoneSet().size; if (markN || doneN) { log("\u5df2\u5b66\u6807\u8bb0\u5df2\u4fdd\u7559"); } return all; } function platformLessonByTitle(title, sidHint) { const t = String(title || "").trim(); if (!t) return null; const findIn = (list) => { if (!Array.isArray(list) || !list.length) return null; let hit = list.find((x) => x.title === t); if (hit) return hit; return ( list.find((x) => x.title && (x.title.indexOf(t) >= 0 || t.indexOf(x.title) >= 0)) || null ); }; const hint = String(sidHint || "").trim(); const bags = state.platformLessonsBySid || {}; const hintKeys = []; if (hint) { hintKeys.push(hint); const ref = resolveSubjectRef(hint); if (ref && ref.key && hintKeys.indexOf(ref.key) < 0) hintKeys.push(ref.key); if (ref && ref.id && hintKeys.indexOf(ref.id) < 0) hintKeys.push(ref.id); if (hint.indexOf(":") < 0) { const staged = subjectKey(getPageStageId() || (ref && ref.stageId) || "", hint); if (staged !== ":" && hintKeys.indexOf(staged) < 0) hintKeys.push(staged); } } for (const k of hintKeys) { if (bags[k]) { const hit = findIn(bags[k]); if (hit) return hit; } } let hit = findIn(state.platformLessons || []); if (hit) return hit; for (const sid of Object.keys(bags)) { if (hintKeys.indexOf(sid) >= 0) continue; hit = findIn(bags[sid]); if (hit) return hit; } return null; } function findLocalProgForLesson(lessonId, title) { const local = loadLocalProg(); const id = String(lessonId || ""); if (id && local[id]) return { key: id, prog: local[id] }; const t = String(title || "").trim(); const matchIn = (bag) => { if (!bag || typeof bag !== "object" || !t) return null; for (const [key, prog] of Object.entries(bag)) { if (!prog || !prog.title) continue; if (prog.title === t || prog.title.indexOf(t) >= 0 || t.indexOf(prog.title) >= 0) { return { key, prog }; } } return null; }; let hit = matchIn(local); if (!hit) { hit = matchIn(storeGet(K.localProg, null)); } if (hit) { if (id && hit.key !== id) { local[id] = Object.assign({}, hit.prog, { title: t || hit.prog.title }); saveLocalProg(local); return { key: id, prog: local[id] }; } return { key: hit.key, prog: hit.prog }; } return null; } function isLessonBrushed(loc, lessonId) { const done = loadDoneSet(); if (lessonId && done.has(String(lessonId))) return true; try { const legacyDone = storeGet(K.done, []); if (lessonId && Array.isArray(legacyDone) && legacyDone.map(String).includes(String(lessonId))) { return true; } } catch (_) {} if (!loc) return false; return !!(loc.done || isWatchDone(loc.submittedMs, loc.totalMs)); } async function _rp(opts) { opts = opts || {}; const keyOrSid = String( opts.sid || state.selectedSid || (state.subjects[0] && state.subjects[0].key) || "" ).trim(); if (!keyOrSid) return; const sub = resolveSubjectRef(keyOrSid); const sid = String((sub && sub.id) || keyOrSid.split(":").pop() || "").trim(); const stageId = resolveStageId((sub && sub.stageId) || ""); if (!stageId) { if (!quiet) log("\u540c\u6b65\u5e73\u53f0\u5df2\u5b66\u5931\u8d25\uff1a\u5b66\u6bb5\u672a\u77e5"); return; } const cacheKey = subjectKey(stageId, sid); const quiet = !!opts.quiet; const pageOnly = !!opts.pageOnly; if (!sid) return; if (state.platformProgressInflight[cacheKey]) { return state.platformProgressInflight[cacheKey]; } state.platformProgressInflight[cacheKey] = (async () => { try { const html = await fetchHtml( "/user/showStu/" + state.companyId + "/0/" + sid + "/" + stageId ); const parsed = parseShowStuSections(html); const platLessons = []; for (const sec of parsed.sections.slice(0, 20)) { try { const oneHtml = await fetchHtml( "/user/showStuOne/" + state.companyId + "/" + sec.stuOneId + "/" + sec.kind ); parseShowStuOneLessons(oneHtml, sec.stuOneId).forEach((L) => { platLessons.push( Object.assign({}, L, { section: sec.title, subjectId: sid, stageId: stageId }) ); }); await sleep(80); } catch (e) { if (!quiet) log("\u540c\u6b65\u300c" + sec.title + "\u300d\u5931\u8d25"); } } state.platformLessonsBySid[cacheKey] = platLessons; state.platformSectionsBySid[cacheKey] = parsed.sections; const isPanelSid = cacheKey === String(state.selectedSid || "") || sid === String(state.selectedSid || "") || (sub && sub.key === String(state.selectedSid || "")); if (isPanelSid) { state.platformSections = parsed.sections; state.platformLessons = platLessons; } const withWatch = platLessons.filter((x) => x.watchedMs > 0).length; if (!quiet) { log("\u5e73\u53f0\u5df2\u5b66\u5df2\u540c\u6b65\uff08" + withWatch + " \u95e8\u6709\u8fdb\u5ea6\uff09"); if (parsed.remainders.length) log("\u5e73\u53f0\u63d0\u793a\uff1a" + parsed.remainders.slice(0, 3).join("\uff1b")); } if (!pageOnly && isPanelSid) { renderChapterPreview(); pruneQueueLocked(); renderCourseList(); updatePanel(); } else { _sp(); } } catch (e) { if (!quiet) log("\u540c\u6b65\u5e73\u53f0\u5df2\u5b66\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u91cd\u8bd5"); } finally { delete state.platformProgressInflight[cacheKey]; } })(); return state.platformProgressInflight[cacheKey]; } function getPageStageId() { const el = document.getElementById("stage"); if (el && String(el.value || "").trim()) return String(el.value).trim(); const mIndex = String(location.href || "").match(/\/px\/index\/\d+\/\d+\/(\d+)/i); if (mIndex) return mIndex[1]; const mStu = String(location.href || "").match(/\/user\/showStu\/\d+\/\d+\/\d+\/(\d+)/i); if (mStu) return mStu[1]; return ""; } function getPageSubjectId() { const el = document.getElementById("sid"); return el ? String(el.value || "").trim() : ""; } function getPageSubjectKey() { const sid = getPageSubjectId(); const stage = getPageStageId(); if (sid && stage) return subjectKey(stage, sid); return sid || ""; } function randT() { return ("0" + Date.now() + String(Math.floor(Math.random() * 1e6)).padStart(6, "0")).slice(0, 18); } function doubleEncodeURIComponent(s) { return encodeURIComponent(encodeURIComponent(String(s || ""))); } async function _al(url) { const headers = { Accept: "*/*", Referer: "https://bjpep.gensee.com/", }; if (IS_GENSEE) { try { const xhrRes = await new Promise((resolve, reject) => { try { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.onload = () => resolve({ status: xhr.status, text: String(xhr.responseText || "") }); xhr.onerror = () => reject(new Error("xhr network error")); xhr.send(); } catch (e) { reject(e); } }); return { status: xhrRes.status, text: xhrRes.text, via: "xhr" }; } catch (_) {} try { const res = await fetch(url, { method: "GET", mode: "cors", credentials: "omit", headers: headers, }); return { status: res.status, text: await res.text(), via: "fetch" }; } catch (_) {} } const res = await _gt(url, { headers: Object.assign({ Origin: "https://bjpep.gensee.com" }, headers), }); return { status: res.status, text: res.text, via: "gm" }; } function parseSessionId(text) { const raw = String(text || "").trim(); if (!raw) return ""; const first = raw.split(/\s+/)[0]; if (/^\d{5,}$/.test(first)) return first; const m = raw.match(/\b(\d{6,})\b/); return m ? m[1] : ""; } function normalizeAlbHost(alb) { const a = String(alb || "").trim(); if (a && a.indexOf(".") > 0 && a.indexOf(" ") < 0) return a; return "albcdnvodproxy.gensee.com"; } async function requestLicenseSession(opts) { const alb = normalizeAlbHost(opts.alb); const siteId = opts.siteId; const uid = opts.uid; const confid = opts.confid; const uname = String(opts.uname || "").trim(); const userEncList = []; if (uname) { userEncList.push(doubleEncodeURIComponent(uname)); userEncList.push(encodeURIComponent(uname)); } userEncList.push(null); let lastDetail = ""; for (const userEnc of userEncList) { let url = "https://" + alb + "/albcmd/license?siteid=" + encodeURIComponent(siteId) + "&userid=" + encodeURIComponent(uid) + "&confid=" + encodeURIComponent(confid); if (userEnc) url += "&username=" + userEnc; url += "&onlylogin=0&type=0&t=" + randT(); const res = await _al(url); const sid = parseSessionId(res.text); if (sid) return { sessionid: sid, alb: alb, raw: res.text, via: res.via }; lastDetail = "via=" + res.via + " HTTP " + res.status + " body=" + String(res.text || "") .replace(/\s+/g, " ") .slice(0, 120); } throw new Error("license \u672a\u8fd4\u56de sessionid\uff08" + lastDetail + "\uff09"); } function parsePlayPage(html, playUrl) { const owner = (html.match(/ownerid=["']([a-f0-9]+)["']/i) || [])[1] || (playUrl.match(/play-([a-f0-9]+)/i) || [])[1] || ""; const code = (html.match(/code=["']([a-f0-9]+)["']/i) || [])[1] || ""; const site = (html.match(/\bsite=["']([^"']+)["']/i) || [])[1] || "bjpep.gensee.com:443"; const uname = (html.match(/\buname=["']([^"']*)["']/i) || [])[1] || (html.match(/name="nickName"[^>]*value="([^"]*)"/i) || [])[1] || (html.match(/value="([^"]*)"[^>]*name="nickName"/i) || [])[1] || ""; const uid = (html.match(/var\s+quid\s*=\s*"(\d+)"/) || [])[1] || (html.match(/id="uid"[^>]*value="(\d+)"/) || [])[1] || (html.match(/value="(\d+)"[^>]*id="uid"/) || [])[1] || (playUrl.match(/[?&]uid=(\d+)/) || [])[1] || ""; return { ownerid: owner, code, site, uname, uid, playUrl }; } function parseSdkVod(jsText) { const tid = (jsText.match(/tid\s*:\s*"([a-f0-9]+)"/i) || [])[1] || ""; const siteId = (jsText.match(/siteId\s*:\s*"(\d+)"/i) || [])[1] || "183681"; const nameM = jsText.match(/userId\s*:\s*"(\d+)"\s*,\s*userName\s*:\s*"([^"]*)"/i) || jsText.match(/userId:"(\d+)",\s*userName:"([^"]*)"/i); const userId = (nameM && nameM[1]) || (jsText.match(/userId\s*:\s*"(\d+)"/i) || [])[1] || ""; const userName = (nameM && nameM[2]) || (jsText.match(/userName\s*:\s*"([^"]*)"/i) || [])[1] || ""; const confId = (jsText.match(/confId\s*:\s*"([a-f0-9]+)"/i) || [])[1] || ""; const xmlUrl = (jsText.match(/xmlUrl\s*=\s*'([^']+)'/) || [])[1] || ""; let alb = "albcdnvodproxy.gensee.com"; const albLit = jsText.match(/isNotEmpty\("([^"]*albcdn[^"]*)"\)/i); if (albLit && albLit[1]) alb = albLit[1]; else { const assigns = [...jsText.matchAll(/alb\s*=\s*"([^"]*)"/g)].map((m) => m[1]).filter(Boolean); if (assigns.length) alb = assigns[assigns.length - 1]; } return { tid, siteId, userId, userName, confId, xmlUrl, alb }; } async function resolveDurationMs(xmlUrl) { if (!xmlUrl) return 0; const jsonUrl = xmlUrl.replace(/record\d+\.xml(\?.*)?$/i, "hls/record.json"); try { const res = await _gt(jsonUrl + (jsonUrl.includes("?") ? "&" : "?") + "t=" + randT()); const m = res.text.match(/"duration"\s*:\s*"([\d.]+)"/); if (m) return Math.round(parseFloat(m[1]) * 1000); } catch (_) {} return 0; } function updateJob(patch) { const cur = storeGet(K.job, null) || {}; const next = Object.assign({}, cur, patch, { updatedAt: Date.now() }); storeSet(K.job, next); return next; } async function pageFetchText(url, opts) { opts = opts || {}; try { const res = await fetch(url, { method: opts.method || "GET", credentials: "include", headers: opts.headers || {}, }); const text = await res.text(); return { status: res.status, text, finalUrl: res.url || url, headers: "" }; } catch (e) { return _gt(url, opts); } } async function openGenseeSessionFromHtml(playHtml, playUrl) { const play = parsePlayPage(playHtml, playUrl); if (!play.ownerid) throw new Error("\u64ad\u653e\u9875\u7f3a\u5c11 ownerid"); if (!play.code) throw new Error("\u64ad\u653e\u9875\u7f3a\u5c11 code\uff08\u9274\u6743\u7801\uff09\uff0c\u8bf7\u786e\u8ba4\u56de\u653e\u5df2\u5f00\u653e"); const sdkUrl = "https://bjpep.gensee.com/sdk/site/sdk/gs/h5/vod?" + new URLSearchParams({ widgetid: "_GS_WIDGET_1_" + Date.now(), site: play.site || "bjpep.gensee.com:443", ctx: "gensee", ownerid: play.ownerid, code: play.code, uid: "", uname: play.uname || "", py: "1", loop: "false", lang: "zh_CN", userdata: "", hlsmode: "false", gsver: "2", }).toString(); let sdkRes; if (IS_GENSEE) { sdkRes = await pageFetchText(sdkUrl, { headers: { Accept: "*/*", Referer: playUrl }, }); } else { sdkRes = await _gt(sdkUrl, { headers: { Accept: "*/*", Referer: playUrl, "Accept-Language": "zh-CN,zh;q=0.9", }, }); } const sdk = parseSdkVod(sdkRes.text); const uid = sdk.userId || play.uid; const uname = sdk.userName || play.uname; const siteId = sdk.siteId || "183681"; const confid = sdk.confId || play.ownerid; if (!sdk.tid) { const preview = String(sdkRes.text || "") .replace(/\s+/g, " ") .slice(0, 180); throw new Error( "SDK \u672a\u8fd4\u56de tid\uff08HTTP " + sdkRes.status + " len=" + (sdkRes.text || "").length + "\uff09" + (preview ? " · " + preview : "") ); } if (!uid) throw new Error("\u7f3a\u5c11 userid"); let durationMs = await resolveDurationMs(sdk.xmlUrl); if (!durationMs && typeof platformLessonByTitle === "function") { try { const plat = platformLessonByTitle(play.uname); if (plat && plat.totalMs) durationMs = plat.totalMs; } catch (_) {} } if (!durationMs) durationMs = 60 * 60 * 1000; const alb = normalizeAlbHost(sdk.alb); const sess0 = await requestLicenseSession({ alb: alb, siteId: siteId, uid: uid, confid: confid, uname: uname, }); return { alb: sess0.alb, siteId, uid, uname, confid, tid: sdk.tid, sessionid: sess0.sessionid, durationMs, playUrl, ownerid: play.ownerid, }; } async function openGenseeSession(lesson) { const entry = lesson.href || "https://wp.pep.com.cn/web/index.php?/px/entryRoom/" + (lesson.companyId || state.companyId) + "/" + lesson.id + "/2"; const entryRes = await _gt(entry, { headers: { Accept: "text/html,application/xhtml+xml", "Upgrade-Insecure-Requests": "1" }, }); let playUrl = entryRes.finalUrl || ""; const loc = (entryRes.headers.match(/location:\s*(\S+)/i) || [])[1]; if (loc) playUrl = loc.trim(); if (!/gensee\.com/i.test(playUrl)) { const m = entryRes.text.match(/https?:\/\/bjpep\.gensee\.com\/[^"'<\s]+/); if (m) playUrl = m[0]; } if (!/gensee\.com/i.test(playUrl)) throw new Error("entryRoom \u672a\u8df3\u8f6c\u5230\u5c55\u89c6\u56de\u653e"); const playRes = await _gt(playUrl, { headers: { Accept: "text/html,application/xhtml+xml", Referer: "https://wp.pep.com.cn/", }, }); return openGenseeSessionFromHtml(playRes.text, playUrl); } async function licenseHeartbeat(sess, opts) { const t = Math.max(0, Math.floor(opts.t || 0)); const d = Math.max(0, Math.floor(opts.d || 0)); const v = Math.max(0, Math.floor(opts.v || sess.durationMs || 0)); const pos = Math.max(0, Math.floor(opts.pos != null ? opts.pos : Math.min(d, v))); const other = "tid=" + sess.tid + ",t=" + t + ",d=" + d + ",v=" + v + ",sc=2,pos=" + pos; let url = "https://" + normalizeAlbHost(sess.alb) + "/albcmd/license?siteid=" + encodeURIComponent(sess.siteId) + "&userid=" + encodeURIComponent(sess.uid) + "&confid=" + encodeURIComponent(sess.confid) + "&onlylogin=0&type=1&sessionid=" + encodeURIComponent(sess.sessionid) + "&needlicense=1&other=" + encodeURIComponent(other); const uname = String(sess.uname || "").trim(); if (uname) url += "&username=" + encodeURIComponent(uname); url += "&t=" + randT(); return _al(url); } const DONE_RATIO = 1; // 刷满片长 100%(d >= v) function doneThresholdMs(totalMs) { const v = Math.max(0, Math.floor(Number(totalMs) || 0)); return Math.min(v, Math.ceil(v * DONE_RATIO)); } function isWatchDone(submittedMs, totalMs) { const v = Math.max(0, Math.floor(Number(totalMs) || 0)); if (!v) return false; return Math.floor(Number(submittedMs) || 0) >= doneThresholdMs(v); } function isSectionFull(sectionTitle, sidHint) { const title = String(sectionTitle || "").trim(); if (!title) return false; const bags = []; const hint = String(sidHint || "").trim(); const bySid = state.platformSectionsBySid || {}; if (hint && bySid[hint]) bags.push(bySid[hint]); if (state.platformSections) bags.push(state.platformSections); Object.keys(bySid).forEach((sid) => { if (hint && sid === hint) return; bags.push(bySid[sid]); }); for (const sections of bags) { if (!Array.isArray(sections)) continue; const sec = sections.find((s) => String(s.title || "").trim() === title); if (sec && Number(sec.percent) >= 100) return true; } return false; } function isPlatformFull(plat, sidHint) { if (!plat) return false; const total = Number(plat.totalMs) || 0; const watched = Number(plat.watchedMs) || 0; if (total > 0) { if (isWatchDone(watched, total)) return true; if (watched > 0 && total - watched <= 2000) return true; } if (plat.section && isSectionFull(plat.section, sidHint || plat.subjectId)) return true; return false; } function isCreditSectionName(name) { const s = String(name || ""); if (/\u9009\u5b66|\u4e0d\u8ba1\u5b66\u65f6|\u4e0d\u8ba1\u8bfe\u65f6/.test(s)) return false; return true; } function formatLessonLearnStatus(plat, loc, lessonId, sidHint) { if (isPlatformFull(plat, sidHint)) { return { kind: "plat", tag: "\u5e73\u53f0\u5df2\u5b66\u66f4\u65b0", text: "\u5e73\u53f0\u5df2\u5b66\u66f4\u65b0 " + formatHms(plat.watchedMs) + "/" + formatHms(plat.totalMs), }; } if (isLessonBrushed(loc, lessonId)) { return { kind: "pending", tag: "\u5df2\u5b66\u5f85\u5e73\u53f0\u66f4\u65b0", text: "\u5df2\u5b66\u5f85\u5e73\u53f0\u66f4\u65b0" }; } return { kind: "todo", tag: "\u8bfe\u7a0b\u5f85\u5b66\u4e60", text: "\u8bfe\u7a0b\u5f85\u5b66\u4e60" }; } function selectedSubjectSupportsReplay() { const sid = String(state.selectedSid || ""); const list = (state.lessons || []).filter( (x) => lessonMatchesSelected(x, sid) && x.countsCredit !== false ); if (!list.length) return false; return list.some((x) => x.isReplay && x.status !== "\u672a\u5f00\u59cb"); } function updateCourseTip() { const tip = document.getElementById("rjpx-course-tip"); if (!tip) return; tip.hidden = selectedSubjectSupportsReplay(); } function blockEntryRoomClick(e) { e.preventDefault(); e.stopPropagation(); return false; } function restoreEntryRoomLink(link) { if (!link || !link.classList.contains("rjpx-entry-locked")) return; const raw = link.getAttribute("data-rjpx-orig-html"); if (raw != null) link.innerHTML = raw; link.classList.remove("rjpx-entry-locked", "rjpx-entry-pending", "rjpx-entry-done"); link.removeAttribute("data-rjpx-orig-html"); link.removeAttribute("data-rjpx-lock"); link.removeAttribute("title"); link.removeAttribute("aria-disabled"); link.removeEventListener("click", blockEntryRoomClick, true); } function lockEntryRoomLink(link, mode) { if (!link || (mode !== "pending" && mode !== "done")) return; const label = mode === "done" ? "\u5df2\u5b66\u5b8c" : "\u5df2\u5b66\u5b8c\u5f85\u5e73\u53f0\u66f4\u65b0"; const prev = link.getAttribute("data-rjpx-lock"); if (prev === mode && link.classList.contains("rjpx-entry-locked")) { const el = link.querySelector(".rjpx-entry-label"); if (el && el.textContent === label) return; } if (!link.getAttribute("data-rjpx-orig-html")) { link.setAttribute("data-rjpx-orig-html", link.innerHTML); } link.classList.add("rjpx-entry-locked"); link.classList.toggle("rjpx-entry-pending", mode === "pending"); link.classList.toggle("rjpx-entry-done", mode === "done"); link.setAttribute("data-rjpx-lock", mode); link.setAttribute("title", label); link.setAttribute("aria-disabled", "true"); link.innerHTML = '"; link.removeEventListener("click", blockEntryRoomClick, true); link.addEventListener("click", blockEntryRoomClick, true); } function _pb() { if (!IS_PEP || IN_IFRAME) return; state.patchingPageButtons = true; try { const pageSid = getPageSubjectId(); const pageStage = getPageStageId(); const pageKey = getPageSubjectKey(); const links = document.querySelectorAll('a[href*="/px/entryRoom/"]'); links.forEach((link) => { if (link.closest("#rjpx-auto-panel")) return; const href = link.href || link.getAttribute("href") || ""; const m = href.match(/entryRoom\/(\d+)\/(\d+)\/(\d)/); if (!m) return; const lessonId = String(m[2]); const roomType = String(m[3]); const img = link.querySelector("img"); const imgSrc = img ? img.getAttribute("src") || "" : ""; const txt = String(link.textContent || "").replace(/\s+/g, ""); const isReplayBtn = roomType === "2" || /btn_gkhf/i.test(imgSrc) || /\u89c2\u770b\u56de\u653e|\u5df2\u5b66\u5b8c/.test(txt) || link.classList.contains("rjpx-entry-locked"); if (!isReplayBtn) { restoreEntryRoomLink(link); return; } const tr = link.closest("tr") || link.closest("li") || link.parentElement; const titleEl = (tr && (tr.querySelector("td.txt_pxkc_pxxx h6") || tr.querySelector("h6"))) || null; const les = (state.lessons || []).find((x) => String(x.id) === lessonId); const title = (titleEl && titleEl.textContent.trim()) || (les && les.title) || ""; const sidHint = (les && subjectKey(les.stageId, les.subjectId)) || pageKey || subjectKey(pageStage, pageSid) || pageSid; const found = findLocalProgForLesson(lessonId, title); const loc = found ? found.prog : null; const plat = platformLessonByTitle(title, sidHint); const st = formatLessonLearnStatus(plat, loc, lessonId, sidHint); if (st.kind === "plat") lockEntryRoomLink(link, "done"); else if (st.kind === "pending") lockEntryRoomLink(link, "pending"); else restoreEntryRoomLink(link); }); } finally { state.patchingPageButtons = false; } } let pageBtnObserver = null; let pageBtnPatchTimer = 0; let lastPageSidSynced = ""; let pageSidEnsureTimer = 0; function _sp() { if (pageBtnPatchTimer) clearTimeout(pageBtnPatchTimer); pageBtnPatchTimer = setTimeout(() => { pageBtnPatchTimer = 0; _pb(); }, 80); } async function _eps(force) { if (!IS_PEP || IN_IFRAME) return; const key = getPageSubjectKey() || getPageSubjectId(); if (!key) { _sp(); return; } const cached = (state.platformLessonsBySid && state.platformLessonsBySid[key]) || (state.platformLessonsBySid && state.platformLessonsBySid[subjectKey(getPageStageId(), getPageSubjectId())]); if (cached) _sp(); if (!force && cached && lastPageSidSynced === key) return; try { await _rp({ quiet: true, pageOnly: true, sid: key }); lastPageSidSynced = key; } catch (_) {} _sp(); } function scheduleEnsurePageSubjectProgress(force) { if (pageSidEnsureTimer) clearTimeout(pageSidEnsureTimer); pageSidEnsureTimer = setTimeout(() => { pageSidEnsureTimer = 0; _eps(!!force); }, 120); } function _bw() { if (!IS_PEP || IN_IFRAME) return; const onPageCourseChange = () => { lastPageSidSynced = ""; scheduleEnsurePageSubjectProgress(true); }; const sidEl = document.getElementById("sid"); if (sidEl && !sidEl.dataset.rjpxSidBound) { sidEl.dataset.rjpxSidBound = "1"; sidEl.addEventListener("change", onPageCourseChange); sidEl.addEventListener("input", onPageCourseChange); } const stageEl = document.getElementById("stage"); if (stageEl && !stageEl.dataset.rjpxStageBound) { stageEl.dataset.rjpxStageBound = "1"; stageEl.addEventListener("change", onPageCourseChange); stageEl.addEventListener("input", onPageCourseChange); } } function ensureCoursePageButtonObserver() { if (!IS_PEP || IN_IFRAME || pageBtnObserver || !document.body) return; _bw(); pageBtnObserver = new MutationObserver(() => { if (state.patchingPageButtons) return; _bw(); const key = getPageSubjectKey() || getPageSubjectId(); if (key && key !== lastPageSidSynced) { scheduleEnsurePageSubjectProgress(true); } else { _sp(); } }); pageBtnObserver.observe(document.body, { childList: true, subtree: true }); } function pruneQueueLocked() { const local = loadLocalProg(); const done = loadDoneSet(); const next = loadQueue().filter((id) => { const sid = String(id); if (done.has(sid)) return false; const loc = local[sid]; if (loc && (loc.done || isWatchDone(loc.submittedMs, loc.totalMs))) return false; const les = state.lessons.find((x) => String(x.id) === sid); if (!les || !les.isReplay || les.status === "\u672a\u5f00\u59cb") return false; if (isPlatformFull(platformLessonByTitle(les.title))) return false; return true; }); saveQueue(next); return next; } async function _ve(sess, lessonId, title, onProgress) { const total = Math.max(0, Math.floor(Number(sess.durationMs) || 0)); const need = doneThresholdMs(total); const local = loadLocalProg(); const prev = local[lessonId] || {}; let d = Math.min(Number(prev.submittedMs) || 0, total); const started = Date.now(); const peixunUid = String(resolveLearningUserId() || "").trim(); const genseeUid = String(sess.uid || "").trim(); let startRes; try { startRes = await _es({ duration_ms: total, submitted_ms: d, userid: genseeUid, learning_user_id: peixunUid, peixun_uid: peixunUid, confid: String(sess.confid || ""), tid: String(sess.tid || ""), sessionid: String(sess.sessionid || ""), lesson_id: String(lessonId), title: String(title || ""), }); } catch (e) { if (String(e && e.code ? e.code : e.message || e).includes("free_quota")) { handleFreeQuotaExceeded(e.detail || null); throw new Error("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro"); } if (String(e && e.code ? e.code : e.message || e).includes("token_bound")) { throw new Error("\u5f53\u524d Pro Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7"); } throw new Error("\u5b66\u4e60\u542f\u52a8\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"); } let sessionId = String(startRes.session_id || ""); let cmd = startRes.command || {}; const startLog = String(startRes.log || "").trim(); if (startLog && !/^\u5f00\u59cb\u5b66\u4e60/.test(startLog)) log(startLog); logCourse("\u5f00\u59cb\u5b66\u4e60", title); const maxSteps = 800; for (let i = 0; i < maxSteps; i++) { const job = storeGet(K.job, null); if (job && job.status === "cancel") throw new Error("\u5df2\u505c\u6b62"); if (IS_PEP && !state.enabled) throw new Error("\u5df2\u505c\u6b62"); const type = String(cmd.type || ""); if (type === "done") { local[lessonId] = { title: title, submittedMs: need, totalMs: total, needMs: need, updatedAt: Date.now(), done: true, finishedAt: Date.now(), }; saveLocalProg(local); const done = loadDoneSet(); done.add(String(lessonId)); saveDoneSet(done); if (typeof onProgress === "function") onProgress(need, total); logCourse("\u672c\u8bfe\u5df2\u5b66\u5b8c", title); return { total, need, elapsedSec: Math.round((Date.now() - started) / 1000) }; } if (type === "failed") { throw new Error(String(cmd.message || "\u5b66\u4e60\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5")); } if (type === "license_heartbeat") { const waitMs = Math.max(0, Number(cmd.wait_ms) || 0); if (waitMs) await sleep(waitMs); const job2 = storeGet(K.job, null); if (job2 && job2.status === "cancel") throw new Error("\u5df2\u505c\u6b62"); if (IS_PEP && !state.enabled) throw new Error("\u5df2\u505c\u6b62"); let res; try { res = await licenseHeartbeat(sess, { t: Math.max(0, Math.floor(Number(cmd.t) || 0)), d: Math.max(0, Math.floor(Number(cmd.d) || 0)), v: Math.max(0, Math.floor(Number(cmd.v) || total)), pos: Math.max( 0, Math.floor(Number(cmd.pos != null ? cmd.pos : cmd.d) || 0) ), }); } catch (_) { res = { status: 0, text: "" }; } const body = String(res.text || "").trim(); const ok = body === "0"; const nextD = Math.max(0, Math.floor(Number(cmd.d) || 0)); if (ok) { d = Math.min(need, nextD); local[lessonId] = { title: title, submittedMs: d, totalMs: total, needMs: need, updatedAt: Date.now(), done: isWatchDone(d, total), }; saveLocalProg(local); if (typeof onProgress === "function") onProgress(d, total); } const step = await _estep(sessionId, "heartbeat_result", { body: body, http_status: Number(res.status) || 0, ok: ok, }); sessionId = String(step.session_id || sessionId); const stepLog = String(step.log || "").trim(); if (stepLog && !/^\u5f00\u59cb\u5b66\u4e60/.test(stepLog) && !/^\u672c\u8bfe\u5df2\u5b66\u5b8c/.test(stepLog) && !/^\u786e\u8ba4\u5b66\u5b8c/.test(stepLog)) { log(stepLog); } cmd = step.command || {}; continue; } const step = await _estep(sessionId, "tick", null); sessionId = String(step.session_id || sessionId); const stepLog = String(step.log || "").trim(); if (stepLog && !/^\u5f00\u59cb\u5b66\u4e60/.test(stepLog) && !/^\u672c\u8bfe\u5df2\u5b66\u5b8c/.test(stepLog) && !/^\u786e\u8ba4\u5b66\u5b8c/.test(stepLog)) { log(stepLog); } cmd = step.command || {}; } throw new Error("\u5b66\u4e60\u8d85\u65f6\uff0c\u8bf7\u91cd\u8bd5"); } async function submitLessonByApi(lesson) { state.current = lesson; storeSet(K.current, lesson); state.currentTask = "\u5f00\u59cb\u5b66\u4e60\uff1a" + lesson.title; updatePanel(); const entry = lesson.href || "https://wp.pep.com.cn/web/index.php?/px/entryRoom/" + (lesson.companyId || state.companyId) + "/" + lesson.id + "/2"; const jobId = String(Date.now()) + "_" + lesson.id; updateJob({ id: jobId, lessonId: String(lesson.id), title: lesson.title, entryUrl: entry, openMode: "iframe", status: "queued", submittedMs: 0, totalMs: 0, error: "", msg: "\u51c6\u5907\u5b66\u4e60\u73af\u5883", }); openPlayContext(entry); const started = Date.now(); while (true) { if (!state.enabled) { updateJob({ status: "cancel", msg: "\u7528\u6237\u505c\u6b62" }); removePlayIframe(); throw new Error("\u5df2\u505c\u6b62"); } await sleep(1000); const job = storeGet(K.job, null); if (!job || job.id !== jobId) continue; if (job.submittedMs || job.totalMs) { state.currentTask = "\u5b66\u4e60\u4e2d " + lesson.title + " " + formatHms(job.submittedMs || 0) + "/" + formatHms(job.totalMs || 0); updatePanel(); } else if (job.msg) { state.currentTask = job.msg; updatePanel(); } if (job.status === "done") { removePlayIframe(); state.current = null; storeSet(K.current, null); state.currentTask = ""; updatePanel(); return; } if (job.status === "error") { removePlayIframe(); const err = String(job.error || "\u5b66\u4e60\u5931\u8d25"); if (/free_quota|\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c/.test(err)) { handleFreeQuotaExceeded(null); throw new Error("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro"); } throw new Error(/\u5df2\u505c\u6b62/.test(err) ? err : "\u5b66\u4e60\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"); } if (Date.now() - started > 3 * 60 * 60 * 1000) { removePlayIframe(); throw new Error("\u5355\u8bfe\u8d85\u65f6\uff0c\u8bf7\u91cd\u8bd5"); } } } async function bootGenseeApiWorker() { if (!/\/webcast\/site\/vod\/play-/i.test(location.pathname)) return; let job = null; for (let i = 0; i < 40; i++) { job = storeGet(K.job, null); if (job && (job.status === "queued" || job.status === "running")) break; await sleep(250); } if (!job || (job.status !== "queued" && job.status !== "running")) return; const stopMedia = () => { try { document.querySelectorAll("video,audio").forEach((v) => { v.muted = true; v.pause(); v.removeAttribute("autoplay"); }); } catch (_) {} const cont = document.getElementById("continueBtn"); if (cont) { try { cont.click(); } catch (_) {} } const idle = document.getElementById("idleModal"); if (idle) idle.style.display = "none"; const stopBg = document.querySelector(".stop-reporting-bg"); if (stopBg) stopBg.style.display = "none"; const volIgnore = document.querySelector(".autoplay-volume-ignore"); if (volIgnore) { try { volIgnore.click(); } catch (_) {} } }; updateJob({ status: "running", msg: "\u51c6\u5907\u5b66\u4e60\u73af\u5883…", }); let mediaTimer = null; try { for (let i = 0; i < 40; i++) { if (document.documentElement && /ownerid=/i.test(document.documentElement.innerHTML)) break; await sleep(250); } let sdkText = ""; const waitUntil = Date.now() + 25000; while (Date.now() < waitUntil) { const sdkScript = [...document.scripts].find((s) => /\/sdk\/site\/sdk\/gs\/h5\/vod/i.test(s.src || "")); if (sdkScript && sdkScript.src && !sdkText) { try { const res = await pageFetchText(sdkScript.src, { headers: { Accept: "*/*", Referer: location.href }, }); if (/tid\s*:\s*"[a-f0-9]+"/i.test(res.text || "")) sdkText = res.text || ""; } catch (_) {} } if (stolenLicense.sessionid && sdkText) break; if (stolenLicense.sessionid && Date.now() > waitUntil - 5000) break; await sleep(400); } const play = parsePlayPage(document.documentElement.innerHTML, location.href); const sdk = sdkText ? parseSdkVod(sdkText) : null; let tid = (sdk && sdk.tid) || stolenLicense.otherTid || ""; let uid = (sdk && sdk.userId) || play.uid || stolenLicense.userid || ""; let uname = (sdk && sdk.userName) || play.uname || ""; let siteId = (sdk && sdk.siteId) || stolenLicense.siteid || "183681"; let confid = (sdk && sdk.confId) || play.ownerid || stolenLicense.confid || ""; let alb = normalizeAlbHost((sdk && sdk.alb) || stolenLicense.alb); let durationMs = sdk && sdk.xmlUrl ? await resolveDurationMs(sdk.xmlUrl) : 0; if (!durationMs) durationMs = 60 * 60 * 1000; if (!tid) { for (let i = 0; i < 20 && !stolenLicense.otherTid; i++) await sleep(300); tid = stolenLicense.otherTid || tid; } if (!tid) { const built = await openGenseeSessionFromHtml(document.documentElement.innerHTML, location.href); if (built && built.sessionid && built.tid) { mediaTimer = setInterval(stopMedia, 2000); stopMedia(); updateJob({ status: "running", msg: "\u5b66\u4e60\u4e2d", totalMs: built.durationMs, submittedMs: 0, }); const result = await _ve( built, job.lessonId, job.title, (d, total) => { updateJob({ status: "running", submittedMs: d, totalMs: total, msg: "\u5b66\u4e60\u4e2d " + formatHms(d) + "/" + formatHms(total), }); } ); updateJob({ status: "done", submittedMs: result.total, totalMs: result.total, msg: "\u5b8c\u6210", error: "", }); if (mediaTimer) clearInterval(mediaTimer); setTimeout(() => { if (!IN_IFRAME) { try { window.close(); } catch (_) {} } }, 600); return; } throw new Error("\u672a\u80fd\u51c6\u5907\u5b66\u4e60\u73af\u5883"); } let sessionid = stolenLicense.sessionid; if (!sessionid) { updateJob({ status: "running", msg: "\u51c6\u5907\u5b66\u4e60\u73af\u5883…" }); if (!uid || !confid) { throw new Error("\u672a\u80fd\u51c6\u5907\u5b66\u4e60\u73af\u5883"); } const sess0 = await requestLicenseSession({ alb: alb, siteId: siteId, uid: uid, confid: confid, uname: uname, }); sessionid = sess0.sessionid; alb = sess0.alb || alb; } else { updateJob({ status: "running", msg: "\u5b66\u4e60\u4e2d", }); } const sess = { alb: alb, siteId: siteId, uid: uid, uname: uname, confid: confid, tid: tid, sessionid: sessionid, durationMs: durationMs, playUrl: location.href, }; mediaTimer = setInterval(stopMedia, 2000); stopMedia(); updateJob({ status: "running", msg: "\u5b66\u4e60\u4e2d", totalMs: sess.durationMs, submittedMs: 0, }); const result = await _ve( sess, job.lessonId, job.title, (d, total) => { updateJob({ status: "running", submittedMs: d, totalMs: total, msg: "\u5b66\u4e60\u4e2d " + formatHms(d) + "/" + formatHms(total), }); } ); updateJob({ status: "done", submittedMs: result.total, totalMs: result.total, msg: "\u5b8c\u6210", error: "", }); if (mediaTimer) clearInterval(mediaTimer); setTimeout(() => { if (!IN_IFRAME) { try { window.close(); } catch (_) {} } }, 600); } catch (e) { if (mediaTimer) clearInterval(mediaTimer); const raw = String(e && e.message ? e.message : e); const friendly = /free_quota|\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c/.test(raw) ? "\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u8bf7\u5347\u7ea7 Pro" : /\u5df2\u505c\u6b62/.test(raw) ? "\u5df2\u505c\u6b62" : /\u8d85\u65f6/.test(raw) ? raw : "\u5b66\u4e60\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"; updateJob({ status: "error", error: friendly, msg: "\u5931\u8d25", }); } } function pickNext() { const q = loadQueue(); const done = loadDoneSet(); const local = loadLocalProg(); const map = new Map(state.lessons.map((x) => [String(x.id), x])); for (const id of q) { if (done.has(id)) continue; const loc = local[id]; if (loc && (loc.done || isWatchDone(loc.submittedMs, loc.totalMs))) continue; const les = map.get(String(id)); if (!les || !les.isReplay || les.status === "\u672a\u5f00\u59cb" || les.countsCredit === false) continue; if (isPlatformFull(platformLessonByTitle(les.title))) continue; return les; } return null; } async function bgLoop() { if (state.bgBusy) return; state.bgBusy = true; try { while (state.enabled) { const next = pickNext(); if (!next) { log("\u6240\u9009\u8bfe\u7a0b\u5df2\u5168\u90e8\u5b66\u5b8c"); state.enabled = false; storeSet(K.enabled, false); state.currentTask = "\u5df2\u5168\u90e8\u5b66\u5b8c"; updatePanel(); try { showFinishTipDialog(); } catch (_) {} break; } try { await submitLessonByApi(next); } catch (e) { const msg = String(e && e.message ? e.message : e); if (/free_quota|\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c/.test(msg)) { log("\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c"); } else if (/\u5df2\u505c\u6b62/.test(msg)) { log("\u5df2\u505c\u6b62"); } else { log("\u5b66\u4e60\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"); } state.enabled = false; storeSet(K.enabled, false); state.currentTask = "\u51fa\u9519\u5df2\u6682\u505c"; break; } await sleep(1200); renderCourseList(); renderChapterPreview(); updatePanel(); } } finally { state.bgBusy = false; updatePanel(); } } async function startRun() { if (state.enabled) return; if (!loadQueue().length) { log("\u8bf7\u5148\u52fe\u9009\u56de\u653e\u8bfe\u7a0b"); return; } try { await _cl(true); } catch (e) { const msg = String(e && e.message ? e.message : e); if (/free_quota|\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c/.test(msg)) { handleFreeQuotaExceeded(e.detail || null); return; } if (/token_bound/.test(msg)) { log("\u5f53\u524d Pro Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7"); return; } log("\u4e91\u7aef\u6388\u6743\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"); return; } if ( String(state.cloudTier || "").toLowerCase() !== "pro" && Number(state.freeUsedVideos || 0) >= Number(state.freeVideoLimit || 2) ) { handleFreeQuotaExceeded({ free_video_limit: state.freeVideoLimit, free_used_videos: Math.max(state.freeUsedVideos, state.freeVideoLimit), }); return; } state.enabled = true; storeSet(K.enabled, true); log("\u5f00\u59cb\u961f\u5217\u5b66\u4e60"); updatePanel(); bgLoop(); } function stopRun() { state.enabled = false; storeSet(K.enabled, false); updateJob({ status: "cancel", msg: "\u7528\u6237\u505c\u6b62" }); removePlayIframe(); state.currentTask = "\u5df2\u505c\u6b62"; log("\u5df2\u505c\u6b62"); updatePanel(); } function injectPanelStyles() { if (document.getElementById("rjpx-panel-style")) return; const st = document.createElement("style"); st.id = "rjpx-panel-style"; st.textContent = "#rjpx-auto-panel{position:fixed;right:20px;top:70px;z-index:999999;width:412px;display:flex;flex-direction:column;background:#f4f7fb;border:1px solid #dbe4f0;border-radius:16px;box-shadow:0 16px 40px rgba(15,23,42,.17);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif;font-size:12px;color:#0f172a;overflow:hidden}" + "#rjpx-auto-panel.rjpx-min #rjpx-panel-body,#rjpx-auto-panel.rjpx-min #rjpx-panel-footer,#rjpx-auto-panel.rjpx-min .rjpx-footer-extra{display:none!important}" + "#rjpx-panel-header{padding:6px 10px;background:linear-gradient(180deg,#f8ecd5,#f4e8cf);border-bottom:1px solid #e5dbc6;display:flex;justify-content:space-between;align-items:center;gap:8px;cursor:move;user-select:none}" + "#rjpx-panel-brand{display:flex;align-items:center;gap:7px;min-width:0;flex:1}" + "#rjpx-panel-logo{width:28px;height:28px;border-radius:7px;object-fit:cover;flex:0 0 auto;box-shadow:0 1px 3px rgba(15,23,42,.12);background:#fff}" + "#rjpx-panel-title{font-size:12px;font-weight:900;color:#9a3412;line-height:1.2}" + "#rjpx-panel-sub{margin-top:2px;display:flex;flex-wrap:wrap;gap:3px}" + ".rjpx-chip{font-size:10px;color:#7c2d12;background:rgba(255,255,255,.62);padding:1px 6px;border-radius:999px;border:1px solid rgba(180,83,9,.18);font-weight:700}" + ".rjpx-chip-em{color:#0f766e;background:rgba(236,253,245,.9);border-color:rgba(15,118,110,.28)}" + ".rjpx-ctl{border:none;background:#fff;color:#64748b;width:26px;height:26px;border-radius:999px;cursor:pointer;box-shadow:0 1px 2px rgba(15,23,42,.1);font-weight:900;font-size:16px;line-height:1;flex:0 0 auto}" + "#rjpx-panel-body{padding:7px;max-height:min(78vh,720px);overflow:auto}" + ".rjpx-card{background:#fff;border:1px solid #d9e2ee;border-radius:12px;padding:7px 9px;margin-bottom:7px}" + ".rjpx-status-row{display:flex;justify-content:space-between;align-items:center;gap:8px}" + ".rjpx-progress-bar{height:6px;border-radius:999px;background:#e2e8f0;overflow:hidden;margin-top:6px}" + ".rjpx-progress-bar>span{display:block;height:100%;width:0;background:linear-gradient(90deg,#22d3ee,#2563eb);transition:width .2s}" + ".rjpx-tabbar{display:flex;gap:4px;margin-bottom:6px}" + ".rjpx-tab-btn{flex:1;border:1px solid #cbd5e1;background:#f8fafc;border-radius:8px;padding:5px 4px;cursor:pointer;font-size:11px;font-weight:700;color:#64748b}" + ".rjpx-tab-btn.active{background:#fff7ed;border-color:#fdba74;color:#9a3412}" + ".rjpx-pane{display:none}.rjpx-pane.active{display:block}" + ".rjpx-course-tip{margin:0 0 5px;padding:5px 7px;border-radius:8px;background:linear-gradient(180deg,#fffbeb,#fef3c7);border:1px solid #fcd34d;color:#92400e;font-size:11px;font-weight:700;line-height:1.35}" + ".rjpx-course-tip[hidden]{display:none!important}" + ".rjpx-course-toolbar{display:flex;gap:5px;margin-bottom:5px;align-items:center}" + ".rjpx-course-toolbar .rjpx-plan-select{flex:1;min-width:0;padding:3px 6px;font-size:11px;border-radius:7px}" + ".rjpx-btn-sm{flex:0 0 auto;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:7px;padding:3px 8px;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap}" + ".rjpx-btn-sm:hover{border-color:#fdba74;color:#9a3412;background:#fff7ed}" + "a.rjpx-entry-locked{pointer-events:none!important;cursor:not-allowed!important;text-decoration:none!important}" + "a.rjpx-entry-locked .rjpx-entry-label{display:inline-flex;align-items:center;justify-content:center;min-width:88px;padding:6px 10px;border-radius:4px;font-size:12px;font-weight:700;line-height:1.2;color:#fff;box-sizing:border-box;white-space:nowrap}" + "a.rjpx-entry-pending .rjpx-entry-label{background:#d97706}" + "a.rjpx-entry-done .rjpx-entry-label{background:#64748b}" + "#rjpx-course-list,#rjpx-chapter-preview,#rjpx-run-log{max-height:200px;overflow:auto;background:#f8fafc;border:1px solid #dbe4f0;border-radius:11px;padding:5px}" + ".rjpx-item{display:flex;gap:7px;align-items:flex-start;padding:6px 7px;border-bottom:1px solid #eef2f7}" + ".rjpx-item-title{font-weight:700;line-height:1.35}.rjpx-item-meta{color:#64748b;font-size:11px;margin-top:2px}" + ".rjpx-tag{display:inline-block;padding:1px 6px;border-radius:999px;font-size:10px;font-weight:700;margin-left:4px;border:1px solid #e2e8f0}" + ".rjpx-tag-ok{background:#ecfdf5;color:#047857;border-color:#a7f3d0}.rjpx-tag-wait{background:#fff7ed;color:#c2410c;border-color:#fed7aa}" + ".rjpx-tag-done{background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe}.rjpx-tag-local{background:#fef3c7;color:#92400e;border-color:#fcd34d}.rjpx-tag-plat{background:#ecfdf5;color:#047857;border-color:#a7f3d0}" + ".rjpx-meta-row{display:flex;align-items:center;gap:8px;margin:5px 0}.rjpx-meta-label{color:#64748b;font-weight:700;min-width:4.8em}" + ".rjpx-btn-row{display:flex;gap:6px}.rjpx-btn{flex:1;border:none;border-radius:10px;padding:8px 10px;font-weight:800;cursor:pointer}" + ".rjpx-btn-start{background:linear-gradient(135deg,#ea580c,#f59e0b);color:#fff}.rjpx-btn-stop{background:#fee2e2;color:#b91c1c}" + ".rjpx-btn-ghost{background:#fff;border:1px solid #cbd5e1;color:#334155}.rjpx-btn:disabled{opacity:.45;cursor:not-allowed}" + ".rjpx-plan-select{width:100%;border:1px solid #cbd5e1;border-radius:8px;padding:5px 8px;font-size:12px;background:#fff}" + ".rjpx-set-card{background:linear-gradient(180deg,#fffdf8,#fff);border:1px solid #e7d5b8;border-radius:12px;padding:10px;margin:0}" + ".rjpx-set-head{font-size:12px;font-weight:900;color:#9a3412;margin:0 0 8px;padding-bottom:6px;border-bottom:1px dashed #f0e0c8}" + ".rjpx-set-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:6px 0;padding:6px 8px;background:#f8fafc;border:1px solid #e8eef5;border-radius:9px}" + ".rjpx-set-label{color:#64748b;font-weight:700;font-size:11px;flex:0 0 auto}" + ".rjpx-set-val{font-weight:800;color:#0f172a;text-align:right;min-width:0}" + ".rjpx-set-token{display:flex;flex-direction:column;gap:6px;margin-top:8px}" + ".rjpx-set-actions{display:flex;gap:6px;flex-wrap:wrap}" + ".rjpx-set-tip{margin-top:8px;padding:7px 8px;border-radius:9px;background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af;font-size:11px;line-height:1.45;font-weight:600}" + ".rjpx-ann{background:linear-gradient(180deg,#fffdf5,#fff7e6);border:1px solid #fcd34d;border-radius:11px;padding:8px 9px;color:#334155;line-height:1.42}" + ".rjpx-footer-extra{padding:6px 9px;background:#f8fafc;border-top:1px solid #e2e8f0}" + "#rjpx-panel-footer{padding:6px 10px;background:#f1f5f9;border-top:1px solid #e2e8f0;color:#475569;font-size:11px}" + ".rjpx-log-line{padding:3px 4px;border-bottom:1px dashed #e2e8f0}.rjpx-empty{padding:14px 8px;text-align:center;color:#94a3b8;font-weight:800}" + ".rjpx-qq-row{display:flex;justify-content:space-between;align-items:center;margin-top:6px;gap:8px}" + ".rjpx-qq-btn{border:none;background:#ea580c;color:#fff;border-radius:8px;padding:6px 10px;font-weight:800;cursor:pointer}" + ".rjpx-btn-pro{border:none;background:linear-gradient(135deg,#0f766e,#14b8a6);color:#fff;border-radius:8px;padding:6px 10px;font-weight:800;cursor:pointer;white-space:nowrap}" + ".rjpx-token-input{width:100%;box-sizing:border-box;border:1px solid #cbd5e1;border-radius:8px;padding:6px 8px;font-size:12px}" + ".rjpx-hint{margin-top:4px;padding:6px 8px;border-radius:8px;background:#eff6ff;border:1px solid #bfdbfe;color:#1e40af;line-height:1.4}" + ".rjpx-chapter-course{margin-bottom:7px;border:1px solid #dbe4f0;border-radius:9px;background:#fff}" + ".rjpx-chapter-title{padding:6px 9px;background:#fff7ed;border-bottom:1px solid #fed7aa;color:#9a3412;font-weight:700}" + ".rjpx-chapter-item{padding:5px 9px;display:flex;justify-content:space-between;gap:8px;font-size:12px;border-top:1px solid #f1f5f9}" + "input.rjpx-num{width:56px;border:1px solid #cbd5e1;border-radius:8px;padding:4px 6px}" + "#rjpx-dialog-mask{position:fixed;inset:0;z-index:1000001;background:rgba(15,23,42,.42);display:flex;align-items:center;justify-content:center;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif}" + "#rjpx-dialog-card{width:min(380px,92vw);background:#fff;border:1px solid #dbe4f0;border-radius:16px;box-shadow:0 20px 48px rgba(15,23,42,.22);overflow:hidden}" + "#rjpx-dialog-head{padding:12px 14px;background:linear-gradient(180deg,#f8ecd5,#f4e8cf);border-bottom:1px solid #e5dbc6}" + "#rjpx-dialog-title{font-size:15px;font-weight:900;color:#9a3412;margin:0}" + "#rjpx-dialog-body{padding:14px 16px;color:#334155;font-size:13px;line-height:1.55;font-weight:600}" + "#rjpx-dialog-link{display:block;margin-top:10px;padding:8px 10px;border-radius:9px;background:#f0fdfa;border:1px solid #99f6e4;color:#0f766e;font-size:12px;font-weight:700;line-height:1.4;word-break:break-all;text-decoration:none}" + "#rjpx-dialog-link:hover{background:#ccfbf1}" + "#rjpx-dialog-actions{padding:0 14px 14px;display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap}" + "#rjpx-dialog-cancel{border:1px solid #cbd5e1;border-radius:10px;padding:8px 16px;font-weight:800;cursor:pointer;background:#fff;color:#475569;font-size:13px}" + "#rjpx-dialog-ok{border:none;border-radius:10px;padding:8px 18px;font-weight:800;cursor:pointer;background:linear-gradient(135deg,#0f766e,#14b8a6);color:#fff;font-size:13px}"; document.documentElement.appendChild(st); } function closePanelDialog() { const el = document.getElementById("rjpx-dialog-mask"); if (el) el.remove(); } function showPanelDialog(opts) { opts = opts || {}; injectPanelStyles(); closePanelDialog(); const link = String(opts.link || "").trim(); const hasCancel = !!opts.cancelText; const mask = document.createElement("div"); mask.id = "rjpx-dialog-mask"; mask.innerHTML = '"; document.body.appendChild(mask); const close = () => { closePanelDialog(); if (typeof opts.onClose === "function") opts.onClose(); }; mask.addEventListener("click", (e) => { if (e.target === mask) close(); }); const cancel = mask.querySelector("#rjpx-dialog-cancel"); if (cancel) cancel.addEventListener("click", close); const linkEl = mask.querySelector("#rjpx-dialog-link"); if (linkEl) { linkEl.addEventListener("click", (e) => { e.preventDefault(); openUrl(link); if (opts.closeOnLink !== false) close(); }); } const ok = mask.querySelector("#rjpx-dialog-ok"); if (ok) { ok.addEventListener("click", () => { if (typeof opts.onOk === "function") opts.onOk(); close(); }); try { ok.focus(); } catch (_) {} } } function showFinishTipDialog() { showPanelDialog({ title: "\u5168\u90e8\u5b66\u5b8c", message: String(state.finishTip || FINISH_TIP), okText: "\u77e5\u9053\u4e86", }); } function showFreeQuotaDialog() { const url = String(state.proBuyUrl || PRO_BUY_URL).trim() || PRO_BUY_URL; showPanelDialog({ title: "\u4f53\u9a8c\u5df2\u7528\u5b8c", message: "\u514d\u8d39\u4f53\u9a8c\u5df2\u7528\u5b8c\uff0c\u7ee7\u7eed\u5904\u7406\u9700\u8981\u5347\u7ea7 Pro\u3002", link: url, cancelText: "\u5173\u95ed", okText: "\u53bb\u5347\u7ea7 Pro", onOk: () => openUrl(url), }); } function enableDrag(panel) { const header = panel.querySelector("#rjpx-panel-header"); let dragging = false, sx = 0, sy = 0, ol = 0, ot = 0; header.addEventListener("mousedown", (e) => { if (e.target.closest(".rjpx-ctl")) return; dragging = true; const rect = panel.getBoundingClientRect(); sx = e.clientX; sy = e.clientY; ol = rect.left; ot = rect.top; e.preventDefault(); }); window.addEventListener("mousemove", (e) => { if (!dragging) return; panel.style.right = "auto"; panel.style.left = Math.max(0, ol + e.clientX - sx) + "px"; panel.style.top = Math.max(0, ot + e.clientY - sy) + "px"; }); window.addEventListener("mouseup", () => { if (!dragging) return; dragging = false; storeSet(K.pos, { left: parseInt(panel.style.left, 10) || 0, top: parseInt(panel.style.top, 10) || 0, }); }); } function syncMinButton(panel) { const btn = panel.querySelector("#rjpx-btn-min"); if (!btn) return; const collapsed = panel.classList.contains("rjpx-min"); btn.textContent = collapsed ? "+" : "–"; btn.title = collapsed ? "\u5c55\u5f00" : "\u6536\u8d77"; } function bindPanelEvents(panel) { panel.querySelector("#rjpx-btn-min").addEventListener("click", () => { const on = !panel.classList.contains("rjpx-min"); panel.classList.toggle("rjpx-min", on); storeSet(K.collapsed, on); syncMinButton(panel); }); panel.querySelectorAll(".rjpx-tab-btn").forEach((btn) => { btn.addEventListener("click", () => { panel.querySelectorAll(".rjpx-tab-btn").forEach((b) => b.classList.toggle("active", b === btn)); panel.querySelectorAll(".rjpx-pane").forEach((p) => p.classList.toggle("active", p.dataset.pane === btn.dataset.tab)); }); }); panel.querySelector("#rjpx-select-replay").addEventListener("click", () => { const sid = state.selectedSid; const ids = state.lessons .filter((x) => x.isReplay && x.status !== "\u672a\u5f00\u59cb" && x.countsCredit !== false) .filter((x) => lessonMatchesSelected(x, sid)) .filter((x) => !isPlatformFull(platformLessonByTitle(x.title, subjectKey(x.stageId, x.subjectId)), subjectKey(x.stageId, x.subjectId))) .filter((x) => { const loc = loadLocalProg()[String(x.id)]; if (loadDoneSet().has(String(x.id))) return false; if (loc && (loc.done || isWatchDone(loc.submittedMs, loc.totalMs))) return false; return true; }) .map((x) => x.id); saveQueue(ids); renderCourseList(); updatePanel(); log("\u5df2\u52fe\u9009\u53ef\u5237\u5fc5\u5b66\u56de\u653e " + ids.length + " \u8bfe\uff08\u5df2\u6392\u9664\u5e73\u53f0\u5df2\u5b66/\u5df2\u5b66\u5f85\u66f4\u65b0\uff09"); }); const clearLogBtn = panel.querySelector("#rjpx-clear-log"); if (clearLogBtn) { clearLogBtn.addEventListener("click", () => { clearRunLog(); log("\u65e5\u5fd7\u5df2\u6e05\u7406"); }); } panel.querySelector("#rjpx-subject").addEventListener("change", async (e) => { state.selectedSid = String(e.target.value || ""); storeSet(K.subject, state.selectedSid); renderCourseList(); try { await _rp({ quiet: true }); } catch (_) {} renderChapterPreview(); _sp(); }); const tokenInput = panel.querySelector("#rjpx-cloud-token"); const tokenSave = panel.querySelector("#rjpx-token-save"); if (tokenSave && tokenInput) { tokenSave.addEventListener("click", async () => { setCloudToken(tokenInput.value); state.tokenBoundBlocked = false; invalidateCloudAuthCache(); try { await _cl(true); if (String(state.cloudTier || "").toLowerCase() === "pro") { log("Pro \u6388\u6743\u5df2\u66f4\u65b0\uff0c\u5230\u671f\uff1a" + formatProExpireText(state.cloudProExpireAt)); } else if (state.tokenBoundBlocked) { log("Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7\uff0c\u672c\u8d26\u53f7\u4e3a\u514d\u8d39\u4f53\u9a8c"); } else { log("\u5f53\u524d\u4e3a\u514d\u8d39\u4f53\u9a8c " + state.freeUsedVideos + "/" + state.freeVideoLimit); } } catch (e) { const msg = String(e && e.message ? e.message : e); if (/token_bound/.test(msg)) log("Token \u5df2\u7ed1\u5b9a\u5176\u4ed6\u8d26\u53f7"); else log("\u6388\u6743\u6821\u9a8c\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u540e\u91cd\u8bd5"); } updateCloudPanelUI(); }); } const proBtn = panel.querySelector("#rjpx-open-pro"); if (proBtn) { proBtn.addEventListener("click", () => openProBuyPage()); } panel.querySelector("#rjpx-start").addEventListener("click", () => startRun()); panel.querySelector("#rjpx-stop").addEventListener("click", () => stopRun()); panel.querySelector("#rjpx-join-qq").addEventListener("click", () => openUrl(QQ_GROUP_LINK)); } function renderSubjectSelect() { const sel = document.getElementById("rjpx-subject"); if (!sel) return; sel.innerHTML = state.subjects .map((s) => { const val = s.key || subjectKey(s.stageId, s.id) || s.id; return ( '" ); }) .join(""); } function renderCourseList() { const box = document.getElementById("rjpx-course-list"); updateCourseTip(); if (!box) return; const q = new Set(loadQueue()); const sid = state.selectedSid; const list = state.lessons.filter( (x) => lessonMatchesSelected(x, sid) && x.countsCredit !== false ); if (!list.length) { box.innerHTML = '
\u6682\u65e0\u8bfe\u65f6
'; _sp(); return; } box.innerHTML = list .map((les) => { const id = String(les.id); const found = findLocalProgForLesson(id, les.title); const loc = found ? found.prog : null; const subKey = subjectKey(les.stageId, les.subjectId) || sid; const plat = platformLessonByTitle(les.title, subKey); const st = formatLessonLearnStatus(plat, loc, id, subKey); const locked = !les.isReplay || les.status === "\u672a\u5f00\u59cb" || st.kind === "plat" || st.kind === "pending"; let tagClass = "rjpx-tag-ok"; if (st.kind === "plat") tagClass = "rjpx-tag-plat"; else if (st.kind === "pending") tagClass = "rjpx-tag-done"; else if (st.kind === "todo") tagClass = "rjpx-tag-wait"; const tag = '' + st.tag + ""; const platTxt = st.kind === "plat" ? formatHms(plat.watchedMs) + "/" + formatHms(plat.totalMs) : st.kind === "pending" ? "\u5df2\u5b66\u5f85\u5e73\u53f0\u66f4\u65b0" : "\u8bfe\u7a0b\u5f85\u5b66\u4e60"; return ( '" ); }) .join(""); box.querySelectorAll("input[data-id]").forEach((inp) => { inp.addEventListener("change", () => { const other = loadQueue().filter((id) => { const les = state.lessons.find((x) => x.id === id); return les && !lessonMatchesSelected(les, sid); }); const cur = [...box.querySelectorAll("input[data-id]:checked")].map((x) => x.getAttribute("data-id")); saveQueue([...new Set(other.concat(cur))]); updatePanel(); }); }); _sp(); } function renderChapterPreview() { const box = document.getElementById("rjpx-chapter-preview"); if (!box) return; const local = loadLocalProg(); const creditSections = (state.platformSections || []).filter((sec) => isCreditSectionName(sec.title)); if (!creditSections.length && !Object.keys(local).length) { box.innerHTML = '
\u6682\u65e0\u5fc5\u5b66\u5e73\u53f0\u6570\u636e\uff08\u52a0\u8f7d\u8bfe\u5355\u65f6\u81ea\u52a8\u540c\u6b65\uff09
'; return; } const bySection = new Map(); (state.platformLessons || []).forEach((L) => { if (!isCreditSectionName(L.section)) return; const key = L.section || "\u672a\u5206\u7c7b"; if (!bySection.has(key)) bySection.set(key, []); bySection.get(key).push(L); }); let html = ""; creditSections.forEach((sec) => { const items = bySection.get(sec.title) || []; const body = items.length ? items .map((L) => { const found = findLocalProgForLesson("", L.title); const loc = found ? found.prog : Object.values(local).find((x) => x && x.title === L.title); const st = formatLessonLearnStatus(L, loc, ""); return ( '
' + escHtml(L.title) + "" + escHtml(st.text) + "
" ); }) .join("") : '
\u6c47\u603b' + (Number(sec.percent) >= 100 ? "\u5e73\u53f0\u5df2\u5b66\u66f4\u65b0" : "\u8bfe\u7a0b\u5f85\u5b66\u4e60") + "
"; html += '
' + escHtml(sec.title) + "
" + body + "
"; }); box.innerHTML = html || '
\u65e0\u5fc5\u5b66\u660e\u7ec6
'; } function updatePanel() { const q = loadQueue(); const done = loadDoneSet(); const local = loadLocalProg(); const doneCount = q.filter( (id) => done.has(id) || (local[id] && (local[id].done || isWatchDone(local[id].submittedMs, local[id].totalMs))) ).length; const total = q.length; const pct = total ? Math.round((doneCount / total) * 100) : 0; const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; }; set("rjpx-auto-status", state.enabled ? (state.bgBusy ? "\u5b66\u4e60\u4e2d" : "\u8fd0\u884c\u4e2d") : "\u5df2\u505c\u6b62"); set("rjpx-queue-done", String(doneCount)); set("rjpx-queue-total", String(total)); set("rjpx-queue-percent", pct + "%"); const bar = document.getElementById("rjpx-queue-progress"); if (bar) bar.style.width = pct + "%"; set( "rjpx-current-task", state.currentTask || (state.current && state.current.title) || (state.enabled ? "\u6392\u961f\u4e2d…" : "\u672a\u5f00\u59cb") ); set("rjpx-queue-text", total ? "\u5df2\u9009 " + total + " · \u5f85\u5e73\u53f0\u66f4\u65b0 " + doneCount : "\u672a\u9009\u62e9"); const start = document.getElementById("rjpx-start"); const stop = document.getElementById("rjpx-stop"); if (start) start.disabled = !!state.enabled; if (stop) stop.disabled = !state.enabled; const logBox = document.getElementById("rjpx-run-log"); if (logBox) renderRunLog(); } function createPanel() { if (document.getElementById("rjpx-auto-panel")) return; injectPanelStyles(); const panel = document.createElement("div"); panel.id = "rjpx-auto-panel"; panel.innerHTML = '
' + SCRIPT_TITLE + ' v' + SCRIPT_VERSION + '
\u56de\u653e\u4e00\u952e\u5b8c\u6210\u5e73\u53f0\u5df2\u5b66\u66f4\u65b0\u6b21\u65e5\u81ea\u52a8\u66f4\u65b0\u5b66\u65f6
' + '
\u8fd0\u884c\u72b6\u6001\u5df2\u505c\u6b62
0/0 \u5df2\u5b66\u5f85\u5e73\u53f0\u66f4\u65b00%
' + '
' + '
\u57f9\u8bad\u672a\u5f00\u59cb · \u53ea\u5904\u7406\u56de\u653e\u8bfe\u7a0b
\u52a0\u8f7d\u4e2d…
' + '
\u5e73\u53f0\u5df2\u5b66\u66f4\u65b0\u9694\u65e5 / \u6ee1 12 \u5c0f\u65f6\u540e\u67e5\u770b
\u52a0\u8f7d\u8bfe\u5355\u540e\u81ea\u52a8\u540c\u6b65
' + '
\u8fd0\u884c\u65e5\u5fd7
' + '
\u4f1a\u5458\u4e0e\u6388\u6743
\u7528\u6237\u7c7b\u578b\u672a\u6821\u9a8c
\u514d\u8d39\u4f53\u9a8c0/2
\u5237\u5b8c\u63d0\u793a\uff1a\u5e73\u53f0\u8fdb\u5ea6\u901a\u5e38\u9694\u65e5\u66f4\u65b0\uff0c\u4e14\u81f3\u5c11\u5237\u5b8c 12 \u5c0f\u65f6\u540e\u624d\u80fd\u66f4\u65b0\u3002\u57f9\u8bad\u672a\u5f00\u59cb\u65f6\u53ea\u5904\u7406\u56de\u653e\u8bfe\u7a0b\u3002
' + '
\u5f53\u524d\u4efb\u52a1\u672a\u5f00\u59cb
' + ''; document.body.appendChild(panel); const savedPos = storeGet(K.pos, null); if (savedPos && savedPos.left != null) { panel.style.right = "auto"; panel.style.left = savedPos.left + "px"; panel.style.top = (savedPos.top || 70) + "px"; } if (storeGet(K.collapsed, false)) panel.classList.add("rjpx-min"); syncMinButton(panel); enableDrag(panel); bindPanelEvents(panel); loadCachedLease(); updateCloudPanelUI(); updatePanel(); } async function boot() { if (IS_GENSEE) { if (document.readyState === "loading") { await new Promise((r) => document.addEventListener("DOMContentLoaded", r, { once: true })); } loadCachedLease(); state.cloudToken = getCloudToken(); await bootGenseeApiWorker(); return; } if (!IS_PEP) return; await new Promise((resolve) => { const tick = () => (document.body ? resolve() : setTimeout(tick, 100)); tick(); }); createPanel(); log("\u9762\u677f\u5df2\u5c31\u7eea"); _fm().catch(() => {}); state.panelRefreshing = true; updatePanel(); try { await loadAllSubjectsCourses(); renderSubjectSelect(); renderCourseList(); renderChapterPreview(); ensureCoursePageButtonObserver(); _bw(); await _eps(true); } catch (e) { log("\u521d\u59cb\u5316\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u91cd\u8bd5"); } finally { state.panelRefreshing = false; updatePanel(); } if (storeGet(K.enabled, false)) { state.enabled = true; log("\u6062\u590d\u4e0a\u6b21\u672a\u5b8c\u6210\u961f\u5217"); try { await _cl(false); } catch (_) {} bgLoop(); } } boot(); })();