// ==UserScript== // @name 南京专技公需课助手 (V5.0) // @namespace 匿名 // @version 5.0 // @description 进入答题页面,点击智能答题按钮,脚本自动答题。本脚本利用豆包AI实现,所以需要去火山引擎申请API Key和Model ID ,推荐申请Doubao-1.5-pro-32k|250115 模型使用。 // @author 匿名 // @match *://*.mynj.cn*/* // @connect ark.cn-beijing.volces.com // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const DEFAULT_API_URL = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"; // --- 样式区域 --- GM_addStyle(` /* 主面板样式 */ #ai-panel { position: fixed; top: 10px; right: 10px; width: 380px; background: #fff; border: 2px solid #007ee2; box-shadow: 0 0 20px rgba(0,0,0,0.3); z-index: 2147483647; padding: 12px; font-family: "Microsoft YaHei", sans-serif; border-radius: 8px; display: flex; flex-direction: column; max-height: 90vh; transition: opacity 0.3s; } /* 标题栏布局 */ .panel-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 8px; margin-bottom: 8px; } .panel-header h3 { margin: 0; font-size: 16px; color: #007ee2; font-weight: bold; } .panel-minimize-btn { cursor: pointer; font-size: 20px; line-height: 1; color: #666; padding: 0 5px; transition: color 0.2s; } .panel-minimize-btn:hover { color: #007ee2; font-weight: bold; } /* 悬浮球样式 (默认隐藏) */ #ai-float-ball { position: fixed; top: 40%; right: 0; width: 40px; height: 40px; background: #007ee2; border-radius: 20px 0 0 20px; color: #fff; font-size: 20px; text-align: center; line-height: 40px; cursor: pointer; z-index: 2147483647; box-shadow: -2px 2px 10px rgba(0,0,0,0.2); display: none; /* 初始状态根据逻辑控制 */ transition: width 0.2s; } #ai-float-ball:hover { width: 50px; background: #005bb5; } /* 内容区域 */ #ai-response-area { width: 96%; height: 200px; border: 1px solid #ccc; border-radius: 4px; padding: 8px; font-size: 13px; line-height: 1.5; color: #333; overflow-y: auto; margin-bottom: 8px; background: #f8f9fa; font-family: Consolas, monospace; } #log-window { background: #2d2d2d; color: #eee; font-family: Consolas, monospace; font-size: 11px; padding: 8px; border-radius: 4px; margin-bottom: 8px; flex-grow: 1; overflow-y: auto; height: 150px; white-space: pre-wrap; word-break: break-all; } .log-match { color: #a6e22e; } .log-warn { color: #e6db74; } .log-err { color: #f92672; } #ai-panel button { width: 100%; padding: 10px; cursor: pointer; background: #007ee2; color: #fff; border: none; border-radius: 4px; margin-bottom: 5px; font-size:14px; font-weight:bold;} #ai-panel button:hover { background: #005bb5; } #ai-panel button.secondary { background: #e9ecef; color: #495057; font-weight:normal; border:1px solid #ced4da; font-size:12px; padding:6px;} #config-area { display: none; background: #f1f3f5; padding: 10px; border-radius:4px; margin-top: 5px;} #config-area input { width: 95%; padding: 6px; margin-bottom: 6px; border: 1px solid #adb5bd; border-radius:3px;} `); // --- 日志 --- function log(msg, type = '') { const logWin = document.getElementById('log-window'); if (!logWin) return; const line = document.createElement('div'); const time = new Date().toTimeString().split(' ')[0]; line.innerHTML = `[${time}] ${msg}`; if(type) line.className = type; logWin.appendChild(line); logWin.scrollTop = logWin.scrollHeight; } // --- UI 创建 --- function createUI() { if (document.getElementById('ai-panel')) return; // 1. 创建悬浮球 const ball = document.createElement('div'); ball.id = 'ai-float-ball'; ball.innerHTML = '🤖'; // 或者是 '答' ball.title = '点击展开答题助手'; document.body.appendChild(ball); // 2. 创建主面板 const panel = document.createElement('div'); panel.id = 'ai-panel'; panel.innerHTML = `

🎯 答题助手 V5.0

