// ==UserScript== // @name TiKu 智能题库助手 // @namespace https://tiku.help // @version 2.2.1 // @description 支持超星学习通、智慧树等主流网课平台的AI自动搜题助手。绿色主题,悬浮球操作。 // @author TiKu Team // @homepage https://tiku.help/console/tools // @icon https://tiku.help/logo.svg // @match http://*.chaoxing.com/* // @match https://*.chaoxing.com/* // @match http://*.edu.cn/* // @match https://*.edu.cn/* // @match http://*.zhihuishu.com/* // @match https://*.zhihuishu.com/* // @match http://*.xueyinonline.com/* // @match https://*.xueyinonline.com/* // @match http://*.tiku.help/* // @match https://*.tiku.help/* // @match http://*.icve.com.cn/* // @match https://*.icve.com.cn/* // @match http://*.yuketang.cn/* // @match https://*.yuketang.cn/* // @match http://*.icourse163.org/* // @match https://*.icourse163.org/* // @match http://*.courshare.cn/* // @match https://*.courshare.cn/* // @match http://*.webtrn.cn/* // @match https://*.webtrn.cn/* // @match http://*/* // @match https://*/* // @connect tiku.help // @connect cdn.ocsjs.com // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @grant GM_notification // @grant unsafeWindow // @run-at document-end // @license MIT // @original-author enncy // @original-license MIT // @original-script https://github.com/enncy/ocsjs // ==/UserScript== (function() { 'use strict'; // ─── OCS (ocsjs) Copyright (c) 2022 enncy, MIT License ─── // 本脚本匹配引擎、平台适配、刷课流程等参考 ocsjs-4.0 // https://github.com/enncy/ocsjs // ═══════════════════════════════════ // 防 iframe 重复注入 (OCS: self !== top) // 视频/子页面 iframe 会匹配 @match,造成双悬浮球 // ═══════════════════════════════════ if (self !== top) return; // 脚本猫 / Tampermonkey 兼容检测(参考 OCS) if (typeof globalThis !== 'undefined') { var neededAPIs = ['GM_setValue','GM_getValue','GM_info','unsafeWindow']; var missing = neededAPIs.some(function(api) { try { return typeof Reflect.get(globalThis, api) === 'undefined'; } catch(e) { return true; } }); if (missing) { alert('TiKu 助手:当前脚本管理器版本过低或权限不足,请使用 脚本猫 或 Tampermonkey。'); } } // ═══════════════════════════════════ // MUST BE FIRST - 确认脚本已加载 // ═══════════════════════════════════ if(typeof console !== 'undefined') { console.log('[TiKu] 脚本已注入,开始初始化...'); } // ═══════════════════════════════════ // CONSTANTS // ═══════════════════════════════════ var VERSION = '2.2.1'; var API = 'https://tiku.help'; var STORE = 'tiku_v2_'; // ═══════════════════════════════════ // STORAGE // ═══════════════════════════════════ function load(k, d) { try { var v = GM_getValue(STORE + k); return v !== undefined ? v : d; } catch(e) { return d; } } function save(k, v) { try { GM_setValue(STORE + k, v); } catch(e){} } // ═══════════════════════════════════ // ANSWER CACHE (防刷新丢失) // ═══════════════════════════════════ var CACHE_KEY = 'anscache'; var CACHE_TTL = 2*60*60*1000; // 2 小时过期 function getPageId(){ // Use URL pathname as page scope return location.pathname.replace(/\/+$/,'').replace(/\//g,'_'); } function getCache(){ try { return JSON.parse(GM_getValue(STORE + CACHE_KEY, '{}')); } catch(e){ return {}; } } function setCache(c){ try { GM_setValue(STORE + CACHE_KEY, JSON.stringify(c)); } catch(e){} } // Cache key: pageId + title (truncated to avoid too-long keys) function _cacheKey(item){ var pageId = getPageId(); var t = clean((item.title||'').slice(0,80)); return pageId + '|' + t; } function saveAnswerCache(item){ if(!item.answer) return; var cache = getCache(); cache[_cacheKey(item)] = { answer: item.answer, type: item.type, time: Date.now() }; setCache(cache); } function getAnswerCache(item){ var cache = getCache(); var entry = cache[_cacheKey(item)]; if(!entry) return null; // Check TTL if(Date.now() - entry.time > CACHE_TTL){ delete cache[_cacheKey(item)]; setCache(cache); return null; } return entry; } function clearAnswerCache(){ try { GM_setValue(STORE + CACHE_KEY, '{}'); } catch(e){} toast('答案缓存已清除','info'); } var S = load('settings', null); var DEF = { apiKey:'', autoAnswer:true, humanMode:true, humanDelayMin:1500, humanDelayMax:5000, retryOnFail:true, retryMax:2, playbackRate:1.5, autoNext:true, volume:1 }; var SET = S || DEF; function saveS(){ save('settings', SET); } // ═══════════════════════════════════ // HELPERS // ═══════════════════════════════════ function sleep(t){ return new Promise(function(r){ setTimeout(r,t); }); } function rand(a,b){ return Math.floor(Math.random()*(b-a+1))+a; } function clean(t){ return (t||'').replace(/\s+/g,' ').replace(/[\u200B-\u200F\uFEFF]/g,'').trim(); } function esc(s){ return String(s||'').replace(/&/g,'&').replace(//g,'>'); } // ═══════════════════════════════════ // API // ═══════════════════════════════════ function apiQuery(q, type, opts, key){ key = key || SET.apiKey; return new Promise(function(ok, fail){ var p = new URLSearchParams(); p.set('title', q); if(type) p.set('type', type); if(opts) p.set('options', Array.isArray(opts) ? opts.join('\n') : opts); GM_xmlhttpRequest({ method:'GET', url: API+'/api/answer/query?'+p.toString(), headers:{'x-api-key':key,'Accept':'application/json'}, timeout:12000, onload: function(r){ try { var d=JSON.parse(r.responseText); if(d.answer) ok({answer:d.answer,source:d.source,confidence:d.confidence}); else fail(new Error(d.msg||'未找到')); } catch(e){ fail(new Error('解析失败')); } }, onerror: function(){ fail(new Error('网络错误')); }, ontimeout: function(){ fail(new Error('超时')); } }); }); } function testConn(key, cb){ var t = Date.now(); GM_xmlhttpRequest({ method:'GET', url: API+'/api/answer/query?title=test&type=single', headers:{'x-api-key':key||SET.apiKey,'Accept':'application/json'}, timeout:8000, onload: function(r){ var ms = Date.now()-t; try { var d=JSON.parse(r.responseText); if(r.status===200&&(d.answer||d.msg)) cb(true,ms,''); else cb(false,ms,d.msg||'状态'+r.status); } catch(e){ cb(false,ms,'解析失败'); } }, onerror: function(){ cb(false, Date.now()-t, '连接失败'); }, ontimeout: function(){ cb(false, Date.now()-t, '超时'); } }); } // ═══════════════════════════════════ // PLATFORM DETECTION // ═══════════════════════════════════ function isCX(){ return /chaoxing\.com|edu\.cn|xueyinonline/i.test(location.hostname); } function isZHS(){ return /zhihuishu/i.test(location.hostname); } function isICVE(){ return /icve\.com\.cn|course\.icve|mooc\.icve|ai\.icve|courshare\.cn|webtrn\.cn/i.test(location.hostname); } function isMOOC(){ return /icourse163/i.test(location.hostname); } function isZJY(){ return /zjy2\.icve|zyk\.icve/i.test(location.hostname); } function isYKT(){ return /yuketang/i.test(location.hostname); } function platform(){ if(isZJY()) return 'zjy'; if(isICVE()) return 'icve'; if(isCX()) return 'cx'; if(isZHS()) return 'zhs'; if(isYKT()) return 'ykt'; if(isMOOC()) return 'mooc'; return 'other'; } function supported(){ return platform() !== 'other'; } // ═══════════════════════════════════ // CSS // ═══════════════════════════════════ (function(){ var s = document.createElement('style'); s.id = 'tiku-style'; s.textContent = [ '/* --- FLOAT BALL --- */', '#tiku-ball { position:fixed; z-index:2147483646; bottom:100px; right:24px; width:48px; height:48px; border-radius:50%;', ' background:linear-gradient(135deg,#10B981,#059669); box-shadow:0 4px 16px rgba(16,185,129,.4); cursor:pointer;', ' display:flex; align-items:center; justify-content:center; transition:transform .25s,box-shadow .25s; user-select:none; }', '#tiku-ball:hover { transform:scale(1.12); box-shadow:0 6px 24px rgba(16,185,129,.55); }', '#tiku-ball svg { width:22px; height:22px; fill:#fff; }', '#tiku-ball .badge { position:absolute; top:-4px; right:-4px; background:#ED6A5E; color:#fff; font-size:10px; min-width:18px;', ' height:18px; border-radius:9px; display:flex; align-items:center; justify-content:center; font-weight:700; }', '/* --- MASK --- */', '#tiku-mask { position:fixed; inset:0; z-index:2147483645; background:none;', ' opacity:0; pointer-events:none; transition:opacity .25s; }', '#tiku-mask.on { opacity:1; pointer-events:none; }', '/* --- PANEL --- */', '#tiku-panel { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; border-radius:16px;', ' box-shadow:0 16px 48px rgba(0,0,0,.18); width:420px; max-width:92vw;', ' max-height:82vh; display:flex; flex-direction:column; overflow:hidden; font-family:"PingFang SC","Microsoft YaHei",sans-serif;', ' font-size:13px; color:#191919; pointer-events:auto; }', '/* --- HEADER --- */', '.tp-hdr { display:flex; align-items:center; justify-content:space-between; padding:14px 18px; cursor:move;', ' background:linear-gradient(135deg,#ECFDF5,#F7F7F7); border-bottom:1px solid #EDEDED; user-select:none; }', '.tp-hdr .brand { display:flex; align-items:center; gap:8px; }', '.tp-hdr .brand b { font-size:15px; font-weight:700; color:#059669; }', '.tp-hdr .close { width:30px; height:30px; border:none; background:none; cursor:pointer; border-radius:8px; font-size:16px;', ' color:#B0B0B0; display:flex; align-items:center; justify-content:center; transition:.2s; }', '.tp-hdr .close:hover { background:#F5F5F5; color:#333; }', '/* --- TABS --- */', '.tp-tabs { display:flex; border-bottom:1px solid #EDEDED; background:#FAFAFA; padding:0 12px; }', '.tp-tab { padding:11px 18px; border:none; background:none; font-size:13px; color:#888; cursor:pointer;', ' border-bottom:2px solid transparent; transition:.2s; white-space:nowrap; }', '.tp-tab:hover { color:#10B981; }', '.tp-tab.sel { color:#10B981; border-bottom-color:#10B981; font-weight:600; }', '/* --- BODY --- */', '.tp-body { flex:1; overflow-y:auto; padding:16px; }', '.tp-body::-webkit-scrollbar { width:4px; }', '.tp-body::-webkit-scrollbar-thumb { background:#DDD; border-radius:2px; }', '/* --- SECTIONS --- */', '.tp-sec { margin-bottom:18px; }', '.tp-sec-tt { font-size:11px; font-weight:700; color:#B0B0B0; text-transform:uppercase; letter-spacing:.08em;', ' padding-bottom:6px; border-bottom:1px solid #F0F0F0; margin-bottom:10px; }', '/* --- FIELD --- */', '.tp-fld { margin-bottom:10px; }', '.tp-lbl { display:block; font-size:12px; color:#888; margin-bottom:4px; }', '.tp-inp { width:100%; padding:8px 12px; border:1px solid #E0E0E0; border-radius:8px; font-size:13px; color:#333;', ' outline:none; background:#FAFAFA; box-sizing:border-box; transition:.2s; }', '.tp-inp:focus { border-color:#10B981; background:#fff; }', '.tp-sel { width:100%; padding:8px 12px; border:1px solid #E0E0E0; border-radius:8px; font-size:13px; color:#333;', ' outline:none; background:#FAFAFA; cursor:pointer; }', '/* --- BUTTONS --- */', '.tp-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px;', ' padding:8px 20px; border:none; border-radius:20px; font-size:13px; font-weight:500; cursor:pointer; transition:.2s; }', '.tp-btn-pr { background:#10B981; color:#fff; } .tp-btn-pr:hover { background:#059669; }', '.tp-btn-se { background:#F0F0F0; color:#333; } .tp-btn-se:hover { background:#E5E5E5; }', '.tp-btn-ol { border:1px solid #10B981; color:#10B981; background:transparent; } .tp-btn-ol:hover { background:#ECFDF5; }', '.tp-btn-sm { padding:5px 14px; font-size:12px; }', '.tp-btn-xs { padding:3px 10px; font-size:11px; border-radius:14px; }', '.tp-btn:disabled { opacity:.45; cursor:not-allowed; }', '/* --- SWITCH --- */', '.tp-swi { display:flex; align-items:center; justify-content:space-between; padding:10px 0; border-bottom:1px solid #F5F5F5; }', '.tp-swi:last-child { border-bottom:none; }', '.tp-swi-l { font-size:13px; color:#333; }', '.tp-swi-d { font-size:11px; color:#B0B0B0; margin-top:2px; }', '.tp-swi-k { width:44px; height:24px; border-radius:12px; background:#DFDFDF; cursor:pointer; position:relative;', ' flex-shrink:0; transition:background .25s; }', '.tp-swi-k.on { background:#10B981; }', '.tp-swi-k::after { content:""; position:absolute; top:2px; left:2px; width:20px; height:20px; border-radius:50%;', ' background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.12); transition:transform .25s; }', '.tp-swi-k.on::after { transform:translateX(20px); }', '/* --- STATUS --- */', '.tp-st { display:inline-flex; align-items:center; gap:6px; padding:4px 12px; border-radius:20px; font-size:12px; }', '.tp-st-ok { background:#ECFDF5; color:#059669; } .tp-st-err { background:#FEF2F2; color:#DC2626; }', '.tp-st-id { background:#F5F5F5; color:#999; }', '.tp-st-d { width:6px; height:6px; border-radius:50%; }', '.tp-st-ok .tp-st-d { background:#10B981; } .tp-st-err .tp-st-d { background:#EF4444; }', '.tp-st-id .tp-st-d { background:#CCC; }', '/* --- EXAM CARDS --- */', '.tp-qc { border:1px solid #F0F0F0; border-radius:10px; padding:12px; margin-bottom:8px; background:#FAFAFA; transition:.2s; }', '.tp-qc.sr { border-color:#60A5FA; background:#EFF6FF; }', '.tp-qc.ok { border-color:#10B981; background:#ECFDF5; }', '.tp-qc.er { border-color:#FCA5A5; background:#FEF2F2; }', '.tp-qc .qt { font-weight:600; font-size:13px; margin-bottom:4px; }', '.tp-qc .qa { color:#059669; font-size:12px; margin-top:4px; }', '.tp-qc .qe { color:#DC2626; font-size:12px; margin-top:4px; }', '.tp-qc .qm { font-size:11px; color:#999; margin-top:6px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }', '/* --- TOAST --- */', '#tiku-toast { position:fixed; top:16px; left:50%; transform:translate(-50%,-8px); z-index:2147483647;', ' padding:10px 22px; border-radius:20px; font-size:13px; color:#fff; opacity:0; transition:opacity .3s,transform .3s;', ' pointer-events:none; font-weight:500; }', '#tiku-toast.sh { opacity:1; transform:translate(-50%,0); }', '#tiku-toast.info { background:#10B981; }', '#tiku-toast.warn { background:#F59E0B; }', '#tiku-toast.err { background:#EF4444; }', '/* --- FOOTER --- */', '.tp-foot { text-align:center; padding:10px; font-size:11px; color:#CCC; border-top:1px solid #F5F5F5; }', '.tp-foot a { color:#10B981; text-decoration:none; }', '.tp-foot .upd { color:#F59E0B; cursor:pointer; } .tp-foot .upd:hover { text-decoration:underline; }', '/* --- EMPTY --- */', '.tp-empty { text-align:center; padding:24px; color:#CCC; font-size:13px; }', '/* --- PROGRESS --- */', '.tp-prog { display:flex; gap:4px; height:4px; margin-bottom:12px; border-radius:2px; overflow:hidden; }', '.tp-prog-bar { height:100%; transition:.3s; }', '.tp-prog-ok { background:#10B981; } .tp-prog-er { background:#EF4444; } .tp-prog-pd { background:#E5E5E5; }', '/* --- PROGRESS TEXT --- */', '.tp-prog-txt { font-size:12px; color:#666; text-align:center; margin:4px 0 8px; font-weight:500; }', '/* --- ACCORDION --- */', '.tp-acc-group { margin-bottom:6px; }', '.tp-acc-hd { display:flex; align-items:center; justify-content:space-between; padding:8px 12px;', ' background:#F7F7F7; border-radius:8px; cursor:pointer; font-size:12px; font-weight:600; color:#666; user-select:none; transition:background .2s; }', '.tp-acc-hd:hover { background:#ECFDF5; }', '.tp-acc-hd .ac { font-size:10px; transition:transform .25s; }', '.tp-acc-hd.open .ac { transform:rotate(180deg); }', '.tp-acc-body { overflow:hidden; max-height:3000px; transition:max-height .35s ease; }', '.tp-acc-body.cl { max-height:0; }', ].join('\n'); document.head.appendChild(s); })(); // ═══════════════════════════════════ // TOAST // ═══════════════════════════════════ var toastTmr, toastEl; function toast(msg, type){ if(!toastEl){ toastEl=document.createElement('div'); toastEl.id='tiku-toast'; document.body.appendChild(toastEl); } toastEl.textContent=msg; toastEl.className=(type||'info'); toastEl.classList.add('sh'); clearTimeout(toastTmr); toastTmr=setTimeout(function(){ toastEl.classList.remove('sh'); }, 2200); } // ═══════════════════════════════════ // FLOAT BALL // ═══════════════════════════════════ var ball, mask, panel; function makeBall(){ if(!document.body){ console.warn('[TiKu] document.body 未就绪,延迟重试'); setTimeout(makeBall, 200); return; } ball=document.createElement('div'); ball.id='tiku-ball'; ball.innerHTML=''; ball.title='点击打开TiKu助手'; ball.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation(); open(); }); document.body.appendChild(ball); } // ═══════════════════════════════════ // PANEL // ═══════════════════════════════════ var tab = 'settings'; function makePanel(){ mask=document.createElement('div'); mask.id='tiku-mask'; panel=document.createElement('div'); panel.id='tiku-panel'; mask.appendChild(panel); document.body.appendChild(mask); } function open(){ if(mask.classList.contains('on')) { close(); return; } mask.classList.add('on'); render(); } function close(){ mask.classList.remove('on'); } function setTab(t){ tab=t; render(); } function render(){ var tabs=[{k:'settings',n:'设置'},{k:'exam',n:'考试助手'},{k:'course',n:'刷课助手'}]; panel.innerHTML=[ '
', '
TiKu 助手
', '', '
', '
'+tabs.map(function(t){ return ''; }).join('')+'
', '
', '
v'+VERSION+' · 帮助
' ].join(''); panel.querySelector('#tp-cls').addEventListener('click',close); panel.querySelectorAll('.tp-tab').forEach(function(b){ b.addEventListener('click',function(){ setTab(this.dataset.tab); }); }); // 拖动:按住标题栏拖动面板,松手停止 var hdr=panel.querySelector('.tp-hdr'); var startX,startY,origL,origT; hdr.addEventListener('mousedown',function(e){ if(e.target.tagName==='BUTTON') return; e.preventDefault(); var r=panel.getBoundingClientRect(); startX=e.clientX; startY=e.clientY; origL=r.left; origT=r.top; function onMove(ev){ panel.style.transform='none'; panel.style.left=(origL+ev.clientX-startX)+'px'; panel.style.top=(origT+ev.clientY-startY)+'px'; } function onUp(){ document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp); } document.addEventListener('mousemove',onMove); document.addEventListener('mouseup',onUp); }); var body=panel.querySelector('#tp-body'); if(tab==='settings') renderSettings(body); else if(tab==='exam') renderExam(body); else if(tab==='course') renderCourse(body); // Version check GM_xmlhttpRequest({ method:'GET', url:API+'/widget/tiku-widget.user.js?v='+VERSION+'&t='+Date.now(), timeout:6000, onload:function(r){ var m=r.responseText.match(/@version\s+(\d+\.\d+\.\d+)/); if(m && m[1]!==VERSION){ var ft=panel.querySelector('#tp-foot'); if(ft) ft.innerHTML='v'+VERSION+' · 更新 v'+m[1]+' · 帮助'; var upd=panel.querySelector('#tp-upd'); if(upd) upd.addEventListener('click',function(){ window.open(API+'/console/tools','_blank'); }); } } }); } // ═══════════════════════════════════ // HELPERS: Switch // ═══════════════════════════════════ function mkSwitch(body, id, label, desc, on, cb){ body.querySelector('#'+id+'-row').innerHTML = '
'+label+'
'+desc+'
'; body.querySelector('#'+id).addEventListener('click',function(){ var next=!SET[id.replace('set-','')]; SET[id.replace('set-','')]=next; this.classList.toggle('on',next); saveS(); if(cb) cb(next); }); } // ═══════════════════════════════════ // SETTINGS PANEL // ═══════════════════════════════════ function renderSettings(body){ body.innerHTML = [ '
', '
API 配置
', '
', '', '
', '', '', '
', '
', '
', '', ' 未检测', '
', '
', '还没有 Key?前往 TiKu 获取', '
', '
', '
', '
答题设置
', '
', '
', '
', '
', '', '
', '', '', '', '
', '
', '
', '
', '
数据管理
', '', '
', '
', '
刷课设置
', '
', '', '', '
', '
', '
' ].join(''); mkSwitch(body,'set-autoAnswer','自动答题','搜题后自动填写答案',SET.autoAnswer); mkSwitch(body,'set-humanMode','模拟真人','随机延迟,降低检测风险',SET.humanMode); mkSwitch(body,'set-retryOnFail','失败重试','搜题失败后自动重试',SET.retryOnFail); mkSwitch(body,'set-autoNext','自动下一任务','完成当前任务后自动跳转',SET.autoNext); body.querySelector('#set-key').addEventListener('change',function(){ SET.apiKey=this.value; saveS(); }); body.querySelector('#set-key-vis').addEventListener('click',function(){ var i=body.querySelector('#set-key'); i.type=i.type==='password'?'text':'password'; }); body.querySelector('#set-test').addEventListener('click',function(){ var k=body.querySelector('#set-key').value; if(!k){ toast('请先输入 API Key','warn'); return; } var s=body.querySelector('#set-st'); s.className='tp-st tp-st-id'; s.innerHTML=' 检测中...'; testConn(k,function(ok,ms,err){ if(ok){ s.className='tp-st tp-st-ok'; s.innerHTML=' 连接正常 · '+ms+'ms'; SET.apiKey=k; saveS(); toast('连接成功!'+ms+'ms','info'); } else { s.className='tp-st tp-st-err'; s.innerHTML=' '+err+' · '+ms+'ms'; toast(err,'err'); } }); }); body.querySelector('#set-speed').addEventListener('change',function(){ SET.playbackRate=parseFloat(this.value); saveS(); applySpeed(); }); body.querySelector('#set-dmin').addEventListener('change',function(){ SET.humanDelayMin=parseInt(this.value)||1500; saveS(); }); body.querySelector('#set-dmax').addEventListener('change',function(){ SET.humanDelayMax=parseInt(this.value)||5000; saveS(); }); body.querySelector('#set-clear-cache').addEventListener('click',function(){ clearAnswerCache(); }); } // ═══════════════════════════════════ // QUESTION DETECTION (OCS-REF) // ═══════════════════════════════════ function findQuestions(){ var questions = []; var plat = platform(); var docs = [document]; // Gather all iframe documents document.querySelectorAll('iframe').forEach(function(iframe){ try { var d = iframe.contentDocument || iframe.contentWindow.document; if(d && d.body) docs.push(d); } catch(e){} }); docs.forEach(function(doc){ if(plat === 'cx'){ // 超星: 章节测试 .TiMu doc.querySelectorAll('.TiMu').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.Zy_TItle .clearfix'); var title = titleEl ? clean(titleEl.innerText) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('input[id^="answertype"]'); var val = typeEl ? parseInt(typeEl.value) : -1; var type = val===0?'single':val===1?'multiple':val===3?'judgement':[2,4,5,6,7,8,9,10].indexOf(val)>-1?'completion':val===11?'line':val===14?'fill':val===15?'reader':undefined; var options = []; var optionEls = []; node.querySelectorAll('ul li .after, ul li label:not(.before), ul li textarea, ul textarea').forEach(function(opt){ var t = clean(opt.textContent); if(t) { options.push(t); optionEls.push(opt); } }); // 补充 CKeditor 文本框 node.querySelectorAll('textarea').forEach(function(ta){ if(ta.value) options.push(clean(ta.value)); }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'cx_timu' }); }); // 超星: 作业/考试 .questionLi doc.querySelectorAll('.questionLi').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEls = node.querySelectorAll('h3, .mark_name strong, .splitS-left .mark_name'); var title = ''; titleEls.forEach(function(el){ var t=clean(el.textContent); if(t) title=(title?title+'\n':'')+t; }); if(!title || title.length < 3) return; var typeEl = node.querySelector('input[name^="type"], input[id^="answertype"]'); var val = typeEl ? parseInt(typeEl.value) : -1; var type = val===0?'single':val===1?'multiple':val===3?'judgement':[2,4,5,6,7,8,9,10].indexOf(val)>-1?'completion':val===11?'line':val===14?'fill':val===15?'reader':undefined; var options = []; var optionEls = []; node.querySelectorAll('.answerBg .answer_p, .textDIV, .eidtDiv, .line_answer input[name^=answer]').forEach(function(opt){ var t = clean(opt.textContent||opt.value||''); if(t) { options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'cx_questionli' }); }); } if(plat === 'zhs'){ // 共享课考试/作业 (gxkWorkAndExam) doc.querySelectorAll('.examPaper_subject').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.subject_describe > div, .smallStem_describe > div:nth-child(2), .subject_describe'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('.subject_type'); var type = typeEl ? (typeEl.textContent.indexOf('单选')>-1?'single':typeEl.textContent.indexOf('多选')>-1?'multiple':typeEl.textContent.indexOf('判断')>-1?'judgement':typeEl.textContent.indexOf('填空')>-1?'completion':undefined) : undefined; var options = []; var optionEls = []; node.querySelectorAll('.subject_node .nodeLab').forEach(function(opt){ var t=clean(opt.textContent); if(t) { options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'zhs_gxk' }); }); // smartWork / smartExam: Tailwind 样式题目 doc.querySelectorAll('.subject-box').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.subject-describe, .stem-describe > div:first-child'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('.subject-type, [class*="type"]'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('.option-item, .subject-option, label').forEach(function(opt){ var t=clean(opt.textContent); if(t) { options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'zhs_smart' }); }); // fusioncourse / hike: 多形态考试 doc.querySelectorAll('.exam_item, .course-exam-item, .exam-item, .q_main, .question-item').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.questionContent, .questionName .centent-pre, .quest-title .option-name, .question-topic, .qeustion-content, .combination-content, .stem-content'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('.subject_type, .letterSortNum, .quest-type, .question_score, .title-box, .combination-title'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1||typeTxt.indexOf('问答')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('.optionUl label, .radio-view li.clearfix, .checkbox-views label.el-checkbox, label').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1) { options.push(t); optionEls.push(opt); } }); // 也尝试 .option-item if(options.length===0){ node.querySelectorAll('.option-item').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1) { options.push(t); optionEls.push(opt); } }); } questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'zhs_fusion' }); }); // 校内课 (xnkWork): .questionBox doc.querySelectorAll('.questionBox_info .questionBox, [class*="questionBox"]:not([class*="info"])').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.questionContent, .q_content'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('.subject_type, [class*="type"]'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('label, .option-item, li').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1) { options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'zhs_xnk' }); }); } if(plat === 'icve' || plat === 'zjy'){ // 智慧职教/职教云: .questionLi (vbContent) 或 .testContnet 或 .e-q-body doc.querySelectorAll('.questionLi, .testContnet .questionBox, .e-q-body, .subject-list .subject-item').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.questionTit, .mark_name, .q-title, .subject-title, .subject_describe'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3){ title = clean(node.textContent.split('\n')[0]||'').slice(0,120); } if(!title || title.length < 3) return; var typeEl = node.querySelector('input[name^="type"], input[id^="answertype"], .subject_type, [class*="type"]'); var typeTxt = typeEl ? (typeEl.value || typeEl.textContent || '') : ''; var type = typeTxt.indexOf('0')===0?'single':typeTxt.indexOf('1')===0?'multiple':typeTxt.indexOf('3')===0?'judgement':typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; if(plat === 'zjy'){ // 职教云旧版: label input + span node.querySelectorAll('label input[type="radio"], label input[type="checkbox"]').forEach(function(inp){ var label = inp.closest('label'); if(label){ var t=clean(label.textContent); if(t){ options.push(t); optionEls.push(label); } } }); } if(options.length === 0){ node.querySelectorAll('.answerBg .answer_p, .optionLi label, .option-item, .el-radio, .el-checkbox, label').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1){ options.push(t); optionEls.push(opt); } }); } if(options.length === 0){ node.querySelectorAll('.ivu-radio-wrapper, .ivu-checkbox-wrapper, .answer_item').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1){ options.push(t); optionEls.push(opt); } }); } questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'icve' }); }); // 智慧职教: .e-q-body 题目 doc.querySelectorAll('.e-q-body').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.e-q-header .e-q-title, .e-q-title'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; var typeEl = node.querySelector('.e-q-header .e-q-type'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('.e-q-option .e-q-option-item, .option-item, .e-q-options label').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1){ options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'icve' }); }); } if(plat === 'ykt'){ // 雨课堂: .problem_item 或 .question_item doc.querySelectorAll('.problem_item, .question_item, .exam_question, [class*="problem"]').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.problem_des, .question_name, .stem-content, .q_content, p'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3){ title = clean(node.textContent.split('\n')[0]||'').slice(0,120); } if(!title || title.length < 3) return; var typeEl = node.querySelector('.problem_type, .question_type, [class*="type"]'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('.problem_option label, .option_item, .el-radio, .el-checkbox, label, .answer_item').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1){ options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'ykt' }); }); } if(plat === 'mooc'){ // 中国大学MOOC: .u-questionItem 或 .question-wrap doc.querySelectorAll('.u-questionItem, .question-wrap, .exam-question').forEach(function(node){ if(questions.find(function(q){ return q.element===node; })) return; var titleEl = node.querySelector('.u-questionContent, .question-content, .q-title, .stem'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3){ title = clean(node.textContent.split('\n')[0]||'').slice(0,120); } if(!title || title.length < 3) return; var typeEl = node.querySelector('.u-questionType, .question-type, [class*="type"]'); var typeTxt = typeEl ? typeEl.textContent : ''; var type = typeTxt.indexOf('单选')>-1?'single':typeTxt.indexOf('多选')>-1?'multiple':typeTxt.indexOf('判断')>-1?'judgement':typeTxt.indexOf('填空')>-1?'completion':undefined; var options = []; var optionEls = []; node.querySelectorAll('.u-questionOp .u-questionList-li, .option-item, label, .answer_item').forEach(function(opt){ var t=clean(opt.textContent); if(t && t.length>1){ options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type||guessType(doc,node), options:options, optionEls:optionEls, element:node, doc:doc, plat:'mooc' }); }); } if(plat === 'icve' || plat === 'mooc'){ // 通用检测:找 radio/checkbox/textarea 的父容器 var seen = new Set(); doc.querySelectorAll('input[type="radio"], input[type="checkbox"], textarea').forEach(function(input){ var parent = input.closest('div,form,li,p,fieldset'); if(!parent || seen.has(parent)) return; seen.add(parent); var title = clean(parent.textContent.split('\n')[0]||'').slice(0,120); if(title.length < 5) return; var radios = parent.querySelectorAll('input[type="radio"]'); var checks = parent.querySelectorAll('input[type="checkbox"]'); var textas = parent.querySelectorAll('textarea'); var type = textas.length>0?'completion':checks.length>2?'multiple':radios.length===2?'judgement':radios.length>2?'single':'completion'; var options = []; var optionEls = []; parent.querySelectorAll('label, .option, li').forEach(function(opt){ var t=clean(opt.textContent); if(t) { options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type, options:options, optionEls:optionEls, element:parent, doc:doc, plat:'generic' }); }); } }); return questions; } function guessType(doc, node){ var r=node.querySelectorAll('input[type="radio"]').length; var c=node.querySelectorAll('input[type="checkbox"]').length; var t=node.querySelectorAll('textarea').length; if(t>0) return 'completion'; if(r===2) return 'judgement'; if(c>2) return 'multiple'; if(r>2) return 'single'; return 'completion'; } // ═══════════════════════════════════ // ANSWER FILLING (OCS exact matching logic) // ═══════════════════════════════════ // OCS clearString: strip everything except Chinese, English, numbers function _clear(s, ex){ ex = ex || []; ex.push('①②③④⑤⑥⑦⑧⑨'); return (s||'').trim().toLowerCase().replace(RegExp('[^\\u2e80-\\u9fffA-Za-z0-9'+ex.join('')+']+','g'),''); } // OCS normalizeString function _norm(s){ return _clear((s||'') .replace(/[,。!?;:""''、()【】《》\s]/g,'') .replace(/[,.\-!?;:'"()[\]<>]/g,'') .replace(/[a-zA-Z0-9]/g,function(c){ return String.fromCharCode(c.charCodeAt(0)-0xfee0); }) .replace(/%/g,'%') ); } // OCS removeRedundant: strip option prefix function _strip(s){ return (s||'').replace(/^[A-H][..、::\s]+/i,'').replace(/^[((][A-H][))]\s*/i,'').trim(); } // OCS answer-to-option match (保留作为兜底) function _matchOpt(ansText, optText, loose){ var a = _norm(_strip(ansText)); var o = _norm(_strip(optText)); if(!a || !o) { console.log('[TiKu] match: empty after norm ans="'+a+'" opt="'+o+'"'); return false; } if(a === o) { console.log('[TiKu] match: exact '+a); return true; } if(a.indexOf(o) > -1) { console.log('[TiKu] match: ans⊇opt ans="'+a.slice(0,30)+'" opt="'+o.slice(0,30)+'"'); return true; } if(loose && o.indexOf(a) > -1) { console.log('[TiKu] match: opt⊇ans ans="'+a.slice(0,30)+'" opt="'+o.slice(0,30)+'"'); return true; } var score = _diceCoefficient(a, o); var ok = score > (loose?0.5:0.65); if(ok) console.log('[TiKu] match: dice='+score.toFixed(2)+' ans="'+a.slice(0,30)+'"'); return ok; } // ═══════════════════════════════════ // DICE COEFFICIENT (OCS string-similarity) // 基于 bigram 的 Dice 系数,比字符重叠率更精准 // ═══════════════════════════════════ function _diceBigrams(s){ var m = {}; for(var i=0; i -1){ level = 1; } } if(level > 0) result.push({ opt: options[i], level: level }); }); result.sort(function(a,b){ return b.level - a.level; }); return result.map(function(r){ return r.opt; }); } // ═══════════════════════════════════ // OCS answerSimilar — Dice系数相似度 // 返回每个选项的 {rating, target} // ═══════════════════════════════════ function _answerSimilar(answers, options){ var _ans = answers.map(function(a){ return _clear(_strip(a)); }); var _opts = options.map(function(o){ return _clear(_strip(o)); }); return _opts.map(function(opt){ if(!opt||!opt.trim()) return { rating:0, target:'' }; var best=0, bestTarget=''; for(var i=0; i<_ans.length; i++){ var r = _diceCoefficient(opt, _ans[i]); if(r > best){ best=r; bestTarget=_ans[i]; } } return { rating:best, target:bestTarget }; }); } // ═══════════════════════════════════ // OCS answerSimilarWithGap — 带领先度消歧 // 最高分选项附 gap(最高分与次高分差值) // ═══════════════════════════════════ function _answerSimilarWithGap(answers, options){ var ratings = _answerSimilar(answers, options); var sorted = ratings.slice().sort(function(a,b){ return b.rating-a.rating; }); var maxR = sorted[0] ? sorted[0].rating : 0; var secondR = 0; for(var i=1; i threshold){ if((ratings[i]||0) >= (ratings[j]||0)){ elim[j]=true; } else { elim[i]=true; break; } } } } return selectedOptions.filter(function(_,i){ return !elim[i]; }); } // ═══════════════════════════════════ // isPlainAnswer — 检测答案是否为纯ABCD字母格式 // ═══════════════════════════════════ function _isPlainAnswer(s){ return /^[A-Ha-h]+$/.test((s||'').replace(/[\s,,、]/g,'')); } function _resolvePlainAnswer(s){ if(!_isPlainAnswer(s)) return null; return (s||'').replace(/[\s,,、]/g,'').toUpperCase(); } // OCS judgement words var _CORRECT = ['是','对','正确','确定','√','对的','是的','正确的','true','True','T','yes','1']; var _INCORRECT = ['非','否','错','错误','×','X','错的','不对','不正确的','不正确','不是','不是的','false','False','F','no','0']; function _judgeMatch(s, words){ return words.some(function(w){ return _clear(_strip(w), ['√','×']) === _clear(_strip(s), ['√','×']); }); } function fillAnswer(q, answer){ if(!q.element || !answer) { console.log('[TiKu] fill skip: no el/ans'); return false; } answer = answer.trim(); var doc = q.doc || document; var node = q.element; if(!doc.body.contains(node) && !document.body.contains(node)) { console.log('[TiKu] fill skip: node detached'); return false; } var optionEls = q.optionEls || []; var options = q.options || []; console.log('[TiKu] fill: type='+q.type+' ans="'+answer.slice(0,60)+'" opts='+options.length+' els='+optionEls.length); // --- Judgement: OCS keyword matching --- if(q.type==='judgement'){ var ansCorrect = _judgeMatch(answer, _CORRECT); var ansIncorrect = _judgeMatch(answer, _INCORRECT); if(ansCorrect || ansIncorrect){ for(var i=0; i 0 ? parts : [answer]; // 也尝试将完整答案作为一个整体去匹配 if(parts.length > 1) allAnswers.push(answer); console.log('[TiKu] multi: allAnswers=['+allAnswers.join('|').slice(0,80)+']'); // ===== Stage 1: 归一化匹配 (仅 ans⊇opt 方向) ===== var normalizedMatch = _answerNormalizedMatch(allAnswers, options); if(normalizedMatch.length > 0){ var normRatings = normalizedMatch.map(function(){ return 0.8; }); var disambig = _disambiguateSimilarOptions(normalizedMatch, normRatings); console.log('[TiKu] multi S1 norm: '+disambig.length+' options'); var anyClick = false; for(var d=0; d 0.6) simGroup.push({ opt: options[i], rating: r.rating, gap: r.gap }); }); if(simGroup.length > 0){ var simOpts = simGroup.map(function(s){ return s.opt; }); var simRatings = simGroup.map(function(s){ return s.rating; }); var disambig2 = _disambiguateSimilarOptions(simOpts, simRatings); console.log('[TiKu] multi S2 sim: '+disambig2.length+' options'); var anyClick2 = false; for(var d2=0; d2=0 && cIdx 0; if(!fallbackOk) console.log('[TiKu] multi: ALL STAGES FAILED'); return fallbackOk; } // --- Completion --- if(q.type==='completion'){ var ta = node.querySelector('textarea'); var inp = node.querySelector('input[type="text"], input:not([type])'); if(!ta && !inp){ var p = node.parentElement; if(p){ ta = p.querySelector('textarea'); if(!ta) inp = p.querySelector('input[type="text"], input:not([type])'); } } var target = ta || inp; // Try CKEditor/UEditor first if(target && _fillCKEditor(target, answer)){ console.log('[TiKu] fill: CKEditor OK'); return true; } if(target){ target.focus(); if(SET.humanMode){ target.value = ''; for(var j=0; j 0){ var idx = options.indexOf(normalizedMatch[0]); if(idx >= 0 && optionEls[idx] && optionEls[idx].isConnected){ console.log('[TiKu] single S1 norm: idx='+idx); return clickOptWrap(optionEls[idx], q.plat); } } // ===== Stage 2: 相似匹配 (取最优 > 0.6) ===== var ratings = _answerSimilar(allAnswers, options); var bestIdx = -1, bestRating = 0; ratings.forEach(function(r, i){ if(r.rating > bestRating){ bestRating = r.rating; bestIdx = i; } }); if(bestIdx >= 0 && bestRating > 0.6){ console.log('[TiKu] single S2 sim: idx='+bestIdx+' rating='+bestRating.toFixed(2)); return clickOptWrap(optionEls[bestIdx], q.plat); } // ===== Stage 3: 纯ABCD答案兜底 (仅单字母) ===== var plain = _resolvePlainAnswer(answer); if(plain && plain.length === 1){ var pIdx = plain.charCodeAt(0)-65; if(pIdx >= 0 && optionEls[pIdx] && optionEls[pIdx].isConnected){ console.log('[TiKu] single S3 plain: '+plain); return clickOptWrap(optionEls[pIdx], q.plat); } } // ===== Stage 4: 多片段答案合并重试 ===== var parts = answer.split(/[#,;,;、\s]+/).filter(Boolean); if(parts.length > 1){ var merged = parts.join(''); console.log('[TiKu] single S4 merge: '+merged.slice(0,60)); var mergedMatch = _answerNormalizedMatch([merged], options); if(mergedMatch.length > 0){ var mIdx = options.indexOf(mergedMatch[0]); if(mIdx >= 0 && optionEls[mIdx] && optionEls[mIdx].isConnected){ console.log('[TiKu] single S4 merge match: idx='+mIdx); return clickOptWrap(optionEls[mIdx], q.plat); } } // Merge + similarity fallback var mergedSim = _answerSimilar([merged], options); var mBestIdx = -1, mBestRating = 0; mergedSim.forEach(function(r, i){ if(r.rating > mBestRating){ mBestRating = r.rating; mBestIdx = i; } }); if(mBestIdx >= 0 && mBestRating > 0.5){ console.log('[TiKu] single S4 merge sim: idx='+mBestIdx+' rating='+mBestRating.toFixed(2)); return clickOptWrap(optionEls[mBestIdx], q.plat); } } // ===== Stage 5: loose fallback ===== for(var i=0; i', '
', '', '', '', '
', '
就绪
', '', // 搜索进度 '
', '
', '
答题进度
', '', '', '
', '
', '
', // 填入进度 '
', '
', '
填入进度
', '', '', '
', '
', '
', // 题目列表 '
', '
题目列表
', '
', '
' ].join(''); body.querySelector('#exam-detect').addEventListener('click', doDetect); body.querySelector('#exam-start').addEventListener('click', doStart); body.querySelector('#exam-fill').addEventListener('click', doAutoFill); body.querySelector('#exam-stop').addEventListener('click', doStop); var fstop = body.querySelector('#fill-stop'); if(fstop) fstop.addEventListener('click', doFillStop); updateProg(); updateList(); } function doDetect(){ examQuestions = findQuestions().map(function(q,i){ return { index:i, title:q.title, type:q.type, options:q.options, optionEls:q.optionEls, element:q.element, doc:q.doc, plat:q.plat, status:'pending', answer:'', error:'' }; }); // Restore cached answers var restored = 0; examQuestions.forEach(function(item){ var cached = getAnswerCache(item); if(cached && cached.answer){ item.status = 'found'; item.answer = cached.answer; item.cached = true; restored++; } }); if(examQuestions.length===0){ toast('未检测到题目','warn'); } else { var msg = '检测到 '+examQuestions.length+' 题'; if(restored>0) msg += ',恢复缓存 '+restored+' 题'; toast(msg,'info'); } updateList(); updateProg(); } function switchToPreview(){ // 检测是否在考试开始页面(OCS: exam-ans/exam/test/reVersionTestStartNew) var isExamStart = /\/exam\/test\//i.test(location.href) || /reVersionTestStart/i.test(location.href); if(!isExamStart) return; // 检查是否禁止整卷预览 var markInfo = document.querySelector('.mark_info'); if(markInfo && /不允许整卷预览/i.test(markInfo.textContent||'')){ toast('本考试不允许整卷预览,将逐题作答','warn'); return; } // 尝试调用超星的 topreview 函数跳转到整卷预览 var win = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window); if(typeof win.topreview === 'function'){ toast('正在跳转到整卷预览...','info'); win.topreview(); return; } // 备选:查找并点击"整卷预览"按钮 var previewBtns = document.querySelectorAll('a, button, .btn, [onclick]'); for(var i=0; i 0){ var found = []; await doMultiThreadSearch(examQuestions, found); } var ok = examQuestions.filter(function(q){ return q.status==='found'||q.status==='filled'; }).length; toast('搜索完成: '+ok+'/'+examQuestions.length,(ok===examQuestions.length?'info':'warn')); examRunning=false; examStop=false; renderExam(panel.querySelector('#tp-body')); } function doStop(){ examStop=true; examRunning=false; toast('搜索已停止','warn'); renderExam(panel.querySelector('#tp-body')); } function doFillStop(){ fillStop=true; fillRunning=false; toast('填入已停止','warn'); updateFillProg(); } function updateProg(){ var el = panel.querySelector('#exam-prog'); var txt = panel.querySelector('#exam-prog-txt'); if(!el) return; if(examQuestions.length===0){ el.innerHTML=''; if(txt) txt.textContent=''; return; } var oks=0, ers=0, srs=0; examQuestions.forEach(function(q){ if(q.status==='found'||q.status==='filled') oks++; else if(q.status==='failed') ers++; else if(q.status==='searching') srs++; }); var total=examQuestions.length; var done=oks+ers; el.innerHTML = '
'; if(txt){ if(srs>0) txt.textContent = '搜索中... '+done+'/'+total+' 已完成'; else if(done===total) txt.textContent = '全部完成: '+oks+' 成功, '+ers+' 失败'; else if(done>0) txt.textContent = done+'/'+total+' 已处理 ('+oks+' 成功)'; else txt.textContent = '0/'+total+' 等待搜索'; } } function updateList(){ var el = panel.querySelector('#exam-list'); if(!el) return; if(examQuestions.length===0){ el.innerHTML='
点击「检测题目」开始
'; return; } // Save accordion collapse state before rebuild var collapsed = {}; el.querySelectorAll('.tp-acc-hd').forEach(function(hd){ if(!hd.classList.contains('open')) collapsed[hd.dataset.grp] = true; }); var groups = {}; var typeNames = { single:'单选题', multiple:'多选题', judgement:'判断题', completion:'填空题/问答题' }; examQuestions.forEach(function(q){ var t = q.type || 'completion'; if(!groups[t]) groups[t] = []; groups[t].push(q); }); var order = ['single','multiple','judgement','completion']; var html = ''; order.forEach(function(type){ var items = groups[type]; if(!items || items.length===0) return; var done = items.filter(function(q){ return q.status==='found'||q.status==='failed'||q.status==='filled'; }).length; var title = (typeNames[type]||type) + ' ('+done+'/'+items.length+')'; var isOpen = !collapsed[type]; // default open, restore previous state html += '
'; html += '
'+title+'
'; html += '
'; items.forEach(function(q){ var cls='tp-qc'; if(q.status==='searching') cls+=' sr'; else if(q.status==='found') cls+=' ok'; else if(q.status==='filled') cls+=' ok'; else if(q.status==='failed') cls+=' er'; html += '
'; html += '
'+(q.index+1)+'. '+esc(q.title.slice(0,60))+(q.title.length>60?'...':'')+'
'; if(q.status==='searching') html += '
搜索中...
'; if(q.status==='found') html += '
✓ '+esc(q.answer)+(q.cached?' [缓存]':'')+'
'; if(q.status==='filled') html += '
✓ '+esc(q.answer)+' [已填入]
'; if(q.status==='failed') html += '
✗ '+esc(q.error)+'
'; html += '
'; if(q.type) html += ''+q.type+''; if(q.confidence!==undefined) html += '置信:'+Math.round(q.confidence*100)+'%'; if(q.status==='found') html += ''; if(q.status!=='pending' && q.status!=='filled') html += ''; html += '
'; }); html += '
'; }); el.innerHTML = html; // Accordion toggle el.querySelectorAll('.tp-acc-hd').forEach(function(hd){ hd.addEventListener('click',function(){ var body = el.querySelector('[data-grp-body="'+this.dataset.grp+'"]'); if(body){ this.classList.toggle('open'); body.classList.toggle('cl'); } }); }); // Retry buttons el.querySelectorAll('.retry-q').forEach(function(btn){ btn.addEventListener('click', async function(){ var idx = parseInt(this.dataset.idx); var q = examQuestions[idx]; q.status='searching'; q.error=''; q.answer=''; updateList(); updateProg(); try { var r = await apiQuery(q.title, q.type, q.options); q.status='found'; q.answer=r.answer||''; q.confidence=r.confidence; } catch(err){ q.status='failed'; q.error=err.message; } updateList(); updateProg(); }); }); // Single fill buttons el.querySelectorAll('.fill-one-q').forEach(function(btn){ btn.addEventListener('click', function(){ var idx = parseInt(this.dataset.idx); var q = examQuestions[idx]; try { fillAnswer(q, q.answer); q.status='filled'; toast('已填入第'+(idx+1)+'题','info'); } catch(e){ toast('填入失败','err'); } updateList(); updateProg(); }); }); } function updateFillProg(){ var el = panel.querySelector('#fill-prog'); var txt = panel.querySelector('#fill-prog-txt'); if(!el) return; var found = examQuestions.filter(function(q){ return q.status==='filled'||q.status==='found'; }).length; var filled = examQuestions.filter(function(q){ return q.status==='filled'; }).length; if(found===0){ el.innerHTML=''; if(txt) txt.textContent=''; return; } el.innerHTML = '
'; if(txt){ if(fillRunning) txt.textContent = '填入中... '+filled+'/'+found; else if(filled===found) txt.textContent = '填入完成: '+filled+'/'+found; else txt.textContent = '已填入 '+filled+'/'+found; } } async function doAutoFill(){ var found = examQuestions.filter(function(q){ return q.status==='found' && q.answer; }); if(found.length===0){ toast('没有可填入的答案','warn'); return; } fillRunning=true; fillStop=false; // Disable fill button var fb = panel.querySelector('#exam-fill'); if(fb) fb.disabled=true; var fs = panel.querySelector('#fill-stop'); if(fs) fs.disabled=false; updateFillProg(); for(var i=0; i0) await sleep(SET.humanMode ? rand(SET.humanDelayMin, SET.humanDelayMax) : 500); // 滚动题目到可见区域(实时显示填入过程) var qEl = found[i].element; if(qEl && qEl.isConnected){ try { qEl.scrollIntoView({ behavior:'smooth', block:'center' }); } catch(e){} await sleep(200); // 等待滚动完成 } if(fillAnswer(found[i], found[i].answer)) found[i].status='filled'; } catch(e){ /* fill failed, keep status */ } updateFillProg(); } var filled = examQuestions.filter(function(q){ return q.status==='filled'; }).length; toast('自动填入完成: '+filled+'/'+found.length, filled===found.length?'info':'warn'); panel.querySelector('#exam-st').textContent = '已填入 '+filled+' 题'; fillRunning=false; fillStop=false; updateFillProg(); renderExam(panel.querySelector('#tp-body')); } // ═══════════════════════════════════ // COURSE PANEL // ═══════════════════════════════════ var courseRunning = false; var courseLogs = []; // 学习模式: job(只刷任务点) | next(逐章全刷) | manually(手动) var STUDY_MODE = load('studyMode', 'job'); var courseState = 'idle'; // OCS-style: 递归遍历文档树,收集所有 video/audio(含嵌套 iframe) function findAllMedia(doc, visited){ doc = doc || document; visited = visited || new Set(); if(visited.has(doc)) return []; visited.add(doc); var list = []; doc.querySelectorAll('video, audio').forEach(function(v){ list.push(v); }); doc.querySelectorAll('iframe').forEach(function(iframe){ try { var childDoc = iframe.contentDocument || iframe.contentWindow.document; if(childDoc) list = list.concat(findAllMedia(childDoc, visited)); } catch(e){ /* 跨域 iframe,跳过 */ } }); return list; } function renderCourse(body){ body.innerHTML = [ '
', '
', '', '
', '', '', '', '
', '
', '
', '', '
', '', 'x', '', '', '
', '
', '
', '', '', '
', '
'+(courseState==='idle'?'就绪':courseState==='running_job'?'任务中...':'全部完成')+'
', '
未检测到任务点
', '
', '
', '
操作日志
', '
'+courseLogs.join('')+'
', '
' ].join(''); body.querySelector('#cs-start').addEventListener('click', startCourse); body.querySelector('#cs-stop').addEventListener('click', stopCourse); body.querySelector('#cs-apply').addEventListener('click',function(){ SET.playbackRate=Math.min(parseFloat(body.querySelector('#cs-speed').value)||1.5, 2); SET.volume=parseFloat(body.querySelector('#cs-volume').value)||0; saveS(); applySpeed(); clog('倍速 '+SET.playbackRate+'x 音量 '+SET.volume); }); body.querySelectorAll('[data-mode]').forEach(function(btn){ btn.addEventListener('click',function(){ STUDY_MODE=this.dataset.mode; save('studyMode',STUDY_MODE); renderCourse(panel.querySelector('#tp-body')); }); }); updateMedia(); } // OCS-style: 监听 pause 事件自动续播 var _mediaPauseHandlers = []; var _videoQuizInterval = null; function startCourse(){ courseRunning=true; renderCourse(panel.querySelector('#tp-body')); applySpeed(); fixVideoProgress(); startAutoRead(); enableVisibilityResume(); clog('刷课已启动 [模式: '+(STUDY_MODE==='job'?'仅任务点':STUDY_MODE==='next'?'逐章全刷':'手动')+'] ['+platform()+']'); (async function loop(){ if(!courseRunning) return; await processJobs(); if(!courseRunning) return; if(STUDY_MODE==='manually'){ clog('手动模式:等待手动操作...'); return; } if(STUDY_MODE==='job' && isStuckMode()){ clog('闯关模式:尝试跳转'); await sleep(2000); doNextChapter(); await sleep(5000); setTimeout(loop,1000); return; } var allM=findAllMedia(), allEnd=allM.length>0&&allM.every(function(m){return m.ended;}); if(STUDY_MODE==='next'||courseState==='done'||allEnd||allM.length===0){ clog('跳转下一章...'); await sleep(3000); doNextChapter(); await sleep(8000); courseState='idle'; } if(STUDY_MODE!=='manually') setTimeout(loop,5000); })(); } function stopCourse(){ courseRunning=false; if(_videoQuizInterval){ clearInterval(_videoQuizInterval); _videoQuizInterval=null; } // 清理 pause 监听 _mediaPauseHandlers.forEach(function(h){ try { h.media.removeEventListener('pause', h.fn); } catch(e){} }); _mediaPauseHandlers = []; stopAutoRead(); disableVisibilityResume(); clog('刷课已停止'); renderCourse(panel.querySelector('#tp-body')); } function applySpeed(){ // 超星平台风控:超过2倍可能清空进度,强制截断 var maxSafe = 2; if(SET.playbackRate > maxSafe){ SET.playbackRate = maxSafe; } findAllMedia().forEach(function(v){ v.playbackRate = SET.playbackRate; v.volume = SET.volume; // 高倍速可能导致视频暂停,自动恢复 if(v.paused && courseRunning){ try { v.play().then(function(){ v.playbackRate = SET.playbackRate; }).catch(function(){}); } catch(e){} } }); } // OCS searchJobElement 任务点选择器 var _jobSelectors = { video: '#video,#audio', chapterTest: '.TiMu', read: '#img.imglook', pptWithAudio: '.swiper-container', hyperlink: '#hyperlink', timereader: 'iframe[name="bookifame"][src*="timing"]' }; var _jobPriority = ['video','chapterTest','read','pptWithAudio','hyperlink','timereader']; // 判断元素是否为真正的任务点(仅任务点模式用) // 超星任务点特征:ans-attach-ct 容器 / data-jobid / 有 answertype(TiMu) / iframe内 function _isJobPoint(el, type, doc){ if(!el || !el.isConnected) return false; // 通过 DOM 层级检查:是否有任务点容器 var p = el; while(p && p !== (doc||document).body){ // ans-attach-ct 是超星任务点包装器 if(p.className && typeof p.className === 'string' && p.className.indexOf('ans-attach') > -1) return true; // 检查 data 属性 if(p.getAttribute && p.getAttribute('data-jobid')) return true; p = p.parentElement; } // 章节测试:必须有 answertype hidden input(预览无此字段) if(type === 'chapterTest'){ var typeInput = el.querySelector('input[id^="answertype"]'); if(!typeInput) return false; } // 视频/音频:检查是否在 iframe 内(超星任务点视频在独立 iframe) if(type === 'video'){ var inIframe = (doc||document) !== document; if(inIframe) return true; // 非iframe场景,检查是否有 ans-attach 层级祖先 return false; // 不是 iframe 内的 #video 大概率不是任务点 } // 阅读、PPT、超链接、定时阅读:选到即任务点 return true; } // OCS-style: 递归搜索所有 iframe 中的任务点 function findAllJobs(doc, visited){ doc = doc || document; visited = visited || new Set(); if(visited.has(doc)) return []; visited.add(doc); var jobs = [], types = []; for(var key in _jobSelectors){ if(!_jobSelectors.hasOwnProperty(key)) continue; var el = doc.querySelector(_jobSelectors[key]); if(!el) continue; // 仅任务点模式:过滤非任务点元素 if(STUDY_MODE === 'job' && !_isJobPoint(el, key, doc)){ console.log('[TiKu] 跳过非任务点: '+key+' (STUDY_MODE=job)'); continue; } types.push({ type: key, element: el, doc: doc }); } if(types.length > 0) jobs.push({ types: types, doc: doc }); doc.querySelectorAll('iframe').forEach(function(iframe){ try { var childDoc = iframe.contentDocument || iframe.contentWindow.document; if(childDoc) jobs = jobs.concat(findAllJobs(childDoc, visited)); } catch(e){} }); return jobs; } // 视频任务 + 弹题 (OCS videoQuizStrategy:random) async function runVideoJob(doc){ if(_videoQuizInterval) clearInterval(_videoQuizInterval); _videoQuizInterval = setInterval(function(){ if(!courseRunning){ clearInterval(_videoQuizInterval); _videoQuizInterval=null; return; } var quizOpts = doc.querySelectorAll('.ans-videoquiz-opt label'); var submitBtn = doc.querySelector('#videoquiz-submit'); if(quizOpts.length > 0 && submitBtn){ var pick = quizOpts[Math.floor(Math.random()*quizOpts.length)]; if(pick) pick.click(); submitBtn.click(); clog('视频弹题: 随机作答并提交'); setTimeout(function(){ var q = doc.querySelector('#video .ans-videoquiz'); if(q) q.remove(); doc.querySelectorAll('.x-component-default').forEach(function(c){ c.style.display='none'; }); }, 3000); } }, 5000); var media = doc.querySelector('video, audio') || findAllMedia(doc)[0]; if(!media){ clog('视频: 未找到媒体元素'); return; } clog('视频任务: 播放中'); return new Promise(function(resolve){ var errTimer = setInterval(function(){ var errDiv = doc.querySelector('.vjs-modal-dialog-content'); if(errDiv && /视频文件损坏|网络错误|因格式不支持|网络的问题无法加载/.test(errDiv.innerText||'')){ clog('视频加载失败,跳过'); clearInterval(errTimer); resolve(); } }, 5000); var playFn = async function(){ if(!courseRunning || !media.offsetParent) return; if(media.ended === false){ await sleep(1000); try { media.play(); media.playbackRate=SET.playbackRate; media.volume=SET.volume; } catch(e){} } }; media.addEventListener('pause', playFn); _mediaPauseHandlers.push({ media: media, fn: playFn }); media.addEventListener('ended', function(){ media.removeEventListener('pause', playFn); clearInterval(errTimer); if(_videoQuizInterval){ clearInterval(_videoQuizInterval); _videoQuizInterval=null; } clog('视频任务: 播放完毕'); resolve(); }); media.volume = SET.volume; media.currentTime = 0; fixVideoProgress(doc); setTimeout(function(){ try { var p=media.play(); if(p&&p.then) p.then(function(){ media.playbackRate=SET.playbackRate; }).catch(function(){}); } catch(e){ playFn(); } }, 200); }); } // 章节测试(联动API搜索+填入 + 随机兜底) async function runChapterTestJob(doc, iframeEl){ var questions = []; doc.querySelectorAll('.TiMu').forEach(function(node){ var titleEl = node.querySelector('.Zy_TItle .clearfix'); var title = titleEl ? clean(titleEl.textContent) : ''; if(!title || title.length < 3) return; title = title.replace(/^\d+[。、.]/, '').replace(/(\d+\.\d+分)/, '').replace(/\(..题, ?\d+\.?\d*分\)/, '').replace(/[[(【(](..题|名词解释|完形填空|阅读理解)[\])】)]/, '').trim(); var typeInput = node.querySelector('input[id^="answertype"]'); var typeVal = typeInput ? parseInt(typeInput.value) : -1; var tMap = {0:'single',1:'multiple',2:'completion',3:'judgement',4:'completion',5:'completion',6:'completion',7:'completion',8:'completion',9:'completion',10:'completion',11:'line',14:'fill',15:'reader'}; var type = tMap[typeVal] || guessType(doc, node); var options = [], optionEls = []; node.querySelectorAll('ul li .after, ul li label:not(.before)').forEach(function(opt){ var t=clean(opt.textContent); if(t){ options.push(t); optionEls.push(opt); } }); questions.push({ title:title, type:type, options:options, optionEls:optionEls, element:node, doc:doc, plat:'cx_chapter' }); }); if(questions.length === 0){ clog('章节测试: 未解析到题目'); return; } clog('章节测试: '+questions.length+' 道题目'); var answered = 0; for(var i=0; i1?p.concat([ans]):p; var n=_answerNormalizedMatch(all,q.options); if(n.length>0){ var d=_disambiguateSimilarOptions(n,n.map(function(){return 0.8;})); d.forEach(function(o){var i=q.options.indexOf(o);if(i>=0)clickOptWrap(q.optionEls[i],'cx');}); return true; } var s=_answerSimilarWithGap(all,q.options),g=[]; s.forEach(function(r,i){if(r.rating>0.6)g.push({opt:q.options[i],rating:r.rating});}); if(g.length>0){ var d2=_disambiguateSimilarOptions(g.map(function(x){return x.opt;}),g.map(function(x){return x.rating;})); d2.forEach(function(o){var i=q.options.indexOf(o);if(i>=0)clickOptWrap(q.optionEls[i],'cx');}); return true; } return false; } function _fillSingleC(q, ans){ var n=_answerNormalizedMatch([ans],q.options); if(n.length>0){var i=q.options.indexOf(n[0]);if(i>=0)return clickOptWrap(q.optionEls[i],'cx');} var s=_answerSimilar([ans],q.options),b=-1,br=0; s.forEach(function(r,i){if(r.rating>br){br=r.rating;b=i;}}); if(b>=0&&br>0.6)return clickOptWrap(q.optionEls[b],'cx'); return false; } function _fillCompletionC(q, ans){ for(var i=0;i0){ s[s.length-1].click(); clog('PPT: 跳转封底'); } await sleep(3000); } } async function runHyperlinkJob(el){ clog('超链接任务: 已点击'); try{el.click();}catch(e){} await sleep(2000); } async function runTimeReaderJob(iframeEl){ var src=iframeEl.getAttribute('src')||'', timing=60; try{timing=parseInt(new URL(src).searchParams.get('timing')||'60');}catch(e){} var total=(timing+3)*3; clog('定时阅读: 等待 '+total+' 秒...'); await sleep(total*1000); clog('定时阅读: 完成'); } // ═══════════════════════════════════ // OCS randomWork 随机作答 // ═══════════════════════════════════ function _randomChoiceAnswer(q){ var els = q.optionEls, pick = els[Math.floor(Math.random()*els.length)]; if(pick && pick.offsetParent){ // 对章测题型,点击 label 或 a 标签 var clickTarget = pick.parentElement ? pick.parentElement.querySelector('a,label') : null; if(clickTarget) clickTarget.click(); else pick.click(); } } function _randomCompletionAnswer(q){ var texts = ['答案', '根据材料分析', '略', '无精确答案']; var pick = texts[Math.floor(Math.random()*texts.length)]; for(var i=0; i 0) clog('清理弹窗: '+cleaned+' 个'); // 自动确认按钮 clickConfirmButtons(); // ZHS 智慧树课程 if(isZHS()){ courseState='running_job'; await processZHSJobs(); courseState='done'; clog('ZHS当前章节处理完毕'); return; } // ICVE 智慧职教 if(isICVE() && !isZJY()){ courseState='running_job'; await processICVEJobs(); courseState='done'; clog('ICVE当前章节处理完毕'); return; } // ZJY 职教云 if(isZJY()){ courseState='running_job'; await processZJYJobs(); courseState='done'; clog('ZJY当前章节处理完毕'); return; } courseState='running_job'; var jobs = findAllJobs(); if(jobs.length===0){ courseState='done'; clog(STUDY_MODE==='job'?'当前章节无任务点(仅任务点模式)':'未检测到任务点'); return; } clog('检测到 '+jobs.length+' 个任务区域'); var ifs=document.querySelectorAll('iframe'); for(var i=0;i0) tasks.push('视频 '+vis.length); if(tests.length>0) tasks.push('章测 '+tests.length); } if(tasks.length>0){ el.textContent=tasks.join(' | '); el.style.color='#10B981'; } else { el.textContent='未检测到任务点'; el.style.color='#CCC'; } } function clog(msg){ var t=new Date().toLocaleTimeString(); courseLogs.unshift('
['+t+'] '+msg+'
'); if(courseLogs.length>60) courseLogs.length=60; var el=panel.querySelector('#cs-log'); if(el) el.innerHTML=courseLogs.join(''); } // ═══════════════════════════════════ // ANTI-DETECTION // ═══════════════════════════════════ function initAntiDetect(){ try { var s=document.createElement('style'); s.textContent='html *{-webkit-user-select:text!important;user-select:text!important;}'; document.head.appendChild(s); document.addEventListener('copy',function(e){ e.stopPropagation(); },true); document.addEventListener('paste',function(e){ e.stopPropagation(); },true); document.addEventListener('cut',function(e){ e.stopPropagation(); },true); document.addEventListener('selectstart',function(e){ e.stopPropagation(); },true); if(typeof unsafeWindow !== 'undefined'){ try { unsafeWindow.onselectstart=null; unsafeWindow.oncopy=null; } catch(e){} } } catch(e){} } // ═══════════════════════════════════ // CKEditor 富文本填入补丁 // ═══════════════════════════════════ function _fillCKEditor(el, answer){ // 检查 DOM 上的 CKEditor 实例 (智慧树/超星) // @ts-ignore var inst = el.ckeditorInstance || el.ckeditor || el.CKEditor; if(inst && typeof inst.data !== 'undefined' && typeof inst.data.set === 'function'){ inst.data.set(answer); return true; } // 检查全局 CKEDITOR if(typeof CKEDITOR !== 'undefined'){ var name = el.getAttribute('name') || el.id; if(name){ try { CKEDITOR.instances[name].setData(answer); return true; } catch(e){} } } // 检查 UEditor (超星) — 通过contentWindow var ueIframe = el.parentElement ? el.parentElement.querySelector('iframe[id*="ueditor"]') : null; if(ueIframe){ try { var ueDoc = ueIframe.contentDocument || ueIframe.contentWindow.document; if(ueDoc){ ueDoc.body.innerHTML = answer; return true; } } catch(e){} } return false; } // ═══════════════════════════════════ // 视频失焦暂停修复 (visibilitychange) // OCS 策略: 浏览器标签切换时强制续播 // ═══════════════════════════════════ function initVideoFocusFix(){ return; // 空白函数占位,实际修复在 runVideoJob 的 pause 事件中 } // 增强: 加到 doStart 流程 var _visibilityHandler = null; function enableVisibilityResume(){ if(_visibilityHandler) return; _visibilityHandler = function(){ if(!courseRunning) return; if(document.visibilityState === 'visible'){ findAllMedia().forEach(function(m){ if(m.paused && m.offsetParent && !m.ended){ try { m.play(); } catch(e){} } }); } }; document.addEventListener('visibilitychange', _visibilityHandler); } function disableVisibilityResume(){ if(_visibilityHandler){ document.removeEventListener('visibilitychange', _visibilityHandler); _visibilityHandler = null; } } // ═══════════════════════════════════ // 字体混淆识别 (cxSecretFontRecognize) // 检测 font-cxsecret 自定义字体并尝试用API解决 // ═══════════════════════════════════ var _fontRecognized = false; async function initFontRecognition(){ if(_fontRecognized) return; var fontEls = document.querySelectorAll('.font-cxsecret'); if(fontEls.length === 0) return; console.log('[TiKu] 检测到 '+fontEls.length+' 个混淆字体元素'); // 策略: 尝试通过 textContent 获取渲染后的实际文字 // 某些浏览器会在 font-cxsecret 下计算 textContent 时触发回退 // 先尝试直接读取 var replaced = 0; var win = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window); // 尝试加载 OCS 字体映射表 try { var tableUrl = 'https://cdn.ocsjs.com/resources/font/table.json'; var mapping = await new Promise(function(resolve, reject){ GM_xmlhttpRequest({ method: 'GET', url: tableUrl, timeout: 5000, responseType: 'json', onload: function(r){ try { resolve(JSON.parse(r.responseText)); } catch(e){ reject(e); } }, onerror: function(){ reject(new Error('fetch failed')); }, ontimeout: function(){ reject(new Error('timeout')); } }); }); if(mapping && Object.keys(mapping).length > 0){ // 尝试加载 Typr.js await new Promise(function(resolve, reject){ var typrScript = document.createElement('script'); typrScript.src = 'https://cdn.ocsjs.com/resources/lib/typr.min.js'; typrScript.onload = function(){ resolve(); }; typrScript.onerror = function(){ reject(new Error('Typr.js load failed')); }; document.head.appendChild(typrScript); }); // 提取 base64 字体 var fontStyle = Array.from(document.querySelectorAll('style')).find(function(s){ return (s.textContent||'').indexOf('font-cxsecret') !== -1; }); if(fontStyle){ var fontB64 = (fontStyle.textContent||'').match(/base64,([\w\W]+?)'/)?.[1]; if(fontB64 && typeof Typr !== 'undefined'){ var data = (function(b64){ var str = atob(b64); var buf = new Uint8Array(str.length); for(var i=0; i 0){ console.log('[TiKu] 字体识别库不可用,尝试移除混淆字体...'); fontEls.forEach(function(el){ // 记录原始innerHTML var orig = el.innerHTML; // 移除font-cxsecret类,浏览器用回退字体渲染 el.classList.remove('font-cxsecret'); // 重新读取textContent(此时可能已经正确) // 浏览器可能需要重新布局 }); } _fontRecognized = true; console.log('[TiKu] 字体混淆处理完成: '+replaced+' 项替换'); } // ═══════════════════════════════════ // 智慧树课程任务点检测 + 视频弹题 // ═══════════════════════════════════ var _zhsJobSelectors = { video: '#vjs_container, #video, #playTopic-dialog, .clearfix.video', test: '#playTopic-dialog .topic-item, .ai-test-question-wrapper', chapterTest: '.subject_describe .questionBox' }; function findZHSJobs(doc, visited){ doc = doc || document; visited = visited || new Set(); if(visited.has(doc)) return []; visited.add(doc); var jobs = [], types = []; for(var key in _zhsJobSelectors){ if(!_zhsJobSelectors.hasOwnProperty(key)) continue; var el = doc.querySelector(_zhsJobSelectors[key]); if(el){ types.push({ type: key, element: el, doc: doc }); } } if(types.length > 0) jobs.push({ types: types, doc: doc }); doc.querySelectorAll('iframe').forEach(function(iframe){ try { var childDoc = iframe.contentDocument || iframe.contentWindow.document; if(childDoc) jobs = jobs.concat(findZHSJobs(childDoc, visited)); } catch(e){} }); return jobs; } // ZHS视频弹题 (3种UI) async function handleZHSVideoQuiz(doc){ // 标准弹题 var stdItems = doc.querySelectorAll('#playTopic-dialog .el-pager .number'); if(stdItems.length > 0){ for(var i=0; i 0){ var pick = opts[Math.floor(Math.random()*opts.length)]; var radio = pick.querySelector('.radio ul > li') || pick; radio.click(); await sleep(1000); } } await sleep(1000); var close1 = doc.querySelector('#playTopic-dialog .close-btn, #playTopic-dialog .btn'); if(close1) close1.click(); clog('ZHS视频弹题: 随机作答 (标准)'); } // 新版弹题 var newBox = doc.querySelector('.ai-test-question-wrapper'); if(newBox){ var done = newBox.querySelector('.done'); if(!done){ var opts2 = newBox.querySelectorAll('.options .option'); if(opts2.length > 0){ opts2[Math.floor(Math.random()*opts2.length)].click(); await sleep(1000); } var submit = newBox.querySelector('.submit-btn .submits'); if(submit) submit.click(); } await sleep(500); var close2 = newBox.querySelector('.close-box'); if(close2) close2.click(); clog('ZHS视频弹题: 随机作答 (新版)'); } } // ZHS任务处理入口 async function processZHSJobs(){ var jobs = findZHSJobs(); for(var i=0; i 0) jobs.push({ types: types, doc: doc }); doc.querySelectorAll('iframe').forEach(function(iframe){ try { var childDoc = iframe.contentDocument || iframe.contentWindow.document; if(childDoc) jobs = jobs.concat(findICVEJobs(childDoc, visited)); } catch(e){} }); return jobs; } async function processICVEJobs(){ var jobs = findICVEJobs(); if(jobs.length === 0){ clog('ICVE: 未检测到任务点'); return; } clog('ICVE: '+jobs.length+' 个任务区域'); for(var i=0; i 0) jobs.push({ types: types, doc: doc }); doc.querySelectorAll('iframe').forEach(function(iframe){ try { var childDoc = iframe.contentDocument || iframe.contentWindow.document; if(childDoc) jobs = jobs.concat(findZJYJobs(childDoc, visited)); } catch(e){} }); return jobs; } async function processZJYJobs(){ var jobs = findZJYJobs(); if(jobs.length === 0){ clog('ZJY: 未检测到任务点'); return; } clog('ZJY: '+jobs.length+' 个任务区域'); for(var i=0; i 0){ clearInterval(iv); resolve(locks.shift()); } if(finished >= pending.length){ clearInterval(iv); resolve(null); } }, 100); }); }; var workers = []; for(var t=0; t<_searchThreads; t++){ workers.push((async function(){ while(finished < pending.length && !examStop){ var qIdx = idx++; if(qIdx >= pending.length) break; var q = pending[qIdx]; var lock = await waitLock(); if(!lock) break; try { var typeMap = { single:'single', multiple:'multiple', judgement:'judgement', completion:'completion' }; var apiType = typeMap[q.type] || 'single'; if(SET.humanMode) await sleep(rand(SET.humanDelayMin, SET.humanDelayMax)); var result = await apiQuery(q.title, apiType, q.options?q.options.join('\n'):''); q.answer = result.answer; q.status = 'found'; q.confidence = result.confidence; saveAnswerCache(q); found.push(q); } catch(e){ q.error = e.message; // Retry on fail if(SET.retryOnFail && !examStop){ var retryOk = false; for(var rt=0; rt