// ==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();
})();