// ==UserScript==
// @name 云学堂自动答题助手
// @namespace http://tampermonkey.net/
// @license MIT
// @version 2.0
// @description 云学堂考试自动答题脚本,支持题目复制和JSON答案导入自动填充
// @match https://asiainfo.yunxuetang.cn/exam/test*
// @icon https://picobd.yunxuetang.cn/sys/asiainfo/others/202305/efc8749aa9474334a99be88b3c1131e5.ico
// @author Assistant
// @grant GM_addStyle
// ==/UserScript==
/*
* 云学堂自动答题助手使用说明:
*
* 1. 进入考试页面:https://asiainfo.yunxuetang.cn/exam/test
* 2. 点击"复制题目"按钮获取所有题目
* 3. 将题目发送给AI获取JSON格式答案
* 4. 将JSON答案粘贴到输入框中
* 5. 点击"开始答题"按钮自动填充答案
*
* 功能特性:
* - 智能题目识别和提取
* - JSON答案导入和自动填充
* - 反检测机制(随机延时、模拟人工操作)
* - 可视化控制面板
* - 答题进度显示
*/
(function () {
'use strict';
// 配置选项
const CONFIG = {
DEBUG: true // 调试模式
};
// 全局状态
let isRunning = false;
let currentQuestionIndex = 0;
let totalQuestions = 0;
let answeredQuestions = 0;
// 添加样式
// @ts-ignore
GM_addStyle(`
#auto-answer-panel {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
background: #fff;
border: 2px solid #007bff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-family: Arial, sans-serif;
}
#auto-answer-panel .panel-header {
background: #007bff;
color: white;
padding: 12px;
font-weight: bold;
border-radius: 6px 6px 0 0;
cursor: move;
}
#auto-answer-panel .panel-body {
padding: 15px;
}
#auto-answer-panel .config-item {
margin-bottom: 10px;
}
#auto-answer-panel label {
display: block;
margin-bottom: 5px;
font-size: 12px;
color: #666;
}
#auto-answer-panel .config-item label {
display: flex;
align-items: center;
cursor: pointer;
font-size: 13px;
color: #333;
}
#auto-answer-panel input, #auto-answer-panel select {
width: 100%;
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
}
#auto-answer-panel input[type="checkbox"] {
width: auto;
margin-right: 8px;
transform: scale(1.2);
}
#auto-answer-panel button {
width: 100%;
padding: 10px 8px;
margin: 5px 0;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
min-height: 36px;
line-height: 1.2;
box-sizing: border-box;
}
#auto-answer-panel .btn-primary {
background: #007bff;
color: white;
}
#auto-answer-panel .btn-danger {
background: #dc3545;
color: white;
}
#auto-answer-panel .btn-secondary {
background: #6c757d;
color: white;
}
#auto-answer-panel .progress {
background: #f0f0f0;
border-radius: 4px;
height: 20px;
margin: 10px 0;
overflow: hidden;
}
#auto-answer-panel .progress-bar {
background: #28a745;
height: 100%;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 11px;
margin-top: 0;
}
#auto-answer-panel .status {
font-size: 11px;
color: #666;
margin: 5px 0;
}
#auto-answer-panel .log {
max-height: 100px;
overflow-y: auto;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 8px;
font-size: 11px;
margin-top: 10px;
}
`);
// 创建控制面板
function createControlPanel() {
const panel = document.createElement('div');
panel.id = 'auto-answer-panel';
panel.innerHTML = `
<div class="panel-header">
🤖 自动答题助手
<span style="float: right; cursor: pointer;" onclick="this.parentElement.parentElement.style.display='none'">×</span>
</div>
<div class="panel-body">
<div class="config-item">
<label>答案JSON数据:</label>
<textarea id="json-answers" placeholder="粘贴JSON格式的答案数据" rows="8" style="width:100%;resize:vertical;font-size:11px;"></textarea>
</div>
<div class="config-item">
<label>答题延时 (秒):</label>
<input type="number" id="delay-time" value="0.5" min="0.1" max="10" step="0.1">
</div>
<button class="btn-primary" id="start-btn">开始答题</button>
<button class="btn-danger" id="stop-btn" style="display:none;">停止答题</button>
<button class="btn-secondary" id="analyze-btn">分析题目</button>
<button class="btn-secondary" id="copy-questions-btn">复制题目</button>
<button class="btn-secondary" id="submit-btn">提交答案</button>
<div class="config-item">
<label>
<input type="checkbox" id="auto-submit-switch" style="width: auto; margin-right: 5px;" checked>
答题完成后自动提交
</label>
</div>
<div class="progress">
<div class="progress-bar" id="progress-bar" style="width: 0%;">0/0</div>
</div>
<div class="status" id="status">就绪</div>
<div class="log" id="log"></div>
</div>
`;
document.body.appendChild(panel);
// 绑定事件
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const analyzeBtn = document.getElementById('analyze-btn');
const copyQuestionsBtn = document.getElementById('copy-questions-btn');
const submitBtn = document.getElementById('submit-btn');
if (startBtn) startBtn.onclick = startAutoAnswer;
if (stopBtn) stopBtn.onclick = stopAutoAnswer;
if (analyzeBtn) analyzeBtn.onclick = analyzeQuestions;
if (copyQuestionsBtn) copyQuestionsBtn.onclick = copyAllQuestions;
if (submitBtn) submitBtn.onclick = submitExam;
// 使面板可拖拽
makeDraggable(panel);
}
// 使面板可拖拽
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
const header = element.querySelector('.panel-header');
header.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
// 日志输出
function log(message, type = 'info') {
const logElement = document.getElementById('log');
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
if (logElement) {
logElement.innerHTML += `<div style="color: ${type === 'error' ? 'red' : type === 'success' ? 'green' : 'black'}">${logEntry}</div>`;
logElement.scrollTop = logElement.scrollHeight;
}
if (CONFIG.DEBUG) {
console.log(`[自动答题] ${logEntry}`);
}
}
// 更新状态
function updateStatus(status) {
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.textContent = status;
}
}
// 更新进度
function updateProgress() {
const progressBar = document.getElementById('progress-bar');
if (progressBar && totalQuestions > 0) {
const percentage = (answeredQuestions / totalQuestions) * 100;
progressBar.style.width = percentage + '%';
progressBar.textContent = `${answeredQuestions}/${totalQuestions}`;
}
}
// 分析题目
function analyzeQuestions() {
const questions = extractQuestions();
log(`发现 ${questions.length} 道题目`);
questions.forEach((q, index) => {
log(`题目 ${index + 1}: ${q.question.substring(0, 50)}...`);
});
totalQuestions = questions.length;
updateProgress();
}
// 提交考试
function submitExam() {
try {
log('开始提交考试...', 'info');
updateStatus('正在提交考试');
// 查找提交按钮
const submitButton = document.getElementById('btnSubmit');
if (!submitButton) {
log('未找到提交按钮', 'error');
return;
}
// 点击提交按钮
submitButton.click();
log('已点击提交按钮', 'success');
// 等待确认弹窗出现并自动点击确认
setTimeout(() => {
const confirmButton = document.getElementById('btnMyConfirm');
if (confirmButton) {
confirmButton.click();
log('已确认提交', 'success');
updateStatus('考试已提交');
} else {
log('未找到确认按钮,请手动确认提交', 'error');
}
}, 1000); // 等待1秒让弹窗出现
} catch (error) {
log(`提交考试失败: ${error.message}`, 'error');
updateStatus('提交失败');
}
}
// 复制所有题目
function copyAllQuestions() {
try {
const questions = extractQuestions();
if (questions.length === 0) {
log('未找到题目,请确保页面已加载完成', 'error');
return;
}
// 获取考试标题
const examTitleElement = document.getElementById('lblExamName');
const examTitle = examTitleElement && examTitleElement.textContent ? examTitleElement.textContent.trim() : '云学堂考试';
let formattedText = `=== ${examTitle} ===\n\n`;
questions.forEach((q, index) => {
// 题目类型标识
let typeLabel = '';
switch (q.type) {
case 'single': typeLabel = '[单选题]'; break;
case 'multiple': typeLabel = '[多选题]'; break;
case 'judge': typeLabel = '[判断题]'; break;
default: typeLabel = '[题目]';
}
formattedText += `${index + 1}. ${typeLabel} ${q.question}\n`;
// 添加选项
q.options.forEach((option, optIndex) => {
if (option.text && option.text.trim()) {
formattedText += ` ${option.label}. ${option.text}\n`;
}
});
formattedText += '\n';
});
formattedText += '\n=== 使用说明 ===\n';
formattedText += '请将以上题目发送给AI,要求AI按照以下JSON格式返回答案:\n\n';
formattedText += '```json\n';
formattedText += '{\n';
formattedText += ' "单选题": [\n';
formattedText += ' {"题号": 1, "答案": "A"},\n';
formattedText += ' {"题号": 2, "答案": "B"}\n';
formattedText += ' ],\n';
formattedText += ' "多选题": [\n';
formattedText += ' {"题号": 3, "答案": "AB"},\n';
formattedText += ' {"题号": 4, "答案": "ACD"}\n';
formattedText += ' ],\n';
formattedText += ' "判断题": [\n';
formattedText += ' {"题号": 5, "答案": "正确"},\n';
formattedText += ' {"题号": 6, "答案": "错误"}\n';
formattedText += ' ]\n';
formattedText += '}\n';
formattedText += '```\n\n';
// 复制到剪贴板
navigator.clipboard.writeText(formattedText).then(() => {
log(`已复制 ${questions.length} 道题目到剪贴板`, 'success');
updateStatus(`题目已复制到剪贴板,共 ${questions.length} 道题`);
}).catch(err => {
// 如果现代API失败,尝试传统方法
const textArea = document.createElement('textarea');
textArea.value = formattedText;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
log(`已复制 ${questions.length} 道题目到剪贴板(兼容模式)`, 'success');
updateStatus(`题目已复制到剪贴板,共 ${questions.length} 道题`);
});
} catch (error) {
log(`复制题目失败: ${error.message}`, 'error');
updateStatus('复制题目失败');
}
}
// 提取题目
function extractQuestions() {
const questions = [];
const questionElements = document.querySelectorAll('li[name="li_Question"]');
questionElements.forEach((element, index) => {
try {
// 提取题目文本 - 根据不同题型结构提取
const questionTextElement = element.querySelector('.col-18.font-size-16');
let questionText = '';
if (questionTextElement) {
// 克隆元素以避免修改原DOM
const clonedElement = questionTextElement.cloneNode(true);
// 移除转码相关的元素
if (clonedElement instanceof Element) {
const transcodingElements = clonedElement.querySelectorAll('.ote_vedio_wrapfail, .ote-file-status-txt, .hide, .upper-latin-list');
transcodingElements.forEach(el => el.remove());
// 获取纯文本并清理
questionText = clonedElement.textContent || '';
}
questionText = questionText.replace(/转码中,请稍候|转码失败/g, '').trim();
}
// 判断题目类型
const radioElements = element.querySelectorAll('input[type="radio"]');
const checkboxElements = element.querySelectorAll('input[type="checkbox"]');
const optionElements = element.querySelectorAll('.upper-latin-list li');
let questionType = 'single'; // 默认单选题
let inputElements = radioElements;
if (checkboxElements.length > 0) {
questionType = 'multiple';
inputElements = checkboxElements;
} else if (optionElements.length === 0 || (radioElements.length === 0 && checkboxElements.length === 0)) {
// 没有选项或没有输入元素,判定为判断题
questionType = 'judge';
} else if (radioElements.length === 2) {
// 检查是否为判断题(通常只有两个选项:正确/错误)
const optionTexts = Array.from(optionElements).map(el =>
el && el.textContent ? el.textContent.trim() : ''
);
if (optionTexts.some(text => text.includes('正确') || text.includes('错误') || text.includes('对') || text.includes('错'))) {
questionType = 'judge';
}
}
// 提取选项
const options = [];
if (questionType === 'judge' && optionElements.length === 0) {
// 判断题没有选项,创建虚拟的正确/错误选项
options.push(
{
label: 'A',
text: '正确',
value: 'true',
element: null
},
{
label: 'B',
text: '错误',
value: 'false',
element: null
}
);
} else {
optionElements.forEach((optionElement, optionIndex) => {
const optionText = optionElement && optionElement.textContent ? optionElement.textContent.trim() : '';
const inputElement = inputElements[optionIndex];
const optionValue = (inputElement && 'value' in inputElement) ? inputElement.value : '';
options.push({
label: String.fromCharCode(65 + optionIndex), // A, B, C, D
text: optionText,
value: optionValue,
element: inputElement
});
});
}
questions.push({
index: index + 1,
question: questionText,
options: options,
type: questionType,
element: element,
answered: false
});
} catch (error) {
log(`提取题目 ${index + 1} 时出错: ${error.message}`, 'error');
}
});
return questions;
}
// 解析JSON答案数据
function parseJsonAnswers() {
const jsonAnswersInput = document.getElementById('json-answers');
if (!jsonAnswersInput) return null;
try {
const jsonText = jsonAnswersInput && 'value' in jsonAnswersInput ? String(jsonAnswersInput.value).trim() : '';
if (!jsonText) return null;
const answersData = JSON.parse(jsonText);
const allAnswers = [];
// 处理单选题
if (answersData.单选题) {
answersData.单选题.forEach(item => {
allAnswers.push({
questionNumber: item.题号,
answer: item.答案,
type: 'single'
});
});
}
// 处理多选题
if (answersData.多选题) {
answersData.多选题.forEach(item => {
allAnswers.push({
questionNumber: item.题号,
answer: item.答案,
type: 'multiple'
});
});
}
// 处理判断题
if (answersData.判断题) {
answersData.判断题.forEach(item => {
allAnswers.push({
questionNumber: item.题号,
answer: item.答案,
type: 'judge'
});
});
}
return allAnswers;
} catch (error) {
log(`解析JSON答案失败: ${error.message}`, 'error');
return null;
}
}
// 选择答案
function selectAnswer(question, answer) {
if (question.type === 'multiple') {
// 多选题:选择多个答案
let selectedCount = 0;
const answerString = typeof answer === 'string' ? answer : answer.toString();
// 将答案字符串转换为字符数组 (如"ABCD" -> ["A", "B", "C", "D"])
const answerLetters = Array.isArray(answer) ? answer : answerString.split('');
answerLetters.forEach(answerLetter => {
const option = question.options.find(opt => opt.label === answerLetter);
if (option && option.element) {
option.element.click();
const event = new Event('change', { bubbles: true });
option.element.dispatchEvent(event);
selectedCount++;
}
});
return selectedCount > 0;
} else if (question.type === 'judge') {
// 判断题:根据答案文本选择
const answerText = typeof answer === 'string' ? answer : answer.toString();
const targetText = answerText === '正确' || answerText === '对' ? '正确' : '错误';
// 查找页面上实际的判断题选项元素
const radioElements = question.element.querySelectorAll('input[type="radio"]');
const optionElements = question.element.querySelectorAll('.upper-latin-list li');
if (radioElements.length >= 2) {
// 有实际的单选按钮,尝试匹配选项文本
let targetIndex = -1;
// 先通过选项文本匹配
for (let i = 0; i < optionElements.length; i++) {
const optText = optionElements[i].textContent.toLowerCase();
if (targetText === '正确') {
if (optText.includes('正确') || optText.includes('对') || optText.includes('true') || optText.includes('是')) {
targetIndex = i;
break;
}
} else {
if (optText.includes('错误') || optText.includes('错') || optText.includes('false') || optText.includes('否')) {
targetIndex = i;
break;
}
}
}
// 如果文本匹配失败,使用标签匹配(A=正确,B=错误)
if (targetIndex === -1) {
if (answerText === 'A' || (targetText === '正确' && radioElements.length >= 1)) {
targetIndex = 0;
} else if (answerText === 'B' || (targetText === '错误' && radioElements.length >= 2)) {
targetIndex = 1;
}
}
// 点击对应的单选按钮
if (targetIndex >= 0 && targetIndex < radioElements.length) {
radioElements[targetIndex].click();
const event = new Event('change', { bubbles: true });
radioElements[targetIndex].dispatchEvent(event);
return true;
}
}
return false;
} else {
// 单选题:选择单个答案
const answerLetter = typeof answer === 'string' ? answer : answer.toString();
const option = question.options.find(opt => opt.label === answerLetter);
if (option && option.element) {
option.element.click();
const event = new Event('change', { bubbles: true });
option.element.dispatchEvent(event);
return true;
}
}
return false;
}
// 随机延时
function randomDelay() {
const delayInput = document.getElementById('delay-time');
const delayValue = delayInput && 'value' in delayInput ? parseFloat(String(delayInput.value)) : 0.5;
const delay = delayValue * 1000;
const randomOffset = Math.random() * 500; // 随机偏移
return new Promise(resolve => setTimeout(resolve, delay + randomOffset));
}
// 开始自动答题
async function startAutoAnswer() {
if (isRunning) return;
// 配置已保存
isRunning = true;
currentQuestionIndex = 0;
answeredQuestions = 0;
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
if (startBtn) startBtn.style.display = 'none';
if (stopBtn) stopBtn.style.display = 'block';
updateStatus('正在答题...');
log('开始自动答题');
const questions = extractQuestions();
totalQuestions = questions.length;
if (totalQuestions === 0) {
log('未找到题目', 'error');
stopAutoAnswer();
return;
}
// 使用JSON答案模式
const answerMode = 'json';
if (answerMode === 'json') {
// JSON答案模式
const jsonAnswers = parseJsonAnswers();
if (!jsonAnswers) {
log('JSON答案数据无效或为空', 'error');
stopAutoAnswer();
return;
}
log(`共发现 ${totalQuestions} 道题目,使用JSON答案模式`);
for (let i = 0; i < questions.length && isRunning; i++) {
const question = questions[i];
currentQuestionIndex = i + 1;
try {
updateStatus(`正在处理第 ${currentQuestionIndex} 题...`);
log(`正在处理第 ${currentQuestionIndex} 题: ${question.question.substring(0, 30)}...`);
// 根据题号查找答案
const answerData = jsonAnswers.find(ans => ans.questionNumber === question.index);
if (answerData) {
const answer = answerData.answer;
log(`第 ${currentQuestionIndex} 题答案: ${Array.isArray(answer) ? answer.join(',') : answer}`);
// 选择答案
if (selectAnswer(question, answer)) {
log(`第 ${currentQuestionIndex} 题已选择答案`, 'success');
answeredQuestions++;
question.answered = true;
} else {
log(`第 ${currentQuestionIndex} 题选择答案失败`, 'error');
}
} else {
log(`第 ${currentQuestionIndex} 题未找到对应答案`, 'error');
}
updateProgress();
// 随机延时
if (i < questions.length - 1) {
await randomDelay();
}
} catch (error) {
log(`第 ${currentQuestionIndex} 题处理失败: ${error.message}`, 'error');
}
}
}
if (isRunning) {
updateStatus(`答题完成!已回答 ${answeredQuestions}/${totalQuestions} 题`);
log(`自动答题完成!已回答 ${answeredQuestions}/${totalQuestions} 题`, 'success');
// 检查是否需要自动提交
const autoSubmitSwitch = document.getElementById('auto-submit-switch');
if (autoSubmitSwitch && 'checked' in autoSubmitSwitch && autoSubmitSwitch.checked) {
log('自动提交开关已开启,准备自动提交...', 'info');
setTimeout(() => {
submitExam();
}, 2000); // 等待2秒后自动提交
} else {
log('自动提交开关未开启,请手动提交', 'info');
}
}
stopAutoAnswer();
}
// 停止自动答题
function stopAutoAnswer() {
isRunning = false;
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
if (startBtn) startBtn.style.display = 'block';
if (stopBtn) stopBtn.style.display = 'none';
if (answeredQuestions === 0) {
updateStatus('已停止');
}
log('自动答题已停止');
}
// 页面加载完成后初始化
function init() {
// 等待页面完全加载
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
return;
}
// 检查是否在考试页面
if (!window.location.href.includes('/exam/test')) {
return;
}
// 等待题目加载
setTimeout(() => {
const questions = document.querySelectorAll('li[name="li_Question"]');
if (questions.length > 0) {
createControlPanel();
log('自动答题助手已加载');
} else {
log('未检测到题目,请刷新页面重试', 'error');
}
}, 2000);
}
// 启动脚本
init();
})();