// ==UserScript== // @name 小rain的习传助手 // @namespace http://tampermonkey.net/ // @version 3.0 // @description 习传网智能答题助手 - 支持多模型API、批量答题、自动跳转 // @author Rain // @match *://*.xichuanwh.com/* // @match *://xichuanwh.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // ==/UserScript== (function() { 'use strict'; // ==================== 多模型配置 ==================== const MODEL_CONFIGS = { deepseek: { name: 'DeepSeek', url: 'https://api.deepseek.com/v1/chat/completions', model: 'deepseek-chat', keyPlaceholder: 'sk-...' }, openai: { name: 'OpenAI', url: 'https://api.openai.com/v1/chat/completions', model: 'gpt-3.5-turbo', keyPlaceholder: 'sk-...' }, claude: { name: 'Claude', url: 'https://api.anthropic.com/v1/messages', model: 'claude-3-haiku-20240307', keyPlaceholder: 'sk-ant-...' }, kimi: { name: 'Kimi', url: 'https://api.moonshot.cn/v1/chat/completions', model: 'moonshot-v1-8k', keyPlaceholder: 'sk-...' }, qwen: { name: '通义千问', url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', model: 'qwen-turbo', keyPlaceholder: 'sk-...' }, custom: { name: '自定义', url: '', model: '', keyPlaceholder: '输入API Key' } }; // ==================== 状态 ==================== let currentModel = GM_getValue('rain_model', 'deepseek'); let apiKey = GM_getValue('rain_api_key', ''); let customUrl = GM_getValue('rain_custom_url', ''); let customModel = GM_getValue('rain_custom_model', ''); let answers = []; let isRunning = false; let isPanelCollapsed = false; // 批量答题状态 let batchMode = false; let questionList = []; // 题目列表 let currentQuestionIndex = 0; // 当前题目索引 let autoNext = false; // 是否自动跳转下一题 let skipCompleted = false; // 是否跳过已做题目 // 自动刷题状态 let autoSolving = false; // 是否正在自动刷题 let autoSolveQueue = []; // 自动刷题队列 let autoSolveIndex = 0; // 当前自动刷题索引 // ==================== UI样式 ==================== const STYLES = ` #rain-xichuan-container { position: fixed; top: 10px; right: 10px; z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } #rain-xichuan-panel { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 2px; box-shadow: 0 10px 40px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1); min-width: 320px; max-width: 400px; transition: all 0.3s ease; resize: both; overflow: hidden; } #rain-xichuan-panel.collapsed { min-width: 150px; resize: none; } #rain-xichuan-panel .panel-inner { background: rgba(255,255,255,0.98); border-radius: 14px; overflow: hidden; } #rain-xichuan-panel .panel-header { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 16px; cursor: move; display: flex; align-items: center; justify-content: space-between; user-select: none; } #rain-xichuan-panel .panel-header h3 { margin: 0; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } #rain-xichuan-panel .panel-header .header-actions { display: flex; gap: 8px; } #rain-xichuan-panel .panel-header button { background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; display: flex; align-items: center; justify-content: center; } #rain-xichuan-panel .panel-header button:hover { background: rgba(255,255,255,0.3); } #rain-xichuan-panel .panel-body { padding: 16px; } #rain-xichuan-panel.collapsed .panel-body { display: none; } #rain-xichuan-panel .section { margin-bottom: 16px; } #rain-xichuan-panel .section-title { font-size: 12px; font-weight: 600; color: #666; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; } #rain-xichuan-panel select, #rain-xichuan-panel input[type="text"], #rain-xichuan-panel input[type="password"] { width: 100%; padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 13px; box-sizing: border-box; transition: all 0.2s; background: #fafafa; } #rain-xichuan-panel select:focus, #rain-xichuan-panel input:focus { outline: none; border-color: #667eea; background: white; } #rain-xichuan-panel .btn { width: 100%; padding: 12px; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 10px; } #rain-xichuan-panel .btn-primary { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; } #rain-xichuan-panel .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } #rain-xichuan-panel .btn-secondary { background: #f0f0f0; color: #333; } #rain-xichuan-panel .btn-secondary:hover { background: #e0e0e0; } #rain-xichuan-panel .btn-success { background: linear-gradient(90deg, #11998e 0%, #38ef7d 100%); color: white; } #rain-xichuan-panel .btn-success:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(17, 153, 142, 0.4); } #rain-xichuan-panel .btn-warning { background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%); color: white; } #rain-xichuan-panel .status { padding: 10px 12px; background: #f8f9fa; border-radius: 8px; font-size: 13px; color: #666; margin-bottom: 12px; border-left: 3px solid #667eea; } #rain-xichuan-panel .status.error { border-left-color: #f5576c; background: #fff5f5; color: #c53030; } #rain-xichuan-panel .status.success { border-left-color: #38ef7d; background: #f0fff4; color: #276749; } #rain-xichuan-panel .answers-container { background: #f8f9fa; border-radius: 10px; padding: 12px; max-height: 200px; overflow-y: auto; font-size: 13px; } #rain-xichuan-panel .answer-item { display: flex; align-items: center; padding: 6px 0; border-bottom: 1px solid #e0e0e0; } #rain-xichuan-panel .answer-item:last-child { border-bottom: none; } #rain-xichuan-panel .answer-num { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-right: 10px; } #rain-xichuan-panel .answer-val { font-weight: 600; color: #333; } #rain-xichuan-panel .hint-box { background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); border-radius: 10px; padding: 12px; margin-bottom: 12px; font-size: 12px; color: #744210; } #rain-xichuan-panel .hint-box strong { display: block; margin-bottom: 4px; font-size: 13px; } #rain-xichuan-panel .custom-fields { display: none; } #rain-xichuan-panel .custom-fields.visible { display: block; } #rain-xichuan-panel .resize-handle { position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; cursor: se-resize; background: linear-gradient(135deg, transparent 50%, rgba(102, 126, 234, 0.3) 50%); border-radius: 0 0 14px 0; } #rain-xichuan-panel .author-info { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e0e0e0; text-align: center; font-size: 11px; color: #999; } #rain-xichuan-panel .author-info a { color: #667eea; text-decoration: none; } #rain-xichuan-panel .author-info a:hover { text-decoration: underline; } /* 批量答题样式 */ #rain-xichuan-panel .batch-section { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; padding: 12px; margin-bottom: 12px; color: white; } #rain-xichuan-panel .batch-title { font-size: 14px; font-weight: 600; margin-bottom: 8px; } #rain-xichuan-panel .batch-progress { font-size: 12px; margin-bottom: 8px; opacity: 0.9; } #rain-xichuan-panel .batch-list { max-height: 150px; overflow-y: auto; background: rgba(255,255,255,0.1); border-radius: 6px; padding: 8px; font-size: 11px; } #rain-xichuan-panel .batch-item { padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; align-items: center; } #rain-xichuan-panel .batch-item:last-child { border-bottom: none; } #rain-xichuan-panel .batch-item.completed { opacity: 0.6; text-decoration: line-through; } #rain-xichuan-panel .batch-item.current { font-weight: bold; color: #ffecd2; } #rain-xichuan-panel .batch-controls { display: flex; gap: 8px; margin-top: 8px; } /* 自动刷题样式 */ #rain-xichuan-panel #rain-auto-solve-section { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); border-radius: 10px; padding: 12px; margin-bottom: 12px; color: white; } #rain-xichuan-panel #rain-auto-solve-section .batch-title { font-size: 14px; font-weight: 600; margin-bottom: 10px; } #rain-xichuan-panel #rain-auto-solve-list { background: rgba(255,255,255,0.15); border-radius: 6px; padding: 8px; } #rain-xichuan-panel #rain-auto-solve-list > div { padding: 6px; border-bottom: 1px solid rgba(255,255,255,0.2); transition: background 0.2s; } #rain-xichuan-panel #rain-auto-solve-list > div:hover { background: rgba(255,255,255,0.1); } #rain-xichuan-panel #rain-auto-solve-list > div:last-child { border-bottom: none; } #rain-xichuan-panel #rain-auto-solve-list input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; } #rain-xichuan-panel .batch-controls .btn { flex: 1; margin-bottom: 0; padding: 8px; font-size: 12px; } #rain-xichuan-panel .auto-next-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-top: 8px; } #rain-xichuan-panel .auto-next-toggle input[type="checkbox"] { width: auto; } #rain-about-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 100000; display: flex; align-items: center; justify-content: center; } #rain-about-modal .modal-content { background: white; border-radius: 16px; padding: 24px; max-width: 360px; text-align: center; box-shadow: 0 20px 60px rgba(0,0,0,0.3); } #rain-about-modal .modal-content h2 { margin: 0 0 16px 0; color: #667eea; } #rain-about-modal .modal-content .author { font-size: 14px; color: #666; margin-bottom: 8px; } #rain-about-modal .modal-content .school { font-size: 13px; color: #999; margin-bottom: 16px; } #rain-about-modal .modal-content .desc { font-size: 13px; color: #555; line-height: 1.6; margin-bottom: 20px; } #rain-about-modal .modal-content button { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 10px 32px; border-radius: 8px; font-size: 14px; cursor: pointer; } `; // ==================== UI ==================== function createPanel() { // 添加样式 const styleEl = document.createElement('style'); styleEl.textContent = STYLES; document.head.appendChild(styleEl); // 创建容器 const container = document.createElement('div'); container.id = 'rain-xichuan-container'; const panel = document.createElement('div'); panel.id = 'rain-xichuan-panel'; const config = MODEL_CONFIGS[currentModel]; panel.innerHTML = `

