// ==UserScript== // @name 超星学习通自动答题 // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description 调用 DeepSeek API 自动作答,支持单选、多选、判断、填空、简答,带自动翻页、模型选择、智能暂停 // @author dmhnb6 // @match *://mooc1.chaoxing.com/mooc-ans/mooc2/work/dowork* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect api.deepseek.com // ==/UserScript== (function() { 'use strict'; // =========================== // 用户默认配置(可在面板中动态修改) // =========================== let API_KEY = GM_getValue('deepseek_api_key', ''); // 从存储读取,无则留空 let MODEL = 'deepseek-v4-flash'; // 模型:deepseek-v4-flash / deepseek-v4-pro let ENABLE_THINKING = false; // 思考模式(仅Pro有效) const DELAY = 100; // 每题完成后等待(毫秒) const JUMP_DELAY = 100; // 点击题号后等待题目出现的最大时间(毫秒) // =========================== // 全局状态 let isPaused = false; let isRunning = false; let solvingController = null; let currentSheetItems = []; let currentIdx = 0; // 即将处理的题目索引 // UI 元素 let progressBar, progressText, statusLabel; let startBtn, pauseBtn, resumeCurrentBtn; let modelSelect, thinkingCheckbox; const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // 获取可用模型列表 async function fetchModels(apiKey = API_KEY) { return new Promise((resolve) => { if (!apiKey || apiKey.trim() === '') { resolve(['deepseek-v4-flash', 'deepseek-v4-pro']); return; } GM_xmlhttpRequest({ method: 'GET', url: 'https://api.deepseek.com/models', headers: { 'Accept': 'application/json', 'Authorization': `Bearer ${apiKey}` }, timeout: 8000, onload: function(res) { try { const data = JSON.parse(res.responseText); const models = data.data.map(m => m.id).filter(id => id); resolve(models.length ? models : ['deepseek-v4-flash', 'deepseek-v4-pro']); } catch { resolve(['deepseek-v4-flash', 'deepseek-v4-pro']); } }, onerror: () => resolve(['deepseek-v4-flash', 'deepseek-v4-pro']), ontimeout: () => resolve(['deepseek-v4-flash', 'deepseek-v4-pro']) }); }); } function createUI() { const panel = document.createElement('div'); panel.style = ` position: fixed; top: 10px; left: 50%; transform: translateX(-50%); z-index: 10000; width: 500px; background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); font-family: sans-serif; border: 1px solid #ddd; font-size: 13px; `; panel.innerHTML = `
🔑 Key:
✍️ 答题助手 (DeepSeek) 待命中
模型:
准备就绪
`; document.body.appendChild(panel); progressBar = document.getElementById('auto-progress-bar'); progressText = document.getElementById('auto-progress-text'); statusLabel = document.getElementById('auto-status-label'); startBtn = document.getElementById('auto-start-btn'); pauseBtn = document.getElementById('auto-pause-btn'); resumeCurrentBtn = document.getElementById('auto-resume-current-btn'); modelSelect = document.getElementById('auto-model-select'); thinkingCheckbox = document.getElementById('auto-thinking-checkbox'); // 加载模型列表 fetchModels().then(models => { modelSelect.innerHTML = models.map(m => ``).join(''); }); modelSelect.addEventListener('change', () => { MODEL = modelSelect.value; }); thinkingCheckbox.checked = ENABLE_THINKING; thinkingCheckbox.addEventListener('change', () => { ENABLE_THINKING = thinkingCheckbox.checked; }); // ---- 核心按钮逻辑 ---- startBtn.onclick = () => { const isRestart = (startBtn.innerText === '重新开始'); if (isRestart) { currentIdx = 0; isPaused = false; statusLabel.innerText = "正在重新开始..."; } if (!isRunning || isRestart) { if (solvingController) solvingController.abort(); isRunning = true; startBtn.style.display = 'none'; pauseBtn.style.display = 'inline-block'; pauseBtn.innerText = '暂停'; pauseBtn.style.background = '#e74c3c'; resumeCurrentBtn.style.display = 'none'; startSolve(); } }; pauseBtn.onclick = () => { isPaused = !isPaused; if (isPaused) { pauseBtn.innerText = '恢复'; pauseBtn.style.background = '#f1c40f'; statusLabel.innerText = '已暂停'; startBtn.innerText = '重新开始'; startBtn.style.background = '#3498db'; startBtn.style.display = 'inline-block'; // 暂停时显示“从当前开始” resumeCurrentBtn.style.display = 'inline-block'; } else { // 恢复:回溯到前一道题(最小为0) currentIdx = Math.max(0, currentIdx - 1); pauseBtn.innerText = '暂停'; pauseBtn.style.background = '#e74c3c'; statusLabel.innerText = '运行中'; startBtn.style.display = 'none'; resumeCurrentBtn.style.display = 'none'; } }; resumeCurrentBtn.onclick = () => { if (solvingController) solvingController.abort(); isPaused = false; const currentLi = getCurrentLi(); if (currentLi) { const items = document.querySelectorAll('.topicNumber_list li'); currentIdx = Array.from(items).indexOf(currentLi); if (currentIdx < 0) currentIdx = 0; } else { currentIdx = 0; } isRunning = true; startBtn.style.display = 'none'; pauseBtn.style.display = 'inline-block'; pauseBtn.innerText = '暂停'; pauseBtn.style.background = '#e74c3c'; resumeCurrentBtn.style.display = 'none'; statusLabel.innerText = `从第 ${currentIdx+1} 题开始...`; startSolve(); }; // 监听答题卡变化,动态更新“从当前开始”按钮可见性 updateResumeCurrentVisibility(); const observer = new MutationObserver(() => updateResumeCurrentVisibility()); const topicLists = document.querySelectorAll('.topicNumber_list'); topicLists.forEach(list => observer.observe(list, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] })); const keyInput = document.getElementById('auto-api-key-input'); const saveKeyBtn = document.getElementById('auto-save-key-btn'); const keyStatus = document.getElementById('auto-key-status'); // 初始化输入框的值 keyInput.value = API_KEY; // 保存 Key 按钮事件 saveKeyBtn.addEventListener('click', async () => { const newKey = keyInput.value.trim(); if (!newKey) { keyStatus.textContent = 'Key 不能为空'; keyStatus.style.color = 'red'; return; } // 持久化存储 GM_setValue('deepseek_api_key', newKey); API_KEY = newKey; // 更新全局变量 keyStatus.textContent = '已保存,正在刷新模型列表...'; keyStatus.style.color = '#27ae60'; // 重新获取模型列表并更新下拉框 const models = await fetchModels(newKey); modelSelect.innerHTML = models.map(m => ``).join(''); keyStatus.textContent = '✅ 已保存'; setTimeout(() => { if (keyStatus.textContent === '✅ 已保存') keyStatus.textContent = ''; }, 2000); }); } // 获取当前题目的
  • 元素(兼容 current active 或仅 current) function getCurrentLi() { return document.querySelector('.topicNumber_list li.current.active') || document.querySelector('.topicNumber_list li.current'); } // 根据运行状态和答题卡高亮控制“从当前开始”按钮显示 function updateResumeCurrentVisibility() { const currentLi = getCurrentLi(); // 仅在未运行时显示(运行时由各事件控制隐藏) if (!isRunning && !isPaused) { resumeCurrentBtn.style.display = currentLi ? 'inline-block' : 'none'; } // 其他情况(运行中/暂停中)由事件明确控制,不在此处修改 } function startSolve() { if (!API_KEY || API_KEY.trim() === '') { alert('请先输入并保存 DeepSeek API Key!'); return; } const sheetItems = document.querySelectorAll('.topicNumber_list li'); if (!sheetItems.length) { alert('未找到答题卡题目'); return; } currentSheetItems = Array.from(sheetItems); solvingController = new AbortController(); solveAllQuestions(solvingController.signal); } function waitForQuestionElement(qId, timeout = JUMP_DELAY) { const startTime = Date.now(); return new Promise((resolve) => { const check = () => { const el = document.querySelector(`.questionLi[data="${qId}"]`); if (el) resolve(el); else if (Date.now() - startTime > timeout) resolve(null); else requestAnimationFrame(check); }; check(); }); } async function solveAllQuestions(signal) { const total = currentSheetItems.length; let completed = 0; let errors = 0; for (let i = currentIdx; i < total; i++) { if (signal.aborted) return; while (isPaused && !signal.aborted) await sleep(300); if (signal.aborted) return; const item = currentSheetItems[i]; const qId = item.getAttribute('data'); if (!qId) { errors++; continue; } statusLabel.innerText = `跳转至第 ${i+1} 题...`; item.click(); const questionEl = await waitForQuestionElement(qId); if (signal.aborted) return; if (!questionEl) { errors++; continue; } const type = questionEl.getAttribute('typeName') || '单选题'; const percent = Math.floor(((i + 1) / total) * 100); progressBar.style.width = `${percent}%`; progressText.innerText = `${i + 1} / ${total} (${percent}%)`; statusLabel.innerText = `处理第 ${i+1} 题 (${type})`; const titleEl = questionEl.querySelector('h3.mark_name'); if (!titleEl) { errors++; continue; } const titleClone = titleEl.cloneNode(true); const typeSpan = titleClone.querySelector('.colorShallow'); if (typeSpan) typeSpan.remove(); const questionText = titleClone.textContent.replace(/\s+/g, ' ').trim(); try { if (type === '单选题' || type === '多选题' || type === '判断题') { const optionsStr = type !== '判断题' ? extractOptions(questionEl) : ''; const aiAnswer = await askAI(type, questionText, optionsStr, signal); // 🔍 调试输出 console.log(`[题目 ${i+1}] 类型: ${type}`); console.log(`[题目 ${i+1}] 题干: ${questionText.substring(0, 80)}${questionText.length > 80 ? '...' : ''}`); if (optionsStr) console.log(`[题目 ${i+1}] 选项: ${optionsStr}`); console.log(`[题目 ${i+1}] DeepSeek 返回:`, aiAnswer); if (aiAnswer) { selectOption(questionEl, aiAnswer, type) ? completed++ : errors++; } else errors++; } else if (type === '填空题' || type === '简答题') { const aiAnswer = await askAI(type, questionText, '', signal); // 🔍 调试输出 console.log(`[题目 ${i+1}] 类型: ${type}`); console.log(`[题目 ${i+1}] 题干: ${questionText.substring(0, 80)}${questionText.length > 80 ? '...' : ''}`); console.log(`[题目 ${i+1}] DeepSeek 返回:`, aiAnswer); if (aiAnswer) { fillTextAnswer(questionEl, aiAnswer, type) ? completed++ : errors++; } else errors++; } currentIdx = i + 1; await sleep(DELAY); } catch (e) { if (e.name === 'AbortError') return; errors++; currentIdx = i + 1; } } // 答题结束 isRunning = false; progressBar.style.width = '100%'; progressText.innerText = `完成 ${completed}/${total},失败 ${errors} 题`; statusLabel.innerText = "全部作答完成"; pauseBtn.style.display = 'none'; startBtn.style.display = 'inline-block'; startBtn.innerText = '重新开始'; startBtn.style.background = '#2ecc71'; // 完成后依据高亮题目决定“从当前开始”是否显示 updateResumeCurrentVisibility(); alert(`✅ 自动答题完成!\n成功:${completed} 题\n失败/跳过:${errors} 题\n(多次运行或使用高级模型可提高正确率)`); } // API 调用 function askAI(type, question, optionsStr, signal) { let sys = "你是一个助手。"; if (type === "单选题" || type === "判断题") sys = "只回答案字母或'正确'/'错误',不解释。"; else if (type === "多选题") sys = "只回答正确字母组合,如ABC,不解释。"; else if (type === "填空题") sys = "多空答案用###分隔,不解释。"; else if (type === "简答题") sys = "提供精炼答案内容。"; const userMessage = `题型: ${type}\n题目: ${question}${optionsStr ? '\n选项: ' + optionsStr : ''}`; const body = { model: MODEL, messages: [ { role: 'system', content: sys }, { role: 'user', content: userMessage } ], temperature: 0.1, max_tokens: 256, top_p: 1, frequency_penalty: 0, presence_penalty: 0, stream: false, response_format: { type: 'text' } }; if (ENABLE_THINKING && MODEL.includes('pro')) { body.thinking = { type: 'enabled' }; body.reasoning_effort = 'high'; } return new Promise((resolve) => { const xhr = GM_xmlhttpRequest({ method: 'POST', url: 'https://api.deepseek.com/chat/completions', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${API_KEY}` }, data: JSON.stringify(body), timeout: 20000, onload: function(res) { try { const data = JSON.parse(res.responseText); resolve(data.choices?.[0]?.message?.content?.trim() ?? null); } catch { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null) }); if (signal) { signal.addEventListener('abort', () => { if (xhr?.abort) xhr.abort(); resolve(null); }); } }); } function extractOptions(questionEl) { return Array.from(questionEl.querySelectorAll('.answerBg')).map(div => { const letterSpan = div.querySelector('.num_option, .num_option_dx'); const letter = letterSpan ? letterSpan.textContent.trim() : ''; const textDiv = div.querySelector('.answer_p p'); const text = textDiv ? textDiv.textContent.trim() : div.textContent.replace(letter, '').trim(); return letter ? `${letter}. ${text}` : ''; }).filter(Boolean).join(' | '); } function selectOption(questionEl, aiAnswer, type) { let selected = false; questionEl.querySelectorAll('.answerBg').forEach(div => { const letterSpan = div.querySelector('.num_option, .num_option_dx'); const letter = letterSpan ? letterSpan.textContent.trim().toUpperCase() : ''; const isChecked = div.getAttribute('aria-checked') === 'true'; if ((type === '单选题' || type === '多选题') && aiAnswer.includes(letter) && !isChecked) { div.click(); selected = true; } else if (type === '判断题') { const text = div.textContent.trim(); if ((aiAnswer.includes('正确') && text.includes('对')) || (aiAnswer.includes('错误') && text.includes('错'))) { if (!isChecked) { div.click(); selected = true; } } } }); return selected; } function fillTextAnswer(questionEl, aiAnswer, type) { if (!aiAnswer) return false; const textareas = questionEl.querySelectorAll('textarea[id^="answer"]'); if (!textareas.length) return false; const answers = type === '填空题' ? aiAnswer.split('###') : [aiAnswer]; textareas.forEach((ta, idx) => { const id = ta.id; if (typeof UE !== 'undefined' && UE.getEditor) { try { const editor = UE.getEditor(id); if (editor?.ready) editor.ready(() => editor.setContent(answers[idx] || answers[0])); } catch { ta.value = answers[idx] || answers[0]; } } else ta.value = answers[idx] || answers[0]; ta.dispatchEvent(new Event('input', { bubbles: true })); }); const qId = questionEl.getAttribute('data'); if (window.loadEditorAnswerd) window.loadEditorAnswerd(qId, type === '填空题' ? 2 : 4); return true; } window.addEventListener('load', () => setTimeout(createUI, 2000)); })();