// ==UserScript== // @name 重庆工程学院自动答题助手 // @namespace http://tampermonkey.net/ // @version 3.0.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才能使用。
💡 图片公式识别 (v3.0新功能)
支持识别题目中的图片公式!主观题/简答题如果包含图片,会自动提取并发送给AI分析。
📞 联系作者
微信: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 extractQuestionImages = (container) => { const images = []; if (!container) return images; // 查找题目区域内的所有图片 const imgElements = container.querySelectorAll('img'); imgElements.forEach(img => { // 排除收藏图标等小图片 const src = img.src || img.getAttribute('data-src') || ''; if (src && !src.includes('icon') && !src.includes('collect') && !src.includes('favorite')) { // 获取图片的完整URL let fullSrc = src; if (src.startsWith('//')) { fullSrc = 'https:' + src; } else if (src.startsWith('/')) { fullSrc = window.location.origin + src; } images.push({ src: fullSrc, alt: img.alt || '', width: img.naturalWidth || img.width, height: img.naturalHeight || img.height }); } }); // 查找公式图片(通常是 svg 或特殊格式的 img) const mathElements = container.querySelectorAll('.MathJax, .math, [class*="math"], [class*="formula"]'); mathElements.forEach(el => { const img = el.querySelector('img'); if (img && img.src) { let fullSrc = img.src; if (fullSrc.startsWith('//')) fullSrc = 'https:' + fullSrc; else if (fullSrc.startsWith('/')) fullSrc = window.location.origin + fullSrc; images.push({ src: fullSrc, alt: img.alt || 'formula', width: img.naturalWidth || img.width, height: img.naturalHeight || img.height, isFormula: true }); } }); return images; }; // ========== 提取选项文本 ========== 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(); // ========== 提取题目图片 ========== let questionImages = []; let questionContainer = null; // 找到题目所在的容器(通常是 strong 的父元素或祖父元素) if (container) { questionContainer = container.parentElement; if (questionContainer) { questionImages = extractQuestionImages(questionContainer); } } if (questionImages.length > 0) { log(`第${questionNum}题发现 ${questionImages.length} 张图片`); } // ========== 提取选项 ========== 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, questionImages: questionImages, 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': '填空' }; const imgInfo = q.questionImages && q.questionImages.length > 0 ? ` [${q.questionImages.length}张图]` : ''; log(`第${q.num}题: [${typeNames[q.type]||q.type}]${imgInfo} 选项${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 imageInstruction = ''; if (q.questionImages && q.questionImages.length > 0) { imageInstruction = '\n\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}${imageInstruction} 题目:${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(' | ')}`); } if (q.questionImages && q.questionImages.length > 0) { log(`图片: ${q.questionImages.length}张`); } // 构建消息内容 let messages = []; // 如果有图片,使用多模态格式 if (q.questionImages && q.questionImages.length > 0) { // 使用视觉模型 const content = [ { type: "text", text: prompt } ]; // 添加图片(最多3张,避免token过多) q.questionImages.slice(0, 3).forEach(img => { content.push({ type: "image_url", image_url: { url: img.src } }); }); messages = [{ role: 'user', content: content }]; } else { // 纯文本 messages = [{ role: 'user', content: prompt }]; } GM_xmlhttpRequest({ method: 'POST', url: CONFIG.apiUrl, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CONFIG.apiKey}` }, data: JSON.stringify({ model: q.questionImages && q.questionImages.length > 0 ? 'qwen-vl-max' : CONFIG.model, messages: messages, 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 = `
📋 批量答题 (${qs.length}题) ×
范围:
`; 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 = `
🤖 答题助手 v2.9 by wapokka
⚙️
0
已答
0
总题数
点击"检测"开始
`; 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); })();