// ==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
自动刷课

打开课程视频即可自动静音播放,无需任何手动操作。

' + '
2
AI 智能答题(直答)

已默认开启并预填 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 + '二维码
(图片加载失败)
' + '
' + 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 = '
' + '
' + '
爱国者 · 华医网助手
v3.3 · AI 直答
' + '
?
' + '
' + '
' + '
' + '
' + '
加载中...
' + '
' + '
主控
' + '
AI 设置
' + '
工具
' + '
日志
' + '
' + '
' + '
' + '
🤖 AI 智能答题
' + '
自动读题 · AI 作答 · 提交
' + '
进入答题页自动读题交 AI 选答案并提交,未通过自动重考;接口配置见「AI 设置」。
' + '
' + '
' + '
🎬 视频控制
' + '
🔊 自动静音
' + '
' + '
' + '
📚 批量学习
' + '
按学科选课↻ 刷新
' + '
' + '
' + '
抓取本页项目
' + '
按勾选学科抓取
' + '
' + '
' + '
' + '
▶ 开始学习
' + '
下一项目 ⏭
' + '
' + '
' + '
⏹ 暂停
' + '
清空队列
' + '
' + '
' + '
🔓 强制获取/作答
' + '
' + '
' + '
' + '
🔑 AI 接口配置
' + '
' + ' ' + ' ' + ' ' + '
🧪 测试 AI 连接
' + '
' + '
默认「智谱 GLM · glm-4-flash」国内直连免费,Key 已预填,可点测试验证。⚠️ Groq 国内常被 403;SiliconFlow 需充值。
' + '
' + '
' + '
' + '
' + '
📷 OCR 扫描(图片题)
' + '
' + '
📷 遮罩框选扫描题目
' + '
' + ' ' + ' 或 Ctrl+V 粘贴截图' + '
' + ' ' + ' ' + '
' + ' 题号' + ' ' + '
' + '
📷 扫描并作答
' + '
' + '
' + '
文字题无需此功能。框选后优先直读该区域文字(免 OCR);确为纯图片题才走 OCR(云端需 Key,本地可能被站点 CSP 拦截)。
' + '
' + '
' + '
🔍 适配探针
' + '
🔍 导出题目 DOM
' + '
新页面识别失败时,导出题目区 HTML 发给开发者校准。
' + '
' + '
' + '
' + '
' + '
📜 运行日志
' + ' ' + '
' + '
📋 复制诊断日志
' + '
' + ' ' + '
'; 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; } })();