// ==UserScript==
// @name 青书学堂考试助手
// @namespace http://tampermonkey.net/
// @version 0.5
// @description 青书学堂考试助手 使用作业答案+ai方式进行答题
// @license MIT
// @author ruby
// @match https://degree.qingshuxuetang.com/zzqd/Student/ExamPaper*
// @grant GM_xmlhttpRequest
// @connect degree.qingshuxuetang.com
// @downloadURL https://update.greasyfork.icu/scripts/534019/%E9%9D%92%E4%B9%A6%E5%AD%A6%E5%A0%82%E8%80%83%E8%AF%95%E5%8A%A9%E6%89%8B.user.js
// @updateURL https://update.greasyfork.icu/scripts/534019/%E9%9D%92%E4%B9%A6%E5%AD%A6%E5%A0%82%E8%80%83%E8%AF%95%E5%8A%A9%E6%89%8B.meta.js
// ==/UserScript==
(function() {
'use strict';
// 添加显示答案按钮和输入框
function addAnswerButton() {
const container = document.createElement('div');
container.style.position = 'fixed';
container.style.top = '10px';
container.style.right = '10px';
container.style.zIndex = '9999';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '10px';
container.style.backgroundColor = 'white';
container.style.padding = '15px';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
container.style.width = '300px';
// 创建标题
const title = document.createElement('div');
title.textContent = '青书学堂答案助手';
title.style.fontSize = '16px';
title.style.fontWeight = 'bold';
title.style.marginBottom = '10px';
title.style.textAlign = 'center';
title.style.color = '#333';
// 创建输入区域容器
const inputContainer = document.createElement('div');
inputContainer.style.display = 'flex';
inputContainer.style.flexDirection = 'column';
inputContainer.style.gap = '10px';
// 创建API密钥输入框
const apiKeyInput = document.createElement('input');
apiKeyInput.type = 'text';
apiKeyInput.placeholder = '请从豆包AI官网获取API密钥';
apiKeyInput.style.padding = '8px';
apiKeyInput.style.borderRadius = '5px';
apiKeyInput.style.border = '1px solid #ddd';
apiKeyInput.style.width = '100%';
apiKeyInput.style.boxSizing = 'border-box';
apiKeyInput.id = 'ai-api-key';
apiKeyInput.value = localStorage.getItem('ai_api_key') || '';
// 创建保存API密钥按钮
const saveApiKeyButton = document.createElement('button');
saveApiKeyButton.textContent = '保存密钥';
saveApiKeyButton.style.padding = '8px';
saveApiKeyButton.style.backgroundColor = '#2196F3';
saveApiKeyButton.style.color = 'white';
saveApiKeyButton.style.border = 'none';
saveApiKeyButton.style.borderRadius = '5px';
saveApiKeyButton.style.cursor = 'pointer';
saveApiKeyButton.style.width = '100%';
saveApiKeyButton.addEventListener('click', () => {
const apiKey = apiKeyInput.value.trim();
if (apiKey) {
localStorage.setItem('ai_api_key', apiKey);
alert('API密钥已保存!');
} else {
alert('请输入有效的API密钥!');
}
});
// 创建quizId输入框
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '输入 作业quizId(可选)';
input.style.padding = '8px';
input.style.borderRadius = '5px';
input.style.border = '1px solid #ddd';
input.style.width = '100%';
input.style.boxSizing = 'border-box';
input.id = 'manual-quiz-id';
// 创建显示答案按钮
const button = document.createElement('button');
button.textContent = '显示答案';
button.style.padding = '10px';
button.style.backgroundColor = '#4CAF50';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.width = '100%';
button.style.marginBottom = '15px';
button.addEventListener('click', showAnswers);
// 创建分割线
const divider = document.createElement('div');
divider.style.height = '1px';
divider.style.backgroundColor = '#eee';
divider.style.margin = '10px 0';
// 创建二维码容器
const qrContainer = document.createElement('div');
qrContainer.style.display = 'flex';
qrContainer.style.justifyContent = 'space-between';
qrContainer.style.marginTop = '10px';
// 创建打赏二维码
const donateQR = document.createElement('div');
donateQR.style.textAlign = 'center';
donateQR.style.flex = '1';
donateQR.innerHTML = `
打赏作者
`;
// 创建微信二维码
const wechatQR = document.createElement('div');
wechatQR.style.textAlign = 'center';
wechatQR.style.flex = '1';
wechatQR.innerHTML = `
联系作者
`;
// 添加版权信息
const copyright = document.createElement('div');
copyright.style.fontSize = '12px';
copyright.style.color = '#999';
copyright.style.textAlign = 'center';
copyright.style.marginTop = '10px';
copyright.textContent = '© 2024 Ruby. All rights reserved.';
// 组装界面
inputContainer.appendChild(apiKeyInput);
inputContainer.appendChild(saveApiKeyButton);
inputContainer.appendChild(input);
inputContainer.appendChild(button);
qrContainer.appendChild(donateQR);
qrContainer.appendChild(wechatQR);
container.appendChild(title);
container.appendChild(inputContainer);
container.appendChild(divider);
container.appendChild(qrContainer);
container.appendChild(copyright);
document.body.appendChild(container);
// 添加悬浮效果
container.addEventListener('mouseenter', () => {
container.style.boxShadow = '0 4px 15px rgba(0,0,0,0.15)';
});
container.addEventListener('mouseleave', () => {
container.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
});
}
// 获取URL参数
function getUrlParams() {
const url = new URL(window.location.href);
// 检查是否有手动输入的 quizId,仅用于答案接口
const manualQuizId = document.getElementById('manual-quiz-id')?.value;
return {
courseId: url.searchParams.get('courseId'),
originalQuizId: url.searchParams.get('quizId'), // 原始URL中的quizId
quizId: manualQuizId || url.searchParams.get('quizId'),
teachPlanId: url.searchParams.get('teachPlanId'),
periodId: url.searchParams.get('periodId')
};
}
// 获取题目答案
async function getAnswers() {
const params = getUrlParams();
const timestamp = Date.now();
const url = `https://degree.qingshuxuetang.com/zzqd/Student/DetailData?quizId=${params.quizId}&_=${timestamp}&_t=${timestamp + 2000}`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'include'
});
const data = await response.json();
return data;
} catch (error) {
console.error('获取答案失败:', error);
return null;
}
}
// 获取题目内容、选项和图片
function getQuestionContent(question) {
const contentElement = question.querySelector('.subject-content');
if (!contentElement) return { text: '', options: [], imageUrl: null };
// 获取文本内容
let text = contentElement.textContent || '';
// 获取选项
const optionsElements = question.querySelectorAll('.option-item');
const options = Array.from(optionsElements).map(option => {
const label = option.querySelector('.option-label')?.textContent || '';
const content = option.querySelector('.option-content')?.textContent || '';
return `${label}. ${content}`;
});
// 查找图片
const image = contentElement.querySelector('img');
const imageUrl = image ? image.src : null;
return { text, options, imageUrl };
}
// 解析HTML内容,提取文本和图片
function parseDescription(description) {
// 创建一个临时的div来解析HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = description;
// 获取所有图片
const images = tempDiv.querySelectorAll('img');
const imageUrls = Array.from(images).map(img => img.src);
// 获取纯文本内容(移除HTML标签)
const text = tempDiv.textContent.trim();
return {
text,
imageUrls
};
}
// 获取题目类型描述
function getQuestionTypeDesc(typeId) {
switch (typeId) {
case 1:
return '这是一道单选题,请从选项中选择一个正确答案。';
case 2:
return '这是一道多选题,请从选项中选择所有正确答案。';
case 3:
return '这是一道简答题,请简要回答,回答需要200 - 300字。';
default:
return '';
}
}
// 使用豆包AI获取答案
async function getAIAnswer(questionData) {
console.log('开始获取AI答案:', questionData);
const API_URL = 'https://ark.cn-beijing.volces.com/api/v3/chat/completions';
const API_KEY = localStorage.getItem('ai_api_key'); // 从本地存储获取API密钥
if (!API_KEY) {
throw new Error('未设置AI API密钥,请先设置密钥!');
}
// 解析题目描述
const { text, imageUrls } = parseDescription(questionData.description);
// 获取选项
const options = questionData.options?.map(opt =>
`${opt.label}. ${opt.description}`
) || [];
// 构建完整的问题文本
let fullQuestion = text + '\n';
if (options.length > 0) {
fullQuestion += '\n选项:\n' + options.join('\n');
}
// 根据题目类型构建提示语
const typeDesc = getQuestionTypeDesc(questionData.typeId);
const prompt = `${typeDesc}\n请回答以下题目:\n\n${fullQuestion}\n\n要求:
1. ${questionData.typeId === 1 ? '必须且只能选择一个选项' :
questionData.typeId === 2 ? '可以选择多个正确选项' :
'简要回答,不超过50字'}
2. 如果有图片请分析图片内容
3. 如果是选择题,请说明选择理由`;
try {
const messages = [{
role: 'user',
content: [
{
type: 'text',
text: prompt
}
]
}];
// 添加图片(如果有)
if (imageUrls.length > 0) {
imageUrls.forEach(url => {
messages[0].content.push({
type: 'image_url',
image_url: {
url: url
}
});
});
}
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: 'doubao-1.5-vision-pro-250328',
messages: messages
})
});
const data = await response.json();
if (data.choices && data.choices[0] && data.choices[0].message) {
return data.choices[0].message.content;
}
return null;
} catch (error) {
console.error('AI答案获取失败:', error);
return null;
}
}
// 获取题目详情 - 使用URL中的quizId
async function getQuestionDetails() {
const params = getUrlParams();
const timestamp = Date.now();
const url = `https://degree.qingshuxuetang.com/zzqd/Student/DetailData?quizId=${params.originalQuizId}&_=${timestamp}&_t=${timestamp + 2000}`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'include'
});
const data = await response.json();
return data;
} catch (error) {
console.error('获取题目详情失败:', error);
return null;
}
}
// 修改显示答案的函数
async function showAnswers() {
try {
const params = getUrlParams();
if (!params.quizId) {
alert('请提供 quizId!');
return;
}
// 获取题目详情和答案
const [questionDetails, answers] = await Promise.all([
getQuestionDetails(),
getAnswers()
]);
console.log('题目详情:', questionDetails);
console.log('答案数据:', answers);
if (!questionDetails?.data?.paperDetail?.questions || !answers?.data?.paperDetail?.questions) {
alert('获取数据失败,请检查网络或重试!');
return;
}
// 移除已有的答案显示
document.querySelectorAll('.answer-display').forEach(el => el.remove());
// 创建答案映射
const answerMap = new Map(
answers.data.paperDetail.questions.map(q => [q.questionId, q.solution])
);
// 为每个题目显示答案
for (const questionData of questionDetails.data.paperDetail.questions) {
const question = document.querySelector(`[id="${questionData.questionId}"]`);
if (!question) continue;
const answerDisplay = document.createElement('div');
answerDisplay.className = 'answer-display';
answerDisplay.style.color = '#ff0000';
answerDisplay.style.fontWeight = 'bold';
answerDisplay.style.marginTop = '10px';
answerDisplay.style.marginBottom = '10px';
answerDisplay.style.fontSize = '16px';
answerDisplay.style.backgroundColor = '#f0f0f0';
answerDisplay.style.padding = '10px';
answerDisplay.style.borderLeft = '4px solid #ff0000';
// 检查是否有官方答案
const officialAnswer = answerMap.get(questionData.questionId);
if (officialAnswer) {
// 如果有官方答案,直接显示
answerDisplay.innerHTML = `【官方答案】${officialAnswer}`;
}else if(questionData.userAnswer){
// 已答题不在使用ai获取答案 消费算力
answerDisplay.innerHTML = `【已答题】 不再使用AI获取答案`
} else {
// 如果没有官方答案,使用豆包AI获取答案
const questionType = questionData.typeId === 1 ? '单选题' :
questionData.typeId === 2 ? '多选题' :
questionData.typeId === 3 ? '简答题' : '未知类型';
answerDisplay.innerHTML = `【正在获取AI答案...】(${questionType})`;
const aiAnswer = await getAIAnswer(questionData);
if (aiAnswer) {
const { imageUrls } = parseDescription(questionData.description);
answerDisplay.innerHTML = `
未找到官方答案,以下是AI参考答案:
题目类型:${questionType}
${imageUrls.length > 0 ? '(包含图片分析)
' : ''}
${questionData.options?.length > 0 ? '(包含选项分析)
' : ''}
${aiAnswer}
`;
if(questionData.typeId === 3){
console.log(question);
question.querySelector('.cke_wysiwyg_div').innerHTML = aiAnswer;
}
} else {
answerDisplay.innerHTML = '【无法获取答案】';
}
}
question.appendChild(answerDisplay);
}
console.log('答案显示完成!');
} catch (error) {
console.error('显示答案出错:', error);
alert('显示答案过程出错,请重试!');
}
}
// 等待页面加载完成后初始化
window.addEventListener('load', () => {
addAnswerButton();
});
})();