// ==UserScript== // @name 英华学堂全系列刷课助手|自动刷课|考试自动答题 // @namespace https://github.com/iFulling/cdcasSK // @version 4.4.0 // @author iFulling // @description 英华学堂全系列刷课助手(原为成都文理学院刷课助手)🚀目前支持平台包括:【粟湾科技、海旗科技、御瑞在线学堂、如仁科技、厚荣科技】等等。😀目前已具有功能包括:视频自动播放、自动识别填充验证码、考试自动答题等功能。如有bug请留言。🐧QQ交流群:878643471 // @license MIT // @icon data:image/png;base64,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 // @match *://*.yuruixxkj.com/* // @match *://*.zjxkeji.com/* // @match *://mooc.cdcas.com/* // @match *://*.cdcas.com/* // @match *://*.haiqikeji.com/* // @match *://*/* // @require https://registry.npmmirror.com/onnxruntime-web/1.17.0/files/dist/ort.min.js // @connect ark.cn-beijing.volces.com // @connect api.openai.com // @connect api.anthropic.com // @connect generativelanguage.googleapis.com // @connect api.x.ai // @connect api.deepseek.com // @connect * // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_getValue // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // @run-at document-idle // @noframes // ==/UserScript== (function() { "use strict"; var CONFIG = { VERSION: "4.4.0", DEFAULT: { URLS: "cdcas.yuruixxkj.com|cdcas.rurenkj.com|cdcas.suwankj.com|chengxikej.com|haiqikeji.com", TIMEOUT_MINUTES: 40 }, THRESHOLDS: { VIDEO_STUCK_CHECK_INTERVAL: 3e4, VIDEO_STUCK_MIN_DIFF: .5, CAPTCHA_CHECK_INTERVAL: 1e3 } }; var SELECTORS = { VIDEO: "video, .video-player", CAPTCHA_LAYER: ".layui-layer-content", CAPTCHA_IMG: ".layui-layer-content img", CAPTCHA_INPUT: ".layui-layer-content input", PLAY_BUTTON: ".layui-layer-btn0", COURSE_LINKS: "a[target=\"_self\"], .section-item", TOPIC_HEAD: ".topic-head", TOPIC_TAB_PREFIX: "#topic-tab-", EXAM_MAIN: ".courseexamcon-main", EXAM_INTRO_LIST: ".courseexamcon-intro ul li" }; var PROVIDERS = { volcengine: { label: "火山引擎(豆包)", protocol: "openai-chat", baseUrl: "https://ark.cn-beijing.volces.com/api/v3", model: "", host: "ark.cn-beijing.volces.com", keyLabel: "火山引擎 API Key", modelHint: "填接入点 ID(ep- 开头)或模型名称,获取方式见教程" }, openai: { label: "OpenAI", protocol: "openai-chat", protocols: ["openai-chat", "openai-responses"], baseUrl: "https://api.openai.com/v1", model: "gpt-5.4-mini", host: "api.openai.com", keyLabel: "OpenAI API Key", modelHint: "默认使用 gpt-5.4-mini,也可以填写账号可用的其他模型" }, anthropic: { label: "Claude(Anthropic)", protocol: "anthropic", baseUrl: "https://api.anthropic.com/v1", model: "claude-sonnet-5", host: "api.anthropic.com", keyLabel: "Anthropic API Key", modelHint: "默认使用 claude-sonnet-5,也可以填写其他 Claude 模型" }, gemini: { label: "Gemini(Google)", protocol: "gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta", model: "gemini-3.6-flash", host: "generativelanguage.googleapis.com", keyLabel: "Google AI Studio API Key", modelHint: "默认使用 gemini-3.6-flash,也可以填写其他 Gemini 模型" }, grok: { label: "Grok(xAI)", protocol: "openai-chat", protocols: ["openai-chat", "openai-responses"], baseUrl: "https://api.x.ai/v1", model: "grok-4.5", host: "api.x.ai", keyLabel: "xAI API Key", modelHint: "默认使用 grok-4.5,也可以填写账号可用的其他模型" }, deepseek: { label: "DeepSeek", protocol: "openai-chat", protocols: ["openai-chat", "openai-responses"], baseUrl: "https://api.deepseek.com", model: "deepseek-v4-flash", host: "api.deepseek.com", keyLabel: "DeepSeek API Key", modelHint: "默认使用 deepseek-v4-flash,也可以填写账号可用的其他模型" }, custom: { label: "自建接口(sub2api / new-api 等)", protocol: "openai-chat", protocols: [ "openai-chat", "openai-responses", "anthropic", "gemini" ], baseUrl: "", model: "", host: "", keyLabel: "中转站令牌(一般是 sk- 开头)", modelHint: "填写中转站实际提供的模型名称" } }; var PROVIDER_ORDER = [ "volcengine", "openai", "anthropic", "gemini", "grok", "deepseek", "custom" ]; var DEFAULT_PROVIDER = "volcengine"; function getProvider(id) { return PROVIDERS[id] || PROVIDERS["volcengine"]; } function getProviderProtocols(id) { const provider = getProvider(id); return provider.protocols || [provider.protocol]; } function getProvidersForProtocol(protocolId) { return PROVIDER_ORDER.filter((id) => getProviderProtocols(id).includes(protocolId)); } function getDefaultProviderForProtocol(protocolId) { const providerIds = getProvidersForProtocol(protocolId); return providerIds.includes("volcengine") ? DEFAULT_PROVIDER : providerIds[0]; } function getBaseUrlHint(providerId, protocolId) { const provider = getProvider(providerId); if (provider.baseUrl) return `留空时使用预设地址:${provider.baseUrl}`; if (protocolId === "gemini") return "填写到版本路径,例如 https://你的域名/v1beta,不要带 models/...:generateContent"; if (protocolId === "anthropic") return "填写到版本路径,例如 https://你的域名/v1,不要带 messages"; return "填写到版本路径,例如 https://你的域名/v1,不要带 chat/completions 或 responses"; } Object.values(PROVIDERS).map((item) => item.host).filter(Boolean); var ConfigManager = class { static get(key, defaultValue) { return GM_getValue(key, defaultValue); } static set(key, value) { GM_setValue(key, value); } static get urls() { return this.get("刷课配置.urls", CONFIG.DEFAULT.URLS).split("|").map((u) => u.trim()).filter((u) => u); } static set urls(value) { this.set("刷课配置.urls", value); } static get timeoutMinutes() { return this.get("刷课配置.timeout_minutes", CONFIG.DEFAULT.TIMEOUT_MINUTES); } static set timeoutMinutes(value) { this.set("刷课配置.timeout_minutes", value); } static _aiKey(providerId, field) { return `搜题配置.providers.${this._normalizeProvider(providerId)}.${field}`; } static _normalizeProvider(providerId) { return Object.prototype.hasOwnProperty.call(PROVIDERS, providerId) ? providerId : DEFAULT_PROVIDER; } static _migrateAiValue(providerId, field, value) { if (value === "" || value === void 0 || value === null) return; const key = this._aiKey(providerId, field); if (!this.get(key, "")) this.set(key, value); } static migrate() { const savedProvider = this._normalizeProvider(this.get("搜题配置.provider", DEFAULT_PROVIDER)); const endpointId = this.get("搜题配置.endpoint_id", ""); const flatModel = this.get("搜题配置.model", ""); const flatBaseUrl = this.get("搜题配置.base_url", ""); const flatProtocol = this.get("搜题配置.protocol", ""); const flatApiKey = this.get("搜题配置.apikey", ""); this._migrateAiValue("volcengine", "model", endpointId); this._migrateAiValue(savedProvider, "model", flatModel); this._migrateAiValue(savedProvider, "base_url", flatBaseUrl); this._migrateAiValue(savedProvider, "protocol", flatProtocol); this._migrateAiValue(savedProvider, "apikey", flatApiKey); GM_deleteValue("搜题配置.endpoint_id"); GM_deleteValue("搜题配置.model"); GM_deleteValue("搜题配置.base_url"); GM_deleteValue("搜题配置.protocol"); GM_deleteValue("搜题配置.apikey"); } static get aiProvider() { return this._normalizeProvider(this.get("搜题配置.provider", DEFAULT_PROVIDER)); } static set aiProvider(value) { this.set("搜题配置.provider", this._normalizeProvider(value)); } static get aiProtocol() { const provider = getProvider(this.aiProvider); const saved = this.get(this._aiKey(this.aiProvider, "protocol"), ""); return getProviderProtocols(this.aiProvider).includes(saved) ? saved : provider.protocol; } static set aiProtocol(value) { const allowed = getProviderProtocols(this.aiProvider); this.set(this._aiKey(this.aiProvider, "protocol"), allowed.includes(value) ? value : getProvider(this.aiProvider).protocol); } static get aiBaseUrl() { return this.get(this._aiKey(this.aiProvider, "base_url"), ""); } static set aiBaseUrl(value) { this.set(this._aiKey(this.aiProvider, "base_url"), value); } static get aiModel() { return this.get(this._aiKey(this.aiProvider, "model"), ""); } static set aiModel(value) { this.set(this._aiKey(this.aiProvider, "model"), value); } static get apiKey() { return this.get(this._aiKey(this.aiProvider, "apikey"), ""); } static set apiKey(value) { this.set(this._aiKey(this.aiProvider, "apikey"), value); } static get aiRetry() { const val = parseInt(this.get("搜题配置.retry", 1)); return Number.isNaN(val) ? 1 : Math.max(0, Math.min(5, val)); } static set aiRetry(value) { this.set("搜题配置.retry", value); } static getAiSettings(providerId = this.aiProvider) { const normalized = this._normalizeProvider(providerId); const provider = getProvider(normalized); const allowed = getProviderProtocols(normalized); const savedProtocol = this.get(this._aiKey(normalized, "protocol"), ""); return { providerId: normalized, protocolId: allowed.includes(savedProtocol) ? savedProtocol : provider.protocol, baseUrl: this.get(this._aiKey(normalized, "base_url"), ""), apiKey: this.get(this._aiKey(normalized, "apikey"), ""), model: this.get(this._aiKey(normalized, "model"), "") }; } static saveAiSettings(settings) { const providerId = this._normalizeProvider(settings?.providerId); this.aiProvider = providerId; this.aiProtocol = settings?.protocolId; this.aiBaseUrl = settings?.baseUrl || ""; this.apiKey = settings?.apiKey || ""; this.aiModel = settings?.model || ""; } static get aiSettings() { return this.getAiSettings(); } static get autoLoginCaptcha() { return this.get("刷课配置.auto_login_captcha", false); } static set autoLoginCaptcha(value) { this.set("刷课配置.auto_login_captcha", value); } static get autoFillAccount() { return this.get("刷课配置.auto_fill_account", false); } static set autoFillAccount(value) { this.set("刷课配置.auto_fill_account", value); } static get loginAccount() { return this.get("刷课配置.login_account", ""); } static set loginAccount(value) { this.set("刷课配置.login_account", value); } static get loginPassword() { return this.get("刷课配置.login_password", ""); } static set loginPassword(value) { this.set("刷课配置.login_password", value); } static get autoImportModels() { return this.get("刷课配置.auto_import_models", true); } static set autoImportModels(value) { this.set("刷课配置.auto_import_models", value); } static get videoStage() { return this.get("刷课配置.video_stage", 1); } static set videoStage(value) { this.set("刷课配置.video_stage", value); } }; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) { return typeof o; } : function(o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } var Logger = class { static setUIManager(ui) { this._uiManager = ui; } static _log(level, msg) { const formatted = `[${new Date().toLocaleTimeString()}] [${level}] ${msg}`; if (this._uiManager) this._uiManager.addLog(formatted, level.toLowerCase()); } static info(msg) { this._log("INFO", msg); } static error(msg) { this._log("ERROR", msg); } static warn(msg) { this._log("WARN", msg); } }; _defineProperty(Logger, "_uiManager", null); var Utils = class { static async pause(min, max) { if (max === void 0) return new Promise((resolve) => setTimeout(resolve, min * 1e3)); const delay = Math.floor(Math.random() * (max - min + 1)) + min; return new Promise((resolve) => setTimeout(resolve, delay * 1e3)); } }; var LocalOcrEngine = class { constructor(ui) { this.ui = ui; this.session = null; this.charsets = []; this.initialized = false; this.dbName = "iFulling_OCR_DB"; this.storeName = "models"; const GH_USER = "iFulling"; const GH_REPO = "cdcasSK"; const GH_BRANCH = "main"; const GH_DIR = "onnx"; const cdnPrefixes = [ `https://raw.githubusercontent.com/${GH_USER}/${GH_REPO}/${GH_BRANCH}/${GH_DIR}/`, `https://cdn.jsdelivr.net/gh/${GH_USER}/${GH_REPO}@${GH_BRANCH}/${GH_DIR}/`, `https://gh-proxy.org/https://raw.githubusercontent.com/${GH_USER}/${GH_REPO}/${GH_BRANCH}/${GH_DIR}/`, `https://ghfast.top/https://raw.githubusercontent.com/${GH_USER}/${GH_REPO}/${GH_BRANCH}/${GH_DIR}/`, `https://ghproxy.com/https://raw.githubusercontent.com/${GH_USER}/${GH_REPO}/${GH_BRANCH}/${GH_DIR}/`, `https://raw.kkgithub.com/${GH_USER}/${GH_REPO}/${GH_BRANCH}/${GH_DIR}/` ]; this.modelUrls = cdnPrefixes.map((prefix) => prefix + "common.onnx"); this.charsetsUrls = cdnPrefixes.map((prefix) => prefix + "charsets.json"); } _openDB() { return new Promise((resolve) => { const request = indexedDB.open(this.dbName, 1); request.onerror = () => resolve(null); request.onsuccess = (e) => resolve(e.target.result); request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains(this.storeName)) db.createObjectStore(this.storeName); }; }); } async _getFromDB(key) { try { const db = await this._openDB(); if (!db) return null; return new Promise((resolve) => { const req = db.transaction([this.storeName], "readonly").objectStore(this.storeName).get(key); req.onsuccess = () => resolve(req.result); req.onerror = () => resolve(null); }); } catch { return null; } } async _saveToDB(key, data) { try { const db = await this._openDB(); if (!db) return false; return new Promise((resolve) => { const req = db.transaction([this.storeName], "readwrite").objectStore(this.storeName).put(data, key); req.onsuccess = () => resolve(true); req.onerror = () => resolve(false); }); } catch { return false; } } async fetchWithFallback(urls, responseType = "arraybuffer", cacheKey = null) { if (cacheKey) { const cached = await this._getFromDB(cacheKey); if (cached) { Logger.info(`读取视觉模型数据:${cacheKey}`); if (this.ui) this.ui.updateStatus(`[缓存] 命中视觉模型:${cacheKey}`); return cached; } } for (const url of urls) try { Logger.warn(`正在尝试请求视觉识别模型:${url}`); const response = await fetch(url); if (!response.ok) continue; Logger.info(`视觉识别模型请求成功,下载中......`); if (responseType === "json") { const data = await response.json(); if (cacheKey) await this._saveToDB(cacheKey, data); return data; } const contentLength = response.headers.get("content-length"); const total = contentLength ? parseInt(contentLength, 10) : 0; let loaded = 0; const chunks = []; const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); loaded += value.length; if (this.ui) { if (total) { const percent = (loaded / total * 100).toFixed(1); this.ui.updateStatus(`视觉识别模型下载进度: ${percent}% (已下载 ${(loaded / 1024 / 1024).toFixed(1)}MB)`); } else this.ui.updateStatus(`正在下载视觉识别模型: ${(loaded / 1024 / 1024).toFixed(1)}MB`); } } const arrayBuffer = new Uint8Array(loaded); let offset = 0; for (let c of chunks) { arrayBuffer.set(c, offset); offset += c.length; } if (cacheKey) await this._saveToDB(cacheKey, arrayBuffer.buffer); return arrayBuffer.buffer; } catch { Logger.warn(`从 ${url} 获取失败,尝试下一个...`); } throw new Error("所有 CDN 下载失败,请检查网络!"); } async init() { if (this.initialized) return; Logger.info("正在加载视觉识别模型与引擎..."); if (this.ui) this.ui.updateStatus("开始加载视觉识别模型..."); try { ort.env.wasm.numThreads = 1; let wasmBuffer = await this._getFromDB("ort-wasm.wasm"); if (wasmBuffer) Logger.info("从本地缓存读取 WASM 引擎..."); else { if (!ConfigManager.autoImportModels) { Logger.warn("本地没有 WASM 引擎,且自动导入已关闭,无法继续加载"); if (this.ui) this.ui.updateStatus("缺少 WASM 引擎,请手动导入"); return; } Logger.warn("未找到本地 WASM 引擎,正在通过网络下载并缓存 (只需一次)..."); if (this.ui) this.ui.updateStatus("正在下载 WASM 引擎..."); wasmBuffer = await this.fetchWithFallback([ "https://registry.npmmirror.com/onnxruntime-web/1.17.0/files/dist/ort-wasm.wasm", "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/ort-wasm.wasm", "https://unpkg.com/onnxruntime-web@1.17.0/dist/ort-wasm.wasm" ], "arraybuffer", "ort-wasm.wasm"); Logger.info("WASM 引擎下载并缓存成功!"); } if (wasmBuffer) { const wasmBlob = new Blob([wasmBuffer], { type: "application/wasm" }); const wasmUrl = URL.createObjectURL(wasmBlob); ort.env.wasm.wasmPaths = { "ort-wasm.wasm": wasmUrl, "ort-wasm-simd.wasm": wasmUrl, "ort-wasm-threaded.wasm": wasmUrl, "ort-wasm-simd-threaded.wasm": wasmUrl }; } else throw new Error("WASM 引擎获取失败,所有 CDN 均不可用"); let modelBuffer = await this._getFromDB("common.onnx"); if (!modelBuffer) { if (!ConfigManager.autoImportModels) { Logger.warn("本地没有视觉模型,且自动导入已关闭,无法继续加载"); if (this.ui) this.ui.updateStatus("缺少视觉模型,请手动导入"); return; } modelBuffer = await this.fetchWithFallback(this.modelUrls, "arraybuffer", "common.onnx"); } let charsetsData = await this._getFromDB("charsets.json"); if (!charsetsData) { if (!ConfigManager.autoImportModels) { Logger.warn("本地没有字符集,且自动导入已关闭,无法继续加载"); if (this.ui) this.ui.updateStatus("缺少字符集,请手动导入"); return; } charsetsData = await this.fetchWithFallback(this.charsetsUrls, "json", "charsets.json"); } this.charsets = charsetsData; if (this.ui) this.ui.updateStatus("正在构建推理模型..."); this.session = await ort.InferenceSession.create(modelBuffer, { executionProviders: ["wasm"] }); this.initialized = true; Logger.info("视觉识别模型完全加载完毕!"); if (this.ui) this.ui.updateStatus("视觉识别核心加载完毕可用!"); } catch (e) { Logger.error("视觉识别模型初始化失败:" + e.message); if (this.ui) this.ui.updateStatus("视觉识别模型失败!"); } } preprocess(imgElement) { const canvas = document.createElement("canvas"); const width = imgElement.naturalWidth || imgElement.width; const height = imgElement.naturalHeight || imgElement.height; canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, width, height); ctx.drawImage(imgElement, 0, 0); const imageData = ctx.getImageData(0, 0, width, height); const gray = new Float32Array(width * height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3] / 255; const bg = 255 * (1 - a); gray[i / 4] = (.2126 * (r * a + bg) + .7152 * (g * a + bg) + .0722 * (b * a + bg)) / 255; } const targetHeight = 64; const targetWidth = Math.floor(width * (targetHeight / height)); const resized = new Float32Array(targetWidth * targetHeight); for (let y = 0; y < targetHeight; y++) for (let x = 0; x < targetWidth; x++) { const srcX = x * (width / targetWidth); const srcY = y * (height / targetHeight); const x1 = Math.floor(srcX), x2 = Math.min(x1 + 1, width - 1); const y1 = Math.floor(srcY), y2 = Math.min(y1 + 1, height - 1); const dx = srcX - x1, dy = srcY - y1; const val = gray[y1 * width + x1] * (1 - dx) * (1 - dy) + gray[y1 * width + x2] * dx * (1 - dy) + gray[y2 * width + x1] * (1 - dx) * dy + gray[y2 * width + x2] * dx * dy; resized[y * targetWidth + x] = val; } return { tensorData: resized, width: targetWidth }; } async recognize(imgElement) { if (!this.initialized) await this.init(); if (!this.session) return null; try { const { tensorData, width } = this.preprocess(imgElement); const tensor = new ort.Tensor("float32", tensorData, [ 1, 1, 64, width ]); const output = (await this.session.run({ input1: tensor })).output.data; const text = []; let lastIndex = -1; for (let i = 0; i < output.length; i++) { const idx = Math.round(Number(output[i])); if (idx === lastIndex) continue; lastIndex = idx; if (idx <= 0 || idx >= this.charsets.length) continue; text.push(this.charsets[idx]); } return text.join(""); } catch (e) { Logger.error("识别出错: " + e.message); return null; } } }; function joinUrl(base, path) { const left = String(base || "").trim().replace(/\/+$/, ""); const right = String(path || "").trim().replace(/^\/+/, ""); if (!right) return left; return `${left}/${right}`; } function resolveEndpoint(baseUrl, path) { const base = String(baseUrl || "").trim().replace(/\/+$/, ""); const suffix = String(path || "").replace(/^\/+/, ""); if (!base) return ""; if (suffix && base.toLowerCase().endsWith("/" + suffix.toLowerCase())) return base; return joinUrl(base, suffix); } function normalizeText(text) { if (typeof text !== "string") return ""; return text.replace(/[\s\S]*?<\/think>/gi, "").replace(/<\/?think>/gi, "").trim(); } function extractApiError(status, responseText) { let detail = ""; try { const json = JSON.parse(responseText); detail = json?.error?.message || json?.error?.type || json?.message || json?.msg || ""; } catch { detail = String(responseText || "").slice(0, 200); } const hint = HTTP_HINTS[status]; return [ `接口返回 ${status}`, hint, detail ].filter(Boolean).join(":"); } var HTTP_HINTS = { 400: "请求被拒绝,通常是模型名称填错", 401: "鉴权失败,请检查 API Key", 403: "没有访问权限,请检查 API Key 或模型是否开通", 404: "地址或模型不存在,请检查接口地址", 429: "请求过于频繁或余额不足", 500: "服务端异常,稍后再试", 502: "网关异常,稍后再试", 503: "服务不可用,稍后再试" }; function buildOpenAiModelListRequest({ baseUrl, apiKey }) { return { method: "GET", url: resolveEndpoint(baseUrl, "models"), headers: { "Authorization": `Bearer ${apiKey}` } }; } function parseOpenAiModelListResponse(json) { return (Array.isArray(json?.data) ? json.data : []).map((item) => item?.id).filter(Boolean); } var openaiChat = { id: "openai-chat", label: "OpenAI 兼容 · Chat Completions", path: "chat/completions", buildModelListRequest: buildOpenAiModelListRequest, parseModelListResponse: parseOpenAiModelListResponse, buildRequest({ baseUrl, apiKey, model, system, user }) { return { url: resolveEndpoint(baseUrl, this.path), headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: { model, messages: [{ role: "system", content: system }, { role: "user", content: user }] } }; }, parseResponse(json) { const choice = json?.choices?.[0]; const message = choice?.message; return normalizeText(Array.isArray(message?.content) ? message.content.map((part) => part?.text || part?.content || "").join("") : message?.content ?? choice?.text); } }; var openaiResponses = { id: "openai-responses", label: "OpenAI 兼容 · Responses", path: "responses", buildModelListRequest: buildOpenAiModelListRequest, parseModelListResponse: parseOpenAiModelListResponse, buildRequest({ baseUrl, apiKey, model, system, user }) { return { url: resolveEndpoint(baseUrl, this.path), headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: { model, instructions: system, input: user } }; }, parseResponse(json) { if (typeof json?.output_text === "string" && json.output_text.trim()) return normalizeText(json.output_text); const output = Array.isArray(json?.output) ? json.output : []; const parts = []; for (const item of output) { if (item?.type && item.type !== "message") continue; for (const content of item?.content || []) if (typeof content?.text === "string") parts.push(content.text); } return normalizeText(parts.join("")); } }; var anthropic = { id: "anthropic", label: "Anthropic · Claude Messages", path: "messages", buildModelListRequest({ baseUrl, apiKey }) { return { method: "GET", url: resolveEndpoint(baseUrl, "models?limit=1000"), headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01", "anthropic-dangerous-direct-browser-access": "true" } }; }, parseModelListResponse(json) { return (Array.isArray(json?.data) ? json.data : []).map((item) => item?.id).filter(Boolean); }, buildRequest({ baseUrl, apiKey, model, system, user, maxTokens = 64 }) { return { url: resolveEndpoint(baseUrl, this.path), headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01", "anthropic-dangerous-direct-browser-access": "true" }, body: { model, max_tokens: maxTokens, system, messages: [{ role: "user", content: user }] } }; }, parseResponse(json) { return normalizeText((Array.isArray(json?.content) ? json.content : []).filter((block) => block?.type === "text").map((block) => block.text || "").join("")); } }; var gemini = { id: "gemini", label: "Google · Gemini generateContent", buildModelListRequest({ baseUrl, apiKey }) { return { method: "GET", url: resolveEndpoint(baseUrl, "models?pageSize=1000"), headers: { "x-goog-api-key": apiKey } }; }, parseModelListResponse(json) { return (Array.isArray(json?.models) ? json.models : []).filter((item) => { const methods = item?.supportedGenerationMethods; return !Array.isArray(methods) || methods.includes("generateContent"); }).map((item) => String(item?.name || "").replace(/^models\//, "")).filter(Boolean); }, buildRequest({ baseUrl, apiKey, model, system, user }) { return { url: resolveEndpoint(baseUrl, `models/${String(model || "").replace(/^models\//, "")}:generateContent`), headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey }, body: { systemInstruction: { parts: [{ text: system }] }, contents: [{ role: "user", parts: [{ text: user }] }], generationConfig: { maxOutputTokens: 64, temperature: 0 } } }; }, parseResponse(json) { return normalizeText((json?.candidates?.[0]?.content?.parts || []).filter((part) => part?.thought !== true).map((part) => part?.text || "").join("")); } }; var PROTOCOLS = { [openaiChat.id]: openaiChat, [openaiResponses.id]: openaiResponses, [anthropic.id]: anthropic, [gemini.id]: gemini }; var PROTOCOL_ORDER = [ "openai-chat", "openai-responses", "anthropic", "gemini" ]; function getProtocol(id) { return PROTOCOLS[id] || null; } var REQUEST_TIMEOUT = 6e4; var AiRequestError = class extends Error { constructor(message, { status = 0, retryable = false } = {}) { super(message); this.name = "AiRequestError"; this.status = status; this.retryable = retryable; } }; var AiClient = class { constructor(settings) { this.settings = settings || {}; } validate() { const { apiKey, baseUrl, model } = this.resolve(); if (!baseUrl) return "请先在配置面板里填写接口地址"; if (!apiKey) return "请先在配置面板里填写 API Key"; if (!model) return "请先在配置面板里填写模型名称"; return null; } validateModelList() { const { apiKey, baseUrl } = this.resolve(); if (!baseUrl) return "请先填写接口地址"; if (!apiKey) return "请先填写 API Key"; return null; } resolve() { const { providerId, baseUrl, apiKey, model, protocolId } = this.settings; const provider = getProvider(providerId); return { provider, protocolId: getProviderProtocols(providerId).includes(protocolId) ? protocolId : provider.protocol, baseUrl: (baseUrl || provider.baseUrl || "").trim(), apiKey: (apiKey || "").trim(), model: (model || provider.model || "").trim() }; } async ask(question, system) { const invalid = this.validate(); if (invalid) throw new Error(invalid); const { protocolId, baseUrl, apiKey, model } = this.resolve(); const protocol = getProtocol(protocolId); if (!protocol) throw new Error(`未知的接口协议:${protocolId}`); const request = protocol.buildRequest({ baseUrl, apiKey, model, system, user: question }); const responseText = await this._send(request); let json; try { json = JSON.parse(responseText); } catch { throw new Error("接口返回的不是合法 JSON,请检查接口地址是否填对"); } const answer = protocol.parseResponse(json); if (!answer) throw new Error("接口没有返回有效内容,请检查模型名称或账号额度"); return answer; } async listModels() { const invalid = this.validateModelList(); if (invalid) throw new Error(invalid); const { protocolId, baseUrl, apiKey } = this.resolve(); const protocol = getProtocol(protocolId); if (!protocol?.buildModelListRequest || !protocol?.parseModelListResponse) throw new Error("当前接口格式不支持获取模型列表"); const request = protocol.buildModelListRequest({ baseUrl, apiKey }); const responseText = await this._send(request); let json; try { json = JSON.parse(responseText); } catch { throw new Error("模型列表接口返回的不是合法 JSON,请检查接口地址"); } const models = protocol.parseModelListResponse(json); const normalized = [...new Set((Array.isArray(models) ? models : []).map((item) => String(item || "").trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b, void 0, { numeric: true })); if (!normalized.length) throw new Error("接口没有返回可用模型,请手动填写模型名称"); return normalized; } _send({ method = "POST", url, headers, body }) { return new Promise((resolve, reject) => { const options = { method, url, headers, timeout: REQUEST_TIMEOUT, onload: (res) => { if (res.status >= 200 && res.status < 300) resolve(res.responseText); else reject(new AiRequestError(extractApiError(res.status, res.responseText), { status: res.status, retryable: res.status === 0 || res.status === 408 || res.status === 409 || res.status === 429 || res.status >= 500 })); }, onerror: () => reject(new AiRequestError("网络错误,请检查网络或接口地址是否可访问", { retryable: true })), ontimeout: () => reject(new AiRequestError("请求超时,接口响应太慢", { retryable: true })), onabort: () => reject(new AiRequestError("请求已取消")) }; if (body !== void 0) options.data = JSON.stringify(body); GM_xmlhttpRequest(options); }); } }; var donate_wechat_default = "data:image/webp;base64,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"; var donate_alipay_default = "data:image/webp;base64,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"; function esc(value) { return String(value ?? "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function renderAiConfig() { const currentId = ConfigManager.aiProvider; const provider = getProvider(currentId); const settings = ConfigManager.getAiSettings(currentId); const currentProtocol = settings.protocolId; const providerOptions = getProvidersForProtocol(currentProtocol).map((id) => ``).join(""); return ` `; } function renderToolbar() { const href = window.location.href; const pathname = window.location.pathname; return ` ${href.includes("/login") ? "" : ""} ${pathname === "/user/exam" ? "" : ""} `; } function renderPanel() { const urls = ConfigManager.get("刷课配置.urls", CONFIG.DEFAULT.URLS).split("|").join("\n"); return `
英华刷课助手 自动学习控制台
运行中 v${CONFIG.VERSION}
当前状态 刷课助手准备就绪
0 条记录
${renderToolbar()}
等待任务日志
`; } var styles_default = ".sk-panel,.sk-log-popup,.sk-min-icon{--sk-bg:#111412f5;--sk-surface:#171b18;--sk-surface-raised:#1e231f;--sk-surface-strong:#252b26;--sk-surface-inset:#101310;--sk-border:#dae7dd1c;--sk-border-strong:#dae7dd33;--sk-text:#f2f5f2;--sk-text-dim:#b5beb7;--sk-text-faint:#7d8880;--sk-accent:#79d7a8;--sk-accent-strong:#52bb89;--sk-accent-ink:#092116;--sk-blue:#82b8ed;--sk-blue-soft:#82b8ed1f;--sk-warn:#e9b968;--sk-warn-soft:#e9b9681a;--sk-danger:#ef8b82;--sk-danger-soft:#ef8b821c;--sk-ring:#79d7a847;--sk-shadow:0 24px 72px #00000085, 0 4px 18px #00000052;--sk-font-ui:\"MiSans VF\", \"HarmonyOS Sans SC\", \"PingFang SC\", \"Microsoft YaHei UI\", \"Microsoft YaHei\", sans-serif;--sk-font-mono:\"Maple Mono NF CN\", \"Cascadia Code\", \"JetBrains Mono\", Consolas, monospace;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.sk-log-popup,.sk-log-popup *,.sk-log-popup :before,.sk-log-popup :after,.sk-panel,.sk-panel *,.sk-min-icon,.sk-min-icon *{box-sizing:border-box}.sk-log-popup [hidden]{display:none!important}.sk-panel{z-index:999999;background:var(--sk-bg);border:1px solid var(--sk-border);-webkit-backdrop-filter:blur(16px)saturate(1.15);max-width:min(360px,100vw - 36px);min-height:42px;color:var(--sk-text);font-family:var(--sk-font-ui);letter-spacing:0;pointer-events:none;opacity:1;visibility:visible;border-radius:8px;align-items:center;gap:10px;padding:9px 13px;font-size:13px;font-weight:600;line-height:1.45;transition:opacity .16s,transform .2s,visibility linear;display:flex;position:fixed;top:18px;right:18px;overflow:hidden;transform:translateY(0);box-shadow:0 12px 36px #00000061}.sk-panel:after{content:\"\";border-radius:inherit;box-shadow:inset 3px 0 0 var(--sk-accent);pointer-events:none;position:absolute;top:0;bottom:0;left:0;right:0}.sk-panel-indicator{background:var(--sk-accent);border:2px solid #79d7a847;border-radius:50%;flex:none;width:9px;height:9px;position:relative;box-shadow:0 0 0 4px #79d7a814}.sk-panel-text{overflow-wrap:anywhere;min-width:0}.sk-panel.is-updating{animation:.4s cubic-bezier(.22,1,.36,1) sk-status-update}@keyframes sk-status-update{0%{opacity:.72;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.sk-log-popup{z-index:999998;background:linear-gradient(180deg, #ffffff06, transparent 110px), var(--sk-bg);border:1px solid var(--sk-border-strong);width:478px;max-width:calc(100vw - 32px);height:min(610px,100dvh - 32px);min-height:380px;box-shadow:var(--sk-shadow);-webkit-backdrop-filter:blur(20px)saturate(1.12);color:var(--sk-text);font-family:var(--sk-font-ui);letter-spacing:0;opacity:1;visibility:visible;transform-origin:0 0;border-radius:10px;flex-direction:column;font-size:13px;line-height:1.5;transition:opacity .22s,transform .3s cubic-bezier(.22,1,.36,1),visibility linear,box-shadow .2s;animation:.34s cubic-bezier(.22,1,.36,1) sk-console-enter;display:flex;position:fixed;top:16px;left:16px;overflow:hidden;transform:translateY(0)scale(1)}.sk-log-popup:before{content:\"\";z-index:4;background:linear-gradient(90deg, var(--sk-accent), var(--sk-blue) 62%, var(--sk-warn));opacity:.9;pointer-events:none;height:2px;position:absolute;top:0;left:0;right:0}.sk-log-popup.hidden{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .14s,transform .18s,visibility 0s linear .18s;transform:translateY(10px)scale(.985)}.sk-log-popup.is-dragging{box-shadow:0 30px 90px #0009, 0 0 0 1px var(--sk-ring);-webkit-user-select:none;user-select:none}@keyframes sk-console-enter{0%{opacity:0;transform:translateY(12px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}.sk-log-header{z-index:3;border-bottom:1px solid var(--sk-border);background:#171b18f0;flex:none;position:relative}.sk-log-topbar{cursor:grab;touch-action:none;justify-content:space-between;align-items:center;gap:16px;min-width:0;min-height:62px;padding:12px 13px 9px 14px;display:flex}.sk-log-popup.is-dragging .sk-log-topbar{cursor:grabbing}.sk-log-brand,.sk-log-meta,.sk-brand-copy{align-items:center;min-width:0;display:flex}.sk-log-brand{gap:10px}.sk-brand-mark{background:var(--sk-accent);width:34px;height:34px;color:var(--sk-accent-ink);font-family:var(--sk-font-mono);letter-spacing:0;border:1px solid #ffffff38;border-radius:7px;flex:none;justify-content:center;align-items:center;font-size:12px;font-weight:800;line-height:1;display:inline-flex;box-shadow:0 6px 18px #52bb892e}.sk-brand-copy{flex-direction:column;align-items:flex-start;gap:1px}.sk-log-title,.sk-log-subtitle{white-space:nowrap;text-overflow:ellipsis;max-width:190px;display:block;overflow:hidden}.sk-log-title{color:var(--sk-text);font-size:14px;font-weight:700;line-height:1.35}.sk-log-subtitle{color:var(--sk-text-faint);font-size:11px;font-weight:500}.sk-log-meta{flex:none;justify-content:flex-end;gap:7px}.sk-runtime-badge,.sk-log-version,.sk-section-tag,.sk-log-counter{border:1px solid var(--sk-border);white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;display:inline-flex}.sk-runtime-badge{min-height:24px;color:var(--sk-accent);background:#79d7a814;border-color:#79d7a82e;gap:6px;padding:3px 8px;font-size:10.5px;font-weight:700}.sk-runtime-badge i,.sk-min-icon i{background:currentColor;border-radius:50%;width:6px;height:6px;animation:2.4s ease-in-out infinite sk-live-pulse;display:block;box-shadow:0 0 0 3px #79d7a81a}@keyframes sk-live-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.48;transform:scale(.82)}}.sk-log-version{background:var(--sk-surface-raised);min-height:24px;color:var(--sk-text-faint);font-family:var(--sk-font-mono);padding:3px 8px;font-size:10px;font-weight:600}.sk-log-actions{scrollbar-width:none;gap:4px;min-width:0;padding:0 10px 9px;display:flex;overflow-x:auto}.sk-log-actions::-webkit-scrollbar{display:none}.sk-log-btn{-webkit-appearance:none;appearance:none;background:var(--sk-surface-raised);border:1px solid var(--sk-border);min-width:0;min-height:34px;color:var(--sk-text-dim);font-family:var(--sk-font-ui);letter-spacing:0;text-align:center;white-space:nowrap;cursor:pointer;touch-action:manipulation;border-radius:7px;flex:none;justify-content:center;align-items:center;gap:6px;margin:0;padding:7px 11px;font-size:12px;font-weight:650;line-height:1.2;text-decoration:none;transition:color .16s,background-color .16s,border-color .16s,box-shadow .2s,transform .14s;display:inline-flex;position:relative}.sk-log-btn:hover{background:var(--sk-surface-strong);border-color:var(--sk-border-strong);color:var(--sk-text)}.sk-log-btn:active{transform:translateY(1px)scale(.985)}.sk-log-btn:focus-visible{outline:2px solid var(--sk-accent);outline-offset:2px}.sk-log-popup input:focus-visible{outline:2px solid var(--sk-accent);outline-offset:2px}.sk-log-popup textarea:focus-visible{outline:2px solid var(--sk-accent);outline-offset:2px}.sk-log-popup select:focus-visible{outline:2px solid var(--sk-accent);outline-offset:2px}.sk-min-icon:focus-visible{outline:2px solid var(--sk-accent);outline-offset:2px}.sk-log-btn:disabled{opacity:.58;cursor:wait;transform:none}.sk-log-btn[aria-busy=true]{padding-right:30px}.sk-log-btn[aria-busy=true]:after{content:\"\";border:2px solid;border-right-color:#0000;border-radius:50%;width:11px;height:11px;animation:.7s linear infinite sk-spin;position:absolute;right:10px}@keyframes sk-spin{to{transform:rotate(360deg)}}.sk-tab-btn{min-height:32px;color:var(--sk-text-faint);background:0 0;border-color:#0000;border-radius:6px;padding:6px 13px}.sk-tab-btn:hover{background:#ffffff0b;border-color:#0000}.sk-tab-btn.active{background:var(--sk-surface-strong);border-color:var(--sk-border);box-shadow:inset 0 -2px 0 var(--sk-accent);color:var(--sk-text)}.sk-log-close{width:32px;min-height:32px;color:var(--sk-text-faint);font-family:var(--sk-font-ui);background:0 0;border-color:#0000;border-radius:7px;padding:0;font-size:20px;font-weight:400}.sk-log-close:hover{background:var(--sk-danger-soft);color:var(--sk-danger);border-color:#ef8b822e}.sk-btn-primary,.sk-log-btn.primary{background:var(--sk-accent);border-color:var(--sk-accent);color:var(--sk-accent-ink);font-weight:800;box-shadow:0 6px 18px #52bb892e}.sk-btn-primary:hover,.sk-log-btn.primary:hover{color:var(--sk-accent-ink);background:#8be0b6;border-color:#8be0b6;box-shadow:0 8px 24px #52bb8942}.sk-btn-secondary,.sk-btn-blue{background:var(--sk-blue-soft);color:#a9cff5;border-color:#82b8ed42}.sk-btn-secondary:hover,.sk-btn-blue:hover{color:#d3e8fc;background:#82b8ed2e;border-color:#82b8ed66}.sk-btn-danger{background:var(--sk-danger-soft);color:var(--sk-danger);border-color:#ef8b8240}.sk-btn-danger:hover{color:#ffc0ba;background:#ef8b822e;border-color:#ef8b8266}.sk-tab-content{overscroll-behavior:contain;scrollbar-color:#e2ebe433 transparent;scrollbar-width:thin;flex:auto;min-width:0;min-height:0;display:none;overflow-y:auto}.sk-tab-content.active{flex-direction:column;animation:.24s cubic-bezier(.22,1,.36,1) sk-tab-enter;display:flex}@keyframes sk-tab-enter{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}.sk-tab-content::-webkit-scrollbar{width:8px;height:8px}.sk-log-content::-webkit-scrollbar{width:8px;height:8px}.sk-tab-content::-webkit-scrollbar-track{background:0 0}.sk-log-content::-webkit-scrollbar-track{background:0 0}.sk-tab-content::-webkit-scrollbar-thumb{background:#e2ebe42e padding-box padding-box;border:2px solid #0000;border-radius:6px}.sk-log-content::-webkit-scrollbar-thumb{background:#e2ebe42e padding-box padding-box;border:2px solid #0000;border-radius:6px}.sk-tab-content::-webkit-scrollbar-thumb:hover{background:#e2ebe44d padding-box padding-box}.sk-log-content::-webkit-scrollbar-thumb:hover{background:#e2ebe44d padding-box padding-box}#sk-tab-log{overflow:hidden}.sk-log-overview{background:linear-gradient(135deg, #79d7a80e, transparent 58%), var(--sk-surface);border-bottom:1px solid var(--sk-border);justify-content:space-between;align-items:flex-start;gap:16px;min-width:0;padding:17px 16px 14px;display:flex}.sk-current-state{flex-direction:column;gap:3px;min-width:0;display:flex}.sk-overline{color:var(--sk-text-faint);letter-spacing:0;font-size:10.5px;font-weight:700;line-height:1.35}.sk-current-state strong{color:var(--sk-text);overflow-wrap:anywhere;font-size:14px;font-weight:700;line-height:1.45;display:block}.sk-log-counter{background:var(--sk-surface-raised);min-height:27px;color:var(--sk-text-faint);flex:none;gap:4px;padding:4px 9px;font-size:10.5px}.sk-log-counter strong{color:var(--sk-accent);font-family:var(--sk-font-mono);font-size:11px}.sk-log-toolbar{border-bottom:1px solid var(--sk-border);background:#171b18d1;flex-wrap:wrap;flex:none;gap:7px;padding:9px 12px;display:flex}.sk-log-content{background:repeating-linear-gradient(0deg, transparent 0, transparent 27px, #ffffff05 28px), var(--sk-surface-inset);min-width:0;min-height:180px;font-family:var(--sk-font-mono);flex:auto;padding:9px 12px 15px;font-size:11.5px;line-height:1.55;position:relative;overflow-y:auto}.sk-log-empty{color:var(--sk-text-faint);font-family:var(--sk-font-ui);justify-content:center;align-items:center;font-size:12px;display:flex;position:absolute;top:0;bottom:0;left:0;right:0}.sk-log-empty:before{content:\"\";border:1px solid var(--sk-border-strong);border-radius:50%;width:7px;height:7px;margin-right:8px}.sk-log-item{color:var(--sk-blue);overflow-wrap:anywhere;border-bottom:1px solid #e2ebe40e;padding:6px 7px 6px 22px;animation:.22s cubic-bezier(.22,1,.36,1) sk-log-enter;position:relative}.sk-log-item:last-child{border-bottom-color:#0000}.sk-log-item:before{content:\"\";width:6px;height:6px;box-shadow:0 0 0 3px color-mix(in srgb, currentColor 10%, transparent);opacity:.86;background:currentColor;border:1px solid;border-radius:2px;position:absolute;top:12px;left:8px}.sk-log-item.warn{color:var(--sk-warn)}.sk-log-item.error{color:var(--sk-danger)}@keyframes sk-log-enter{0%{opacity:0;transform:translate(-5px)}to{opacity:1;transform:translate(0)}}.sk-config-banner{background:var(--sk-warn-soft);color:#dfc08d;text-align:center;border-bottom:1px solid #e9b96829;justify-content:center;align-items:center;gap:8px;min-height:39px;padding:9px 14px;font-size:11.5px;font-weight:650;display:flex}.sk-config-banner>span{background:var(--sk-warn);border-radius:50%;flex:none;width:6px;height:6px}.sk-config-nav{z-index:3;border-bottom:1px solid var(--sk-border-strong);-webkit-backdrop-filter:blur(12px);background:#111412f5;grid-template-columns:repeat(4,minmax(0,1fr));gap:5px;padding:8px 10px;display:grid;position:sticky;top:0}.sk-config-nav-btn{width:100%;min-height:34px;color:var(--sk-text-faint);background:0 0;border-color:#0000;padding:6px 5px;font-size:11.5px}.sk-config-nav-btn:hover{background:var(--sk-surface-raised);border-color:#0000}.sk-config-nav-btn.active{background:var(--sk-surface-strong);border-color:var(--sk-border);box-shadow:inset 0 -2px 0 var(--sk-accent);color:var(--sk-text)}.sk-config-section{border-bottom:1px solid var(--sk-border);background:0 0;flex:1 0 auto;min-height:0;padding:16px;display:none}.sk-config-section.active{animation:.22s cubic-bezier(.22,1,.36,1) sk-config-pane-enter;display:block}@keyframes sk-config-pane-enter{0%{opacity:0;transform:translate(5px)}to{opacity:1;transform:translate(0)}}.sk-section-heading{justify-content:space-between;align-items:center;gap:12px;min-width:0;margin-bottom:13px;display:flex}.sk-section-control{min-width:0;color:var(--sk-text-dim);white-space:nowrap;align-items:center;gap:9px;font-size:11.5px;font-weight:650;display:inline-flex}.sk-section-heading h3,.sk-doc h3{color:var(--sk-text);font-family:var(--sk-font-ui);letter-spacing:0;margin:0;font-size:14px;font-weight:750;line-height:1.4}.sk-section-heading h3:before{content:\"\";background:var(--sk-accent);vertical-align:-2px;border-radius:2px;width:3px;height:14px;margin-right:8px;display:inline-block}.sk-section-tag{background:var(--sk-surface-raised);min-height:24px;color:var(--sk-text-faint);padding:3px 8px;font-size:10px;font-weight:650}.sk-config-grid{grid-template-columns:minmax(0,1.3fr) minmax(0,.7fr);gap:12px;display:grid}.sk-config-item{min-width:0}.sk-config-item--wide{grid-column:1/-1}.sk-config-item label,.sk-setting-row>label:first-child{color:var(--sk-text-dim);margin:0 0 6px;font-size:11.5px;font-weight:650;line-height:1.45;display:block}.sk-log-popup input,.sk-log-popup textarea,.sk-log-popup select{-webkit-appearance:none;appearance:none;background:var(--sk-surface-inset);border:1px solid var(--sk-border);width:100%;min-width:0;color:var(--sk-text);font-family:var(--sk-font-ui);letter-spacing:0;border-radius:7px;margin:0;padding:8px 10px;font-size:12.5px;line-height:1.45;transition:border-color .16s,box-shadow .18s,background-color .16s;box-shadow:inset 0 1px #ffffff04}.sk-log-popup input,.sk-log-popup select{min-height:38px}.sk-log-popup select{background-image:linear-gradient(45deg, transparent 50%, var(--sk-text-faint) 50%), linear-gradient(135deg, var(--sk-text-faint) 50%, transparent 50%);cursor:pointer;background-position:calc(100% - 15px) 16px,calc(100% - 10px) 16px;background-repeat:no-repeat;background-size:5px 5px,5px 5px;padding-right:30px}.sk-log-popup textarea{resize:vertical;min-height:96px;font-family:var(--sk-font-mono);font-size:11.5px;line-height:1.65}.sk-log-popup input::placeholder,.sk-log-popup textarea::placeholder{color:#687169;opacity:1}.sk-log-popup input:hover,.sk-log-popup textarea:hover,.sk-log-popup select:hover{border-color:var(--sk-border-strong)}.sk-log-popup input:focus,.sk-log-popup textarea:focus,.sk-log-popup select:focus{border-color:var(--sk-accent-strong);box-shadow:0 0 0 3px var(--sk-ring);background-color:#121713;outline:none}.sk-config-hint,.sk-inline-result{color:var(--sk-text-faint);overflow-wrap:anywhere;margin-top:5px;font-size:10.5px;line-height:1.55;display:block}.sk-inline-result:empty{min-height:0;margin-top:0}.sk-inline-result[data-state=success]{color:var(--sk-accent)}.sk-inline-result[data-state=loading]{color:var(--sk-warn)}.sk-inline-result[data-state=error]{color:var(--sk-danger)}.sk-model-status{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:10px;display:grid}.sk-model-state{background:var(--sk-surface-inset);border:1px solid var(--sk-border);border-radius:7px;flex-direction:column;justify-content:center;gap:4px;min-width:0;min-height:62px;padding:9px 10px;display:flex}.sk-model-state>span{color:var(--sk-text-faint);font-size:10.5px;line-height:1.35}.sk-status-value{min-width:0;color:var(--sk-text-dim);overflow-wrap:anywhere;align-items:center;gap:6px;font-size:11.5px;font-weight:750;line-height:1.4;display:flex}.sk-status-value:before{content:\"\";background:var(--sk-text-faint);border-radius:50%;flex:none;width:6px;height:6px}.sk-status-value[data-state=ready]{color:var(--sk-accent)}.sk-status-value[data-state=ready]:before{background:var(--sk-accent)}.sk-status-value[data-state=missing]{color:var(--sk-danger)}.sk-status-value[data-state=missing]:before{background:var(--sk-danger)}.sk-model-import-actions{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;display:grid}.sk-model-import-actions .sk-log-btn{width:100%;padding-inline-start:7px;padding-inline-end:7px}.sk-model-import-actions #btn-import-wasm{background:var(--sk-blue-soft);color:#b7d7f7;border-color:#82b8ed40}.sk-ai-model-input-row{grid-template-columns:minmax(0,1fr) auto;gap:8px;display:grid}.sk-ai-model-input-row .sk-log-btn{min-width:88px}.sk-ai-model-select{margin-top:8px!important}.sk-config-actions{align-items:center;gap:10px;min-width:0;margin-top:12px;display:flex}.sk-ai-actions .sk-inline-result{flex:auto;min-width:0;margin-top:0}.sk-setting-list{border-top:1px solid var(--sk-border);margin-top:12px}.sk-setting-row{border-bottom:1px solid var(--sk-border);justify-content:space-between;align-items:center;gap:16px;min-height:50px;display:flex}.sk-setting-row>label:first-child{margin:0}.sk-config-reveal{padding-top:12px}#cfg-auto-fill-tip,#cfg-auto-fill-fields,#cfg-auto-fill-fields-pwd{animation:.24s cubic-bezier(.22,1,.36,1) sk-reveal-setting}@keyframes sk-reveal-setting{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.sk-note{background:var(--sk-warn-soft);color:#d6bd92;border:1px solid #e9b96833;border-radius:7px;margin-bottom:12px;padding:10px 11px;font-size:11px;line-height:1.6}.sk-config-actions--sticky{z-index:2;border-top:1px solid var(--sk-border-strong);-webkit-backdrop-filter:blur(12px);background:#111412f0;flex:none;margin-top:0;padding:12px 16px 14px;position:sticky;bottom:0}.sk-save-button{width:100%;min-height:42px}.sk-switch{cursor:pointer;flex:none;width:44px;height:24px;margin:0;display:inline-flex;position:relative}.sk-switch input{z-index:2;opacity:0;cursor:pointer;width:100%;min-height:0;margin:0;padding:0;position:absolute;top:0;bottom:0;left:0;right:0}.sk-slider{background:var(--sk-surface-strong);border:1px solid var(--sk-border-strong);border-radius:999px;transition:background-color .2s,border-color .2s,box-shadow .2s;position:absolute;top:0;bottom:0;left:0;right:0}.sk-slider:before{content:\"\";background:#c9d0ca;border-radius:50%;width:16px;height:16px;transition:transform .24s cubic-bezier(.22,1,.36,1),background-color .18s;position:absolute;top:3px;left:3px;box-shadow:0 2px 5px #00000059}.sk-switch input:checked+.sk-slider{background:var(--sk-accent-strong);border-color:var(--sk-accent-strong)}.sk-switch input:checked+.sk-slider:before{background:#fff;transform:translate(20px)}.sk-switch input:focus-visible+.sk-slider{box-shadow:0 0 0 3px var(--sk-ring)}.sk-doc{color:var(--sk-text-dim);padding:18px 18px 24px;font-size:12px;line-height:1.72}.sk-doc-section{border-bottom:1px solid var(--sk-border);padding:0 0 17px}.sk-doc-section+.sk-doc-section{padding-top:17px}.sk-doc-section:last-child{border-bottom:0;padding-bottom:0}.sk-doc h3{margin-bottom:10px;font-size:14.5px}.sk-doc p{margin:0}.sk-doc ol{margin:0;padding-left:21px}.sk-doc li{margin:0 0 8px;padding-left:3px}.sk-doc li:last-child{margin-bottom:0}.sk-doc li::marker{color:var(--sk-accent);font-family:var(--sk-font-mono);font-weight:700}.sk-doc b{color:var(--sk-text);font-weight:700}.sk-link{color:#9ac9f5;text-underline-offset:3px;font-weight:650;-webkit-text-decoration:underline #9ac9f56b;text-decoration:underline #9ac9f56b;text-decoration-thickness:1px;transition:color .16s,text-decoration-color .16s}.sk-link:hover{color:#c7e2fb;text-decoration-color:currentColor}.sk-link:focus-visible{outline:2px solid var(--sk-blue);outline-offset:2px;border-radius:2px}.sk-link-separator{color:var(--sk-text-faint);margin:0 7px}.sk-doc-center{text-align:center}.sk-support-intro{max-width:330px;margin:2px auto 18px}.sk-support-intro h3{margin:5px 0 4px;font-size:16px}.sk-support-intro p{color:var(--sk-text-faint)}.sk-donate-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;max-width:382px;margin:0 auto;display:grid}.sk-donate-item{background:var(--sk-surface-inset);border:1px solid var(--sk-border);border-radius:8px;margin:0;padding:9px 9px 7px;transition:transform .2s cubic-bezier(.22,1,.36,1),border-color .18s,box-shadow .2s}.sk-donate-item:hover{border-color:var(--sk-border-strong);transform:translateY(-3px);box-shadow:0 12px 24px #0000003d}.sk-donate-item img{aspect-ratio:5/7;object-fit:cover;border-radius:6px;width:100%;height:auto;display:block}.sk-donate-item figcaption{color:var(--sk-text-dim);margin-top:6px;font-size:11.5px;font-weight:700}.sk-min-icon{z-index:999998;-webkit-appearance:none;appearance:none;background:var(--sk-bg);border:1px solid var(--sk-border-strong);-webkit-backdrop-filter:blur(16px)saturate(1.12);width:52px;height:52px;color:var(--sk-accent);cursor:pointer;opacity:1;visibility:visible;border-radius:10px;justify-content:center;align-items:center;margin:0;padding:0;transition:opacity .18s,transform .24s cubic-bezier(.22,1,.36,1),visibility linear,border-color .16s,box-shadow .2s;display:flex;position:fixed;top:18px;left:18px;transform:translateY(0)scale(1);box-shadow:0 14px 36px #0006,inset 0 1px #ffffff0a}.sk-min-icon:hover{border-color:#79d7a873;transform:translateY(-2px)scale(1.03);box-shadow:0 18px 42px #0000007a,0 0 0 3px #79d7a814}.sk-min-icon:active{transform:translateY(0)scale(.96)}.sk-min-icon.hidden{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .12s,transform .16s,visibility 0s linear .16s;transform:translateY(7px)scale(.9)}.sk-min-mark{color:var(--sk-accent);font-family:var(--sk-font-mono);font-size:14px;font-weight:850;line-height:1}.sk-min-label{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.sk-min-icon i{color:var(--sk-accent);position:absolute;bottom:7px;right:7px}@media (max-width:560px){.sk-log-popup{transform-origin:top;border-radius:9px;width:calc(100vw - 16px);max-width:none;height:calc(100dvh - 16px);min-height:0;top:8px;left:8px}.sk-log-topbar{gap:10px;min-height:58px;padding:10px 9px 8px 11px}.sk-brand-mark{width:32px;height:32px}.sk-log-title{max-width:150px;font-size:13px}.sk-log-subtitle,.sk-log-version{display:none}.sk-log-actions{padding:0 7px 8px}.sk-tab-btn{min-height:36px;padding-inline-start:14px;padding-inline-end:14px}.sk-log-overview{padding:14px 13px 12px}.sk-log-toolbar{padding:8px}.sk-config-section{padding:14px 13px}.sk-config-grid{grid-template-columns:minmax(0,1fr)}.sk-config-item--wide{grid-column:auto}.sk-config-actions--sticky{padding:10px 12px 12px}.sk-doc{padding:16px 14px 22px}.sk-panel{max-width:calc(100vw - 20px);top:10px;right:10px}.sk-panel.is-console-open{opacity:0;visibility:hidden;transition:opacity .12s,transform .16s,visibility 0s linear .16s;transform:translateY(-6px)}.sk-min-icon{top:10px;left:10px}}@media (max-width:390px){.sk-log-title{max-width:132px}.sk-model-status{gap:6px}.sk-model-state{min-height:58px;padding:8px 7px}.sk-model-state>span,.sk-status-value{font-size:10px}.sk-ai-model-input-row{grid-template-columns:minmax(0,1fr)}.sk-ai-model-input-row .sk-log-btn{width:100%}.sk-donate-grid{gap:8px}}@media (max-height:500px){.sk-log-popup{min-height:0}.sk-log-subtitle{display:none}.sk-log-topbar{min-height:52px;padding-top:8px;padding-bottom:6px}}@media (pointer:coarse){.sk-log-btn{min-height:44px}.sk-log-close{width:44px}.sk-switch{width:48px;height:28px}.sk-slider:before{width:20px;height:20px}.sk-switch input:checked+.sk-slider:before{transform:translate(20px)}}@media (prefers-reduced-motion:reduce){.sk-log-popup,.sk-log-popup *,.sk-log-popup :before,.sk-log-popup :after,.sk-panel,.sk-min-icon,.sk-min-icon *{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@media (forced-colors:active){.sk-log-popup,.sk-panel,.sk-min-icon,.sk-log-btn,.sk-log-popup input,.sk-log-popup textarea,.sk-log-popup select{border:1px solid canvastext}.sk-brand-mark,.sk-btn-primary,.sk-log-btn.primary{forced-color-adjust:none}}"; var UIManager = class { constructor() { this.panel = null; this.statusText = null; this.logPopup = null; this.logContent = null; this.logCountEl = null; this.consoleStatus = null; this._isDragging = false; this._dragOffset = { x: 0, y: 0 }; this._statusAnimationTimer = null; } init() { this._registerMenus(); this._injectStyles(); this._createStatusPanel(); this._createLogPopup(); Logger.setUIManager(this); Logger.info("UI初始化完成"); } _registerMenus() { GM_registerMenuCommand("显示/隐藏日志", () => this.toggleLogPopup()); } _createStatusPanel() { const div = document.createElement("div"); div.id = "sk-status-panel"; div.className = "sk-panel"; div.setAttribute("role", "status"); div.setAttribute("aria-live", "polite"); div.innerHTML = "刷课助手准备就绪"; document.body.appendChild(div); this.panel = div; this.statusText = div.querySelector(".sk-panel-text"); } _createLogPopup() { const popup = document.createElement("div"); popup.id = "sk-log-popup"; popup.className = "sk-log-popup"; popup.setAttribute("role", "dialog"); popup.setAttribute("aria-label", "英华刷课助手控制台"); popup.setAttribute("aria-hidden", "false"); popup.innerHTML = renderPanel(); document.body.appendChild(popup); this.logPopup = popup; this.logContent = popup.querySelector(".sk-log-content"); this.logCountEl = popup.querySelector("#sk-log-count"); this.consoleStatus = popup.querySelector("#sk-console-status"); const icon = document.createElement("button"); icon.type = "button"; icon.id = "sk-min-icon"; icon.className = "sk-min-icon hidden"; icon.innerHTML = "SK刷课助手"; icon.title = "打开刷课助手"; icon.setAttribute("aria-label", "打开刷课助手控制台"); icon.setAttribute("aria-controls", "sk-log-popup"); icon.setAttribute("aria-hidden", "true"); document.body.appendChild(icon); this.minIcon = icon; this.panel?.classList.add("is-console-open"); popup.querySelector(".sk-log-topbar").addEventListener("pointerdown", (e) => this._onDragStart(e)); document.addEventListener("pointermove", (e) => this._onDragMove(e)); document.addEventListener("pointerup", () => this._onDragEnd()); window.addEventListener("resize", () => this._keepPanelInViewport()); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !this.logPopup?.classList.contains("hidden")) this.toggleLogPopup(); }); popup.querySelector(".sk-log-close").addEventListener("click", () => this.toggleLogPopup()); popup.querySelector(".sk-log-clear").addEventListener("click", () => this.clearLog()); const recaptchaBtn = popup.querySelector(".sk-recaptcha"); if (recaptchaBtn) recaptchaBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("sk:recaptcha_login")); }); const examStartBtn = popup.querySelector(".sk-exam-start"); const examStopBtn = popup.querySelector(".sk-exam-stop"); if (examStartBtn) examStartBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("sk:exam_start")); }); if (examStopBtn) examStopBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("sk:exam_stop")); }); const checkModels = async () => { try { const engine = new LocalOcrEngine(this); const sn = popup.querySelector("#status-onnx"); const sj = popup.querySelector("#status-json"); const sw = popup.querySelector("#status-wasm"); if (sn && sj && sw) { const onnx = await engine._getFromDB("common.onnx"); const json = await engine._getFromDB("charsets.json"); const wasm = await engine._getFromDB("ort-wasm.wasm"); sn.textContent = onnx ? "已就绪" : "未导入"; sn.dataset.state = onnx ? "ready" : "missing"; sj.textContent = json ? "已就绪" : "未导入"; sj.dataset.state = json ? "ready" : "missing"; sw.textContent = wasm ? "已就绪" : "未导入"; sw.dataset.state = wasm ? "ready" : "missing"; } const autoFillTip = popup.querySelector("#cfg-auto-fill-tip"); const autoFillFields = popup.querySelector("#cfg-auto-fill-fields"); const autoFillFieldsPwd = popup.querySelector("#cfg-auto-fill-fields-pwd"); const syncVisibility = () => { const show = popup.querySelector("#cfg-auto-fill-account")?.checked; if (autoFillTip) autoFillTip.style.display = show ? "block" : "none"; if (autoFillFields) autoFillFields.style.display = show ? "block" : "none"; if (autoFillFieldsPwd) autoFillFieldsPwd.style.display = show ? "block" : "none"; }; syncVisibility(); } catch {} }; const autoImportVisualModels = async (force = false) => { if (!force && !ConfigManager.autoImportModels) { const engine = new LocalOcrEngine(this); const [onnx, json, wasm] = await Promise.all([ engine._getFromDB("common.onnx"), engine._getFromDB("charsets.json"), engine._getFromDB("ort-wasm.wasm") ]); if (onnx && json && wasm) return; Logger.warn("自动导入已关闭,检测到模型缺失,请手动导入"); return; } try { const engine = new LocalOcrEngine(this); const [onnx, json, wasm] = await Promise.all([ engine._getFromDB("common.onnx"), engine._getFromDB("charsets.json"), engine._getFromDB("ort-wasm.wasm") ]); const missing = []; if (!onnx) missing.push({ key: "common.onnx", type: "arraybuffer", name: "视觉特征模型(ONNX)" }); if (!json) missing.push({ key: "charsets.json", type: "json", name: "字符集(JSON)" }); if (!wasm) missing.push({ key: "ort-wasm.wasm", type: "arraybuffer", name: "推理引擎(WASM)" }); if (!missing.length) { Logger.info("视觉模型已存在,无需自动导入"); await checkModels(); return; } if (!ConfigManager.autoImportModels) { Logger.info("自动导入视觉模型已关闭,但当前页面仍需要模型,请手动在配置面板中导入"); await checkModels(); return; } this.notify("准备导入", "正在自动导入视觉模型..."); for (const item of missing) { const urls = item.type === "json" ? engine.charsetsUrls : item.key === "ort-wasm.wasm" ? [ "https://registry.npmmirror.com/onnxruntime-web/1.17.0/files/dist/ort-wasm.wasm", "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/ort-wasm.wasm", "https://unpkg.com/onnxruntime-web@1.17.0/dist/ort-wasm.wasm" ] : engine.modelUrls; if (!await engine.fetchWithFallback(urls, item.type, item.key)) throw new Error(`${item.name} 下载失败`); Logger.info(`自动导入成功:${item.name}`); } this.notify("导入成功", "视觉模型自动导入完成!"); } catch (err) { Logger.warn(`自动导入视觉模型失败:${err.message}`); this.notify("自动导入失败", err.message); } finally { await checkModels(); } }; setTimeout(async () => { await checkModels(); await autoImportVisualModels(); }, 500); const bindImport = (btnId, fileId, key, isJson, name) => { const btn = popup.querySelector(btnId); const fileInput = popup.querySelector(fileId); if (btn && fileInput) { btn.addEventListener("click", () => fileInput.click()); fileInput.addEventListener("change", async (e) => { const file = e.target.files[0]; if (!file) return; try { this.notify("准备导入", `正在读取 ${name} 文件...`); let data; if (isJson) { const text = await file.text(); data = JSON.parse(text); } else data = await file.arrayBuffer(); if (await new LocalOcrEngine(this)._saveToDB(key, data)) { this.notify("导入成功", `${name} 导入完成!将会在下次触发验证码时生效。`); Logger.info(`已通过本地文件导入 ${name}`); await checkModels(); } else this.notify("导入失败", "写入本地缓存失败!"); } catch (err) { this.notify("导入失败", "读取文件或格式错误:" + err.message); Logger.error(`导入 ${name} 异常: ` + err.message); } fileInput.value = ""; }); } }; bindImport("#btn-import-onnx", "#file-import-onnx", "common.onnx", false, "视觉特征模型(ONNX)"); bindImport("#btn-import-json", "#file-import-json", "charsets.json", true, "字符集(JSON)"); bindImport("#btn-import-wasm", "#file-import-wasm", "ort-wasm.wasm", false, "推理引擎(WASM)"); const autoImportSwitch = popup.querySelector("#cfg-auto-import"); if (autoImportSwitch) autoImportSwitch.addEventListener("change", () => { ConfigManager.autoImportModels = autoImportSwitch.checked; if (autoImportSwitch.checked) autoImportVisualModels(true); else { this.notify("已关闭", "自动导入视觉模型已关闭,后续不会自动补全缺失模型"); Logger.info("自动导入视觉模型已关闭"); } }); const autoFillSwitch = popup.querySelector("#cfg-auto-fill-account"); const autoFillTip = popup.querySelector("#cfg-auto-fill-tip"); const autoFillFields = popup.querySelector("#cfg-auto-fill-fields"); const autoFillFieldsPwd = popup.querySelector("#cfg-auto-fill-fields-pwd"); const updateAutoFillVisibility = () => { const show = !!autoFillSwitch?.checked; if (autoFillTip) autoFillTip.style.display = show ? "block" : "none"; if (autoFillFields) autoFillFields.style.display = show ? "block" : "none"; if (autoFillFieldsPwd) autoFillFieldsPwd.style.display = show ? "block" : "none"; }; if (autoFillSwitch) { autoFillSwitch.addEventListener("change", updateAutoFillVisibility); updateAutoFillVisibility(); } icon.addEventListener("click", () => { this.toggleLogPopup(); if (!this.logPopup.classList.contains("hidden")) checkModels(); }); const tabs = Array.from(popup.querySelectorAll(".sk-tab-btn")); tabs.forEach((btn, index) => { btn.addEventListener("click", () => this._activateTab(popup, btn)); btn.addEventListener("keydown", (e) => { let nextIndex = null; if (e.key === "ArrowRight") nextIndex = (index + 1) % tabs.length; if (e.key === "ArrowLeft") nextIndex = (index - 1 + tabs.length) % tabs.length; if (e.key === "Home") nextIndex = 0; if (e.key === "End") nextIndex = tabs.length - 1; if (nextIndex === null) return; e.preventDefault(); this._activateTab(popup, tabs[nextIndex], true); }); }); this._bindConfigNavigation(popup); this._bindAiConfig(popup); const saveButton = popup.querySelector("#cfg-save"); saveButton.addEventListener("click", () => { ConfigManager.timeoutMinutes = parseInt(popup.querySelector("#cfg-timeout").value) || 0; ConfigManager.saveAiSettings({ providerId: popup.querySelector("#cfg-ai-provider").value, protocolId: popup.querySelector("#cfg-ai-protocol").value, baseUrl: popup.querySelector("#cfg-ai-baseurl").value.trim(), model: popup.querySelector("#cfg-ai-model").value.trim(), apiKey: popup.querySelector("#cfg-apikey").value.trim() }); ConfigManager.aiRetry = parseInt(popup.querySelector("#cfg-ai-retry").value) || 0; ConfigManager.autoLoginCaptcha = popup.querySelector("#cfg-auto-login").checked; ConfigManager.autoImportModels = popup.querySelector("#cfg-auto-import").checked; ConfigManager.autoFillAccount = popup.querySelector("#cfg-auto-fill-account").checked; ConfigManager.loginAccount = popup.querySelector("#cfg-login-account")?.value || ""; ConfigManager.loginPassword = popup.querySelector("#cfg-login-password")?.value || ""; ConfigManager.urls = popup.querySelector("#cfg-urls").value.split("\n").map((u) => u.trim()).filter((u) => u).join("|"); saveButton.disabled = true; saveButton.setAttribute("aria-busy", "true"); saveButton.textContent = "配置已保存"; this.notify("成功", "配置已保存,刷新页面生效"); setTimeout(() => location.reload(), 1e3); }); } _bindConfigNavigation(popup) { const tabs = Array.from(popup.querySelectorAll(".sk-config-nav-btn")); tabs.forEach((button, index) => { button.addEventListener("click", () => this._activateConfigPane(popup, button)); button.addEventListener("keydown", (e) => { let nextIndex = null; if (e.key === "ArrowRight") nextIndex = (index + 1) % tabs.length; if (e.key === "ArrowLeft") nextIndex = (index - 1 + tabs.length) % tabs.length; if (e.key === "Home") nextIndex = 0; if (e.key === "End") nextIndex = tabs.length - 1; if (nextIndex === null) return; e.preventDefault(); this._activateConfigPane(popup, tabs[nextIndex], true); }); }); } _activateConfigPane(popup, button, focus = false) { const pane = button.dataset.configPane; popup.querySelectorAll(".sk-config-nav-btn").forEach((item) => { const isActive = item === button; item.classList.toggle("active", isActive); item.setAttribute("aria-selected", String(isActive)); item.tabIndex = isActive ? 0 : -1; }); popup.querySelectorAll(".sk-config-pane").forEach((content) => { const isActive = content.id === `sk-config-${pane}`; content.classList.toggle("active", isActive); content.hidden = !isActive; }); const configTab = popup.querySelector("#sk-tab-config"); if (configTab) configTab.scrollTop = 0; if (focus) button.focus(); } _bindAiConfig(popup) { const providerSel = popup.querySelector("#cfg-ai-provider"); const protocolSel = popup.querySelector("#cfg-ai-protocol"); const baseUrlInput = popup.querySelector("#cfg-ai-baseurl"); const baseUrlHint = popup.querySelector("#cfg-ai-baseurl-hint"); const modelInput = popup.querySelector("#cfg-ai-model"); const modelFetchBtn = popup.querySelector("#cfg-ai-fetch-models"); const modelSelect = popup.querySelector("#cfg-ai-model-select"); const modelListResult = popup.querySelector("#cfg-ai-model-list-result"); const modelHint = popup.querySelector("#cfg-ai-model-hint"); const keyLabel = popup.querySelector("#cfg-ai-key-label"); const keyInput = popup.querySelector("#cfg-apikey"); const testBtn = popup.querySelector("#cfg-ai-test"); const testResult = popup.querySelector("#cfg-ai-test-result"); if (!providerSel) return; const drafts = new Map(); let activeProviderId = providerSel.value; const readFields = () => ({ baseUrl: baseUrlInput.value.trim(), apiKey: keyInput.value.trim(), model: modelInput.value.trim() }); const readForm = () => ({ providerId: activeProviderId, protocolId: protocolSel.value, ...readFields() }); const setTestResult = (text, state = "neutral") => { if (!testResult) return; testResult.textContent = text; testResult.dataset.state = state; }; const setModelListResult = (text, state = "neutral") => { if (!modelListResult) return; modelListResult.textContent = text; modelListResult.dataset.state = state; }; const clearModelList = () => { if (modelSelect) { modelSelect.replaceChildren(new Option("请选择模型", "")); modelSelect.hidden = true; } setModelListResult(""); }; const syncProviderMeta = () => { const provider = getProvider(activeProviderId); baseUrlInput.placeholder = provider.baseUrl || "例如 https://你的域名/v1"; modelInput.placeholder = provider.model || "请填写模型名称"; if (modelHint) modelHint.textContent = provider.modelHint || ""; if (keyLabel) keyLabel.textContent = `${provider.keyLabel || "API Key"}:`; if (baseUrlHint) baseUrlHint.textContent = getBaseUrlHint(activeProviderId, protocolSel.value); setTestResult(""); }; const loadProviderFields = (id) => { const settings = drafts.get(id) || ConfigManager.getAiSettings(id); baseUrlInput.value = settings.baseUrl; modelInput.value = settings.model; keyInput.value = settings.apiKey; syncProviderMeta(); clearModelList(); }; const renderProviderOptions = (protocolId, preferredId) => { const providerIds = getProvidersForProtocol(protocolId); providerSel.replaceChildren(...providerIds.map((id) => new Option(PROVIDERS[id].label, id))); const nextId = providerIds.includes(preferredId) ? preferredId : getDefaultProviderForProtocol(protocolId); providerSel.value = nextId; return nextId; }; providerSel.addEventListener("change", () => { drafts.set(activeProviderId, readFields()); activeProviderId = providerSel.value; loadProviderFields(activeProviderId); }); protocolSel.addEventListener("change", () => { drafts.set(activeProviderId, readFields()); const nextProviderId = renderProviderOptions(protocolSel.value, activeProviderId); if (nextProviderId !== activeProviderId) { activeProviderId = nextProviderId; loadProviderFields(activeProviderId); } else { syncProviderMeta(); clearModelList(); } }); baseUrlInput.addEventListener("input", clearModelList); keyInput.addEventListener("input", clearModelList); modelInput.addEventListener("input", () => { if (!modelSelect || modelSelect.hidden) return; const hasValue = Array.from(modelSelect.options).some((option) => option.value === modelInput.value.trim()); modelSelect.value = hasValue ? modelInput.value.trim() : ""; }); modelSelect?.addEventListener("change", () => { if (!modelSelect.value) return; modelInput.value = modelSelect.value; setModelListResult(`已选择:${modelSelect.value}`, "success"); }); modelFetchBtn?.addEventListener("click", async () => { const client = new AiClient(readForm()); const invalid = client.validateModelList(); if (invalid) { setModelListResult(invalid, "error"); return; } modelFetchBtn.disabled = true; modelFetchBtn.setAttribute("aria-busy", "true"); setModelListResult("正在获取模型列表...", "loading"); try { const models = await client.listModels(); const firstOption = new Option(`请选择模型(共 ${models.length} 个)`, ""); const options = models.map((model) => new Option(model, model)); modelSelect.replaceChildren(firstOption, ...options); const currentModel = modelInput.value.trim() || client.resolve().model; modelSelect.value = models.includes(currentModel) ? currentModel : ""; modelSelect.hidden = false; setModelListResult(`已获取 ${models.length} 个模型`, "success"); Logger.info(`已从 ${getProvider(activeProviderId).label} 获取 ${models.length} 个模型`); } catch (err) { clearModelList(); setModelListResult(err.message, "error"); Logger.error("获取模型列表失败:" + err.message); } finally { modelFetchBtn.disabled = false; modelFetchBtn.removeAttribute("aria-busy"); } }); if (testBtn) testBtn.addEventListener("click", async () => { const client = new AiClient(readForm()); const invalid = client.validate(); if (invalid) { setTestResult(invalid, "error"); return; } testBtn.disabled = true; testBtn.setAttribute("aria-busy", "true"); setTestResult("测试中...", "loading"); try { const reply = await client.ask("1+1等于几?只回答数字。", "你是一个测试助手,简短回答。"); setTestResult(`连通正常,返回:${reply.slice(0, 20)}`, "success"); Logger.info(`搜题接口测试成功,返回:${reply.slice(0, 50)}`); } catch (err) { setTestResult(err.message, "error"); Logger.error("搜题接口测试失败:" + err.message); } finally { testBtn.disabled = false; testBtn.removeAttribute("aria-busy"); } }); } _activateTab(popup, button, focus = false) { const tab = button.dataset.tab; popup.querySelectorAll(".sk-tab-btn").forEach((item) => { const isActive = item === button; item.classList.toggle("active", isActive); item.setAttribute("aria-selected", String(isActive)); item.tabIndex = isActive ? 0 : -1; }); popup.querySelectorAll(".sk-tab-content").forEach((content) => { const isActive = content.id === `sk-tab-${tab}`; content.classList.toggle("active", isActive); content.hidden = !isActive; }); if (focus) button.focus(); } updateToolbar() { const toolbar = this.logPopup?.querySelector(".sk-log-toolbar"); if (!toolbar) return; toolbar.innerHTML = renderToolbar(); const clearBtn = toolbar.querySelector(".sk-log-clear"); if (clearBtn) clearBtn.onclick = () => this.clearLog(); const recaptchaBtn = toolbar.querySelector(".sk-recaptcha"); if (recaptchaBtn) recaptchaBtn.onclick = () => document.dispatchEvent(new CustomEvent("sk:recaptcha_login")); const examStartBtn = toolbar.querySelector(".sk-exam-start"); const examStopBtn = toolbar.querySelector(".sk-exam-stop"); if (examStartBtn) examStartBtn.onclick = () => document.dispatchEvent(new CustomEvent("sk:exam_start")); if (examStopBtn) examStopBtn.onclick = () => document.dispatchEvent(new CustomEvent("sk:exam_stop")); } _onDragStart(e) { if (e.button !== 0 || e.target.closest("button, a, input, select, textarea, label")) return; this._isDragging = true; const rect = this.logPopup.getBoundingClientRect(); this._dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top }; this.logPopup.classList.add("is-dragging"); e.preventDefault(); } _onDragMove(e) { if (!this._isDragging) return; const rect = this.logPopup.getBoundingClientRect(); const maxX = Math.max(0, window.innerWidth - rect.width); const maxY = Math.max(0, window.innerHeight - Math.min(rect.height, window.innerHeight)); const x = Math.min(maxX, Math.max(0, e.clientX - this._dragOffset.x)); const y = Math.min(maxY, Math.max(0, e.clientY - this._dragOffset.y)); this.logPopup.style.left = `${x}px`; this.logPopup.style.top = `${y}px`; this.logPopup.style.right = "auto"; this.logPopup.style.bottom = "auto"; } _onDragEnd() { this._isDragging = false; this.logPopup?.classList.remove("is-dragging"); } _keepPanelInViewport() { if (!this.logPopup || this.logPopup.classList.contains("hidden")) return; const rect = this.logPopup.getBoundingClientRect(); const maxX = Math.max(0, window.innerWidth - rect.width); const maxY = Math.max(0, window.innerHeight - Math.min(rect.height, window.innerHeight)); this.logPopup.style.left = `${Math.min(maxX, Math.max(0, rect.left))}px`; this.logPopup.style.top = `${Math.min(maxY, Math.max(0, rect.top))}px`; } _injectStyles() { GM_addStyle(styles_default); } addLog(msg, level = "info") { if (!this.logContent) return; this.logContent.querySelector(".sk-log-empty")?.remove(); const item = document.createElement("div"); item.className = `sk-log-item ${level}`; item.textContent = msg; this.logContent.appendChild(item); this._updateLogCount(); this.logContent.scrollTop = this.logContent.scrollHeight; } clearLog() { if (this.logContent) { const empty = document.createElement("div"); empty.className = "sk-log-empty"; empty.textContent = "等待任务日志"; this.logContent.replaceChildren(empty); this._updateLogCount(); } } _updateLogCount() { if (!this.logCountEl || !this.logContent) return; this.logCountEl.textContent = String(this.logContent.querySelectorAll(".sk-log-item").length); } toggleLogPopup() { if (this.logPopup) { const isHidden = this.logPopup.classList.toggle("hidden"); this.logPopup.setAttribute("aria-hidden", String(isHidden)); this.logPopup.inert = isHidden; this.panel?.classList.toggle("is-console-open", !isHidden); if (this.minIcon) { this.minIcon.classList.toggle("hidden", !isHidden); this.minIcon.setAttribute("aria-hidden", String(!isHidden)); if (isHidden) this.minIcon.focus(); else requestAnimationFrame(() => this.logPopup.querySelector(".sk-log-close")?.focus()); } } } updateStatus(text) { if (this.panel) { if (this.statusText) this.statusText.textContent = text; if (this.consoleStatus) this.consoleStatus.textContent = text; this.panel.style.display = "block"; this.panel.classList.remove("is-updating"); this.panel.offsetWidth; this.panel.classList.add("is-updating"); clearTimeout(this._statusAnimationTimer); this._statusAnimationTimer = setTimeout(() => this.panel?.classList.remove("is-updating"), 420); } } notify(title, text) { GM_notification({ title, text, timeout: 3e3 }); } }; var VideoPlayer = class { constructor(ui) { this.ui = ui; this.video = null; this.stuckTimer = null; this.lastTime = 0; this.stuckCount = 0; } async start() { this.video = document.querySelector(SELECTORS.VIDEO); if (!this.video) return false; this._setupVideo(); this._startStuckCheck(); try { await this.video.play(); this.ui.updateStatus("视频播放中..."); Logger.info("视频开始播放"); } catch (e) { Logger.error("播放失败: " + e.message); } return true; } _setupVideo() { this.video.muted = true; this.video.playbackRate = 1; this.video.onended = () => this._onEnded(); } _onEnded() { Logger.info("当前视频播放结束"); this.ui.updateStatus("视频播放结束,准备切换..."); this._stopStuckCheck(); document.dispatchEvent(new CustomEvent("sk:video_ended")); } _startStuckCheck() { this._stopStuckCheck(); this.lastTime = this.video.currentTime; this.stuckCount = 0; this.stuckTimer = setInterval(() => { if (!this.video) return; const currentTime = this.video.currentTime; if (Math.abs(currentTime - this.lastTime) < CONFIG.THRESHOLDS.VIDEO_STUCK_MIN_DIFF && !this.video.paused) { this.stuckCount++; this.ui.updateStatus(`视频卡顿检测 (${this.stuckCount})`); if (this.stuckCount > 3) { this.ui.notify("警告", "视频卡死,尝试刷新页面"); setTimeout(() => location.reload(), 2e3); } } else this.stuckCount = 0; this.lastTime = currentTime; }, CONFIG.THRESHOLDS.VIDEO_STUCK_CHECK_INTERVAL); } _stopStuckCheck() { if (this.stuckTimer) { clearInterval(this.stuckTimer); this.stuckTimer = null; } } }; var CaptchaHandler = class { constructor(ui) { this.ui = ui; this.observer = null; this.isRecognizing = false; this.isLoginRecognizing = false; this.ocrEngine = new LocalOcrEngine(ui); } startObserver() { if (this.observer) return; this.observer = new MutationObserver((mutations) => { for (const mutation of mutations) for (const node of mutation.addedNodes) { if (node.nodeType !== 1) continue; const layer = node.matches(SELECTORS.CAPTCHA_LAYER) ? node : node.querySelector(SELECTORS.CAPTCHA_LAYER); if (layer && layer.querySelector("img") && layer.querySelector("input")) { Logger.info("MutationObserver 检测到验证码弹窗"); this.checkAndSolve(); } } }); this.observer.observe(document.body, { childList: true, subtree: true }); Logger.info("已启动 MutationObserver 监听验证码"); } async checkAndSolve() { if (this.isRecognizing) return false; const layer = document.querySelector(SELECTORS.CAPTCHA_LAYER); if (!layer || layer.offsetParent === null || !layer.querySelector("img") || !layer.querySelector("input")) return false; this.isRecognizing = true; try { this.ui.updateStatus("检测到验证码,准备识别..."); await Utils.pause(2); const imgs = document.querySelectorAll(SELECTORS.CAPTCHA_IMG); let img = null; if (imgs.length > 0) img = imgs[0].style.opacity === "0" && imgs.length > 1 ? imgs[1] : imgs[0]; if (!img) { this.isRecognizing = false; return false; } const code = await this.ocrEngine.recognize(img); if (code) { this.ui.updateStatus(`识别结果: ${code},正在提交...`); Logger.info(`验证码识别结果: ${code}`); const inputs = document.querySelectorAll(SELECTORS.CAPTCHA_INPUT); let input = null; if (inputs.length > 0) input = inputs[0].style.display === "none" && inputs.length > 1 ? inputs[1] : inputs[0]; if (input) { input.dispatchEvent(new MouseEvent("mousedown")); input.value = code; input.dispatchEvent(new Event("input")); await Utils.pause(1); const btn = document.querySelector(SELECTORS.PLAY_BUTTON); if (btn) { btn.click(); this.ui.updateStatus("验证码已提交,等待系统确认..."); for (let i = 0; i < 8; i++) { await Utils.pause(.5); const checkLayer = document.querySelector(SELECTORS.CAPTCHA_LAYER); if (!checkLayer || checkLayer.offsetParent === null) break; } } this.ui.updateStatus("视频播放中..."); this.isRecognizing = false; return true; } } this.isRecognizing = false; return false; } catch { this.isRecognizing = false; return false; } } async checkAndSolveLogin() { if (!ConfigManager.autoLoginCaptcha || this.isLoginRecognizing) return; this.isLoginRecognizing = true; this.ui.updateStatus("正在等待登录验证码加载..."); const getElements = () => { return { img: document.querySelector("#codeImg") || document.querySelector(".captcha-image img"), input: document.querySelector("#code") || document.querySelector("input[placeholder=\"请输入验证码\"]") }; }; let { img, input } = getElements(); let attempts = 0; while ((!img || !input) && attempts < 20) { await Utils.pause(.5); ({img, input} = getElements()); attempts++; } if (img && input) { this.ui.updateStatus("检测到登录验证码,准备识别..."); await Utils.pause(1); const code = await this.ocrEngine.recognize(img); if (!window.location.href.includes("/login")) { this.isLoginRecognizing = false; return; } if (code) { this.ui.updateStatus(`登录验证码识别结果: ${code}`); Logger.info(`登录验证码识别结果: ${code}`); input.value = code; input.dispatchEvent(new Event("input")); } } else this.ui.updateStatus("未检测到登录验证码"); this.isLoginRecognizing = false; } async solveLoginCaptcha() { Logger.info("手动触发重新识别登录验证码"); const img = document.querySelector("#codeImg") || document.querySelector(".captcha-image img"); const input = document.querySelector("#code") || document.querySelector("input[placeholder=\"请输入验证码\"]"); if (!img || !input) { this.ui.updateStatus("未检测到登录验证码元素"); return; } img.click(); await Utils.pause(1.5); this.ui.updateStatus("正在重新识别验证码..."); const code = await this.ocrEngine.recognize(img); if (code) { this.ui.updateStatus(`登录验证码识别结果: ${code}`); Logger.info(`登录验证码识别结果: ${code}`); input.value = code; input.dispatchEvent(new Event("input")); } else { this.ui.updateStatus("验证码识别失败"); Logger.warn("手动重新识别验证码失败"); } } }; var SUPPORTED_TYPES = [ 1, 2, 3 ]; var SYSTEM_PROMPT = "你是考试答题助手。只输出选项字母,多选连写(如 ABD),不要输出理由、标点或任何多余文字。"; function extractChoiceAnswer(rawAnswer) { const text = String(rawAnswer || "").trim().toUpperCase(); if (!text) return ""; const direct = /^[A-H\s,,、/和及]+$/.test(text) ? text : ""; const labelled = text.match(/(?:答案|ANSWER)\s*(?:是|为|[::])?\s*([A-H](?:[\s,,、/和及]*[A-H])*)/)?.[1]; const isolated = Array.from(text.matchAll(/(?:^|[^A-Z])([A-H])(?=$|[^A-Z])/g), (match) => match[1]).join(""); const letters = (direct || labelled || isolated).match(/[A-H]/g) || []; return [...new Set(letters)].join(""); } var ExamSolver = class { constructor(ui) { this.ui = ui; this.running = false; } async start() { if (this.running) return; const client = new AiClient(ConfigManager.aiSettings); const invalid = client.validate(); if (invalid) { this.ui.updateStatus("搜题配置不完整"); Logger.error(invalid); this.ui.notify("无法开始搜题", invalid); return; } this.client = client; this.running = true; const { provider, model } = client.resolve(); this.ui.updateStatus("开始搜题..."); Logger.info(`开始搜题,使用 ${provider.label} · ${model}`); await this._solveLoop(); } stop() { this.running = false; this.ui.updateStatus("搜题已停止"); Logger.info("检测到手动停止,等待本题搜索完毕"); } async _solveLoop() { const introList = document.querySelectorAll(SELECTORS.EXAM_INTRO_LIST); let currentIndex = this._getCurrentIndex(); for (; currentIndex < introList.length; currentIndex++) { if (!this.running) break; const tab = document.querySelector(SELECTORS.TOPIC_TAB_PREFIX + currentIndex); if (!tab) continue; this.ui.updateStatus(`正在搜索第 ${currentIndex + 1} 题...`); Logger.info(`正在搜索第 ${currentIndex + 1} 题...`); await Utils.pause(1); const type = parseInt(tab.querySelector(SELECTORS.EXAM_MAIN)?.dataset.type); if (SUPPORTED_TYPES.includes(type)) await this._handleQuestion(tab, type, currentIndex); else this.ui.updateStatus("未知题型,跳过..."); await Utils.pause(3); this._nextQuestion(tab, currentIndex); } this.running = false; this.ui.updateStatus("搜题结束"); Logger.info("搜题结束"); } _getCurrentIndex() { const onHead = document.querySelector(SELECTORS.TOPIC_HEAD + ".on"); return onHead ? parseInt(onHead.textContent) - 1 : 0; } async _handleQuestion(tab, type, index) { if (type === 2) tab.querySelectorAll("label input").forEach((input) => input.checked = false); try { const question = tab.querySelector(SELECTORS.EXAM_MAIN).innerText.replace(/\n\.\n/g, "."); const answer = await this._getAnswer(question); if (answer) { const cleanAnswer = extractChoiceAnswer(answer); if (!cleanAnswer) throw new Error(`无法从 AI 返回中识别选项:${answer.slice(0, 50)}`); this.ui.updateStatus(`第 ${index + 1} 题答案: ${cleanAnswer}`); Logger.info(`第 ${index + 1} 题答案: ${cleanAnswer}`); const answerList = cleanAnswer.split(""); tab.querySelectorAll("label").forEach((label) => { if (answerList.some((item) => label.innerText.includes(item))) { const input = label.querySelector("input"); if (input) input.checked = true; } }); } } catch (e) { Logger.error("搜题出错: " + e.message); this.ui.updateStatus("搜题出错,跳过..."); } } async _getAnswer(question) { const maxRetry = ConfigManager.aiRetry; let lastError; for (let attempt = 0; attempt <= maxRetry; attempt++) { if (!this.running) break; try { return await this.client.ask(question, SYSTEM_PROMPT); } catch (e) { lastError = e; if (attempt < maxRetry && e?.retryable === true) { Logger.warn(`请求失败(${e.message}),准备第 ${attempt + 1} 次重试`); await Utils.pause(2); } else break; } } throw lastError || new Error("请求失败"); } _nextQuestion(tab, index) { const saveBtn = tab.querySelector("input[value='保存修改']"); if (saveBtn && saveBtn.offsetParent !== null) { saveBtn.click(); const nextLink = document.querySelectorAll(SELECTORS.EXAM_INTRO_LIST)[index + 1]?.querySelector("a"); if (nextLink) nextLink.click(); } else { const nextBtn = tab.querySelector("input[value='继续下一题']"); if (nextBtn) nextBtn.click(); } } }; var App = class { constructor() { this.ui = new UIManager(); this.videoPlayer = new VideoPlayer(this.ui); this.captchaHandler = new CaptchaHandler(this.ui); this.examSolver = new ExamSolver(this.ui); this.checkTimer = null; this.isVideoMode = false; } init() { if (!this._checkFirstUse()) return; ConfigManager.migrate(); this._checkCourseChange(); this.ui.init(); this._initRouteListener(); Logger.info(`安装过老版本的需要把老版本删除或者禁用。`); Logger.info(`如果一直放同一个视频,就把页面停留最大时间设置成0`); Logger.info(`刷课助手 v${CONFIG.VERSION} 启动`); this._setupPageTimeout(); this._initVisibilityListener(); this.captchaHandler.startObserver(); this._checkUrl(); } _checkCourseChange() { const href = window.location.href; const match = href.match(/courseId=([^&]+)/) || href.match(/id=([^&]+)/) || href.match(/course\/(\d+)/); if (match) { const currentCourseId = match[1]; const lastCourseId = GM_getValue("sk_last_course_id"); if (lastCourseId && lastCourseId !== currentCourseId) { Logger.info("检测到切换课程,重置刷课状态与黑名单记录"); ConfigManager.videoStage = 1; GM_setValue("sk_blocked_chapters", []); } GM_setValue("sk_last_course_id", currentCourseId); } } _initVisibilityListener() { document.addEventListener("visibilitychange", () => { if (!this.isVideoMode) return; if (document.hidden) { Logger.warn("检测到页面被隐藏 (最小化或切换标签)"); Logger.warn("页面隐藏可能无法增加学时,若学时会增加可忽略"); } else Logger.info("页面恢复可见"); }); window.addEventListener("blur", () => { if (!this.isVideoMode) return; Logger.warn("检测到窗口失去焦点 (切屏)"); }); window.addEventListener("focus", () => { if (!this.isVideoMode) return; Logger.info("窗口重新获得焦点"); }); } _checkFirstUse() { if (GM_getValue("firstUse", true)) { if (prompt("英华学堂全系列刷课助手|自动刷课|考试自动答题(原为成都文理学院刷课助手)\n若要使用,请阅读并同意以下免责条款。\n\n1. 此脚本仅用于学习研究,您必须在下载后24小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。\n2. 请勿将此脚本用于任何商业或非法目的,若违反规定请自行对此负责。\n3. 本人对此脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害(如封禁、限制等)。\n4. 若有信息功能侵犯到您的权益,请及时联系作者。\n5. 任何以任何方式查看此脚本的人或直接或间接使用此脚本的使用者都应仔细阅读此条款。\n6. 本人保留随时更改或补充此条款的权利,一旦您使用或复制或修改了此脚本,即视为您已接受此免责条款。\n\n若您同意以上内容,请输入“我已阅读并同意以上内容” 然后开始使用。", "") === "我已阅读并同意以上内容") { GM_setValue("firstUse", false); return true; } else { alert("免责条款未同意,脚本停止运行。\n若不想使用,请自行禁用脚本,以免每个页面都弹出该提示。"); return false; } } return true; } async _handleErrorPage() { const errorMsg = document.querySelector("#error-main .name")?.innerText || document.querySelector(".name")?.innerText || ""; if (/未到|未开放|尚未开放|锁定|未开启/.test(errorMsg)) { const currentChapter = GM_getValue("sk_current_brushing_chapter"); if (currentChapter) { Logger.warn(`检测到错误页提示: ${errorMsg},已将 [${currentChapter}] 加入临时黑名单`); const blocked = GM_getValue("sk_blocked_chapters", []); if (!blocked.includes(currentChapter)) { blocked.push(currentChapter); GM_setValue("sk_blocked_chapters", blocked); } } this.ui.updateStatus("检测到章节锁定,正在返回..."); await Utils.pause(2); this._navigateToStudyRecord(); } else { Logger.warn(`进入未知错误页: ${errorMsg},5秒后尝试返回记录页检查进度...`); this.ui.updateStatus("遇到错误,尝试返回..."); await Utils.pause(5); this._navigateToStudyRecord(); } } _navigateToStudyRecord() { const href = window.location.href; let courseId = ""; const match = href.match(/courseId=([^&]+)/) || href.match(/id=([^&]+)/) || href.match(/course\/(\d+)/); if (match) courseId = match[1]; if (!courseId) { const input = document.querySelector("input[name=\"courseId\"]"); if (input) courseId = input.value; } if (!courseId) { const backLink = document.querySelector("#back")?.href || ""; const backMatch = backLink.match(/courseId=([^&]+)/) || backLink.match(/id=([^&]+)/); if (backMatch) courseId = backMatch[1]; } if (!courseId && typeof data !== "undefined" && data.back) { const dataMatch = data.back.match(/courseId=([^&]+)/) || data.back.match(/id=([^&]+)/); if (dataMatch) courseId = dataMatch[1]; } if (!courseId) { courseId = GM_getValue("sk_last_course_id"); if (courseId) Logger.info("URL 丢失课程号,使用缓存记录的 ID: " + courseId); } if (courseId) { ConfigManager.videoStage = 2; GM_setValue("sk_video_stage", 2); const targetPath = href.includes("/student/") ? "/student/course-study-record" : "/user/study_record"; location.href = `${targetPath}?id=${courseId}&courseId=${courseId}`; } else { Logger.error("无法获取课程号,尝试返回首页"); location.href = href.includes("/student/") ? "/student/home" : "/user/index"; } } _checkUrl() { const href = window.location.href; const pathname = window.location.pathname; const savedStage = GM_getValue("sk_video_stage"); if (savedStage) ConfigManager.videoStage = savedStage; if (document.getElementById("error-main") || document.querySelector(".errormain")) { this._handleErrorPage(); return; } if (href.includes("/node") || pathname === "/student/course-study") this._startVideoMode(); else if (pathname === "/user/exam") this._startExamMode(); else if (href.includes("/login")) { this._autoFillLoginFields(); this.captchaHandler.checkAndSolveLogin(); document.addEventListener("sk:recaptcha_login", () => { this.captchaHandler.solveLoginCaptcha(); }); } else if (href.includes("/user/study_record") || href.includes("course-study-record")) this._checkStudyRecord(); else if (pathname === "/student/home") { this.ui.updateStatus("已进入个人首页"); this.ui.clearLog(); Logger.info("成功检测到路由跳转:已进入个人首页"); } else this.ui.updateStatus("请进入课程或考试页面"); } _autoFillLoginFields() { if (!ConfigManager.autoFillAccount) return; if (this._loginFillTimer) return; const isHaiqi = window.location.href.includes("haiqikeji"); const fill = (selector, value) => { const el = document.querySelector(selector); if (!el || !value) return false; el.focus(); const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set; if (setter) setter.call(el, value); else el.value = value; el.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true, data: value, inputType: "insertText" })); el.dispatchEvent(new Event("change", { bubbles: true, composed: true })); el.dispatchEvent(new Event("blur", { bubbles: true, composed: true })); return true; }; const tryFill = () => { if (!ConfigManager.autoFillAccount) return true; if (isHaiqi) { const inputs = document.querySelectorAll("input.el-input__inner"); const userInput = Array.from(inputs).find((el) => el.type === "text" || (el.placeholder || "").includes("学号")); const passInput = Array.from(inputs).find((el) => el.type === "password" || (el.placeholder || "").includes("密码")); const ok1 = userInput && ConfigManager.loginAccount ? fill("input.el-input__inner[type=\"text\"]", ConfigManager.loginAccount) || fill("input[placeholder=\"请输入学号\"]", ConfigManager.loginAccount) : false; const ok2 = passInput && ConfigManager.loginPassword ? fill("input.el-input__inner[type=\"password\"]", ConfigManager.loginPassword) || fill("input[placeholder=\"请输入密码\"]", ConfigManager.loginPassword) : false; return !!(ok1 && ok2); } const ok1 = !!fill("#username", ConfigManager.loginAccount); const ok2 = !!fill("#password", ConfigManager.loginPassword); return ok1 && ok2; }; let attempts = 0; const maxAttempts = 40; const interval = setInterval(() => { attempts++; if (tryFill() || attempts >= maxAttempts) { clearInterval(interval); this._loginFillTimer = null; } }, 250); this._loginFillTimer = interval; tryFill(); } _initRouteListener() { const self = this; const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function() { originalPushState.apply(this, arguments); self._handleRouteChange(); }; history.replaceState = function() { originalReplaceState.apply(this, arguments); self._handleRouteChange(); }; window.addEventListener("popstate", () => this._handleRouteChange()); } _handleRouteChange() { this.ui.updateToolbar(); this._checkUrl(); } _getPageInfo() { const totalEl = document.querySelector(".pagebar-bbs .total") || document.querySelector(".total"); if (!totalEl) return null; const text = totalEl.innerText; const match = text.match(/页次:\s*(\d+)\s*\/\s*(\d+)/) || text.match(/(\d+)\s*\/\s*(\d+)/); if (match) return { current: parseInt(match[1]), total: parseInt(match[2]) }; return null; } async _checkStudyRecord() { if (ConfigManager.videoStage !== 2) return; GM_deleteValue("sk_current_brushing_chapter"); this.ui.updateStatus("正在检查学习进度..."); Logger.info("核对学习进度"); const videoTab = Array.from(document.querySelectorAll(".tab")).find((t) => t.innerText.includes("视频记录")); if (videoTab && !videoTab.classList.contains("active")) { Logger.info("正在切换至视频记录标签..."); videoTab.click(); await Utils.pause(2); } await Utils.pause(1); let progressEl = null; for (let i = 0; i < 5; i++) { progressEl = document.querySelector(".score-table td") || document.querySelector(".progress-text"); if (progressEl) break; await Utils.pause(1); } if (progressEl && progressEl.innerText.includes("100%")) { Logger.info("学习进度已达 100%,刷课完成!"); this.ui.notify("完成", "学习进度 100%,所有课程已完成!"); this.ui.updateStatus("刷课完成"); ConfigManager.videoStage = 1; GM_setValue("sk_video_stage", 1); this.isVideoMode = false; return; } await Utils.pause(1); let rows = []; for (let i = 0; i < 5; i++) { rows = document.querySelectorAll("table#list tbody tr, .video-table tbody tr"); if (rows.length > 0) break; Logger.info(`正在等待记录列表加载... (${i + 1}/5)`); await Utils.pause(1); } let foundUnfinished = false; for (const row of rows) { const status = row.querySelector("td:last-child span, .col-status span, td:nth-last-child(2) span")?.innerText; const blockedChapters = GM_getValue("sk_blocked_chapters", []); if (status && !status.includes("已学") && !status.includes("已完成") && !/未开放|尚未开放|锁定|未开启/.test(status)) { const link = row.querySelector("td:first-child a"); const chapterTitle = (row.querySelector("td:first-child") || row.querySelector(".video-link"))?.innerText?.trim(); if (blockedChapters.includes(chapterTitle)) { Logger.warn(`章节 ${chapterTitle} 在黑名单中(未开放),跳过...`); continue; } if (link) { Logger.info(`发现未学课程: ${chapterTitle},准备跳转...`); this.ui.updateStatus(`补刷: ${chapterTitle}`); foundUnfinished = true; GM_setValue("sk_current_brushing_chapter", chapterTitle); GM_setValue("sk_video_stage", 1); await Utils.pause(1); link.click(); return; } else if (chapterTitle) { Logger.info(`发现未学课程: ${chapterTitle},准备回跳学习页...`); this.ui.updateStatus(`准备补刷: ${chapterTitle}`); GM_setValue("sk_target_chapter", chapterTitle); GM_setValue("sk_current_brushing_chapter", chapterTitle); GM_setValue("sk_video_stage", 1); foundUnfinished = true; await Utils.pause(2); this._navigateToStudyPage(); return; } } } if (!foundUnfinished) { const nextBtn = document.querySelector(".page-btn.next, .btn-next"); const pageInfo = this._getPageInfo(); if ((pageInfo ? pageInfo.current < pageInfo.total : nextBtn && !nextBtn.classList.contains("disabled") && !nextBtn.hasAttribute("disabled")) && nextBtn) { Logger.info("当前页已完成,进入下一页..."); this.ui.updateStatus("翻页检查中..."); await Utils.pause(1); nextBtn.click(); setTimeout(() => this._checkStudyRecord(), 3e3); } else { Logger.info("所有页面检查完毕,未发现可刷课程或章节已锁定。"); this.ui.notify("完成", "学习进度核对结束"); this.ui.updateStatus("检查结束"); ConfigManager.videoStage = 1; GM_setValue("sk_video_stage", 1); this.isVideoMode = false; } } } _navigateToStudyPage() { const href = window.location.href; let courseId = ""; const match = href.match(/id=([^&]+)/) || href.match(/courseId=([^&]+)/); if (match) courseId = match[1]; if (courseId) { const targetPath = href.includes("/student/") ? "/student/course-study" : "/node/index"; location.href = `${targetPath}?id=${courseId}&courseId=${courseId}`; } else { Logger.error("无法返回学习页,ID 丢失"); location.href = "/student/home"; } } async _startVideoMode() { this.isVideoMode = true; this.ui.updateStatus("进入视频刷课模式"); Logger.info("进入视频刷课模式"); GM_registerMenuCommand("开始刷课", () => { ConfigManager.videoStage = 1; this._videoLoop(); }); GM_registerMenuCommand("停止刷课", () => this._stopVideoLoop()); document.addEventListener("sk:video_ended", () => { if (ConfigManager.videoStage === 2) { const links = document.querySelectorAll(SELECTORS.COURSE_LINKS); const currentIndex = this._getCurrentVideoIndex(links); if (currentIndex !== -1 && currentIndex < links.length - 1) { const nextItem = links[currentIndex + 1]; const nextTitle = nextItem.querySelector(".section-title")?.innerText?.trim() || nextItem.innerText?.trim(); if (nextTitle) { Logger.info(`侧边栏发现后续视频: ${nextTitle},继续播放...`); this._playNextVideo(); return; } } this._playNextVideo(); } else this._playNextVideo(); }); await Utils.pause(3); this._videoLoop(); } async _returnToStudyRecord() { this.ui.updateStatus("本节补刷完成,返回记录页..."); Logger.info("本节补刷完成,返回记录页继续检查"); await Utils.pause(2); this._navigateToStudyRecord(); } _startExamMode() { this.ui.updateStatus("进入考试搜题模式"); Logger.warn("答案由 AI 生成,不能保证 100% 正确。分数高低与作者无关,建议搜完后人工复核。"); Logger.info("进入考试搜题模式,点击【开始搜题】"); GM_registerMenuCommand("开始搜题", () => this.examSolver.start()); GM_registerMenuCommand("停止搜题", () => this.examSolver.stop()); document.addEventListener("sk:exam_start", () => this.examSolver.start()); document.addEventListener("sk:exam_stop", () => this.examSolver.stop()); } async _videoLoop() { if (this.checkTimer) clearInterval(this.checkTimer); const targetChapter = GM_getValue("sk_target_chapter"); if (targetChapter) { this.ui.updateStatus(`自动定位章节: ${targetChapter}`); Logger.info(`正在尝试自动跳转至补刷章节: ${targetChapter}`); const links = Array.from(document.querySelectorAll(SELECTORS.COURSE_LINKS)); const activeIndex = links.findIndex((link) => link.classList?.contains("active") || link.querySelector?.(".active")); if (activeIndex !== -1) { const activeItem = links[activeIndex]; const activeTitle = activeItem.querySelector(".section-title")?.innerText?.trim() || activeItem.innerText?.trim(); if (activeTitle) { GM_deleteValue("sk_target_chapter"); GM_setValue("sk_current_brushing_chapter", targetChapter); Logger.info(`使用 active 定位当前章节: ${activeTitle}`); } } for (const link of links) { const title = link.querySelector(".section-title")?.innerText?.trim() || link.innerText?.trim(); if (title && (targetChapter.includes(title) || title.includes(targetChapter))) { GM_deleteValue("sk_target_chapter"); GM_setValue("sk_current_brushing_chapter", targetChapter); Logger.info(`成功定位,开始补刷: ${title}`); link.click(); break; } } } this.checkTimer = setInterval(async () => { if (this.captchaHandler.isRecognizing) return; const layer = document.querySelector(SELECTORS.CAPTCHA_LAYER); const isRealCaptcha = layer && layer.querySelector("img") && layer.querySelector("input"); if (isRealCaptcha && layer.offsetParent !== null) { await this.captchaHandler.checkAndSolve(); return; } if (layer && !isRealCaptcha && layer.offsetParent !== null) { const content = layer.innerText || ""; if (/未开放|尚未开放|锁定|请先学习|未开启/.test(content)) { const currentChapter = targetChapter || GM_getValue("sk_current_brushing_chapter"); if (currentChapter) { Logger.warn(`检测到章节锁定弹窗: ${content},已将 [${currentChapter}] 加入临时黑名单`); const blocked = GM_getValue("sk_blocked_chapters", []); if (!blocked.includes(currentChapter)) { blocked.push(currentChapter); GM_setValue("sk_blocked_chapters", blocked); } } const confirmBtn = layer.parentNode.querySelector(".layui-layer-btn0"); if (confirmBtn) confirmBtn.click(); await Utils.pause(1); this._returnToStudyRecord(); return; } const confirmBtn = layer.parentNode.querySelector(".layui-layer-btn0"); if (confirmBtn) { Logger.info("拦截到系统提示弹窗,尝试自动关闭/确认..."); confirmBtn.click(); await Utils.pause(1); } return; } const video = document.querySelector(SELECTORS.VIDEO); if (video) { if (video.paused) await this.videoPlayer.start(); else if (this.ui.panel && this.ui.panel.textContent.includes("验证码")) this.ui.updateStatus("视频播放中..."); } else if (!document.querySelector(SELECTORS.VIDEO) && !document.querySelector(".detmain-tabs .ico1") && (Array.from(document.querySelectorAll("button span, .el-button, .detmain-tabs .item span")).some((el) => /考试|作业|练习|测验|资料/.test(el.innerText)) || !!document.querySelector(".ico3, .ico4, .external-link-container")) && !this._examSkipping) { this._examSkipping = true; Logger.warn("检测到当前章节为考试/作业/非视频内容,准备跳过..."); this.ui.updateStatus("自动跳过非视频章节..."); await Utils.pause(3); this._playNextVideo(); setTimeout(() => { this._examSkipping = false; }, 5e3); } }, CONFIG.THRESHOLDS.CAPTCHA_CHECK_INTERVAL); } _stopVideoLoop() { if (this.checkTimer) { clearInterval(this.checkTimer); this.checkTimer = null; } this.ui.updateStatus("刷课已暂停"); } _getCurrentVideoIndex(links) { const items = Array.from(links || []); if (window.location.href.includes("haiqikeji")) { const activeIndex = items.findIndex((link) => link.classList?.contains("active") || link.querySelector?.(".active")); if (activeIndex !== -1) return activeIndex; const activeChapter = document.querySelector(".section-item.active"); if (activeChapter) { const idx = items.findIndex((link) => link === activeChapter || link.contains?.(activeChapter) || activeChapter.contains?.(link)); if (idx !== -1) return idx; } const currentTitle = document.querySelector("h1")?.innerText?.trim(); if (currentTitle) for (let i = 0; i < items.length; i++) { const link = items[i]; const sectionTitle = link.querySelector(".section-title")?.innerText?.trim() || link.title?.trim() || link.innerText?.trim(); if (!sectionTitle) continue; if (currentTitle === sectionTitle) return i; if (currentTitle.includes(sectionTitle) || sectionTitle.includes(currentTitle)) return i; } return -1; } const currentUrl = new URL(window.location.href); for (let i = 0; i < items.length; i++) { const link = items[i]; if (link.querySelector("a.on") || link.matches && link.matches("a.on")) return i; try { const linkNodeId = new URL(link.href, window.location.origin).searchParams.get("nodeId"); const currentNodeId = currentUrl.searchParams.get("nodeId"); if (linkNodeId && currentNodeId && linkNodeId === currentNodeId) return i; } catch {} } const currentTitle = document.querySelector("h1")?.innerText?.trim(); if (currentTitle) for (let i = 0; i < items.length; i++) { const link = items[i]; const sectionTitle = link.querySelector(".section-title")?.innerText?.trim() || link.title?.trim() || link.innerText?.trim(); if (!sectionTitle) continue; if (currentTitle === sectionTitle) return i; if (currentTitle.includes(sectionTitle) || sectionTitle.includes(currentTitle)) return i; } return -1; } async _playNextVideo() { this._stopVideoLoop(); const sidebar = document.querySelector(".detmain-navlist, .course-sidebar, .section-list, .detmain-navs"); const links = sidebar ? sidebar.querySelectorAll(SELECTORS.COURSE_LINKS) : document.querySelectorAll(SELECTORS.COURSE_LINKS); let currentIndex = this._getCurrentVideoIndex(links); if (currentIndex !== -1 && currentIndex < links.length - 1) { this.ui.updateStatus("准备播放下一个视频..."); await Utils.pause(3); const nextItem = links[currentIndex + 1]; if (nextItem.classList.contains("section-item") || nextItem.parentElement.classList.contains("item")) (nextItem.querySelector(".section-header") || nextItem).click(); else nextItem.click(); setTimeout(() => this._videoLoop(), 3e3); } else { const globalNext = Array.from(document.querySelectorAll("a, button, span")).find((el) => { const text = el.innerText; const isNextText = /下一章|下一节|下一页|Next Chapter|Next Section/i.test(text); const isBBS = el.closest(".pagebar-bbs, .discuss-list, .bbs-list, .pagebar"); const isRecord = text.includes("记录") || text.includes("首页") || text.includes("尾页"); return isNextText && el.offsetParent !== null && !isBBS && !isRecord; }); if (globalNext) { Logger.info(`侧边栏已到头,发现跳转按钮: ${globalNext.innerText},尝试进入下一章...`); this.ui.updateStatus("跳转下一章..."); globalNext.click(); setTimeout(() => this._videoLoop(), 5e3); return; } this.ui.notify("完成", "当前页面所有视频已播放完毕"); this.ui.updateStatus("准备核对总进度..."); Logger.info("当前播放列表已结束,开始回跳记录页核对进度"); ConfigManager.videoStage = 2; await Utils.pause(2); this._navigateToStudyRecord(); } } _setupPageTimeout() { const minutes = ConfigManager.timeoutMinutes; if (minutes > 0) setTimeout(() => { if (this.isVideoMode) { this.ui.notify("超时", "页面停留超时,即将刷新"); setTimeout(() => location.reload(), 5e3); } }, minutes * 60 * 1e3); } }; function matchUrl() { let iconLink = document.querySelector("link[rel='shortcut icon']"); if (iconLink && /yuruixxkj|yinghua|canghui|gyxy|ruren|zjxkeji|yuncanjykeji|haiqikeji/.test(iconLink.getAttribute("href"))) return true; const urls = ConfigManager.urls; const currentUrl = window.location.hostname; for (let i = 0; i < urls.length; i++) if (currentUrl.includes(urls[i].trim())) return true; return false; } function quick502Check() { const errorText = document.body.innerText || document.documentElement.innerText || ""; const title = document.title || ""; if (/Internal Server Error|Service Unavailable|Bad Gateway/i.test(errorText + title)) { setTimeout(() => { location.reload(); }, 5e3); return true; } return false; } (function() { "use strict"; if (!matchUrl()) return; if (quick502Check()) return; const app = new App(); window.addEventListener("load", () => app.init()); })(); })();