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