// ==UserScript==
// @name 爱国者 · 华医网 AI 助手
// @namespace https://github.com/jsk789456
// @version 3.3
// @description 【爱国者 · 华医网 AI 助手】进入答题页即由 AI直接读取题目自动作答并提交,无需遮罩;考试通过后自动连播下一个视频/课程。UI 为「主控 / AI 设置 / 工具 / 日志」分页签布局(页签记忆),同屏只显示一组功能,面板高度大幅缩短不遮挡页面。核心能力:hdbl SPA 答题页单选/多选题支持(多选由 AI 一次返回多个字母)、findHdblSubmit 强查找提交/下一题按钮(含 div 按钮与确认弹窗)、遮罩框选扫描优先直读页面文字(免 OCR、不受 CSP 限制)、视频自动静音、OCR 云端双引擎、强制获取、批量选课、面板拖动+位置记忆、首次教程、AI 连接测试、复制诊断日志、一键 DOM 探针。作者:爱国者。反馈交流 QQ 群:https://qm.qq.com/q/HmejDdd0Ec
// @author 爱国者
// @homepage https://github.com/jsk789456
// @supportURL https://qm.qq.com/q/HmejDdd0Ec
// @copyright 爱国者
// @match *://*.91huayi.com/course_ware/course_ware_polyv.aspx?*
// @match *://*.91huayi.com/course_ware/course_ware_cc.aspx?*
// @match *://*.91huayi.com/pages/exam.aspx?*
// @match *://*.91huayi.com/pages/exam_result.aspx?*
// @match *://*.91huayi.com/cme/index*
// @match *://*.91huayi.com/cme/*
// @match *://*.91huayi.com/*
// @grant GM_xmlhttpRequest
// @connect *
// @connect api.siliconflow.cn
// @connect api.deepseek.com
// @connect api.groq.com
// @connect openrouter.ai
// @connect api.moonshot.cn
// @connect dashscope.aliyuncs.com
// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @icon data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%3E%3Cdefs%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='1'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%23FFD88A'/%3E%3Cstop%20offset='1'%20stop-color='%23F2994A'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='64'%20height='64'%20rx='14'%20fill='%230b1f4d'/%3E%3Ctext%20x='32'%20y='45'%20font-size='36'%20font-family='Microsoft%20YaHei,sans-serif'%20font-weight='bold'%20text-anchor='middle'%20fill='url(%23g)'%3E%E7%88%B1%3C/text%3E%3C/svg%3E
// @license All Rights Reserved
// ==/UserScript==
(function () {
'use strict';
// 仅顶层窗口渲染面板,避免 iframe 内重复创建
var isTop = (window.self === window.top);
// ===================== 诊断捕获设施(日志复制 / F12 分析) =====================
var capLog = [];
var CAP_MAX = 300;
function pushCap(level, msg) {
try {
capLog.push("[" + new Date().toLocaleTimeString('zh-CN', { hour12: false }) + "][" + level + "] " + msg);
if (capLog.length > CAP_MAX) capLog.shift();
} catch (e) {}
}
// 捕获浏览器控制台输出(F12 内容)
['log', 'info', 'warn', 'error', 'debug'].forEach(function (level) {
var orig = console[level] ? console[level].bind(console) : function () {};
console[level] = function () {
try {
var s = Array.prototype.map.call(arguments, function (a) {
try { return (typeof a === 'object' && a !== null) ? JSON.stringify(a) : String(a); }
catch (e) { return String(a); }
}).join(' ');
pushCap(level, s);
} catch (e) {}
return orig.apply(null, arguments);
};
});
// 捕获页面 JS 错误
window.addEventListener('error', function (e) {
pushCap('error', (e.message || '') + ' @ ' + (e.filename || '') + ':' + (e.lineno || ''));
});
window.addEventListener('unhandledrejection', function (e) {
var r = e.reason;
pushCap('error', 'UnhandledRejection: ' + (r && r.message ? r.message : String(r)));
});
// 捕获网络请求
(function () {
try {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (m, u) { this.__capM = m; this.__capU = u; return origOpen.apply(this, arguments); };
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
var self = this;
this.addEventListener('loadend', function () { pushCap('net', (self.__capM || '?') + ' ' + (self.__capU || '?') + ' -> ' + self.status); });
return origSend.apply(this, arguments);
};
} catch (e) {}
try {
if (window.fetch) {
var of = window.fetch;
window.fetch = function () {
var a = arguments, u = (a[0] && (a[0].url || a[0])) || '';
return of.apply(this, a).then(function (r) { pushCap('net', 'fetch ' + u + ' -> ' + (r && r.status)); return r; },
function (err) { pushCap('net', 'fetch ERR ' + u); throw err; });
};
}
} catch (e) {}
})();
function getDiagReport() {
var L = [];
L.push("===== 华医网刷课脚本 诊断报告 =====");
L.push("时间: " + new Date().toLocaleString('zh-CN'));
L.push("URL: " + window.location.href);
L.push("UA: " + navigator.userAgent);
L.push("脚本版本: V3.3");
L.push("--- 视频/播放器 ---");
var v = document.querySelector("video");
L.push("video 存在: " + (!!v));
if (v) {
L.push(" src: " + (v.src ? v.src.slice(0, 90) : 'none'));
L.push(" playbackRate: " + v.playbackRate + " paused: " + v.paused);
L.push(" currentTime: " + (v.currentTime | 0) + " duration: " + (v.duration ? (v.duration | 0) : '?'));
L.push(" muted: " + v.muted);
}
L.push("player: " + (typeof window.player) + " polyvPlayer: " + (typeof window.polyvPlayer) + " cc_js_Player: " + (typeof window.cc_js_Player));
L.push("--- 脚本状态 ---");
L.push("AI 智能答题(华医AION): " + (localStorage.getItem("华医AION") !== "0" ? "开" : "关"));
L.push("AI Key 已配置: " + (!!localStorage.getItem("华医AIKey")));
try { var all = JSON.parse(localStorage.getItem(keyAllAnswer) || "{}"); L.push("已记录答案课程数: " + Object.keys(all).length); }
catch (e) { L.push("已记录答案: 解析失败"); }
try { var wh = JSON.parse(localStorage.getItem(keyWrongHistory) || "{}"); L.push("错题排除记录: " + Object.keys(wh).length + " 题"); }
catch (e) { L.push("错题排除记录: 解析失败"); }
L.push("--- 脚本运行日志(debug-box) ---");
var box = document.getElementById('debug-box');
L.push(box ? box.value : "(无面板/iframe 内)");
L.push("--- 捕获的 console / error / network ---");
L.push(capLog.length ? capLog.join("\n") : "(无)");
L.push("===== 报告结束 =====");
return L.join("\n");
}
function diagToast(msg) {
var t = document.getElementById("diag-toast");
if (!t) {
t = document.createElement("div");
t.id = "diag-toast";
t.style.cssText = "position:fixed;bottom:30px;left:50%;transform:translateX(-50%);z-index:99999999;padding:10px 18px;background:rgba(0,0,0,0.82);color:#fff;border-radius:12px;font-size:13px;font-family:-apple-system,'PingFang SC',sans-serif;transition:opacity .3s;pointer-events:none;";
(document.body || document.documentElement).appendChild(t);
}
t.innerText = msg; t.style.opacity = "1";
clearTimeout(t.__t); t.__t = setTimeout(function () { t.style.opacity = "0"; }, 2200);
}
function copyDiag() {
var text = getDiagReport();
var ok = function () { window.debugLog("📋 诊断日志已复制,可粘贴发给「爱国者」分析"); diagToast("✅ 诊断日志已复制"); };
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(ok, function () { copyDiagFallback(text, ok); });
} else { copyDiagFallback(text, ok); }
} catch (e) { copyDiagFallback(text, ok); }
}
function copyDiagFallback(text, cb) {
try {
var ta = document.createElement("textarea");
ta.value = text; ta.style.position = "fixed"; ta.style.top = "-9999px"; ta.style.opacity = "0";
(document.body || document.documentElement).appendChild(ta); ta.focus(); ta.select();
var r = document.execCommand("copy");
(document.body || document.documentElement).removeChild(ta);
if (r) { window.debugLog("📋 诊断日志已复制(兼容模式)"); diagToast("✅ 诊断日志已复制"); }
else diagToast("⚠️ 复制失败,请手动选择");
} catch (e) { diagToast("⚠️ 复制失败"); }
if (cb) cb();
}
// ===================== 调试窗口 =====================
window.debugLog = function (msg) {
pushCap('script', msg);
var box = document.getElementById('debug-box');
if (!box) { console.log('[华医] ' + msg); return; }
var time = new Date().toLocaleTimeString('zh-CN', { hour12: false });
box.value = '[' + time + '] ' + msg + '\n' + box.value;
};
// ===================== 样式注入(玻璃拟态 + 深蓝/金橙设计语言) =====================
function injectStyle() {
if (document.getElementById("jj-style")) return;
var s = document.createElement("style");
s.id = "jj-style";
s.textContent = [
"#jj-panel{position:fixed;top:18px;right:18px;z-index:9999999;width:300px;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);",
"font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;color:#1d1d1f;",
"background:rgba(255,255,255,0.66);backdrop-filter:blur(22px) saturate(180%);-webkit-backdrop-filter:blur(22px) saturate(180%);",
"border:0.5px solid rgba(255,255,255,0.6);border-radius:22px;box-shadow:0 12px 40px rgba(11,31,77,0.18);overflow:hidden;user-select:none;transition:box-shadow .2s;",
"animation:jjPop .4s cubic-bezier(0.34,1.56,0.64,1);}",
"#jj-panel.jj-dragging{transition:none!important;box-shadow:0 18px 60px rgba(11,31,77,0.32);}",
"@keyframes jjPop{from{opacity:0;transform:translateY(-12px) scale(.96)}to{opacity:1;transform:none}}",
".jj-collapsed #jj-body{display:none}",
"#jj-panel .jj-head{display:flex;align-items:center;gap:10px;padding:13px 15px;background:linear-gradient(135deg,#0b1f4d,#163a8a);color:#fff;cursor:move;}",
".jj-brand{width:34px;height:34px;border-radius:10px;flex:0 0 auto;background:linear-gradient(135deg,#0b1f4d,#123a8a);",
"border:1px solid rgba(255,179,71,0.5);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px;color:#ffb347;box-shadow:0 2px 8px rgba(0,0,0,0.3);}",
".jj-headtext{margin-right:auto;line-height:1.2;}",
".jj-title{font-size:15px;font-weight:700;letter-spacing:-0.3px;}",
".jj-sub{font-size:10px;opacity:.72;margin-top:2px;}",
".jj-ico{width:24px;height:24px;border-radius:7px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;font-size:13px;cursor:pointer;background:rgba(255,255,255,0.14);transition:all .15s;}",
".jj-ico:hover{background:rgba(255,255,255,0.28);transform:scale(1.06);}",
"#jj-body{padding:12px 14px 14px;max-height:calc(100vh - 78px);overflow-y:auto;overscroll-behavior:contain;}",
".jj-status{display:flex;align-items:center;gap:8px;background:rgba(11,31,77,0.05);border-radius:11px;padding:8px 11px;font-size:12px;font-weight:500;margin-bottom:10px;}",
"#tixing{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
".jj-dot{width:8px;height:8px;border-radius:50%;background:#34c759;box-shadow:0 0 0 3px rgba(52,199,89,.18);flex:0 0 auto;animation:jjPulse 2s infinite;}",
"@keyframes jjPulse{0%,100%{opacity:1}50%{opacity:.4}}",
".jj-tabs{display:flex;gap:4px;background:rgba(11,31,77,0.06);border-radius:11px;padding:3px;margin-bottom:10px;}",
".jj-tab{flex:1;text-align:center;padding:7px 0;border-radius:9px;font-size:12px;font-weight:600;color:#51515a;cursor:pointer;transition:all .18s;user-select:none;}",
".jj-tab:hover{color:#0b1f4d;}",
".jj-tab.active{background:#fff;color:#0b1f4d;box-shadow:0 2px 8px rgba(11,31,77,.14);}",
".jj-pane.jj-hidden{display:none;}",
".jj-section{margin-bottom:10px;}",
".jj-card{background:rgba(255,255,255,0.58);border:1px solid rgba(255,255,255,0.75);border-radius:14px;padding:11px 12px;margin-bottom:10px;box-shadow:0 4px 14px rgba(11,31,77,0.09);}",
".jj-card.jj-hidden{display:none;}",
".jj-card-title{font-size:12.5px;font-weight:700;color:#0b1f4d;display:flex;align-items:center;gap:6px;margin-bottom:9px;}",
".jj-card-title.jj-foldable{cursor:pointer;user-select:none;}",
".jj-card-title.jj-foldable:hover{opacity:.78;}",
".jj-arrow{margin-left:auto;font-size:10px;opacity:.55;transition:transform .18s;}",
".jj-card.jj-folded .jj-card-body{display:none;}",
".jj-card.jj-folded .jj-card-title{margin-bottom:0;}",
".jj-footlinks{display:flex;gap:12px;justify-content:center;margin-top:10px;padding-top:9px;border-top:1px solid rgba(11,31,77,.08);}",
".jj-fl{font-size:11px;color:#86868b;cursor:pointer;text-decoration:none;}",
".jj-fl:hover{color:#0b1f4d;}",
".jj-card .jj-section{margin-bottom:0;}",
".jj-card .jj-ai-cfg{margin-top:9px;}",
".jj-card .jj-note{margin-bottom:0;}",
".jj-row{display:flex;align-items:center;justify-content:space-between;}",
".jj-label{font-size:11px;font-weight:700;color:#86868b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;}",
".jj-row .jj-label{margin-bottom:0;}",
".jj-seg{display:flex;background:rgba(11,31,77,0.06);border-radius:12px;padding:4px;gap:4px;}",
".jj-seg button{flex:1;border:none;background:transparent;padding:9px 0;border-radius:9px;font-size:13px;font-weight:600;color:#1d1d1f;cursor:pointer;transition:all .2s;font-family:inherit;}",
".jj-seg button.active{background:#fff;color:#0b1f4d;box-shadow:0 2px 8px rgba(11,31,77,.12);}",
".jj-switch{width:48px;height:28px;border-radius:999px;background:rgba(120,120,128,.22);position:relative;cursor:pointer;transition:all .25s;flex:0 0 auto;}",
".jj-switch.on{background:linear-gradient(135deg,#34c759,#28a745);}",
".jj-knob{position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 2px 5px rgba(0,0,0,.2);transition:all .25s cubic-bezier(0.34,1.56,0.64,1);}",
".jj-switch.on .jj-knob{left:23px;}",
".jj-note{font-size:12px;line-height:1.6;color:#51515a;background:rgba(255,138,0,0.08);border-left:3px solid #ff8a00;border-radius:10px;padding:10px 12px;margin-bottom:14px;}",
".jj-note b{color:#ff8a00;}",
"#debug-box{width:100%;height:120px;background:rgba(11,31,77,0.05);border:none;border-radius:12px;padding:10px;box-sizing:border-box;resize:none;outline:none;font-family:'SF Mono',Menlo,monospace;font-size:11px;line-height:1.5;color:#1d1d1f;}",
".jj-btns{margin-top:4px;}",
".jj-btn{border:none;border-radius:12px;padding:12px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:inherit;text-align:center;display:block;text-decoration:none;}",
".jj-btn:active{transform:scale(.97);}",
".jj-btn-soft{background:rgba(11,31,77,0.06);color:#1d1d1f;margin-top:4px;}",
".jj-btn-soft:hover{background:rgba(11,31,77,0.1);}",
".jj-btn-force{background:linear-gradient(135deg,#ff5a5f,#ff8a00);color:#fff;margin-top:4px;box-shadow:0 6px 16px rgba(255,90,95,.3);}",
".jj-btn-force:hover{filter:brightness(1.05);transform:translateY(-1px);}",
".jj-footer{display:flex;gap:8px;margin-top:12px;}",
".jj-btn-gold{flex:1;background:linear-gradient(135deg,#ff8a00,#ffb347);color:#fff;box-shadow:0 6px 16px rgba(255,138,0,.32);}",
".jj-btn-gold:hover{filter:brightness(1.05);transform:translateY(-1px);}",
".jj-btn-line{flex:1;background:rgba(11,31,77,0.06);color:#0b1f4d;}",
".jj-btn-line:hover{background:rgba(11,31,77,0.12);}",
".jj-help{margin-top:10px;text-align:center;font-size:12px;color:#86868b;cursor:pointer;}",
".jj-help:hover{color:#0b1f4d;}",
"#jj-mask{position:fixed;inset:0;z-index:10000000;background:rgba(11,31,77,0.5);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:20px;animation:jjFade .25s ease;}",
"@keyframes jjFade{from{opacity:0}to{opacity:1}}",
".jj-modal{width:380px;max-width:100%;max-height:90vh;overflow:auto;background:rgba(255,255,255,0.92);backdrop-filter:blur(30px) saturate(180%);-webkit-backdrop-filter:blur(30px) saturate(180%);",
"border:0.5px solid rgba(255,255,255,0.7);border-radius:22px;box-shadow:0 24px 70px rgba(11,31,77,0.4);color:#1d1d1f;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;animation:jjPop .35s cubic-bezier(0.34,1.56,0.64,1);}",
".jj-modal-head{display:flex;align-items:center;justify-content:space-between;padding:18px 20px;background:linear-gradient(135deg,#0b1f4d,#163a8a);color:#fff;border-radius:22px 22px 0 0;}",
".jj-modal-title{font-size:17px;font-weight:700;}",
".jj-modal-close{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer;background:rgba(255,255,255,0.15);font-size:13px;}",
".jj-modal-close:hover{background:rgba(255,255,255,0.3);}",
".jj-modal-body{padding:20px;}",
".jj-step{display:flex;gap:12px;margin-bottom:16px;}",
".jj-num{flex:0 0 auto;width:26px;height:26px;border-radius:50%;background:linear-gradient(135deg,#ff8a00,#ffb347);color:#fff;font-weight:700;font-size:13px;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 8px rgba(255,138,0,.3);}",
".jj-step b{font-size:14px;color:#0b1f4d;}",
".jj-step p{font-size:13px;color:#51515a;margin:3px 0 0;line-height:1.5;}",
".jj-reward-copy{font-size:14px;line-height:1.7;color:#1d1d1f;text-align:center;margin:0 0 18px;}",
".jj-qr{display:flex;gap:14px;justify-content:center;}",
".jj-qr-item{flex:1;text-align:center;}",
".jj-qr-img{width:100%;aspect-ratio:1/1;background:rgba(11,31,77,0.05);border-radius:14px;overflow:hidden;display:flex;align-items:center;justify-content:center;}",
".jj-qr-img img{width:100%;height:100%;object-fit:cover;}",
".jj-qr-fallback{display:none;font-size:12px;color:#86868b;padding:10px;}",
".jj-qr-cap{margin-top:8px;font-size:13px;font-weight:600;color:#0b1f4d;}",
".jj-qq-line{display:block;margin-top:18px;text-align:center;padding:12px;background:linear-gradient(135deg,#0b1f4d,#163a8a);color:#fff;text-decoration:none;border-radius:12px;font-size:13px;font-weight:600;transition:all .15s;}",
".jj-qq-line:hover{transform:translateY(-1px);filter:brightness(1.1);}",
".jj-modal-foot{padding:0 20px 20px;}",
".jj-batch{margin-bottom:14px;}",
".jj-batch .jj-label{display:flex;justify-content:space-between;align-items:center;}",
".jj-disc-wrap{max-height:170px;overflow:auto;background:rgba(11,31,77,0.04);border-radius:12px;padding:6px 4px;margin-bottom:10px;}",
".jj-disc{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;cursor:pointer;font-size:13px;color:#1d1d1f;}",
".jj-disc:hover{background:rgba(11,31,77,0.06);}",
".jj-disc input{accent-color:#ff8a00;width:15px;height:15px;flex:0 0 auto;cursor:pointer;}",
".jj-queue{max-height:130px;overflow:auto;background:rgba(11,31,77,0.04);border-radius:12px;padding:6px 4px;margin:8px 0;font-size:12px;}",
".jj-queue-empty{padding:6px;color:#86868b;}",
".jj-queue-item{display:flex;gap:6px;padding:5px 7px;border-radius:7px;line-height:1.4;color:#1d1d1f;}",
".jj-queue-item.done{color:#34c759;}",
".jj-queue-item.cur{background:rgba(255,138,0,0.12);font-weight:600;}",
".jj-queue-item .qidx{color:#86868b;flex:0 0 auto;}",
".jj-batch-btns{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;}",
".jj-batch-btns .jj-btn{flex:1;min-width:84px;padding:10px;}",
".jj-input{width:100%;box-sizing:border-box;margin:6px 0;padding:8px 10px;border:1px solid rgba(11,31,77,0.18);border-radius:10px;font-size:12px;font-family:inherit;color:#1d1d1f;background:rgba(255,255,255,0.7);outline:none;}",
".jj-input:focus{border-color:#ff8a00;box-shadow:0 0 0 3px rgba(255,138,0,0.15);}",
".jj-ai-cfg{background:rgba(11,31,77,0.04);border-radius:12px;padding:10px 12px;margin-bottom:12px;}",
".jj-hint{font-size:11px;line-height:1.5;color:#86868b;margin-top:6px;}",
".jj-hint b{color:#ff8a00;}",
"#jj-ai.on{background:linear-gradient(135deg,#ff8a00,#ffb347);}",
".jj-hidden{display:none!important;}"
].join("\n");
(document.head || document.documentElement).appendChild(s);
}
// ===================== 使用教程(仅首次自动弹出) =====================
function openTutorial(force) {
if (!force) { try { if (localStorage.getItem("华医TutorialDone") === "1") return; } catch (e) {} }
var html =
'
' +
'' +
'
1自动刷课打开课程视频即可自动静音播放,无需任何手动操作。
' +
'
2AI 智能答题(直答)已默认开启并预填 Key,答题页会直接用 AI 扫描题目、选择正确答案并自动提交;AI 未配置时回落已记录答案/排除错项兜底,仍错则自动重考直到通过。
' +
'
3循环答题直到通过若考试未通过,会自动重新作答、更换答案,直到考试通过为止,无需手动干预。
' +
'
4强制获取若答题页 AI 未自动作答,点面板「🔓 强制获取/作答」或在 F12 控制台输入 forceGetAnswers() 立即扫描题目、填答并提交,并导出每题明细。
' +
'
5诊断日志遇到异常点「复制诊断日志」发给开发者快速定位。
' +
'
6打赏支持用得顺手?欢迎打赏,让脚本持续更新得更好。
' +
'
7📷 遮罩扫描图片题遇到题干/选项是图片、AI 读不到文字的题:点面板「📷 遮罩框选扫描题目」,在页面上拖拽框选该题区域,脚本自动截图 → OCR → AI 选答案 → 在页面勾选对应选项(自动按框选位置定位题号)。填 OCR.space 免费 Key 更准,未填自动用本地 Tesseract 兜底;也可选图/粘贴截图作补充。
' +
'
' +
'';
var mask = document.createElement("div");
mask.id = "jj-mask";
mask.innerHTML = '' + html + '
';
document.body.appendChild(mask);
mask.addEventListener("click", function (e) { if (e.target === mask) closeTut(); });
function closeTut() { try { localStorage.setItem("华医TutorialDone", "1"); } catch (e) {} mask.remove(); }
mask.querySelector(".jj-modal-close").onclick = closeTut;
mask.querySelector(".jj-modal-ok").onclick = closeTut;
}
// ===================== 打赏弹窗 =====================
function openReward() {
var zfb = "https://a1.boltp.com/2026/08/31/6a950ca30913e.jpg";
var wx = "https://a1.boltp.com/2026/08/31/6a950ca33c5d1.png";
var qq = "https://qm.qq.com/q/HmejDdd0Ec";
var qrImg = function (src, cap) {
return '' +
'

' +
'
' + cap + '二维码
(图片加载失败)' +
'
' + cap + '
';
};
var html =
'' +
'' +
'
如果这款脚本帮您省下了宝贵的时间,
欢迎请开发者喝杯咖啡 ☕
您的每一份支持,都是我持续维护、更新功能的动力!
' +
'
' + qrImg(zfb, "支付宝") + qrImg(wx, "微信") + '
' +
'
💬 使用遇到问题?点击加入 QQ 群反馈' +
'
' +
'';
var mask = document.createElement("div");
mask.id = "jj-mask";
mask.innerHTML = '' + html + '
';
document.body.appendChild(mask);
mask.addEventListener("click", function (e) { if (e.target === mask) mask.remove(); });
mask.querySelector(".jj-modal-close").onclick = function () { mask.remove(); };
mask.querySelector(".jj-modal-ok").onclick = function () { mask.remove(); };
}
function createDebugWindow() {
if (!isTop) return;
if (document.getElementById('jj-panel')) return;
if (!document.body) { document.addEventListener('DOMContentLoaded', createDebugWindow); return; }
injectStyle();
var panel = document.createElement('div');
panel.id = 'jj-panel';
panel.innerHTML =
'' +
'
华
' +
'
' +
'
?
' +
'
—
' +
'
✕
' +
'
' +
'' +
'
加载中...
' +
'
' +
'
主控
' +
'
AI 设置
' +
'
工具
' +
'
日志
' +
'
' +
'
' +
'
' +
'
🤖 AI 智能答题
' +
'
' +
'
进入答题页自动读题交 AI 选答案并提交,未通过自动重考;接口配置见「AI 设置」。
' +
'
' +
'
' +
'
' +
'
📚 批量学习
' +
'
按学科选课↻ 刷新
' +
'
' +
'
' +
'
抓取本页项目
' +
'
按勾选学科抓取
' +
'
' +
'
' +
'
' +
'
▶ 开始学习
' +
'
下一项目 ⏭
' +
'
' +
'
' +
'
⏹ 暂停
' +
'
清空队列
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
🔑 AI 接口配置
' +
'
' +
'
默认「智谱 GLM · glm-4-flash」国内直连免费,Key 已预填,可点测试验证。⚠️ Groq 国内常被 403;SiliconFlow 需充值。
' +
'
' +
'
' +
'
' +
'
' +
' ' +
'
';
document.body.appendChild(panel);
// AI 智能答题开关 + 配置(默认开启;未配置 Key 时预填内置 SiliconFlow Key)
var aiSw = panel.querySelector("#jj-ai");
var aiCfg = panel.querySelector("#jj-ai-cfg");
var aiKey = panel.querySelector("#jj-ai-key");
var aiBase = panel.querySelector("#jj-ai-base");
var aiModel = panel.querySelector("#jj-ai-model");
var AI_DEFAULT_KEY = "fef551208fc14e6bb511fa56898cdae3.jPY2YI5eVbwqB5Mv";
function aiOn() { return localStorage.getItem("华医AION") !== "0"; }
function syncAI() {
aiSw.classList.toggle("on", aiOn());
aiCfg.classList.toggle("jj-hidden", !aiOn());
// 迁移:若仍存着旧的 SiliconFlow 默认 Key(已余额不足 402),且用户明示改用 Zhipu,则替换为新默认
var OLD_SF = "sk-znowkugiqqlpwysdwfyqcculzinwvbxqnqffbwertebxjvlz";
if (localStorage.getItem("华医AIKey") === OLD_SF) {
localStorage.setItem("华医AIKey", AI_DEFAULT_KEY);
localStorage.setItem("华医AIBase", "https://open.bigmodel.cn/api/paas/v4");
localStorage.setItem("华医AIModel", "glm-4-flash");
}
// 首次使用(且开启)预填内置 Key:自用脚本可直接用;若分享给他人请先清空
if (!localStorage.getItem("华医AIKey") && aiOn()) {
localStorage.setItem("华医AIKey", AI_DEFAULT_KEY);
}
aiKey.value = localStorage.getItem("华医AIKey") || "";
aiBase.value = localStorage.getItem("华医AIBase") || "https://open.bigmodel.cn/api/paas/v4";
var m = localStorage.getItem("华医AIModel");
if (m) aiModel.value = m;
else aiModel.value = "glm-4-flash";
}
aiSw.onclick = function () {
localStorage.setItem("华医AION", (aiOn() ? "0" : "1"));
syncAI();
window.debugLog("🤖 AI 智能答题:" + (aiOn() ? "已开启(答题页将自动用 AI 作答并提交)" : "已关闭(答题页将停手,可手动或点强制获取)"));
};
aiKey.onchange = function () { localStorage.setItem("华医AIKey", aiKey.value.trim()); window.debugLog("🔑 已保存 AI API Key"); };
aiBase.onchange = function () { localStorage.setItem("华医AIBase", aiBase.value.trim()); };
aiModel.onchange = function () {
localStorage.setItem("华医AIModel", aiModel.value);
var v = aiModel.value;
var map = {
"deepseek-chat": "https://api.deepseek.com/v1",
"qwen-plus": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"qwen-turbo": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"glm-4-flash": "https://open.bigmodel.cn/api/paas/v4",
"Qwen/Qwen2.5-7B-Instruct": "https://api.siliconflow.cn/v1",
"deepseek-ai/DeepSeek-V3": "https://api.siliconflow.cn/v1",
"llama-3.3-70b-versatile": "https://api.groq.com/openai/v1"
};
if (map[v]) {
aiBase.value = map[v];
localStorage.setItem("华医AIBase", aiBase.value);
var note = (v === "llama-3.3-70b-versatile") ? "(⚠️ Groq 国内常被 403 地域限制,如遇 Forbidden 请换 DeepSeek/阿里云/智谱)" : "";
window.debugLog("🔄 已切换模型:" + aiModel.options[aiModel.selectedIndex].text + ",Base 已改为 " + aiBase.value + note);
}
};
// AI 连接测试:用内置示例题验证 Key / Base / 模型是否可用
panel.querySelector("#jj-ai-test").onclick = function () {
if (!aiReady()) { window.debugLog("⚠️ 请先开启 AI 并填入 API Key"); return; }
window.debugLog("🧪 正在测试 AI 连接…");
var demo = [
{ letter: "A", text: "H₂O" },
{ letter: "B", text: "CO₂" },
{ letter: "C", text: "O₂" },
{ letter: "D", text: "N₂" }
];
aiAnswer("水的化学式是什么?", demo).then(function (res) {
if (res.letter) {
window.debugLog("✅ AI 测试通过,返回:" + res.letter + (res.letter === "A" ? "(与预期一致 ✓)" : "(注意:预期为 A,模型给了 " + res.letter + ",请确认模型是否适配单选)"));
} else {
window.debugLog("❌ AI 测试失败:" + (res.message || "未返回有效字母") + (res.status ? "(HTTP " + res.status + ")" : "") + (res.status === 402 ? " → 余额不足,请充值或更换免费 Key" : (res.status === 401 ? " → Key 无效" : (res.status === 404 ? " → 模型名不存在" : ""))));
}
});
};
syncAI();
// ===== 视频自动静音 =====
var muteSw = panel.querySelector("#jj-mute");
function syncMute() { muteSw.classList.toggle("on", localStorage.getItem("华医Mute") !== "0"); }
muteSw.onclick = function () {
localStorage.setItem("华医Mute", (localStorage.getItem("华医Mute") === "0" ? "1" : "0"));
syncMute();
muteAllVideos();
window.debugLog("🔊 视频静音:" + (localStorage.getItem("华医Mute") === "0" ? "已关闭(恢复声音)" : "已开启(自动静音)"));
};
syncMute();
// ===== OCR 扫描答题:事件绑定 =====
var ocrFile = panel.querySelector("#jj-ocr-file");
var ocrKey = panel.querySelector("#jj-ocr-key");
var ocrEngine = panel.querySelector("#jj-ocr-engine");
var ocrQidx = panel.querySelector("#jj-ocr-qidx");
var ocrAutosub = panel.querySelector("#jj-ocr-autosub");
ocrKey.value = localStorage.getItem("华医OCRKey") || "";
ocrEngine.value = localStorage.getItem("华医OCREngine") || "ocrspace";
ocrQidx.value = localStorage.getItem("华医OCRQIndex") || "1";
ocrAutosub.checked = localStorage.getItem("华医OCRAutoSubmit") === "1";
ocrKey.onchange = function () { localStorage.setItem("华医OCRKey", ocrKey.value.trim()); };
ocrEngine.onchange = function () { localStorage.setItem("华医OCREngine", ocrEngine.value); };
ocrQidx.onchange = function () { localStorage.setItem("华医OCRQIndex", ocrQidx.value); };
ocrAutosub.onchange = function () { localStorage.setItem("华医OCRAutoSubmit", ocrAutosub.checked ? "1" : "0"); };
ocrFile.onchange = function () {
var f = ocrFile.files && ocrFile.files[0];
if (f) fileToDataUrl(f, function (d) { pendingOcrImage = d; window.debugLog("🖼️ 已载入图片,点「扫描并作答」"); });
};
panel.querySelector("#jj-ocr-scan").onclick = function () { doOcrAnswer(); };
var ocrMaskBtn = panel.querySelector("#jj-ocr-mask");
if (ocrMaskBtn) ocrMaskBtn.onclick = function () { startMaskScan(); };
// 粘贴截图(Ctrl+V)
document.addEventListener("paste", function (e) {
var items = e.clipboardData && e.clipboardData.items;
if (!items) return;
for (var i = 0; i < items.length; i++) {
if (items[i].type && items[i].type.indexOf("image") === 0) {
var blob = items[i].getAsFile();
if (blob) fileToDataUrl(blob, function (d) {
pendingOcrImage = d;
window.debugLog("🖼️ 已从剪贴板载入截图,点「扫描并作答」");
});
}
}
});
// 复制诊断日志
panel.querySelector("#jj-copy").onclick = function () { copyDiag(); };
// 强制获取/作答(无视开关,立即扫描题目、填答并提交)
panel.querySelector("#jj-force").onclick = function () { forceAnswer(); };
// 适配探针:导出当前题目区 DOM 供校准
panel.querySelector("#jj-probe").onclick = function () { dumpHdblDom(); };
// 打赏 / 反馈 / 教程
panel.querySelector("#jj-reward").onclick = function () { openReward(); };
panel.querySelector("#jj-help").onclick = function () { openTutorial(true); };
panel.querySelector("#jj-help2").onclick = function () { openTutorial(true); };
// 批量学习(按学科选课)UI
initBatchUI();
// 收起 / 关闭
panel.querySelector("#jj-min").onclick = function () { panel.classList.toggle("jj-collapsed"); };
panel.querySelector("#jj-close").onclick = function () { panel.remove(); };
// 面板拖动 + 位置记忆
initPanelDrag(panel);
initTabs(panel);
initCardCollapse(panel);
// 首次使用自动弹出教程(仅一次)
showTutorialIfNeeded();
}
// 分页签:同一时间只显示一个面板,避免 UI 过长遮挡页面
function initTabs(panel) {
var tabs = panel.querySelectorAll(".jj-tab");
if (!tabs.length) return;
function activate(name) {
for (var i = 0; i < tabs.length; i++) {
tabs[i].classList.toggle("active", tabs[i].getAttribute("data-pane") === name);
}
var panes = panel.querySelectorAll(".jj-pane");
for (var j = 0; j < panes.length; j++) {
panes[j].classList.toggle("jj-hidden", panes[j].id !== ("jj-pane-" + name));
}
try { localStorage.setItem("华医Tab", name); } catch (e) {}
}
for (var k = 0; k < tabs.length; k++) {
(function (t) {
t.onclick = function () { activate(t.getAttribute("data-pane")); };
})(tabs[k]);
}
var last = null;
try { last = localStorage.getItem("华医Tab"); } catch (e) {}
if (last && panel.querySelector("#jj-pane-" + last)) activate(last);
}
// 卡片折叠:把每张卡片标题之后的兄弟节点包进 .jj-card-body,点击标题折叠/展开(状态记忆)
var CARD_FOLD_PREFIX = "华医CardFold_";
function cardFoldKey(titleEl) {
return CARD_FOLD_PREFIX + (titleEl.getAttribute("data-key") || titleEl.getAttribute("data-idx") || "0");
}
// 分页后每页内容都很短,默认全部展开(仍可点标题手动折叠)
function cardDefaultFolded(name) {
return "0";
}
function initCardCollapse(panel) {
// v3.1 起改为分页布局:清掉旧的默认折叠记忆,避免卡片莫名收起
try {
if (localStorage.getItem("华医FoldReset31") !== "1") {
var del = [];
for (var d = 0; d < localStorage.length; d++) {
var kk = localStorage.key(d);
if (kk && kk.indexOf(CARD_FOLD_PREFIX) === 0) del.push(kk);
}
for (var dd = 0; dd < del.length; dd++) localStorage.removeItem(del[dd]);
localStorage.setItem("华医FoldReset31", "1");
}
} catch (e) {}
var cards = panel.querySelectorAll(".jj-card");
for (var i = 0; i < cards.length; i++) {
(function (card) {
var title = card.querySelector(".jj-card-title");
if (!title) return;
if (!card.querySelector(".jj-card-body")) {
var body = document.createElement("div");
body.className = "jj-card-body";
var n = title.nextSibling;
while (n) { var nx = n.nextSibling; body.appendChild(n); n = nx; }
card.appendChild(body);
}
var key = cardFoldKey(title);
if (localStorage.getItem(key) === null) {
localStorage.setItem(key, cardDefaultFolded((title.innerText || "")));
}
title.setAttribute("data-idx", String(i));
var arrow = title.querySelector(".jj-arrow");
if (!arrow) {
arrow = document.createElement("span");
arrow.className = "jj-arrow";
title.appendChild(arrow);
title.classList.add("jj-foldable");
}
function apply() {
var folded = localStorage.getItem(key) === "1";
card.classList.toggle("jj-folded", folded);
arrow.textContent = folded ? "▶" : "▼";
}
title.onclick = function () {
localStorage.setItem(key, localStorage.getItem(key) === "1" ? "0" : "1");
apply();
};
apply();
})(cards[i]);
}
var fa = panel.querySelector("#jj-foldall");
if (fa) fa.onclick = function () {
var anyOpen = false;
var list = panel.querySelectorAll(".jj-card .jj-card-title.jj-foldable");
for (var i = 0; i < list.length; i++) {
if (localStorage.getItem(cardFoldKey(list[i])) !== "1") { anyOpen = true; break; }
}
for (var j = 0; j < list.length; j++) localStorage.setItem(cardFoldKey(list[j]), anyOpen ? "1" : "0");
initCardCollapse(panel);
try { window.debugLog(anyOpen ? "🗂 已收起全部卡片" : "🗂 已展开全部卡片"); } catch (e) {}
};
}
function initPanelDrag(panel) {
try {
var pos = (localStorage.getItem("华医PanelPos") || "").split(",");
if (pos.length === 2 && pos[0] && pos[1]) {
var x = parseInt(pos[0], 10), y = parseInt(pos[1], 10);
if (!isNaN(x) && !isNaN(y)) {
panel.style.left = x + "px";
panel.style.top = y + "px";
panel.style.right = "auto";
panel.style.bottom = "auto";
}
}
} catch (e) {}
var head = panel.querySelector(".jj-head");
if (!head) head = panel;
var dragging = false, sx = 0, sy = 0, ox = 0, oy = 0;
function onDown(e) {
if (e.target.closest && e.target.closest(".jj-ico")) return; // 点按钮不触发拖动
dragging = true;
panel.classList.add("jj-dragging");
var r = panel.getBoundingClientRect();
panel.style.left = r.left + "px";
panel.style.top = r.top + "px";
panel.style.right = "auto";
panel.style.bottom = "auto";
sx = (e.touches ? e.touches[0].clientX : e.clientX);
sy = (e.touches ? e.touches[0].clientY : e.clientY);
ox = r.left; oy = r.top;
if (e.cancelable) e.preventDefault();
}
function onMove(e) {
if (!dragging) return;
var cx = (e.touches ? e.touches[0].clientX : e.clientX);
var cy = (e.touches ? e.touches[0].clientY : e.clientY);
var nx = ox + (cx - sx), ny = oy + (cy - sy);
nx = Math.max(4, Math.min(nx, window.innerWidth - panel.offsetWidth - 4));
ny = Math.max(4, Math.min(ny, window.innerHeight - panel.offsetHeight - 4));
panel.style.left = nx + "px";
panel.style.top = ny + "px";
}
function onUp() {
if (!dragging) return;
dragging = false;
panel.classList.remove("jj-dragging");
try { localStorage.setItem("华医PanelPos", panel.offsetLeft + "," + panel.offsetTop); } catch (e) {}
}
head.addEventListener("mousedown", onDown);
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
head.addEventListener("touchstart", onDown, { passive: false });
document.addEventListener("touchmove", onMove, { passive: false });
document.addEventListener("touchend", onUp);
}
function showTutorialIfNeeded() {
try { openTutorial(false); } catch (e) {}
}
// ===================== 核心变量 =====================
var submitTime = 6800;
var reTryTime = 3500;
var examTime = 7000;
var randomX = 5000;
var autoSkip = false;
var keyTest = "JJ_Test";
var keyResult = "JJ_Result";
var keyThisTitle = "JJ_ThisTitle";
var keyTestAnswer = "JJ_TestAnswer";
var keyRightAnswer = "JJ_RightAnswer";
var keyAllAnswer = "JJ_AllAnswer";
var keyWrongHistory = "JJ_WrongHistory"; // 每道题已排除的错误选项字母
var keyChoiceMap = "JJ_ChoiceMap"; // 每道题的选项字母->文本映射
var keyGuessMode = "JJ_GuessMode"; // (v2.4 起不再使用:原盲猜轮换模式标记)
var keyRoundMap = "JJ_RoundMap"; // (v2.4 起仅用于清理:原盲猜轮换轮次)
// === 批量学习(按学科选课)===
var keyBatchQueue = "华医BatchQueue"; // [{cid,title,done}]
var keyExamFrom = "华医ExamFrom"; // 进入考试前记录的课件视频页 URL,考完后跳回正确播放器类型
var keyBatchRunning = "华医BatchRunning"; // "1"/"0"
var keyBatchIndex = "华医BatchIndex"; // 当前处理索引
var btstyleA = "font-size: 16px;font-weight: 300;text-decoration: none;text-align: center;line-height: 40px;height: 40px;padding: 0 40px;display: inline-block;appearance: none;cursor: pointer;border: none;box-sizing: border-box;transition-property: all;transition-duration: .3s;background-color: #4cb0f9;border-color: #4cb0f9;border-radius: 4px;margin: 5px;color: #FFF;";
var btstyleB = "font-size: 12px;font-weight: 300;text-decoration: none;text-align: center;line-height: 20px;height: 20px;padding: 0 5px;display: inline-block;appearance: none;cursor: pointer;border: none;box-sizing: border-box;transition-property: all;transition-duration: .3s;background-color: #4cb0f9;border-color: #4cb0f9;border-radius: 4px;margin: 5px;color: #FFF;";
var btstyleC = "font-size: 12px;font-weight: 300;text-decoration: none;text-align: center;line-height: 20px;height: 20px;padding: 0 5px;display: inline-block;appearance: none;cursor: pointer;border: none;box-sizing: border-box;transition-property: all;transition-duration: .3s;background-color: f15854;border-color: #f15854;border-radius: 4px;margin: 5px;color: #FFF;";
var huayi = getHuayi();
var clock = null;
var examClicked = false; // 防止重复点击「进入考试」
function hasJQ() {
return (typeof window.jQuery !== 'undefined') || (typeof window.$ !== 'undefined');
}
// (v2.4)自动答题门控已移除:答题页 / 视频播完 / cme 列表页均直接进入 AI 答题,不再有「自动答题」开关
// ===================== 路由判断(修复:基于 pathname 模糊匹配,识别 /cme/index) =====================
function pathHas(p) { return window.location.pathname.indexOf(p) !== -1; }
function hashHas(p) { return (window.location.hash || "").indexOf(p) !== -1; }
function inHdbl() { return /hdbl\.91huayi\.com/i.test(window.location.host); }
function routeHdbl() {
if (inHdbl() && hashHas("/problem/question")) {
huayi.doHdblQuestion();
return true;
}
return false;
}
function boot() {
createDebugWindow();
debugLog("🚀 爱国者 · 华医网 AI 助手 启动成功,版本:V3.3");
var matched = true;
if (pathHas("course_ware_polyv")) {
huayi.seeVideo(1);
} else if (pathHas("course_ware_cc")) {
huayi.seeVideo(2);
} else if (pathHas("exam_result")) {
huayi.doResult();
} else if (pathHas("exam.aspx") || pathHas("/pages/exam")) {
huayi.doTest();
} else if (pathHas("course.aspx") && !pathHas("course_ware")) {
// /pages/course.aspx?cid= 项目详情页:自动进入课件学习(批量选课模式)
debugLog("📂 项目详情页:尝试自动进入课件学习");
setTimeout(handleProjectDetail, 2500);
setTimeout(handleProjectDetail, 6000);
} else if (pathHas("/cme/") || pathHas("cme.aspx")) {
// /cme/index、/cme/ 等课程总览页 → 课程列表逻辑
huayi.courseList();
} else {
matched = false;
}
// SPA 页面(如 hdbl 答题页)不在 pathname 中,改用 hash 路由判断
if (!matched) {
if (routeHdbl()) matched = true;
}
// 批量学习区:仅在有学科/项目列表的页面显示
try {
var batchSec = document.getElementById("jj-batch");
if (batchSec) {
var showBatch = !!document.querySelector(".sut_lis, .jet_lis") || pathHas("course.aspx");
batchSec.classList.toggle("jj-hidden", !showBatch);
}
} catch (e) {}
try {
var tixing = document.querySelector("span[id='tixing']");
if (tixing) {
tixing.innerHTML = matched ? "当前网址已适配 ✅" : "此页面非视频、考试或未适配
";
}
} catch (e) {}
if (matched) {
debugLog("✅ 当前页面已识别,准备执行任务");
} else {
debugLog("ℹ️ 此页不在自动处理范围(如课程总览可手动操作)");
}
}
function ready(cb) {
if (document.body) cb();
else window.addEventListener('DOMContentLoaded', cb);
}
ready(boot);
// SPA 路由:hdbl 等页面不刷新,靠 hash 变化触发答题流程
window.addEventListener("hashchange", function () {
try { if (inHdbl()) setTimeout(routeHdbl, 700); } catch (e) {}
});
// 视频自动静音:全局轮询(视频可能是动态加载 / SPA 路由切换后出现),仅在开关开启时执行
setInterval(function () {
try { if (localStorage.getItem("华医Mute") !== "0") muteAllVideos(); } catch (e) {}
}, 1500);
function getHuayi() {
return {
courseList: function () {
var main = document.getElementById("main_div");
if (!main) {
debugLog("ℹ️ 未找到 main_div,本课程页无需注入答案按钮");
return;
}
addAnwserCopybtn();
DelAllAnwser();
},
doHdblQuestion: function () {
var self = this;
self.__hdblTimer && clearTimeout(self.__hdblTimer);
debugLog("🤖 适配 hdbl 答题页,自动识别题目…");
var attempt = 0;
function tryOnce() {
var qs = extractHdblQuestions();
if (qs.length === 0) {
attempt++;
if (attempt <= 40) {
if (attempt === 1) debugLog("⏳ 题目尚未渲染,等待 SPA 加载…");
self.__hdblTimer = setTimeout(tryOnce, 1200);
} else {
debugLog("⚠️ 未识别到题目结构,已导出 DOM 供校准(也可点面板「🔍 适配探针」)");
dumpHdblDom();
}
return;
}
debugLog("✅ 识别到 " + qs.length + " 道题目,调用 AI 作答…");
(async function () {
var answered = 0;
for (var k = 0; k < qs.length; k++) {
var q = qs[k];
var isMulti = /多选/.test(q.badge || "");
if (isMulti) {
var mr = await aiAnswerMulti(q.text, q.choices);
if (mr.letters && mr.letters.length) {
mr.letters.forEach(function (L) {
var c = q.choices.filter(function (x) { return x.letter === L; })[0];
if (c) { try { (c.label || c.el).click(); } catch (e) {} }
});
answered++;
debugLog("🤖 第" + (k + 1) + "题 [多选·AI] 选 " + mr.letters.join("、"));
} else {
debugLog("⚠️ 第" + (k + 1) + "题(多选)AI 未给答案:" + (mr.message || "未知"));
}
} else {
var res = await jjPickAnswer(q.text, q.text, q.choices, { qRightAnswer: {}, qWrongHistory: {} });
if (res.pick) {
try { (res.pick.label || res.pick.el).click(); } catch (e) {}
answered++;
var tag = res.src === "AI 智能" ? "🤖" : (res.src.indexOf("兜底") !== -1 ? "🛟" : "📝");
debugLog(tag + " 第" + (k + 1) + "题 [" + res.src + "] 选 " + res.pick.letter);
} else {
debugLog("⚠️ 第" + (k + 1) + "题 AI 未给答案(" + (res.message || "") + ")");
}
}
}
debugLog("✅ 已作答 " + answered + " 题");
setTimeout(function () {
var btn = findHdblSubmit() || findButtonByText(/提交|下一题|下一页|确定|交卷|next|submit|save/i);
if (btn) {
try { btn.click(); } catch (e) {}
debugLog("📤 已点击「" + (ownText(btn) || "").slice(0, 12) + "」");
setTimeout(function () {
var cf = findHdblConfirm();
if (cf) { try { cf.click(); } catch (e) {} debugLog("📤 已确认弹窗「" + ownText(cf).slice(0, 10) + "」"); }
}, 900);
} else {
debugLog("ℹ️ 未找到提交/下一题按钮,已导出按钮结构(发开发者即可精准校准)");
dumpHdblButtons();
}
}, 1500);
})();
}
tryOnce();
},
seeVideo: function (e) {
localStorage.setItem(keyExamFrom, location.href);
cleanKeyStorage();
asynckillsendQuestion();
killsendQuestion2();
killsendQuestion3();
addinfo();
var onReady = function () {
localStorage.setItem(keyThisTitle, JSON.stringify(window.document.title));
if (autoSkip == true) {
setTimeout(function () { skipVideo(); }, (submitTime + Math.ceil(Math.random() * randomX)));
}
clock = setInterval(examherftest, 3000);
switch (e) {
case 1:
// 修复:原代码写成 window.s2j_onPlayerInitOver(){}(当成函数调用,运行时抛错)
window.s2j_onPlayerInitOver = function () {
if (typeof player !== 'undefined' && player) player.j2s_setVolume(0);
var v = document.querySelector("video");
if (v) v.defaultMuted = true;
setTimeout(function () {
try {
if (typeof player !== 'undefined' && player) player.j2s_resumeVideo();
examherftest();
debugLog("▶️ 视频已自动播放,已静音");
} catch (error) { }
}, 8000);
};
break;
case 2:
window.on_CCH5player_ready = function () {
if (typeof cc_js_Player !== 'undefined' && cc_js_Player) cc_js_Player.setVolume(0);
var v = document.querySelector("video");
if (v) v.defaultMuted = true;
setTimeout(function () {
try {
if (typeof cc_js_Player !== 'undefined' && cc_js_Player) cc_js_Player.play();
examherftest();
debugLog("▶️ 视频已自动播放,已静音");
} catch (error) { }
}, 8000);
};
break;
}
};
if (document.readyState === 'complete') { onReady(); }
else { window.addEventListener('load', onReady, { once: true }); }
},
doTest: function () {
var self = this;
if (self.__testTimer) { clearTimeout(self.__testTimer); self.__testTimer = null; }
var attempt = 0;
var MAX = 25;
function tryOnce() {
var qs = document.querySelectorAll("table[class='tablestyle']");
if (qs.length === 0) {
attempt++;
if (attempt <= MAX) {
debugLog("⏳ 考试题目尚未渲染,重试 (" + attempt + "/" + MAX + ")");
self.__testTimer = setTimeout(tryOnce, 1500);
} else {
debugLog("⚠️ 未找到考试题目(table.tablestyle),可能页面异常或非标准考试页");
}
return;
}
self.__doAnswer();
}
debugLog("🤖 自动 AI 答题中(直接读题,无需遮罩)");
tryOnce();
},
__doAnswer: async function () {
var questions = JSON.parse(localStorage.getItem(keyTest)) || {};
var qRightAnswer = JSON.parse(localStorage.getItem(keyRightAnswer)) || {};
if (JSON.stringify(qRightAnswer) == "{}") {
qRightAnswer = LoadRightAnwser();
}
var qTestAnswer = {};
var qChoiceMap = JSON.parse(localStorage.getItem(keyChoiceMap)) || {};
var qWrongHistory = JSON.parse(localStorage.getItem(keyWrongHistory)) || {};
// ctx 持有对象引用,jjPickAnswer 内部对 wrong 的修改会直接写回
var ctx = { qRightAnswer: qRightAnswer, qWrongHistory: qWrongHistory };
var index = 0;
var answered = 0;
while (true) {
var question = document.querySelectorAll("table[class='tablestyle']")[index];
if (question == null) break;
var qEl = question.querySelector(".q_name");
if (!qEl) { index++; continue; }
var qRaw = qEl.innerText || "";
var q = normalizeQuestion(qRaw);
if (!q) { index++; continue; }
var tbody = question.querySelector("tbody");
if (!tbody) tbody = question;
var choices = parseChoices(tbody);
if (choices.length === 0) { index++; continue; }
// 更新选项映射(用于结果页把文本转回字母)
var choiceMap = {};
choices.forEach(function (c) { choiceMap[c.letter] = c.text; });
qChoiceMap[q] = choiceMap;
// 优先 AI 作答,失败/未配置回落记录答案,仍错则由结果页重考并排除错项
var res = await jjPickAnswer(qRaw, q, choices, ctx);
var pick = res.pick;
var pickLetter = pick ? pick.letter : null;
var pickText = pick ? pick.text : null;
if (pick) {
pick.el.click();
questions[q] = pickLetter;
qTestAnswer[q] = pickText || choiceMap[pickLetter];
answered++;
var tag = res.src === "AI 智能" ? "🤖" : (res.src === "记录答案" ? "✅" : (res.src.indexOf("兜底") !== -1 ? "🛟" : "📝"));
debugLog(tag + " 第" + (index + 1) + "题 [" + res.src + "] 选 " + pickLetter + ":" + (pickText || "").slice(0, 30));
}
index++;
}
localStorage.setItem(keyTest, JSON.stringify(questions));
localStorage.setItem(keyTestAnswer, JSON.stringify(qTestAnswer));
localStorage.setItem(keyChoiceMap, JSON.stringify(qChoiceMap));
localStorage.setItem(keyWrongHistory, JSON.stringify(qWrongHistory));
debugLog("✅ 已作答 " + answered + " 题,准备提交");
setTimeout(function () {
var sb = document.querySelector("#btn_submit");
if (sb) { sb.click(); debugLog("📤 已自动提交试卷"); }
else debugLog("⚠️ 未找到提交按钮 #btn_submit");
}, (submitTime + Math.ceil(Math.random() * randomX)));
},
doResult: function () {
var tipsEl = document.querySelector(".tips_text");
var res = tipsEl ? tipsEl.innerText : "";
var dds = document.querySelectorAll(".state_cour_lis");
localStorage.removeItem(keyResult);
var qTestAnswer = JSON.parse(localStorage.getItem(keyTestAnswer)) || {};
var qChoiceMap = JSON.parse(localStorage.getItem(keyChoiceMap)) || {};
var qWrongHistory = JSON.parse(localStorage.getItem(keyWrongHistory)) || {};
var qRightAnswer = JSON.parse(localStorage.getItem(keyRightAnswer)) || {};
var resultInfo = parseResultPage();
if (jjIsPass(res)) {
saveRightAnwser();
SaveAllAnwser();
// 通过的题目从错误历史中释放
Object.keys(qTestAnswer).forEach(function (q) {
delete qWrongHistory[q];
});
localStorage.setItem(keyWrongHistory, JSON.stringify(qWrongHistory));
cleanKeyStorage();
setTimeout(function () {
var from = localStorage.getItem(keyExamFrom);
if (from) { window.location.href = from; return; }
var cwid = new URLSearchParams(window.location.search).get("cwid");
if (cwid) {
var player = (location.host + location.pathname).indexOf("course_ware_cc") !== -1 ? "course_ware_cc" : "course_ware_polyv";
window.location.href = location.origin + "/" + player + ".aspx?cwid=" + cwid + "&ff=0&ft=0";
} else {
window.history.back();
}
}, 2000);
return;
}
// 未通过:解析结果页,精准拉黑错题的本次选项
if (tipsEl) tipsEl.innerText = "本次未通过,正在尝试更换答案\r\n(此为正常现象,脚本几秒后刷新,请勿操作)";
var qWrong = {};
function recordWrong(q, letter) {
qWrong[q] = true;
if (!letter) return;
if (!qWrongHistory[q]) qWrongHistory[q] = [];
if (qWrongHistory[q].indexOf(letter) === -1) {
qWrongHistory[q].push(letter);
}
}
function recordRight(q, text) {
if (text) qRightAnswer[q] = text;
}
// 优先使用解析到的结构化结果
resultInfo.items.forEach(function (item) {
var q = normalizeQuestion(item.question);
if (!q) return;
if (item.rightText) {
recordRight(q, item.rightText);
}
if (item.isWrong) {
var letter = item.userLetter || getLetterByText(q, qTestAnswer[q], qChoiceMap);
recordWrong(q, letter);
} else {
// 非错题:本次答案就是正确答案
if (qTestAnswer[q]) recordRight(q, qTestAnswer[q]);
}
});
// 兜底:原逻辑扫描 .state_cour_lis(直接从「您的答案:X、」提取错选项字母,更稳)
if (Object.keys(qWrong).length === 0 && dds.length > 0) {
for (var i = 0; i < dds.length; ++i) {
var img = dds[i].querySelector("img");
var isWrong = img && !img.src.includes("bar_img");
var psAll = dds[i].querySelectorAll("p");
var qp = psAll[0];
var ap = psAll[1];
var q = qp ? normalizeQuestion(qp.title || qp.innerText) : "";
if (!q) continue;
if (isWrong) {
var letter = null;
if (ap) {
var am = (ap.title || ap.innerText || "").match(/您的答案[::]\s*([A-Za-z])[、.\s]/);
if (am) letter = am[1].toUpperCase();
}
if (!letter) letter = getLetterByText(q, qTestAnswer[q], qChoiceMap);
recordWrong(q, letter);
} else {
if (qTestAnswer[q]) recordRight(q, qTestAnswer[q]);
}
}
}
var parsedWrong = Object.keys(qWrong).length;
if (parsedWrong > 0) {
// 精确识别到对错:将错题的本次选项加入排除列表,重考时 AI 自动避开
localStorage.removeItem(keyRoundMap);
localStorage.setItem(keyWrongHistory, JSON.stringify(qWrongHistory));
localStorage.setItem(keyRightAnswer, JSON.stringify(qRightAnswer));
localStorage.setItem(keyResult, JSON.stringify(qWrong));
saveRightAnwser();
debugLog("❌ 本次错题 " + parsedWrong + " 道,已加入排除列表,重考将避开");
} else {
// 结果页无法识别对错:保持现状,重考时由 AI 重新扫描作答
localStorage.setItem(keyWrongHistory, JSON.stringify(qWrongHistory));
localStorage.setItem(keyRightAnswer, JSON.stringify(qRightAnswer));
debugLog("⚠️ 结果页无法识别对错,将重新用 AI 扫描作答");
}
setTimeout(function () {
var retryBtn = findRetryExamButton();
if (retryBtn) { retryBtn.click(); debugLog("🔄 点击重新考试"); }
else debugLog("⚠️ 未找到「重新考试」按钮");
}, (reTryTime + Math.ceil(Math.random() * randomX)));
}
};
}
// ===================== 强制获取(F12 控制台 / 面板按钮,v2.2 新增) =====================
// 无视开关,立即扫描题目、填答并打印每题明细,再自动提交。
// 用法:面板点「🔓 强制获取/作答」,或 F12 控制台输入 forceGetAnswers() / 强制获取答案()。
function jjExtractQuestionText(el) {
var sels = [".q_name", ".question-title", ".topic-title", "dt", "p[title]", ".tit", ".exam-q", ".q-title", "h3", "h4"];
for (var i = 0; i < sels.length; i++) {
var q = el.querySelector(sels[i]);
if (q) {
var t = (q.getAttribute("title") || q.innerText || "").trim();
if (t) return t;
}
}
// 兜底:第一个较长文本节点
var ps = el.querySelectorAll("p, div, span, h3, h4, h5");
for (var j = 0; j < ps.length; j++) {
var tt = (ps[j].innerText || "").trim();
if (tt.length > 8) return tt;
}
return "";
}
function jjGetInputLabel(input) {
// 尝试 label[for]、父级 label、相邻文本、内部文本
try {
if (input.id) {
var l = document.querySelector("label[for='" + input.id + "']");
if (l) return l.innerText.trim();
}
var p = input.parentNode;
if (p && p.tagName === "LABEL") return p.innerText.trim();
var txt = "";
var n = input.nextSibling;
while (n) { if (n.nodeType === 3) txt += n.textContent; n = n.nextSibling; }
if (txt.trim()) return txt.trim();
if (p) {
var cl = p.querySelector("label, span, font");
if (cl) return cl.innerText.trim();
}
} catch (e) {}
return "";
}
function jjExtractChoices(el) {
var labels = el.querySelectorAll("label");
var arr = [];
if (labels.length) {
for (var i = 0; i < labels.length; i++) {
var txt = labels[i].innerText || "";
var m = txt.match(/^([A-Za-z])[、.\s)))]+(.*)/) || txt.match(/^([A-Za-z])[\.、\s]*(.*)/);
if (m) {
arr.push({ letter: m[1].toUpperCase(), text: m[2].trim(), el: labels[i] });
} else {
var letter = String.fromCharCode("A".charCodeAt(0) + i);
arr.push({ letter: letter, text: txt.trim(), el: labels[i] });
}
}
if (arr.length) return arr;
}
// 兜底:radio/checkbox + 文本
var inputs = el.querySelectorAll("input[type=radio], input[type=checkbox]");
for (var k = 0; k < inputs.length; k++) {
var t = jjGetInputLabel(inputs[k]);
if (!t) continue;
var lm = t.match(/^([A-Za-z])[、.\s)))]+(.*)/) || t.match(/^([A-Za-z])[\.、\s]*(.*)/);
var letter, text;
if (lm) { letter = lm[1].toUpperCase(); text = lm[2].trim(); }
else { letter = String.fromCharCode("A".charCodeAt(0) + k); text = t; }
arr.push({ letter: letter, text: text, el: inputs[k] });
}
return arr;
}
function jjCollectQuestions() {
var containers = [];
// 策略1:原始结构
var t1 = document.querySelectorAll("table[class='tablestyle']");
Array.prototype.forEach.call(t1, function (e) { containers.push(e); });
// 策略2:常见题块选择器
if (!containers.length) {
var sels = [".question", ".exam-question", ".topic", ".q_item", ".question-item",
".test_table", ".exam_table", "form table", ".topic-list li", ".question-list li", ".cour-list li"];
sels.forEach(function (s) {
var els = document.querySelectorAll(s);
Array.prototype.forEach.call(els, function (e) { containers.push(e); });
});
}
var out = [];
containers.forEach(function (c) {
var qRaw = jjExtractQuestionText(c);
if (!qRaw) return;
var choices = jjExtractChoices(c);
if (!choices.length) return;
out.push({ el: c, raw: qRaw, question: normalizeQuestion(qRaw), choices: choices });
});
return out;
}
function jjDumpCandidateHTML() {
var sels = ["form", ".exam_box", ".test_box", ".question_box", "#main_div", ".content", "table"];
console.warn("[华医][强制获取] 未识别到题目,候选区域 HTML(前 1500 字符,最多 3 个):");
sels.forEach(function (s) {
var els = document.querySelectorAll(s);
Array.prototype.slice.call(els, 0, 3).forEach(function (e) {
console.log("--- 候选 " + s + " ---\n" + e.outerHTML.slice(0, 1500));
});
});
}
async function forceAnswer(opts) {
opts = opts || {};
var doSubmit = opts.submit !== false; // 默认提交(两者都要:先导出日志再提交)
window.debugLog("🔓 强制获取启动…");
var questions = jjCollectQuestions();
if (!questions.length) {
window.debugLog("⚠️ 未识别到题目结构,已打印候选 HTML 到 F12 控制台");
console.warn("[华医][强制获取] 当前页面未识别到题目。请按以下方式排查:");
console.warn("1) 确认已进入答题页(exam.aspx)且题目已渲染;");
console.warn("2) 在 F12 控制台查看上方「候选区域 HTML」,把题目块的 class 发给我,我可加选择器;");
console.warn("3) 若题目在 iframe 内,脚本默认不处理 iframe,需单独处理。");
jjDumpCandidateHTML();
return;
}
var qRightAnswer = JSON.parse(localStorage.getItem(keyRightAnswer) || "{}");
if (JSON.stringify(qRightAnswer) == "{}") qRightAnswer = LoadRightAnwser();
var qWrongHistory = JSON.parse(localStorage.getItem(keyWrongHistory) || "{}");
var qChoiceMap = JSON.parse(localStorage.getItem(keyChoiceMap) || "{}");
var ctx = { qRightAnswer: qRightAnswer, qWrongHistory: qWrongHistory };
var cnt = { "AI 智能": 0, "记录答案": 0, "排除兜底": 0, "首答兜底": 0 };
var summary = [];
for (var i = 0; i < questions.length; i++) {
var item = questions[i];
var q = item.question;
if (!q) continue;
var choices = item.choices;
var choiceMap = {};
choices.forEach(function (c) { choiceMap[c.letter] = c.text; });
qChoiceMap[q] = choiceMap;
var res = await jjPickAnswer(item.raw, q, choices, ctx);
var pick = res.pick;
if (pick) {
pick.el.click();
cnt[res.src] = (cnt[res.src] || 0) + 1;
var line = "第" + (i + 1) + "题 [" + res.src + "] " + pick.letter + ":" + pick.text.slice(0, 40) + " | 题干:" + item.raw.slice(0, 50);
summary.push(line);
console.log("[华医][强制获取] " + line);
window.debugLog("🔓 " + line);
}
}
localStorage.setItem(keyChoiceMap, JSON.stringify(qChoiceMap));
localStorage.setItem(keyWrongHistory, JSON.stringify(qWrongHistory));
// 导出可复制的汇总到控制台
console.log("[华医][强制获取] ===== 本题识别/作答汇总(共 " + questions.length + " 题)=====");
console.log("AI 智能 " + cnt["AI 智能"] + " / 记录答案 " + cnt["记录答案"] + " / 兜底 " + (cnt["排除兜底"] + cnt["首答兜底"]));
console.table(summary);
window.debugLog("✅ 强制获取完成:共 " + questions.length + " 题(AI " + cnt["AI 智能"] + ",记录 " + cnt["记录答案"] + ",兜底 " + (cnt["排除兜底"] + cnt["首答兜底"]) + ")" + (doSubmit ? ",即将提交" : ",未提交"));
if (doSubmit) {
setTimeout(function () {
var sb = document.querySelector("#btn_submit");
if (sb) { sb.click(); window.debugLog("📤 已强制提交试卷"); }
else {
window.debugLog("⚠️ 未找到提交按钮 #btn_submit,请手动提交");
console.warn("[华医][强制获取] 未找到 #btn_submit,请手动点击页面上的提交/交卷按钮。");
}
}, (submitTime + Math.ceil(Math.random() * randomX)));
}
}
// 暴露到全局,方便 F12 控制台直接调用
window.forceGetAnswers = forceAnswer;
window.强制获取答案 = forceAnswer;
window.forceAnswer = forceAnswer;
// ===================== OCR 扫描答题(图片题) =====================
var pendingOcrImage = null; // 待识别的题目截图(dataURL)
function escapeHtml(s) { return String(s).replace(/&/g, "&").replace(//g, ">"); }
function fileToDataUrl(file, cb) {
var reader = new FileReader();
reader.onload = function () { cb(reader.result); };
reader.onerror = function () { window.debugLog("❌ 图片读取失败"); };
reader.readAsDataURL(file);
}
// OCR.space 云端识别(需免费 Key,中文/医学词最稳)
function ocrSpace(imgDataUrl) {
return new Promise(function (resolve) {
var key = (localStorage.getItem("华医OCRKey") || "").trim();
if (!key) { resolve({ ok: false, text: "", err: "未配置 OCR.space Key" }); return; }
var url = "https://api.ocr.space/parse/image";
var form = "apikey=" + encodeURIComponent(key) +
"&base64image=" + encodeURIComponent(imgDataUrl) +
"&language=chs&isOverlayRequired=false&OCREngine=2&scale=true&detectOrientation=true";
try {
GM_xmlhttpRequest({
method: "POST", url: url,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
data: form, timeout: 30000,
onload: function (r) {
try {
var j = JSON.parse(r.responseText);
if (j.IsErroredOnProcessing) { resolve({ ok: false, text: "", err: ((j.ErrorMessage || []).join(";") || "OCR.space 处理失败") }); return; }
var txt = (j.ParsedResults && j.ParsedResults[0] && j.ParsedResults[0].ParsedText) || "";
resolve({ ok: true, text: txt });
} catch (e) { resolve({ ok: false, text: "", err: "OCR.space 响应解析失败" }); }
},
onerror: function () { resolve({ ok: false, text: "", err: "OCR.space 网络错误(检查 @connect)" }); },
ontimeout: function () { resolve({ ok: false, text: "", err: "OCR.space 超时" }); }
});
} catch (e) { resolve({ ok: false, text: "", err: "OCR.space 请求异常:" + e }); }
});
}
// Tesseract.js 本地识别(免 Key 兜底,中文/医学词一般)
var tessLoading = null;
function loadTesseract() {
if (window.Tesseract) return Promise.resolve(window.Tesseract);
if (tessLoading) return tessLoading;
tessLoading = new Promise(function (res) {
var s = document.createElement("script");
s.src = "https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js";
s.onload = function () { res(window.Tesseract || null); };
s.onerror = function () { res(null); };
(document.head || document.documentElement).appendChild(s);
});
return tessLoading;
}
function ocrTesseract(imgDataUrl) {
return loadTesseract().then(function (T) {
if (!T) return { ok: false, text: "", err: "Tesseract.js 加载失败(可能因网络或页面 CSP 限制)" };
return T.recognize(imgDataUrl, 'chi_sim', { logger: function () { } }).then(function (r) {
return { ok: true, text: (r.data && r.data.text) || "" };
}).catch(function (e) { return { ok: false, text: "", err: "Tesseract 识别失败:" + e }; });
});
}
// 把 OCR 文本解析成 题干 + A/B/C/D 选项
function parseQuestionOptions(text) {
var lines = String(text).split(/\r?\n/).map(function (l) { return l.replace(/^\s+|\s+$/g, ""); }).filter(Boolean);
var choices = [], qLines = [];
var re = /^([A-Da-d])[\s、..::))]+(.*)$/;
for (var i = 0; i < lines.length; i++) {
var m = lines[i].match(re);
if (m) choices.push({ letter: m[1].toUpperCase(), text: m[2].trim() });
else qLines.push(lines[i]);
}
if (!choices.length) { // 退化:同行多选项 "A.xx B.xx C.xx D.xx"
var inline = String(text).match(/([A-D])[\s、..::))]?([^A-D]+?)(?=[A-D][\s、..::))]|$)/g);
if (inline) inline.forEach(function (seg) {
var mm = seg.match(/^([A-D])[\s、..::))]?(.*)$/);
if (mm) choices.push({ letter: mm[1], text: mm[2].trim() });
});
}
return { question: qLines.join(" ").trim(), choices: choices };
}
// 定位题目块(与 jjCollectQuestions 同策略)
function jjGetQuestionContainers() {
var containers = [];
var t1 = document.querySelectorAll("table[class='tablestyle']");
Array.prototype.forEach.call(t1, function (e) { containers.push(e); });
if (!containers.length) {
var sels = [".question", ".exam-question", ".topic", ".q_item", ".question-item", ".test_table", ".exam_table", "form table", ".topic-list li", ".question-list li", ".cour-list li"];
sels.forEach(function (s) {
var els = document.querySelectorAll(s);
Array.prototype.forEach.call(els, function (e) { containers.push(e); });
});
}
return containers;
}
// 扫描某题块内的可点选项(与 parseChoices 同逻辑)
function jjScanChoicesIn(c) {
var labels = c.getElementsByTagName("label");
var arr = [];
for (var i = 0; i < labels.length; i++) {
var txt = labels[i].innerText || "";
var m = txt.match(/^([A-Z])[、.\s]+(.*)/);
var letter = m ? m[1] : String.fromCharCode("A".charCodeAt(0) + i);
arr.push({ letter: letter, text: m ? m[2].trim() : txt.trim(), el: labels[i] });
}
if (!arr.length) {
var inputs = c.querySelectorAll("input[type=radio],input[type=checkbox]");
Array.prototype.forEach.call(inputs, function (inp, i) {
var lab = inp.closest ? inp.closest("label") : null;
var txt = lab ? (lab.innerText || "") : (inp.getAttribute("value") || "");
arr.push({ letter: String.fromCharCode("A".charCodeAt(0) + i), text: txt.trim(), el: inp });
});
}
return arr;
}
// 在指定题块勾选某字母对应的选项
function applyAnswerToPage(qIndex, letter) {
var containers = jjGetQuestionContainers();
var c = containers[qIndex];
if (!c) { window.debugLog("⚠️ 未找到第 " + (qIndex + 1) + " 题的题块,请核对题号"); return false; }
var choices = jjScanChoicesIn(c);
var target = choices.filter(function (x) { return x.letter === letter; })[0];
if (!target && choices.length) target = choices[letter ? letter.charCodeAt(0) - 65 : 0];
if (target && target.el) {
try { target.el.click(); } catch (e) { window.debugLog("⚠️ 勾选失败:" + e); return false; }
window.debugLog("✅ 已在第 " + (qIndex + 1) + " 题勾选 " + letter + ":" + (target.text || "").slice(0, 30));
return true;
}
window.debugLog("⚠️ 第 " + (qIndex + 1) + " 题未找到选项 " + letter);
return false;
}
function showOcrPreview(text, parsed) {
var el = document.getElementById("jj-ocr-preview");
if (!el) return;
el.classList.remove("jj-hidden");
var html = "识别题干:" + escapeHtml((parsed.question || "(空)").slice(0, 200)) + "
";
html += (parsed.choices.length ? parsed.choices.map(function (c) { return c.letter + "、" + escapeHtml((c.text || "").slice(0, 60)); }).join("
") : "(未解析到选项,请检查截图清晰度)");
el.innerHTML = html;
}
// 在元素祖先里找题块(与 jjGetQuestionContainers 同策略)
function findQuestionContainer(el) {
if (!el || !el.parentElement) return null;
var cur = el, guard = 0;
while (cur && guard++ < 10) {
if (cur.matches && (cur.matches("table[class='tablestyle']") || cur.matches(".question,.exam-question,.topic,.q_item,.question-item,.test_table,.exam_table,.cour-list li,.question-list li"))) return cur;
cur = cur.parentElement;
}
return null;
}
function containerIndex(c) {
if (!c) return 0;
var all = jjGetQuestionContainers();
var i = all.indexOf(c);
return i >= 0 ? i : 0;
}
// 主流程:OCR → 解析 → AI 选答案 → 勾选(→ 自动提交)。dataUrl 为题图,qIndex 为题块下标(0 起)
function runOcrPipeline(dataUrl, qIndex) {
if (!aiReady()) { window.debugLog("⚠️ 请先在上方「AI 设置」配置 AI Key(如 DeepSeek / 阿里云 / 智谱 Key,国内可直连),OCR 需要 AI 来选答案"); return; }
var engine = localStorage.getItem("华医OCREngine") || "ocrspace";
if (engine === "ocrspace" && !(localStorage.getItem("华医OCRKey") || "").trim()) {
engine = "tesseract";
window.debugLog("ℹ️ 未填 OCR.space Key,自动改用本地 Tesseract");
}
window.debugLog("🔍 正在 OCR 识别(" + (engine === "ocrspace" ? "OCR.space 云端" : "Tesseract 本地") + ")…");
var p = engine === "ocrspace" ? ocrSpace(dataUrl) : ocrTesseract(dataUrl);
p.then(function (o) {
if (!o.ok) { window.debugLog("❌ OCR 失败:" + o.err); return; }
var parsed = parseQuestionOptions(o.text);
showOcrPreview(o.text, parsed);
if (parsed.choices.length < 2) {
window.debugLog("⚠️ 未从截图解析到足够选项(需 A/B/C/D),请检查图片清晰度;识别原文:\n" + o.text.slice(0, 300));
return;
}
window.debugLog("🤖 OCR 得到 " + parsed.choices.length + " 个选项,调用 AI 选答案…");
aiAnswer(parsed.question, parsed.choices).then(function (res) {
if (!res.letter) { window.debugLog("❌ AI 未能给出答案:" + (res.message || "未知")); return; }
var ch = parsed.choices.filter(function (c) { return c.letter === res.letter; })[0];
window.debugLog("✅ AI 选择:" + res.letter + "(" + (ch ? ch.text : "").slice(0, 40) + ")");
var ok = applyAnswerToPage(qIndex, res.letter);
if (ok && localStorage.getItem("华医OCRAutoSubmit") === "1") {
setTimeout(function () {
var sb = document.querySelector("#btn_submit");
if (sb) { sb.click(); window.debugLog("📤 已自动提交试卷"); }
else window.debugLog("⚠️ 未找到提交按钮 #btn_submit,请手动提交");
}, 900);
}
});
});
}
// 上传/粘贴图片方式:按手动「题号」定位
function doOcrAnswer() {
if (!pendingOcrImage) { window.debugLog("⚠️ 请先选择或 Ctrl+V 粘贴一张题目截图"); return; }
var qIndex = Math.max(0, (parseInt(localStorage.getItem("华医OCRQIndex") || "1", 10) || 1) - 1);
runOcrPipeline(pendingOcrImage, qIndex);
}
// 遮罩框选:在页面上拖拽框选题目区域 → 截图该区域 → OCR → AI 选答案 → 自动作答
function startMaskScan() {
if (!aiReady()) { window.debugLog("⚠️ 请先配置 AI Key(如 DeepSeek / 阿里云 / 智谱 Key,国内可直连),遮罩扫描需要 AI 选答案"); return; }
var overlay = document.createElement("div");
overlay.id = "jj-mask-layer";
overlay.style.cssText = "position:fixed;inset:0;z-index:2147483600;cursor:crosshair;background:rgba(8,16,32,.32);user-select:none;";
var box = document.createElement("div");
box.style.cssText = "position:absolute;border:2px dashed #ffb347;background:rgba(255,179,71,.12);display:none;box-sizing:border-box;";
overlay.appendChild(box);
var tip = document.createElement("div");
tip.textContent = "📷 拖拽框选题目区域(含选项),松开即扫描 · 按 Esc 取消";
tip.style.cssText = "position:absolute;top:14px;left:50%;transform:translateX(-50%);padding:8px 14px;background:rgba(0,0,0,.72);color:#ffe;border-radius:10px;font-size:13px;white-space:nowrap;";
overlay.appendChild(tip);
document.body.appendChild(overlay);
var sx = 0, sy = 0, dragging = false;
function onDown(e) {
dragging = true; sx = e.clientX; sy = e.clientY;
box.style.display = "block";
box.style.left = sx + "px"; box.style.top = sy + "px";
box.style.width = "0px"; box.style.height = "0px";
e.preventDefault();
}
function onMove(e) {
if (!dragging) return;
var x = Math.min(e.clientX, sx), y = Math.min(e.clientY, sy);
var w = Math.abs(e.clientX - sx), h = Math.abs(e.clientY - sy);
box.style.left = x + "px"; box.style.top = y + "px";
box.style.width = w + "px"; box.style.height = h + "px";
}
function onUp(e) {
if (!dragging) return;
dragging = false;
var rect = { x: parseInt(box.style.left, 10), y: parseInt(box.style.top, 10), w: parseInt(box.style.width, 10), h: parseInt(box.style.height, 10) };
cleanup();
if (rect.w < 12 || rect.h < 12) { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); window.debugLog("⚠️ 框选区域太小,已取消"); return; }
captureRegion(rect, overlay, tip);
}
function onKey(e) { if (e.key === "Escape") { cleanup(); if (overlay.parentNode) overlay.parentNode.removeChild(overlay); window.debugLog("ℹ️ 已取消遮罩扫描"); } }
function cleanup() {
document.removeEventListener("mousedown", onDown, true);
document.removeEventListener("mousemove", onMove, true);
document.removeEventListener("mouseup", onUp, true);
document.removeEventListener("keydown", onKey, true);
}
document.addEventListener("mousedown", onDown, true);
document.addEventListener("mousemove", onMove, true);
document.addEventListener("mouseup", onUp, true);
document.addEventListener("keydown", onKey, true);
}
// 直接从 DOM 读取框选区域的题干与选项(无需 OCR)
function parseDomText(root) {
if (!root) return null;
var raw = (root.innerText || root.textContent || "").trim();
if (!raw) return null;
var q = "", ch = [];
var qEl = root.querySelector(".question-text") || root.querySelector("[class*='stem']");
if (qEl) q = (qEl.innerText || "").trim();
var optEls = root.querySelectorAll(".option-item,[class*='option-item'],[class*='option-list'] > div");
if (optEls.length >= 2) {
for (var i = 0; i < optEls.length; i++) {
var t = (optEls[i].innerText || "").trim();
if (!t || t.length > 400) continue;
var mm = t.match(/^([A-Za-z])[、..))\s::]?/);
var letter = mm ? mm[1].toUpperCase() : String(letterFromText(t, i) || "").toUpperCase();
if (!letter) letter = String.fromCharCode(65 + i);
ch.push({ letter: letter, text: stripLetter(t), el: optEls[i], label: optEls[i] });
}
}
if (ch.length < 2) {
var lines = raw.split(/\n+/).map(function (s) { return s.trim(); }).filter(Boolean);
for (var j = 0; j < lines.length; j++) {
var m2 = lines[j].match(/^([A-Za-z])[、..))\s::](.+)$/);
if (m2) ch.push({ letter: m2[1].toUpperCase(), text: m2[2].trim(), el: null, label: null });
else if (!q && lines[j].length > 8) q = lines[j];
}
if (!q && lines.length) q = lines[0];
}
if (!q) q = raw.split(/\n+/)[0] || "";
if (ch.length < 2) return null;
return { question: q, choices: ch, raw: raw };
}
// 用解析结果问 AI 并勾选(优先点解析到的选项元素,否则按题号回落)
function answerFromParsed(parsed, qIndex) {
if (!aiReady()) { window.debugLog("⚠️ 请先配置 AI Key(DeepSeek / 阿里云 / 智谱,国内可直连)"); return; }
aiAnswer(parsed.question, parsed.choices).then(function (res) {
if (!res.letter) { window.debugLog("❌ AI 未能给出答案:" + (res.message || "未知")); return; }
var ch = parsed.choices.filter(function (c) { return c.letter === res.letter; })[0];
window.debugLog("✅ AI 选择:" + res.letter + "(" + (ch ? ch.text : "").slice(0, 40) + ")");
var ok = false;
if (ch && (ch.label || ch.el)) {
try { (ch.label || ch.el).click(); ok = true; } catch (e) { ok = false; }
}
if (!ok) ok = applyAnswerToPage(qIndex, res.letter);
if (ok && localStorage.getItem("华医OCRAutoSubmit") === "1") {
setTimeout(function () {
var sb = document.querySelector("#btn_submit") || findHdblSubmit();
if (sb) { sb.click(); window.debugLog("📤 已自动提交"); }
else window.debugLog("⚠️ 未找到提交按钮,请手动提交");
}, 900);
}
});
}
function captureRegion(rect, overlay, tip) {
var cx = rect.x + rect.w / 2, cy = rect.y + rect.h / 2;
var hit = document.elementFromPoint(cx, cy);
var container = findQuestionContainer(hit);
var target = container || hit;
var qIndex = containerIndex(container);
// 没找到题块时向上爬几层,确保能读到整道题
if (!container) {
var up = hit, g = 0;
while (up && g++ < 6) {
var lines0 = (up.innerText || "").split(/\n+/);
var cnt0 = 0;
for (var z = 0; z < lines0.length; z++) { if (/^[A-Za-z][、..))\s]/.test(lines0[z].trim())) cnt0++; }
if (cnt0 >= 2) { target = up; break; }
up = up.parentElement;
}
}
// ✅ 首选:直接读取框选区域的页面文字(免 OCR,规避 CSP/跨域,文字题 100% 准确)
var domParsed = parseDomText(target);
if (domParsed && domParsed.choices.length >= 2) {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
window.debugLog("📖 已直接读取框选区域文字(免 OCR),识别到 " + domParsed.choices.length + " 个选项,调用 AI 选答案…");
showOcrPreview(domParsed.raw || "", domParsed);
answerFromParsed(domParsed, qIndex);
return;
}
if (typeof html2canvas !== "function") {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
window.debugLog("ℹ️ 该区域未读到可识别文字(可能是纯图片题),但截图库 html2canvas 未加载(CDN 被拦截)。请改用「选择题目图片」上传,或填 OCR.space Key 后用云端识别。");
return;
}
tip.textContent = "📷 正在截取选中区域…";
html2canvas(target, { scale: 2, useCORS: true, allowTaint: false, backgroundColor: null, logging: false }).then(function (canvas) {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
var dataUrl;
try { dataUrl = canvas.toDataURL("image/png"); }
catch (err) {
window.debugLog("❌ 截图被跨域图片污染(tainted),无法读取像素。该题图片可能禁止跨域:建议改用「选择题目图片」上传该图,或用系统截图后 Ctrl+V 粘贴。");
return;
}
window.debugLog("📸 已截取第 " + (qIndex + 1) + " 题区域,开始 OCR…");
runOcrPipeline(dataUrl, qIndex);
}).catch(function (err) {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
window.debugLog("❌ 区域截图失败:" + (err && err.message ? err.message : err));
});
}
// ===================== AI 智能答题(免费额度 OpenAI 兼容接口) =====================
function aiReady() {
return localStorage.getItem("华医AION") !== "0" && !!localStorage.getItem("华医AIKey");
}
// 调用免费大模型作答。返回 {letter, status, message};letter 为 A/B/C/D 或 null,message 携带失败原因(含 HTTP 状态/平台报错),便于诊断
var lastAIError = "";
var lastAIWarnMsg = "";
function aiAnswer(questionText, choices) {
return new Promise(function (resolve) {
if (!aiReady()) { resolve({ letter: null, status: -1, message: "AI 未启用或未填 Key" }); return; }
var key = (localStorage.getItem("华医AIKey") || "").trim();
var base = (localStorage.getItem("华医AIBase") || "https://open.bigmodel.cn/api/paas/v4").trim();
var model = localStorage.getItem("华医AIModel") || "glm-4-flash";
var opts = choices.map(function (c) { return c.letter + "、" + c.text; }).join("\n");
var sys = "你是医学继续教育考试答题助手。下面是一道单选题,请直接给出正确选项的字母(A/B/C/D 之一),不要任何解释、标点或多余文字。";
var user = "题目:" + questionText + "\n选项:\n" + opts + "\n请只输出正确选项字母:";
var body = JSON.stringify({
model: model,
messages: [
{ role: "system", content: sys },
{ role: "user", content: user }
],
temperature: 0.1,
max_tokens: 4,
stream: false
});
var url = base.replace(/\/+$/, "") + "/chat/completions";
try {
GM_xmlhttpRequest({
method: "POST",
url: url,
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + key },
data: body,
timeout: 25000,
onload: function (r) {
try {
var j = JSON.parse(r.responseText);
if (r.status !== 200 || !j.choices || !j.choices[0]) {
var raw = (j && (j.message || (j.error && j.error.message))) || ("HTTP " + r.status);
var tip = "";
if (r.status === 403) tip = "(403 多为该服务对您所在地区/IP 限制,或 Key 无效;国内推荐换 DeepSeek 官方 / 阿里云百炼 / 智谱 GLM 等可直连服务,或给 SiliconFlow 充值)";
else if (r.status === 401) tip = "(401 多为 Key 无效,请检查 Key)";
else if (r.status === 402) tip = "(402 余额不足,请充值)";
else if (r.status === 404) tip = "(404 模型名不存在)";
var msg = raw + " " + tip;
lastAIError = msg;
resolve({ letter: null, status: r.status, message: msg });
return;
}
var txt = String(j.choices[0].message.content || "").trim();
// 优先匹配"答案/选"后的字母;退化时只在合法选项字母集合内取首个,避免命中单词首字母
var m = txt.match(/(?:答案|正确选项|选择|选)\s*[::]?\s*([A-Za-z])/i) || txt.match(/\b([A-Za-z])\b/);
var letter = m ? m[1].toUpperCase() : null;
if (letter && choices.some(function (c) { return c.letter === letter; })) { resolve({ letter: letter, status: 200, message: "" }); return; }
var any = (txt.match(/[A-Za-z]/g) || []).map(function (x) { return x.toUpperCase(); })
.filter(function (x) { return choices.some(function (c) { return c.letter === x; }); });
if (any.length) { resolve({ letter: any[0], status: 200, message: "" }); return; }
lastAIError = "返回内容无有效选项字母:" + txt.slice(0, 50);
resolve({ letter: null, status: 200, message: lastAIError });
} catch (e) {
lastAIError = "响应解析失败:" + String(r.responseText || "").slice(0, 100);
resolve({ letter: null, status: r.status, message: lastAIError });
}
},
onerror: function () { lastAIError = "网络/跨域错误(onerror),请检查 @connect 或网络"; resolve({ letter: null, status: 0, message: lastAIError }); },
ontimeout: function () { lastAIError = "请求超时(25s)"; resolve({ letter: null, status: 0, message: lastAIError }); }
});
} catch (e) { lastAIError = "发起请求异常:" + e; resolve({ letter: null, status: 0, message: lastAIError }); }
});
}
// 多选题:一次返回多个正确选项字母(如 "A,C,D"),返回 {letters:[...], message}
function aiAnswerMulti(questionText, choices) {
return new Promise(function (resolve) {
if (!aiReady()) { resolve({ letters: [], message: "AI 未启用或未填 Key" }); return; }
var key = (localStorage.getItem("华医AIKey") || "").trim();
var base = (localStorage.getItem("华医AIBase") || "https://open.bigmodel.cn/api/paas/v4").trim();
var model = localStorage.getItem("华医AIModel") || "glm-4-flash";
var opts = choices.map(function (c) { return c.letter + "、" + c.text; }).join("\n");
var sys = "你是医学继续教育考试答题助手。下面是一道多选题,请直接给出所有正确选项的字母,用英文逗号分隔(如 A,C,D),不要任何解释或多余文字。";
var user = "题目:" + questionText + "\n选项:\n" + opts + "\n请只输出所有正确选项字母(逗号分隔):";
var body = JSON.stringify({
model: model,
messages: [{ role: "system", content: sys }, { role: "user", content: user }],
temperature: 0.1, max_tokens: 16, stream: false
});
var url = base.replace(/\/+$/, "") + "/chat/completions";
try {
GM_xmlhttpRequest({
method: "POST", url: url,
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + key },
data: body, timeout: 25000,
onload: function (r) {
try {
var j = JSON.parse(r.responseText);
if (r.status !== 200 || !j.choices || !j.choices[0]) {
var msg = (j && (j.message || (j.error && j.error.message))) || ("HTTP " + r.status);
resolve({ letters: [], message: msg }); return;
}
var txt = String(j.choices[0].message.content || "").trim().toUpperCase();
var all = (txt.match(/[A-Z]/g) || []);
var letters = [];
all.forEach(function (x) {
if (letters.indexOf(x) === -1 && choices.some(function (c) { return c.letter === x; })) letters.push(x);
});
if (letters.length) { resolve({ letters: letters, message: "" }); return; }
resolve({ letters: [], message: "多选返回无有效字母:" + txt.slice(0, 40) });
} catch (e) { resolve({ letters: [], message: "响应解析失败" }); }
},
onerror: function () { resolve({ letters: [], message: "网络/跨域错误" }); },
ontimeout: function () { resolve({ letters: [], message: "请求超时" }); }
});
} catch (e) { resolve({ letters: [], message: "发起请求异常:" + e }); }
});
}
// 单题作答决策:优先 AI(已配置且推荐项不在排除列表),否则回落 记录答案 → 排除错项兜底
// ctx = { qRightAnswer, qWrongHistory }(对象引用,内部对 wrong 的修改会写回)
// 返回 {pick:{letter,text,el}|null, src:"AI 智能"|"记录答案"|"排除兜底"|"首答兜底"}
async function jjPickAnswer(qRaw, q, choices, ctx) {
var wrongs = ctx.qWrongHistory[q] || [];
// 1) AI 优先(免费额度 OpenAI 兼容接口);拒绝"已知错误项",保证收敛
if (aiReady()) {
var aiRes = await aiAnswer(qRaw, choices);
if (aiRes.letter) {
var aiChoice = choices.filter(function (c) { return c.letter === aiRes.letter; })[0];
if (aiChoice && wrongs.indexOf(aiChoice.letter) === -1) {
return { pick: aiChoice, src: "AI 智能" };
}
} else if (aiRes.message && aiRes.message !== lastAIWarnMsg) {
lastAIWarnMsg = aiRes.message;
debugLog("⚠️ AI 调用未成功:" + aiRes.message + "(将回落记录/排除作答)");
}
}
// 2) 已记录正确答案
if (ctx.qRightAnswer.hasOwnProperty(q)) {
var rightText = ctx.qRightAnswer[q];
var matched = choices.filter(function (c) { return c.text == rightText; })[0]
|| choices.filter(function (c) { return c.text.indexOf(rightText) !== -1 || rightText.indexOf(c.text) !== -1; })[0];
if (matched) return { pick: matched, src: "记录答案" };
}
// 3) 兜底:排除已知错项后选第一个(仍错则由结果页重考并补充排除,直到通过)
var cand = choices.filter(function (c) { return wrongs.indexOf(c.letter) === -1; })[0];
if (!cand) { ctx.qWrongHistory[q] = []; cand = choices[0]; }
return { pick: cand, src: (wrongs.length ? "排除兜底" : "首答兜底") };
}
// ===================== 答题增强辅助函数 =====================
function normalizeQuestion(text) {
if (!text) return "";
return String(text)
.replace(/^\s*\d+[、.\s]+/, "") // 去掉前导题号 "1、"
.replace(/[((].*?[))]/g, "") // 去掉括号及其内容
.replace(/[【】\[\]\(\)()]/g, "") // 去掉各种括号符号
.replace(/[\s\u00A0\u200B]/g, "") // 去掉所有空白
.trim();
}
// ===================== hdbl SPA 答题页适配(通用解析 + 探针) =====================
function closestEl(el, sel) {
while (el && el !== document) {
if (el.matches && el.matches(sel)) return el;
el = el.parentElement;
}
return null;
}
function letterFromText(t, i) {
if (!t) return String.fromCharCode(65 + i);
var m = t.match(/^[A-Za-z][、..))\s]/);
if (m) return m[0][0].toUpperCase();
return String.fromCharCode(65 + i);
}
function stripLetter(t) {
if (!t) return "";
return t.replace(/^[A-Za-z][、..))\s]+/, "").trim();
}
function findQuestionText(n) {
// hdbl 实测:题干在 .question-text(span),优先取它;.question-title 是"问题1"这类小节标题需跳过
var qt = n.querySelector(".question-text,[class*='stem'],[class*='question-text']");
if (qt) {
var t0 = (qt.innerText || qt.getAttribute("title") || "").trim();
if (t0.length > 4) return t0;
}
var cand = n.querySelector(".q_name,.question-title,.topic-title,.title,.stem,[class*='title'],[class*='name']");
if (cand) {
var t = (cand.getAttribute("title") || cand.innerText || "").trim();
// 跳过"问题1/问题2"这类小节标题,它不是题目
if (t.length > 4 && !/^问题\s*\d+$/.test(t.trim())) return t;
}
var best = "";
var all = n.querySelectorAll("div,p,span,li,dt,h3,h4,h5");
if (all.forEach) all.forEach(function (e) {
var t = (e.innerText || "").trim();
if (t.length > best.length && t.length > 10) best = t;
});
return best;
}
function findChoices(n) {
var ch = [];
var inputs = n.querySelectorAll("input[type='radio'],input[type='checkbox']");
if (inputs.length >= 2) {
for (var i = 0; i < inputs.length; i++) {
var inp = inputs[i];
var label = (inp.id ? document.querySelector("label[for='" + inp.id + "']") : null) || closestEl(inp, "label") || inp.parentElement;
var t = (label ? label.innerText : (inp.value || "")) || "";
ch.push({ letter: letterFromText(t, i), text: stripLetter(t), el: inp, label: label });
}
return ch;
}
var labels = n.querySelectorAll("label");
if (labels.length >= 2) {
for (var j = 0; j < labels.length; j++) {
var lt = labels[j].innerText || "";
ch.push({ letter: letterFromText(lt, j), text: stripLetter(lt), el: labels[j], label: labels[j] });
}
return ch;
}
var items = n.querySelectorAll("li,div,span,p,button,a");
if (items.forEach) items.forEach(function (it, k) {
var tt = it.innerText || "";
if (/^[A-Za-z][、..))\s]/.test(tt)) {
ch.push({ letter: tt[0].toUpperCase(), text: stripLetter(tt), el: it, label: it });
}
});
return ch;
}
// hdbl 选项专属解析:选项 DOM 未知(探针被截断),多套选择器兜底
function findHdblChoices(n) {
var ch = [];
var inputs = n.querySelectorAll("input[type='radio'],input[type='checkbox']");
if (inputs.length >= 2) {
for (var i = 0; i < inputs.length; i++) {
var inp = inputs[i];
var label = (inp.id ? document.querySelector("label[for='" + inp.id + "']") : null) || closestEl(inp, "label") || inp.parentElement;
var t = (label ? label.innerText : (inp.value || "")) || "";
ch.push({ letter: letterFromText(t, i), text: stripLetter(t), el: inp, label: label });
}
return ch;
}
var items = n.querySelectorAll("[class*='option'],[class*='choice'],[class*='answer'],[class*='item'],[class*='opt']");
if (items.length >= 2) {
for (var j = 0; j < items.length; j++) {
var tt = (items[j].innerText || "").trim();
if (/^(解析|提交|上一题|下一题|确定|收起)$/.test(tt)) continue;
ch.push({ letter: letterFromText(tt, j), text: stripLetter(tt), el: items[j], label: items[j] });
}
if (ch.length >= 2) return ch;
}
var any = n.querySelectorAll("li,div,span,p,button,a");
if (any.forEach) any.forEach(function (it, k) {
var tt2 = (it.innerText || "").trim();
if (/^[A-Za-z][、..))\s]/.test(tt2)) ch.push({ letter: tt2[0].toUpperCase(), text: stripLetter(tt2), el: it, label: it });
});
return ch;
}
function extractHdblQuestions() {
var useHdbl = !!document.querySelector(".question-container");
var sels = useHdbl
? ".question-container"
: "[class*='question'],[class*='problem'],[class*='topic'],[class*='exam'],[class*='paper'],[class*='quiz'],[class*='choice'],[class*='judge'],[class*='item'],[class*='subject'],[class*='option'],[id*='question']";
var nodes = document.querySelectorAll(sels);
var blocks = [], seen = (typeof Set !== "undefined") ? new Set() : {};
for (var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if (seen instanceof Set ? seen.has(n) : seen[n]) continue;
if (seen instanceof Set) seen.add(n); else seen[n] = 1;
var txt;
if (useHdbl) {
var q = n.querySelector(".question-text");
txt = q ? (q.innerText || "").trim() : findQuestionText(n);
} else {
txt = findQuestionText(n);
}
var ch = useHdbl ? findHdblChoices(n) : findChoices(n);
var badgeEl = n.querySelector(".question-type-badge,[class*='type-badge'],[class*='qtype']");
var badge = badgeEl ? (badgeEl.innerText || "").trim() : "";
if (txt && ch.length >= 2) blocks.push({ text: txt, choices: ch, el: n, badge: badge });
}
// 去除被包含(父子重复)的块
blocks = blocks.filter(function (b) {
return !blocks.some(function (o) { return o !== b && b.text.indexOf(o.text) >= 0 && o.text.length > b.text.length; });
});
return blocks;
}
function findButtonByText(re) {
var els = document.querySelectorAll("button,a,input[type=button],input[type=submit],[role=button]");
for (var i = 0; i < els.length; i++) {
var t = (els[i].innerText || els[i].value || els[i].getAttribute("title") || "").trim();
if (re.test(t)) return els[i];
}
return null;
}
// 取元素“自身”的文字(只看直接文本节点),避免把整个容器的长文本当成按钮名
function ownText(el) {
if (!el) return "";
var s = "";
for (var i = 0; i < el.childNodes.length; i++) {
if (el.childNodes[i].nodeType === 3) s += el.childNodes[i].textContent;
}
s = (s || "").trim();
if (!s) s = (el.innerText || el.value || el.getAttribute("title") || "").trim();
return s;
}
// hdbl 等 SPA 的按钮常是 div/span(非 button/a),需强查找
function findHdblSubmit() {
var RE = /提交|交卷|下一题|下一页|下一问|下一节|确定|确认|保存答案|继续学习|下一步|完成/;
var EX = /^(提交|交卷|确定|确认|下一题|下一页|下一步|保存|提交答案|完成学习|完成)$/;
var cands = [];
var els = document.querySelectorAll("button,a,input[type=button],input[type=submit],[role=button],div,span,li,p");
for (var i = 0; i < els.length; i++) {
var el = els[i];
if (el.closest && el.closest("#jj-panel")) continue;
var vis = el.offsetParent || el.offsetHeight || el.getClientRects().length;
if (!vis) continue;
var t = ownText(el);
if (!t) continue;
if (EX.test(t)) cands.push({ el: el, t: t, score: 0 });
else if (t.length <= 10 && RE.test(t)) cands.push({ el: el, t: t, score: 1 });
}
if (!cands.length) return null;
cands.sort(function (a, b) { return a.score - b.score || a.t.length - b.t.length; });
return cands[0].el;
}
// 提交后可能弹确认框(确定/确认提交)
function findHdblConfirm() {
var els = document.querySelectorAll(".el-message-box__wrapper button,.el-dialog button,.van-dialog__confirm,[class*='modal'] button,[class*='dialog'] button,button,a,div,span");
for (var i = 0; i < els.length; i++) {
var el = els[i];
if (el.closest && el.closest("#jj-panel")) continue;
var t = ownText(el);
if (/^(确定|确认|确认提交|是|OK|好的|继续)$/.test(t)) return el;
}
return null;
}
function dumpHdblButtons() {
var out = [];
var els = document.querySelectorAll("button,a,div,span,li");
for (var i = 0; i < els.length && out.length < 120; i++) {
var el = els[i];
if (el.closest && el.closest("#jj-panel")) continue;
if (!el.offsetParent && !el.offsetHeight) continue;
var t = ownText(el);
if (!t || t.length > 12) continue;
if (el.children.length > 2) continue;
out.push("<" + el.tagName.toLowerCase() + " class=\"" + (el.className || "") + "\">" + t);
}
var txt = "【hdbl 按钮探针】\n" + out.join("\n");
debugLog(txt);
try { if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(txt); } catch (e) {}
}
function dumpHdblDom() {
var area = document.querySelector("[class*='question'],[class*='problem'],[class*='paper'],[class*='exam'],[class*='quiz'],[id*='question']") || document.body;
var html = area.outerHTML || "";
if (html.length > 30000) html = html.slice(0, 30000) + "\n...(已截断)";
debugLog("🔍【hdbl 题目区 DOM 探针】\n" + html.replace(/>\n<"));
try { console.log("【hdbl DOM probe】", area); } catch (e) {}
try { if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(html); } catch (e) {}
debugLog("📋 题目区 HTML 已尝试复制到剪贴板,可直接粘贴发给开发者校准");
}
function parseChoices(tbody) {
var labels = tbody.getElementsByTagName("label");
var arr = [];
for (var i = 0; i < labels.length; i++) {
var txt = labels[i].innerText || "";
var m = txt.match(/^([A-Z])[、.\s]+(.*)/);
if (m) {
arr.push({ letter: m[1], text: m[2].trim(), index: i, el: labels[i] });
} else {
var letter = String.fromCharCode("A".charCodeAt(0) + i);
arr.push({ letter: letter, text: txt.trim(), index: i, el: labels[i] });
}
}
return arr;
}
function getLetterByText(q, text, map) {
if (!q || !text) return null;
var cm = (map && map[q]) || {};
for (var letter in cm) {
if (cm[letter] === text) return letter;
}
return null;
}
// 判分:必须排除「未通过/不通过/不及格」等否定表述,避免「考试未通过」被误判为通过
function jjIsPass(res) {
if (!res) return false;
if (/未通过|不通过|未及格|不及格|失败|不合格|未合格/.test(res)) return false;
return /考试通过|通过!|完成项目学习可以申请学分|成绩合格|合格/.test(res);
}
function parseResultPage() {
var info = { pass: false, items: [] };
var tipsEl = document.querySelector(".tips_text");
var res = tipsEl ? tipsEl.innerText : "";
info.pass = jjIsPass(res);
// 多选择器兼容不同结果页结构
var items = document.querySelectorAll(".state_cour_lis");
if (items.length === 0) items = document.querySelectorAll(".exam-result-item, .question-result, .result-list li, .topic-list li, .cour-list");
Array.prototype.forEach.call(items, function (item, idx) {
var qEl = item.querySelector(".q_name, .question-title, .topic-title, p[title], dt, .tit, .exam-q");
var question = "";
if (qEl) question = qEl.getAttribute("title") || qEl.innerText;
if (!question) {
// 兜底:取第一个较长文本
var ps = item.querySelectorAll("p, div");
for (var i = 0; i < ps.length; i++) {
var t = ps[i].innerText || "";
if (t.length > 10) { question = t; break; }
}
}
if (!question) return;
var txt = item.innerText || "";
var isWrong = false;
// 通过图标判断对错
var img = item.querySelector("img");
if (img) {
var src = (img.src || "").toLowerCase();
isWrong = !/right|correct|bar_img|dui|yes|ok/.test(src);
}
// 通过文字再次确认
if (/您的答案[::]/.test(txt) && /错误|错|×|X/.test(txt)) isWrong = true;
if (/正确答案[::]/.test(txt) && !/您的答案/.test(txt)) isWrong = false;
var userMatch = txt.match(/您的答案[::]\s*([A-Z])[、.\s]+([^\n\r]+)/);
var userLetter = userMatch ? userMatch[1] : null;
var userText = userMatch ? userMatch[2].trim() : null;
var rightMatch = txt.match(/正确答案[::]\s*([A-Z])[、.\s]+([^\n\r]+)/);
var rightLetter = rightMatch ? rightMatch[1] : null;
var rightText = rightMatch ? rightMatch[2].trim() : null;
info.items.push({
index: idx,
question: question,
isWrong: isWrong,
userLetter: userLetter,
userText: userText,
rightLetter: rightLetter,
rightText: rightText
});
});
return info;
}
function findRetryExamButton() {
var keywords = ["重新考试", "再考一次", "重新答题", "重考", "再次考试", "再来一次"];
// 优先精确匹配 input
var btn = document.querySelector("input[type=button][value='重新考试']");
if (btn) return btn;
// 兜底:遍历 input/button/a 等可点击元素,按文本匹配
var all = document.querySelectorAll("input, button, a, div[onclick], span[onclick]");
for (var i = 0; i < all.length; i++) {
var txt = (all[i].value || all[i].innerText || all[i].textContent || "").trim();
for (var k = 0; k < keywords.length; k++) {
if (txt.indexOf(keywords[k]) !== -1) return all[i];
}
}
return null;
}
// ===================== 辅助函数 =====================
function SaveAllAnwser() {
var qAllAnswer = JSON.parse(localStorage.getItem(keyAllAnswer)) || {};
var qRightAnswer = JSON.parse(localStorage.getItem(keyRightAnswer)) || {};
var qTitle = JSON.parse(localStorage.getItem(keyThisTitle)) || "没有记录到章节名称";
var qOldAnswer = qAllAnswer[qTitle] || {};
for (var q in qRightAnswer) {
qOldAnswer[q] = qRightAnswer[q];
}
qAllAnswer[qTitle] = qOldAnswer;
if (qAllAnswer != null) {
localStorage.setItem(keyAllAnswer, JSON.stringify(qAllAnswer));
}
}
function LoadRightAnwser() {
var qAllAnswer = JSON.parse(localStorage.getItem(keyAllAnswer)) || {};
var qTitle = JSON.parse(localStorage.getItem(keyThisTitle)) || "没有记录到章节名称";
if (qTitle == "没有记录到章节名称") return {};
return qAllAnswer[qTitle] || {};
}
function saveRightAnwser() {
var qRightAnswer = JSON.parse(localStorage.getItem(keyRightAnswer)) || {};
var qTestAnswer = JSON.parse(localStorage.getItem(keyTestAnswer)) || {};
var qWrongs = JSON.parse(localStorage.getItem(keyResult)) || {};
for (var q in qTestAnswer) {
if (!qWrongs.hasOwnProperty(q)) {
qRightAnswer[q] = qTestAnswer[q];
}
}
localStorage.removeItem(keyTestAnswer);
if (qRightAnswer != null) {
localStorage.setItem(keyRightAnswer, JSON.stringify(qRightAnswer));
}
}
function addAnwserCopybtn() {
var main = document.getElementById("main_div");
if (!main) return;
var alink = document.createElement("a");
alink.innerHTML = '显示已记录答案';
alink.style = btstyleB;
alink.onclick = function (event) {
var qAllAnswer = JSON.parse(localStorage.getItem(keyAllAnswer)) || {};
var Aout = JSON.stringify(qAllAnswer, null, "\t");
var out = document.getElementById("AnwserOut");
if (out) {
out.innerHTML = Aout;
} else {
var textout = document.createElement("textarea");
textout.id = "AnwserOut";
textout.innerHTML = Aout;
textout.rows = 20; textout.cols = 30;
main.parentNode.append(textout);
}
};
main.parentNode.append(alink);
}
function DelAllAnwser() {
var main = document.getElementById("main_div");
if (!main) return;
var alink = document.createElement("a");
alink.innerHTML = '清除已记录答案';
alink.style = btstyleB;
alink.onclick = function (event) {
var r = confirm("确定清除历史答案?!");
if (r) localStorage.removeItem(keyAllAnswer);
};
main.parentNode.append(alink);
}
function skipVideo() {
var oVideo = document.getElementsByTagName('video')[0];
if (oVideo) oVideo.currentTime = oVideo.duration - 1;
}
// 视频自动静音:对所有 video 设 muted + 调播放器 API(polyv/cc),受「视频自动静音」开关控制
function muteAllVideos() {
if (localStorage.getItem("华医Mute") === "0") return;
var vs = document.getElementsByTagName("video");
for (var i = 0; i < vs.length; i++) {
try { vs[i].muted = true; vs[i].volume = 0; } catch (e) { }
}
try { if (typeof player !== "undefined" && player && player.j2s_setVolume) player.j2s_setVolume(0); } catch (e) { }
try { if (typeof cc_js_Player !== "undefined" && cc_js_Player && cc_js_Player.setVolume) cc_js_Player.setVolume(0); } catch (e) { }
}
// 原生查找「下一个未完成课程/课件」入口(不依赖 jQuery,单课程自动连播用,不受批量模式门控)
function findNextCourseEntry() {
var labels = ['未学习', '学习中', '待考试', '继续学习', '下一课程', '开始学习', '进入学习', '去学习'];
var nodes = document.querySelectorAll('button, a, input[type="button"], .btn, span[onclick]');
for (var i = 0; i < nodes.length; i++) {
var t = (nodes[i].innerText || nodes[i].value || nodes[i].title || nodes[i].getAttribute('onclick') || '');
for (var j = 0; j < labels.length; j++) {
if (t.indexOf(labels[j]) !== -1) {
var row = nodes[i].closest ? nodes[i].closest('li, tr, .lis-inside-content, div[class*="lis"], td, .cour-list li') : nodes[i].parentNode;
if (row) {
var link = row.querySelector('a[href*="course_ware"], a[href*="cid="], a[href*="course.aspx"]');
if (link && link.href) return link;
}
if (nodes[i].href) return nodes[i];
return nodes[i];
}
}
}
// 兜底:页面上任何指向其他课件/项目的链接
var links = document.querySelectorAll('a[href*="course_ware"], a[href*="cid="], a[href*="course.aspx"]');
for (var k = 0; k < links.length; k++) {
var href = links[k].href || '';
if (href.indexOf('cwid=') !== -1 || href.indexOf('cid=') !== -1) return links[k];
}
return null;
}
function clickexam() {
setTimeout(function () {
var j = document.querySelector("#jrks");
if (j) j.click();
}, (Math.ceil(Math.random() * randomX)));
}
// ===================== 倍速功能已移除(v1.9) =====================
function addinfo() {
debugLog("ℹ️ 视频页面初始化完成,运行正常");
}
function cleanKeyStorage() {
localStorage.removeItem(keyTest);
localStorage.removeItem(keyResult);
localStorage.removeItem(keyTestAnswer);
localStorage.removeItem(keyRightAnswer);
}
function examherftest() {
try {
var jrks = document.getElementById("jrks");
var hreftest = jrks ? jrks.attributes["disabled"] : undefined;
var topPlay = document.querySelectorAll("i[id='top_play']")[0];
if (!topPlay) return;
var state = topPlay.parentNode.nextElementSibling.nextElementSibling.nextElementSibling.innerText;
// 视频播完,考试入口已就绪
if (state == "待考试") {
if (!examClicked) {
examClicked = true;
debugLog("📌 视频播放完成,自动进入考试");
try { clickexam(); } catch (error) { }
}
return;
}
// 本章已完成或未锁考试入口 → 寻找下一个视频
if (state == "已完成" || !hreftest) {
debugLog("📌 本章已完成,寻找下一个视频");
var lis = document.querySelectorAll("li[class='lis-inside-content']");
var targetElements = document.querySelectorAll("i[id='top_play']");
if (!targetElements[0]) return;
var grandparentElement = targetElements[0].parentElement.parentElement;
var index = Array.from(lis).findIndex(function (li) { return li === grandparentElement; });
if (index + 2 <= lis.length) {
index += 2;
var h2 = document.querySelector("#top_body > div.video-container > div.page-container > div.page-content > ul > li:nth-child(" + index + ") > h2");
if (h2) h2.click();
setTimeout(function () {
try {
var b = document.evaluate("//button[contains(., '知道了')]", document, null, XPathResult.ANY_TYPE).iterateNext();
if (b) b.click();
} catch (e) { }
}, 2000);
} else {
// 同课件无更多视频:自动进入下一个课程/课件(不依赖 jQuery)
var nextEl = findNextCourseEntry();
if (nextEl) {
try {
if (nextEl.href) { window.location.href = nextEl.href; }
else { nextEl.click(); }
debugLog("➡️ 自动进入下一课程/视频");
} catch (e) { debugLog("⚠️ 进入下一课程失败:" + (e && e.message)); }
} else {
debugLog("✅ 本课程全部视频已完成!");
clearInterval(clock);
onProjectComplete();
}
}
}
} catch (e) {
// 元素尚未就绪时静默重试
}
}
function sleep(timeout) { return new Promise(function (resolve) { setTimeout(resolve, timeout); }); }
function asynckillsendQuestion() {
(async function () {
var tries = 0;
while ((!window.player || !window.player.sendQuestion) && tries < 600) {
await sleep(20);
tries++;
}
if (window.player && window.player.sendQuestion) {
player.sendQuestion = function () { };
}
})();
}
function killsendQuestion2() { if (typeof (isInteraction) != "undefined") isInteraction = "off"; }
function killsendQuestion3() {
setInterval(async function () {
try { if (hasJQ() && $('.pv-ask-head').length > 0) $(".pv-ask-skip").click(); } catch (err) { }
try { if (hasJQ() && $('.signBtn').length > 0) $(".signBtn").click(); } catch (err) { }
try { if (hasJQ() && $("button[onclick='closeProcessbarTip()']").length > 0) { $("button[onclick='closeBangZhu()']").click(); $("button[onclick='closeProcessbarTip()']").click(); } } catch (err) { }
try { if (hasJQ() && $("button[class='btn_sign']").length > 0) $("button[class='btn_sign']").click(); } catch (err) { }
try {
var topPlay = document.querySelectorAll("i[id='top_play']")[0];
if (!topPlay) return;
var state = topPlay.parentNode.nextElementSibling.nextElementSibling.nextElementSibling.innerText;
var v = document.querySelector('video');
if (!v) return;
if (v.paused && state != "已完成" && state != "待考试") {
if (hasJQ()) { $(v).get(0).play(); $(v).prop('muted', true); }
else { v.play().catch(function () { }); v.muted = true; }
} else if (state == "已完成") {
try { v.pause(); } catch (e) { }
}
} catch (err) { }
}, 10000);
}
// ===================== 批量学习(按学科选课 + 自动逐个学习答题) =====================
function getDisciplines() {
var out = [];
var lis = document.querySelectorAll('.sut_lis');
Array.prototype.forEach.call(lis, function (li) {
var p = li.querySelector('.sut_p');
if (!p) return;
var name = p.innerText.trim();
if (name) out.push(name);
});
return out;
}
function findDisciplineLi(name) {
var lis = document.querySelectorAll('.sut_lis');
for (var i = 0; i < lis.length; i++) {
var p = lis[i].querySelector('.sut_p');
if (p && p.innerText.trim() === name) return lis[i];
}
return null;
}
function scrapeProjects() {
var out = [];
var cards = document.querySelectorAll('.jet_lis');
Array.prototype.forEach.call(cards, function (card) {
var a = card.querySelector('a[href*="course.aspx?cid="]');
if (!a) return;
var href = a.getAttribute('href') || '';
var m = href.match(/cid=([^&]+)/);
if (!m) return;
var t = card.querySelector('.test_tit');
out.push({ cid: m[1], title: t ? t.innerText.trim() : m[1] });
});
return out;
}
function getQueue() { try { return JSON.parse(localStorage.getItem(keyBatchQueue)) || []; } catch (e) { return []; } }
function setQueue(q) { try { localStorage.setItem(keyBatchQueue, JSON.stringify(q)); } catch (e) {} }
function renderDisciplines() {
var wrap = document.getElementById('jj-disc-wrap');
if (!wrap) return;
var discs = getDisciplines();
if (!discs.length) { wrap.innerHTML = '未检测到学科分类(请确认在「项目筛选」页面)
'; return; }
wrap.innerHTML = '';
discs.forEach(function (d) {
var lbl = document.createElement('label');
lbl.className = 'jj-disc';
lbl.innerHTML = '' + d + '';
wrap.appendChild(lbl);
});
}
function getCheckedDisciplines() {
var arr = [];
var boxes = document.querySelectorAll('#jj-disc-wrap input:checked');
Array.prototype.forEach.call(boxes, function (b) { arr.push(b.value); });
return arr;
}
function addToQueue(items) {
var q = getQueue();
var seen = {};
q.forEach(function (i) { seen[i.cid] = true; });
var added = 0;
items.forEach(function (it) {
if (it.cid && !seen[it.cid]) { q.push({ cid: it.cid, title: it.title, done: false }); seen[it.cid] = true; added++; }
});
setQueue(q); renderQueue();
return added;
}
function renderQueue() {
var box = document.getElementById('jj-queue');
if (!box) return;
var q = getQueue();
var idx = parseInt(localStorage.getItem(keyBatchIndex) || '-1', 10);
if (!q.length) { box.innerHTML = '队列为空,先抓取项目
'; return; }
box.innerHTML = '';
q.forEach(function (it, i) {
var d = document.createElement('div');
d.className = 'jj-queue-item' + (it.done ? ' done' : '') + (i === idx ? ' cur' : '');
d.innerHTML = '' + (i + 1) + '.' + (it.done ? '✅ ' : '') + it.title + '';
box.appendChild(d);
});
}
async function grabByDisciplines() {
var names = getCheckedDisciplines();
if (!names.length) { window.debugLog('⚠️ 请先勾选至少一个学科'); return; }
window.debugLog('🔍 按 ' + names.length + ' 个学科抓取项目…');
for (var i = 0; i < names.length; i++) {
var li = findDisciplineLi(names[i]);
if (!li) { window.debugLog('⚠️ 未找到学科:' + names[i]); continue; }
try { li.click(); } catch (e) {}
var child = li.querySelector('.child_p'); // 如「护理学全部」
if (child) { try { child.click(); } catch (e) {} }
await sleep(2200);
var ps = scrapeProjects();
var n = addToQueue(ps);
window.debugLog('📥 [' + names[i] + '] 新增 ' + n + ' 个(本页共 ' + ps.length + ')');
}
window.debugLog('✅ 学科抓取完成,队列共 ' + getQueue().length + ' 个项目');
}
function clickByText(txts) {
var all = document.querySelectorAll('a,button,input');
for (var i = 0; i < all.length; i++) {
var t = (all[i].innerText || all[i].value || '').trim();
for (var j = 0; j < txts.length; j++) { if (t.indexOf(txts[j]) !== -1) return all[i]; }
}
return null;
}
function handleProjectDetail() {
if (localStorage.getItem(keyBatchRunning) !== '1') return;
if (!pathHas('course.aspx') || pathHas('course_ware')) return; // 仅项目详情页生效,防止跳转后误触发
// 1) 直接点「立即学习 / 开始学习」等入口
var btn = clickByText(['立即学习', '开始学习', '去学习', '进入学习', '参加学习']);
if (btn) {
try {
if (btn.href) { navTo(btn.href); }
else { btn.click(); }
window.debugLog('📂 点击「' + (btn.innerText || btn.value || '学习') + '」进入学习');
return;
} catch (e) {}
}
// 2) 课件/课程列表:找第一个未完成项进入
var items = document.querySelectorAll('li.lis-inside-content, .course, .jet_lis, .cour-list li');
for (var i = 0; i < items.length; i++) {
var it = items[i];
var status = (it.innerText || '');
if (status.indexOf('已完成') !== -1 || status.indexOf('待考试') !== -1) continue;
if (status.indexOf('选修') !== -1 || status.indexOf('互动') !== -1) continue;
var link = it.querySelector('a[href*="course_ware"], h2[onclick], a');
if (link) {
try {
if (link.href) { navTo(link.href); }
else if (typeof link.onclick === 'function') { link.onclick(); }
else { link.click(); }
window.debugLog('📂 进入课件学习');
return;
} catch (e) {}
}
}
window.debugLog('ℹ️ 项目详情页未找到自动学习入口(可点「下一项目」手动推进)');
}
function navTo(u) { window.location.href = u; }
function startBatch() {
var q = getQueue();
if (!q.length) { window.debugLog('⚠️ 队列为空,请先抓取项目'); return; }
if (q.every(function (i) { return i.done; })) { q.forEach(function (i) { i.done = false; }); setQueue(q); }
localStorage.setItem(keyBatchRunning, '1');
window.debugLog('🚀 开始批量学习,共 ' + q.length + ' 个项目');
advanceBatch(false);
}
function stopBatch() { localStorage.setItem(keyBatchRunning, '0'); window.debugLog('⏹ 已暂停批量学习(当前项目继续,点开始学习可继续)'); renderQueue(); }
function clearBatch() { setQueue([]); localStorage.setItem(keyBatchIndex, '-1'); renderQueue(); window.debugLog('🗑️ 队列已清空'); }
function advanceBatch(force) {
if (localStorage.getItem(keyBatchRunning) !== '1') return;
var q = getQueue();
var idx = -1;
for (var i = 0; i < q.length; i++) { if (!q[i].done) { idx = i; break; } }
if (idx === -1) {
localStorage.setItem(keyBatchRunning, '0');
window.debugLog('🎉 全部项目学习完成!');
renderQueue();
return;
}
localStorage.setItem(keyBatchIndex, String(idx));
renderQueue();
var item = q[idx];
window.debugLog('➡️ 进入项目 ' + (idx + 1) + '/' + q.length + ':' + item.title);
navTo(location.origin + '/pages/course.aspx?cid=' + item.cid);
}
function markCurrentProjectDone() {
var q = getQueue();
var idx = parseInt(localStorage.getItem(keyBatchIndex) || '-1', 10);
if (idx >= 0 && q[idx]) { q[idx].done = true; setQueue(q); renderQueue(); }
}
function onProjectComplete() {
if (localStorage.getItem(keyBatchRunning) !== '1') return;
markCurrentProjectDone();
setTimeout(function () { advanceBatch(false); }, 1500);
}
function initBatchUI() {
var batchSec = document.getElementById('jj-batch');
if (!batchSec) return;
renderDisciplines();
renderQueue();
var refresh = document.getElementById('jj-batch-refresh');
if (refresh) refresh.onclick = function () { renderDisciplines(); renderQueue(); window.debugLog('🔄 已刷新学科与队列'); };
var grab = document.getElementById('jj-grab');
if (grab) grab.onclick = function () { var ps = scrapeProjects(); var n = addToQueue(ps); window.debugLog('📥 本页抓取 ' + n + ' 个新项目(本页共 ' + ps.length + ')'); };
var grabD = document.getElementById('jj-grab-disc');
if (grabD) grabD.onclick = grabByDisciplines;
var start = document.getElementById('jj-batch-start');
if (start) start.onclick = startBatch;
var next = document.getElementById('jj-batch-next');
if (next) next.onclick = function () { markCurrentProjectDone(); advanceBatch(false); };
var stop = document.getElementById('jj-batch-stop');
if (stop) stop.onclick = stopBatch;
var clear = document.getElementById('jj-batch-clear');
if (clear) clear.onclick = clearBatch;
}
})();