您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
党旗飘飘学习助手增强版,支持自动刷课、自动答题,配合AI模型实现智能解答
// ==UserScript== // @name 在线培训助手增强版 // @namespace http://tampermonkey.net/ // @version 1.35 // @description 党旗飘飘学习助手增强版,支持自动刷课、自动答题,配合AI模型实现智能解答 // @author bugo // @match *://*/jjfz/play* // @match *://*/*/jjfz/play* // @match *://*/jjfz/lesson/exam* // @match *://*/*/jjfz/lesson/exam* // @match *://*/jjfz/exam_center/end_exam* // @match *://*/*/jjfz/exam_center/end_exam* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant GM_xmlhttpRequest // @run-at document-start // @license MIT // @connect * // ==/UserScript== (function() { 'use strict'; console.log("在线培训助手已加载"); // 检测当前页面类型 const isExamPage = window.location.href.includes('/lesson/exam') || window.location.href.includes('/exam_center/end_exam'); const isPlayPage = window.location.href.includes('/jjfz/play'); // 配置信息 const config = { // AI API设置 ai: { apiKey: GM_getValue('apiKey', ''), modelUrl: GM_getValue('modelUrl', 'https://deepseek.com/api/v1/chat/completions'), modelName: GM_getValue('modelName', 'deepseek-chat'), temperature: GM_getValue('temperature', 0.7), maxTokens: GM_getValue('maxTokens', 500), apiTimeout: 30000 // API请求超时时间(毫秒) }, // 课程设置 course: { autoPlay: GM_getValue('autoPlay', true), autoNext: true }, // UI设置 ui: { panelExpanded: GM_getValue('panelExpanded', true), activeTab: GM_getValue('activeTab', isExamPage ? 'exam' : 'course') } }; // 保存配置 function saveConfig() { try { // 保存API配置 GM_setValue('modelUrl', config.ai.modelUrl); GM_setValue('modelName', config.ai.modelName); GM_setValue('apiKey', config.ai.apiKey); GM_setValue('temperature', config.ai.temperature); GM_setValue('maxTokens', config.ai.maxTokens); GM_setValue('apiTimeout', config.ai.apiTimeout); // 保存课程配置 GM_setValue('autoPlay', config.course.autoPlay); // 保存UI配置 GM_setValue('panelExpanded', config.ui.panelExpanded); GM_setValue('activeTab', config.ui.activeTab); // 输出保存配置日志 console.log("配置已保存"); return true; } catch (error) { console.error("保存配置失败:", error); return false; } } // 工具函数 const utils = { // 获取视频元素 getVideo: () => document.querySelector("video"), // 获取资源ID getRid: () => { const html = document.documentElement.innerHTML; const match = html.match(/rid[\s:="']*(\d+)["'\s]/); return match ? match[1] : null; }, // 获取当前题目信息 getQuestionInfo: () => { try { // 获取题目文本 const questionTitle = document.querySelector('.exam_h2')?.textContent || ''; // 判断题目类型 let questionType = ''; if (document.querySelector('.e_cont_title span')?.textContent.includes('单选题')) { questionType = '单选题'; } else if (document.querySelector('.e_cont_title span')?.textContent.includes('多选题')) { questionType = '多选题'; } else if (document.querySelector('.e_cont_title span')?.textContent.includes('判断题')) { questionType = '判断题'; } else if (document.querySelector('.summary_question')) { questionType = '填空题'; } // 获取选项文本(如果有) const options = []; document.querySelectorAll('.answer_list li label, .answer_list_box li label').forEach(label => { options.push(label.textContent.trim()); }); // 获取当前题号 const questionNumber = document.querySelector('.exam_pages span')?.textContent?.split('/')[0] || ''; // 获取题目ID const questionId = document.querySelector('.answer_list ul')?.getAttribute('qid') || document.querySelector('.answer_list_box ul')?.getAttribute('qid') || document.querySelector('.exam_label_btn')?.getAttribute('data-val') || ''; // 获取当前题目索引 const questionIndex = document.querySelector('.answer_list ul')?.getAttribute('qindex') || document.querySelector('.answer_list_box ul')?.getAttribute('qindex') || document.querySelector('.exam_label_btn')?.getAttribute('data-index') || ''; return { title: questionTitle, type: questionType, options: options, number: questionNumber, id: questionId, index: questionIndex }; } catch (e) { console.error('获取题目信息失败:', e); return null; } }, // 清除所有定时器 clearAllTimers: () => { // 清除原页面定时器 clearInterval(window.timer); window.clearInterval(window.loop_flag); window.clearInterval(window.flag); // 清除我们自己的定时器 if (window.autoInterval) { clearInterval(window.autoInterval); window.autoInterval = null; } // 清除答题定时器 if (window.answerInterval) { clearInterval(window.answerInterval); window.answerInterval = null; } }, // 处理各种弹窗 handleDialogs: () => { document.querySelectorAll(".public_close, .public_cancel, .public_submit, .public_cont1").forEach(btn => { if (btn) { if (btn.textContent?.includes("继续观看") || btn.textContent?.includes("我知道了") || btn.textContent?.includes("继续")) { btn.click(); } } }); }, // 用于防止页面监测 preventDetection: () => { // 防止页面切换时暂停 const originAddListener = Document.prototype.addEventListener; Document.prototype.addEventListener = function(type, listener, options) { if (type === "visibilitychange") return; return originAddListener.call(this, type, listener, options); }; Object.defineProperty(document, 'hidden', { configurable: true, get: () => false }); Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'visible' }); } }; // AI接口 const ai = { // 调用AI API callAI: (question, options, type) => { return new Promise((resolve, reject) => { if (!config.ai.apiKey) { reject("请先设置API Key"); return; } // 构建prompt let prompt = `请帮我回答以下问题,直接给出答案选项编号,不要任何解释。\n问题: ${question}\n`; // 添加题型信息 prompt += `题型: ${type}\n\n`; // 只有单选题和多选题需要拼接选项 if ((type === '单选题' || type === '多选题') && options && options.length > 0) { prompt += "选项:\n"; // 根据题型设置选项前缀 options.forEach((option, index) => { // 单选题用A,B,C,D标记,多选题用数字标记 const prefix = type === '多选题' ? (index + 1) : String.fromCharCode(65 + index); prompt += `${prefix}. ${option}\n`; }); } else if (type === '判断题') { // 判断题直接提示回答正确或错误 prompt += "这是一道判断题,请判断题目描述是否正确。\n"; } else if (type === '填空题') { prompt += "这是一道填空题,请直接给出答案。\n"; } // 根据题型调整prompt的要求 if (type === '单选题') { prompt += "\n只需回答一个选项编号(A/B/C/D),不要任何多余文字。"; } else if (type === '多选题') { prompt += "\n回答格式: 数字序号,用逗号分隔,如'1,3,4',不要任何多余文字。"; } else if (type === '判断题') { prompt += "\n只需回答'正确'或'错误',不要任何多余文字。"; } else { prompt += "\n请直接给出答案,简明扼要。"; } console.log("发送AI请求: ", { url: config.ai.modelUrl, model: config.ai.modelName, prompt: prompt }); // 调用API GM_xmlhttpRequest({ method: "POST", url: config.ai.modelUrl, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${config.ai.apiKey}` }, data: JSON.stringify({ model: config.ai.modelName, messages: [ { role: "system", content: "你是一个擅长党建知识的考试助手,你的回答必须简洁,只提供答案,不解释原因。" }, { role: "user", content: prompt } ], temperature: config.ai.temperature, max_tokens: config.ai.maxTokens }), timeout: config.ai.apiTimeout, onload: function(response) { try { console.log("AI响应状态: ", response.status); // 处理非200响应 if (response.status !== 200) { reject(`API请求失败,状态码: ${response.status}, 信息: ${response.statusText}`); return; } const result = JSON.parse(response.responseText); if (result.choices && result.choices[0]) { const answer = result.choices[0].message.content.trim(); // 简单处理回答 let processedAnswer = answer; // 对单选题回答处理字母格式 if (type === '单选题') { // 提取第一个字母作为答案 const letterMatch = answer.match(/[A-Da-d]/); if (letterMatch) { processedAnswer = letterMatch[0].toUpperCase(); } } // 对多选题处理,确保格式正确 if (type === '多选题') { // 提取所有数字 const numbersMatch = answer.match(/\d+/g); if (numbersMatch) { processedAnswer = numbersMatch.join(','); } } // 处理判断题答案 if (type === '判断题') { // 使用更强大的判断逻辑 const lowerAnswer = answer.toLowerCase().trim(); // 判断"正确"的各种表达方式 if (lowerAnswer.includes('正确') || lowerAnswer.includes('对') || lowerAnswer.includes('√') || lowerAnswer.includes('true') || lowerAnswer.includes('right') || lowerAnswer === 'a' || lowerAnswer === 't' || lowerAnswer === 'yes' || lowerAnswer === 'y') { processedAnswer = '正确'; } // 判断"错误"的各种表达方式 else if (lowerAnswer.includes('错误') || lowerAnswer.includes('错') || lowerAnswer.includes('×') || lowerAnswer.includes('false') || lowerAnswer.includes('wrong') || lowerAnswer === 'b' || lowerAnswer === 'f' || lowerAnswer === 'no' || lowerAnswer === 'n') { processedAnswer = '错误'; } // 如果无法识别,默认为"错误" else { console.warn(`无法识别的判断题答案: "${answer}",默认处理为"错误"`); processedAnswer = '错误'; } } console.log("AI原始回答: ", answer); console.log("处理后回答: ", processedAnswer); resolve(processedAnswer); } else { reject("API返回格式错误"); } } catch (e) { reject("解析API响应失败: " + e.message); } }, onerror: function(error) { reject("API请求失败: " + error); }, ontimeout: function() { reject("API请求超时"); } }); }); }, // 获取答案(调用指定的AI模型) getAnswer: async (question) => { try { const info = utils.getQuestionInfo(); if (!info) { throw new Error("无法获取题目信息"); } // 记录到控制台 console.log(`正在解答题目: ${info.title}`); console.log(`题目类型: ${info.type}`); console.log(`选项: `, info.options); // 更新UI显示为等待状态 ui.updateAIAnswerStatus('waiting'); // 调用API获取答案 const answer = await ai.callAI(info.title, info.options, info.type); // 更新UI显示为成功状态 ui.updateAIAnswerStatus('success', answer); console.log(`AI回答: ${answer}`); return { answer: answer, info: info }; } catch (error) { console.error("获取答案失败:", error); // 更新UI显示为错误状态 ui.updateAIAnswerStatus('error', error.message); // 返回错误信息 return null; } } }; // 考试功能 const exam = { // 封装不同题型的答题函数 handlers: { // 处理单选题 handleSingleChoice: (answer, options) => { try { // 处理单选题,转换A,B,C,D为数字索引 let index = -1; // 支持多种格式的单选答案 if (/^[A-Da-d]$/.test(answer.trim())) { // 如果是A、B、C、D格式 index = answer.trim().toUpperCase().charCodeAt(0) - 65; // 'A'=0, 'B'=1, ... } else if (/^\d+$/.test(answer.trim())) { // 如果是数字格式 (1、2、3、4) index = parseInt(answer.trim()) - 1; } const labels = document.querySelectorAll('.answer_list li label'); if (index >= 0 && index < labels.length) { // 点击对应选项 console.log(`选择单选选项: ${index + 1}`); labels[index].click(); return true; } else { throw new Error(`无效的单选答案索引: ${answer} -> ${index}`); } } catch (error) { console.error("单选题处理失败:", error); throw error; } }, // 处理多选题 handleMultipleChoice: (answer, options) => { return new Promise((resolve, reject) => { try { // 处理多选题,答案格式为: "1,2,3" 或 "1,3,4" let selectedIndexes = []; // 支持多种格式的多选答案 if (answer.includes(',')) { // 处理以逗号分隔的格式 selectedIndexes = answer.split(',').map(num => { const trimmed = num.trim(); return /^\d+$/.test(trimmed) ? parseInt(trimmed) - 1 : -1; }).filter(idx => idx >= 0); } else if (/^[A-Za-z]+$/.test(answer.trim())) { // 处理字母格式 (如 "ACD") selectedIndexes = Array.from(answer.trim().toUpperCase()).map(char => { return char.charCodeAt(0) - 65; }).filter(idx => idx >= 0 && idx < 26); } else { // 尝试提取所有数字 const matches = answer.match(/\d+/g); if (matches) { selectedIndexes = matches.map(num => parseInt(num) - 1); } } const labels = document.querySelectorAll('.answer_list_box li label'); if (selectedIndexes.length === 0) { throw new Error(`无法从答案中提取选项: ${answer}`); } console.log(`选择多选选项: ${selectedIndexes.map(i => i + 1).join(',')}`); // 使用循环队列处理多选项的点击,避免递归调用可能导致的问题 let currentIndex = 0; // 创建间隔计时器,逐一点击选项 const clickInterval = setInterval(() => { if (currentIndex >= selectedIndexes.length) { // 所有选项都已点击完毕 clearInterval(clickInterval); console.log("多选题选项已全部选择完毕"); resolve(true); return; } const optionIndex = selectedIndexes[currentIndex]; if (optionIndex >= 0 && optionIndex < labels.length) { console.log(`选择多选选项 ${currentIndex + 1}/${selectedIndexes.length}: 选项=${optionIndex + 1}`); labels[optionIndex].click(); } else { console.warn(`无效的多选选项索引: ${optionIndex}`); } // 移至下一个选项 currentIndex++; }, 300); // 每300毫秒点击一个选项 } catch (error) { console.error("多选题处理失败:", error); reject(error); } }); }, // 处理判断题 handleJudgement: (answer, options) => { try { // 处理判断题 let isCorrect = false; // 使用更强大的判断逻辑 const lowerAnswer = answer.toLowerCase().trim(); // 判断"正确"的各种表达方式 if (lowerAnswer.includes('正确') || lowerAnswer.includes('对') || lowerAnswer.includes('√') || lowerAnswer.includes('true') || lowerAnswer.includes('right') || lowerAnswer === 'a' || lowerAnswer === 't' || lowerAnswer === 'yes' || lowerAnswer === 'y') { isCorrect = true; } // 其他情况视为"错误" // 获取所有选项 const labels = document.querySelectorAll('.answer_list li label'); // 默认假设第一个是"正确",第二个是"错误" let correctOptionIndex = -1; let wrongOptionIndex = -1; // 遍历选项文本,查找正确和错误选项 for (let i = 0; i < labels.length; i++) { const optionText = labels[i].textContent.trim().toLowerCase(); if (optionText.includes('正确') || optionText.includes('对') || optionText.includes('√') || optionText.includes('true') || optionText.includes('right') || optionText === 'a') { correctOptionIndex = i; } else if (optionText.includes('错误') || optionText.includes('错') || optionText.includes('×') || optionText.includes('false') || optionText.includes('wrong') || optionText === 'b') { wrongOptionIndex = i; } } // 如果没有找到明确的选项,使用默认方式处理 if (correctOptionIndex === -1) correctOptionIndex = 0; if (wrongOptionIndex === -1) wrongOptionIndex = 1; // 根据AI回答选择选项 const indexToClick = isCorrect ? correctOptionIndex : wrongOptionIndex; console.log(`选择判断: ${isCorrect ? '正确' : '错误'} (选项索引: ${indexToClick})`); // 点击对应选项 if (indexToClick >= 0 && indexToClick < labels.length) { labels[indexToClick].click(); return true; } else { throw new Error(`判断题选项不存在: ${indexToClick}`); } } catch (error) { console.error("判断题处理失败:", error); throw error; } }, // 处理填空题 handleFillBlank: (answer, options) => { try { const inputField = document.querySelector('.summary_question'); if (inputField) { inputField.value = answer; // 触发blur事件以保存答案 inputField.dispatchEvent(new Event('input')); inputField.dispatchEvent(new Event('change')); inputField.dispatchEvent(new Event('blur')); console.log(`填空: ${answer}`); return true; } else { throw new Error("未找到填空输入框"); } } catch (error) { console.error("填空题处理失败:", error); throw error; } } }, // 回答当前题目 answerCurrentQuestion: async () => { try { // 使用更新状态函数更新UI ui.updateAIAnswerStatus('waiting'); // 获取AI答案 const result = await ai.getAnswer(); if (!result) { throw new Error("获取答案失败"); } const { answer, info } = result; // 根据题型调用相应的处理函数 let success = false; switch (info.type) { case '单选题': success = exam.handlers.handleSingleChoice(answer, info.options); break; case '多选题': success = await exam.handlers.handleMultipleChoice(answer, info.options); break; case '判断题': success = exam.handlers.handleJudgement(answer, info.options); break; case '填空题': success = exam.handlers.handleFillBlank(answer, info.options); break; default: throw new Error(`未知题型: ${info.type}`); } if (!success) { throw new Error(`答题失败: ${info.type}`); } // 显示答题结果 ui.showMessage(`回答成功: ${answer}`, 'success'); return true; } catch (error) { // 使用更新状态函数更新错误状态 ui.updateAIAnswerStatus('error', error.message); ui.showMessage(`回答失败: ${error.message}`, 'error'); console.error("回答题目失败:", error); return false; } } }; // 课程功能 const course = { // 添加学习时长 - 参数为要添加的秒数 addStudyTime: (seconds) => { const rid = utils.getRid(); const video = utils.getVideo(); if (!video || !rid || seconds <= 0) return false; // 计算目标时间 const currentTime = video.currentTime || 0; const duration = video.duration || 600; // 减少2秒,等待网址自动上报完成事件 const targetTime = Math.min(duration-2, currentTime + seconds); // 发送请求并设置视频时间 let success = false; $.ajax({ type: "POST", cache: false, dataType: "json", url: "/jjfz/lesson/current_time", data: { rid: rid, time: targetTime, _xsrf: $(":input[name='_xsrf']").val() }, async: false, success: function(data) { if (data && data.code === 1) { try { video.currentTime = targetTime; success = true; } catch (e) { success = false; } } else { success = false; } }, error: function() { success = false; } }); return success; }, // 自动播放下一集 playNextVideo: () => { const current = document.querySelector('.video_red1')?.closest('li'); const next = current?.nextElementSibling; if (next && next.querySelector('a')) { next.querySelector('a').click(); return true; } else { GM_notification({ text: '✅ 本章播放完成,跳转课程列表页~', timeout: 4000 }); location.href = `${location.protocol}//${location.host}/jjfz/lesson`; return false; } }, // 处理视频结束事件 handleVideoEnded: () => { utils.clearAllTimers(); if (config.course.autoNext) { // 等待2秒,确保视频结束事件触发 setTimeout(course.playNextVideo, 2000); } }, // 启动自动学习 startAutoLearning: () => { if (!config.course.autoPlay || window.autoInterval) return; // 处理弹窗 utils.handleDialogs(); // 监听视频结束事件 const video = utils.getVideo(); if (video && !video.hasEndedListener) { video.addEventListener('ended', function() { console.log("视频播放结束"); course.handleVideoEnded(); }); video.hasEndedListener = true; } // 启动定时器 window.autoInterval = setInterval(() => { const video = utils.getVideo(); if (!video) return; // 尝试点击播放按钮 if (video.paused) { const playButton = document.querySelector('.plyr__controls [data-plyr="play"], .plyr__play-large, [aria-label="播放"]'); if (playButton) { playButton.click(); } else { // 触发点击事件 video.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); } video.play(); } // 自动处理弹窗 utils.handleDialogs(); // 更新界面 ui.updateCourseStatus(); // 检测视频是否接近结束但尚未触发ended事件 if (video.currentTime > video.duration * 0.98 && !video.ended) { video.currentTime = video.duration; // 强制跳到结尾触发ended事件 } }, 1500); }, // 停止自动学习 stopAutoLearning: () => { utils.clearAllTimers(); config.course.autoPlay = false; saveConfig(); ui.showMessage('自动学习已停止', 'info'); } }; // UI界面 const ui = { // 添加CSS样式 addStyles: () => { GM_addStyle(` /* 主面板样式 */ .helper-panel { position: fixed; top: 110px; left: 10px; background: #fff; border-radius: 4px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); z-index: 9999; padding: 12px; width: 400px; font-size: 14px; margin-top: -1px; max-height: 80vh; overflow-y: auto; } /* 面板标题 */ .helper-panel h3 { margin: 0 0 10px 0; padding-bottom: 8px; border-bottom: 1px solid #eee; text-align: center; font-size: 16px; color: #e61d1d; font-weight: bold; } /* 标签页 */ .helper-tabs { display: flex; margin-bottom: 10px; border-bottom: 1px solid #eee; } .helper-tab { flex: 1; text-align: center; padding: 6px 0; cursor: pointer; border-bottom: 2px solid transparent; font-weight: 500; transition: all 0.2s ease; } .helper-tab:hover { background-color: #f5f5f5; } .helper-tab.active { color: #2196F3; border-bottom: 2px solid #2196F3; font-weight: bold; } /* 内容区域 */ .helper-content { display: none; padding: 5px 0; } .helper-content.active { display: block; } /* 选项区域 */ .helper-option { margin-bottom: 12px; } .helper-option label { display: block; margin-bottom: 4px; font-weight: bold; font-size: 13px; } /* 复选框包装 */ .checkbox-wrapper { display: flex; align-items: center; margin: 5px 0; padding: 2px 0; } .checkbox-wrapper input[type="checkbox"] { width: auto; margin: 0 6px 0 0; cursor: pointer; } .checkbox-wrapper label { margin: 0; font-weight: normal; cursor: pointer; } /* 表单元素 */ .helper-panel select, .helper-panel input, .helper-panel button { width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 3px; margin-bottom: 6px; cursor: pointer; font-size: 13px; } .helper-panel select, .helper-panel input[type="text"], .helper-panel input[type="password"], .helper-panel input[type="number"] { cursor: auto; transition: border-color 0.2s ease; } .helper-panel select:focus, .helper-panel input[type="text"]:focus, .helper-panel input[type="password"]:focus, .helper-panel input[type="number"]:focus { border-color: #2196F3; outline: none; } /* 按钮样式 */ .helper-panel button { background: #2196F3; color: white; border: none; font-weight: bold; transition: background 0.2s ease, transform 0.1s ease; } .helper-panel button:hover { background: #1976D2; } .helper-panel button:active { transform: scale(0.98); } .helper-panel button.stop { background: #F44336; } .helper-panel button.stop:hover { background: #D32F2F; } /* 信息显示区域 */ .helper-status { background: #f8f8f8; padding: 8px 10px; border-radius: 3px; border: 1px solid #eee; margin-top: 5px; } .helper-status p { margin: 4px 0; font-size: 13px; display: flex; justify-content: space-between; } .helper-status p span { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 切换按钮 */ .helper-toggle { position: fixed; top: 80px; left: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; z-index: 10000; font-weight: bold; width: 80px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); transition: background 0.2s ease; } .helper-toggle:hover { background: #1976D2; } /* 消息提示 */ .helper-message { position: fixed; top: 10px; right: 10px; padding: 10px 15px; border-radius: 4px; z-index: 10001; color: #fff; font-weight: bold; transition: all 0.3s ease; opacity: 0; transform: translateY(-20px); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); } .helper-message.show { opacity: 1; transform: translateY(0); } /* 不同类型的消息颜色 */ .helper-message.success { background: #4CAF50; } .helper-message.error { background: #F44336; } .helper-message.info { background: #2196F3; } .helper-message.warning { background: #FFC107; color: #333; } /* 高亮答案 */ #helper-ai-answer { font-family: monospace; letter-spacing: 0.5px; } /* 设置模块内排列 */ .settings-row { display: flex; gap: 8px; margin-top: 8px; } .settings-col { flex: 1; } .settings-col label { font-weight: normal !important; font-size: 12px !important; color: #555; } `); }, // 显示消息 showMessage: (text, type = 'info') => { // 移除现有的消息 const oldMessage = document.querySelector('.helper-message'); if (oldMessage) { oldMessage.remove(); } // 创建新消息 const message = document.createElement('div'); message.className = `helper-message ${type}`; message.textContent = text; document.body.appendChild(message); // 显示动画 setTimeout(() => { message.classList.add('show'); }, 10); // 自动消失 setTimeout(() => { message.classList.remove('show'); setTimeout(() => { message.remove(); }, 300); }, 1500); }, // 更新课程状态 updateCourseStatus: () => { if (!isPlayPage) return; const video = utils.getVideo(); if (!video) return; // 当前视频标题 const videoTitle = document.querySelector('.video_red1 a')?.textContent.trim() || "未知"; // 计算进度 - 使用与原脚本相同的方式 const total = document.querySelectorAll('.video_lists ul li').length; const current = Array.from(document.querySelectorAll('.video_lists ul li')).findIndex(li => li.classList.contains('video_red1')) + 1; // 更新到UI const videoTitleEl = document.querySelector('#helper-current-video'); const progressEl = document.querySelector('#helper-video-progress'); if (videoTitleEl) videoTitleEl.textContent = videoTitle; if (progressEl) progressEl.textContent = `${current}/${total}`; // 记录日志便于调试 console.log(`学习进度: ${current}/${total}`); }, // 更新问题状态(考试模式) updateExamStatus: () => { if (!isExamPage) return; const info = utils.getQuestionInfo(); if (!info) return; // 检查题目是否变化 const currentQuestionId = info.id; if (window.lastQuestionId !== currentQuestionId) { // 题目变化了,重置回答 ui.updateAIAnswerStatus('reset'); window.lastQuestionId = currentQuestionId; } // 更新到UI const currentQuestionEl = document.querySelector('#helper-current-question'); const questionTypeEl = document.querySelector('#helper-question-type'); if (currentQuestionEl) currentQuestionEl.textContent = info.title?.substring(0, 30) + '...'; if (questionTypeEl) questionTypeEl.textContent = info.type; }, // 创建面板 createPanel: () => { // 创建切换按钮 const toggleButton = document.createElement('button'); toggleButton.className = 'helper-toggle'; toggleButton.textContent = config.ui.panelExpanded ? '收起' : '展开'; toggleButton.onclick = function() { const panel = document.querySelector('.helper-panel'); config.ui.panelExpanded = !config.ui.panelExpanded; panel.style.display = config.ui.panelExpanded ? 'block' : 'none'; this.textContent = config.ui.panelExpanded ? '收起' : '展开'; saveConfig(); }; document.body.appendChild(toggleButton); // 创建面板容器 const panel = document.createElement('div'); panel.className = 'helper-panel'; panel.style.display = config.ui.panelExpanded ? 'block' : 'none'; document.body.appendChild(panel); // 面板标题 const title = document.createElement('h3'); title.textContent = '在线培训助手'; panel.appendChild(title); // 创建标签页 const tabs = document.createElement('div'); tabs.className = 'helper-tabs'; panel.appendChild(tabs); // 根据页面类型添加对应标签 // 课程标签 - 仅在课程页面显示 if (isPlayPage) { const courseTab = document.createElement('div'); courseTab.className = `helper-tab ${config.ui.activeTab === 'course' ? 'active' : ''}`; courseTab.textContent = '课程'; courseTab.onclick = function() { if (config.ui.activeTab !== 'course') { document.querySelectorAll('.helper-tab').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.helper-content').forEach(content => content.classList.remove('active')); this.classList.add('active'); document.querySelector('#helper-course-content').classList.add('active'); config.ui.activeTab = 'course'; saveConfig(); } }; tabs.appendChild(courseTab); // 如果在课程页面,但活动标签不是课程,则自动设置为课程 if (config.ui.activeTab !== 'course' && config.ui.activeTab !== 'settings') { config.ui.activeTab = 'course'; saveConfig(); } } // 考试标签 - 仅在考试页面显示 if (isExamPage) { const examTab = document.createElement('div'); examTab.className = `helper-tab ${config.ui.activeTab === 'exam' ? 'active' : ''}`; examTab.textContent = '考试'; examTab.onclick = function() { if (config.ui.activeTab !== 'exam') { document.querySelectorAll('.helper-tab').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.helper-content').forEach(content => content.classList.remove('active')); this.classList.add('active'); document.querySelector('#helper-exam-content').classList.add('active'); config.ui.activeTab = 'exam'; saveConfig(); } }; tabs.appendChild(examTab); // 如果在考试页面,但活动标签不是考试,则自动设置为考试 if (config.ui.activeTab !== 'exam' && config.ui.activeTab !== 'settings') { config.ui.activeTab = 'exam'; saveConfig(); } } // 设置标签 - 始终显示 const settingsTab = document.createElement('div'); settingsTab.className = `helper-tab ${config.ui.activeTab === 'settings' ? 'active' : ''}`; settingsTab.textContent = '设置'; settingsTab.onclick = function() { if (config.ui.activeTab !== 'settings') { document.querySelectorAll('.helper-tab').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.helper-content').forEach(content => content.classList.remove('active')); this.classList.add('active'); document.querySelector('#helper-settings-content').classList.add('active'); config.ui.activeTab = 'settings'; saveConfig(); } }; tabs.appendChild(settingsTab); // 创建内容区域 // 1. 课程内容 - 仅在课程页面创建 if (isPlayPage) { const courseContent = document.createElement('div'); courseContent.id = 'helper-course-content'; courseContent.className = `helper-content ${config.ui.activeTab === 'course' ? 'active' : ''}`; panel.appendChild(courseContent); // 自动学习开关 const autoLearningOption = document.createElement('div'); autoLearningOption.className = 'helper-option'; autoLearningOption.innerHTML = ` <button id="helper-toggle-auto" class="${config.course.autoPlay ? 'stop' : ''}">${config.course.autoPlay ? '⏸️ 暂停刷课' : '▶️ 开始刷课'}</button> `; courseContent.appendChild(autoLearningOption); // 快进控制 const jumpOption = document.createElement('div'); jumpOption.className = 'helper-option'; jumpOption.innerHTML = ` <label>跳转控制</label> <div style="display:flex;gap:5px;"> <input type="number" id="helper-jump-time" value="30" min="1" max="3600" style="flex:2;"> <button id="helper-jump-btn" style="flex:1;">快进(秒)⏩</button> </div> `; courseContent.appendChild(jumpOption); // 课程状态 const courseStatus = document.createElement('div'); courseStatus.className = 'helper-status'; courseStatus.innerHTML = ` <p>当前视频: <span id="helper-current-video">获取中...</span></p> <p>学习进度: <span id="helper-video-progress">0/0</span></p> `; courseContent.appendChild(courseStatus); } // 2. 考试内容 - 仅在考试页面创建 if (isExamPage) { const examContent = document.createElement('div'); examContent.id = 'helper-exam-content'; examContent.className = `helper-content ${config.ui.activeTab === 'exam' ? 'active' : ''}`; panel.appendChild(examContent); // 搜题和选择答案按钮 const examButtonsOption = document.createElement('div'); examButtonsOption.className = 'helper-option'; examButtonsOption.innerHTML = ` <button id="helper-manual-answer">🤖 搜索并选择答案</button> `; examContent.appendChild(examButtonsOption); // 考试状态 const examStatus = document.createElement('div'); examStatus.className = 'helper-status'; examStatus.innerHTML = ` <p>当前题目: <span id="helper-current-question">等待...</span></p> <p>题目类型: <span id="helper-question-type">-</span></p> <p>模型回答: <span id="helper-ai-answer">-</span></p> `; examContent.appendChild(examStatus); } // 3. 设置内容 - 总是创建 const settingsContent = document.createElement('div'); settingsContent.id = 'helper-settings-content'; settingsContent.className = `helper-content ${config.ui.activeTab === 'settings' ? 'active' : ''}`; panel.appendChild(settingsContent); // AI设置 const aiSettingsOption = document.createElement('div'); aiSettingsOption.className = 'helper-option'; aiSettingsOption.innerHTML = ` <label>AI接口设置</label> <label style="font-weight:normal;margin-top:5px;font-size:12px;">接口URL:</label> <input type="text" id="helper-base-url" placeholder="完整API地址" value="${config.ai.modelUrl || ''}"> <label style="font-weight:normal;margin-top:5px;font-size:12px;">模型名称:</label> <input type="text" id="helper-model-name" placeholder="如: gpt-4" value="${config.ai.modelName || ''}"> <label style="font-weight:normal;margin-top:5px;font-size:12px;">API密钥:</label> <input type="password" id="helper-api-key" placeholder="API Key" value="${config.ai.apiKey || ''}"> <div class="settings-row"> <div class="settings-col"> <label>温度:</label> <input type="number" id="helper-temperature" value="${config.ai.temperature}" min="0" max="1" step="0.1"> </div> <div class="settings-col"> <label>Token:</label> <input type="number" id="helper-max-tokens" value="${config.ai.maxTokens}" min="100" max="8000" step="100"> </div> <div class="settings-col"> <label>超时(ms):</label> <input type="number" id="helper-api-timeout" value="${config.ai.apiTimeout}" min="5000" max="120000" step="1000"> </div> </div> `; settingsContent.appendChild(aiSettingsOption); // 保存设置按钮 const saveSettingsOption = document.createElement('div'); saveSettingsOption.className = 'helper-option'; saveSettingsOption.innerHTML = ` <button id="helper-save-settings">💾 保存设置</button> `; settingsContent.appendChild(saveSettingsOption); }, // 初始化事件监听 - 根据页面类型添加对应事件 setupEventListeners: () => { // 课程页面事件 - 仅在课程页面添加 if (isPlayPage) { // 切换自动学习按钮 document.getElementById('helper-toggle-auto')?.addEventListener('click', function() { if (config.course.autoPlay) { course.stopAutoLearning(); this.textContent = '▶️ 开始刷课'; this.classList.remove('stop'); } else { config.course.autoPlay = true; saveConfig(); course.startAutoLearning(); this.textContent = '⏸️ 暂停刷课'; this.classList.add('stop'); } }); // 快进按钮 document.getElementById('helper-jump-btn')?.addEventListener('click', function() { const seconds = parseInt(document.getElementById('helper-jump-time')?.value) || 30; if (course.addStudyTime(seconds)) { ui.showMessage(`快进${seconds}秒成功`, 'success'); } else { ui.showMessage('快进失败,请检查视频状态', 'error'); } }); } // 考试页面事件 - 仅在考试页面添加 if (isExamPage) { // 搜题并选择答案按钮 document.getElementById('helper-manual-answer')?.addEventListener('click', function() { exam.answerCurrentQuestion(); }); } // 设置页面事件 - 总是添加 // 保存设置按钮 document.getElementById('helper-save-settings')?.addEventListener('click', function() { try { // 获取设置值 const baseUrl = document.getElementById('helper-base-url')?.value || ''; const modelName = document.getElementById('helper-model-name')?.value || ''; const apiKey = document.getElementById('helper-api-key')?.value || ''; const temperature = parseFloat(document.getElementById('helper-temperature')?.value) || 0.7; const maxTokens = parseInt(document.getElementById('helper-max-tokens')?.value) || 500; const apiTimeout = parseInt(document.getElementById('helper-api-timeout')?.value) || 30000; // 更新配置 config.ai.modelUrl = baseUrl; config.ai.modelName = modelName; config.ai.apiKey = apiKey; config.ai.temperature = temperature; config.ai.maxTokens = maxTokens; config.ai.apiTimeout = apiTimeout; // 保存设置 if (saveConfig()) { ui.showMessage('设置已保存', 'success'); } else { ui.showMessage('设置保存失败', 'error'); } } catch (error) { ui.showMessage(`设置保存出错: ${error.message}`, 'error'); console.error('保存设置错误:', error); } }); }, // 更新AI回答状态 updateAIAnswerStatus: (status, answer = '', isError = false) => { const aiAnswerEl = document.querySelector('#helper-ai-answer'); if (!aiAnswerEl) return; // 清除之前的样式 aiAnswerEl.style.color = ''; aiAnswerEl.style.fontWeight = 'normal'; switch (status) { case 'waiting': aiAnswerEl.textContent = "获取中..."; aiAnswerEl.style.color = "#2196F3"; break; case 'success': aiAnswerEl.textContent = answer || "获取成功"; aiAnswerEl.style.color = "#e61d1d"; aiAnswerEl.style.fontWeight = "bold"; break; case 'error': aiAnswerEl.textContent = answer ? `错误: ${answer}` : "获取失败"; aiAnswerEl.style.color = "#F44336"; break; case 'reset': aiAnswerEl.textContent = "-"; break; default: aiAnswerEl.textContent = answer || "-"; if (isError) { aiAnswerEl.style.color = "#F44336"; } } } }; // 初始化脚本 function init() { // 添加样式 ui.addStyles(); // 防止页面监测 utils.preventDetection(); // 创建UI界面 ui.createPanel(); // 设置事件监听 ui.setupEventListeners(); // 根据页面类型初始化不同功能 if (isPlayPage) { // 课程页面,检查是否要自动开始学习 if (config.course.autoPlay) { setTimeout(() => { course.startAutoLearning(); }, 1000); } // 定期更新课程状态 setInterval(() => { ui.updateCourseStatus(); }, 1000); } else if (isExamPage) { // 考试页面 // 定期更新考试状态 setInterval(() => { ui.updateExamStatus(); }, 1000); // 绑定答题卡点击事件 setTimeout(() => { // 为答题卡中的题目添加点击事件 document.querySelectorAll('.exam_ul li').forEach(li => { li.addEventListener('click', () => { // 题目切换时重置答案显示 setTimeout(() => { ui.updateAIAnswerStatus('reset'); }, 200); }); }); // 监听下一题和上一题按钮 const nextBtn = document.querySelector('#next_question'); const prevBtn = document.querySelector('#prev_question'); if (nextBtn) { nextBtn.addEventListener('click', () => { ui.updateAIAnswerStatus('reset'); }); } if (prevBtn) { prevBtn.addEventListener('click', () => { ui.updateAIAnswerStatus('reset'); }); } }, 2000); // 等待页面元素完全加载 } } init(); })();