// ==UserScript== // @name 西电MOOC通用自动答题(v14 数据库优先+可视化版) // @namespace http://tampermonkey.net/ // @version 14.0 // @description 数据库最高优先级,支持搜索、预览匹配、逐题结果展示、颜色区分 // @match *://mooc1.xidian.edu.cn/* // @match *://mooc2-ans.xidian.edu.cn/* // @match *://*.xidian.edu.cn/* // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // ==/UserScript== (function () { 'use strict'; /* ===================== 配置区 ===================== */ const CONFIG = { questionBlockSelector: '.questionLi, .TiMu, .exam-question, [class*="question-block"]', stemSelector: '.mark_name, .qt-title, h3.u-tit, h3, .question-stem', optionSelector: '.answerBg, .position, .qt-item, .choice, [class*="option"], ul li, .option-item', optionLabelSelector: 'input, .label, [class*="lbl"], .option-label', submitSelector: '.btn-blue, [class*="submit"], a.btn, .moco-btn, .submit-btn, .completeBtn', stepDelay: 400, verbose: true, }; const log = (...a) => CONFIG.verbose && console.log('%c[AutoAns]', 'color:#1F3A2E;font-weight:bold', ...a); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /* ===================== 核心逻辑 ===================== */ // 全局数据源 let dbQuestionList = []; // 数据库题库列表 let finalAnswerMap = {}; // 最终答题用的题号->答案映射 let manualTemplateMap = {}; // 手动粘贴的模板 // 题干归一化 function normalizeKey(s) { return (s || '') .replace(/\s+/g, '') .replace(/(/g, '(').replace(/)/g, ')') .replace(/\(\s*\)/g, '') .replace(/[,。、;:!?.,;:!?]/g, '') .toLowerCase() .trim(); } // Bigram 相似度计算 function getSimilarity(str1, str2) { if (str1 === str2) return 1; const len1 = str1.length, len2 = str2.length; if (len1 < 2 || len2 < 2) return 0; const getBigrams = (str) => { const set = new Map(); for (let i = 0; i < str.length - 1; i++) { const bigram = str.substring(i, i + 2); set.set(bigram, (set.get(bigram) || 0) + 1); } return set; }; const bigrams1 = getBigrams(str1), bigrams2 = getBigrams(str2); let intersection = 0; bigrams1.forEach((count, bigram) => { if (bigrams2.has(bigram)) intersection += Math.min(count, bigrams2.get(bigram)); }); return (2.0 * intersection) / (len1 - 1 + len2 - 1); } // 解析 JSON 题库 function parseQuestionBank(rawJson) { let data = typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson; let questions = []; if (Array.isArray(data)) questions = data; else if (data.questions && Array.isArray(data.questions)) questions = data.questions; else if (!Array.isArray(data) && typeof data === 'object') questions = Object.entries(data).map(([stem, answer]) => ({ stem, correctAnswer: answer })); else throw new Error("无法识别的JSON格式"); return questions.map(q => { const stem = q.stem || q.q || q.question || ''; let answer = q.correctAnswer || q.a || q.answer || q.ans || ''; if (Array.isArray(answer)) answer = answer.join(''); if (typeof answer === 'boolean') answer = answer ? 'A' : 'B'; return { stem, correctAnswer: String(answer).toUpperCase().replace(/[^A-Z]/g, '') }; }).filter(q => q.stem && q.correctAnswer); } // 解析答案模板 function parseAnswerTemplate(text) { const map = {}; const lines = text.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('###')) continue; const rangeMatch = trimmed.match(/(\d+)\s*[-‑]\s*(\d+)\s*[::]\s*(.+)/); if (rangeMatch) { const start = parseInt(rangeMatch[1]), end = parseInt(rangeMatch[2]); const answers = rangeMatch[3].trim().split(/\s+/); for (let i = 0; i <= end - start; i++) { if (answers[i]) map[start + i] = answers[i].toUpperCase().replace(/[^A-Z]/g, ''); } continue; } const singleMatch = trimmed.match(/(\d+)\s*[.::\s]\s*([A-Z]+)/g); if (singleMatch) { for (const item of singleMatch) { const m = item.match(/(\d+)\s*[.::\s]\s*([A-Z]+)/); if (m) map[parseInt(m[1])] = m[2].toUpperCase(); } } } return map; } // 获取选项字母 function getOptionLabel(opt) { const labelEl = opt.querySelector(CONFIG.optionLabelSelector); if (labelEl) { const v = (labelEl.value || labelEl.innerText || '').trim(); if (v) return v.charAt(0).toUpperCase(); } const m = (opt.innerText || '').trim().match(/^([A-Z])/); return m ? m[1] : ''; } // 核心:重建最终答案池(数据库优先,模板兜底) function rebuildFinalAnswerMap() { finalAnswerMap = {}; // 1. 先写入手动模板 Object.assign(finalAnswerMap, manualTemplateMap); // 2. 再用数据库按顺序覆盖(数据库优先级最高) dbQuestionList.forEach((q, idx) => { finalAnswerMap[idx + 1] = q.correctAnswer; }); } /* ===================== 可视化与答题逻辑 ===================== */ // 执行自动答题(isPreview 为 true 时只预览不点击) async function runAutoAnswer(isPreview = false) { const blocks = document.querySelectorAll(CONFIG.questionBlockSelector); const resultPanel = document.getElementById('__aa-result-list'); const statusEl = document.getElementById('__aa-status'); if (blocks.length === 0) { statusEl.textContent = '⚠ 未找到题目块'; statusEl.style.color = '#A8442A'; return; } resultPanel.innerHTML = ''; // 清空旧结果 let stats = { ok: 0, miss: 0, err: 0 }; for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; const stemEl = block.querySelector(CONFIG.stemSelector); const stemText = stemEl ? stemEl.innerText.trim().substring(0, 30) : `第${i+1}题`; const correct = finalAnswerMap[i + 1]; // 清除之前的边框样式 block.style.borderLeft = ''; let resHtml = ''; if (!correct) { // 未收录 stats.miss++; block.style.borderLeft = '4px solid #FF9800'; resHtml = `