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