AI回复预览:
准备就绪。点击右上角➖可最小化。
`; document.body.appendChild(panel); // --- 事件绑定 --- // 最小化/展开逻辑 const btnMin = document.getElementById('btn-minimize'); // 点击最小化按钮 btnMin.onclick = () => { panel.style.display = 'none'; ball.style.display = 'block'; }; // 点击悬浮球 ball.onclick = () => { ball.style.display = 'none'; panel.style.display = 'flex'; }; // 功能按钮 document.getElementById('btn-auto-run').onclick = runAutoProcess; document.getElementById('btn-exec-text').onclick = () => { const text = document.getElementById('ai-response-area').value; if(!text) return log("文本框为空!", "log-err"); parseAndFillByPosition(text); }; document.getElementById('btn-toggle-config').onclick = () => { const el = document.getElementById('config-area'); el.style.display = el.style.display === 'block' ? 'none' : 'block'; }; document.getElementById('btn-save-config').onclick = saveConfig; loadConfigToUI(); } function saveConfig() { GM_setValue('doubao_key', document.getElementById('cfg-key').value.trim()); GM_setValue('doubao_model', document.getElementById('cfg-model').value.trim()); log("配置已保存"); document.getElementById('config-area').style.display = 'none'; } function loadConfigToUI() { document.getElementById('cfg-key').value = GM_getValue('doubao_key', ''); document.getElementById('cfg-model').value = GM_getValue('doubao_model', ''); } // --- 主流程 (保持 V4.1 逻辑) --- async function runAutoProcess() { const apiKey = GM_getValue('doubao_key'); const modelId = GM_getValue('doubao_model'); if (!apiKey || !modelId) return log("❌ 请先设置 API Key", "log-err"); log("🔍 提取题目...", "log-match"); const questionData = extractQuestionsWithIndex(); if (!questionData) return log("❌ 未找到题目", "log-err"); log(`✅ 提取成功,共 ${questionData.count} 题`, "log-match"); log("🚀 请求豆包...", "log-match"); try { const aiResponse = await callDoubaoAPI(apiKey, modelId, questionData.text); document.getElementById('ai-response-area').value = aiResponse; log("⬇️ AI回复已接收", "log-match"); parseAndFillByPosition(aiResponse); } catch (error) { log(`❌ 出错: ${error.message}`, "log-err"); } } function extractQuestionsWithIndex() { let text = ""; const panels = document.querySelectorAll('.exam-subject-text-panel'); if(panels.length === 0) return null; panels.forEach((panel, index) => { let rawText = panel.innerText.replace(/\n\s*\n/g, '\n').trim(); text += `【第 ${index + 1} 题】\n${rawText}\n\n`; }); return { text: text, count: panels.length }; } function callDoubaoAPI(apiKey, modelId, content) { return new Promise((resolve, reject) => { const systemPrompt = `你是一个答题机器人。 1. 请按题号输出答案,只输出字母。 2. 格式示例: 1. A 2. ABC 3. 正确 3. 忽略选项文字,只回字母!多选题字母连写。`; GM_xmlhttpRequest({ method: "POST", url: DEFAULT_API_URL, headers: { "Content-Type": "application/json", "Authorization": "Bearer " + apiKey }, data: JSON.stringify({ "model": modelId, "messages": [ { "role": "system", "content": systemPrompt }, { "role": "user", "content": content } ], "temperature": 0.1 }), onload: function(response) { if (response.status === 200) { try { const json = JSON.parse(response.responseText); resolve(json.choices[0].message.content); } catch (e) { reject(new Error("JSON解析失败")); } } else { reject(new Error(`HTTP ${response.status}`)); } }, onerror: function(err) { reject(new Error("网络错误")); } }); }); } function parseAndFillByPosition(input) { log("⚙️ 开始执行...", "log-match"); const lines = input.split('\n'); const panels = document.querySelectorAll('.exam-subject-text-panel'); let successCount = 0; lines.forEach(line => { line = line.trim(); if (!line) return; const match = line.match(/^(\d+)[\.\s:、]*([A-Z]+|正确|错误)/); if (match) { const qIndex = parseInt(match[1]) - 1; const answer = match[2]; if (qIndex >= 0 && qIndex < panels.length) { const currentPanel = panels[qIndex]; if (answer === "正确" || answer === "错误") { const val = (answer === "正确") ? "1" : "0"; const radio = currentPanel.querySelector(`input[value="${val}"]`); if (radio) { if (!radio.checked) radio.click(); log(` [第${qIndex+1}题] -> ${answer}`, "log-match"); successCount++; } } else { const inputs = currentPanel.querySelectorAll('input[type="radio"], input[type="checkbox"]'); if (inputs.length === 0) return; const letters = answer.split(''); let clicked = false; letters.forEach(char => { const optIndex = char.charCodeAt(0) - 65; if (inputs[optIndex]) { if(!inputs[optIndex].checked) inputs[optIndex].click(); clicked = true; } }); if(clicked) { log(` [第${qIndex+1}题] -> ${answer}`, "log-match"); successCount++; } } } } }); log(`🎉 完毕! 勾选 ${successCount} 题。`, "log-match"); } setTimeout(createUI, 1000); })();