🌧️ 小rain的习传助手

💡 使用提示 如果觉得好用可以分享给你的朋友,让他们进群支持我~
选择模型
自定义 API URL
自定义模型名
API Key
💡 可直接点击"开始答题"手动刷题,或先"读取题目列表"批量刷题
就绪 - 请选择模型并配置API Key
作者:重庆工程职院一位不知名同学
咨询群:915332250
`; container.appendChild(panel); document.body.appendChild(container); // 绑定事件 bindEvents(panel); // 初始化拖动功能 initDrag(panel); // 如果有已保存的 API Key,隐藏配置区域 if (apiKey) { document.getElementById('rain-api-config-section').style.display = 'none'; document.getElementById('rain-toggle-api-btn').style.display = 'flex'; updateStatus('已加载保存的配置,点击开始答题'); } } function bindEvents(panel) { // 模型选择 const modelSelect = document.getElementById('rain-model-select'); modelSelect.onchange = () => { currentModel = modelSelect.value; const config = MODEL_CONFIGS[currentModel]; document.getElementById('rain-api-key').placeholder = config.keyPlaceholder; const customFields = document.querySelector('.custom-fields'); if (currentModel === 'custom') { customFields.classList.add('visible'); } else { customFields.classList.remove('visible'); } }; // 保存配置 document.getElementById('rain-save-btn').onclick = () => { const key = document.getElementById('rain-api-key').value.trim(); const url = document.getElementById('rain-custom-url')?.value.trim() || ''; const model = document.getElementById('rain-custom-model')?.value.trim() || ''; if (key) { GM_setValue('rain_api_key', key); GM_setValue('rain_model', currentModel); GM_setValue('rain_custom_url', url); GM_setValue('rain_custom_model', model); apiKey = key; customUrl = url; customModel = model; updateStatus('配置已保存', 'success'); // 隐藏 API 配置区域,显示切换按钮 document.getElementById('rain-api-config-section').style.display = 'none'; document.getElementById('rain-toggle-api-btn').style.display = 'flex'; } else { updateStatus('请输入 API Key', 'error'); } }; // 切换 API 配置显示/隐藏 document.getElementById('rain-toggle-api-btn').onclick = () => { const apiSection = document.getElementById('rain-api-config-section'); const toggleBtn = document.getElementById('rain-toggle-api-btn'); if (apiSection.style.display === 'none') { apiSection.style.display = 'block'; toggleBtn.innerHTML = '🔼 隐藏 API 配置'; } else { apiSection.style.display = 'none'; toggleBtn.innerHTML = '⚙️ 切换 API 配置'; } }; // 读取题目列表 document.getElementById('rain-read-list-btn').onclick = readQuestionList; // 自动刷题按钮 document.getElementById('rain-auto-solve-btn').onclick = showAutoSolveSelector; // 开始/停止自动刷题 document.getElementById('rain-start-auto-btn').onclick = startAutoSolve; document.getElementById('rain-stop-auto-btn').onclick = stopAutoSolve; // 开始答题 document.getElementById('rain-start-btn').onclick = startAnswering; // 填写/标记答案 document.getElementById('rain-fill-btn').onclick = fillAnswers; // 提交答案 document.getElementById('rain-submit-btn').onclick = submitAnswers; // 批量答题控制 document.getElementById('rain-prev-btn').onclick = goToPrevQuestion; document.getElementById('rain-next-btn').onclick = goToNextQuestion; document.getElementById('rain-auto-next').onchange = (e) => { autoNext = e.target.checked; GM_setValue('rain_auto_next', autoNext); }; document.getElementById('rain-skip-completed').onchange = (e) => { skipCompleted = e.target.checked; GM_setValue('rain_skip_completed', skipCompleted); }; // 加载保存的设置 autoNext = GM_getValue('rain_auto_next', false); skipCompleted = GM_getValue('rain_skip_completed', false); document.getElementById('rain-auto-next').checked = autoNext; document.getElementById('rain-skip-completed').checked = skipCompleted; // 收起/展开 document.getElementById('rain-collapse-btn').onclick = () => { isPanelCollapsed = !isPanelCollapsed; panel.classList.toggle('collapsed', isPanelCollapsed); document.getElementById('rain-collapse-btn').textContent = isPanelCollapsed ? '+' : '−'; }; // 关闭面板 document.getElementById('rain-close-btn').onclick = () => { if (confirm('确定要关闭助手面板吗?刷新页面可重新打开。')) { document.getElementById('rain-xichuan-container').remove(); } }; } // ==================== 拖动功能 ==================== function initDrag(panel) { const header = panel.querySelector('.panel-header'); const container = document.getElementById('rain-xichuan-container'); let isDragging = false; let startX, startY, startLeft, startTop; header.onmousedown = (e) => { // 如果点击的是按钮,不触发拖动 if (e.target.tagName === 'BUTTON') return; isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = container.offsetLeft; startTop = container.offsetTop; panel.style.transition = 'none'; document.body.style.userSelect = 'none'; }; document.onmousemove = (e) => { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; container.style.left = (startLeft + dx) + 'px'; container.style.top = (startTop + dy) + 'px'; container.style.right = 'auto'; }; document.onmouseup = () => { if (isDragging) { isDragging = false; panel.style.transition = ''; document.body.style.userSelect = ''; } }; } function updateStatus(text, type = '') { const el = document.getElementById('rain-status'); if (el) { el.textContent = text; el.className = 'status ' + type; } console.log('[Rain习传]', text); } // ==================== 自动刷题功能 ==================== // 显示自动刷题选择界面 function showAutoSolveSelector() { const section = document.getElementById('rain-auto-solve-section'); const list = document.getElementById('rain-auto-solve-list'); const autoSolveBtn = document.getElementById('rain-auto-solve-btn'); if (!section || !list) return; // 隐藏自动刷题按钮,显示选择区域 if (autoSolveBtn) autoSolveBtn.style.display = 'none'; section.style.display = 'block'; // 生成题目列表(只显示未完成的题目) list.innerHTML = ''; const unanswered = questionList.filter(q => !q.isCompleted); if (unanswered.length === 0) { list.innerHTML = '
没有未完成的题目
'; return; } unanswered.forEach((q, idx) => { const item = document.createElement('div'); item.className = 'batch-item'; item.style.cssText = 'display: flex; align-items: center; padding: 6px; border-bottom: 1px solid #eee; font-size: 13px;'; item.innerHTML = ` ${idx + 1}. ${q.title} `; list.appendChild(item); }); // 绑定复选框事件 const checkboxes = list.querySelectorAll('.rain-solve-checkbox'); checkboxes.forEach(cb => { cb.addEventListener('change', updateSelectedCount); }); // 全选复选框事件 const selectAll = document.getElementById('rain-select-all'); if (selectAll) { selectAll.checked = true; selectAll.addEventListener('change', (e) => { checkboxes.forEach(cb => cb.checked = e.target.checked); updateSelectedCount(); }); } updateSelectedCount(); updateStatus(`请选择要自动刷的题目(共 ${unanswered.length} 道未做)`, 'success'); } // 更新已选题目数量 function updateSelectedCount() { const checked = document.querySelectorAll('.rain-solve-checkbox:checked'); const countEl = document.getElementById('rain-selected-count'); if (countEl) countEl.textContent = checked.length; } // 开始自动刷题 async function startAutoSolve() { const checked = document.querySelectorAll('.rain-solve-checkbox:checked'); if (checked.length === 0) { updateStatus('请至少选择一道题目', 'error'); return; } // 构建队列 autoSolveQueue = Array.from(checked).map(cb => parseInt(cb.dataset.index)); autoSolveIndex = 0; autoSolving = true; // 更新UI document.getElementById('rain-start-auto-btn').style.display = 'none'; document.getElementById('rain-stop-auto-btn').style.display = 'block'; document.getElementById('rain-auto-solve-list').style.opacity = '0.6'; document.getElementById('rain-select-all').disabled = true; document.querySelectorAll('.rain-solve-checkbox').forEach(cb => cb.disabled = true); updateStatus(`开始自动刷题,共 ${autoSolveQueue.length} 道`, 'success'); // 开始刷第一题 await processAutoSolve(); } // 处理自动刷题流程 async function processAutoSolve() { if (!autoSolving || autoSolveIndex >= autoSolveQueue.length) { finishAutoSolve(); return; } const questionIdx = autoSolveQueue[autoSolveIndex]; const question = questionList.find(q => q.index === questionIdx); if (!question) { autoSolveIndex++; await processAutoSolve(); return; } updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 正在处理: ${question.title.substring(0, 20)}...`, 'success'); // 保存当前进度 GM_setValue('rain_autosolve_queue', autoSolveQueue); GM_setValue('rain_autosolve_index', autoSolveIndex); GM_setValue('rain_autosolving', true); // 进入题目 if (question.element) { batchMode = true; currentQuestionIndex = questionList.indexOf(question); GM_setValue('rain_current_index', currentQuestionIndex); GM_setValue('rain_batch_mode', true); // 触发点击 question.element.click(); updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 已进入题目,等待加载...`, 'success'); // 等待页面加载,然后自动答题 setTimeout(() => { autoAnswerAndSubmit(); }, 2500); } else { autoSolveIndex++; await processAutoSolve(); } } // 自动获取答案并提交 async function autoAnswerAndSubmit() { if (!autoSolving) return; updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 正在获取答案...`, 'success'); try { // 1. 提取页面内容 const { count, type } = getQuestionCount(); const pageText = extractPageText(); if (count === 0) { updateStatus('未能提取题目内容,跳过', 'error'); setTimeout(() => nextAutoSolve(), 2000); return; } // 2. 构建prompt并调用AI const prompt = buildPrompt(pageText, count, type); const aiResponse = await callAI(prompt); const answers = parseAnswers(aiResponse, count); if (!answers || answers.length === 0) { updateStatus('未能获取答案,跳过', 'error'); setTimeout(() => nextAutoSolve(), 2000); return; } // 3. 保存答案并填入 updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 正在填入答案...`, 'success'); window.rainAnswers = answers; fillAnswers(); // 4. 等待一下再提交 setTimeout(() => { if (!autoSolving) return; updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 正在提交...`, 'success'); // 使用 submitAnswers 函数提交,它有更完善的查找逻辑 submitAnswers(); // 等待提交完成,然后处理可能的确认弹窗 setTimeout(() => { // 查找并点击确认/确定按钮(提交后的提示弹窗) let confirmBtn = document.querySelector('.el-message-box__btns .el-button--primary'); if (!confirmBtn) { confirmBtn = document.querySelector('.el-message-box__btns button'); } if (!confirmBtn) { // 查找包含"确定"或"确认"文字的按钮 const allBtns = document.querySelectorAll('button'); for (const btn of allBtns) { const text = btn.innerText?.trim() || ''; if (text === '确定' || text === '确认') { confirmBtn = btn; break; } } } if (confirmBtn) { console.log('[DEBUG] 找到确认按钮,点击:', confirmBtn.innerText); confirmBtn.click(); } // 再等待一下后进入下一题 setTimeout(() => { nextAutoSolve(); }, 1500); }, 2500); }, 2000); } catch (err) { console.error('自动答题出错:', err); updateStatus('自动答题出错: ' + err.message, 'error'); setTimeout(() => nextAutoSolve(), 2000); } } // 记录已完成的题目标题(避免重复做) const completedTitles = new Set(); // 下一题 function nextAutoSolve() { autoSolveIndex++; if (autoSolveIndex >= autoSolveQueue.length) { finishAutoSolve(); return; } // 返回列表页 - 尝试多种选择器 let backBtn = document.querySelector('.backBtn, .back-btn, .el-button--default, [class*="back"]'); // 如果没找到,尝试通过其他方式 if (!backBtn) { // 查找包含返回图标的按钮(通常是第一个按钮) const iconBtns = document.querySelectorAll('button i.el-icon-back, button i[class*="back"]'); if (iconBtns.length > 0) { backBtn = iconBtns[0].closest('button'); console.log('[DEBUG] 通过返回图标找到按钮'); } } // 还是没找到,遍历所有按钮 if (!backBtn) { const allBtns = Array.from(document.querySelectorAll('button')); console.log('[DEBUG] 查找返回按钮,总按钮数:', allBtns.length); // 打印前5个按钮的信息用于调试 allBtns.slice(0, 5).forEach((btn, i) => { console.log(`[DEBUG] 按钮${i}:`, btn.innerText?.trim(), 'class:', btn.className); }); for (const btn of allBtns) { const text = (btn.innerText?.trim() || '').toLowerCase(); const className = (btn.className || '').toLowerCase(); // 匹配包含"返回"文字的按钮 if (text.includes('返回')) { backBtn = btn; console.log('[DEBUG] 通过文字找到返回按钮:', text); break; } // 匹配class包含back的按钮 if (className.includes('back')) { backBtn = btn; console.log('[DEBUG] 通过class找到返回按钮:', className); break; } } } if (backBtn) { updateStatus(`[${autoSolveIndex + 1}/${autoSolveQueue.length}] 返回列表页...`, 'success'); console.log('[DEBUG] 找到返回按钮:', backBtn.innerText?.trim()); // 触发点击 backBtn.click(); // 等待2秒后才开始轮询,给页面切换时间 setTimeout(() => { // 等待页面返回列表,轮询检查表格是否出现 let attempts = 0; const maxAttempts = 30; // 最多等待15秒 const waitForList = setInterval(() => { attempts++; const rows = document.querySelectorAll('.el-table__row'); console.log(`[DEBUG] 等待列表页... 尝试 ${attempts}, 表格行数: ${rows.length}`); if (rows.length > 0) { // 表格已出现,停止轮询 clearInterval(waitForList); console.log('[DEBUG] 列表页已加载'); // 读取列表 readQuestionList(); // 等待一下确保列表读取完成 setTimeout(() => { // 找到第一个未完成的题目(按钮文字是"做题"且不在completedTitles中) let foundIndex = -1; for (let i = 0; i < questionList.length; i++) { const q = questionList[i]; // 通过按钮文字判断:"做题"表示未完成,"查看"表示已完成 // 同时排除已经记录为完成的题目 if (q.btnText && q.btnText.includes('做题') && !completedTitles.has(q.title)) { foundIndex = i; break; } } if (foundIndex !== -1 && questionList[foundIndex]) { console.log('[DEBUG] 找到下一题:', questionList[foundIndex].title, '索引:', foundIndex); enterQuestion(foundIndex); } else { // 如果没有找到,可能是列表还没刷新,尝试找下一个在队列中的题目 console.log('[DEBUG] 未找到新的未做题,尝试从队列中找...'); const nextIdx = autoSolveQueue[autoSolveIndex]; if (nextIdx !== undefined && questionList[nextIdx] && !completedTitles.has(questionList[nextIdx].title)) { console.log('[DEBUG] 从队列中找到:', questionList[nextIdx].title); enterQuestion(nextIdx); } else { updateStatus('未找到未完成的题目', 'error'); finishAutoSolve(); } } }, 2000); // 增加等待时间到2秒 } else if (attempts >= maxAttempts) { // 超时 clearInterval(waitForList); updateStatus('返回列表页超时,请手动返回', 'error'); } }, 500); // 每500ms检查一次 }, 2000); // 点击返回后等待2秒再开始轮询 } else { updateStatus('未找到返回按钮,请手动返回列表页', 'error'); console.log('[DEBUG] 未找到返回按钮,当前按钮:', document.querySelectorAll('button').length); } } // 完成自动刷题 function finishAutoSolve() { autoSolving = false; GM_setValue('rain_autosolving', false); // 恢复UI document.getElementById('rain-start-auto-btn').style.display = 'block'; document.getElementById('rain-stop-auto-btn').style.display = 'none'; document.getElementById('rain-auto-solve-list').style.opacity = '1'; document.getElementById('rain-select-all').disabled = false; document.querySelectorAll('.rain-solve-checkbox').forEach(cb => { cb.disabled = false; cb.checked = false; }); updateSelectedCount(); updateStatus('自动刷题完成!', 'success'); // 刷新题目列表 setTimeout(() => readQuestionList(), 1000); } // 停止自动刷题 function stopAutoSolve() { autoSolving = false; GM_setValue('rain_autosolving', false); document.getElementById('rain-start-auto-btn').style.display = 'block'; document.getElementById('rain-stop-auto-btn').style.display = 'none'; document.getElementById('rain-auto-solve-list').style.opacity = '1'; document.getElementById('rain-select-all').disabled = false; document.querySelectorAll('.rain-solve-checkbox').forEach(cb => cb.disabled = false); updateStatus('已停止自动刷题', 'error'); } function showAnswers(ans) { const section = document.getElementById('rain-answers-section'); const container = document.getElementById('rain-answers'); if (section && container) { section.style.display = 'block'; container.innerHTML = ans.map((a, i) => `
${i+1} ${a}
`).join(''); } } // ==================== 核心功能 ==================== function extractPageText() { let result = ''; const leftPanel = document.querySelector('.question_left'); if (leftPanel) { const titleEl = leftPanel.querySelector('.el-tabs__item.is-active') || document.querySelector('.font-size-20') || document.querySelector('h1, h2'); if (titleEl) { result += '【文章标题】\n' + titleEl.innerText.trim() + '\n\n'; } const directions = leftPanel.querySelector('.directions'); if (directions) { let articleText = ''; let foundDirections = false; const walker = document.createTreeWalker(leftPanel, NodeFilter.SHOW_TEXT, null, false); let node; while (node = walker.nextNode()) { const text = node.textContent.trim(); if (foundDirections && text.length > 0) { articleText += text + '\n'; } if (node.parentElement?.closest('.directions')) { foundDirections = true; } } if (articleText.length > 100) { result += '【文章内容】\n' + articleText.substring(0, 3000) + '\n\n'; } } } if (result.length < 200) { const articleTitle = document.querySelector('.font-size-20, .article-title'); const articleContent = document.querySelector('.line-height-28, .article-content'); if (articleTitle) { result += '【文章标题】\n' + articleTitle.innerText.trim() + '\n\n'; } if (articleContent) { result += '【文章内容】\n' + articleContent.innerText.trim() + '\n\n'; } } const questionRight = document.querySelector('.question_right'); if (questionRight) { result += '【题目】\n'; const questions = questionRight.querySelectorAll('.first_question'); questions.forEach((q, idx) => { const qTextEl = q.querySelector('.question'); const qText = qTextEl?.innerText?.trim(); if (qText) { const cleanQText = qText.replace(/^\d+[\.\.\、]\s*/, ''); result += (idx + 1) + '. ' + cleanQText + '\n'; const cards = q.querySelectorAll('.el-card__body'); cards.forEach(card => { const optionText = card.innerText?.trim(); if (optionText && optionText.match(/^[ABCD][\.\.\、]/)) { result += optionText + '\n'; } }); result += '\n'; } }); } if (result.length < 200) { const selectors = ['.el-main', '.main-content', '.content', 'main', '.question_box']; for (const sel of selectors) { const el = document.querySelector(sel); if (el && el.innerText.length > 500) { result = cleanText(el.innerText); break; } } } if (result.length < 200) { result = cleanText(document.body.innerText); } console.log('[DEBUG] 提取内容长度:', result.length); return result; } function cleanText(text) { const panel = document.getElementById('rain-xichuan-container'); if (panel) { text = text.replace(panel.innerText, ''); } return text.trim(); } function getQuestionCount() { // 1. 检查阅读题新结构(.question_right) const questionRight = document.querySelector('.question_right'); if (questionRight) { let questions = questionRight.querySelectorAll('.first_question'); if (questions.length === 0) { questions = questionRight.querySelectorAll('.question'); } if (questions.length > 0) { const hasArticle = document.querySelector('.line-height-28') !== null; return { count: questions.length, type: hasArticle ? 'reading' : 'choice' }; } } // 2. 检查输入框(填空题/单词变形)- 优先检测 const inputs = document.querySelectorAll('input.answer, input[name="answer[]"], input[type="text"]'); const visibleInputs = Array.from(inputs).filter(inp => { const rect = inp.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }); if (visibleInputs.length > 0) { return { count: visibleInputs.length, type: 'fill' }; } // 3. 检查传统选择题 try { const text = document.body.innerText; const matches = text.match(/\d+[\.\、\.]\s*[^\n]+[\n\s]*A[\.\、\.]/g); if (matches && Array.isArray(matches) && matches.length > 0) { const hasLongArticle = document.querySelectorAll('p').length > 5; if (hasLongArticle && matches.length >= 3) { const hasBlanks = text.match(/_{3,}|\(\s*\)/); return { count: matches.length, type: hasBlanks ? 'cloze' : 'reading' }; } return { count: matches.length, type: 'choice' }; } } catch (e) { console.log('[DEBUG] 正则匹配出错:', e); } return { count: 0, type: 'unknown' }; } function buildPrompt(pageText, count, type) { const typeNames = { reading: '阅读理解', cloze: '完形填空', choice: '选择题', fill: '填空题' }; const typeName = typeNames[type] || '题目'; let taskDesc = ''; if (type === 'reading' || type === 'cloze') { taskDesc = '1. 仔细阅读文章\n2. 阅读每道题目和A/B/C/D四个选项\n3. 根据文章内容选择答案'; } else if (type === 'choice') { taskDesc = '1. 仔细阅读每道题目和选项\n2. 选择最合适的答案'; } else { taskDesc = '1. 识别每个填空的提示类型(首字母/中文/变形)\n2. 根据上下文填写答案'; } const outputFormat = (type === 'reading' || type === 'cloze' || type === 'choice') ? '只输出选项字母,格式:1. A 或 A, B, C...' : '只输出答案单词,格式:1. answer 或 answer1, answer2...'; return `你是一位专业的英语考试专家,精通${typeName}。 请完成以下${count}道${typeName}。 【题目内容】 ${pageText.substring(0, 5000)} 【任务】 ${taskDesc} 【输出格式】 ${outputFormat} 不要任何解释,只输出答案:`; } async function callAI(prompt) { if (!apiKey) throw new Error('请先配置API Key'); const config = MODEL_CONFIGS[currentModel]; let url = config.url; let model = config.model; if (currentModel === 'custom') { url = customUrl; model = customModel; if (!url || !model) throw new Error('请配置自定义 API URL 和模型名'); } // 特殊处理 Claude API if (currentModel === 'claude') { return callClaudeAPI(prompt, url, model); } // 特殊处理通义千问 if (currentModel === 'qwen') { return callQwenAPI(prompt, url, model); } return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, data: JSON.stringify({ model: model, messages: [ { role: 'system', content: '你是一位英语专家,只输出答案,不要解释。' }, { role: 'user', content: prompt } ], temperature: 0.3, max_tokens: 2000 }), onload: (res) => { try { const data = JSON.parse(res.responseText); if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) { resolve(data.choices[0].message.content); } else if (data.error) { reject(new Error('API错误: ' + (data.error.message || JSON.stringify(data.error)))); } else { reject(new Error('API返回格式错误: ' + JSON.stringify(data).substring(0, 200))); } } catch (e) { reject(new Error('解析失败: ' + e.message)); } }, onerror: (err) => reject(new Error('请求失败: ' + (err.message || '未知错误'))) }); }); } async function callClaudeAPI(prompt, url, model) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, data: JSON.stringify({ model: model, max_tokens: 2000, messages: [ { role: 'user', content: '你是一位英语专家,只输出答案,不要解释。\n\n' + prompt } ] }), onload: (res) => { try { const data = JSON.parse(res.responseText); if (data.content && data.content[0] && data.content[0].text) { resolve(data.content[0].text); } else if (data.error) { reject(new Error('API错误: ' + (data.error.message || JSON.stringify(data.error)))); } else { reject(new Error('API返回格式错误')); } } catch (e) { reject(new Error('解析失败: ' + e.message)); } }, onerror: (err) => reject(new Error('请求失败: ' + (err.message || '未知错误'))) }); }); } async function callQwenAPI(prompt, url, model) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, data: JSON.stringify({ model: model, input: { messages: [ { role: 'system', content: '你是一位英语专家,只输出答案,不要解释。' }, { role: 'user', content: prompt } ] }, parameters: { temperature: 0.3, max_tokens: 2000 } }), onload: (res) => { try { const data = JSON.parse(res.responseText); if (data.output && data.output.text) { resolve(data.output.text); } else if (data.error) { reject(new Error('API错误: ' + (data.error.message || JSON.stringify(data.error)))); } else { reject(new Error('API返回格式错误')); } } catch (e) { reject(new Error('解析失败: ' + e.message)); } }, onerror: (err) => reject(new Error('请求失败: ' + (err.message || '未知错误'))) }); }); } function parseAnswers(response, count) { const numbered = response.match(/^\s*\d+[\.\)]\s*(.+)$/gm); if (numbered) { return numbered.map(line => line.match(/^\s*\d+[\.\)]\s*(.+)$/)[1].trim()).slice(0, count); } const comma = response.split(/[,,]/).map(s => s.trim()).filter(s => s); if (comma.length >= count) return comma.slice(0, count); return response.split('\n').map(s => s.trim()).filter(s => s && !/^\d+$/.test(s)).slice(0, count); } async function startAnswering() { if (isRunning) return; isRunning = true; answers = []; try { updateStatus('提取页面内容...'); const { count, type } = getQuestionCount(); const pageText = extractPageText(); console.log('[DEBUG] 提取内容长度:', pageText.length); console.log('[DEBUG] 内容前1000字:', pageText.substring(0, 1000)); if (count === 0) { updateStatus('未找到题目', 'error'); return; } updateStatus(`找到${count}道题,调用${MODEL_CONFIGS[currentModel].name}...`); const prompt = buildPrompt(pageText, count, type); const aiResponse = await callAI(prompt); answers = parseAnswers(aiResponse, count); updateStatus(`获取到${answers.length}个答案`, 'success'); showAnswers(answers); // 根据题型设置按钮文字 const fillBtn = document.getElementById('rain-fill-btn'); const submitBtn = document.getElementById('rain-submit-btn'); if (type === 'fill') { fillBtn.innerHTML = '✅ 填入答案'; submitBtn.innerHTML = '📝 提交答案'; } else { fillBtn.innerHTML = '✅ 应用答案'; submitBtn.innerHTML = '📝 提交答案'; } fillBtn.style.display = 'flex'; submitBtn.style.display = 'none'; // 默认隐藏提交按钮 } catch (err) { updateStatus('错误: ' + err.message, 'error'); } finally { isRunning = false; } } // 模拟真实点击 - 通过 Vue 实例触发选择 function simulateRealClick(element) { if (!element) return false; try { // 先移除同组其他选项的 right 类(单选) const parent = element.closest('.first_question'); if (parent) { parent.querySelectorAll('.el-card').forEach(card => { card.classList.remove('right'); }); } // 添加 right 类模拟选中(视觉反馈) element.classList.add('right'); // 方法1: 尝试通过 Vue 实例触发点击 const vueInstance = element.__vue__; if (vueInstance && vueInstance.$emit) { vueInstance.$emit('click'); vueInstance.$emit('mousedown'); vueInstance.$emit('mouseup'); } // 方法2: 触发内部元素的点击事件 const innerDiv = element.querySelector('.el-card__body > div'); if (innerDiv) { // 触发原生点击事件 innerDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); innerDiv.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); innerDiv.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); // 尝试触发 Vue 的点击处理 if (innerDiv.__vue__) { innerDiv.__vue__.$emit('click'); } } // 方法3: 触发 el-card 本身的点击 element.click(); // 方法4: 触发 el-card__body 的点击 const cardBody = element.querySelector('.el-card__body'); if (cardBody) { cardBody.click(); cardBody.dispatchEvent(new MouseEvent('click', { bubbles: true })); } return true; } catch (e) { console.log('[DEBUG] 点击失败:', e); return false; } } // 提交答案 function submitAnswers() { let submitBtn = null; // 1. 优先在题目区域查找(选择题在 .question_right,填空题可能在 .question_left 或其他地方) const questionContainers = [ '.question_right', '.question_left', '.question_box', '.el-main', '.main-content' ]; for (const container of questionContainers) { const parent = document.querySelector(container); if (parent) { // 在容器内查找按钮 const buttons = parent.querySelectorAll('button, .el-button, input[type="submit"]'); for (const btn of buttons) { const text = (btn.innerText || btn.textContent || '').trim(); if (text.includes('提交') || text.includes('确认') || text.includes('完成') || btn.type === 'submit' || btn.classList.contains('el-button--primary')) { submitBtn = btn; console.log(`[DEBUG] 在 ${container} 中找到提交按钮:`, text); break; } } if (submitBtn) break; } } // 2. 如果没找到,全局查找 if (!submitBtn) { const allButtons = document.querySelectorAll('button, .el-button, input[type="submit"]'); for (const btn of allButtons) { const text = (btn.innerText || btn.textContent || '').trim(); if (text.includes('提交') || text.includes('确认') || text.includes('完成')) { submitBtn = btn; console.log('[DEBUG] 全局找到提交按钮:', text); break; } } } // 3. 还是没找到,尝试通用的 el-button--primary if (!submitBtn) { submitBtn = document.querySelector('.el-button--primary'); if (submitBtn) console.log('[DEBUG] 使用 el-button--primary'); } if (submitBtn) { console.log('[DEBUG] 点击提交按钮:', submitBtn); submitBtn.click(); updateStatus('已提交答案', 'success'); // 标记当前题目完成 setTimeout(() => { markCurrentCompleted(); }, 1000); } else { updateStatus('未找到提交按钮,请手动提交', 'error'); console.log('[DEBUG] 未找到提交按钮,所有按钮:', Array.from(document.querySelectorAll('button')).map(b => b.innerText?.trim() || b.className)); } } function fillAnswers() { // 使用传入的答案或全局保存的答案 const ansToFill = window.rainAnswers || answers; const inputs = document.querySelectorAll('input.answer, input[name="answer[]"], input[type="text"]'); const visibleInputs = Array.from(inputs).filter(inp => { const rect = inp.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }); if (visibleInputs.length > 0) { // 填空题 - 只填入答案,不自动提交 let filled = 0; visibleInputs.forEach((input, i) => { if (ansToFill[i]) { input.value = ansToFill[i]; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); filled++; } }); updateStatus(`已填入${filled}个答案,请检查后再提交`, 'success'); // 显示提交按钮 const submitBtn = document.getElementById('rain-submit-btn'); if (submitBtn) submitBtn.style.display = 'flex'; } else { // 选择题 - 尝试自动点击 let filled = 0; const questions = document.querySelectorAll('.first_question'); // 禁用页面变化检测,防止选中后被重置 window._rainDisableReset = true; ansToFill.forEach((ans, i) => { if (!ans) return; const letterMatch = ans.toString().toUpperCase().match(/[ABCD]/); if (!letterMatch || !letterMatch[0]) return; if (!questions[i]) return; const cards = questions[i].querySelectorAll('.el-card'); const letter = letterMatch[0]; const idx = letter.charCodeAt(0) - 'A'.charCodeAt(0); if (cards[idx]) { // 尝试多种方式点击 simulateRealClick(cards[idx]); filled++; } }); if (filled > 0) { updateStatus(`已自动选择${filled}个答案,请点击提交按钮`, 'success'); // 显示提交按钮,让用户手动提交 const submitBtn = document.getElementById('rain-submit-btn'); if (submitBtn) submitBtn.style.display = 'flex'; } else { updateStatus('未能选择任何答案', 'error'); } // 5秒后恢复页面变化检测 setTimeout(() => { window._rainDisableReset = false; }, 5000); } } // 检测页面变化并刷新 function setupPageChangeDetector() { let lastUrl = location.href; let lastQuestionCount = 0; // 检测URL变化 const observer = new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; console.log('[小rain的习传助手] 页面切换,重置状态...'); resetPanelState(); // 如果在题目详情页,尝试恢复批量模式 setTimeout(() => { restoreBatchMode(); }, 500); } }); observer.observe(document, { subtree: true, childList: true }); // 定期检测题目数量变化(用于检测题目切换) setInterval(() => { const { count } = getQuestionCount(); if (count > 0 && count !== lastQuestionCount) { console.log('[小rain的习传助手] 题目变化,重置状态...'); lastQuestionCount = count; resetPanelState(); // 恢复批量模式UI setTimeout(() => { restoreBatchMode(); }, 500); } }, 2000); } // 恢复批量模式 function restoreBatchMode() { if (!location.href.includes('specialtyDetail') && !location.href.includes('question')) { return; } const savedList = GM_getValue('rain_question_list', ''); const savedIndex = GM_getValue('rain_current_index', 0); const savedBatchMode = GM_getValue('rain_batch_mode', false); // 只有当用户明确开启了批量模式时才恢复UI // 如果没点过"读取题目列表",savedBatchMode为false,不显示批量UI if (!savedBatchMode || !savedList) { console.log('[DEBUG] 非批量模式,保持简洁界面'); // 确保批量区域隐藏 const batchSection = document.getElementById('rain-batch-section'); if (batchSection) batchSection.style.display = 'none'; // 只显示开始答题按钮 const readBtn = document.getElementById('rain-read-list-btn'); const startBtn = document.getElementById('rain-start-btn'); const semiAutoBtn = document.getElementById('rain-semi-auto-btn'); const autoSolveBtn = document.getElementById('rain-auto-solve-btn'); if (readBtn) readBtn.style.display = 'none'; if (startBtn) startBtn.style.display = 'flex'; if (semiAutoBtn) semiAutoBtn.style.display = 'none'; if (autoSolveBtn) autoSolveBtn.style.display = 'none'; return; } console.log('[DEBUG] 尝试恢复批量模式...'); // 重试几次,确保UI已渲染 let attempts = 0; const maxAttempts = 10; const tryRestore = () => { attempts++; try { questionList = JSON.parse(savedList); batchMode = true; currentQuestionIndex = parseInt(savedIndex) || 0; const batchSection = document.getElementById('rain-batch-section'); const readBtn = document.getElementById('rain-read-list-btn'); const startBtn = document.getElementById('rain-start-btn'); console.log('[DEBUG] 恢复尝试', attempts, '元素:', !!batchSection, !!readBtn, !!startBtn); if (batchSection && readBtn && startBtn) { batchSection.style.display = 'block'; readBtn.style.display = 'none'; startBtn.style.display = 'flex'; const autoSolveBtn = document.getElementById('rain-auto-solve-btn'); if (autoSolveBtn) autoSolveBtn.style.display = 'flex'; updateBatchUI(); console.log('[DEBUG] 恢复批量模式成功,当前题目:', currentQuestionIndex + 1); return true; } } catch (e) { console.log('[DEBUG] 恢复失败:', e); } if (attempts < maxAttempts) { setTimeout(tryRestore, 300); } return false; }; tryRestore(); } // 重置面板状态 function resetPanelState(fullReset = true) { // 如果禁用重置标志被设置,则跳过 if (window._rainDisableReset) { console.log('[小rain的习传助手] 重置被禁用,跳过'); return; } answers = []; isRunning = false; // 重置UI document.getElementById('rain-answers-section').style.display = 'none'; document.getElementById('rain-fill-btn').style.display = 'none'; document.getElementById('rain-submit-btn').style.display = 'none'; updateStatus('就绪 - 请开始答题'); // 清除之前的绿色标记 document.querySelectorAll('.rain-suggested').forEach(el => { el.classList.remove('rain-suggested'); }); // 保存题目列表到存储 if (batchMode && questionList.length > 0) { const saveData = questionList.map(q => ({ index: q.index, title: q.title, btnText: q.btnText, isCompleted: q.isCompleted, completed: q.completed })); GM_setValue('rain_question_list', JSON.stringify(saveData)); GM_setValue('rain_current_index', currentQuestionIndex); } } // ==================== 批量答题功能 ==================== // 读取题目列表 function readQuestionList() { // 查找表格行 const rows = document.querySelectorAll('.el-table__row'); if (rows.length === 0) { updateStatus('未找到题目列表,请确保在题目列表页面', 'error'); return; } console.log('[DEBUG] 找到表格行数:', rows.length); questionList = []; rows.forEach((row, index) => { const cells = row.querySelectorAll('td'); // 只处理有操作按钮的行(4个单元格的行) if (cells.length !== 4) return; // 4个单元格格式:标题、数量、分类、操作 const title = cells[0]?.innerText?.trim(); const btnCell = cells[3]; const clickTarget = btnCell?.querySelector('button'); if (title && clickTarget) { const btnText = clickTarget.innerText?.trim(); const isCompleted = btnText?.includes('查看'); questionList.push({ index: index, title: title, btnText: btnText, element: clickTarget, isCompleted: isCompleted, completed: false }); } }); if (questionList.length === 0) { updateStatus('未能提取题目信息', 'error'); console.log('[DEBUG] 行数:', rows.length); console.log('[DEBUG] 第一行单元格数:', rows[0]?.querySelectorAll('td').length); console.log('[DEBUG] 第一行内容:', rows[0]?.innerHTML?.substring(0, 500)); return; } console.log('[DEBUG] 成功提取题目数:', questionList.length); console.log('[DEBUG] 第一题:', questionList[0]); // 显示批量答题区域 batchMode = true; currentQuestionIndex = 0; updateBatchUI(); // 标记为批量模式,保存到存储 GM_setValue('rain_batch_mode', true); document.getElementById('rain-batch-section').style.display = 'block'; document.getElementById('rain-read-list-btn').style.display = 'none'; document.getElementById('rain-start-btn').style.display = 'flex'; document.getElementById('rain-auto-solve-btn').style.display = 'flex'; // 隐藏模式提示 const modeHint = document.getElementById('rain-mode-hint'); if (modeHint) modeHint.style.display = 'none'; // 过滤掉已完成的题目(只保留"做题"按钮的) const todoList = questionList.filter(q => q.btnText?.includes('做题')); const doneList = questionList.filter(q => q.btnText?.includes('查看')); console.log('[DEBUG] 未做题:', todoList.length, '已做完:', doneList.length); if (todoList.length === 0) { updateStatus(`已读取 ${questionList.length} 道题目,全部已完成!`, 'success'); } else { updateStatus(`已读取 ${questionList.length} 道题目,${todoList.length}道未做,点击开始答题`, 'success'); } } // 更新批量答题UI function updateBatchUI() { const progressEl = document.getElementById('rain-batch-progress'); const listEl = document.getElementById('rain-batch-list'); if (progressEl) { const completed = questionList.filter(q => q.completed).length; progressEl.textContent = `进度: ${completed}/${questionList.length} (当前: ${currentQuestionIndex + 1})`; } if (listEl) { listEl.innerHTML = questionList.map((q, i) => `
${i + 1}. ${q.title.substring(0, 20)}${q.title.length > 20 ? '...' : ''} ${q.completed ? '✓' : (i === currentQuestionIndex ? '▶' : '○')}
`).join(''); // 点击题目跳转 listEl.querySelectorAll('.batch-item').forEach(item => { item.onclick = () => { const idx = parseInt(item.dataset.index); enterQuestion(idx); }; }); } // 更新按钮状态 const prevBtn = document.getElementById('rain-prev-btn'); const nextBtn = document.getElementById('rain-next-btn'); if (prevBtn) prevBtn.disabled = currentQuestionIndex <= 0; if (nextBtn) nextBtn.disabled = currentQuestionIndex >= questionList.length - 1; } // 从题目标题提取题型 function getQuestionType(title) { if (title.includes('完形填空')) return '完形填空'; if (title.includes('单词变形')) return '单词变形'; if (title.includes('阅读理解')) return '阅读理解'; if (title.includes('单项选择')) return '单项选择'; return ''; } // 进入指定题目 function enterQuestion(index) { if (index < 0 || index >= questionList.length) return; currentQuestionIndex = index; let question = questionList[index]; // 提前获取题型,确保在else分支中也能使用 const qType = getQuestionType(question.title); // 保存当前索引和题型 GM_setValue('rain_current_index', currentQuestionIndex); if (qType) { GM_setValue('rain_question_type', qType); console.log('[DEBUG] 保存题型:', qType); } console.log('[DEBUG] 尝试进入题目:', question.title, '索引:', index, '有element:', !!question.element); updateStatus(`正在进入第 ${index + 1} 题...`, 'success'); // 尝试获取或重新查找element let btn = question.element; // 如果没有element(可能在详情页返回后失效),尝试通过标题重新查找 if (!btn) { console.log('[DEBUG] element失效,尝试重新查找...'); console.log('[DEBUG] 目标标题:', question.title); const rows = document.querySelectorAll('.el-table__row'); console.log('[DEBUG] 表格行数:', rows.length); for (const row of rows) { const cells = row.querySelectorAll('td'); if (cells.length !== 4) continue; const title = cells[0]?.innerText?.trim(); const btnText = cells[3]?.querySelector('button')?.innerText?.trim(); // 优先匹配标题,如果标题匹配不上,就找第一个"做题"按钮 if (title === question.title) { btn = cells[3]?.querySelector('button'); if (btn) { question.element = btn; console.log('[DEBUG] 通过标题重新找到element:', title); break; } } } // 如果还没找到,找第一个未完成的题目(有"做题"按钮的) if (!btn) { console.log('[DEBUG] 标题匹配失败,尝试找第一个未完成的题目...'); for (const row of rows) { const cells = row.querySelectorAll('td'); if (cells.length !== 4) continue; const btnInCell = cells[3]?.querySelector('button'); const btnText = btnInCell?.innerText?.trim(); if (btnInCell && btnText && btnText.includes('做题')) { btn = btnInCell; console.log('[DEBUG] 找到第一个未完成的题目:', cells[0]?.innerText?.trim()); break; } } } } // 如果找到element,点击它 if (btn) { try { // 方式1: 触发 Vue 点击事件(最有效) if (btn.__vue__) { btn.__vue__.$emit('click'); console.log('[DEBUG] 已触发Vue click事件'); } // 方式2: 触发原生点击事件 setTimeout(() => { const event = new MouseEvent('click', { bubbles: true, cancelable: true }); btn.dispatchEvent(event); }, 50); // 自动刷题模式下,进入题目后自动开始答题 if (autoSolving) { console.log('[DEBUG] 自动刷题模式,进入题目后3秒自动答题'); setTimeout(() => { if (autoSolving) { autoAnswerAndSubmit(); } }, 3000); } } catch (e) { console.log('[DEBUG] 进入题目失败:', e); updateStatus('进入题目失败,请手动点击', 'error'); } } else { // 在详情页但没有element,提示用户手动返回 updateStatus('请在列表页点击题目', 'error'); } updateBatchUI(); resetPanelState(false); } // 上一题 function goToPrevQuestion() { let targetIndex = currentQuestionIndex - 1; // 向前查找未完成的题目(跳过"查看"按钮的已做题目) while (targetIndex >= 0) { const q = questionList[targetIndex]; if (q?.completed || q?.btnText?.includes('查看')) { targetIndex--; } else { break; } } if (targetIndex >= 0) { enterQuestion(targetIndex); } else { updateStatus('前面没有更多题目了', 'error'); } } // 下一题 function goToNextQuestion() { let targetIndex = currentQuestionIndex + 1; // 向后查找未完成的题目(跳过"查看"按钮的已做题目) while (targetIndex < questionList.length) { const q = questionList[targetIndex]; // 跳过:1) 标记为completed的 2) 按钮是"查看"的 if (q?.completed || q?.btnText?.includes('查看')) { targetIndex++; } else { break; } } if (targetIndex < questionList.length) { enterQuestion(targetIndex); } else { updateStatus('已经是最后一题了', 'error'); } } // 标记当前题目完成并进入下一题 function markCurrentCompleted() { if (batchMode && currentQuestionIndex < questionList.length) { const currentQuestion = questionList[currentQuestionIndex]; currentQuestion.completed = true; // 自动刷题模式下,记录已完成的题目标题 if (autoSolving && currentQuestion.title) { completedTitles.add(currentQuestion.title); console.log('[DEBUG] 记录已完成题目:', currentQuestion.title); } updateBatchUI(); // 自动刷题模式下,由 nextAutoSolve 处理进入下一题,这里不处理 if (autoSolving) { console.log('[DEBUG] 自动刷题模式,跳过 autoNext 逻辑'); return; } if (autoNext && currentQuestionIndex < questionList.length - 1) { updateStatus('本题已完成,2秒后自动进入下一题...', 'success'); setTimeout(() => { goToNextQuestion(); }, 2000); } } } function init() { console.log('[小rain的习传助手] v3.0 初始化...'); // 初始化时清除批量模式标记(只有点击读取列表后才开启) GM_setValue('rain_batch_mode', false); GM_setValue('rain_question_list', ''); createPanel(); setupPageChangeDetector(); // 检查是否在题目详情页,恢复批量模式 restoreBatchMode(); } // 检查是否在题目列表页 function isQuestionListPage() { const rows = document.querySelectorAll('.el-table__row'); return rows.length > 0; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();