您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【Key逻辑优化】构造Key时,增加去除所有括号和空格的步骤,大幅提升匹配的稳定性和容错性。
// ==UserScript== // @name 华医网自动化考试助手 (V2.3.3 - Key优化版) // @namespace http://tampermonkey.net/ // @version 2.3.3 // @description 【Key逻辑优化】构造Key时,增加去除所有括号和空格的步骤,大幅提升匹配的稳定性和容错性。 // @author Gemini (Key Optimized) // @match *://*.91huayi.com/pages/course.aspx* // @match *://*.91huayi.com/pages/exam.aspx* // @match *://*.91huayi.com/pages/exam_result.aspx* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // --- 配置项 --- const BASE_ANSWER_DELAY_MS = 500; const BASE_SUBMIT_DELAY_MS = 2000; const BASE_RETRY_DELAY_MS = 3000; const SCRIPT_STATE_KEY = 'exam_script_state_v11'; // 版本号更新 const COURSE_LIST_URL_FRAGMENT = '/pages/course.aspx'; // --- 辅助函数 --- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const getRandomDelay = (base, range = 1500) => base + Math.random() * range; // --- 数据存储模块 --- const db = { load: (key, defaultValue) => { const value = GM_getValue(key); return value === undefined ? defaultValue : JSON.parse(value); }, save: (key, data) => GM_setValue(key, JSON.stringify(data)), clear: (key) => GM_deleteValue(key) }; // --- 数据库定义 (版本号更新) --- const correctAnswersDB = { key: 'correct_answers_db_v11', get: () => db.load(correctAnswersDB.key, {}), set: (data) => db.save(correctAnswersDB.key, data) }; const wrongAttemptsDB = { key: 'wrong_attempts_db_v11', get: () => db.load(wrongAttemptsDB.key, {}), set: (data) => db.save(wrongAttemptsDB.key, data), clear: () => db.clear(wrongAttemptsDB.key) }; const examQueueDB = { key: 'exam_queue_db_v11', get: () => db.load(examQueueDB.key, []), set: (data) => db.save(examQueueDB.key, data), clear: () => db.clear(examQueueDB.key) }; const returnUrlDB = { key: 'return_url_db_v11', get: () => db.load(returnUrlDB.key, null), set: (data) => db.save(returnUrlDB.key, data), clear: () => db.clear(returnUrlDB.key) }; const questionOptionMapDB = { key: 'question_option_map_v11', get: () => db.load(questionOptionMapDB.key, {}), set: (data) => db.save(questionOptionMapDB.key, data), clear: () => db.clear(questionOptionMapDB.key) }; const debugKeysDB = { key: 'debug_keys_db_v11', get: () => db.load(debugKeysDB.key, []), set: (data) => db.save(debugKeysDB.key, data), clear: () => db.clear(debugKeysDB.key) }; // --- 核心逻辑 --- // --- V2.3.3 核心修改:优化Key的构造逻辑 --- function normalizeQuestion(text) { if (!text) return ''; return text .trim() // 1. 去除首尾空格 .replace(/^\d+、\s*/, '') // 2. 去除题号 .replace(/[()()]/g, '') // 3. 【新增】移除所有括号 .replace(/\s+| /g, '') // 4. 【新增】移除所有空白符(包括全角空格) .trim(); // 5. 最后再trim一次确保万无一失 } function normalizeOption(text) { if (!text) return ''; // 选项的标准化逻辑也统一,确保一致性 return text .trim() .replace(/^[A-Z]、\s*/, '') .replace(/[()()]/g, '') .replace(/\s+| /g, '') .trim(); } function getOptionInfo(optionEl) { if (!optionEl) return null; const label = optionEl.querySelector('label'); if (!label) return null; const rawText = label.innerText; const match = rawText.trim().match(/^([A-Z])、/); return { letter: match ? match[1] : null, content: normalizeOption(rawText), // 使用标准化的选项内容 element: optionEl.querySelector('input.qo_name') }; } function handleCourseListPage() { console.log("脚本:进入课程列表页面。"); const allCourses = document.querySelectorAll('.course'); if (allCourses.length === 0) return; const pendingExams = []; allCourses.forEach(courseEl => { const statusSpan = courseEl.querySelector('h3 > span'); const link = courseEl.querySelector('h3 > a.f14blue'); if (link && statusSpan && !statusSpan.innerText.includes('已完成')) { const courseName = link.innerText.trim(); const href = link.getAttribute('href'); const cwidMatch = href.match(/cwid=([a-f0-9-]+)/); if (cwidMatch && cwidMatch[1]) { pendingExams.push({ cwid: cwidMatch[1], name: courseName }); } } }); if (pendingExams.length > 0) { console.log(`脚本:发现 ${pendingExams.length} 个待考课程。已创建考试队列。`); examQueueDB.set(pendingExams); returnUrlDB.set(window.location.href); updatePanel(); processNextInQueue(); } else { console.log("脚本:所有课程均已完成,无需操作。"); alert("太棒了!所有课程都已完成!"); setScriptState(false); updatePanel(); } } async function handleExamPage() { console.log("脚本:进入考试页面。开始根据内容智能作答..."); const correctAnswers = correctAnswersDB.get(); const wrongAttempts = wrongAttemptsDB.get(); const questions = document.querySelectorAll('.tablestyle'); const questionOptionMap = {}; const examPageGeneratedKeys = []; for (const questionEl of Array.from(questions)) { const questionKey = normalizeQuestion(questionEl.querySelector('.q_name').innerText); console.log(`[考试页Key生成]: "${questionKey}"`); examPageGeneratedKeys.push(questionKey); let isAnswered = false; const pageOptions = Array.from(questionEl.querySelectorAll('tbody tr')).map(getOptionInfo).filter(Boolean); const currentOptionMap = {}; pageOptions.forEach(opt => { if(opt.letter && opt.content) currentOptionMap[opt.content] = opt.letter; }); questionOptionMap[questionKey] = currentOptionMap; const knownCorrects = correctAnswers[questionKey] || []; if (knownCorrects.length > 0) { for (const knownGood of knownCorrects) { for (const pageOpt of pageOptions) { if (pageOpt.content === knownGood.content) { pageOpt.element.click(); isAnswered = true; break; } } if (isAnswered) break; } } if (!isAnswered) { const knownWrongs = wrongAttempts[questionKey] || []; const wrongContents = knownWrongs.map(item => item.content); for (const pageOpt of pageOptions) { if (!wrongContents.includes(pageOpt.content)) { pageOpt.element.click(); isAnswered = true; break; } } } if (!isAnswered && pageOptions.length > 0) { pageOptions[0].element.click(); isAnswered = true; } if (!isAnswered) { console.error("脚本错误:有题目未能作答,流程停止。"); setScriptState(false); updatePanel(); return; } await sleep(getRandomDelay(BASE_ANSWER_DELAY_MS, 1000)); } console.log("脚本:保存本次考试的<问题-选项内容-字母>映射表..."); questionOptionMapDB.set(questionOptionMap); debugKeysDB.set(examPageGeneratedKeys); const submitDelay = getRandomDelay(BASE_SUBMIT_DELAY_MS, 2000); console.log(`脚本:作答完毕,将在约 ${(submitDelay / 1000).toFixed(1)} 秒后提交...`); await sleep(submitDelay); document.getElementById('btn_submit')?.click(); } async function handleResultPage() { console.log("脚本:进入结果页面,准备进行高保真学习。"); const previousExamKeys = debugKeysDB.get(); if (previousExamKeys.length > 0) { console.log("--- 考试页Key摘要 (用于对比) ---"); console.table(previousExamKeys); console.log("---------------------------------"); debugKeysDB.clear(); } const questionMap = questionOptionMapDB.get(); if(Object.keys(questionMap).length === 0){ console.error("无法加载问题映射表,无法进行学习。"); return; } const isPassed = !!document.querySelector('.tips_text')?.innerText.includes('考试通过'); if (isPassed) { console.log("考试通过!准备处理下一个课程..."); console.log("正在清除本次考试的答案库(适配不同考试)..."); db.clear(correctAnswersDB.key); wrongAttemptsDB.clear(); questionOptionMapDB.clear(); const cwidMatch = window.location.href.match(/cwid=([a-f0-9-]+)/); if (cwidMatch) { const completedCwid = cwidMatch[1]; let queue = examQueueDB.get(); queue = queue.filter(exam => exam.cwid !== completedCwid); examQueueDB.set(queue); } await sleep(1000); processNextInQueue(); } else { console.log("考试未通过。正在更新智能题库并准备重试..."); const correctAnswers = correctAnswersDB.get(); const wrongAttempts = wrongAttemptsDB.get(); document.querySelectorAll('.state_cour_ul .state_cour_lis').forEach(item => { const questionKey = normalizeQuestion(item.querySelector('.state_lis_text:first-of-type').title); console.log(`[结果页Key生成]: "${questionKey}"`); const answerElement = item.querySelectorAll('.state_lis_text')[1]; if (!answerElement) return; const userAnswerContent = normalizeOption(answerElement.innerText.replace(/【您的答案:|】/g, '')); const isCorrect = item.querySelector('.state_error').src.includes('bar_img.png'); const optionsForThisQuestion = questionMap[questionKey]; if (!optionsForThisQuestion) { console.warn(`警告:在映射表中未找到问题 KEY: "${questionKey}"`); return; } // 使用标准化的内容在映射表中反查字母 const userAnswerLetter = optionsForThisQuestion[userAnswerContent]; if(!userAnswerLetter) { console.warn(`警告:在问题 "${questionKey}" 的映射中未找到答案内容 "${userAnswerContent}"`); return; } const knowledgeBit = { letter: userAnswerLetter, content: userAnswerContent }; if (isCorrect) { if (!correctAnswers[questionKey]) correctAnswers[questionKey] = []; if (!correctAnswers[questionKey].some(k => k.content === knowledgeBit.content)) { correctAnswers[questionKey].push(knowledgeBit); } if (wrongAttempts[questionKey]) { wrongAttempts[questionKey] = wrongAttempts[questionKey].filter(k => k.content !== knowledgeBit.content); if (wrongAttempts[questionKey].length === 0) delete wrongAttempts[questionKey]; } } else { if (!wrongAttempts[questionKey]) wrongAttempts[questionKey] = []; if (!wrongAttempts[questionKey].some(k => k.content === knowledgeBit.content)) { wrongAttempts[questionKey].push(knowledgeBit); } } }); correctAnswersDB.set(correctAnswers); wrongAttemptsDB.set(wrongAttempts); await sleep(getRandomDelay(BASE_RETRY_DELAY_MS, 2000)); const retryButton = Array.from(document.querySelectorAll('input.state_foot_btn')).find(btn => btn.value === '重新考试'); if(retryButton) retryButton.click(); else console.error("找不到“重新考试”按钮!"); } } function processNextInQueue() { const queue = examQueueDB.get(); if (queue.length > 0) { const nextExam = queue[0]; console.log(`脚本:正在导航到下一个考试,课程名: ${nextExam.name}`); window.location.href = `/pages/exam.aspx?cwid=${nextExam.cwid}`; } else { console.log("队列已清空!所有考试均已完成!"); const returnUrl = returnUrlDB.get(); examQueueDB.clear(); returnUrlDB.clear(); wrongAttemptsDB.clear(); questionOptionMapDB.clear(); setScriptState(false); alert("所有待考课程均已完成!脚本已停止。即将返回课程列表页。"); setTimeout(() => { if (returnUrl) window.location.href = returnUrl; }, 2000); } } // --- UI 控制面板 --- function setupUI() { if (document.getElementById('exam-helper-panel')) return; const panel = document.createElement('div'); panel.id = 'exam-helper-panel'; document.body.appendChild(panel); const dbViewer = document.createElement('div'); dbViewer.id = 'db-viewer-panel'; dbViewer.style.display = 'none'; dbViewer.innerHTML = `<div id="db-viewer-header"><h3>数据库信息 (只读)</h3><span id="db-viewer-close-btn">×</span></div><div id="db-viewer-content"></div>`; document.body.appendChild(dbViewer); dbViewer.querySelector('#db-viewer-close-btn').addEventListener('click', () => dbViewer.style.display = 'none'); GM_addStyle(` #exam-helper-panel { position: fixed; bottom: 20px; right: 20px; background-color: #f0f9ff; border: 2px solid #1e90ff; border-radius: 8px; padding: 15px; z-index: 9999; font-family: sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,0.15); width: 260px; font-size: 14px; } #exam-helper-panel h3 { margin: 0 0 12px 0; color: #1e90ff; text-align: center; font-size: 16px; } #exam-helper-panel button { width: 100%; padding: 8px; margin-bottom: 10px; border: none; border-radius: 5px; color: white; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } #exam-helper-panel button:hover { opacity: 0.9; } #toggle-script-btn { background-color: #28a745; } #toggle-script-btn.running { background-color: #dc3545; } #clear-queue-btn { background-color: #007bff; } #view-db-btn { background-color: #6c757d; } #clear-db-btn { background-color: #ffc107; } #exam-helper-panel p { margin: 5px 0; line-height: 1.4; } #exam-helper-panel strong { color: #333; } #queue-list-container { background-color: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 5px 10px; margin-top: 10px; max-height: 150px; overflow-y: auto; font-size: 12px; } #db-viewer-panel { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 800px; height: 80%; max-height: 600px; background-color: #fff; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.5); z-index: 10001; flex-direction: column; } #db-viewer-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background-color: #f7f7f7; border-bottom: 1px solid #eee; } #db-viewer-close-btn { font-size: 24px; color: #888; cursor: pointer; font-weight: bold; } #db-viewer-content { padding: 15px; overflow: auto; flex-grow: 1; font-family: Consolas, Monaco, monospace; font-size: 13px; background-color: #fafafa; } #db-viewer-content h4 { margin-top: 0; color: #1e90ff; border-bottom: 1px solid #ddd; padding-bottom: 5px;} #db-viewer-content pre { white-space: pre-wrap; word-wrap: break-word; margin: 0; } `); updatePanel(); } function updatePanel() { const panel = document.getElementById('exam-helper-panel'); if (!panel) return; const isRunning = getScriptState(); const queue = examQueueDB.get(); const currentExam = queue.length > 0 ? queue[0].name : "无"; const remainingQueue = queue.slice(1); let queueHtml = '<p style="color:#888;text-align:center;font-style:italic;">队列为空</p>'; if (remainingQueue.length > 0) queueHtml = '<ol style="margin:0;padding-left:20px;">' + remainingQueue.map(exam => `<li style="margin-bottom:5px;color:#555;">${exam.name}</li>`).join('') + '</ol>'; panel.innerHTML = ` <h3>自动考试控制 V2.3.3</h3> <button id="toggle-script-btn">${isRunning ? '暂停自动考试' : '开始自动考试'}</button> <button id="clear-queue-btn">清空队列</button> <button id="view-db-btn">查看数据库</button> <button id="clear-db-btn">清除全部数据(重要)</button> <p><strong>脚本状态:</strong> <span style="color: ${isRunning ? '#28a745' : '#dc3545'}; font-weight: bold;">${isRunning ? '运行中' : '已停止'}</span></p> <p><strong>当前操作:</strong> <span style="color: #007bff; font-weight: bold;">${currentExam}</span></p> <strong>待考列表:</strong> <div id="queue-list-container" style="background-color:#fff;border:1px solid #ddd;border-radius:4px;padding:5px 10px;margin-top:10px;max-height:150px;overflow-y:auto;font-size:12px;">${queueHtml}</div> `; if (isRunning) panel.querySelector('#toggle-script-btn').classList.add('running'); panel.querySelector('#toggle-script-btn').addEventListener('click', toggleScript); panel.querySelector('#view-db-btn').addEventListener('click', showDbInfo); panel.querySelector('#clear-queue-btn').addEventListener('click', () => { if (confirm('确定要清空当前的考试队列吗?')) { examQueueDB.clear(); returnUrlDB.clear(); updatePanel(); } }); panel.querySelector('#clear-db-btn').addEventListener('click', () => { if (confirm('【!!!重要!!!】\n确定要清除所有本地数据吗?\n\n新版脚本的数据库结构与旧版不兼容,必须清除旧数据才能正常工作!此操作不可逆!')) { db.clear(correctAnswersDB.key); db.clear(wrongAttemptsDB.key); db.clear(examQueueDB.key); db.clear(returnUrlDB.key); db.clear(questionOptionMapDB.key); db.clear(debugKeysDB.key); alert('所有数据已清除!脚本现在可以正常运行。'); updatePanel(); } }); } function showDbInfo() { const viewer = document.getElementById('db-viewer-panel'); const contentEl = document.getElementById('db-viewer-content'); if (!viewer || !contentEl) return; const correct = correctAnswersDB.get(); const wrong = wrongAttemptsDB.get(); contentEl.innerHTML = `<h4>✔️ 正确答案库</h4><pre>${JSON.stringify(correct, null, 2)}</pre><hr style="margin:20px 0;"><h4>❌ 错题本</h4><pre>${JSON.stringify(wrong, null, 2)}</pre>`; viewer.style.display = 'flex'; } function getScriptState() { return db.load(SCRIPT_STATE_KEY, false); } function setScriptState(isRunning) { db.save(SCRIPT_STATE_KEY, isRunning); } function toggleScript() { const currentState = getScriptState(); setScriptState(!currentState); if (!currentState) main(); else console.log("脚本已由用户暂停。"); updatePanel(); } function main() { setupUI(); if (!getScriptState()) { console.log("脚本当前为停止状态,请在课程列表页点击【开始自动考试】按钮启动。"); return; } const urlPath = window.location.pathname; if (urlPath.includes(COURSE_LIST_URL_FRAGMENT)) handleCourseListPage(); else if (urlPath.includes('/exam.aspx')) handleExamPage(); else if (urlPath.includes('/exam_result.aspx')) handleResultPage(); } main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址