- // ==UserScript==
- // @name hyw自动答题助手
- // @namespace http://tampermonkey.net/
- // @version 0.5.4
- // @description hyw自动答题脚本
- // @author 小马
- // @license MIT
- // @match https://hyw.shixizhi.huawei.com/*
- // @grant GM_addStyle
- // @grant unsafeWindow
- // @grant GM_xmlhttpRequest
- // @run-at document-end
- // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- let questionBank = [];
-
- // 添加面板样式
- GM_addStyle(`
- .answer-panel {
- position: fixed;
- top: 500px;
- right: 500px;
- background: white;
- padding: 15px;
- border: 1px solid #ccc;
- border-radius: 5px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
- z-index: 2147483647; /* 最大z-index值 */
- min-width: 200px;
- font-family: Arial, sans-serif;
- user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- }
-
- .answer-panel h3 {
- margin: 0 0 10px 0;
- padding: 0;
- font-size: 16px;
- color: #333;
- }
-
- .answer-panel select,
- .answer-panel input,
- .answer-panel button {
- margin: 5px 0;
- padding: 5px;
- width: 100%;
- box-sizing: border-box;
- }
-
- .answer-panel button {
- background: #007bff;
- color: white;
- border: none;
- border-radius: 3px;
- padding: 8px;
- margin: 5px 0;
- cursor: pointer;
- }
-
- .answer-panel button:hover {
- background: #0056b3;
- }
-
- #status {
- margin-top: 10px;
- color: #666;
- font-size: 14px;
- word-break: break-all;
- }
-
- /* 确保面板始终可见 */
- .answer-panel * {
- display: block;
- visibility: visible !important;
- opacity: 1 !important;
- }
- `);
-
- // 创建控制面板
- function createPanel() {
- try {
- // 先检查是否已存在面板
- const existingPanel = document.querySelector('.answer-panel');
- if (existingPanel) {
- existingPanel.remove();
- }
-
- const panel = document.createElement('div');
- panel.className = 'answer-panel';
- panel.innerHTML = `
- <div>
- <h3>自动答题助手</h3>
- <select id="examType">
- <option value="security">保密考试</option>
- <option value="functional">职能考试</option>
- <option value="shixizhi">应知应会考试</option>
- </select>
- <input type="file" id="fileInput" accept=".xlsx,.xls">
- <button id="startBtn">开始答题</button>
- <button id="stopBtn">停止答题</button>
- <div id="status">等待上传题库...</div>
- </div>
- `;
-
- // 确保面板被添加到 body 的最后
- document.body.appendChild(panel);
-
- // 添加拖拽相关变量
- let isDragging = false;
- let currentX;
- let currentY;
- let initialX;
- let initialY;
- let xOffset = 0;
- let yOffset = 0;
-
- // 拖拽开始
- function dragStart(e) {
- // 如果点击的是select、input或button元素,不启动拖拽
- if (e.target.tagName.toLowerCase() === 'select' ||
- e.target.tagName.toLowerCase() === 'input' ||
- e.target.tagName.toLowerCase() === 'button') {
- return;
- }
-
- if (e.type === "mousedown") {
- initialX = e.clientX - xOffset;
- initialY = e.clientY - yOffset;
- } else if (e.type === "touchstart") {
- initialX = e.touches[0].clientX - xOffset;
- initialY = e.touches[0].clientY - yOffset;
- }
-
- if (e.target === panel || panel.contains(e.target)) {
- isDragging = true;
- }
- }
-
- // 拖拽过程
- function drag(e) {
- if (isDragging) {
- e.preventDefault();
-
- if (e.type === "mousemove") {
- currentX = e.clientX - initialX;
- currentY = e.clientY - initialY;
- } else if (e.type === "touchmove") {
- currentX = e.touches[0].clientX - initialX;
- currentY = e.touches[0].clientY - initialY;
- }
-
- xOffset = currentX;
- yOffset = currentY;
-
- setTranslate(currentX, currentY, panel);
- }
- }
-
- // 设置面板位置
- function setTranslate(xPos, yPos, el) {
- el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
- }
-
- // 拖拽结束
- function dragEnd() {
- initialX = currentX;
- initialY = currentY;
- isDragging = false;
- }
-
- // 添加拖拽事件监听
- panel.addEventListener('mousedown', dragStart, false);
- document.addEventListener('mousemove', drag, false);
- document.addEventListener('mouseup', dragEnd, false);
-
- panel.addEventListener('touchstart', dragStart, false);
- document.addEventListener('touchmove', drag, false);
- document.addEventListener('touchend', dragEnd, false);
-
- // 阻止select的mousedown事件冒泡
- document.getElementById('examType').addEventListener('mousedown', (e) => {
- e.stopPropagation();
- });
-
- // 原有的事件绑定
- document.getElementById('fileInput').addEventListener('change', (e) => {
- const file = e.target.files[0];
- if (file) processExcel(file);
- });
-
- document.getElementById('startBtn').addEventListener('click', startAutoAnswer);
- document.getElementById('stopBtn').addEventListener('click', stopAutoAnswer);
- } catch (error) {
- // 可以尝试使用更简单的备用面板
- try {
- const simplePanel = document.createElement('div');
- simplePanel.className = 'answer-panel';
- simplePanel.innerHTML = `
- <div>
- <h3>自动答题助手(简易版)</h3>
- <input type="file" id="fileInput" accept=".xlsx,.xls">
- <button id="startBtn">开始答题</button>
- <button id="stopBtn">停止答题</button>
- <div id="status">等待上传题库...</div>
- </div>
- `;
- document.body.appendChild(simplePanel);
- } catch (backupError) {
- }
- }
- }
-
- // 更新状态显示
- function updateStatus(message) {
- document.getElementById('status').textContent = message;
- }
-
- let isRunning = false;
-
- // 停止自动答题
- function stopAutoAnswer() {
- isRunning = false;
- updateStatus('已停止答题');
- }
-
- // 开始自动答题
- async function startAutoAnswer() {
- if (questionBank.length === 0) {
- updateStatus('请先上传题库!');
- return;
- }
-
- isRunning = true;
- updateStatus('开始自动答题...');
-
- while (isRunning) {
- try {
- const questionInfo = getCurrentQuestionInfo();
- if (!questionInfo.question) {
- updateStatus('未检测到题目,可能已完成答题');
- isRunning = false;
- break;
- }
-
- console.log('当前题目:', questionInfo.question);
-
- const answerInfo = findAnswer(questionInfo.question);
- if (answerInfo) {
- const selected = selectAnswer(answerInfo, questionInfo.isMultipleChoice);
- if (selected) {
- updateStatus(`已答题: ${questionInfo.question.substring(0, 20)}...`);
- // 减少答题后的等待时间为500ms
- await new Promise(resolve => setTimeout(resolve, 200));
-
- if (!clickNext(true)) {
- updateStatus('无法找到下一题按钮,停止答题');
- isRunning = false;
- break;
- }
- } else {
- updateStatus('答案选择失败');
- if (!clickNext(false)) break;
- }
- } else {
- updateStatus('未找到匹配答案');
- if (!clickNext(false)) break;
- }
-
- // 减少题目间的等待时间为500ms
- await new Promise(resolve => setTimeout(resolve, 200));
-
- } catch (error) {
- updateStatus('答题过程出错,已停止');
- isRunning = false;
- break;
- }
- }
- }
-
- // 处理Excel文件上传
- async function handleFileUpload(e) {
- const file = e.target.files[0];
- const reader = new FileReader();
-
- reader.onload = function (e) {
- const data = new Uint8Array(e.target.result);
- const workbook = XLSX.read(data, { type: 'array' });
- const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
- questionBank = XLSX.utils.sheet_to_json(firstSheet);
- document.getElementById('status').innerText = `已加载 ${questionBank.length} 道题目`;
- };
-
- reader.readAsArrayBuffer(file);
- }
-
- // 处理Excel数据结构
- function processExcel(file) {
- const reader = new FileReader();
- reader.onload = function (e) {
- const data = new Uint8Array(e.target.result);
- const workbook = XLSX.read(data, { type: 'array' });
- const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
- const jsonData = XLSX.utils.sheet_to_json(firstSheet);
-
- // 获取当前选择的考试类型
- const examType = document.getElementById('examType').value;
-
- // 根据不同的考试类型处理数据
- if (examType === 'security') {
- // 保密考试题库格式
- questionBank = jsonData.map(row => ({
- sequence: row['序号'],
- type: row['试题类别'],
- questionId: row['试题类型'],
- question: row['试题题目'],
- options: row['选项'],
- answer: row['正确答案']
- }));
- } else if (examType === 'functional') {
- // 职能考试题格式
- questionBank = jsonData.map(row => ({
- sequence: row['题库'],
- type: row['题型'],
- questionId: '',
- question: row['题'],
- options: `${row['选项A']}\n${row['选项B']}\n${row['选项C']}\n${row['选项D']}\n${row['选项E'] || ''}\n${row['选项F'] || ''}\n${row['选项G'] || ''}\n${row['选项H'] || ''}`.trim(),
- answer: row['正确答案']
- }));
- } else if (examType === 'shixizhi') {
- // 输出原始数据到控制台
- console.log('时习知原始数据:', jsonData);
-
- // 时习知考试题库格式处理
- questionBank = jsonData.map(row => {
- // 跳过表头行
- if (row['序号'] === '序号') return null;
-
- // 处理题目中的下划线
- let processedQuestion = row['__EMPTY'] || '';
- processedQuestion = processedQuestion
- // 统一下划线格式(将连续的下划线替换为5个下划线)
- .replace(/_{2,}/g, '_____')
- // 处理可能存在的特殊下划线字符
- .replace(/_/g, '_')
- // 处理下划线加空格的情况
- .replace(/_ /g, '_')
- .replace(/ _/g, '_');
-
- return {
- sequence: row['序号'] || '',
- type: row['__EMPTY_3'] || '单选',
- questionId: '',
- question: processedQuestion,
- options: row['__EMPTY_1'] || '',
- answer: row['__EMPTY_2'] || '',
- originalQuestion: row['__EMPTY'] || ''
- };
- }).filter(item => item !== null);
-
- // 输出处理后的题库到控制台
- console.log('时习知题库数据:', questionBank);
- updateStatus(`已导入 ${questionBank.length} 道题目`);
- }
-
- updateStatus(`已导入 ${questionBank.length} 道题目`);
- };
- reader.readAsArrayBuffer(file);
- }
-
- // 查找答案
- function findAnswer(currentQuestion) {
- try {
- if (!currentQuestion) {
- console.log('当前题目为空');
- return null;
- }
-
- // 获取当前页面的所有选项文本
- const currentOptions = Array.from(document.querySelectorAll('.option-list-item'))
- .map(option => option.textContent.trim());
- console.log('当前页面选项:', currentOptions);
-
- // 标准化当前题目
- let normalizedCurrentQuestion = currentQuestion
- .replace(/\s+/g, '') // 移除所有空格
- .replace(/()/g, '______') // 将括号替换为6个下划线
- .replace(/\(\)/g, '______') // 将英文括号替换为6个下划线
- .replace(/_+/g, '______'); // 将任意数量的下划线替换为6个下划线
-
- console.log('标准化后的当前题目:', normalizedCurrentQuestion);
-
- // 在题库中查找匹配的题目
- const matchedQuestions = questionBank.filter(item => {
- if (!item || !item.question) {
- console.log('题库中存在无效题目:', item);
- return false;
- }
-
- // 标准化题库中的题目
- const normalizedItemQuestion = item.question
- .replace(/\s+/g, '') // 移除所有空格
- .replace(/_+/g, '______') // 将任意数量的下划线替换为6个下划线
- .replace(/。$/, ''); // 移除句尾句号
-
- // 完全匹配比较
- if (normalizedCurrentQuestion === normalizedItemQuestion) {
- return true;
- }
-
- // 移除标点符号后的模糊匹配
- const cleanCurrentQuestion = normalizedCurrentQuestion.replace(/[。,,]/g, '');
- const cleanItemQuestion = normalizedItemQuestion.replace(/[。,,]/g, '');
-
- // 检查是否包含相同数量的填空
- const currentBlanks = (cleanCurrentQuestion.match(/______/g) || []).length;
- const itemBlanks = (cleanItemQuestion.match(/______/g) || []).length;
-
- // 如果填空数量相同且文本相似,则认为是匹配的
- return currentBlanks === itemBlanks &&
- (cleanItemQuestion.includes(cleanCurrentQuestion) ||
- cleanCurrentQuestion.includes(cleanItemQuestion));
- });
-
- console.log('匹配到的题目:', matchedQuestions);
-
- if (matchedQuestions.length === 0) {
- return null;
- }
-
- // 如果只有一个匹配项,验证必要属性后返回
- if (matchedQuestions.length === 1) {
- const question = matchedQuestions[0];
- if (!question.answer || !question.options) {
- console.log('匹配题目缺少必要属性:', question);
- return null;
- }
- return {
- answer: question.answer,
- type: question.type,
- options: question.options
- };
- }
-
- // 如果有多个匹配项,通过比对选项找到最匹配的题目
- let bestMatch = null;
- let highestMatchScore = 0;
-
- for (const question of matchedQuestions) {
- // 确保题目包含必要属性
- if (!question.options) {
- console.log('题目缺少选项:', question);
- continue;
- }
-
- // 将题库中的选项按分隔符分割并清空
- const bankOptions = question.options.split(/[\n^]/)
- .map(opt => opt.trim())
- .filter(opt => opt)
- .map(opt => opt.replace(/^[A-Z]\s*[..、]\s*/, '').trim());
-
- // 计算选项匹配分数
- let matchScore = 0;
- let matchedOptionsCount = 0;
-
- // 对每个当前页面的选项进行匹配度计算
- for (const currentOpt of currentOptions) {
- const cleanCurrentOpt = currentOpt.replace(/^[A-Z]\s*[..、]\s*/, '').trim();
-
- // 在题库选项中寻找最佳匹配
- const bestOptionMatch = bankOptions.find(bankOpt => {
- if (!bankOpt) return false;
- // 完全匹配得3分
- if (bankOpt === cleanCurrentOpt) {
- return true;
- }
- // 包含关系得2分
- if (bankOpt.includes(cleanCurrentOpt) || cleanCurrentOpt.includes(bankOpt)) {
- return true;
- }
- // 部分词语匹配得1分
- const bankWords = bankOpt.split(/\s+/);
- const currentWords = cleanCurrentOpt.split(/\s+/);
- return bankWords.some(word => currentWords.includes(word));
- });
-
- if (bestOptionMatch) {
- matchedOptionsCount++;
- if (bestOptionMatch === cleanCurrentOpt) {
- matchScore += 3;
- } else if (bestOptionMatch.includes(cleanCurrentOpt) || cleanCurrentOpt.includes(bestOptionMatch)) {
- matchScore += 2;
- } else {
- matchScore += 1;
- }
- }
- }
-
- // 计算最终匹配分数
- const finalScore = matchScore * (matchedOptionsCount / currentOptions.length);
-
- // 更新最佳匹配
- if (finalScore > highestMatchScore) {
- highestMatchScore = finalScore;
- bestMatch = question;
- }
- }
-
- // 如果找到了足够好的匹配(设置一个阈值)
- if (bestMatch && highestMatchScore >= currentOptions.length * 1.5) {
- if (!bestMatch.answer || !bestMatch.options) {
- console.log('最佳匹配题目缺少必要属性:', bestMatch);
- return null;
- }
- return {
- answer: bestMatch.answer,
- type: bestMatch.type,
- options: bestMatch.options
- };
- }
-
- // 如果没有找到足够好的匹配,返回null
- return null;
-
- } catch (error) {
- console.error('查找答案时出错:', error);
- return null;
- }
- }
-
- // 获取当前题目信息
- function getCurrentQuestionInfo() {
- try {
- // 修改选择器以匹配实际DOM结构
- const questionElement = document.querySelector('.main-title .content');
- if (!questionElement) {
- console.log('未找到题目元素');
- return { question: '', isMultipleChoice: false };
- }
-
- const question = questionElement.textContent.trim();
-
- // 判断是否为多选题 - 检查题类型标签
- const typeElement = document.querySelector('.type-name');
- const isMultipleChoice = typeElement && typeElement.textContent.includes('多选题');
-
- return { question, isMultipleChoice };
- } catch (error) {
- console.error('获取题目信息出错:', error);
- return { question: '', isMultipleChoice: false };
- }
- }
-
- // 选择答案
- function selectAnswer(answerInfo, isMultipleChoice) {
- try {
- if (!answerInfo) return false;
- const options = document.querySelectorAll('.option-list-item');
- let selected = false;
- console.log('answerInfo:', answerInfo);
-
- // 同时处理 ^ \n 分隔符
- const allOptions = answerInfo.options
- .split(/[\n^]/) // 使用正则表达式同时匹配\n和^
- .map(opt => opt.trim())
- .filter(opt => opt); // 过滤掉空字符串
-
- console.log('allOptions', allOptions);
-
- if (isMultipleChoice) {
- // 多选题处理:答案可能是多个字母组合(如"ABC")
- const correctAnswers = answerInfo.answer.split('').map(letter => {
- // 尝试在选项中找到以该字母开头的选项
- const matchedOption = allOptions.find((opt, index) => {
- // 如果选项没有字母前缀,则使用索引作为选项序号(A=0, B=1, 等)
- if (!opt.match(/^[A-Z]/)) {
- return index === (letter.charCodeAt(0) - 'A'.charCodeAt(0));
- }
- // 则匹配选项前缀
- return opt.startsWith(letter + '.') ||
- opt.startsWith(letter + '.') ||
- opt.startsWith(letter + '、') ||
- opt.startsWith(letter + ' 、') ||
- opt.match(new RegExp(`^${letter}\\s*[..、]`));
- });
- console.log('matchedOption', matchedOption);
-
- return matchedOption;
- }).filter(Boolean);
-
-
-
- // 遍历页面上的选项
- options.forEach((option, index) => {
- const optionText = option.textContent.trim();
- // 检查当前选项是否是正确答案之一
- const isCorrectOption = correctAnswers.some(correctAnswer => {
- // 如果正确答案没有字母前缀,直接比较内容
- const correctContent = correctAnswer.replace(/^[A-Z]\s*[..、]\s*/, '').trim();
- const cleanOptionText = optionText.replace(/^[A-Z]\s*[..、]\s*/, '').trim();
- return cleanOptionText === correctContent ||
- cleanOptionText.includes(correctContent) ||
- correctContent.includes(cleanOptionText);
- });
-
- const input = option.querySelector('input[type="checkbox"]');
- if (input && isCorrectOption && !input.checked) {
- input.click();
- selected = true;
- }
- });
- } else {
- // 单选题处理
- let correctAnswerContent;
-
- // 检查是否是标准的选项格式(A. B. C. 等)
- const hasStandardPrefix = allOptions.every(opt =>
- opt.match(/^[A-Z][..、\s]/) || // 匹配 A. A. A、 A [空格]
- opt.match(/^[A-Z][\s]*[..、]/) // 匹配 A . A . A 、
- );
-
- if (!hasStandardPrefix) {
- // 如果不是标准格式,直接使用索引
- const index = answerInfo.answer.charCodeAt(0) - 'A'.charCodeAt(0);
- if (index >= 0 && index < allOptions.length) {
- correctAnswerContent = allOptions[index];
- console.log('根据索引找到的正确答案内容:', correctAnswerContent);
- }
- } else {
- // 如果是标准格式,查找匹配的选项
- correctAnswerContent = allOptions.find(opt =>
- opt.match(new RegExp(`^${answerInfo.answer}[..、\\s]`)) || // 匹配 A. A. A、 A [空格]
- opt.match(new RegExp(`^${answerInfo.answer}\\s*[..、]`)) // 匹配 A . A . A 、
- );
- }
-
- if (!correctAnswerContent) {
- console.log('未找到正确答案内容');
- return false;
- }
-
- // 清理选项内容时保留原始文本
- const cleanContent = (text) => {
- let cleanText = text;
- // 如果文本以标准选项格式开头,才移除前缀
- if (text.match(/^[A-Z][..、\s]/) || text.match(/^[A-Z][\s]*[..、]/)) {
- cleanText = text.replace(/^[A-Z]\s*[..、]\s*/, '').trim();
- }
- // 处理分号和空格
- return cleanText
- .replace(/[;;]/g, ' ') // 将中英文分号替换为空格
- .replace(/\s+/g, ' ') // 将多个空格合并为单个空格
- .trim(); // 移除首尾空格
- };
-
- // 比较选项时使用清理后的内容
- options.forEach((option, index) => {
- const optionText = option.textContent.trim();
- const cleanOptionText = cleanContent(optionText);
- const cleanCorrectContent = cleanContent(correctAnswerContent);
-
- console.log(`比较选项 ${index + 1}:`, {
- correctContent: cleanCorrectContent,
- cleanOptionText: cleanOptionText,
- isMatch: cleanOptionText === cleanCorrectContent
- });
-
- // 只在完全相等时选中选项
- if (cleanOptionText === cleanCorrectContent) {
- const input = option.querySelector('input[type="radio"]');
- if (input && !input.checked) {
- console.log('找到完全匹配选项,点击选择');
- input.click();
- selected = true;
- }
- }
- });
- }
-
- return selected;
- } catch (error) {
- console.error('选择答案出错:', error);
- return false;
- }
- }
-
- // 点击下一题
- function clickNext(answered) {
- try {
- // 获取下一题按钮
- const nextButton = Array.from(document.querySelectorAll('.subject-btns .subject-btn'))
- .find(button => button.textContent.trim() === '下一题');
-
- // 点击下一题按钮
- if (nextButton) {
- nextButton.click();
- return true;
- }
- return false;
- } catch (error) {
- console.error('点击下一题按钮出错:', error);
- return false;
- }
- }
-
- // 修改初始化部分
- function init() {
- // 等待页面完全加载
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', () => setTimeout(createPanel, 1000));
- } else {
- setTimeout(createPanel, 1000);
- }
- }
-
- // 使用 window.onload 确保所有资源都加载完成
- window.addEventListener('load', () => {
- setTimeout(init, 1000);
- });
- })();
-