// ==UserScript==
// @name 厦门理工高校邦考试AI助手 (DeepSeek)
// @namespace https://github.com/Wu557666/gaoxiaobangai
// @version 1.1.4
// @description 在厦门理工高校邦考试/测验页面调用 DeepSeek API 自动答题,支持自定义 API 地址
// @author Wu557666
// @icon https://favicon.im/xmut.gaoxiaobang.com?size=128
// @match https://xmut.class.gaoxiaobang.com/class/*/exam/*
// @match https://xmut.class.gaoxiaobang.com/class/*/quiz/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect api.deepseek.com
// @connect api.deepseek.com
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// ========== 默认配置 ==========
const DEFAULT_API_URL = 'https://api.deepseek.com/chat/completions';
const DEFAULT_MODEL = 'deepseek-chat';
// ========== 存储键名 ==========
const STORAGE_API_KEY = 'deepseek_api_key';
const STORAGE_API_URL = 'deepseek_api_url';
const STORAGE_MODEL = 'deepseek_model';
// ========== 获取/设置配置 ==========
function getApiKey() {
return GM_getValue(STORAGE_API_KEY, '');
}
function setApiKey(key) {
GM_setValue(STORAGE_API_KEY, key);
}
function getApiUrl() {
return GM_getValue(STORAGE_API_URL, DEFAULT_API_URL);
}
function setApiUrl(url) {
GM_setValue(STORAGE_API_URL, url);
}
function getModel() {
return GM_getValue(STORAGE_MODEL, DEFAULT_MODEL);
}
function setModel(model) {
GM_setValue(STORAGE_MODEL, model);
}
// ========== 页面内弹窗配置 ==========
function showSettingsPanel() {
const oldOverlay = document.getElementById('ai-settings-overlay');
if (oldOverlay) oldOverlay.remove();
const apiKey = getApiKey();
const apiUrl = getApiUrl();
const model = getModel();
const overlay = document.createElement('div');
overlay.id = 'ai-settings-overlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;';
const panel = document.createElement('div');
panel.id = 'ai-settings-panel';
panel.style.cssText = 'background:white;padding:24px;border-radius:16px;box-shadow:0 10px 40px rgba(0,0,0,0.3);width:400px;max-width:90vw;font-family:Arial,sans-serif;';
panel.innerHTML = `
🤖 考试 AI 助手设置
请输入你的 DeepSeek API Key(点击获取)
高级设置(可选)
`;
overlay.appendChild(panel);
document.body.appendChild(overlay);
document.getElementById('ai-settings-cancel').onclick = () => {
overlay.remove();
if (!getApiKey()) {
const tip = document.createElement('div');
tip.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9999;background:#ff9800;color:white;padding:10px 15px;border-radius:8px;';
tip.innerText = '⚠️ 未设置 API Key,无法使用';
document.body.appendChild(tip);
setTimeout(() => tip.remove(), 3000);
}
};
document.getElementById('ai-settings-save').onclick = () => {
const newKey = document.getElementById('ai-api-key-input').value.trim();
const newUrl = document.getElementById('ai-api-url-input').value.trim();
const newModel = document.getElementById('ai-model-input').value.trim();
if (!newKey) {
alert('❌ API Key 不能为空!');
return;
}
setApiKey(newKey);
if (newUrl) setApiUrl(newUrl);
if (newModel) setModel(newModel);
overlay.remove();
alert('✅ 配置已保存!现在可以使用 AI 答题了。');
location.reload();
};
}
// 注册菜单命令
GM_registerMenuCommand('⚙️ 打开设置面板', showSettingsPanel);
GM_registerMenuCommand('📋 查看当前配置', () => {
const key = getApiKey();
const url = getApiUrl();
const model = getModel();
const keyPreview = key ? key.slice(0, 8) + '...' + key.slice(-4) : '未设置';
alert(`当前配置:\n\nAPI Key: ${keyPreview}\nAPI 地址: ${url}\n模型: ${model}`);
});
// ========== 初始化检查 ==========
const apiKey = getApiKey();
if (!apiKey) {
console.warn('⚠️ 未设置 DeepSeek API Key,正在打开设置面板...');
setTimeout(showSettingsPanel, 1000);
return;
}
console.log('✅ DeepSeek 配置已加载');
// ========== 获取本页所有题目区块 ==========
function getAllQuestions() {
const questions = [];
const titles = document.querySelectorAll('.quiz-title');
const answerWaps = document.querySelectorAll('.answer-wap');
if (titles.length === answerWaps.length && titles.length > 0) {
for (let i = 0; i < titles.length; i++) {
const titleEl = titles[i];
const answerEl = answerWaps[i];
const questionText = titleEl.querySelector('p')?.innerText.trim() || titleEl.innerText.trim();
questions.push({
titleElement: titleEl,
answerElement: answerEl,
text: questionText
});
}
return questions;
}
// 降级:通过容器查找
const containers = document.querySelectorAll('.question-item, .exam-item, .quiz-item, .question');
containers.forEach(container => {
const title = container.querySelector('.quiz-title p, .exam-question, .question-title, .stem');
const answerWap = container.querySelector('.answer-wap');
if (title && answerWap) {
questions.push({
titleElement: title,
answerElement: answerWap,
text: title.innerText.trim()
});
}
});
return questions;
}
// ========== 提取选项 ==========
function extractOptionsFromAnswerWap(answerWap) {
const options = [];
if (!answerWap) return options;
const answerItems = answerWap.querySelectorAll('.answer');
answerItems.forEach(item => {
const letterSpan = Array.from(item.childNodes).find(node => node.nodeType === 3 && node.nodeValue.trim());
const letter = letterSpan ? letterSpan.nodeValue.trim() : '';
const content = item.querySelector('p')?.innerText.trim() || '';
const fullText = letter + ' ' + content;
if (fullText.trim()) options.push(fullText.trim());
});
return options;
}
// ========== 选项选择 ==========
function selectOptionForAnswerWap(answerWap, answer) {
const letters = answer.match(/[A-D]/gi);
if (!letters) {
console.warn('⚠️ 未识别到有效答案字母:', answer);
return;
}
const answerItems = answerWap.querySelectorAll('.answer');
letters.forEach(letter => {
const targetIndex = letter.toUpperCase().charCodeAt(0) - 65;
const targetItem = answerItems[targetIndex];
if (!targetItem) return;
const icon = targetItem.querySelector('i');
if (!icon) return;
const isSelected = icon.classList.contains('gxb-icon-radio-selected') ||
icon.classList.contains('gxb-icon-check-selected') ||
icon.classList.contains('selected');
if (!isSelected) {
icon.click();
console.log(`📌 已选择: ${letter}`);
} else {
console.log(`⏭️ 选项 ${letter} 已选中,跳过`);
}
});
}
// ========== AI 批量答题 ==========
async function answerAllQuestions() {
const apiKeyNow = getApiKey();
if (!apiKeyNow) {
alert('请先设置 API Key!');
showSettingsPanel();
return;
}
const questions = getAllQuestions();
if (!questions.length) {
alert('❌ 未检测到任何题目,请确保在考试/测验页面');
return;
}
console.log(`🎯 本页共检测到 ${questions.length} 道题目,开始逐题解答...`);
for (let i = 0; i < questions.length; i++) {
const q = questions[i];
const questionText = q.text;
const options = extractOptionsFromAnswerWap(q.answerElement);
if (!questionText || !options.length) {
console.warn(`⚠️ 第 ${i+1} 题无法提取题目或选项,跳过`);
continue;
}
console.log(`\n📌 第 ${i+1}/${questions.length} 题:${questionText.slice(0, 50)}...`);
const optionsText = options.join('\n');
const prompt = `请回答以下题目,只返回正确答案的字母(如 A, B, C 或 AB):\n题目:${questionText}\n选项:\n${optionsText}`;
const apiUrl = getApiUrl();
const model = getModel();
await new Promise(resolve => {
GM_xmlhttpRequest({
method: 'POST',
url: apiUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKeyNow}`
},
data: JSON.stringify({
model: model,
messages: [
{ role: 'system', content: '你是一个考试答题助手,只返回正确答案的字母,不要任何解释。' },
{ role: 'user', content: prompt }
],
temperature: 0.1
}),
onload: function(resp) {
try {
const data = JSON.parse(resp.responseText);
const answer = data.choices[0].message.content.trim();
console.log(` AI 答案: ${answer}`);
selectOptionForAnswerWap(q.answerElement, answer);
} catch (e) {
console.error(' 解析 AI 响应失败:', e);
}
resolve();
},
onerror: function(err) {
console.error(' API 请求失败:', err);
resolve();
}
});
});
await new Promise(r => setTimeout(r, 1000));
}
console.log('🎉 本页所有题目处理完毕!');
}
// ========== 浮动控制面板 ==========
function addControlPanel() {
const panel = document.createElement('div');
panel.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9998;background:#1fb6ff;color:white;padding:12px 18px;border-radius:12px;box-shadow:0 4px 15px rgba(0,0,0,0.2);display:flex;align-items:center;gap:12px;font-family:Arial,sans-serif;';
const status = document.createElement('span');
status.innerText = '🤖 AI 就绪';
status.style.fontWeight = 'bold';
const btn = document.createElement('button');
btn.innerText = '答本页全部';
btn.style.cssText = 'background:white;color:#1fb6ff;border:none;padding:6px 16px;border-radius:6px;cursor:pointer;font-weight:bold;font-size:14px;';
btn.onclick = () => {
status.innerText = '🤖 AI 答题中...';
answerAllQuestions().finally(() => {
status.innerText = '🤖 AI 就绪';
});
};
const settingsBtn = document.createElement('button');
settingsBtn.innerText = '⚙️';
settingsBtn.style.cssText = 'background:transparent;color:white;border:none;font-size:18px;cursor:pointer;padding:0 4px;';
settingsBtn.title = '打开设置';
settingsBtn.onclick = showSettingsPanel;
panel.appendChild(status);
panel.appendChild(btn);
panel.appendChild(settingsBtn);
document.body.appendChild(panel);
}
setTimeout(addControlPanel, 1500);
})();