// ==UserScript== // @name Touge // @namespace http://tampermonkey.net/ // @version 9.1 // @description 记录所有题目选项和题型,一键复制,带开关和悬浮日志加双区复制,并增加图片题扫描功能 // @match https://tg.zcst.edu.cn/* // @match https://www.educoder.net/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; // ===== 双区复制整合:可配置的 XPath 与常见选择器兜底 ===== // 区域1 的 XPath(可添加多个) const COPY_A_XPATHS = [ '/html/body/div[1]/div/div/div[1]/div[2]/section[1]/div[2]/div[1]/div/div[1]/div[1]' ]; // 区域2 的 XPath(可添加多个) const COPY_B_XPATHS = [ //为空将自动使用常见选择器与页面文本兜底 ]; // 常见内容选择器兜底 const COMMON_SELECTORS = [ '.content', '.main-content', '#content', 'article', 'main', '.question-content', '.exam-content', '.container .content' ]; // ===== 新增:图片题目扫描器模块 ===== const imageScanner = { imageQuestions: new Set(), isScanning: false, scanIntervalId: null, currentQuestionIndex: 0, // 检查当前题目是否有图片 checkCurrentImage: function() { const container = document.querySelector('.question_title, .question-content, [id^="Anchor_0_"]'); // 更通用的题目容器 if (!container) return; const images = container.querySelectorAll('img'); if (images.length > 0) { const { qNum } = getCurrentQuestionInfo(); if (qNum && !this.imageQuestions.has(qNum)) { log(`【扫描】发现图片题,题号: ${qNum}`, 'success'); this.imageQuestions.add(qNum); markQuestionOnSheet('image'); // 使用新的标记函数 } } }, // 开始扫描 start: function() { if (this.isScanning) { log('【扫描】已在进行中,请勿重复点击', 'info'); return; } log('【扫描】开始遍历所有题目以查找图片...', 'info'); this.isScanning = true; this.imageQuestions.clear(); this.currentQuestionIndex = 0; // 扫描当前页 this.checkCurrentImage(); this.scanIntervalId = setInterval(() => { const hasNext = clickNext(true); // 调用 clickNext 并告知是扫描模式 if (hasNext) { this.currentQuestionIndex++; // 等待题目加载 setTimeout(() => this.checkCurrentImage(), 1000); } else { this.stop(); log(`【扫描】完成!共找到 ${this.imageQuestions.size} 道图片题。`, 'success'); log(`【扫描】题号: ${Array.from(this.imageQuestions).sort((a, b) => a - b).join(', ')}`, 'info'); // 最终重新标记一次所有找到的题目 setTimeout(() => { this.imageQuestions.forEach(num => markQuestionOnSheet('image', num)); }, 500); } }, 2000); // 2秒间隔 }, // 停止扫描 stop: function() { if (this.scanIntervalId) { clearInterval(this.scanIntervalId); this.scanIntervalId = null; } this.isScanning = false; log('【扫描】已停止', 'info'); } }; function firstNodeByXPath(xpath) { try { const res = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); return res && res.singleNodeValue ? res.singleNodeValue : null; } catch (e) { return null; } } function normalizeText(text) { return String(text) .replace(/\u00A0/g, ' ') .replace(/[\t ]+/g, ' ') .replace(/\n{3,}/g, '\n\n') .trim(); } function getVisibleTextExcludingUI() { try { const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { if (!node || !node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; const p = node.parentElement; if (!p) return NodeFilter.FILTER_REJECT; if (p.closest('#main-panel')) return NodeFilter.FILTER_REJECT; if (p.closest('#show-main-panel-btn')) return NodeFilter.FILTER_REJECT; const cs = getComputedStyle(p); if (cs.display === 'none' || cs.visibility === 'hidden') return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); const parts = []; let cur = walker.nextNode(); while (cur) { parts.push(cur.nodeValue.trim()); cur = walker.nextNode(); } return normalizeText(parts.join('\n')); } catch (_) { // Fallback: remove panel/show button innerText by replacement let t = (document.body && document.body.innerText) ? document.body.innerText : ''; const panel = document.getElementById('main-panel'); if (panel && panel.innerText) t = t.replace(panel.innerText, ''); const showBtn = document.getElementById('show-main-panel-btn'); if (showBtn && showBtn.innerText) t = t.replace(showBtn.innerText, ''); return normalizeText(t); } } function removeUiStrings(text) { try { let t = String(text || ''); const panel = document.getElementById('main-panel'); if (panel && panel.innerText) t = t.replace(panel.innerText, ''); const showBtn = document.getElementById('show-main-panel-btn'); if (showBtn && showBtn.innerText) t = t.replace(showBtn.innerText, ''); const blacklist = [ '读取剪贴板', '读取剪切板', '题目收集', '题目收集:关', '题目收集: 开', '复制题目', '双区复制', '显示助手', '答题助手', '日志', '剪贴板预览' ]; for (const w of blacklist) { t = t.replace(new RegExp(w, 'g'), ''); } return normalizeText(t); } catch (_) { return normalizeText(text || ''); } } async function copyText(text) { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); return true; } } catch (_) { } //退回旧方案 try { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); const ok = document.execCommand('copy'); document.body.removeChild(ta); return !!ok; } catch (_) { return false; } } function collectContentTextFrom(xpaths) { //1) XPath 优先 for (const xp of xpaths || []) { const node = firstNodeByXPath(xp); if (!node) continue; const t = (node.innerText || node.textContent || '').trim(); if (t) return normalizeText(t); } //2) 常见选择器 for (const sel of COMMON_SELECTORS) { const nodes = Array.from(document.querySelectorAll(sel)).filter(n => (n.innerText || '').trim()); if (nodes.length) { const joined = nodes.map(n => (n.innerText || '').trim()).join('\n\n'); if (joined.trim()) return normalizeText(joined); } } //3)兜底整个页面文本(排除脚本面板文字) const bodyText = getVisibleTextExcludingUI(); return normalizeText(bodyText || ''); } // ===== 原助手逻辑 ===== let allQuestions = []; let enabled = false; let autoAnswerLoop = false; let loopIntervalId = null; let autoSubmitEnabled = false; let autoSubmitTimerId = null; const SUBMIT_TIMEOUT = 5000; //5 seconds let lastKnownQuestionTitle = ''; // Variable to track the current question // --- AI Answering Variables --- let aiAnswers = []; let currentAiAnswerIndex = 0; // ====== 补充的通用日志与循环控制函数 ====== function log(message, type = 'info') { try { const panel = document.getElementById('question-log-content'); if (!panel) { // 控件未渲染时降级到控制台 console[(type === 'error' ? 'error' : type === 'success' ? 'log' : 'info')](message); return; } const line = document.createElement('div'); line.style.margin = '2px0'; line.style.fontSize = '12px'; switch (type) { case 'success': line.style.color = '#2ecc71'; break; case 'error': line.style.color = '#e74c3c'; break; default: line.style.color = '#333'; } const time = new Date().toLocaleTimeString(); line.innerHTML = `[${time}] ${message}`; panel.appendChild(line); panel.scrollTop = panel.scrollHeight; } catch (_) { // 忽略日志异常 } } function startLoop() { enabled = true; autoAnswerLoop = true; const switchBtn = document.getElementById('question-switch'); if (switchBtn) { switchBtn.innerText = '题目收集: 开'; switchBtn.style.background = '#ff69b4'; } if (loopIntervalId) clearInterval(loopIntervalId); if (aiAnswers.length > 0) { log('【AI】AI循环作答已开启', 'info'); aiAnswerAndNext(); loopIntervalId = setInterval(aiAnswerAndNext, 3000); } else { log('【收集】循环收集已开启', 'info'); recordQuestion().then(() => { clickNext(); loopIntervalId = setInterval(clickNext, 3000); }); } } function stopLoop() { autoAnswerLoop = false; enabled = false; if (loopIntervalId) { clearInterval(loopIntervalId); loopIntervalId = null; } const switchBtn = document.getElementById('question-switch'); if (switchBtn) { switchBtn.innerText = '题目收集:关'; switchBtn.style.background = '#aaa'; } log('【循环】已关闭', 'info'); } function createAutoAnswerLoopButton() { // 创建一个隐藏的按钮,作为统一的循环开关(供代码内部 click 调用) const footer = document.getElementById('main-panel-footer'); const btn = document.createElement('button'); btn.id = 'auto-answer-loop-btn'; btn.style.display = 'none'; btn.onclick = () => { if (autoAnswerLoop) { stopLoop(); } else { startLoop(); } }; if (footer) footer.appendChild(btn); return btn; } // --- UI Creation --- function createMainPanel() { if (document.getElementById('main-panel')) return; // --- Create Show Button (for when panel is hidden) --- const showBtn = document.createElement('button'); showBtn.id = 'show-main-panel-btn'; showBtn.innerText = '显示助手'; showBtn.style.position = 'fixed'; showBtn.style.bottom = '40px'; showBtn.style.right = '40px'; showBtn.style.zIndex = '10001'; showBtn.style.padding = '10px 15px'; showBtn.style.border = 'none'; showBtn.style.background = '#1890ff'; showBtn.style.color = 'white'; showBtn.style.borderRadius = '8px'; showBtn.style.cursor = 'pointer'; showBtn.style.display = 'block'; // Initially visible document.body.appendChild(showBtn); // --- Create Main Panel --- const panel = document.createElement('div'); panel.id = 'main-panel'; panel.style.position = 'fixed'; panel.style.bottom = '40px'; panel.style.right = '40px'; panel.style.width = '550px'; panel.style.height = '350px'; panel.style.zIndex = '10000'; panel.style.background = '#f0f2f5'; panel.style.border = '1px solid #d9d9d9'; panel.style.borderRadius = '8px'; panel.style.boxShadow = '4px 12px rgba(0,0,0,0.15)'; panel.style.display = 'none'; // Initially hidden panel.style.flexDirection = 'column'; panel.style.resize = 'both'; // Make it resizable panel.style.overflow = 'hidden'; // Hide overflow from resizing panel.style.minWidth = '400px'; panel.style.minHeight = '250px'; panel.innerHTML = `
答题助手
剪贴板预览
日志
`; document.body.appendChild(panel); // --- Get Element References --- const header = document.getElementById('main-panel-header'); const hideBtn = document.getElementById('hide-main-panel-btn'); const readClipboardBtn = document.getElementById('read-clipboard-btn'); const questionSwitchBtn = document.getElementById('question-switch'); const copyBtn = document.getElementById('copy-all-questions-btn'); const loopBtn = createAutoAnswerLoopButton(); // Create the hidden loop button const scanImagesBtn = document.getElementById('scan-images-btn'); // 新增按钮 // 插入「双区复制」UI 到读取剪贴板按钮的左边 injectDualCopyUI(readClipboardBtn); // --- Event Listeners --- // Show/Hide Logic hideBtn.onclick = () => { panel.style.display = 'none'; showBtn.style.display = 'block'; }; showBtn.onclick = () => { panel.style.display = 'flex'; showBtn.style.display = 'none'; }; // Dragging Logic let isDragging = false; let offsetX, offsetY; header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON') return; isDragging = true; offsetX = e.clientX - panel.getBoundingClientRect().left; offsetY = e.clientY - panel.getBoundingClientRect().top; panel.style.bottom = 'auto'; panel.style.right = 'auto'; document.body.style.userSelect = 'none'; // Prevent text selection while dragging }); document.addEventListener('mousemove', (e) => { if (isDragging) { panel.style.left = `${e.clientX - offsetX}px`; panel.style.top = `${e.clientY - offsetY}px`; } }); document.addEventListener('mouseup', () => { isDragging = false; document.body.style.userSelect = ''; }); // --- Button OnClick Handlers --- // 新增:扫描图片题按钮 scanImagesBtn.onclick = () => { if (imageScanner.isScanning) { imageScanner.stop(); } else { imageScanner.start(); } }; // Read Clipboard Button readClipboardBtn.onclick = async () => { try { const text = await navigator.clipboard.readText(); const preview = document.getElementById('clipboard-preview'); if (preview) preview.textContent = text; // ---解析 JSON 数组或多段 JSON --- const jsonStrings = text.match(/{[\s\S]*?}/g); let parsedAnswers = []; if (jsonStrings) { parsedAnswers = jsonStrings.map(s => JSON.parse(s)); } aiAnswers = parsedAnswers; currentAiAnswerIndex = 0; const loopBtnEl = document.getElementById('auto-answer-loop-btn'); if (Array.isArray(aiAnswers) && aiAnswers.length > 0) { log(`【AI】成功加载 ${aiAnswers.length} 个答案`, 'success'); if (!autoAnswerLoop && loopBtnEl) { // 自动启动循环 loopBtnEl.click(); } } else { aiAnswers = []; log('【AI】加载失败,未在剪贴板内容中找到有效的JSON答案', 'error'); } } catch (err) { aiAnswers = []; log('【AI】读取或解析剪贴板失败', 'error'); console.error('Clipboard read/parse failed: ', err); } }; // Question Collection Switch questionSwitchBtn.onclick = function () { enabled = !enabled; autoAnswerLoop = enabled; this.innerText = enabled ? '题目收集: 开' : '题目收集:关'; this.style.background = enabled ? '#ff69b4' : '#aaa'; log(enabled ? '【开关】题目收集已开启' : '【开关】题目收集已关闭', 'info'); if (autoAnswerLoop) { if (loopIntervalId) clearInterval(loopIntervalId); if (aiAnswers.length > 0) { log('【AI】AI循环作答已开启', 'info'); aiAnswerAndNext(); loopIntervalId = setInterval(aiAnswerAndNext, 3000); } else { log('【收集】循环收集已开启', 'info'); // First, record the current question, then click next. recordQuestion().then(() => { clickNext(); loopIntervalId = setInterval(clickNext, 3000); }); } } else { log('【循环】已关闭', 'info'); if (loopIntervalId) clearInterval(loopIntervalId); } }; // Copy Questions Button copyBtn.onclick = function () { let text = allQuestions.map((q, idx) => { let opts = q.options.map((opt, i) => { const label = opt.label || String.fromCharCode(65 + i); return `${label}. ${opt.text} (value: ${opt.value})`; }).join('\n'); return `【${q.type}】第${idx + 1}题:${q.question}\n${opts}`; // 这里的 idx + 1 是为了题号从 1 开始 }).join('\n\n'); if (!text) text = '暂无题目记录'; text += `\n\n----\n请为当前页面上的所有题目生成答案,并严格返回一个 JSON 数组,数组中每道题对应一个独立 JSON 对象。除 JSON 外不要输出任何文字,不要加代码块标记。每题必须严格使用以下字段:{"id":题号,"choiceValue":"单选填字符串;多选填字符串数组;非选择题填null","choice":"单选填字符串;多选填字符串数组;非选择题填null","judge":"判断题填true或false;非判断题填null","fills":["填空题答案数组;非填空题填[]"]}。规则:单选题只填写 choiceValue 和 choice;多选题的 choiceValue 和 choice 必须为数组;判断题只填写 judge;填空题只填写 fills;所有非对应题型字段必须填 null 或 [];最终只返回 JSON 数组。`; navigator.clipboard.writeText(text).then(() => { this.innerText = '已复制!'; log('【复制】已复制全部题目到剪贴板,正在刷新页面...', 'success'); // 刷新页面 window.location.reload(); }, () => { log('【复制】复制失败', 'error'); }); }; } // 注入双区复制 UI(放在读取剪贴板左边) function injectDualCopyUI(readClipboardBtn) { const footer = document.getElementById('main-panel-footer'); if (!footer) return; const btn = document.createElement('button'); btn.textContent = '双区复制'; btn.style.padding = '8px 10px'; btn.style.border = 'none'; btn.style.background = '#722ed1'; btn.style.color = '#fff'; btn.style.borderRadius = '4px'; btn.style.cursor = 'pointer'; btn.onclick = async () => { // 尝试区1与区2,两者去重后合并;都没有则回落到常见选择器与页面文本 const textA = collectContentTextFrom(COPY_A_XPATHS) || ''; const textB = collectContentTextFrom(COPY_B_XPATHS) || ''; let combined = ''; if (textA && textB) { combined = textA === textB ? textA : `${textA}\n\n${textA}`; } else { combined = textA || textB || collectContentTextFrom([]); } const finalText = removeUiStrings(combined); if (!finalText) { log('【复制】未找到可复制的内容', 'error'); return; } const ok = await copyText(finalText); if (ok) log('【复制】内容已复制', 'success'); else log('【复制】复制失败', 'error'); }; // 插到读取剪贴板按钮左侧 footer.insertBefore(btn, readClipboardBtn); } // --- Core Logic --- function resetAutoSubmitTimer() { if (autoSubmitTimerId) { clearTimeout(autoSubmitTimerId); autoSubmitTimerId = null; // log('【计时器】自动提交已重置', 'info'); // This can be spammy } } function startAutoSubmitTimer() { resetAutoSubmitTimer(); if (autoSubmitEnabled) { log(`【计时器】${SUBMIT_TIMEOUT / 1000}秒后将自动提交...`, 'info'); autoSubmitTimerId = setTimeout(() => { log('【计时器】时间到,执行自动提交', 'success'); clickSubmit(); }, SUBMIT_TIMEOUT); } } function clickNext(isScanning = false) { resetAutoSubmitTimer(); const btn = [...document.querySelectorAll('button.ant-btn, button')] .find(el => (el.innerText || '').includes('下一题')); if (btn && !btn.disabled) { btn.click(); if (!isScanning) log('【操作】已点击“下一题”按钮', 'success'); return true; } else { if (!isScanning) { log('【操作】未找到“下一题”按钮或已是最后一题', 'error'); if (autoAnswerLoop) { const loopBtn = document.getElementById('auto-answer-loop-btn'); if (loopBtn) loopBtn.click(); // 停止循环 } } return false; } } function clickSubmit() { resetAutoSubmitTimer(); const submitKeywords = ['提交', '确认', '交卷']; const btn = [...document.querySelectorAll('button.ant-btn')] .find(el => submitKeywords.some(kw => (el.innerText || '').includes(kw) && !(el.innerText || '').includes('下一题'))); if (btn) { btn.click(); log('【操作】已点击“提交”按钮', 'success'); return true; } else { log('【操作】未找到“提交”按钮', 'error'); return false; } } function detectQuestionType() { if (document.querySelector('input[type="text"], textarea')) { return '填空题'; } const radioOptions = document.querySelectorAll('.ant-radio-wrapper'); if (radioOptions.length > 2) { return '单选题'; } if (radioOptions.length === 2 && [...radioOptions].some(el => (el.innerText || '').includes('正确') || (el.innerText || '').includes('错误'))) { return '判断题'; } const checkOptions = document.querySelectorAll('.ant-checkbox-wrapper'); if (checkOptions.length > 1) { return '多选题'; } return '未知'; } async function executeAnswer(answer) { try { let answered = false; const type = detectQuestionType(); // 填空题处理 if (type === '填空题' && Array.isArray(answer.fills)) { const inputs = Array.from(document.querySelectorAll('input[type="text"], textarea, [contenteditable="true"]')); if (inputs.length > 0) { const setNativeValue = (el, value) => { const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : el.tagName === 'INPUT' ? HTMLInputElement.prototype : HTMLElement.prototype; const descriptor = Object.getOwnPropertyDescriptor(proto, 'value'); if (descriptor && descriptor.set) { descriptor.set.call(el, value); } else { el.value = value; } }; const fire = (el, type, opts = {}) => el.dispatchEvent(new Event(type, { bubbles: true, cancelable: true, ...opts })); const fireKeyboard = (el, type, key) => el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, key, code: `Key${(key || '').toUpperCase()}`, composed: true })); const fireInput = (el, data) => { try { el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data, inputType: 'insertText' })); } catch (_) { // 部分环境不支持 InputEvent 构造函数 el.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); } }; for (let i = 0; i < inputs.length && i < answer.fills.length; i++) { if (i > 0) { await new Promise(r => setTimeout(r, 200)); // 空与空之间的缓冲 } const el = inputs[i]; const text = String(answer.fills[i] ?? ''); el.focus(); fire(el, 'focus'); fire(el, 'focusin'); fire(el, 'compositionstart'); setNativeValue(el, ''); fireInput(el, ''); fire(el, 'change'); for (const ch of text) { fireKeyboard(el, 'keydown', ch); fireKeyboard(el, 'keypress', ch); setNativeValue(el, (el.value || '') + ch); fireInput(el, ch); fireKeyboard(el, 'keyup', ch); await new Promise(r => setTimeout(r, 100)); // 字与字之间的缓冲 } fire(el, 'compositionend'); fireKeyboard(el, 'keydown', 'Enter'); fireKeyboard(el, 'keypress', 'Enter'); fireKeyboard(el, 'keyup', 'Enter'); fire(el, 'change'); fire(el, 'blur'); fire(el, 'focusout'); log(`✅ 第${i + 1}个填空题已模拟输入: ${text}`, 'success'); } answered = true; } } // 判断题处理 else if (type === '判断题' && typeof answer.judge === 'boolean') { const text = answer.judge ? '正确' : '错误'; const labels = Array.from(document.querySelectorAll('.ant-radio-wrapper')); const target = labels.find(l => (l.innerText || '').includes(text)); if (target) { target.click(); log(`【AI】已选择判断: ${text}`, 'success'); answered = true; } } //选择题处理 else if ((type === '单选题' || type === '多选题') && (answer.choiceValue || answer.choice)) { let choices = []; if (answer.choiceValue && (Array.isArray(answer.choiceValue) ? answer.choiceValue.length > 0 : true)) { choices = Array.isArray(answer.choiceValue) ? answer.choiceValue : [answer.choiceValue]; } else if (answer.choice) { choices = Array.isArray(answer.choice) ? answer.choice : [answer.choice]; } let foundAny = false; for (let val of choices) { let input = document.querySelector(`input[type="radio"][value="${val}"], input[type="checkbox"][value="${val}"]`); if (!input && typeof val === 'string') { const options = Array.from(document.querySelectorAll('.ant-radio-wrapper, .ant-checkbox-wrapper')); const choiceIndex = val.charCodeAt(0) - 'A'.charCodeAt(0); if (options[choiceIndex]) { input = options[choiceIndex].querySelector('input'); } } if (input) { input.click(); log(`【AI】已选择选项: ${val}`, 'success'); foundAny = true; } } if (foundAny) { answered = true; } } if (answered) { startAutoSubmitTimer(); return true; } else { log(`【AI】未能根据JSON执行答题 (类型: ${type})`, 'error'); return false; } } catch (e) { log('【AI】执行时发生错误', 'error'); console.error('Answer execution error:', e); return false; } } // New function for AI-powered looping async function aiAnswerAndNext() { const loopBtn = document.getElementById('auto-answer-loop-btn'); if (aiAnswers.length === 0) { log('【AI】答案列表为空,循环终止', 'error'); if (loopBtn) loopBtn.click(); return; } if (currentAiAnswerIndex >= aiAnswers.length) { log('【AI】所有答案已用完,循环终止', 'info'); if (loopBtn) loopBtn.click(); return; } log(`【AI】执行第 ${currentAiAnswerIndex + 1} 个答案...`, 'info'); const answered = await executeAnswer(aiAnswers[currentAiAnswerIndex]); currentAiAnswerIndex++; if (answered) { const type = detectQuestionType(); if (type !== '填空题') { setTimeout(() => clickNext(), 800); } else { await new Promise(r => setTimeout(r, 200)); clickNext(); } } else { log('【AI】答题失败,循环终止', 'error'); if (loopBtn) loopBtn.click(); } } //记录题目、选项、题型 async function recordQuestion() { if (!enabled) return; const type = detectQuestionType(); if (type === '未知') { return; } let question = ''; const questionEl = document.querySelector('.question_title'); if (questionEl) { question = questionEl.innerText.trim(); } else { // Fallback const typeKeywords = ['单选题', '多选题', '判断题', '填空题', '选择题', '简答题']; let all = Array.from(document.querySelectorAll('div, p, span')); let idx = all.findIndex(el => (el.innerText || '') && typeKeywords.some(k => (el.innerText || '').includes(k))); if (idx !== -1 && idx < all.length - 1) { let next = all[idx + 1]; if (next) { question = next.innerText ? next.innerText.trim() : ''; let p = next.querySelector && next.querySelector('p'); if (p && p.innerText) question = p.innerText.trim(); } } } let options = []; let optionEls = Array.from(document.querySelectorAll('.ant-radio-wrapper, .ant-checkbox-wrapper')); for (const el of optionEls) { const text = el.innerText ? el.innerText.trim() : ''; const input = el.querySelector('input'); const value = input ? input.value : null; const labelMatch = text.match(/^([A-Z])\s*\.?\s*/); const label = labelMatch ? labelMatch[1] : null; const cleanText = text.replace(/^([A-Z])\s*\.?\s*/, ''); options.push({ label, text: cleanText, value }); } if (question && !allQuestions.some(q => q.question === question)) { allQuestions.push({ question, options, type }); const coloredQuestion = `${question}`; const coloredOptions = options.map(o => `${o.text}`).join(' | '); log(`【成功】已记录 [${type}]
题目:${coloredQuestion}
选项:${coloredOptions}`, 'success'); // Mark the active question on the answer sheet try { markQuestionOnSheet('current'); // 标记为当前题目 } catch (e) { log('【标记】标记题目时出错', 'error'); console.error('Marking question failed:', e); } } } // --- 增强的标记函数和辅助函数 --- // 获取当前题目的题型和题号 function getCurrentQuestionInfo() { const typeEl = document.querySelector('.questionTypeTitle___r6Fo9'); const qType = typeEl ? typeEl.textContent.trim().replace(/^[一二三四五六七八九十]、/, '') : ''; const numEl = document.querySelector('.font16.noWrap___X6AS3'); const qNum = numEl ? numEl.textContent.replace('、', '').trim() : null; return { qType, qNum }; } // 融合并增强的标记函数 function markQuestionOnSheet(styleType = 'current', questionNum = null) { const { qType, qNum } = getCurrentQuestionInfo(); const targetNum = questionNum || qNum; if (!targetNum) { if (!questionNum) log('【标记】无法获取当前题号', 'error'); return; } const groups = document.querySelectorAll('.answerSheetWrap___aPipx'); let marked = false; groups.forEach(group => { const titleEl = group.querySelector('.answerSheetQuestionTitle___P18Ss .ml5.c-grey-666'); // 如果是按题号精确查找,则不需要匹配题型 if (!questionNum && titleEl && !qType.includes(titleEl.textContent.trim())) { return; } const items = group.querySelectorAll('.answerSheetItem___DIH2V'); items.forEach(itemEl => { const indexEl = itemEl.querySelector('.qindex___XuKA8'); if (indexEl && indexEl.textContent.trim() === targetNum) { // 清除旧样式,但保留图片标记 if (itemEl.dataset.marker !== 'image') { itemEl.style.cssText = ''; indexEl.style.cssText = ''; } if (styleType === 'current') { itemEl.style.backgroundColor = 'yellow'; itemEl.style.border = '2px solid red'; itemEl.dataset.marker = 'current'; } else if (styleType === 'image') { // 应用来自辅助脚本的醒目样式 Object.assign(indexEl.style, { background: '#ff3333', color: '#ffffff', fontWeight: 'bold', borderRadius: '8px', border: '2px solid rgba(255, 255, 255, 0.95)', transform: 'scale(1.1)', textShadow: '0 0 3px rgba(0, 0, 0, 0.5)', boxShadow: '0 0 15px 5px rgba(255, 51, 51, 0.9)', display: 'inline-block', transition: 'background 0.5s, box-shadow 0.5s' }); itemEl.dataset.marker = 'image'; } marked = true; } else { // 清除非当前题目的 'current' 标记 if (itemEl.dataset.marker === 'current') { itemEl.style.cssText = ''; indexEl.style.cssText = ''; delete itemEl.dataset.marker; } } }); }); if (marked) { if (!questionNum) log(`【标记】已标记题号 ${targetNum} (类型: ${styleType})`, 'info'); } else { if (!questionNum) log(`【标记】未在答题卡找到匹配项: ${qType} - ${targetNum}`, 'error'); } } // 初始化界面 function init() { createMainPanel(); // Interval to check for question changes setInterval(() => { const questionEl = document.querySelector('.question_title'); const currentQuestionTitle = questionEl ? questionEl.innerText.trim() : ''; if (currentQuestionTitle && currentQuestionTitle !== lastKnownQuestionTitle) { lastKnownQuestionTitle = currentQuestionTitle; resetAutoSubmitTimer(); const type = detectQuestionType(); if (type !== '未知') { (typeof log === 'function' ? log(`检测到题目变化: ${type}`, 'info') : console.log(`检测到题目变化: ${type}`)); // 自动检查新题目是否为图片题 if (imageScanner.isScanning) { setTimeout(() => imageScanner.checkCurrentImage(), 500); } } } }, 1000); setInterval(() => { if (enabled) { recordQuestion(); } }, 2000); } init(); })();