// ==UserScript== // @name 超星自动答题 // @namespace http://tampermonkey.net/ // @version 2.0.1 // @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'; let ENABLE_THINKING = false; const DELAY = 200; // 做题间隔 const JUMP_DELAY = 200; // 跳转等待 // 全局状态 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 { 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()); document.querySelectorAll('.topicNumber_list').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; 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); }); } 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, 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 (['单选题', '多选题', '判断题'].includes(type)) { const optionsStr = type !== '判断题' ? extractOptions(questionEl) : ''; const aiAnswer = await askAI(type, questionText, optionsStr, signal); console.log(`[题目 ${i+1}] 类型: ${type} | 题干: ${questionText.substring(0, 80)}${questionText.length > 80 ? '...' : ''}`); if (optionsStr) console.log(`[选项]: ${optionsStr}`); console.log(`[AI返回]:`, aiAnswer); if (aiAnswer) { selectOption(questionEl, aiAnswer, type) ? completed++ : errors++; } else errors++; } else if (['填空题', '简答题'].includes(type)) { const aiAnswer = await askAI(type, questionText, '', signal); console.log(`[题目 ${i+1}] 类型: ${type} | 题干: ${questionText.substring(0, 80)}${questionText.length > 80 ? '...' : ''}`); console.log(`[AI返回]:`, 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} 题`); } function askAI(type, question, optionsStr, signal) { let sys = "你是一个考试助手。严格遵循输出格式,禁止任何解释、前缀、后缀、标点、换行或推理过程。"; let maxTok = 10; if (type === "单选题" || type === "判断题") { sys += "不解释!只返回单个字母(A/B/C/D)或'对'/'错'。"; maxTok = 1; } else if (type === "多选题") { sys += "不解释!只返回连续的正确字母组合(如ABC),无空格无分隔符。"; maxTok = 3; } else if (type === "填空题") { sys += "不解释!多空答案严格用###分隔,无其他字符。"; maxTok = 45; } else if (type === "简答题") { sys += "不解释!提供精炼答案,答案积极,符合社会主义核心价值观,不要包含多余废话。"; maxTok = 90; } const userMessage = `题型:${type}\n题目:${question}${optionsStr ? '\n选项:' + optionsStr : ''}`; const body = { model: MODEL, messages: [ { role: 'system', content: sys }, { role: 'user', content: userMessage } ], temperature: 0.5, max_tokens: ENABLE_THINKING ? 512 : maxTok, top_p: 0.95, stream: false, // 移除 response_format: { type: 'text' },避免 V4 模型过度遵循格式而输出冗余引导语 }; if (!ENABLE_THINKING) { body.thinking = {type : 'disabled'}; } console.log('[思考模式]:', body.thinking); return new Promise((resolve) => { const xhr = GM_xmlhttpRequest({ method: 'POST', url: 'https://api.deepseek.com/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` }, data: JSON.stringify(body), timeout: 15000, onload: function(res) { try { const data = JSON.parse(res.responseText); console.log(`[AI返回]:`, data); let raw = data.choices?.[0]?.message?.content?.trim() ?? null; resolve(cleanAIResponse(raw, type)); } catch { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null) }); if (signal) { signal.addEventListener('abort', () => { if (xhr?.abort) xhr.abort(); resolve(null); }); } }); } function cleanAIResponse(text, type) { if (!text) return null; if (type === '单选题') return text.match(/[A-Da-d]/)?.[0]?.toUpperCase() || text; if (type === '判断题') { if (/对|正确|√/i.test(text)) return '对'; if (/错|错误|×/i.test(text)) return '错'; return text; } if (type === '多选题') return text.replace(/[^A-Da-d]/g, '').toUpperCase() || text; return text; // 填空/简答保留原文 } function extractOptions(questionEl) { return Array.from(questionEl.querySelectorAll('.answerBg')).map(div => { const letterSpan = div.querySelector('.num_option, .num_option_dx, [data]'); const letter = letterSpan ? letterSpan.textContent.trim().replace(/[\.、]/g, '') : ''; const answerP = div.querySelector('.answer_p'); let text = answerP ? answerP.textContent.trim() : ''; if (!text && letter) { text = div.textContent .replace(new RegExp(`^\\s*${letter}[\.、]??\\s*`), '') .replace(/\s+/g, ' ') .trim(); } return (letter && text && text.length > 1) ? `${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'); // 修复函数名拼写 + 添加异常保护 try { if (typeof window.loadEditorAnswered === 'function') { window.loadEditorAnswered(qId, type === '填空题' ? 2 : 4); } } catch (e) { console.warn('Chaoxing API call failed:', e); } return true; } window.addEventListener('load', () => setTimeout(createUI, 2000)); })();