NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name YNU-SPOC自动答题脚本
// @namespace http://tampermonkey.net/
// @version 1.1
// @updateURL https://raw.githubusercontent.com/HeLongaa/YNU-Tools/main/YNU-SPOC-AutoAnswer.user.js
// @downloadURL https://raw.githubusercontent.com/HeLongaa/YNU-Tools/main/YNU-SPOC-AutoAnswer.user.js
// @description SPOC课程单元测试自动答题助手,支持顺序答题,集成AI分析功能,无需缓存题库,适用于云南大学等使用SPOC平台的高校。
// @author HeLong
// @match https://www.icourse163.org/learn/*
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_addStyle
// @connect api.siliconflow.cn
// @license MIT
// @icon data:image/png;base64,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
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 配置AI接口
const AI_CONFIG = {
API_KEY: '',
API_URL: 'https://api.siliconflow.cn/v1/chat/completions',
MODEL: 'deepseek-ai/DeepSeek-V3',
MAX_RETRIES: 3,
RETRY_DELAY: 2000,
SELECT_DELAY: 2000,
OPTION_DELAY: 500
};
// 常量定义
const CONSTANTS = {
QUESTION_TYPES: {
SINGLE: 'single',
MULTIPLE: 'multiple',
JUDGE: 'judge',
UNKNOWN: 'unknown'
},
STATUS_TYPES: {
CORRECT: 'correct',
WRONG: 'wrong',
PROCESSING: 'processing'
},
SELECTORS: {
QUESTION: '.m-choiceQuestion',
QUESTION_TYPE: '.qaCate',
QUESTION_TEXT: '.j-richTxt',
OPTIONS: '.choices li',
OPTION_TEXT: '.optionCnt',
INPUT: 'input[type="radio"], input[type="checkbox"]',
CORRECT_ICON: '.u-icon-correct',
WRONG_ICON: '.u-icon-wrong'
},
ICONS: {
CORRECT: '✓',
WRONG: '✗',
PROCESSING: '⏳'
},
LETTER_A_CODE: 65
};
// 添加样式
GM_addStyle(`
.auto-answer-btn {
position: fixed;
top: 100px;
right: 20px;
z-index: 9999;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: all 0.3s ease;
}
.auto-answer-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.auto-answer-btn:active {
transform: translateY(0);
}
.auto-answer-btn.loading {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
cursor: not-allowed;
}
.auto-answer-btn.success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.auto-answer-btn.error {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.answer-status {
position: fixed;
top: 160px;
right: 20px;
z-index: 9999;
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
max-width: 300px;
max-height: 400px;
overflow-y: auto;
display: none;
font-family: Arial, sans-serif;
}
.status-header {
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #667eea;
color: #333;
}
.status-item {
margin: 8px 0;
padding: 8px;
border-radius: 5px;
font-size: 12px;
display: flex;
align-items: center;
}
.status-icon {
margin-right: 8px;
font-size: 14px;
}
.status-correct .status-icon {
color: #28a745;
}
.status-wrong .status-icon {
color: #dc3545;
}
.status-processing .status-icon {
color: #ffc107;
}
.status-text {
flex: 1;
}
.status-correct {
background-color: rgba(40, 167, 69, 0.1);
color: #155724;
border-left: 3px solid #28a745;
}
.status-wrong {
background-color: rgba(220, 53, 69, 0.1);
color: #721c24;
border-left: 3px solid #dc3545;
}
.status-processing {
background-color: rgba(255, 193, 7, 0.1);
color: #856404;
border-left: 3px solid #ffc107;
}
.current-question {
background-color: rgba(0, 123, 255, 0.1) !important;
border-left: 3px solid #007bff !important;
}
.progress-bar {
width: 100%;
height: 4px;
background-color: #e9ecef;
border-radius: 2px;
margin: 10px 0;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
transition: width 0.3s ease;
}
.stats-info {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #dee2e6;
font-size: 12px;
color: #6c757d;
}
.controls {
display: flex;
gap: 10px;
margin-top: 10px;
}
.control-btn {
flex: 1;
padding: 5px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
}
.control-btn-pause {
background-color: #ffc107;
color: #212529;
}
.control-btn-resume {
background-color: #28a745;
color: white;
}
.control-btn-stop {
background-color: #dc3545;
color: white;
}
.control-btn-skip {
background-color: #6c757d;
color: white;
}
`);
class AutoAnswer {
constructor() {
this.questions = [];
this.isProcessing = false;
this.isPaused = false;
this.currentQuestionIndex = 0;
this.init();
}
init() {
this.createUI();
this.bindEvents();
}
createUI() {
// 创建主按钮
this.btn = document.createElement('button');
this.btn.className = 'auto-answer-btn';
this.btn.textContent = '自动答题';
document.body.appendChild(this.btn);
// 创建状态显示区域
this.statusPanel = document.createElement('div');
this.statusPanel.className = 'answer-status';
this.statusPanel.innerHTML = `
<div class="status-header">顺序答题进度</div>
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<div class="status-list"></div>
<div class="controls">
<button class="control-btn control-btn-pause">暂停</button>
<button class="control-btn control-btn-resume" style="display:none">继续</button>
<button class="control-btn control-btn-skip">跳过当前</button>
<button class="control-btn control-btn-stop">停止</button>
</div>
<div class="stats-info">准备开始...</div>
`;
document.body.appendChild(this.statusPanel);
}
bindEvents() {
this.btn.addEventListener('click', () => this.startAutoAnswer());
// 控制按钮事件
this.statusPanel.querySelector('.control-btn-pause').addEventListener('click', () => this.pause());
this.statusPanel.querySelector('.control-btn-resume').addEventListener('click', () => this.resume());
this.statusPanel.querySelector('.control-btn-stop').addEventListener('click', () => this.stop());
this.statusPanel.querySelector('.control-btn-skip').addEventListener('click', () => this.skipCurrent());
}
async startAutoAnswer() {
if (this.isProcessing) return;
this.isProcessing = true;
this.isPaused = false;
this.currentQuestionIndex = 0;
this.btn.classList.add('loading');
this.btn.textContent = '正在分析题目...';
this.showStatusPanel();
try {
// 收集所有题目
await this.collectQuestions();
// 顺序处理每个题目
await this.processQuestionsSequentially();
if (!this.isPaused) {
this.showSuccess('所有题目处理完成!');
}
} catch (error) {
this.showError('答题失败: ' + error.message);
} finally {
if (!this.isPaused) {
this.isProcessing = false;
this.btn.classList.remove('loading');
this.btn.textContent = '自动答题';
}
}
}
showStatusPanel() {
this.statusPanel.style.display = 'block';
}
collectQuestions() {
return new Promise((resolve) => {
const questionElements = document.querySelectorAll(CONSTANTS.SELECTORS.QUESTION);
this.questions = Array.from(questionElements).map((questionEl, index) => ({
index: index + 1,
element: questionEl,
type: this.getQuestionType(questionEl),
text: this.getQuestionText(questionEl),
options: this.getQuestionOptions(questionEl),
hasDirectAnswer: this.hasDirectAnswer(questionEl),
processed: false,
answer: null,
source: null
}));
console.log(`收集到 ${this.questions.length} 道题目`);
this.updateProgress(0);
this.updateStats(`找到 ${this.questions.length} 道题目`);
resolve();
});
}
hasDirectAnswer(questionEl) {
return !!(questionEl.querySelector(CONSTANTS.SELECTORS.CORRECT_ICON) ||
questionEl.querySelector(CONSTANTS.SELECTORS.WRONG_ICON));
}
getQuestionType(questionEl) {
const cateEl = questionEl.querySelector(CONSTANTS.SELECTORS.QUESTION_TYPE);
if (!cateEl) return CONSTANTS.QUESTION_TYPES.UNKNOWN;
const text = cateEl.textContent.toLowerCase();
const typeMap = {
'单选': CONSTANTS.QUESTION_TYPES.SINGLE,
'多选': CONSTANTS.QUESTION_TYPES.MULTIPLE,
'判断': CONSTANTS.QUESTION_TYPES.JUDGE
};
for (const [key, value] of Object.entries(typeMap)) {
if (text.includes(key)) return value;
}
return CONSTANTS.QUESTION_TYPES.UNKNOWN;
}
getQuestionText(questionEl) {
const textEl = questionEl.querySelector(CONSTANTS.SELECTORS.QUESTION_TEXT);
return textEl ? textEl.textContent.trim().replace(/\s+/g, ' ') : '';
}
getQuestionOptions(questionEl) {
const optionEls = questionEl.querySelectorAll(CONSTANTS.SELECTORS.OPTIONS);
return Array.from(optionEls).map((optionEl, index) => {
const textEl = optionEl.querySelector(CONSTANTS.SELECTORS.OPTION_TEXT);
return {
index,
element: optionEl,
text: textEl ? textEl.textContent.trim() : '',
input: optionEl.querySelector(CONSTANTS.SELECTORS.INPUT),
isCorrect: this.isCorrectOption(optionEl)
};
});
}
isCorrectOption(optionEl) {
if (optionEl.querySelector(CONSTANTS.SELECTORS.CORRECT_ICON)) return true;
if (optionEl.querySelector(CONSTANTS.SELECTORS.WRONG_ICON)) return false;
return null;
}
async processQuestionsSequentially() {
const statusList = this.statusPanel.querySelector('.status-list');
statusList.innerHTML = '';
for (let i = 0; i < this.questions.length; i++) {
if (this.isPaused) {
await this.waitForResume();
if (!this.isProcessing) return;
}
this.currentQuestionIndex = i;
const question = this.questions[i];
// 更新进度
const progress = ((i) / this.questions.length) * 100;
this.updateProgress(progress);
// 创建状态项
const statusItem = this.createStatusItem(question.index, '等待处理...', 'processing');
try {
// 处理当前题目
await this.processSingleQuestion(question, statusItem);
// 等待2秒再进行下一题
this.updateStats(`第${question.index}题处理完成,等待${AI_CONFIG.SELECT_DELAY/1000}秒...`);
await this.delay(AI_CONFIG.SELECT_DELAY);
} catch (error) {
this.updateStatusItem(statusItem, `处理失败: ${error.message}`, 'wrong');
console.error(`第${question.index}题处理失败:`, error);
}
// 更新进度
const newProgress = ((i + 1) / this.questions.length) * 100;
this.updateProgress(newProgress);
this.updateStats(`已完成: ${i + 1}/${this.questions.length}`);
}
this.updateProgress(100);
this.updateStats('所有题目处理完成!');
}
async processSingleQuestion(question, statusItem) {
// 标记为当前正在处理的题目
statusItem.classList.add('current-question');
this.updateStatusItem(statusItem, '正在分析题目...', 'processing');
const answer = await this.getAnswerForQuestion(question);
question.answer = answer.answer;
question.source = answer.source;
question.processed = true;
this.updateStatusItem(statusItem, `答案: ${answer.answer} (${answer.source})`, 'processing');
this.updateStatusItem(statusItem, '正在选择答案...', 'processing');
const selectionResult = await this.selectAnswerWithDetails(question, answer.answer, statusItem);
if (selectionResult) {
this.updateStatusItem(statusItem, `已选择: ${answer.answer}`, 'correct');
} else {
this.updateStatusItem(statusItem, `选择失败`, 'wrong');
}
// 移除当前题目标记
statusItem.classList.remove('current-question');
return selectionResult;
}
async getAnswerForQuestion(question, retryCount = 0) {
// 优先使用直接答案
if (question.hasDirectAnswer) {
const directAnswer = this.getDirectAnswer(question);
if (directAnswer) {
return {
answer: directAnswer,
source: '直接答案',
confidence: 1.0
};
}
}
// 使用AI获取答案
try {
const answer = await this.queryAI(question);
return {
answer: answer,
source: 'AI分析',
confidence: 0.9
};
} catch (error) {
if (retryCount < AI_CONFIG.MAX_RETRIES) {
await this.delay(AI_CONFIG.RETRY_DELAY * (retryCount + 1));
return this.getAnswerForQuestion(question, retryCount + 1);
} else {
throw new Error(`获取答案失败: ${error.message}`);
}
}
}
getDirectAnswer(question) {
const correctOptions = question.options.filter(opt => opt.isCorrect === true);
if (correctOptions.length === 0) return null;
if (question.type === CONSTANTS.QUESTION_TYPES.JUDGE) {
return correctOptions[0].index === 0 ? 'A' : 'B';
}
const correctLetters = correctOptions.map((opt, idx) =>
String.fromCharCode(CONSTANTS.LETTER_A_CODE + question.options.indexOf(opt))
).sort();
return question.type === CONSTANTS.QUESTION_TYPES.SINGLE ?
correctLetters[0] : correctLetters.join('');
}
queryAI(question) {
return new Promise((resolve, reject) => {
const prompt = this.buildPrompt(question);
GM_xmlhttpRequest({
method: 'POST',
url: AI_CONFIG.API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${AI_CONFIG.API_KEY}`
},
data: JSON.stringify({
model: AI_CONFIG.MODEL,
messages: [
{
role: 'system',
content: '你是一个教育助手,专门帮助解答创业课程的测试题。请准确分析题目并给出正确答案。对于选择题,只返回选项字母(如A、B、AB等)。对于判断题,只返回"正确"或"错误"。不要解释,只要答案。'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.1,
max_tokens: 50
}),
timeout: 15000,
onload: (response) => {
if (response.status >= 200 && response.status < 300) {
try {
const data = JSON.parse(response.responseText);
const answer = data.choices[0].message.content.trim();
resolve(this.parseAIAnswer(answer, question.type));
} catch (error) {
reject(new Error('AI响应解析失败'));
}
} else {
reject(new Error(`HTTP ${response.status}`));
}
},
onerror: (error) => {
reject(new Error('网络请求失败'));
},
ontimeout: () => {
reject(new Error('请求超时'));
}
});
});
}
buildPrompt(question) {
let prompt = `请分析以下创业课程测试题并给出正确答案:\n\n题目: ${question.text}\n`;
if (question.type === 'judge') {
prompt += `\n只需回答"正确"或"错误":`;
} else {
prompt += `\n选项:\n`;
question.options.forEach((option, index) => {
prompt += `${String.fromCharCode(65 + index)}. ${option.text}\n`;
});
if (question.type === 'single') {
prompt += `\n只需返回一个正确答案的字母(如A):`;
} else if (question.type === 'multiple') {
prompt += `\n只需返回所有正确答案的字母(如ABC,按字母顺序):`;
}
}
return prompt;
}
parseAIAnswer(answer, type) {
const cleanAnswer = answer.replace(/[^A-D正确错误]/gi, '').trim();
if (type === CONSTANTS.QUESTION_TYPES.JUDGE) {
return cleanAnswer.includes('正确') ? 'A' : 'B';
}
const letters = cleanAnswer.match(/[A-D]/gi);
if (!letters?.length) return null;
const uniqueLetters = [...new Set(letters.map(l => l.toUpperCase()))].sort();
return type === CONSTANTS.QUESTION_TYPES.SINGLE ? uniqueLetters[0] : uniqueLetters.join('');
}
async selectJudgeAnswer(question, answer, statusItem) {
const isCorrect = answer === 'A';
const option = question.options[isCorrect ? 0 : 1];
if (option?.input) {
option.input.click();
this.updateStatusItem(statusItem, `已选择: ${isCorrect ? '正确' : '错误'}`, CONSTANTS.STATUS_TYPES.PROCESSING);
return true;
}
return false;
}
async selectSingleAnswer(answer, question, statusItem) {
const optionIndex = answer.charCodeAt(0) - CONSTANTS.LETTER_A_CODE;
const option = question.options[optionIndex];
if (optionIndex >= 0 && optionIndex < question.options.length && option?.input) {
option.input.click();
this.updateStatusItem(statusItem, `已选择: ${answer}`, CONSTANTS.STATUS_TYPES.PROCESSING);
return true;
}
return false;
}
async selectMultipleAnswers(answer, question, statusItem) {
const answerLetters = answer.split('');
let selectedCount = 0;
for (let i = 0; i < answerLetters.length; i++) {
if (this.isPaused) {
await this.waitForResume();
if (!this.isProcessing) return false;
}
const optionIndex = answerLetters[i].charCodeAt(0) - CONSTANTS.LETTER_A_CODE;
const option = question.options[optionIndex];
if (optionIndex >= 0 && optionIndex < question.options.length &&
option?.input && !option.input.checked) {
option.input.click();
selectedCount++;
this.updateStatusItem(
statusItem,
`已选择 ${selectedCount}/${answerLetters.length}: ${answerLetters.slice(0, i+1).join('')}`,
CONSTANTS.STATUS_TYPES.PROCESSING
);
if (i < answerLetters.length - 1) {
await this.delay(AI_CONFIG.OPTION_DELAY);
}
}
}
return selectedCount > 0;
}
async selectAnswerWithDetails(question, answer, statusItem) {
try {
const handlers = {
[CONSTANTS.QUESTION_TYPES.JUDGE]: () => this.selectJudgeAnswer(question, answer, statusItem),
[CONSTANTS.QUESTION_TYPES.SINGLE]: () => this.selectSingleAnswer(answer, question, statusItem),
[CONSTANTS.QUESTION_TYPES.MULTIPLE]: () => this.selectMultipleAnswers(answer, question, statusItem)
};
const handler = handlers[question.type];
return handler ? await handler() : false;
} catch (error) {
console.error(`选择答案失败:`, error);
return false;
}
}
getStatusIcon(type) {
const iconMap = {
[CONSTANTS.STATUS_TYPES.CORRECT]: CONSTANTS.ICONS.CORRECT,
[CONSTANTS.STATUS_TYPES.WRONG]: CONSTANTS.ICONS.WRONG,
[CONSTANTS.STATUS_TYPES.PROCESSING]: CONSTANTS.ICONS.PROCESSING
};
return iconMap[type] || CONSTANTS.ICONS.PROCESSING;
}
createStatusItem(questionIndex, message, type) {
const statusList = this.statusPanel.querySelector('.status-list');
const statusItem = document.createElement('div');
statusItem.className = `status-item status-${type}`;
statusItem.id = `status-item-${questionIndex}`;
statusItem.innerHTML = `
<span class="status-icon">${this.getStatusIcon(type)}</span>
<span class="status-text">第${questionIndex}题: ${message}</span>
`;
statusList.appendChild(statusItem);
statusList.scrollTop = statusList.scrollHeight;
return statusItem;
}
updateStatusItem(statusItem, message, type) {
if (!statusItem) return;
statusItem.className = `status-item status-${type}`;
const icon = statusItem.querySelector('.status-icon');
const text = statusItem.querySelector('.status-text');
icon.textContent = this.getStatusIcon(type);
text.textContent = `第${statusItem.id.split('-')[2]}题: ${message}`;
}
pause() {
this.isPaused = true;
this.btn.textContent = '已暂停';
this.statusPanel.querySelector('.control-btn-pause').style.display = 'none';
this.statusPanel.querySelector('.control-btn-resume').style.display = 'block';
this.updateStats('已暂停');
}
resume() {
this.isPaused = false;
this.btn.textContent = '继续答题...';
this.statusPanel.querySelector('.control-btn-pause').style.display = 'block';
this.statusPanel.querySelector('.control-btn-resume').style.display = 'none';
this.updateStats('继续中...');
}
skipCurrent() {
if (this.currentQuestionIndex < this.questions.length) {
const question = this.questions[this.currentQuestionIndex];
question.processed = true;
question.answer = 'skipped';
question.source = 'skipped';
// 更新状态项
const statusItem = document.getElementById(`status-item-${question.index}`);
if (statusItem) {
this.updateStatusItem(statusItem, '已跳过', 'wrong');
statusItem.classList.remove('current-question');
}
this.updateStats(`已跳过第${question.index}题`);
}
}
stop() {
this.isProcessing = false;
this.isPaused = false;
this.btn.classList.remove('loading');
this.btn.textContent = '自动答题';
this.statusPanel.style.display = 'none';
}
async waitForResume() {
while (this.isPaused && this.isProcessing) {
await this.delay(1000);
}
}
updateProgress(percent) {
const progressFill = this.statusPanel.querySelector('.progress-fill');
if (progressFill) {
progressFill.style.width = `${percent}%`;
}
}
updateStats(text) {
const statsInfo = this.statusPanel.querySelector('.stats-info');
if (statsInfo) {
statsInfo.textContent = text;
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
showSuccess(message) {
this.btn.classList.add('success');
this.btn.textContent = message;
setTimeout(() => {
this.btn.classList.remove('success');
this.btn.textContent = '自动答题';
}, 3000);
GM_notification({
title: '答题完成',
text: message,
timeout: 3000
});
}
showError(message) {
this.btn.classList.add('error');
this.btn.textContent = '出错了';
setTimeout(() => {
this.btn.classList.remove('error');
this.btn.textContent = '自动答题';
}, 3000);
GM_notification({
title: '答题失败',
text: message,
timeout: 3000
});
}
}
// 页面加载完成后初始化
window.addEventListener('load', () => {
setTimeout(() => {
if (document.querySelector(CONSTANTS.SELECTORS.QUESTION)) {
new AutoAnswer();
console.log('答题助手已加载');
}
}, 2000);
});
})();