// ==UserScript==
// @name 重庆工程学院自动答题助手
// @namespace http://tampermonkey.net/
// @version 2.9.0
// @description 支持单选题、多选题、判断题、简答题、论述题、填空题 - 改进题型识别 | by wapokka
// @author wapokka
// @match https://cqgcxy.wdjycj.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect dashscope.aliyuncs.com
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
let CONFIG = {
apiKey: GM_getValue('qwen_api_key', ''), // 请先在设置中配置API KEY
apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
model: GM_getValue('qwen_model', 'qwen-max'),
autoAnswerInterval: 2000
};
let isRunning = false, isAnswering = false, answeredCount = 0, logs = [];
const log = (msg) => {
const time = new Date().toLocaleTimeString();
logs.push(`[${time}] ${msg}`);
if (logs.length > 500) logs.shift();
console.log('[答题助手]', msg);
const el = document.getElementById('kwai-answer-logs');
if (el) {
el.innerHTML = logs.slice(-50).join('
');
el.scrollTop = el.scrollHeight;
}
};
// ========== 显示重要提示 ==========
const showImportantTips = () => {
const tips = document.getElementById('kwai-important-tips');
if (tips) { tips.remove(); return; }
const div = document.createElement('div');
div.id = 'kwai-important-tips';
div.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 420px;
background: linear-gradient(145deg, #1a1a2e, #16213e);
border: 2px solid #667eea;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
z-index: 999999;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
overflow: hidden;
`;
div.innerHTML = `
⚠️ 重要提示 - 使用前必看 ⚠️
📌 第一步:先点击"检测题目"
进入答题页面后,必须先点击"检测题目"按钮,等待系统识别所有题目后再答题。
📌 第二步:再点击"答全部"或"批量答题"
检测完成后,点击"答全部"开始自动答题,或点击"批量答题"选择范围。
🔑 配置API KEY
目前只支持通义千问API!点击右上角⚙️设置,输入你的API Key才能使用。
💡 简答题说明
简答题和论述题需要输入内容,系统会自动填写到富文本框中。
📞 联系作者
微信:wapokka
办宽带用电信,电信宽带杠杠的!
`;
document.body.appendChild(div);
document.getElementById('kwai-close-tips').onclick = () => div.remove();
// 点击背景关闭
div.onclick = (e) => {
if (e.target === div) div.remove();
};
};
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// ========== 导出日志 ==========
const exportLogs = () => {
const content = logs.join('\n');
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const now = new Date();
const filename = `答题日志_${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}_${String(now.getHours()).padStart(2,'0')}${String(now.getMinutes()).padStart(2,'0')}.txt`;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
log(`✅ 日志已导出: ${filename}`);
};
// ========== 从答题卡获取题型分布 ==========
const getQuestionTypeMapFromCard = () => {
const typeMap = {};
const cardBox = document.querySelector('.card-box');
if (!cardBox) return typeMap;
const items = cardBox.querySelectorAll('.item');
items.forEach(item => {
const heading = item.querySelector('h3');
if (!heading) return;
const typeName = heading.textContent.trim().toLowerCase();
// 使用关键词匹配识别题型(支持多种表述方式)
let type = 'choice'; // 默认单选
// 单选题关键词:单选、单选题、单项选择、单项选择题
if (/单选|单项选择/.test(typeName)) {
type = 'choice';
}
// 多选题关键词:多选、多选题、多项选择、多项选择题、不定项、不定项选择
else if (/多选|多项选择|不定项/.test(typeName)) {
type = 'multiple';
}
// 判断题关键词:判断、判断题、是非题、对错
else if (/判断|是非|对错/.test(typeName)) {
type = 'judge';
}
// 填空题关键词:填空、填空题
else if (/填空/.test(typeName)) {
type = 'fill';
}
// 简答题/论述题关键词:简答、简答题、论述、论述题、问答、问答题、主观题
else if (/简答|论述|问答|主观/.test(typeName)) {
type = 'essay';
}
// 获取题号
const spans = item.querySelectorAll('p span i');
spans.forEach(span => {
const num = parseInt(span.textContent.trim());
if (num) typeMap[num] = type;
});
});
log(`答题卡解析: ${JSON.stringify(typeMap)}`);
return typeMap;
};
// ========== 提取选项文本 ==========
const extractOptionText = (text) => {
text = text.trim();
const patterns = [
/^([A-H])[、..::]\s*(.+)$/i,
/^([①-⑨])\s*(.+)$/,
/^[A-H]\s+(.+)$/i
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match) {
return { label: match[1].toUpperCase(), text: match[2].trim() };
}
}
return null;
};
// ========== 获取单个题目的数据 ==========
const getQuestionData = (strongElement, forcedType) => {
const questionNumMatch = strongElement.textContent.match(/第\s*(\d+)\s*题/);
if (!questionNumMatch) return null;
const questionNum = parseInt(questionNumMatch[1]);
let type = forcedType || 'choice';
// ========== 提取题目文本 ==========
let questionText = '';
const container = strongElement.closest('div');
if (container) {
const titleSpan = container.querySelector('span');
if (titleSpan) {
questionText = titleSpan.textContent.trim();
}
}
if (!questionText) {
let node = strongElement.nextSibling;
let attempts = 0;
while (node && attempts < 30) {
attempts++;
if (node.nodeType === Node.TEXT_NODE) {
const t = node.textContent.trim();
if (t) questionText += ' ' + t;
} else if (node.nodeType === Node.ELEMENT_NODE) {
const text = node.textContent.trim();
if (extractOptionText(text)) break;
if (text && !text.includes('收藏本题目') && !text.includes('你的答案')) {
questionText += ' ' + text;
}
}
node = node.nextSibling;
}
}
questionText = questionText.replace(/\s+/g, ' ').trim();
questionText = questionText.replace(/^第\s*\d+\s*题[::]?\s*/, '').trim();
// ========== 提取选项 ==========
const options = [];
if (container) {
const parent = container.parentElement;
if (parent) {
// 找选项容器
let optionContainer = null;
// 方案1: 找 st-main
const mainDivs = parent.querySelectorAll('.st-main, [class*="st-main"]');
mainDivs.forEach(m => {
if (m !== container) {
const ps = m.querySelectorAll('p');
ps.forEach(p => {
const text = p.textContent.trim();
const opt = extractOptionText(text);
if (opt) options.push(opt);
});
}
});
// 方案2: 直接从父元素找 p
if (options.length === 0) {
parent.querySelectorAll('p').forEach(p => {
if (p !== container) {
const text = p.textContent.trim();
const opt = extractOptionText(text);
if (opt) options.push(opt);
}
});
}
}
}
// ========== 获取 Radio 和 Checkbox ==========
const radios = [];
const checkboxes = [];
if (container) {
const parent = container.parentElement;
if (parent) {
// 找答案区域
const answerBox = parent.querySelector('.answer-box, .answer, [class*="answer"]');
if (answerBox) {
answerBox.querySelectorAll('input[type="radio"]').forEach(r => radios.push(r));
answerBox.querySelectorAll('input[type="checkbox"]').forEach(c => checkboxes.push(c));
}
// 从父元素直接找
if (radios.length === 0 && checkboxes.length === 0) {
parent.querySelectorAll('input[type="radio"]').forEach(r => {
if (!radios.includes(r)) radios.push(r);
});
parent.querySelectorAll('input[type="checkbox"]').forEach(c => {
if (!checkboxes.includes(c)) checkboxes.push(c);
});
}
}
}
// ========== 获取 Textarea / Quill 编辑器 ==========
const textareas = [];
const essayEditors = [];
if (container) {
const parent = container.parentElement;
if (parent) {
// 查找 textarea
parent.querySelectorAll('textarea').forEach(t => {
if (!textareas.includes(t)) textareas.push(t);
});
// 查找 Quill 编辑器
parent.querySelectorAll('.ql-editor').forEach(el => {
if (!essayEditors.includes(el)) essayEditors.push(el);
});
}
}
// ========== 获取填空题输入框 ==========
const fillInputs = [];
if (container) {
const parent = container.parentElement;
if (parent) {
// 查找 input[type="text"], input[type="number"]
parent.querySelectorAll('input[type="text"], input[type="number"], [contenteditable="true"]').forEach(el => {
// 排除 radio/checkbox
if (el.type !== 'radio' && el.type !== 'checkbox' && el.type !== 'hidden') {
if (!fillInputs.includes(el)) fillInputs.push(el);
}
});
}
}
// ========== 根据控件自动判断题型 ==========
if (!forcedType) {
// 优先根据控件类型判断
if (essayEditors.length > 0 || textareas.length > 0) {
type = 'essay';
} else if (checkboxes.length > 0) {
type = 'multiple';
} else if (radios.length > 0) {
if (radios.length === 2) {
// 检查是否是判断题(根据选项文本)
const text1 = radios[0]?.parentElement?.textContent?.toLowerCase() || '';
const text2 = radios[1]?.parentElement?.textContent?.toLowerCase() || '';
// 判断题通常只有"正确/错误"、"对/错"、"是/否"两种选项
if (/正确|错误|对|错|是|否|√|×/.test(text1 + text2)) {
type = 'judge';
} else {
type = 'choice';
}
} else {
type = 'choice';
}
} else if (fillInputs.length > 0) {
type = 'fill';
} else if (options.length === 0 && questionText.length > 0) {
// 没有选项也没有控件,可能是简答
type = 'essay';
}
// 根据题目文本中的关键词进行二次确认
const qTextLower = questionText.toLowerCase();
if (/^.{0,10}(单选|单项选择)/.test(qTextLower)) {
type = 'choice';
} else if (/^.{0,10}(多选|多项选择|不定项)/.test(qTextLower)) {
type = 'multiple';
} else if (/^.{0,10}(判断|是非)/.test(qTextLower)) {
type = 'judge';
} else if (/^.{0,10}(填空)/.test(qTextLower)) {
type = 'fill';
} else if (/^.{0,10}(简答|论述)/.test(qTextLower)) {
type = 'essay';
}
}
return {
num: questionNum,
questionText: questionText,
type: type,
options: options,
radios: radios,
checkboxes: checkboxes,
textareas: textareas,
essayEditors: essayEditors,
fillInputs: fillInputs
};
};
// ========== 获取所有题目 ==========
const getQuestions = () => {
// 先从答题卡获取题型分布
const typeMap = getQuestionTypeMapFromCard();
const qs = [];
const allStrongs = document.querySelectorAll('strong');
allStrongs.forEach(strong => {
const text = strong.textContent.trim();
if (text.match(/第\s*\d+\s*题/)) {
const questionNumMatch = text.match(/第\s*(\d+)\s*题/);
if (questionNumMatch) {
const num = parseInt(questionNumMatch[1]);
const forcedType = typeMap[num];
const q = getQuestionData(strong, forcedType);
if (q && q.questionText) {
qs.push(q);
const typeNames = { 'choice': '单选', 'multiple': '多选', 'judge': '判断', 'essay': '简答/论述', 'fill': '填空' };
log(`第${q.num}题: [${typeNames[q.type]||q.type}] 选项${q.options.length}个, Radio${q.radios.length}, Checkbox${q.checkboxes.length}, Quill${q.essayEditors.length}`);
}
}
}
});
return qs.sort((a, b) => a.num - b.num);
};
// ========== 单选题选择 ==========
const selectChoice = (letter, q) => {
letter = letter.toUpperCase();
log(`选择单选 ${letter}, radio数: ${q.radios.length}`);
if (q.radios.length === 0) {
log(`❌ 没有找到radio按钮`);
return false;
}
const index = letter.charCodeAt(0) - 65;
if (index >= 0 && index < q.radios.length) {
const radio = q.radios[index];
radio.click();
radio.checked = true;
radio.dispatchEvent(new Event('change', { bubbles: true }));
log(`✅ 已点击 ${letter}`);
return true;
}
for (const radio of q.radios) {
const label = radio.parentElement?.querySelector('label')?.textContent?.trim() || '';
const parentText = radio.parentElement?.textContent || '';
if (label.toUpperCase().startsWith(letter) || parentText.toUpperCase().startsWith(letter + '、') ||
parentText.toUpperCase().startsWith(letter + '.')) {
radio.click();
radio.checked = true;
radio.dispatchEvent(new Event('change', { bubbles: true }));
log(`✅ 已点击 ${letter}`);
return true;
}
}
log(`❌ 找不到选项 ${letter}`);
return false;
};
// ========== 多选题选择 ==========
const selectMultiple = (letters, q) => {
if (!letters || letters.length === 0) { log(`❌ 多选答案为空`); return false; }
log(`选择多选: ${letters.join(', ')}, checkbox数: ${q.checkboxes.length}`);
if (q.checkboxes.length === 0) {
log(`❌ 没有找到checkbox`);
return false;
}
q.checkboxes.forEach(cb => { if (cb.checked) cb.click(); });
let selectedCount = 0;
const upperLetters = letters.map(l => l.toUpperCase());
for (const letter of upperLetters) {
const index = letter.charCodeAt(0) - 65;
if (index >= 0 && index < q.checkboxes.length) {
const checkbox = q.checkboxes[index];
checkbox.click();
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
setTimeout(() => log(` ${letter}: checked=${checkbox.checked}`), 50);
selectedCount++;
continue;
}
for (const checkbox of q.checkboxes) {
const parentText = checkbox.parentElement?.textContent || '';
if (parentText.toUpperCase().startsWith(letter + '、') || parentText.toUpperCase().startsWith(letter + '.')) {
if (!checkbox.checked) {
checkbox.click();
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
setTimeout(() => log(` ${letter}: checked=${checkbox.checked}`), 50);
selectedCount++;
break;
}
}
}
if (selectedCount > 0) { log(`✅ 多选题完成: ${letters.join(', ')}`); return true; }
return false;
};
// ========== 判断题选择 ==========
const selectJudge = (answer, q) => {
log(`判断题: 答案="${answer}", radio数: ${q.radios.length}`);
if (q.radios.length === 0) { log(`❌ 没有找到radio按钮`); return false; }
const answerLower = answer.toLowerCase();
const isCorrect = ['正确', '对', '是', '√', 'true', 'yes'].some(k => answerLower.includes(k));
const isWrong = ['错误', '错', '否', '×', 'false', 'no'].some(k => answerLower.includes(k));
if (!isCorrect && !isWrong) {
log(`❌ 无法判断答案: ${answer}`);
return false;
}
let correctRadio = null;
let wrongRadio = null;
for (const radio of q.radios) {
const parentText = (radio.parentElement?.textContent || '').toLowerCase();
if (!correctRadio && (parentText.includes('正确') || parentText.includes('对'))) {
correctRadio = radio;
}
if (!wrongRadio && (parentText.includes('错误') || parentText.includes('错'))) {
wrongRadio = radio;
}
}
const targetRadio = isCorrect ? correctRadio : wrongRadio;
if (targetRadio) {
targetRadio.click();
targetRadio.checked = true;
targetRadio.dispatchEvent(new Event('change', { bubbles: true }));
log(`✅ 判断题选择: ${isCorrect ? '正确' : '错误'}`);
return true;
}
if (q.radios.length >= 2) {
q.radios[isCorrect ? 0 : 1].click();
q.radios[isCorrect ? 0 : 1].checked = true;
q.radios[isCorrect ? 0 : 1].dispatchEvent(new Event('change', { bubbles: true }));
log(`✅ 判断题选择: ${isCorrect ? '正确' : '错误'}`);
return true;
}
log(`❌ 无法选择判断题`);
return false;
};
// ========== 填空题填写 ==========
const fillBlank = (text, q) => {
if (q.fillInputs && q.fillInputs.length > 0) {
const el = q.fillInputs[0];
log(`填写填空: ${text.substring(0, 50)}...`);
try {
if (el.tagName === 'INPUT') {
el.value = text;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
} else {
el.focus();
el.innerHTML = text;
el.dispatchEvent(new Event('input', { bubbles: true }));
}
log(`✅ 已填写填空`);
return true;
} catch (e) {
log(`❌ 填写失败: ${e.message}`);
}
}
log(`❌ 找不到填空输入框`);
return false;
};
// ========== 简答题/论述题填写 ==========
const fillEssay = (text, q) => {
if (q.essayEditors && q.essayEditors.length > 0) {
const el = q.essayEditors[0];
log(`填写简答(Quill): ${text.substring(0, 50)}...`);
try {
el.focus();
el.innerHTML = '' + text + '
';
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new KeyboardEvent('input', { bubbles: true }));
log(`✅ 已填写简答`);
return true;
} catch (e) {
log(`❌ Quill填写失败: ${e.message}`);
}
}
if (q.textareas && q.textareas.length > 0) {
const el = q.textareas[0];
log(`填写简答(Textarea): ${text.substring(0, 50)}...`);
try {
el.value = text;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
log(`✅ 已填写简答`);
return true;
} catch (e) {
log(`❌ Textarea填写失败: ${e.message}`);
}
}
log(`❌ 找不到答题区`);
return false;
};
// ========== 生成Prompt ==========
const generatePrompt = (q) => {
let optionsText = '';
if (q.options.length > 0) {
optionsText = '\n\n选项:\n' + q.options.map(o => `${o.label}. ${o.text}`).join('\n');
}
let typeInstruction = '';
switch (q.type) {
case 'choice':
typeInstruction = '请仔细分析以下单选题,选出唯一正确答案。只回答一个字母(A/B/C/D/E/F/G/H),不要解释。';
break;
case 'multiple':
typeInstruction = '请仔细分析以下多选题,选出所有正确答案。只回答正确答案的字母,用逗号分隔(如:A,B 或 A,C,E),不要默认全选,不要解释。';
break;
case 'judge':
typeInstruction = '请判断以下说法是否正确。只回答"正确"或"错误",不要解释。';
break;
case 'fill':
typeInstruction = '请回答以下填空题。只给出答案,不要解释,不要参考解析。';
break;
case 'essay':
typeInstruction = '请简要回答以下问题。回答要准确专业,控制在150字以内。直接给出答案,不要解释,不要参考解析。';
break;
default:
typeInstruction = '请回答以下问题。直接给出答案,不要解释。';
}
return `你是一位专业的课程教师。请回答以下问题。
${typeInstruction}
题目:${q.questionText}${optionsText}
直接给出答案即可。`;
};
// ========== 解析AI回答 ==========
const parseAnswer = (content, q) => {
const clean = content.trim().replace(/[`'""']/g, '');
switch (q.type) {
case 'choice':
// 匹配纯字母或带格式的答案
const m = clean.match(/^([A-H])\s*[,.]?$/i) || clean.match(/^答案是?\s*([A-H])\s*[,.]?$/i) || clean.match(/^选\s*([A-H])\s*[,.]?$/i);
return m ? m[1].toUpperCase() : null;
case 'multiple':
const ms = clean.match(/([A-H])\s*[,./;]?\s*/gi);
return ms ? [...new Set(ms.map(l => l.replace(/[^A-Ha-h]/g, '').toUpperCase()).filter(l => l))] : null;
case 'judge':
return clean;
case 'fill':
case 'essay':
return clean;
default:
return clean;
}
};
// ========== 调用API ==========
const callAPI = (q) => {
return new Promise(resolve => {
const prompt = generatePrompt(q);
const typeNames = { 'choice': '单选', 'multiple': '多选', 'judge': '判断', 'essay': '简答/论述', 'fill': '填空' };
log(`========== 第${q.num}题 [${typeNames[q.type]||q.type}] ==========`);
log(`题目: ${q.questionText.substring(0, 60)}${q.questionText.length > 60 ? '...' : ''}`);
if (q.options.length > 0) {
log(`选项: ${q.options.map(o => `${o.label}. ${o.text}`).join(' | ')}`);
}
GM_xmlhttpRequest({
method: 'POST',
url: CONFIG.apiUrl,
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CONFIG.apiKey}` },
data: JSON.stringify({
model: CONFIG.model,
messages: [{ role: 'user', content: prompt }],
max_tokens: q.type === 'choice' ? 10 : (q.type === 'multiple' ? 30 : 500),
temperature: 0.1
}),
timeout: 45000,
onload: res => {
if (res.status !== 200) { log(`API错误: ${res.status}`); resolve(null); return; }
try {
const content = JSON.parse(res.responseText).choices[0].message.content.trim();
log(`AI回答: ${content}`);
resolve(parseAnswer(content, q));
} catch (e) { log(`解析错误: ${e.message}`); resolve(null); }
},
onerror: () => { log('网络错误'); resolve(null); },
ontimeout: () => { log('超时'); resolve(null); }
});
});
};
// ========== 答一道题 ==========
const answerOne = async (q) => {
if (q.type === 'essay' && q.textareas.length === 0 && q.essayEditors.length === 0) {
log(`⚠️ 第${q.num}题是简答但没有答题区,跳过`);
return false;
}
if ((q.type === 'choice' || q.type === 'multiple') && q.radios.length === 0 && q.checkboxes.length === 0) {
log(`⚠️ 第${q.num}题没有找到选项控件,跳过`);
return false;
}
if (q.type === 'fill' && q.fillInputs.length === 0) {
log(`⚠️ 第${q.num}题是填空但没有输入框,跳过`);
return false;
}
const ans = await callAPI(q);
if (!ans) { log(`❌ 第${q.num}题无答案`); return false; }
let success = false;
switch (q.type) {
case 'choice': success = selectChoice(ans, q); break;
case 'multiple': success = selectMultiple(ans, q); break;
case 'judge': success = selectJudge(ans, q); break;
case 'fill': success = fillBlank(ans, q); break;
case 'essay': success = fillEssay(ans, q); break;
default: log(`❌ 未知题型: ${q.type}`);
}
if (success) { answeredCount++; document.getElementById('kwai-answered-count').textContent = answeredCount; }
return success;
};
// ========== 检查API KEY ==========
const checkApiKey = () => {
if (!CONFIG.apiKey || CONFIG.apiKey.trim() === '') {
alert('⚠️ 请先配置API KEY!\n\n点击面板右上角的 ⚙️ 设置按钮,输入你的通义千问API Key。\n\n获取API Key: https://dashscope.aliyun.com');
showSettings();
return false;
}
return true;
};
// ========== 答全部 ==========
const answerAll = async () => {
if (!checkApiKey()) return;
if (isAnswering) return;
isAnswering = true; isRunning = true; answeredCount = 0;
const qs = getQuestions();
log(`🚀 开始答题: ${qs.length}题`);
for (const q of qs) {
if (!isRunning) break;
await answerOne(q);
await sleep(CONFIG.autoAnswerInterval);
}
isAnswering = false; log('✅ 全部完成');
};
const stop = () => { isRunning = false; isAnswering = false; log('⏹ 已停止'); };
// ========== 检测题目 ==========
const detectQuestions = () => {
const qs = getQuestions();
document.getElementById('kwai-total-count').textContent = qs.length;
const counts = { choice: 0, multiple: 0, judge: 0, essay: 0, fill: 0 };
qs.forEach(q => counts[q.type] !== undefined ? counts[q.type]++ : counts['choice']++);
log(`检测到 ${qs.length} 题`);
log(`单选:${counts.choice} 多选:${counts.multiple} 判断:${counts.judge} 填空:${counts.fill} 简答:${counts.essay}`);
return qs;
};
// ========== 设置面板 ==========
const showSettings = () => {
const old = document.getElementById('kwai-settings-modal');
if (old) { old.remove(); return; }
const div = document.createElement('div');
div.id = 'kwai-settings-modal';
div.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:999999;display:flex;align-items:center;justify-content:center;';
div.innerHTML = `
⚙️ 设置
×
`;
document.body.appendChild(div);
div.querySelector('#kwai-close-settings').onclick = () => div.remove();
div.querySelector('#kwai-copy-key-btn').onclick = () => {
navigator.clipboard.writeText(div.querySelector('#kwai-apikey-input').value).then(() => {
div.querySelector('#kwai-copy-key-btn').textContent = '✅';
setTimeout(() => div.querySelector('#kwai-copy-key-btn').textContent = '📋', 1000);
});
};
div.querySelector('#kwai-test-btn').onclick = () => {
const key = div.querySelector('#kwai-apikey-input').value.trim();
const model = div.querySelector('#kwai-model-select').value;
const res = div.querySelector('#kwai-test-result-msg');
res.style.display = 'block'; res.style.background = '#333'; res.style.color = '#fff'; res.textContent = '⏳ 测试中...';
GM_xmlhttpRequest({
method: 'POST', url: CONFIG.apiUrl,
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
data: JSON.stringify({ model: model, messages: [{ role: 'user', content: '1+1=?' }], max_tokens: 10, temperature: 0.1 }),
timeout: 20000,
onload: r => {
if (r.status === 200) {
try {
const data = JSON.parse(r.responseText);
res.style.background = 'rgba(76,175,80,0.3)';
res.textContent = `✅ 成功! 回答: ${data.choices?.[0]?.message?.content}`;
} catch (e) { res.style.background = 'rgba(244,67,54,0.3)'; res.textContent = `❌ 解析失败`; }
} else { res.style.background = 'rgba(244,67,54,0.3)'; res.textContent = `❌ 错误 ${r.status}`; }
},
onerror: () => { res.style.background = 'rgba(244,67,54,0.3)'; res.textContent = '❌ 网络错误'; },
ontimeout: () => { res.style.background = 'rgba(244,67,54,0.3)'; res.textContent = '❌ 超时'; }
});
};
div.querySelector('#kwai-save-btn').onclick = () => {
const key = div.querySelector('#kwai-apikey-input').value.trim();
const model = div.querySelector('#kwai-model-select').value;
const interval = parseInt(div.querySelector('#kwai-interval-input').value) || 2000;
if (!key) { alert('请输入API Key'); return; }
CONFIG.apiKey = key; CONFIG.model = model; CONFIG.autoAnswerInterval = interval;
GM_setValue('qwen_api_key', key); GM_setValue('qwen_model', model);
div.remove(); log(`✅ 设置已保存`);
};
};
// ========== 批量面板 ==========
const showBatch = () => {
const old = document.getElementById('kwai-batch-modal');
if (old) { old.remove(); return; }
const qs = getQuestions();
const div = document.createElement('div');
div.id = 'kwai-batch-modal';
div.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:999998;display:flex;align-items:center;justify-content:center;';
div.innerHTML = `
`;
document.body.appendChild(div);
div.querySelector('#kwai-close-batch').onclick = () => div.remove();
div.querySelector('#batch-all-btn').onclick = () => {
if (!checkApiKey()) { div.remove(); return; }
div.remove(); answerAll();
};
div.querySelector('#batch-start-btn').onclick = () => {
if (!checkApiKey()) { div.remove(); return; }
const s = parseInt(div.querySelector('#batch-start-num').value) || 1;
const e = parseInt(div.querySelector('#batch-end-num').value) || qs.length;
div.remove();
const target = qs.filter(q => q.num >= s && q.num <= e);
log(`批量 ${s}-${e}, 共${target.length}题`);
(async () => {
isAnswering = true; isRunning = true; answeredCount = 0;
for (const q of target) {
if (!isRunning) break;
await answerOne(q);
await sleep(CONFIG.autoAnswerInterval);
}
isAnswering = false; log('✅ 完成');
})();
};
};
// ========== 拖拽 ==========
const makeDraggable = (el, handle) => {
let dragging = false, startX, startY, startLeft, startTop;
handle.style.cursor = 'move';
handle.addEventListener('mousedown', e => {
dragging = true; startX = e.clientX; startY = e.clientY;
startLeft = el.offsetLeft; startTop = el.offsetTop;
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
el.style.left = (startLeft + e.clientX - startX) + 'px';
el.style.top = (startTop + e.clientY - startY) + 'px';
el.style.right = 'auto';
});
document.addEventListener('mouseup', () => dragging = false);
};
// ========== 创建主面板 ==========
const createPanel = () => {
if (document.getElementById('kwai-main-panel')) {
document.getElementById('kwai-main-panel').style.display = 'block';
return;
}
const panel = document.createElement('div');
panel.id = 'kwai-main-panel';
panel.style.cssText = `
position:fixed;right:20px;top:100px;width:340px;
background:linear-gradient(145deg,#1a1a2e,#16213e);
border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.4);
z-index:99997;color:#fff;font-family:-apple-system,BlinkMacSystemFont,sans-serif;
font-size:13px;overflow:hidden;
`;
panel.innerHTML = `
点击"检测"开始
`;
document.body.appendChild(panel);
makeDraggable(panel, panel.querySelector('#kwai-panel-header'));
panel.querySelector('#kwai-tips-btn').onclick = showImportantTips;
panel.querySelector('#kwai-settings-btn').onclick = showSettings;
panel.querySelector('#kwai-minimize-btn').onclick = () => panel.style.display = 'none';
panel.querySelector('#kwai-batch-btn').onclick = showBatch;
panel.querySelector('#kwai-start-all-btn').onclick = answerAll;
panel.querySelector('#kwai-check-btn').onclick = detectQuestions;
panel.querySelector('#kwai-export-btn').onclick = exportLogs;
panel.querySelector('#kwai-stop-btn').onclick = stop;
setTimeout(() => {
log('✅ v2.9 已加载 - by wapokka');
log(`模型: ${CONFIG.model}`);
detectQuestions();
// 首次使用自动弹出提示
if (!localStorage.getItem('kwai_tips_shown')) {
setTimeout(showImportantTips, 1000);
localStorage.setItem('kwai_tips_shown', 'true');
}
}, 500);
};
// ========== 初始化 ==========
const init = () => {
createPanel();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
GM_registerMenuCommand('⚠️ 使用提示', showImportantTips);
GM_registerMenuCommand('⚙️ 设置', showSettings);
GM_registerMenuCommand('📄 导出日志', exportLogs);
GM_registerMenuCommand('📊 检测题目', detectQuestions);
})();