// ==UserScript== // @name YOOC易班助手 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 彻底修复跳题BUG(DOM渲染探测)、加入骨架级纯净文本匹配(抗一切标点干扰)、完善题库ID与TXT排版。 // @author 毫厘 // @match *://*.yooc.me/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; const injectCode = ` (function() { window.__yooc = { params: { token: '', yibanId: '', examuserId: '', examId: '' }, currentPaper: [], isHoneypot: false, harvested: {}, solving: false, currentViewExam: null, examNames: {}, dict: { 'EHOQnmIlFTWj4w/KduiYhA==': ['0'], 'rpEySfwpHRpZ2e63UNBznQ==': ['1'], 'GDq/oTf9hSo5WnREQUwIUA==': ['2'], 'X1yuE+WZgBTLj8HheQY4hA==': ['3'], 'AVAmpoTedb8redjJKEL0nQ==': ['0', '1'], 'xJOq/yEqHuZ3sAikzvJYnQ==': ['0', '2'], 'uorBHjvuhl1ZcgpGbktEBw==': ['1', '2'], 'YfPgy8DC6iwWF9XM0WK7kg==': ['0', '1', '2'], '+z0GgEWd2u7XnlBRKv6i1g==': ['0', '2', '3'], 'itQk2tSqzl3TTlYLAvJbQA==': ['1', '2', '3'], 'Z2NpcmomFRvndsT6LF0rh5E0tgszOUVM9h9JBtBTbWo=': ['0', '1', '2', '3'] } }; // --- 全局样式注入 --- function injectCSS() { if(document.getElementById('yooc-modern-css')) return; const style = document.createElement('style'); style.id = 'yooc-modern-css'; style.innerHTML = \` #yooc-sniffer-panel { font-family: system-ui, -apple-system, sans-serif; box-sizing: border-box; } #yooc-sniffer-panel * { box-sizing: inherit; } .yooc-btn { transition: all 0.2s ease; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; cursor: pointer; outline: none; border: none; } .yooc-btn:hover { transform: translateY(-1px); filter: brightness(1.1); } .yooc-btn:active { transform: translateY(1px); } .yooc-tabs { display: flex; border-bottom: 1px solid #334155; background: #0f172a; } .yooc-tab { flex: 1; padding: 10px 0; text-align: center; color: #94a3b8; cursor: pointer; font-size: 13px; font-weight: bold; transition: color 0.3s; } .yooc-tab:hover { color: #f8fafc; background: rgba(255,255,255,0.05); } .yooc-tab.active { color: #38bdf8; border-bottom: 2px solid #38bdf8; background: rgba(56, 189, 248, 0.1); } .yooc-scroll { overflow-y: auto; overflow-x: hidden; scroll-behavior: smooth; } .yooc-scroll::-webkit-scrollbar { width: 6px; } .yooc-scroll::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; margin: 4px 0; } .yooc-scroll::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } .yooc-scroll::-webkit-scrollbar-thumb:hover { background: #64748b; } .yooc-card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 12px; margin-bottom: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); } .yooc-q-title { color: #f8fafc; font-size: 14px; font-weight: bold; margin-bottom: 10px; line-height: 1.5; } .yooc-opt { font-size: 13px; margin-bottom: 6px; padding: 6px 10px; border-radius: 6px; display: flex; align-items: flex-start; line-height: 1.4; } .yooc-opt-correct { background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.3); color: #10b981; } .yooc-opt-wrong { background: rgba(255, 255, 255, 0.03); color: #94a3b8; } .yooc-input { width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #475569; background: #0f172a; color: #f8fafc; outline: none; font-size: 13px; transition: border-color 0.2s; } .yooc-input:focus { border-color: #38bdf8; } \`; document.head.appendChild(style); } // --- 无感 Toast 提示 --- window.__yoocToast = function(msg, duration = 3000) { let box = document.getElementById('yooc-toast-box'); if (!box) { box = document.createElement('div'); box.id = 'yooc-toast-box'; box.style.cssText = 'position:fixed; top:20px; left:50%; transform:translateX(-50%); z-index:9999999; display:flex; flex-direction:column; gap:10px; pointer-events:none;'; document.body.appendChild(box); } const el = document.createElement('div'); el.style.cssText = 'background:rgba(15,23,42,0.9); color:#fff; padding:12px 24px; border-radius:8px; font-size:14px; box-shadow:0 10px 25px rgba(0,0,0,0.2); border:1px solid #38bdf8; opacity:0; transition:opacity 0.3s, transform 0.3s; transform:translateY(-20px); font-weight:bold; text-align:center; backdrop-filter:blur(4px);'; el.innerText = msg; box.appendChild(el); setTimeout(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0)'; }, 10); setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(-20px)'; setTimeout(() => el.remove(), 300); }, duration); }; // --- 题库存储模块 --- function loadBank() { try { return JSON.parse(localStorage.getItem('yooc_text_bank_v7')) || {}; } catch(e) { return {}; } } function saveBank(bank) { localStorage.setItem('yooc_text_bank_v7', JSON.stringify(bank)); window.__yoocRenderOverview(); } function getRandAllow() { return localStorage.getItem('yooc_allow_random') !== 'false'; } function setRandAllow(val) { localStorage.setItem('yooc_allow_random', val); } function handleExamList(responseData) { if (!responseData || !Array.isArray(responseData.data)) return; let updated = false; const bank = loadBank(); responseData.data.forEach(exam => { if (exam.examId && exam.name) { const eId = exam.examId.toString(); window.__yooc.examNames[eId] = exam.name; if (bank[eId] && bank[eId].name !== exam.name) { bank[eId].name = exam.name; updated = true; } } }); if (updated) { saveBank(bank); if(document.getElementById('yooc-tab-bank').style.display === 'flex' && !window.__yooc.currentViewExam && !window.__yooc.isViewingCurrent) { window.__yoocRenderExamList(); } } } // --- 核心:动态全景收割引擎 --- function harvestKnowledge(sourceData, answerArray) { if (!sourceData) return; const pData = sourceData.data || sourceData || []; if (!Array.isArray(pData) || pData.length === 0) return; let currentExamId = window.__yooc.params.examId; if (!currentExamId && sourceData.data && sourceData.data[0] && sourceData.data[0].examId) { currentExamId = sourceData.data[0].examId.toString(); } if (!currentExamId) currentExamId = '未知试卷_' + new Date().getTime().toString().slice(-6); const bank = loadBank(); const realExamName = window.__yooc.examNames[currentExamId] || ('试卷 ID: ' + currentExamId); if (!bank[currentExamId]) { bank[currentExamId] = { name: realExamName, questions: {} }; } else if (bank[currentExamId].name !== realExamName) { bank[currentExamId].name = realExamName; } let newCount = 0; const subjectMap = {}; const cipherSet = new Set(); pData.forEach(sec => sec.subjects.forEach(sub => { if (sub.answer) cipherSet.add(sub.answer); })); const isCurrentDataHoneypot = cipherSet.size <= 2; pData.forEach(sec => { sec.subjects.forEach(sub => { const title = sub.title.join('').trim(); const options = sub.option.map(o => o[0] ? o[0].trim() : o.join('').trim()); subjectMap[sub.subjectId] = { id: sub.subjectId, title: title, options: options }; if (sub.answer && window.__yooc.dict[sub.answer] && !isCurrentDataHoneypot) { const correctTexts = window.__yooc.dict[sub.answer].map(idx => options[parseInt(idx)]).filter(Boolean); if (correctTexts.length > 0) { if (!bank[currentExamId].questions[title] || JSON.stringify(bank[currentExamId].questions[title].correct) !== JSON.stringify(correctTexts)) { bank[currentExamId].questions[title] = { id: sub.subjectId, options: options, correct: correctTexts }; newCount++; } } } }); }); if (answerArray && Array.isArray(answerArray)) { answerArray.forEach(item => { let subjId, correctIndexes; if (item.id && item.a) { subjId = item.id; correctIndexes = item.a; } else { subjId = Object.keys(item)[0]; if (item[subjId] && item[subjId][1] === 1) correctIndexes = Object.values(item[subjId][0])[0]; } if (subjId && correctIndexes && subjectMap[subjId]) { const { id, title, options } = subjectMap[subjId]; const correctTexts = correctIndexes.map(idx => options[parseInt(idx)]).filter(Boolean); if (correctTexts.length > 0) { if (!bank[currentExamId].questions[title] || JSON.stringify(bank[currentExamId].questions[title].correct) !== JSON.stringify(correctTexts)) { bank[currentExamId].questions[title] = { id: id, options: options, correct: correctTexts }; newCount++; } } } }); } if (newCount > 0) { saveBank(bank); window.__yoocToast(\`🎉 成功收割 \${newCount} 道新题!\`); } } function activeFetchHarvest() { const p = window.__yooc.params; if (!p.token || !p.yibanId || !p.examuserId || window.__yooc.harvested[p.examuserId]) return; window.__yooc.harvested[p.examuserId] = true; window.__yoocRenderOverview(); const fetchOpts = { headers: { 'Accept': '*/*', 'Origin': 'https://exam.yooc.me' } }; window.fetch('https://exambackend.yooc.me/api/exam/answer/get?examuserId=' + p.examuserId + '&token=' + p.token + '&yibanId=' + p.yibanId, fetchOpts) .then(r => r.json()) .then(data => { harvestKnowledge(data, data.answers || data.s_answers); window.__yoocRenderOverview(); }).catch(()=>{}); } function cachePaper(data) { if (!data || !data.data) return; window.__yooc.currentPaper = data.data; const cipherSet = new Set(); data.data.forEach(sec => { sec.subjects.forEach(sub => { if (sub.answer) cipherSet.add(sub.answer); }); }); window.__yooc.isHoneypot = cipherSet.size <= 2; window.__yoocRenderOverview(); if (window.__yooc.isViewingCurrent) window.__yoocViewCurrentPaper(); } // --- 网络拦截 --- function checkUrlAndParams(urlStr, responseData) { let updated = false; if (urlStr && typeof urlStr === 'string') { try { const urlObj = new URL(urlStr, window.location.origin); ['token', 'yibanId', 'examuserId', 'examId'].forEach(key => { const val = urlObj.searchParams.get(key); if (val && window.__yooc.params[key] !== val) { window.__yooc.params[key] = val; updated = true; } }); } catch (e) {} } if (responseData && responseData.data && typeof responseData.data === 'object' && !Array.isArray(responseData.data)) { if (responseData.data.examuserId && window.__yooc.params.examuserId !== responseData.data.examuserId.toString()) { window.__yooc.params.examuserId = responseData.data.examuserId.toString(); updated = true; } if (responseData.data.examId && window.__yooc.params.examId !== responseData.data.examId.toString()) { window.__yooc.params.examId = responseData.data.examId.toString(); updated = true; } } if (updated) window.__yoocRenderOverview(); } const originFetch = window.fetch; window.fetch = function(...args) { const url = (typeof args[0] === 'string') ? args[0] : (args[0] && args[0].url); checkUrlAndParams(url, null); return originFetch.apply(this, args).then(res => { try { res.clone().json().then(data => { checkUrlAndParams(url, data); if (url.includes('/api/exam/list/get')) { handleExamList(data); } if (url.includes('/api/exam/paper/get')) { cachePaper(data); harvestKnowledge(data, null); } if (url.includes('/api/exam/submit/action')) { harvestKnowledge({data: window.__yooc.currentPaper}, data.s_answers || data.answers); } if (url.includes('/api/exam/answer/get')) { harvestKnowledge(data, data.s_answers || data.answers); } if (url.includes('/api/exam/result/get')) { activeFetchHarvest(); } }).catch(()=>{}); } catch(e) {} return res; }); }; const originOpen = XMLHttpRequest.prototype.open; const originSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url) { this._url = url; checkUrlAndParams(url, null); return originOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(...args) { this.addEventListener('load', function() { try { const data = JSON.parse(this.responseText); checkUrlAndParams(this._url, data); if (this._url.includes('/api/exam/list/get')) { handleExamList(data); } if (this._url.includes('/api/exam/paper/get')) { cachePaper(data); harvestKnowledge(data, null); } if (this._url.includes('/api/exam/submit/action')) { harvestKnowledge({data: window.__yooc.currentPaper}, data.s_answers || data.answers); } if (this._url.includes('/api/exam/answer/get')) { harvestKnowledge(data, data.s_answers || data.answers); } if (this._url.includes('/api/exam/result/get')) { activeFetchHarvest(); } } catch (e) {} }); return originSend.apply(this, args); }; // --- 底层翻页作答引擎 (极致稳定版) --- window.__yoocToggleRandom = function(e) { setRandAllow(e.checked); }; window.__yoocAutoSolve = async function() { if (window.__yooc.solving) return; window.__yooc.solving = true; const allowRandom = getRandAllow(); const btn = document.getElementById('yooc-solve-btn'); btn.innerHTML = '🔄 正在自动作答... 请勿触碰页面'; btn.style.background = '#f59e0b'; window.__yoocToast('🚀 作答引擎已启动,正在跨卷匹配题库...'); const rawBank = loadBank(); const flatBank = {}; Object.values(rawBank).forEach(exam => { if (exam.questions) Object.assign(flatBank, exam.questions); }); const { currentPaper, isHoneypot, dict } = window.__yooc; // 【革命性优化】骨架级提取:彻底去除所有中英文标点、空格、特殊符号,只留汉字字母数字进行比对。 const coreTxt = s => (s||'').replace(/[^\\u4e00-\\u9fa5a-zA-Z0-9]/g, ''); // 选项清洗:先去 A. 前缀,再提骨架 const optTxt = s => (s||'').replace(/^[A-Z][.、.\\s]*/, '').replace(/[^\\u4e00-\\u9fa5a-zA-Z0-9]/g, ''); const delay = ms => new Promise(r => setTimeout(r, ms)); let totalSolved = 0, totalGuessed = 0, totalSkipped = 0; while (window.__yooc.solving) { const oldPageText = document.body.innerText; // 记录当前页面的文本,用于比对翻页 let labels = Array.from(document.querySelectorAll('label, li, .option-item, .yiban-option, .exam-option-item')).filter(el => el.textContent.trim().length > 0); if (labels.length === 0) { document.querySelectorAll('input[type=\"radio\"], input[type=\"checkbox\"]').forEach(inp => { const p = inp.closest('label, li, div'); if (p && p.textContent.trim().length > 0 && !labels.includes(p)) labels.push(p); }); } if (labels.length > 0) { const pageCoreText = coreTxt(document.body.innerText); let activeSubs = []; if (currentPaper && currentPaper.length > 0) { activeSubs = currentPaper.flatMap(sec => sec.subjects).filter(sub => { const titleCore = coreTxt(sub.title.join('')); // 【修复跳题】仅提取前12个骨架字符进行匹配,无视截断和标点错乱 const matchStr = titleCore.length > 12 ? titleCore.substring(0, 12) : titleCore; return pageCoreText.includes(matchStr) && matchStr.length > 0; }); } if (activeSubs.length > 0) { activeSubs.forEach(sub => { const origTitle = sub.title.join('').trim(); const optionsTexts = sub.option.map(o => (o[0] ? o[0] : o.join(''))); let targets = []; let hasDefinitive = false; let alreadyCheckedAny = false; const subNodes = []; optionsTexts.forEach(opt => { const targetCore = optTxt(opt); const node = labels.find(l => optTxt(l.textContent) === targetCore && targetCore.length > 0); if (node) { subNodes.push({ opt: opt, node: node }); const inp = node.querySelector('input'); if ((inp && inp.checked) || node.className.includes('active') || node.className.includes('checked') || node.getAttribute('aria-checked') === 'true') { alreadyCheckedAny = true; } } }); if (flatBank[origTitle] && flatBank[origTitle].correct) { targets = flatBank[origTitle].correct; hasDefinitive = true; totalSolved++; } else if (!isHoneypot && dict[sub.answer]) { targets = dict[sub.answer].map(idx => optionsTexts[parseInt(idx)]); hasDefinitive = true; totalSolved++; } else if (allowRandom) { if (alreadyCheckedAny) { totalSkipped++; } else if (optionsTexts.length > 0) { targets = [optionsTexts[0]]; totalGuessed++; } } else { totalSkipped++; } subNodes.forEach(sn => { const isTarget = targets.includes(sn.opt); const inp = sn.node.querySelector('input'); const isChecked = (inp && inp.checked) || sn.node.className.includes('active') || sn.node.className.includes('checked') || sn.node.getAttribute('aria-checked') === 'true'; if (hasDefinitive) { if (isTarget && !isChecked) { if (inp) inp.click(); else sn.node.click(); } else if (!isTarget && isChecked) { if (inp) inp.click(); else sn.node.click(); } } else if (targets.length > 0) { if (isTarget && !isChecked) { if (inp) inp.click(); else sn.node.click(); } } }); }); } else { // 备用 DOM 匹配,同样应用骨架逻辑 let foundInBank = false; let anyChecked = false; let optionNodes = []; labels.forEach(node => { const txt = node.textContent.trim(); if (/^[A-D][.、\\s]/.test(txt) || node.querySelector('input')) { optionNodes.push(node); const inp = node.querySelector('input'); if ((inp && inp.checked) || node.className.includes('active') || node.className.includes('checked') || node.getAttribute('aria-checked') === 'true') { anyChecked = true; } } }); for (let qTitle in flatBank) { const titleCore = coreTxt(qTitle); const matchStr = titleCore.length > 12 ? titleCore.substring(0, 12) : titleCore; if (pageCoreText.includes(matchStr) && flatBank[qTitle].correct) { flatBank[qTitle].correct.forEach(tgt => { const tgtCore = optTxt(tgt); const node = labels.find(l => optTxt(l.textContent) === tgtCore && tgtCore.length > 0); if (node) { foundInBank = true; const inp = node.querySelector('input'); const isChecked = (inp && inp.checked) || node.className.includes('active') || node.className.includes('checked') || node.getAttribute('aria-checked') === 'true'; if (!isChecked) { if (inp) inp.click(); else node.click(); } } }); } } if (!foundInBank && allowRandom) { if (anyChecked) totalSkipped++; else if (optionNodes.length > 0) { optionNodes[0].click(); totalGuessed++; } } else if (!foundInBank) { totalSkipped++; } } } // 【修复跳题】基于 DOM 文本变更探测,确保页面真的刷新了再进入下一次循环 const nextBtn = Array.from(document.querySelectorAll('button, div, span, a')).find(el => (el.textContent.trim() === '下一题' || el.textContent.trim() === '下一页') && el.offsetParent !== null && !el.disabled && !el.className.includes('disabled') ); if (nextBtn) { nextBtn.click(); let waited = 0; // 等待直到页面文本改变,或超时 3000ms while(document.body.innerText === oldPageText && waited < 3000) { await delay(100); waited += 100; } await delay(300); // 额外缓冲,确保选项渲染完成 } else { break; } } const submitEle = Array.from(document.querySelectorAll('button, div, span, a')).find(el => (el.textContent.trim() === '交卷' || el.textContent.trim() === '提交') && el.offsetParent !== null ); if (submitEle) { submitEle.click(); await delay(500); const confirmBtn = Array.from(document.querySelectorAll('button, div, span, a')).find(el => (el.textContent.trim() === '确定' || el.textContent.trim() === '确认交卷') && el.offsetParent !== null ); if (confirmBtn) confirmBtn.click(); } window.__yooc.solving = false; btn.innerHTML = '🚀 重新扫描并作答'; btn.style.background = '#0ea5e9'; window.__yoocToast(\`✅ 答卷完毕!命中:\${totalSolved} | 兜底A:\${totalGuessed} | 跳过:\${totalSkipped}\`, 6000); }; // --- 现代化 UI 与题库分卷渲染 --- window.__yoocSwitchTab = function(tabId) { document.querySelectorAll('.yooc-tab-content').forEach(el => el.style.display = 'none'); document.querySelectorAll('.yooc-tab').forEach(el => el.classList.remove('active')); document.getElementById(tabId).style.display = 'flex'; document.querySelector(\`[data-target="\${tabId}"]\`).classList.add('active'); window.__yooc.isViewingCurrent = false; if(tabId === 'yooc-tab-bank') window.__yoocRenderExamList(); }; window.__yoocRenderExamList = function() { const bank = loadBank(); const container = document.getElementById('yooc-bank-content'); if(!container) return; let html = ''; // 实时考试面板入口 if (window.__yooc.currentPaper && window.__yooc.currentPaper.length > 0) { html += \`