// ==UserScript== // @name 【限时免费使用题库】超星学习通学起plus视频作业助手 // @namespace http://tampermonkey.net/ // @version 0.3.5.1 // @description 【限时免费使用题库】【超星学习通】【学起plus】的自动挂机视频、章节测试、文档、直播、作业、考试;百亿题库;正确率高,限时免费使用 // @author 艾凌科技工作室 // @match *://exam.chinaedu.net/* // @match *://mooc1-2.chaoxing.com/exam-ans/mooc2/exam/* // @match *://mooc1.chaoxing.com/mooc-ans/mooc2/work/* // @match *://mooc1-api.chaoxing.com/exam-ans/mooc2/exam* // @match *://mooc1.chaoxing.com/mycourse/studentstudy* // @match https://mooc1.chaoxing.com/mooc-ans/knowledge/* // @require https://scriptcat.org/lib/668/1.0/TyprMd5.js // @resource Table https://www.forestpolice.org/ttf/2.0/table.json // @match https://mooc1.mtc.edu.cn/* // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant unsafeWindow // @connect * // ==/UserScript== (function () { 'use strict'; Object.defineProperty(HTMLMediaElement.prototype, 'muted', { get() { return false; }, set(value) { this._actualMuted = value; return true; } }); // 拦截 visibilitychange 事件 document.addEventListener('visibilitychange', function (e) { e.stopImmediatePropagation(); }, true); // 拦截 blur(失焦)事件 window.addEventListener('blur', function (e) { e.stopImmediatePropagation(); }, true); // 拦截 focus 事件(有些页面可能反复检测) window.addEventListener('focus', function (e) { e.stopImmediatePropagation(); }, true); // 拦截 mouseout(鼠标离开窗口)事件 window.addEventListener('mouseout', function (e) { if (!e.relatedTarget && !e.toElement) { e.stopImmediatePropagation(); } }, true); // 模拟页面始终可见 const fakeVisibility = () => 'visible'; const fakeHidden = () => false; try { Object.defineProperty(document, 'visibilityState', { get: fakeVisibility, configurable: true }); Object.defineProperty(document, 'hidden', { get: fakeHidden, configurable: true }); } catch (e) { console.warn('[模拟失败] 无法重定义 visibilityState / hidden:', e); } // 强制页面焦点(有些检测用 document.hasFocus) document.hasFocus = () => true; // 使用unsafeWindow来访问页面的真实window对象 const pageWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; const pageDocument = pageWindow.document; // ========================================= // 1. 解除网站的控制台限制(老版本正确做法) // ========================================= // 阻止Piwik统计脚本加载 pageWindow._paq = []; // 重写document.createElement方法以拦截piwik.js加载 const originalCreateElement = pageDocument.createElement; pageDocument.createElement = function (tagName) { if (tagName.toLowerCase() === 'script') { const script = originalCreateElement.call(pageDocument, tagName); const originalSrcSetter = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src').set; Object.defineProperty(script, 'src', { set: function (value) { if (value.includes('piwik.js')) { return; // 阻止设置src } originalSrcSetter.call(this, value); }, configurable: true }); return script; } return originalCreateElement.call(pageDocument, tagName); }; // 解除键盘事件限制 pageDocument.onkeydown = null; pageDocument.addEventListener('keydown', function (e) { // 阻止网站检测F12、Ctrl+Shift+I等 if (e.keyCode === 123 || (e.ctrlKey && e.shiftKey && e.keyCode === 73) || (e.shiftKey && e.keyCode === 121)) { e.stopImmediatePropagation(); return; } }, true); // 解除鼠标右键限制 pageWindow.oncontextmenu = null; pageWindow.addEventListener('contextmenu', function (e) { e.stopImmediatePropagation(); }, true); // 禁用网站的控制台检测定时器 const originalSetInterval = pageWindow.setInterval; pageWindow.setInterval = function (callback, interval) { // 检查是否是控制台检测函数 const callbackStr = callback.toString(); const detectionKeywords = [ 'window.outerWidth', 'window.outerHeight', 'detectZoom', 'getBrowserType', '请勿打开控制台' ]; // 如果检测到控制台检测函数,阻止其执行 if (detectionKeywords.some(keyword => callbackStr.includes(keyword))) { return -1; // 返回无效的定时器ID } return originalSetInterval.apply(this, arguments); }; // 重写alert函数,阻止"请勿打开控制台"弹窗 pageWindow.alert = function (message) { if (message && message.includes("请勿打开控制台")) { return; } // 对于其他alert,正常显示(可选) // return Function.prototype.bind.call(originalAlert, pageWindow); }; // 防止网站关闭窗口 pageWindow.close = function () { // 阻止窗口关闭 }; // 保护console对象 Object.defineProperty(pageWindow, 'console', { value: pageWindow.console, writable: false, configurable: false }); const SERVER_CONFIG = { apiUrl: 'https://www.toptk.xyz/api', answerApiUrl: 'https://www.toptk.xyz/api', timeout: 30000 }; const GLOBAL_STATE = { isAnswering: false, isChapterTesting: false, lastAnswerTime: 0 }; const SITES = { XUEQI: { name: '学起', host: 'exam.chinaedu.net', getQuestions: getXueqiQuestions, selectAnswer: selectXueqiAnswer, }, CHAOXING: { name: '超星学习通', host: 'mooc1.chaoxing.com', getQuestions: getChaoxingQuestions, selectAnswer: selectChaoxingAnswer, }, CHAOXING_STUDY: { name: '超星学习通-课程学习', host: 'mooc1.chaoxing.com', path: '/mycourse/studentstudy', autoStudy: true, getQuestions: () => [], selectAnswer: () => false, } }; let currentSite = null; function detectSite() { const currentHost = window.location.hostname; const currentUrl = window.location.href; // 优先通过URL精确检测超星学习页面 if (currentUrl.includes('/mycourse/studentstudy')) { currentSite = SITES.CHAOXING_STUDY; logMessage(`[站点检测] 检测到学习页面: ${currentSite.name}`, 'success'); return currentSite; } // 精确匹配其他站点(排除学习页面) for (const key in SITES) { if (SITES[key].autoStudy) continue; // 跳过学习页面,已在上面处理 if (currentHost.includes(SITES[key].host)) { currentSite = SITES[key]; logMessage(`[站点检测] 已识别: ${currentSite.name}`, 'success'); return currentSite; } } // 模糊匹配和页面特征检测 // 通过DOM特征检测超星学习页面(作为备用) if (currentHost.includes('chaoxing') && !currentUrl.includes('/exam-ans/') && !currentUrl.includes('/mooc-ans/')) { // 检测学习页面的特征元素 const hasJobIcon = document.querySelector('.ans-job-icon'); const hasNextButton = document.querySelector('#prevNextFocusNext'); const hasCourseMain = document.querySelector('.course_main'); const hasVideoFrame = document.querySelector('iframe.ans-attach-online'); if (hasJobIcon || hasNextButton || hasCourseMain || hasVideoFrame) { currentSite = SITES.CHAOXING_STUDY; logMessage(`[站点检测] 通过DOM检测到学习页面: ${currentSite.name}`, 'success'); return currentSite; } } // 检测超星学习通特征(包括考试和作业) if (currentHost.includes('chaoxing') || currentUrl.includes('chaoxing') || document.querySelector('.questionLi') || document.querySelector('.mark_name') || document.querySelector('[typename]') || // 作业界面特征 document.querySelector('.workTextWrap') || // 作业界面特征 document.title.includes('超星') || document.title.includes('学习通') || currentUrl.includes('work') || // 作业URL特征 currentUrl.includes('exam')) { // 考试URL特征 currentSite = SITES.CHAOXING; // 根据URL路径精确检测页面类型 let pageType = '未知'; if (currentUrl.includes('/exam-ans/mooc2/exam/')) { pageType = '考试'; currentSite.pageType = 'exam'; } else if (currentUrl.includes('/mooc-ans/mooc2/work/')) { pageType = '作业'; currentSite.pageType = 'homework'; } else if (currentUrl.includes('/mooc-ans/work/doHomeWorkNew/')) { pageType = '章节测验'; currentSite.pageType = 'chapter_test'; } else if (currentUrl.includes('/mooc-ans/api/work/')) { pageType = '章节测验'; currentSite.pageType = 'chapter_test'; } else if (currentUrl.includes('/ananas/modules/work/')) { pageType = '章节测验'; currentSite.pageType = 'chapter_test'; } else { // 降级检测:通过DOM元素判断 const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null; pageType = isHomeworkPage ? '作业' : '考试'; currentSite.pageType = isHomeworkPage ? 'homework' : 'exam'; } logMessage(`[站点检测] 通过特征识别: ${currentSite.name} - ${pageType}页面`, 'success'); return currentSite; } // 检测学起特征 if (currentHost.includes('chinaedu') || currentUrl.includes('exam') || document.querySelector('.questionItem') || document.querySelector('.queStemC') || document.title.includes('学起') || document.title.includes('考试')) { currentSite = SITES.XUEQI; logMessage(`[站点检测] 通过特征识别: ${currentSite.name}`, 'success'); return currentSite; } // 通用检测:查找题目相关元素 const hasQuestionElements = document.querySelector('[class*="question"], [id*="question"], .exam, .test, .quiz'); if (hasQuestionElements) { currentSite = SITES.CHAOXING; // 默认使用超星解析器,因为它更通用 logMessage(`[站点检测] 通用题目页面,使用: ${currentSite.name}`, 'warning'); return currentSite; } currentSite = SITES.XUEQI; logMessage(`[站点检测] 未识别当前站点, 使用默认解析器: ${currentSite.name}`, 'warning'); return currentSite; } function gmFetch(url, options = {}) { return new Promise((resolve, reject) => { const { method = 'GET', headers = {}, body = null, timeout = SERVER_CONFIG.timeout } = options; GM_xmlhttpRequest({ method: method, url: url, headers: headers, data: body, timeout: timeout, onload: function (response) { const result = { ok: response.status >= 200 && response.status < 300, status: response.status, statusText: response.statusText, json: () => { try { return Promise.resolve(JSON.parse(response.responseText)); } catch (error) { return Promise.reject(new Error(`Invalid JSON response: ${error.message}`)); } }, text: () => Promise.resolve(response.responseText) }; resolve(result); }, onerror: function (error) { reject(new Error(`Request failed: ${error.error || 'Network error'}`)); }, ontimeout: function () { reject(new Error('Request timeout')); } }); }); } const TokenManager = { TOKEN_KEY: 'user_token', _requestCache: new Map(), _lastRequestTime: 0, _minRequestInterval: 500, async _throttleRequest(key, requestFn, cacheTime = 30000) { const now = Date.now(); if (this._requestCache.has(key)) { const cached = this._requestCache.get(key); if (now - cached.timestamp < cacheTime) { return cached.result; } } let actualInterval = this._minRequestInterval; if (key.includes('validate') || key.includes('verify')) { actualInterval = 200; } else if (key.includes('check') || key.includes('status')) { actualInterval = 100; } const timeSinceLastRequest = now - this._lastRequestTime; if (timeSinceLastRequest < actualInterval) { const waitTime = actualInterval - timeSinceLastRequest; await new Promise(resolve => setTimeout(resolve, waitTime)); } this._lastRequestTime = Date.now(); try { const result = await requestFn(); this._requestCache.set(key, { result: result, timestamp: Date.now() }); return result; } catch (error) { if (!error.message.includes('429') && !error.message.includes('网络')) { this._requestCache.delete(key); } throw error; } }, getToken() { return localStorage.getItem(this.TOKEN_KEY); }, setToken(token) { localStorage.setItem(this.TOKEN_KEY, token); }, clearToken() { localStorage.removeItem(this.TOKEN_KEY); this._requestCache.clear(); }, async checkVisitorStatus() { const cacheKey = 'check_visitor_status'; return await this._throttleRequest(cacheKey, async () => { const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/check-visitor`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, timeout: SERVER_CONFIG.timeout }); if (!response.ok) { throw new Error(`检测访问者状态失败: ${response.status} ${response.statusText}`); } const result = await response.json(); if (result.success) { // 首次访问,自动创建Token if (result.data.isNewUser && result.data.userToken) { this.setToken(result.data.userToken); return { success: true, hasToken: true, token: result.data.userToken, userInfo: result.data.userInfo, message: result.data.message }; } // 非首次访问,已有Token if (!result.data.isNewUser && result.data.userToken) { this.setToken(result.data.userToken); return { success: true, hasToken: true, token: result.data.userToken, userInfo: result.data.userInfo, message: result.data.message }; } } if (result.data && result.data.needsToken) { return { success: false, needsToken: true, message: result.data.message || '请输入您的用户Token' }; } throw new Error(result.message || '检测访问者状态失败'); }, 120000); }, async verifyUserToken(userToken) { const cacheKey = `verify_${userToken.substring(0, 16)}`; return await this._throttleRequest(cacheKey, async () => { const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/verify-token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ userToken: userToken }), timeout: SERVER_CONFIG.timeout }); if (!response.ok) { let errorMessage = `Token验证失败: ${response.status} ${response.statusText}`; try { const result = await response.json(); errorMessage = result.message || errorMessage; } catch (parseError) { try { const errorText = await response.text(); errorMessage = errorText || errorMessage; } catch (textError) { // 忽略获取错误文本的失败 } } throw new Error(errorMessage); } const result = await response.json(); if (!result.success) { const errorMessage = result.message || 'Token验证失败'; throw new Error(errorMessage); } this.setToken(result.data.userToken); return { success: true, token: result.data.userToken, userInfo: result.data.userInfo, message: result.data.message }; }, 300000); // 缓存5分钟 }, // 提示用户输入Token async promptUserToken() { try { const userToken = prompt( '🔐 请输入您的用户Token\n\n' + '如果您是首次使用,系统会在首次访问时自动为您创建Token。\n' + '如果您已有Token,请输入完整的64位Token字符串:' ); if (!userToken) { throw new Error('用户取消输入Token'); } if (userToken.length !== 64) { throw new Error('Token格式不正确,应为64位字符串'); } return await this.verifyUserToken(userToken); } catch (error) { throw error; } }, // 内部验证Token有效性(带节流) async _validateToken(token) { const cacheKey = `validate_${token.substring(0, 16)}`; return await this._throttleRequest(cacheKey, async () => { const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/info`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-User-Token': token }, timeout: SERVER_CONFIG.timeout }); if (!response.ok) { return false; } const result = await response.json(); return result.success; }, 60000); // 缓存60秒 }, async getValidToken() { let token = this.getToken(); // 如果有token,验证有效性 if (token) { const isValid = await this._validateToken(token); if (isValid) { return token; } this.clearToken(); } try { const result = await this.initialize(); if (result.success && result.hasToken) { return this.getToken(); // 返回初始化后的token } } catch (error) { } throw new Error('Token获取失败,请刷新页面重试'); }, async getUserInfo() { try { const token = await this.getValidToken(); const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/info`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, timeout: SERVER_CONFIG.timeout }); if (!response.ok) { throw new Error(`获取用户信息失败: ${response.status} ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '获取用户信息失败'); } return result.userInfo; } catch (error) { throw error; } }, async initialize() { try { const storedToken = this.getToken(); if (storedToken) { const isValid = await this._validateToken(storedToken); if (isValid) { return { success: true, hasToken: true, token: storedToken, message: '欢迎回来!Token已自动加载。' }; } else { this.clearToken(); } } return await this.showTokenSelectionDialog(); } catch (error) { return { success: false, error: error.message, message: '初始化失败,请刷新页面重试' }; } }, async showTokenSelectionDialog() { return new Promise((resolve) => { const dialogHTML = `

🎯 TK星球答题系统

请选择您的Token获取方式:

`; document.body.insertAdjacentHTML('beforeend', dialogHTML); const dialog = document.getElementById('token-dialog'); const createBtn = document.getElementById('create-new-token'); const inputBtn = document.getElementById('input-existing-token'); const inputArea = document.getElementById('token-input-area'); const tokenInput = document.getElementById('token-input'); const verifyBtn = document.getElementById('verify-token'); const cancelBtn = document.getElementById('cancel-input'); const messageDiv = document.getElementById('dialog-message'); const showMessage = (message, type = 'info') => { messageDiv.style.display = 'block'; messageDiv.textContent = message; if (type === 'error') { messageDiv.style.background = '#f8d7da'; messageDiv.style.color = '#721c24'; messageDiv.style.border = '1px solid #f5c6cb'; } else if (type === 'success') { messageDiv.style.background = '#d4edda'; messageDiv.style.color = '#155724'; messageDiv.style.border = '1px solid #c3e6cb'; } else { messageDiv.style.background = '#d1ecf1'; messageDiv.style.color = '#0c5460'; messageDiv.style.border = '1px solid #bee5eb'; } }; const closeDialog = () => { dialog.remove(); }; createBtn.addEventListener('click', async () => { try { createBtn.disabled = true; createBtn.textContent = '生成中...'; showMessage('正在生成新Token...', 'info'); const result = await this.checkVisitorStatus(); if (result.success && result.hasToken) { // 显示Token给用户 showMessage(`Token生成成功!`, 'success'); // 创建一个可复制的Token显示区域 const tokenDisplay = document.createElement('div'); tokenDisplay.style.cssText = ` background: #f8f9fa; border: 2px solid #28a745; border-radius: 5px; padding: 10px; margin: 10px 0; font-family: monospace; font-size: 12px; word-break: break-all; cursor: pointer; text-align: center; `; tokenDisplay.textContent = result.token; tokenDisplay.title = '点击复制Token'; // 添加点击复制功能 tokenDisplay.addEventListener('click', async () => { try { await navigator.clipboard.writeText(result.token); showMessage('Token已复制到剪贴板!', 'success'); } catch (err) { // 备用复制方法 const textArea = document.createElement('textarea'); textArea.value = result.token; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showMessage('Token已复制到剪贴板!', 'success'); } }); // 添加充值提示信息 const rechargeNotice = document.createElement('div'); rechargeNotice.style.cssText = ` background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px; padding: 10px; margin: 10px 0; font-size: 14px; color: #856404; text-align: center; `; rechargeNotice.innerHTML = ` ⚠️ 重要提示
新用户默认0次查询机会,请充值后使用答题功能。
请妥善保存您的Token,它是您的唯一凭证! `; // 将Token显示区域和充值提示插入到消息div后面 messageDiv.parentNode.insertBefore(tokenDisplay, messageDiv.nextSibling); messageDiv.parentNode.insertBefore(rechargeNotice, tokenDisplay.nextSibling); // 修改按钮文字为"开始答题" createBtn.textContent = '开始答题'; createBtn.onclick = () => { closeDialog(); resolve({ success: true, hasToken: true, token: result.token, message: '新Token已生成,您可以开始答题了!' }); }; } else { throw new Error(result.message || 'Token生成失败'); } } catch (error) { showMessage('生成Token失败: ' + error.message, 'error'); createBtn.disabled = false; createBtn.textContent = '🆕 生成新Token'; } }); // 显示Token输入区域 inputBtn.addEventListener('click', () => { inputArea.style.display = 'block'; tokenInput.focus(); }); // 验证Token verifyBtn.addEventListener('click', async () => { const token = tokenInput.value.trim(); if (!token) { showMessage('请输入Token', 'error'); return; } try { verifyBtn.disabled = true; verifyBtn.textContent = '验证中...'; showMessage('正在验证Token...', 'info'); const result = await this.verifyUserToken(token); if (result.success) { showMessage('Token验证成功!', 'success'); setTimeout(() => { closeDialog(); resolve({ success: true, hasToken: true, token: result.token, message: 'Token验证成功,您可以开始答题了!' }); }, 1500); } else { throw new Error(result.message || 'Token验证失败'); } } catch (error) { showMessage('Token验证失败: ' + error.message, 'error'); verifyBtn.disabled = false; verifyBtn.textContent = '验证Token'; } }); // 取消输入 cancelBtn.addEventListener('click', () => { inputArea.style.display = 'none'; tokenInput.value = ''; messageDiv.style.display = 'none'; }); // 支持Enter键提交 tokenInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { verifyBtn.click(); } }); }); } }; function handleSubmitConfirmDialog() { const documents = [ document, // 当前document window.parent?.document, // 父窗口document window.top?.document // 顶层窗口document ].filter(doc => doc); let confirmDialog = null; let foundInDocument = null; for (const doc of documents) { try { confirmDialog = doc.querySelector('.popBottom'); if (confirmDialog) { foundInDocument = doc; break; } } catch (error) { } } if (!confirmDialog) { return false; } // 使用找到弹窗的document来查找其他元素 const targetDoc = foundInDocument || document; // 检查弹窗内容 const popContent = targetDoc.querySelector('#popcontent'); if (!popContent || !popContent.textContent.includes('确认提交')) { return false; } // 查找提交按钮 const submitBtn = targetDoc.querySelector('#popok'); if (!submitBtn) { return false; } // 立即点击提交按钮 try { submitBtn.click(); console.log('✅ [提交确认] 已点击提交按钮'); // 重要:标记已提交,避免重复答题 GLOBAL_STATE.lastAnswerTime = Date.now(); GLOBAL_STATE.isAnswering = false; GLOBAL_STATE.isChapterTesting = false; console.log('📝 [提交确认] 章节测验已完成,状态已重置'); // 不再自动跳转下一节,让主循环来控制跳转 // 主循环会检测到所有任务完成后自动跳转 return true; } catch (error) { console.warn('❌ [提交确认] 点击提交按钮失败:', error); return false; } } function monitorSubmitDialog() { let checkCount = 0; const maxChecks = 10; // 最多检查10秒 const checkInterval = setInterval(() => { checkCount++; // 检查弹窗 const dialogHandled = handleSubmitConfirmDialog(); // 如果处理了弹窗或超过最大检查次数,停止监控 if (dialogHandled || checkCount >= maxChecks) { clearInterval(checkInterval); } }, 1000); // 每秒检查一次 } // ========================================= // 8. 获取考试题目和选项功能 // ========================================= function getXueqiQuestions() { try { const questions = []; // 获取题目类型信息 const examTypeElement = document.querySelector('.test-part .f18.c_2d4.fb'); const examType = examTypeElement ? examTypeElement.textContent.trim() : '未知题型'; // 获取题目统计信息 const examInfoElement = document.querySelector('.test-part .c_dan'); const examInfo = examInfoElement ? examInfoElement.textContent.trim() : ''; // 获取所有题目 const questionElements = document.querySelectorAll('.questionItem'); questionElements.forEach((questionEl, index) => { const questionData = { type: examType, questionType: '', // 新增:具体题型(单选、判断等) number: index + 1, stem: '', options: [], score: '' }; // 判断题目类型 if (questionEl.classList.contains('singItem') || questionEl.querySelector('.singItem')) { questionData.questionType = '单选题'; } else if (questionEl.classList.contains('judge') || questionEl.querySelector('.judge')) { questionData.questionType = '判断题'; } else if (questionEl.classList.contains('Mutli') || questionEl.querySelector('.Mutli')) { questionData.questionType = '多选题'; } else { questionData.questionType = '未知题型'; } // 获取题干 const stemElement = questionEl.querySelector('.queStemC'); if (stemElement) { // 获取题号 const numberEl = stemElement.querySelector('.din.fb.mr10'); if (numberEl) { questionData.number = numberEl.textContent.trim(); } // 获取题目内容 - 查找包含题目文本的div.din const contentEls = stemElement.querySelectorAll('.din'); if (contentEls.length > 1) { // 通常第一个是题号,第二个是题目内容 const contentEl = contentEls[1]; if (contentEl) { // 先尝试从table内的span获取内容 const tableSpan = contentEl.querySelector('table span'); if (tableSpan) { questionData.stem = tableSpan.textContent.trim(); } // 如果没有table,尝试从p标签获取内容 else { const pEl = contentEl.querySelector('p'); if (pEl) { questionData.stem = pEl.textContent.trim(); } else { // 提取纯文本内容,过滤掉HTML标签 const textContent = contentEl.textContent.trim(); // 清理多余的空白字符 questionData.stem = textContent.replace(/\s+/g, ' ').trim(); } } } } // 如果还是没有获取到内容,尝试其他方式 if (!questionData.stem) { // 尝试从整个题干容器中提取文本 const allText = stemElement.textContent.trim(); // 移除题号和分数信息 const cleanText = allText.replace(/^\d+\.\s*/, '').replace(/(\d+分)$/, '').trim(); if (cleanText) { questionData.stem = cleanText; } } // 获取分数 const scoreEl = stemElement.querySelector('.f13.c_dan var'); if (scoreEl) { questionData.score = scoreEl.textContent.trim() + '分'; } } // 获取选项 - 根据题型不同处理(基于老版本的正确逻辑) if (questionData.questionType === '判断题') { // 判断题的选项处理(老版本正确做法) const judgeButtons = questionEl.querySelectorAll('.JudgeBtn input'); judgeButtons.forEach((btn, idx) => { const value = btn.value.trim(); questionData.options.push({ label: idx === 0 ? 'T' : 'F', // T表示正确,F表示错误 content: value, element: btn // 保存按钮元素引用 }); }); } else if (questionData.questionType === '多选题') { // 多选题的选项处理(老版本正确做法) const optionElements = questionEl.querySelectorAll('dd.clearfix'); optionElements.forEach(optionEl => { const optionLabel = optionEl.querySelector('.duplexCheck'); const optionContent = optionEl.querySelector('div'); if (optionLabel && optionContent) { questionData.options.push({ label: optionLabel.textContent.trim(), content: optionContent.textContent.trim(), element: optionEl // 保存选项元素引用 }); } }); } else { // 单选题等其他题型的选项处理(老版本正确做法) const optionElements = questionEl.querySelectorAll('dd.clearfix'); optionElements.forEach(optionEl => { const optionLabel = optionEl.querySelector('.singleCheck'); const optionContent = optionEl.querySelector('div'); if (optionLabel && optionContent) { questionData.options.push({ label: optionLabel.textContent.trim(), content: optionContent.textContent.trim(), element: optionEl // 保存选项元素引用 }); } }); } // 直接添加题目(与老版本一致) questions.push(questionData); }); return questions; } catch (error) { return []; } } // 超星学习通题目解析函数(兼容考试和作业界面) function getChaoxingQuestions() { try { logMessage('🔍 [超星] 开始解析题目...', 'info'); const questions = []; const currentUrl = window.location.href; // 根据URL精确判断页面类型 let pageType = '未知'; let isExamPage = false; let isHomeworkPage = false; let isChapterTestPage = false; if (currentUrl.includes('/exam-ans/mooc2/exam/')) { pageType = '考试'; isExamPage = true; } else if (currentUrl.includes('/mooc-ans/mooc2/work/')) { pageType = '作业'; isHomeworkPage = true; } else if (currentUrl.includes('/mooc-ans/work/doHomeWorkNew/') || currentUrl.includes('/mooc-ans/api/work/') || currentUrl.includes('/ananas/modules/work/')) { pageType = '章节测验'; isChapterTestPage = true; } else { // 降级检测:通过DOM元素判断 const hasTypenameAttr = document.querySelector('.questionLi[typename]') !== null; if (hasTypenameAttr) { pageType = '作业'; isHomeworkPage = true; } else { pageType = '考试'; isExamPage = true; } } // 查找所有题目容器 const questionElements = document.querySelectorAll('.questionLi'); if (questionElements.length === 0) { logMessage('⚠️ [超星] 未找到题目元素 (.questionLi),请确认页面结构。', 'warning'); return []; } logMessage(`[超星] 发现 ${questionElements.length} 个题目容器 (${pageType}页面)。`, 'info'); questionElements.forEach((questionEl, index) => { try { const questionData = { type: '超星学习通', questionType: '未知题型', number: index + 1, stem: '', options: [], score: '', questionId: '' }; // 1. 提取题目ID - 多种方式尝试 let questionId = questionEl.getAttribute('data') || questionEl.id?.replace('sigleQuestionDiv_', '') || ''; // 如果从题目元素获取不到ID,尝试从选项中获取qid属性 if (!questionId) { const optionWithQid = questionEl.querySelector('[qid]'); if (optionWithQid) { questionId = optionWithQid.getAttribute('qid'); } } questionData.questionId = questionId; // 2. 根据页面类型提取题型 if (isHomeworkPage) { // 作业页面:优先从typename属性获取 const typeNameAttr = questionEl.getAttribute('typename'); if (typeNameAttr) { questionData.questionType = typeNameAttr; console.log(`[作业页面] 从typename属性获取题型: ${typeNameAttr}`); } } else if (isExamPage) { // 考试页面:从span.colorShallow获取题型 const markNameEl = questionEl.querySelector('h3.mark_name'); if (markNameEl) { const typeScoreSpan = markNameEl.querySelector('span.colorShallow'); if (typeScoreSpan) { const typeScoreText = typeScoreSpan.textContent.trim(); // 匹配格式:(单选题, 2.0分) 或 (单选题) 或 (填空题) const match = typeScoreText.match(/\(([^,)]+)(?:,\s*([^)]+))?\)/); if (match) { questionData.questionType = match[1].trim(); if (match[2]) { questionData.score = match[2].trim(); } console.log(`[考试页面] 从span.colorShallow获取题型: ${questionData.questionType}`); } } } } // 3. 提取题号和题干 const markNameEl = questionEl.querySelector('h3.mark_name'); if (markNameEl) { // 提取题号(从h3的文本节点中) const titleText = markNameEl.childNodes[0]?.textContent?.trim() || ''; const numberMatch = titleText.match(/^(\d+)\./); if (numberMatch) { questionData.number = numberMatch[1]; } // 根据页面类型提取题干 let stemText = ''; if (isExamPage) { // 考试页面:从div中提取 const stemDiv = markNameEl.querySelector('div[style*="overflow:hidden"]'); if (stemDiv) { stemText = stemDiv.textContent.trim(); } else { // 降级方案:从h3文本提取 const fullText = markNameEl.textContent.trim(); stemText = fullText.replace(/^\d+\.\s*/, ''); const typePattern = /^\s*\((单选题|多选题|判断题|填空题)(?:,\s*[^)]+)?\)\s*/; stemText = stemText.replace(typePattern, '').trim(); } } else if (isHomeworkPage) { // 作业页面:从h3文本节点提取 const fullText = markNameEl.textContent.trim(); // 移除题号(如"1.") stemText = fullText.replace(/^\d+\.\s*/, ''); // 更精确地移除题型标识:只移除开头的题型标识,保留题干中的括号内容 const typePattern = /^\s*\((单选题|多选题|判断题|填空题)(?:,\s*[^)]+)?\)\s*/; stemText = stemText.replace(typePattern, '').trim(); } questionData.stem = stemText; // 最终的题型识别逻辑 if (!questionData.questionType || questionData.questionType === '未知题型') { // 通过题干内容识别填空题 if (questionData.stem && ( questionData.stem.includes('____') || questionData.stem.includes('()') || questionData.stem.includes('()') || questionData.stem.includes('_____') || questionData.stem.includes('填空') || questionData.stem.includes('空白') )) { questionData.questionType = '填空题'; } else { // 默认设置为单选题 questionData.questionType = '单选题'; } } } // 3. 提取选项(填空题没有选项) // 先检查是否有输入框,如果有输入框但没有选项,可能是填空题 const answerContainer = questionEl.querySelector('.stem_answer'); const hasInputElements = questionEl.querySelectorAll('input[type="text"], textarea').length > 0; // 基于HTML结构识别填空题 const hasBlankItems = questionEl.querySelectorAll('.blankItemDiv').length > 0; const hasUEditor = questionEl.querySelectorAll('textarea[name*="answerEditor"]').length > 0; const hasTiankongSize = questionEl.querySelector('input[name*="tiankongsize"]'); // 检查题目类型文本是否包含"填空题" const typeElement = questionEl.querySelector('.colorShallow'); const typeText = typeElement ? typeElement.textContent : ''; const isBlankQuestionByType = typeText.includes('填空题') || typeText.includes('【填空题】'); if ((hasInputElements || hasBlankItems || hasUEditor || hasTiankongSize || isBlankQuestionByType) && questionData.questionType !== '填空题') { questionData.questionType = '填空题'; } if (questionData.questionType !== '填空题') { if (answerContainer) { const optionElements = answerContainer.querySelectorAll('.clearfix.answerBg'); optionElements.forEach(optionEl => { // 获取选项标签(A、B、C、D等)- 兼容单选和多选题 const labelSpan = optionEl.querySelector('.num_option, .num_option_dx'); // 获取选项内容 const contentDiv = optionEl.querySelector('.answer_p'); if (labelSpan && contentDiv) { let label = labelSpan.textContent.trim(); let content = ''; // 对于判断题,内容在p标签内 const pElement = contentDiv.querySelector('p'); if (pElement) { content = pElement.textContent.trim(); // 判断题特殊处理:将"对"/"错"转换为T/F if (questionData.questionType === '判断题') { if (content === '对') { label = 'T'; content = '正确'; } else if (content === '错') { label = 'F'; content = '错误'; } } } else { content = contentDiv.textContent.trim(); } questionData.options.push({ label: label, content: content, element: optionEl, dataValue: labelSpan.getAttribute('data') || label, qid: labelSpan.getAttribute('qid') || questionData.questionId, isMultipleChoice: questionData.questionType === '多选题' }); } }); } } else { // 填空题:按照HTML层级结构逐步定位 // 1. 先找到 .stem_answer 答案区域 const stemAnswerEl = questionEl.querySelector('.stem_answer'); if (stemAnswerEl) { // 2. 在答案区域中找到所有 .Answer 容器(每个代表一个填空) const answerContainers = stemAnswerEl.querySelectorAll('.Answer'); answerContainers.forEach((answerContainer, index) => { // 3. 在每个 .Answer 容器中查找输入元素 const textareaEl = answerContainer.querySelector('textarea[name*="answerEditor"]'); const iframe = answerContainer.querySelector('iframe'); const ueditorContainer = answerContainer.querySelector('.edui-editor'); if (textareaEl) { const editorId = textareaEl.id || textareaEl.name; // 从iframe的src中提取UEditor实例名 let ueditorInstanceName = null; if (iframe && iframe.src) { const match = iframe.src.match(/ueditorInstant(\d+)/); if (match) { ueditorInstanceName = `ueditorInstant${match[1]}`; } } // 尝试获取iframe内的body元素 let iframeBody = null; try { if (iframe && iframe.contentDocument && iframe.contentDocument.body) { iframeBody = iframe.contentDocument.body; } } catch (e) { // iframe可能还没加载完成或跨域限制 } questionData.options.push({ label: `填空${index + 1}`, content: '', element: textareaEl, dataValue: '', isFillInBlank: true, inputType: 'ueditor', editorId: editorId, ueditorContainer: ueditorContainer, iframe: iframe, iframeBody: iframeBody, ueditorInstanceName: ueditorInstanceName, answerContainer: answerContainer }); } else { // 查找普通输入框 const inputEl = answerContainer.querySelector('input[type="text"], textarea'); if (inputEl) { questionData.options.push({ label: `填空${index + 1}`, content: '', element: inputEl, dataValue: '', isFillInBlank: true, inputType: 'normal', answerContainer: answerContainer }); } } }); // 如果没有找到任何Answer容器,可能是其他类型的填空题 if (answerContainers.length === 0) { const inputElements = stemAnswerEl.querySelectorAll('input[type="text"], textarea'); inputElements.forEach((inputEl, index) => { questionData.options.push({ label: `填空${index + 1}`, content: '', element: inputEl, dataValue: '', isFillInBlank: true, inputType: 'normal' }); }); } } // 如果完全没有找到输入元素,创建虚拟填空 if (questionData.options.length === 0) { questionData.options.push({ label: '填空1', content: '', element: null, dataValue: '', isFillInBlank: true, isVirtual: true }); } } // 4. 验证题目数据完整性 if (questionData.stem && (questionData.options.length > 0 || questionData.questionType === '填空题')) { questions.push(questionData); } else { logMessage(`⚠️ [超星] 第 ${index + 1} 题数据不完整,跳过`, 'warning'); } } catch (e) { logMessage(`❌ [超星] 解析第 ${index + 1} 题时出错: ${e.message}`, 'error'); } }); if (questions.length > 0) { logMessage(`✅ [超星] 成功解析 ${questions.length} 道题目`, 'success'); // 统计题型分布 const typeCount = {}; questions.forEach(q => { typeCount[q.questionType] = (typeCount[q.questionType] || 0) + 1; }); const typeStats = Object.entries(typeCount) .map(([type, count]) => `${type}:${count}`) .join(' '); logMessage(`📊 [超星] 题型分布: ${typeStats}`, 'info'); } else { logMessage('❌ [超星] 未能解析到任何题目', 'error'); } return questions; } catch (error) { logMessage(`❌ [超星] 题目解析失败: ${error.message}`, 'error'); return []; } } // 增强的题目获取函数(带调试信息) function getExamQuestions() { try { // 检测站点 if (!currentSite) { detectSite(); } // 尝试多种题目选择器 const selectors = [ '.questionLi', // 超星学习通 '.questionItem', // 学起 '.question-item', // 通用 '.exam-question', // 通用 '[class*="question"]' // 包含question的类名 ]; let foundElements = []; let usedSelector = ''; for (const selector of selectors) { const elements = document.querySelectorAll(selector); if (elements.length > 0) { foundElements = elements; usedSelector = selector; break; } } if (foundElements.length === 0) { return []; } console.log(`✅ [调试] 使用选择器 "${usedSelector}" 找到 ${foundElements.length} 个题目元素`); // 调用对应的解析函数 if (currentSite && typeof currentSite.getQuestions === 'function') { console.log('🔍 [调试] 调用站点专用解析函数:', currentSite.name); const questions = currentSite.getQuestions(); console.log('🔍 [调试] 解析结果:', questions.length, '道题目'); return questions; } else { console.warn('⚠️ [调试] 站点解析函数不存在,尝试通用解析'); return tryGenericParsing(foundElements, usedSelector); } } catch (error) { console.error('❌ [调试] 获取题目时出错:', error); return []; } } // 通用题目解析函数(备用) function tryGenericParsing(elements, selector) { const questions = []; elements.forEach((el, index) => { try { const questionData = { type: '通用解析', questionType: '未知题型', number: index + 1, stem: '', options: [], score: '' }; // 尝试提取题干 const textContent = el.textContent.trim(); if (textContent.length > 10) { questionData.stem = textContent.substring(0, 200) + (textContent.length > 200 ? '...' : ''); // 尝试识别题型 if (textContent.includes('单选') || textContent.includes('Single')) { questionData.questionType = '单选题'; } else if (textContent.includes('多选') || textContent.includes('Multiple')) { questionData.questionType = '多选题'; } else if (textContent.includes('判断') || textContent.includes('True') || textContent.includes('False')) { questionData.questionType = '判断题'; } questions.push(questionData); console.log(`🔍 [调试] 通用解析题目 ${index + 1}:`, questionData.questionType, questionData.stem.substring(0, 50) + '...'); } } catch (error) { console.warn(`⚠️ [调试] 通用解析第 ${index + 1} 题失败:`, error.message); } }); console.log(`✅ [调试] 通用解析完成,共 ${questions.length} 道题目`); return questions; } // ========================================= // 9. 云端API调用功能(简化429处理) // ========================================= async function callCloudAPI(questionData) { try { const token = await TokenManager.getValidToken(); const requestData = { question: questionData.stem, questionType: questionData.questionType, options: questionData.options.map(opt => ({ label: opt.label, content: opt.content })) }; // 填空题特殊处理:不需要选项,但需要标识题型 if (questionData.questionType === '填空题') { requestData.options = []; // 填空题没有选项 requestData.fillInBlank = true; // 标识为填空题 } const response = await gmFetch(`${SERVER_CONFIG.answerApiUrl}/answer`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-User-Token': token }, body: JSON.stringify(requestData), timeout: SERVER_CONFIG.timeout }); // 特殊处理429错误 if (response.status === 429) { logMessage('⚠️ 请求过于频繁,等待10秒后继续...', 'warning'); await new Promise(resolve => { let remaining = 10; const countdownInterval = setInterval(() => { if (remaining <= 0) { clearInterval(countdownInterval); resolve(); return; } const statusElement = document.getElementById('question-count'); if (statusElement) { statusElement.textContent = `⏳ 限流等待中... ${remaining}s`; } remaining--; }, 1000); }); throw new Error('429_RATE_LIMIT_HANDLED'); } if (!response.ok) { if (response.status === 401) { TokenManager.clearToken(); throw new Error('Token无效或已过期,请刷新页面重新获取Token'); } let errorText = `API请求失败: ${response.status} ${response.statusText}`; try { const errorResponse = await response.json(); if (errorResponse.message) { errorText = `API请求失败: ${errorResponse.message}`; } } catch (jsonError) { try { const textResponse = await response.text(); if (textResponse) { errorText = `API请求失败: ${response.status} ${response.statusText} - ${textResponse.substring(0, 200)}`; } } catch (textError) { // 忽略错误详情获取失败 } } throw new Error(errorText); } const result = await response.json(); if (!result.success) { throw new Error(result.message || '云端分析失败'); } // 显示余额变化 if (result.quota) { logMessage(result.quota.message, 'info'); // 已移除自动刷新余额,用户可手动刷新 } // 添加缓存状态信息 result.data.cached = result.cached || false; result.data.responseTime = result.data.responseTime || 0; return result.data; } catch (error) { console.error('❌ [客户端API] 调用失败:', error.message); // 如果是Token相关错误,显示友好的错误消息 if (error.message.includes('Token') || error.message.includes('token')) { logMessage('❌ Token验证失败,请刷新页面重新获取Token', 'error'); throw new Error('Token验证失败,请刷新页面重新获取Token'); } throw error; } } // 自动答题所有题目(简化版) async function autoAnswerAllQuestions(delay = 1000) { try { // 检查是否已经在答题中 if (GLOBAL_STATE.isAnswering || GLOBAL_STATE.isChapterTesting) { logMessage('⏸️ 已有答题任务在进行中,请稍后再试', 'warning'); return []; } GLOBAL_STATE.isAnswering = true; const questions = getExamQuestions(); if (!questions || !Array.isArray(questions) || questions.length === 0) { logMessage('❌ 批量答题失败: 未找到题目', 'error'); return []; } logMessage(`🚀 开始自动答题,共 ${questions.length} 道题目`, 'info'); const results = []; for (let i = 0; i < questions.length; i++) { const statusElement = document.getElementById('question-count'); if (statusElement) { statusElement.textContent = `📝 答题进度: ${i + 1}/${questions.length} (${Math.round((i + 1) / questions.length * 100)}%)`; } try { const result = await answerSingleQuestion(i); if (result) { results.push(result); } else { logMessage(`❌ 第 ${i + 1} 题答题失败`, 'error'); } } catch (error) { logMessage(`❌ 第 ${i + 1} 题出错: ${error.message}`, 'error'); } // 题目间延迟 if (i < questions.length - 1) { await new Promise(resolve => { let remaining = Math.ceil(delay / 1000); const countdownInterval = setInterval(() => { if (remaining <= 0) { clearInterval(countdownInterval); resolve(); return; } if (statusElement) { statusElement.textContent = `⏳ 等待中... ${remaining}s (第${i + 2}题准备中)`; } remaining--; }, 1000); }); } } setTimeout(() => { updateQuestionCount(); }, 1000); if (results.length === questions.length) { logMessage(`🎉 自动答题完成!全部成功 (${results.length}/${questions.length})`, 'success'); } else { logMessage(`⚠️ 自动答题完成,成功: ${results.length}/${questions.length}`, 'warning'); } return results; } catch (error) { logMessage(`❌ 批量答题失败: ${error.message}`, 'error'); return []; } finally { // 重置状态 GLOBAL_STATE.isAnswering = false; GLOBAL_STATE.lastAnswerTime = Date.now(); } } // 修改答题成功后的日志记录 async function fillAnswers(questions, answers) { if (!questions || questions.length === 0) { logMessage('❌ 没有题目需要填写答案', 'error'); return { success: false, message: '没有题目需要填写答案' }; } if (!answers || answers.length === 0) { logMessage('❌ 没有获取到答案', 'error'); return { success: false, message: '没有获取到答案' }; } let successCount = 0; const results = []; for (let i = 0; i < Math.min(questions.length, answers.length); i++) { const question = questions[i]; const answer = answers[i]; try { // 记录题目和答案到日志 logQuestionAnswer(question.stem, answer.answer, question.questionType); const result = await fillSingleAnswer(question, answer); results.push(result); if (result.success) { successCount++; logMessage(`✅ 第${i + 1}题填写成功`, 'success'); } else { logMessage(`❌ 第${i + 1}题填写失败: ${result.message}`, 'error'); } // 添加延迟避免操作过快 await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { logMessage(`❌ 第${i + 1}题处理异常: ${error.message}`, 'error'); results.push({ success: false, message: error.message }); } } const message = `答题完成:成功 ${successCount}/${questions.length} 题`; logMessage(message, successCount > 0 ? 'success' : 'error'); return { success: successCount > 0, message: message, successCount: successCount, totalCount: questions.length, results: results }; } // 选择答案(点击选项) function selectXueqiAnswer(questionIndex, answer) { try { const questionElements = document.querySelectorAll('.questionItem'); if (questionIndex >= questionElements.length) { console.log('题目索引超出范围'); return false; } const questionEl = questionElements[questionIndex]; // 判断是否为判断题 if (questionEl.classList.contains('judge') || questionEl.querySelector('.judge')) { // 判断题处理 const judgeButtons = questionEl.querySelectorAll('.JudgeBtn input'); for (let btn of judgeButtons) { const btnValue = btn.value.trim(); // T对应"正确",F对应"错误" if ((answer === 'T' && btnValue === '正确') || (answer === 'F' && btnValue === '错误')) { // 模拟点击 btn.click(); // 添加视觉反馈 const judgeBtn = btn.parentElement; judgeBtn.style.backgroundColor = '#e8f5e8'; setTimeout(() => { judgeBtn.style.backgroundColor = ''; }, 1000); return true; } } } else if (questionEl.classList.contains('Mutli') || questionEl.querySelector('.Mutli')) { // 多选题处理 const options = questionEl.querySelectorAll('dd.clearfix'); // 安全检查:确保answer是字符串且不为空 if (!answer || typeof answer !== 'string' || answer.length === 0) { console.error('多选题答案无效:', answer); return false; } let answersToSelect; try { // 将答案字符串转换为字符数组(安全处理) answersToSelect = [...answer]; } catch (err) { console.error('多选题答案转换失败:', err); return false; } let successCount = 0; for (let option of options) { const optionLabel = option.querySelector('.duplexCheck'); if (optionLabel) { const labelText = optionLabel.textContent.trim(); if (answersToSelect && answersToSelect.includes(labelText)) { // 模拟点击 option.click(); // 添加视觉反馈 option.style.backgroundColor = '#e8f5e8'; setTimeout(() => { option.style.backgroundColor = ''; }, 1000); successCount++; } } } return answersToSelect && successCount === answersToSelect.length; } else { // 单选题处理 const options = questionEl.querySelectorAll('dd.clearfix'); for (let option of options) { const optionLabel = option.querySelector('.singleCheck'); if (optionLabel && optionLabel.textContent.trim() === answer) { // 模拟点击 option.click(); // 添加视觉反馈 option.style.backgroundColor = '#e8f5e8'; setTimeout(() => { option.style.backgroundColor = ''; }, 1000); return true; } } } return false; } catch (error) { console.error('选择答案失败:', error); return false; } } // 超星学习通答案选择函数(根据实际HTML结构优化) function selectChaoxingAnswer(questionIndex, answer) { try { logMessage(`🎯 [超星] 选择第 ${questionIndex + 1} 题答案: ${answer}`, 'info'); const questions = getExamQuestions(); const questionData = questions[questionIndex]; if (!questionData) { logMessage(`❌ [超星] 第 ${questionIndex + 1} 题数据未找到`, 'error'); return false; } // 填空题特殊处理 if (questionData.questionType === '填空题') { return selectFillInBlankAnswer(questionData, answer); } // 处理不同类型的答案格式 let answersToSelect = []; if (questionData.questionType === '判断题') { // 判断题:T/F 转换为对应的选项 if (answer === 'T' || answer === 'true' || answer === '对') { answersToSelect = ['T']; } else if (answer === 'F' || answer === 'false' || answer === '错') { answersToSelect = ['F']; } else { // 如果是A/B格式,查找对应的data值 const option = questionData.options.find(opt => opt.label === answer); if (option && option.dataValue) { answersToSelect = [option.dataValue === 'true' ? 'T' : 'F']; } else { answersToSelect = [answer]; } } } else { // 单选题和多选题:直接使用字母答案 answersToSelect = [...answer.toUpperCase()]; } let successCount = 0; const questionId = questionData.questionId; // 对于多选题,使用异步方式逐个选择选项,避免选择太快导致失败 if (questionData.questionType === '多选题') { return new Promise(async (resolve) => { for (let i = 0; i < answersToSelect.length; i++) { const ans = answersToSelect[i]; // 查找对应的选项 const targetOption = questionData.options.find(opt => opt.label === ans); if (targetOption && targetOption.element) { // 检查是否已经选中 const isSelected = targetOption.element.classList.contains('hasBeenTo') || targetOption.element.classList.contains('selected') || targetOption.element.querySelector('.num_option, .num_option_dx')?.classList.contains('hasBeenTo'); if (isSelected) { logMessage(`✅ [超星] 选项 ${ans} 已经选中`, 'info'); successCount++; continue; } // 点击选择答案 - 兼容考试和作业界面 try { // 检测页面类型并使用对应的点击方式 const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null; if (isHomeworkPage) { // 作业界面:多选题使用addMultipleChoice函数 if (typeof pageWindow.addMultipleChoice === 'function') { pageWindow.addMultipleChoice(targetOption.element); logMessage(`✅ [超星] 通过addMultipleChoice()选择选项 ${ans}`, 'success'); } else { targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); } } else { // 考试界面:多选题处理 - 尝试多种方法 const optionQid = targetOption.qid || questionId; // 优先使用选项的qid let success = false; // 方法1: 尝试saveMultiSelect函数(考试页面多选题专用) if (typeof pageWindow.saveMultiSelect === 'function' && optionQid) { try { pageWindow.saveMultiSelect(targetOption.element, optionQid); logMessage(`✅ [超星] 通过saveMultiSelect()选择选项 ${ans}`, 'success'); success = true; } catch (e) { console.log(`[超星] saveMultiSelect失败:`, e.message); } } // 方法2: 尝试addMultipleChoice函数 if (!success && typeof pageWindow.addMultipleChoice === 'function') { try { pageWindow.addMultipleChoice(targetOption.element); logMessage(`✅ [超星] 通过addMultipleChoice()选择选项 ${ans}`, 'success'); success = true; } catch (e) { console.log(`[超星] addMultipleChoice失败:`, e.message); } } // 方法3: 备用方案:直接点击 if (!success) { try { targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); success = true; } catch (e) { console.log(`[超星] click失败:`, e.message); } } if (!success) { logMessage(`❌ [超星] 所有方法都失败,无法选择多选题选项 ${ans}`, 'error'); } } // 添加视觉反馈 targetOption.element.style.backgroundColor = '#e8f5e8'; targetOption.element.style.border = '2px solid #4ade80'; setTimeout(() => { targetOption.element.style.backgroundColor = ''; targetOption.element.style.border = ''; }, 2000); successCount++; // 多选题选项之间添加延迟,确保页面有时间处理 if (i < answersToSelect.length - 1) { await new Promise(resolve => setTimeout(resolve, 300)); // 300ms延迟 } } catch (clickError) { logMessage(`❌ [超星] 点击选项 ${ans} 失败: ${clickError.message}`, 'error'); } } else { logMessage(`⚠️ [超星] 未找到答案选项 '${ans}'`, 'warning'); } } // 返回选择结果 const success = successCount > 0; if (success) { logMessage(`✅ [超星] 第 ${questionIndex + 1} 题多选题答案选择完成 (${successCount}/${answersToSelect.length})`, 'success'); } else { logMessage(`❌ [超星] 第 ${questionIndex + 1} 题多选题答案选择失败`, 'error'); } resolve(success); }); } else { // 单选题和判断题保持原有的同步处理方式 answersToSelect.forEach(ans => { // 查找对应的选项 let targetOption = null; if (questionData.questionType === '判断题') { // 判断题特殊处理 targetOption = questionData.options.find(opt => opt.label === ans || (ans === 'T' && (opt.dataValue === 'true' || opt.content === '正确' || opt.content === '对')) || (ans === 'F' && (opt.dataValue === 'false' || opt.content === '错误' || opt.content === '错')) ); } else { // 单选题 targetOption = questionData.options.find(opt => opt.label === ans); } if (targetOption && targetOption.element) { // 检查是否已经选中 const isSelected = targetOption.element.classList.contains('hasBeenTo') || targetOption.element.classList.contains('selected') || targetOption.element.querySelector('.num_option, .num_option_dx')?.classList.contains('hasBeenTo'); if (isSelected) { logMessage(`✅ [超星] 选项 ${ans} 已经选中`, 'info'); successCount++; return; } // 点击选择答案 - 兼容考试和作业界面 try { // 检测页面类型并使用对应的点击方式 const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null; if (isHomeworkPage) { // 作业界面:单选题使用addChoice函数 if (typeof pageWindow.addChoice === 'function') { pageWindow.addChoice(targetOption.element); logMessage(`✅ [超星] 通过addChoice()选择选项 ${ans}`, 'success'); } else { targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); } } else { // 考试界面:单选题和判断题使用saveSingleSelect函数 const optionQid = targetOption.qid || questionId; // 优先使用选项的qid if (typeof pageWindow.saveSingleSelect === 'function' && optionQid) { pageWindow.saveSingleSelect(targetOption.element, optionQid); logMessage(`✅ [超星] 通过saveSingleSelect()选择选项 ${ans}`, 'success'); } else { // 备用方案:直接点击 targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); } } // 添加视觉反馈 targetOption.element.style.backgroundColor = '#e8f5e8'; targetOption.element.style.border = '2px solid #4ade80'; setTimeout(() => { targetOption.element.style.backgroundColor = ''; targetOption.element.style.border = ''; }, 2000); successCount++; } catch (clickError) { logMessage(`❌ [超星] 点击选项 ${ans} 失败: ${clickError.message}`, 'error'); console.error('[超星] 点击错误详情:', clickError); } } else { logMessage(`⚠️ [超星] 未找到答案选项 '${ans}'`, 'warning'); console.log('[超星] 可用选项:', questionData.options.map(opt => `${opt.label}(${opt.content})`)); } }); } // 返回选择结果 const success = successCount > 0; if (success) { logMessage(`✅ [超星] 第 ${questionIndex + 1} 题答案选择完成 (${successCount}/${answersToSelect.length})`, 'success'); } else { logMessage(`❌ [超星] 第 ${questionIndex + 1} 题答案选择失败`, 'error'); } return success; } catch (error) { logMessage(`❌ [超星] 选择答案时出错: ${error.message}`, 'error'); console.error('[超星] 答案选择错误:', error); return false; } } // 填空题答案选择函数 function selectFillInBlankAnswer(questionData, answer) { try { logMessage(`📝 [填空题] 填入答案: ${answer}`, 'info'); // 查找输入框元素 const fillInBlankOptions = questionData.options.filter(opt => opt.isFillInBlank); if (fillInBlankOptions.length === 0) { logMessage(`❌ [填空题] 未找到输入框`, 'error'); return false; } let successCount = 0; // 处理多个填空的情况 - 支持多种分隔符 let answers = []; if (answer.includes('|')) { answers = answer.split('|').map(a => a.trim()); logMessage(`📝 [填空题] 使用|分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info'); } else if (answer.includes(',')) { answers = answer.split(',').map(a => a.trim()); logMessage(`📝 [填空题] 使用,分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info'); } else if (answer.includes(',')) { answers = answer.split(',').map(a => a.trim()); logMessage(`📝 [填空题] 使用,分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info'); } else { answers = [answer.trim()]; logMessage(`📝 [填空题] 单个答案: ${answers[0]}`, 'info'); } fillInBlankOptions.forEach((option, index) => { // 确保每个填空都有对应的答案,如果答案不够就跳过 if (index >= answers.length) { logMessage(`⚠️ [填空题] 填空${index + 1}没有对应答案,跳过`, 'warning'); return; } const answerText = answers[index]; logMessage(`📝 [填空题] 准备填入填空${index + 1}: "${answerText}"`, 'info'); if (option.element) { try { if (option.inputType === 'ueditor') { // UEditor富文本编辑器处理 const editorId = option.editorId; logMessage(`🔍 [填空题] UEditor ID: ${editorId}`, 'info'); let ueditorSuccess = false; // 方法0:优先使用保存的iframeBody引用(最直接的方法) if (option.iframeBody) { try { option.iframeBody.innerHTML = `

${answerText}

`; // 触发内容变化事件 const inputEvent = new Event('input', { bubbles: true }); option.iframeBody.dispatchEvent(inputEvent); logMessage(`✅ [填空题] 直接操作iframeBody填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } catch (error) { logMessage(`⚠️ [填空题] 直接操作iframeBody失败: ${error.message}`, 'warning'); } } // 方法1:通过UE全局对象设置内容 if (typeof window.UE !== 'undefined') { let editor = null; // 尝试多种方式获取UEditor实例 if (window.UE.getEditor) { editor = window.UE.getEditor(editorId); } // 如果通过ID获取失败,尝试通过instants获取 if (!editor && window.UE.instants && option.ueditorInstanceName) { editor = window.UE.instants[option.ueditorInstanceName]; } logMessage(`🔍 [填空题] UEditor实例状态: ${editor ? '找到' : '未找到'} (ID: ${editorId})`, 'info'); if (editor && editor.setContent) { editor.setContent(answerText); logMessage(`✅ [填空题] UEditor setContent填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } else if (editor && editor.execCommand) { // 尝试使用execCommand方法 editor.execCommand('inserthtml', answerText); logMessage(`✅ [填空题] UEditor execCommand填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } else if (editor && editor.body) { // 直接操作editor的body editor.body.innerHTML = `

${answerText}

`; logMessage(`✅ [填空题] UEditor body操作填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } else { logMessage(`⚠️ [填空题] UEditor实例方法不可用,尝试其他方法`, 'warning'); } } else { logMessage(`⚠️ [填空题] UE对象不存在,尝试其他方法`, 'warning'); } // 方法2:重新获取iframe body(防止初始检测时iframe未加载完成) if (!ueditorSuccess && option.iframe) { try { const iframe = option.iframe; logMessage(`🔍 [填空题] 重新尝试获取iframe body: ${iframe.id}`, 'info'); // 多次尝试获取iframe body const tryGetIframeBody = (attempts = 0) => { try { if (iframe.contentDocument && iframe.contentDocument.body) { const iframeBody = iframe.contentDocument.body; // 检查body是否可编辑 if (iframeBody.contentEditable === 'true' || iframeBody.classList.contains('view')) { iframeBody.innerHTML = `

${answerText}

`; // 触发多种事件确保UEditor检测到变化 const events = ['input', 'change', 'keyup', 'blur']; events.forEach(eventType => { try { const event = new iframe.contentWindow.Event(eventType, { bubbles: true }); iframeBody.dispatchEvent(event); } catch (e) { // 忽略事件触发失败 } }); logMessage(`✅ [填空题] iframe重新获取填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; return true; } else { logMessage(`⚠️ [填空题] iframe body不可编辑`, 'warning'); } } } catch (e) { logMessage(`⚠️ [填空题] iframe访问失败 (尝试${attempts + 1}): ${e.message}`, 'warning'); } // 如果失败且尝试次数少于3次,延迟重试 if (attempts < 2) { setTimeout(() => tryGetIframeBody(attempts + 1), 200); } return false; }; tryGetIframeBody(); } catch (error) { logMessage(`⚠️ [填空题] iframe重新获取失败: ${error.message}`, 'warning'); } } // 方法3:模拟用户操作 if (!ueditorSuccess && option.iframe) { try { const iframe = option.iframe; if (iframe.contentDocument && iframe.contentWindow) { const iframeDoc = iframe.contentDocument; const iframeBody = iframeDoc.body; if (iframeBody) { // 清空现有内容 iframeBody.innerHTML = ''; // 创建新的段落元素 const p = iframeDoc.createElement('p'); p.textContent = answerText; p.appendChild(iframeDoc.createElement('br')); iframeBody.appendChild(p); // 触发UEditor的内容变化事件 const inputEvent = new Event('input', { bubbles: true }); iframeBody.dispatchEvent(inputEvent); logMessage(`✅ [填空题] 模拟操作填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } } } catch (error) { logMessage(`⚠️ [填空题] 模拟操作失败: ${error.message}`, 'warning'); } } // 方法4:使用保存的iframe引用(最可靠的方法) if (!ueditorSuccess && option.iframe) { try { const iframe = option.iframe; logMessage(`🔍 [填空题] 使用保存的iframe引用: ${iframe.id}`, 'info'); const setIframeContent = () => { try { if (iframe.contentDocument && iframe.contentDocument.body) { const body = iframe.contentDocument.body; body.innerHTML = `

${answerText}

`; // 触发内容变化事件 if (iframe.contentWindow) { const inputEvent = new iframe.contentWindow.Event('input', { bubbles: true }); body.dispatchEvent(inputEvent); } logMessage(`✅ [填空题] 使用iframe引用填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; return true; } } catch (e) { logMessage(`⚠️ [填空题] iframe内容设置失败: ${e.message}`, 'warning'); } return false; }; // 立即尝试,失败则延迟重试 if (!setIframeContent()) { setTimeout(setIframeContent, 200); } } catch (error) { logMessage(`⚠️ [填空题] iframe引用操作失败: ${error.message}`, 'warning'); } } } else { // 普通输入框处理 const element = option.element; // 调试信息:显示元素信息 logMessage(`🔍 [填空题] 元素类型: ${element.tagName}, name: ${element.name}, id: ${element.id}`, 'info'); // 设置值 element.value = answerText; // 验证值是否设置成功 if (element.value === answerText) { logMessage(`✅ [填空题] 值设置成功: ${element.value}`, 'info'); } else { logMessage(`❌ [填空题] 值设置失败,期望: ${answerText}, 实际: ${element.value}`, 'error'); } // 触发多种事件,确保页面能检测到变化 const events = ['input', 'change', 'blur', 'keyup']; events.forEach(eventType => { const event = new Event(eventType, { bubbles: true, cancelable: true }); element.dispatchEvent(event); }); // 尝试触发focus和blur来模拟用户操作 element.focus(); setTimeout(() => { element.blur(); }, 100); logMessage(`✅ [填空题] 普通填空${index + 1}已填入: ${answerText}`, 'success'); successCount++; } // 添加视觉反馈 if (option.element.style) { option.element.style.backgroundColor = '#e8f5e8'; option.element.style.border = '2px solid #4ade80'; setTimeout(() => { option.element.style.backgroundColor = ''; option.element.style.border = ''; }, 2000); } // 延迟验证答案是否真的填入了 setTimeout(() => { const currentValue = option.element.value; if (currentValue === answerText) { logMessage(`✅ [填空题] 验证成功,填空${index + 1}当前值: ${currentValue}`, 'success'); } else { logMessage(`❌ [填空题] 验证失败,填空${index + 1}期望: ${answerText}, 实际: ${currentValue}`, 'error'); logMessage(`💡 [填空题] 请手动检查并填入答案: ${answerText}`, 'warning'); } }, 1000); } catch (error) { logMessage(`❌ [填空题] 填空${index + 1}填入失败: ${error.message}`, 'error'); console.error(`[填空题] 详细错误:`, error); } } else if (option.isVirtual) { // 虚拟填空:只显示答案,不实际填入 logMessage(`📝 [填空题] 虚拟填空${index + 1}答案: ${answerText}`, 'info'); logMessage(`💡 [填空题] 请手动将答案"${answerText}"填入对应位置`, 'warning'); successCount++; } else { logMessage(`❌ [填空题] 填空${index + 1}没有找到输入框`, 'error'); } }); const success = successCount > 0; if (success) { logMessage(`✅ [填空题] 答案填入完成 (${successCount}/${fillInBlankOptions.length})`, 'success'); } else { logMessage(`❌ [填空题] 答案填入失败`, 'error'); } return success; } catch (error) { logMessage(`❌ [填空题] 填入答案时出错: ${error.message}`, 'error'); return false; } } // New dispatcher function async function selectAnswer(questionIndex, answer) { if (!currentSite) detectSite(); if (currentSite && typeof currentSite.selectAnswer === 'function') { const result = currentSite.selectAnswer(questionIndex, answer); // 如果返回的是Promise,等待它完成 if (result && typeof result.then === 'function') { return await result; } return result; } // Fallback for safety return false; } // ========================================= // 10. 可视化操作窗口(简化版)- 只显示答题日志 // ========================================= // 创建操作窗口 - 仅在预览页面显示 function createControlWindow() { // 检查是否为预览页面 const isPreviewPage = window.location.href.includes('/exam-ans/mooc2/exam/preview'); if (!isPreviewPage) { return null; } // 如果窗口已存在,直接返回 if (document.getElementById('exam-auto-control')) { return document.getElementById('exam-auto-control'); } const controlWindow = document.createElement('div'); controlWindow.id = 'exam-auto-control'; // 简约现代的窗口样式 controlWindow.style.cssText = ` position: fixed; top: 20px; right: 20px; width: 450px; max-height: 400px; background: #ffffff; border: 1px solid #e1e5e9; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; backdrop-filter: blur(10px); `; // 简约的标题栏 const titleBar = document.createElement('div'); titleBar.style.cssText = ` background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 16px; cursor: move; font-size: 14px; font-weight: 600; display: flex; align-items: center; justify-content: space-between; `; const titleText = document.createElement('span'); titleText.textContent = '🤖 AI答题助手'; const minimizeBtn = document.createElement('button'); minimizeBtn.innerHTML = '−'; minimizeBtn.style.cssText = ` background: rgba(255, 255, 255, 0.2); border: none; color: white; width: 24px; height: 24px; border-radius: 4px; cursor: pointer; font-size: 16px; line-height: 1; `; titleBar.appendChild(titleText); titleBar.appendChild(minimizeBtn); // 日志显示区域 const logArea = document.createElement('div'); logArea.id = 'log-display'; logArea.style.cssText = ` height: 320px; overflow-y: auto; padding: 16px; font-size: 13px; line-height: 1.5; background: #fafbfc; `; // 添加最小化功能 let isMinimized = false; minimizeBtn.addEventListener('click', () => { isMinimized = !isMinimized; if (isMinimized) { logArea.style.display = 'none'; controlWindow.style.height = 'auto'; minimizeBtn.innerHTML = '+'; } else { logArea.style.display = 'block'; minimizeBtn.innerHTML = '−'; } }); controlWindow.appendChild(titleBar); controlWindow.appendChild(logArea); document.body.appendChild(controlWindow); // 添加拖动功能 makeDraggable(controlWindow, titleBar); return controlWindow; } // 简化的拖动功能 function makeDraggable(element, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; handle.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } // 简约的日志显示函数 function logMessage(message, type = 'info') { const logArea = document.getElementById('log-display'); if (!logArea) return; const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false }); const colors = { 'info': '#6c757d', 'success': '#28a745', 'warning': '#ffc107', 'error': '#dc3545', 'question': '#007bff', 'answer': '#17a2b8' }; const logEntry = document.createElement('div'); logEntry.style.cssText = ` margin-bottom: 8px; padding: 8px 12px; border-radius: 6px; background: white; border-left: 3px solid ${colors[type] || colors.info}; font-size: 12px; line-height: 1.4; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); `; const timeSpan = document.createElement('span'); timeSpan.style.cssText = ` color: #6c757d; font-weight: 500; margin-right: 8px; `; timeSpan.textContent = `[${timestamp}]`; const messageSpan = document.createElement('span'); messageSpan.style.color = colors[type] || colors.info; messageSpan.textContent = message; logEntry.appendChild(timeSpan); logEntry.appendChild(messageSpan); logArea.appendChild(logEntry); logArea.scrollTop = logArea.scrollHeight; // 限制日志条数 if (logArea.children.length > 50) { logArea.removeChild(logArea.firstChild); } } // 专门用于显示题目和答案的日志函数 function logQuestionAnswer(question, answer, questionType = '') { const logArea = document.getElementById('log-display'); if (!logArea) return; const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false }); const logEntry = document.createElement('div'); logEntry.style.cssText = ` margin-bottom: 12px; padding: 12px; border-radius: 8px; background: linear-gradient(135deg, #f8f9ff 0%, #fff 100%); border: 1px solid #e3f2fd; font-size: 12px; line-height: 1.5; `; const timeSpan = document.createElement('div'); timeSpan.style.cssText = ` color: #666; font-weight: 500; margin-bottom: 6px; font-size: 11px; `; timeSpan.textContent = `[${timestamp}] ${questionType}`; const questionDiv = document.createElement('div'); questionDiv.style.cssText = ` color: #333; margin-bottom: 6px; font-weight: 500; `; questionDiv.textContent = question.length > 80 ? question.substring(0, 80) + '...' : question; const answerDiv = document.createElement('div'); answerDiv.style.cssText = ` color: #28a745; font-weight: 600; padding: 4px 8px; background: rgba(40, 167, 69, 0.1); border-radius: 4px; display: inline-block; `; answerDiv.textContent = `答案:${answer}`; logEntry.appendChild(timeSpan); logEntry.appendChild(questionDiv); logEntry.appendChild(answerDiv); logArea.appendChild(logEntry); logArea.scrollTop = logArea.scrollHeight; // 限制日志条数 if (logArea.children.length > 50) { logArea.removeChild(logArea.firstChild); } } // ========================================= // 单题答题函数 // ========================================= async function answerSingleQuestion(questionIndex) { try { const questions = getExamQuestions(); if (!questions || questionIndex >= questions.length) { logMessage(`❌ 第 ${questionIndex + 1} 题: 题目不存在`, 'error'); return null; } const questionData = questions[questionIndex]; logMessage(`📝 正在答第 ${questionIndex + 1} 题: ${questionData.questionType}`, 'info'); // 调用云端API获取答案 const apiResponse = await callCloudAPI(questionData); if (!apiResponse || !apiResponse.answer) { logMessage(`❌ 第 ${questionIndex + 1} 题: 未获取到答案`, 'error'); return null; } const answer = apiResponse.answer.trim(); logMessage(`💡 第 ${questionIndex + 1} 题答案: ${answer}`, 'success'); // 选择答案 const selectSuccess = await selectAnswer(questionIndex, answer); if (selectSuccess) { logMessage(`✅ 第 ${questionIndex + 1} 题答题成功`, 'success'); return { questionIndex: questionIndex + 1, answer: answer, success: true }; } else { logMessage(`❌ 第 ${questionIndex + 1} 题: 答案选择失败`, 'error'); return null; } } catch (error) { logMessage(`❌ 第 ${questionIndex + 1} 题答题异常: ${error.message}`, 'error'); return null; } } // 自动开始答题 async function autoStartAnswering() { try { // 等待页面加载完成 await new Promise(resolve => setTimeout(resolve, 1500)); const currentUrl = window.location.href; logMessage(`🔍 当前页面URL: ${currentUrl}`, 'info'); // 检查URL类型并执行相应操作 if (currentUrl.includes('exam-ans/exam/test/reVersionTestStartNew')) { // 需要点击整卷预览的页面 logMessage('📄 检测到考试开始页面,查找整卷预览按钮...', 'info'); const previewButton = document.querySelector('.sub-button a.completeBtn'); if (previewButton && previewButton.textContent.includes('整卷预览')) { logMessage('📄 找到整卷预览按钮,将自动点击...', 'info'); setTimeout(() => { if (typeof pageWindow.topreview === 'function') { logMessage('⚡️ 直接调用页面函数 topreview()。', 'info'); pageWindow.topreview(); } else { logMessage('⚠️ 页面函数 topreview 不存在,回退到模拟点击。', 'warning'); previewButton.click(); } }, 1500); return; // 停止执行,因为页面将重定向 } else { logMessage('❌ 未找到整卷预览按钮', 'warning'); } } else if (currentUrl.includes('exam-ans/mooc2/exam/preview')) { // 答题页面,开始自动答题 logMessage('📝 检测到答题预览页面,开始自动答题...', 'info'); // 检测题目 const questions = getExamQuestions(); if (questions && questions.length > 0) { logMessage(`发现 ${questions.length} 道题目,开始自动答题`, 'success'); const results = await autoAnswerAllQuestions(2000); if (results.length > 0) { logMessage(`自动答题完成,成功 ${results.length} 题`, 'success'); } else { logMessage('自动答题未成功,请检查页面', 'warning'); } } else { logMessage('未发现题目,可能页面还在加载中', 'info'); } } else { // 其他页面,使用原有逻辑 logMessage('🔍 其他页面,使用通用检测逻辑...', 'info'); // 超星学习通考试页面检查整卷预览(兼容旧逻辑) if (currentSite && currentSite.name === '超星学习通') { const previewButton = document.querySelector('.sub-button a.completeBtn'); if (previewButton && previewButton.textContent.includes('整卷预览')) { logMessage('📄 检测到单题模式,将自动点击"整卷预览"...', 'info'); setTimeout(() => { if (typeof pageWindow.topreview === 'function') { logMessage('⚡️ 直接调用页面函数 topreview()。', 'info'); pageWindow.topreview(); } else { logMessage('⚠️ 页面函数 topreview 不存在,回退到模拟点击。', 'warning'); previewButton.click(); } }, 1500); return; // 停止执行,因为页面将重定向 } } // 检测题目 const questions = getExamQuestions(); if (questions && questions.length > 0) { logMessage(`发现 ${questions.length} 道题目,开始自动答题`, 'success'); const results = await autoAnswerAllQuestions(2000); if (results.length > 0) { logMessage(`自动答题完成,成功 ${results.length} 题`, 'success'); } else { logMessage('自动答题未成功,请检查页面', 'warning'); } } else { logMessage('未发现题目,可能不是答题页面', 'info'); } } } catch (error) { logMessage(`自动答题启动失败: ${error.message}`, 'error'); } } // 初始化应用 async function initializeApp() { try { // 创建日志显示窗口 createControlWindow(); // 添加429错误的特殊处理 window.addEventListener('unhandledrejection', event => { if (event.reason && event.reason.message && event.reason.message.includes('429')) { logMessage('⚠️ 请求过于频繁,请稍后重试', 'warning'); } }); // Detect current site detectSite(); // 初始化Token管理 const tokenResult = await TokenManager.initialize(); if (!tokenResult.hasToken) { logMessage('❌ Token未设置,请先配置Token', 'error'); return; } // 检查是否是学习页面 if (currentSite && currentSite.autoStudy) { logMessage('🎓 学习页面检测成功,自动学习功能已启动', 'success'); logMessage('📹 视频自动播放功能已启用', 'info'); logMessage('📝 章节测验自动答题功能已启用', 'info'); logMessage('⏭️ 自动进入下一节功能已启用', 'info'); return; } logMessage('✅ 云端AI答题助手启动完成', 'success'); // 对于答题页面,自动开始答题 await autoStartAnswering(); } catch (error) { logMessage(`❌ 初始化失败: ${error.message}`, 'error'); } } // 防止重复初始化 if (pageWindow.AI_ASSISTANT_INITIALIZED) { return; } pageWindow.AI_ASSISTANT_INITIALIZED = true; if (pageDocument.readyState === 'loading') { pageDocument.addEventListener('DOMContentLoaded', () => { setTimeout(initializeApp, 800); }); } else { setTimeout(initializeApp, 800); } // 脚本已启动 // ========================================= // iframe视频控制功能 // ========================================= // 创建视频倍速控制浮窗 function createVideoSpeedPanel() { if (document.getElementById('videoSpeedPanel')) { return; // 已存在,不重复创建 } const speedPanel = document.createElement('div'); speedPanel.id = 'videoSpeedPanel'; speedPanel.innerHTML = `
🎬 视频倍速
当前倍速: 1x
`; // 添加样式 const style = document.createElement('style'); style.textContent = ` #videoSpeedPanel { position: fixed; top: 120px; right: 20px; width: 160px; background: white; border: 1px solid #ddd; border-radius: 6px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); z-index: 10000; font-family: Arial, sans-serif; display: none; } .speed-panel-header { background: #f5f5f5; padding: 8px 12px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #333; } .speed-panel-close { background: none; border: none; color: #666; font-size: 16px; cursor: pointer; padding: 0; width: 20px; height: 20px; } .speed-panel-close:hover { color: #333; } .speed-panel-content { padding: 10px; } .speed-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } .speed-btn { background: #f9f9f9; border: 1px solid #ddd; border-radius: 3px; padding: 6px; font-size: 12px; cursor: pointer; color: #333; } .speed-btn:hover { background: #e9e9e9; } .speed-btn.active { background: #007cba; border-color: #007cba; color: white; } .speed-info { text-align: center; font-size: 11px; color: #666; margin-top: 8px; padding-top: 6px; border-top: 1px solid #eee; } `; document.head.appendChild(style); document.body.appendChild(speedPanel); // 绑定事件 bindVideoSpeedEvents(); } // 绑定视频倍速控制事件 function bindVideoSpeedEvents() { const speedPanel = document.getElementById('videoSpeedPanel'); const closeBtn = speedPanel.querySelector('.speed-panel-close'); const speedBtns = speedPanel.querySelectorAll('.speed-btn'); const currentSpeedSpan = document.getElementById('currentSpeed'); // 关闭按钮 closeBtn.addEventListener('click', () => { speedPanel.style.display = 'none'; }); // 倍速按钮 speedBtns.forEach(btn => { btn.addEventListener('click', () => { const speed = parseFloat(btn.dataset.speed); setVideoSpeed(speed); // 更新按钮状态 speedBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); // 更新显示 currentSpeedSpan.textContent = speed + 'x'; }); }); // 根据保存的倍速设置初始状态 const savedSpeed = getSavedVideoSpeed(); speedBtns.forEach(btn => { const btnSpeed = parseFloat(btn.dataset.speed); if (btnSpeed === savedSpeed) { btn.classList.add('active'); currentSpeedSpan.textContent = `${savedSpeed}x`; } }); // 如果没有找到匹配的按钮,默认选中1x if (!speedPanel.querySelector('.speed-btn.active')) { speedBtns[0].classList.add('active'); } } // 设置视频播放倍速 function setVideoSpeed(speed) { // 保存倍速设置到localStorage try { localStorage.setItem('videoPlaybackRate', speed.toString()); console.log(`💾 [倍速设置] 已保存倍速: ${speed}x`); } catch (e) { console.warn('⚠️ [倍速设置] 保存失败:', e); } // 查找所有视频元素 const videos = document.querySelectorAll('video'); const iframes = document.querySelectorAll('iframe'); let videoCount = 0; // 设置直接的video元素倍速 videos.forEach(video => { if (video.readyState >= 1) { // 确保视频已加载 video.playbackRate = speed; videoCount++; } }); // 设置iframe中的video元素倍速 iframes.forEach(iframe => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeVideos = iframeDoc.querySelectorAll('video'); iframeVideos.forEach(video => { if (video.readyState >= 1) { video.playbackRate = speed; videoCount++; } }); } catch (e) { // 跨域iframe无法访问,忽略错误 } }); console.log(`🎬 [倍速设置] 已设置 ${videoCount} 个视频为 ${speed}x 倍速`); } // 获取保存的视频倍速设置 function getSavedVideoSpeed() { try { const savedSpeed = localStorage.getItem('videoPlaybackRate'); if (savedSpeed) { const speed = parseFloat(savedSpeed); if (speed > 0 && speed <= 16) { // 合理范围检查 console.log(`📖 [倍速设置] 读取保存的倍速: ${speed}x`); return speed; } } } catch (e) { console.warn('⚠️ [倍速设置] 读取失败:', e); } console.log(`📖 [倍速设置] 使用默认倍速: 1x`); return 1; // 默认倍速 } // 应用保存的倍速到视频 function applySavedSpeedToVideo(video) { if (!video) return; const savedSpeed = getSavedVideoSpeed(); if (savedSpeed !== 1) { try { video.playbackRate = savedSpeed; console.log(`🎬 [倍速恢复] 视频倍速已设置为: ${savedSpeed}x`); } catch (e) { console.warn('⚠️ [倍速恢复] 设置失败:', e); } } } // 显示视频倍速控制面板 function showVideoSpeedPanel() { createVideoSpeedPanel(); const speedPanel = document.getElementById('videoSpeedPanel'); speedPanel.style.display = 'block'; } // 检测并控制iframe中的视频 function controlIframeVideo() { // 查找视频iframe const videoIframe = document.querySelector('iframe.ans-attach-online.ans-insertvideo-online'); if (!videoIframe) { return false; } // 显示视频倍速控制面板 showVideoSpeedPanel(); try { // 访问iframe内容 const iframeDoc = videoIframe.contentDocument || videoIframe.contentWindow.document; if (!iframeDoc) { return false; } // 查找视频元素 const playButton = iframeDoc.querySelector('.vjs-big-play-button'); const video = iframeDoc.querySelector('video'); if (!playButton && !video) { return false; } if (video) { video.muted = true; video.volume = 0.01; video.autoplay = true; if (video.paused && !video.ended) { if (playButton && playButton.offsetParent !== null) { playButton.click(); } else { video.play().then(() => { applySavedSpeedToVideo(video); }).catch(() => {}); } } } return true; } catch (error) { return false; } } // ========================================= // 超星字体解密 - 完全按照原版实现 // ========================================= function base64ToUint8Array(base64) { var data = window.atob(base64); var buffer = new Uint8Array(data.length); for (var i = 0; i < data.length; ++i) { buffer[i] = data.charCodeAt(i); } return buffer; } function chaoxingFontDecrypt(doc = document) { // 判断是否存在加密字体 - 原版实现 var $tip = Array.from(doc.querySelectorAll('style')).find(style => (style.textContent || style.innerHTML || '').includes('font-cxsecret') ); if (!$tip) return false; // 解析font-cxsecret字体 - 原版实现 var fontText = $tip.textContent || $tip.innerHTML; var fontMatch = fontText.match(/base64,([\w\W]+?)'/); if (!fontMatch || !fontMatch[1]) return false; var font = Typr.parse(base64ToUint8Array(fontMatch[1]))[0]; // 匹配解密字体 - 原版实现 var table = JSON.parse(GM_getResourceText('Table')); var match = {}; for (var i = 19968; i < 40870; i++) { // 中文[19968, 40869] var glyph = Typr.U.codeToGlyph(font, i); if (!glyph) continue; var path = Typr.U.glyphToPath(font, glyph); var pathMD5 = md5(JSON.stringify(path)).slice(24); // 8位即可区分 if (table[pathMD5]) { match[i] = table[pathMD5]; } } // 替换加密字体 - 原版实现 var elements = doc.querySelectorAll('.font-cxsecret'); elements.forEach(function (element) { var html = element.innerHTML; for (var key in match) { if (match[key]) { var keyChar = String.fromCharCode(key); var valueChar = String.fromCharCode(match[key]); var regex = new RegExp(keyChar, 'g'); html = html.replace(regex, valueChar); } } element.innerHTML = html; element.classList.remove('font-cxsecret'); }); return elements.length > 0; } // 重新启动定期检查 window.restartMainInterval = function () { if (window.mainIntervalId) { clearInterval(window.mainIntervalId); } }; // 恢复被字体解密破坏的页面 window.restorePage = function () { let restoredCount = 0; // 恢复被标记为已解密的元素 const decryptedElements = document.querySelectorAll('[data-decrypted="true"]'); decryptedElements.forEach(element => { // 重新添加font-cxsecret类(如果原本有的话) element.classList.add('font-cxsecret'); element.removeAttribute('data-decrypted'); restoredCount++; }); // 恢复原版解密标记的元素 const originalDecryptedElements = document.querySelectorAll('[data-decrypted-original="true"]'); originalDecryptedElements.forEach(element => { element.classList.add('font-cxsecret'); element.removeAttribute('data-decrypted-original'); element.style.background = ''; element.style.borderColor = ''; restoredCount++; }); // 恢复内联样式解密的元素 const inlineDecryptedElements = document.querySelectorAll('[data-decrypted-inline="true"]'); inlineDecryptedElements.forEach(element => { element.removeAttribute('data-decrypted-inline'); restoredCount++; }); return { restoredCount: restoredCount, success: restoredCount > 0 }; }; // 直接应用原版超星字体解密.js的核心逻辑 window.applyChaoxingFontDecryptOriginal = function () { try { // 判断是否存在加密字体 - 使用原版的jQuery风格检测 const allStyles = document.querySelectorAll('style'); let $tip = null; for (const style of allStyles) { const content = style.textContent || style.innerHTML || ''; if (content.includes('font-cxsecret')) { $tip = style; break; } } if (!$tip) { console.log('ℹ️ [原版解密] 未找到font-cxsecret样式'); return false; } console.log('✅ [原版解密] 找到font-cxsecret样式'); // 检查是否有加密元素 const fontSecretElements = document.querySelectorAll('.font-cxsecret'); if (fontSecretElements.length === 0) { console.log('ℹ️ [原版解密] 未找到.font-cxsecret元素'); return false; } console.log(`✅ [原版解密] 找到 ${fontSecretElements.length} 个加密元素`); // 应用原版的解密逻辑:直接移除font-cxsecret类 // 这是原版脚本的最后一步,也是最关键的一步 let processedCount = 0; fontSecretElements.forEach((element, index) => { try { const originalText = element.textContent || ''; // 移除font-cxsecret类 - 这是原版脚本的核心操作 element.classList.remove('font-cxsecret'); element.setAttribute('data-decrypted-original', 'true'); const newText = element.textContent || ''; console.log(` 元素 ${index + 1}: "${originalText.substring(0, 30)}..." → "${newText.substring(0, 30)}..."`); processedCount++; } catch (error) { console.log(`⚠️ [原版解密] 处理元素 ${index + 1} 失败:`, error.message); } }); return processedCount > 0; } catch (error) { console.log('❌ [原版解密] 执行失败:', error.message); return false; } }; // 批量解码页面文本 window.decodePageTexts = async function () { console.log('🔄 [批量解码] 开始解码页面中的所有乱码文本...'); try { // 获取AI映射表 const mapping = await buildAIDecodingMapping(); if (Object.keys(mapping).length === 0) { console.log('⚠️ 未获取到有效的字符映射表'); return false; } // 应用映射到页面元素 const elements = document.querySelectorAll('.fontLabel, .after, .CeYan *'); let decodedCount = 0; for (const element of elements) { const originalText = element.textContent || ''; if (originalText && /[\u5600-\u56FF]/.test(originalText)) { let decodedText = originalText; // 应用字符映射 for (const [garbled, decoded] of Object.entries(mapping)) { decodedText = decodedText.replace(new RegExp(garbled, 'g'), decoded); } if (decodedText !== originalText) { // 更新元素文本(可选,谨慎使用) // element.textContent = decodedText; decodedCount++; } } } console.log(`✅ 批量解码完成,共处理 ${decodedCount} 个文本`); return true; } catch (error) { console.log(`❌ 批量解码失败: ${error.message}`); return false; } }; // ========================================= // 多类型iframe检测和处理功能 // ========================================= // iframe类型定义 const IFRAME_TYPES = { VIDEO: 'video', // 视频 CHAPTER_TEST: 'test', // 章节测验 DOCUMENT: 'document', // PPT文档 DISCUSSION: 'discussion', // 章节讨论 UNKNOWN: 'unknown' // 未知类型 }; // 递归查找最深层iframe function findDeepestIframes(doc = document, depth = 0) { const iframes = doc.querySelectorAll('iframe'); let deepestFrames = []; for (const iframe of iframes) { const src = iframe.src || ''; // 提前过滤UEditor iframe if (src.startsWith('javascript:void') || src.includes('UE.instants') || src.includes('ueditorInstant')) { continue; } try { const subDoc = iframe.contentDocument; if (subDoc) { // 检查是否是UEditor的body if (subDoc.body && subDoc.body.className === 'view') { continue; } const video = subDoc.querySelector('video'); if (video) { video.muted = true; video.volume = 0.01; video.autoplay = true; if (video.paused && !video.ended) { const bigPlayBtn = subDoc.querySelector('.vjs-big-play-button'); const playControlBtn = subDoc.querySelector('.vjs-play-control'); if (bigPlayBtn && bigPlayBtn.offsetParent !== null) { bigPlayBtn.click(); } else if (playControlBtn && playControlBtn.offsetParent !== null) { playControlBtn.click(); } else { video.play().catch(() => {}); } } } // 递归查找子iframe const childFrames = findDeepestIframes(subDoc, depth + 1); if (childFrames.length > 0) { // 有子iframe,使用子iframe deepestFrames.push(...childFrames); } else { // 没有子iframe,这是最深层 const frameType = classifyIframe(src, subDoc); // 只记录iframe信息,不在递归中处理 if (frameType !== IFRAME_TYPES.UNKNOWN) { deepestFrames.push({ iframe, doc: subDoc, src: src, type: frameType, accessible: true, depth: depth }); } } } else { // 无法访问,也算最深层 const frameType = classifyIframe(src, null); if (frameType !== IFRAME_TYPES.UNKNOWN) { deepestFrames.push({ iframe, doc: null, src: src, type: frameType, accessible: false, depth: depth }); } } } catch (e) { // 跨域,也算最深层 const frameType = classifyIframe(src, null); if (frameType !== IFRAME_TYPES.UNKNOWN) { deepestFrames.push({ iframe, doc: null, src: src, type: frameType, accessible: false, depth: depth, error: e.message }); } } } return deepestFrames; } // 根据URL和DOM特征分类iframe function classifyIframe(src, doc) { // 0. 过滤UEditor内部iframe if (src.startsWith('javascript:void') || src.includes('UE.instants') || src.includes('ueditorInstant') || (doc && doc.body && doc.body.className === 'view')) { return IFRAME_TYPES.UNKNOWN; // 标记为unknown,后续会被忽略 } // 1. 视频iframe if (src.includes('mooc1.chaoxing.com/ananas/modules/video') || src.includes('ans-attach-online') || src.includes('insertvideo')) { return IFRAME_TYPES.VIDEO; } // 2. 章节测验iframe if (src.includes('mooc1.chaoxing.com/mooc-ans/api/work') || src.includes('/mooc-ans/work/doHomeWorkNew/') || src.includes('/ananas/modules/work/')) { return IFRAME_TYPES.CHAPTER_TEST; } // 3. PPT文档iframe if (src.includes('pan-yz.chaoxing.com/screen/file') || src.includes('/screen/file')) { return IFRAME_TYPES.DOCUMENT; } // 4. 章节讨论/评论iframe if (src.includes('mooc1.chaoxing.com/mooc-ans/bbscircle/chapter') || src.includes('/bbscircle/chapter') || src.includes('insertbbs/index.html') || src.includes('/modules/insertbbs/') || src.includes('discussion') || src.includes('comment')) { return IFRAME_TYPES.DISCUSSION; } // 5. 通过DOM特征进一步判断 if (doc) { try { // 检查视频元素 if (doc.querySelector('video') || doc.querySelector('.vjs-big-play-button')) { console.log('📹 [分类] 通过DOM识别为视频iframe'); return IFRAME_TYPES.VIDEO; } // 检查测验元素 if (doc.querySelector('.singleQuesId') || doc.querySelector('.CeYan h3') || doc.querySelector('.questionLi')) { console.log('📝 [分类] 通过DOM识别为章节测验iframe'); return IFRAME_TYPES.CHAPTER_TEST; } // 检查文档元素 if (doc.querySelector('.ppt-container') || doc.querySelector('.document-viewer') || doc.querySelector('canvas')) { console.log('📄 [分类] 通过DOM识别为文档iframe'); return IFRAME_TYPES.DOCUMENT; } // 检查讨论元素 if (doc.querySelector('.bbs-content') || doc.querySelector('.discussion-area')) { console.log('💬 [分类] 通过DOM识别为讨论iframe'); return IFRAME_TYPES.DISCUSSION; } } catch (e) { console.log(`⚠️ [分类] DOM检查时出错: ${e.message}`); } } console.log('❓ [分类] 未知类型iframe'); return IFRAME_TYPES.UNKNOWN; } // 处理不同类型的iframe内容 async function processIframeContent() { // 基本状态检查 if (GLOBAL_STATE.isAnswering || GLOBAL_STATE.isChapterTesting) { console.log('⏸️ [iframe处理] 已有任务在进行中,跳过'); return { processed: false, reason: 'busy' }; } // 递归查找最深层iframe const deepestFrames = findDeepestIframes(); if (deepestFrames.length === 0) { console.log('ℹ️ [iframe处理] 未找到任何iframe,尝试直接进入下一节'); // 没有iframe时,直接尝试点击下一节 const nextSectionResult = goToNextSection(); if (nextSectionResult) { console.log('✅ [iframe处理] 已点击下一节按钮'); return { processed: true, reason: 'no_frames_next_section', action: 'next_section' }; } else { console.log('❌ [iframe处理] 未找到下一节按钮'); return { processed: false, reason: 'no_frames_no_next' }; } } console.log(`🔍 找到 ${deepestFrames.length} 个最深层iframe`); deepestFrames.forEach(frame => { console.log(`📋 深度${frame.depth}: ${frame.type} - ${frame.src}`); }); // 按页面顺序处理iframe - 每次只处理一个,处理完就返回 console.log('📋 [顺序处理] 按页面顺序逐个处理iframe内容'); // 按照页面中的顺序处理第一个未处理的iframe for (let i = 0; i < deepestFrames.length; i++) { const frame = deepestFrames[i]; console.log(`📋 [顺序处理] 处理第${i + 1}个iframe: ${frame.type}`); // 根据iframe类型调用相应的处理函数 switch (frame.type) { case IFRAME_TYPES.CHAPTER_TEST: const testResult = await handleChapterTest([frame]); if (testResult) { console.log(`✅ [顺序处理] 第${i + 1}个iframe(章节测验)处理完成,本轮结束`); return { processed: true, type: 'chapter_test' }; } break; case IFRAME_TYPES.VIDEO: const videoResult = handleVideoFrames([frame]); if (videoResult) { console.log(`✅ [顺序处理] 第${i + 1}个iframe(视频)处理完成,本轮结束`); return { processed: true, type: 'video' }; } break; case IFRAME_TYPES.DOCUMENT: const docResult = handleDocumentFrames([frame]); if (docResult) { console.log(`✅ [顺序处理] 第${i + 1}个iframe(文档)处理完成,本轮结束`); return { processed: true, type: 'document' }; } break; case IFRAME_TYPES.DISCUSSION: // 检查是否是评论/讨论页面 const isCommentPage = frame.src.includes('insertbbs/index.html') || frame.src.includes('/modules/insertbbs/') || frame.src.includes('bbscircle/chapter') || frame.src.includes('discussion') || frame.src.includes('comment'); if (isCommentPage) { console.log(`💬 [顺序处理] 第${i + 1}个iframe确认为评论页面,标记为完成`); } else { console.log(`💬 [顺序处理] 第${i + 1}个iframe为普通讨论区域,标记为完成`); } console.log(`✅ [顺序处理] 第${i + 1}个iframe(讨论)处理完成,本轮结束`); return { processed: true, type: 'discussion' }; default: console.log(`❓ [顺序处理] 第${i + 1}个iframe类型未知(${frame.type}),跳过`); continue; // 跳过未知类型,继续处理下一个 } } // 如果没有找到任何需要处理的iframe console.log('ℹ️ [顺序处理] 无需处理的内容'); return { processed: false, type: 'none' }; } // 处理章节测验iframe async function handleChapterTest(testFrames) { for (const frame of testFrames) { if (!frame.accessible || !frame.doc) { console.log('❌ [章节测验] iframe不可访问,跳过'); continue; } const doc = frame.doc; const iframeWindow = frame.iframe.contentWindow; // 检查章节测验是否已完成 const completedStatus = doc.querySelector('.testTit_status_complete'); if (completedStatus && completedStatus.textContent.includes('已完成')) { console.log('✅ [章节测验] 检测到已完成状态,跳过答题'); return true; // 返回true表示已处理(已完成) } // 也检查其他可能的完成状态标识 const completedDiv = doc.querySelector('.fr.testTit_status.testTit_status_complete'); if (completedDiv && completedDiv.textContent.includes('已完成')) { console.log('✅ [章节测验] 检测到已完成状态(方式2),跳过答题'); return true; } // 字体解密 chaoxingFontDecrypt(doc); // 获取题目 const questions = doc.querySelectorAll('.singleQuesId'); console.log(`📄 共 ${questions.length} 道题目`); // 如果没有题目,直接返回 if (questions.length === 0) { continue; } // 设置答题标记,开始答题流程 GLOBAL_STATE.isChapterTesting = true; GLOBAL_STATE.isAnswering = true; console.log('🚀 [章节测验] 开始答题流程'); // 答题循环 for (let i = 0; i < questions.length; i++) { const qEl = questions[i]; const typeText = qEl.querySelector('.newZy_TItle')?.innerText || '未知类型'; let content = qEl.querySelector('.fontLabel')?.innerText?.trim() || ''; // 清理题目内容 content = content.replace(/【[^】]*题】/g, '').trim(); console.log(`📝 [${i + 1}/${questions.length}] ${typeText}`); // 获取选项 const options = qEl.querySelectorAll('li'); const optionsData = []; // 根据题型分别处理选项解析 const cleanQuestionType = typeText.replace(/【|】/g, ''); options.forEach(opt => { let spanElement = null; let label = ''; // 分别处理单选题和多选题的选项 if (cleanQuestionType.includes('多选题')) { spanElement = opt.querySelector('span.num_option_dx'); } else { spanElement = opt.querySelector('span.num_option'); } label = spanElement?.innerText || ''; const aElement = opt.querySelector('a.after'); const text = aElement?.innerText || ''; const dataValue = spanElement?.getAttribute('data') || ''; if (label && text) { optionsData.push({ label: label, content: text, value: dataValue, element: opt, questionType: cleanQuestionType }); } }); // 调用AI答题 try { const cleanQuestionType = typeText.replace(/【|】/g, ''); const questionData = { stem: content, questionType: cleanQuestionType, options: optionsData }; const apiResponse = await callCloudAPI(questionData); if (apiResponse && apiResponse.answer) { const answer = apiResponse.answer.trim(); console.log(` ✅ 答案: ${answer}`); // 选择答案 console.log(`🎯 [答题] 选择答案: ${answer}`); if (cleanQuestionType.includes('填空题')) { // 填空题处理 console.log(`📝 [填空题] 开始处理填空题`); // 查找填空项 const blankItems = qEl.querySelectorAll('.blankItemDiv'); console.log(`📝 [填空题] 找到 ${blankItems.length} 个填空项`); // 将答案按分隔符分割(支持多种分隔符) let answerParts = []; if (typeof answer === 'string') { // 尝试多种分隔符 if (answer.includes('|')) { answerParts = answer.split('|'); } else if (answer.includes(';')) { answerParts = answer.split(';'); } else if (answer.includes(';')) { answerParts = answer.split(';'); } else if (answer.includes(',')) { answerParts = answer.split(','); } else if (answer.includes(',')) { answerParts = answer.split(','); } else { answerParts = [answer]; // 单个答案 } } else { answerParts = [answer]; } console.log(`📝 [填空题] 答案分割结果:`, answerParts); // 为每个填空填入答案 blankItems.forEach((blankItem, blankIndex) => { if (blankIndex < answerParts.length) { const answerText = answerParts[blankIndex].trim(); console.log(`📝 [填空题] 第${blankIndex + 1}空填入: ${answerText}`); // 查找UEditor编辑器 const editorTextarea = blankItem.querySelector('textarea[name*="answerEditor"]'); if (editorTextarea) { const editorId = editorTextarea.id; try { let fillSuccess = false; // 方法1: 尝试通过UEditor API填入答案 if (iframeWindow && iframeWindow.UE && iframeWindow.UE.getEditor) { try { const editor = iframeWindow.UE.getEditor(editorId); if (editor && editor.setContent) { editor.setContent(answerText); fillSuccess = true; } } catch (ueError) { // UEditor API调用失败,使用备用方法 } } // 方法2: 直接设置textarea值 if (!fillSuccess) { editorTextarea.value = answerText; // 触发多种事件确保值被识别 const events = ['input', 'change', 'blur', 'keyup']; events.forEach(eventType => { try { // 使用iframe的window上下文创建事件 const event = new (iframeWindow || window).Event(eventType, { bubbles: true }); editorTextarea.dispatchEvent(event); } catch (eventError) { // 备用方法:使用原生事件 const event = doc.createEvent('Event'); event.initEvent(eventType, true, true); editorTextarea.dispatchEvent(event); } }); fillSuccess = true; } // 方法3: 尝试查找并填入InpDIV if (!fillSuccess) { const inpDiv = blankItem.querySelector('.InpDIV'); if (inpDiv) { inpDiv.innerHTML = answerText; inpDiv.textContent = answerText; fillSuccess = true; } } } catch (error) { // 填入失败,跳过 } } } }); } else if (cleanQuestionType.includes('判断题')) { for (const opt of options) { const text = opt.querySelector('a')?.innerText || ''; if ((answer === 'T' && (text === '对' || text === '正确' || text === '是')) || (answer === 'F' && (text === '错' || text === '错误' || text === '否'))) { opt.click(); console.log(` 🎯 已选择: ${text}`); break; } } } else if (cleanQuestionType.includes('多选题')) { // 多选题处理 for (const opt of options) { const spanElement = opt.querySelector('span.num_option_dx'); const label = spanElement?.innerText || ''; if (answer.includes(label)) { // 多选题:调用addMultipleChoice函数 if (typeof iframeWindow.addMultipleChoice === 'function') { try { iframeWindow.addMultipleChoice(opt); console.log(` 🎯 已选择多选项: ${label}`); } catch (error) { opt.click(); console.log(` 🎯 已选择多选项: ${label} (备用)`); } } else { opt.click(); console.log(` 🎯 已选择多选项: ${label}`); } } } } else { // 单选题处理 for (const opt of options) { const spanElement = opt.querySelector('span.num_option'); const label = spanElement?.innerText || ''; if (answer.includes(label)) { opt.click(); console.log(` 🎯 已选择: ${label}`); break; } } } } } catch (error) { console.log(` ❌ 答题异常: ${error.message}`); } await new Promise(resolve => setTimeout(resolve, 1000)); } console.log('✅ [章节测验] 答题完成,准备提交'); GLOBAL_STATE.isAnswering = false; // 提交测验 setTimeout(() => { if (iframeWindow.btnBlueSubmit) { iframeWindow.btnBlueSubmit(); console.log('✅ [自动提交] 测验提交成功'); // 提交后立即开始监控确认弹窗 setTimeout(() => { monitorSubmitDialog(); }, 500); // 等待500ms让弹窗出现 } }, 2000); // 注意:不在这里重置isChapterTesting,等提交确认弹窗处理后再重置 return true; } return false; } // 处理视频iframe - 支持传入特定的iframe数组或自动查找 function handleVideoFrames(specificFrames = null) { let videoContainers; if (specificFrames && specificFrames.length > 0) { // 如果传入了特定的iframe,从这些iframe中查找视频容器 videoContainers = []; specificFrames.forEach(frame => { if (frame.iframe && frame.type === IFRAME_TYPES.VIDEO) { // 查找包含这个iframe的视频容器 const container = frame.iframe.closest('.ans-attach-ct.videoContainer'); if (container) { videoContainers.push(container); } } }); console.log(`📹 [视频处理] 处理指定的 ${videoContainers.length} 个视频容器`); } else { // 原有逻辑:查找所有视频容器 const wrapContainer = document.querySelector('.wrap'); if (wrapContainer) { videoContainers = wrapContainer.querySelectorAll('.ans-attach-ct.videoContainer'); } else { videoContainers = document.querySelectorAll('.ans-attach-ct.videoContainer'); } console.log(`📹 [视频处理] 找到 ${videoContainers.length} 个视频容器`); } if (videoContainers.length === 0) { return false; } let processedAny = false; let playingVideoFound = false; // 第一步:检查所有视频的任务点状态 const videoStates = []; videoContainers.forEach((container, index) => { const videoState = { index: index + 1, container: container, isCompleted: false, isPlaying: false, needsPlay: false }; try { // 检查任务点是否已完成 if (container.classList.contains('ans-job-finished')) { videoState.isCompleted = true; // 立即暂停已完成任务点的视频 const videoIframe = container.querySelector('iframe.ans-insertvideo-online'); if (videoIframe) { try { const videoDoc = videoIframe.contentDocument; if (videoDoc) { const video = videoDoc.querySelector('video'); if (video) { // 检查是否已经处理过 const alreadyProcessed = video.getAttribute('data-task-completed') === 'true'; if (!alreadyProcessed) { // 标记为已完成任务点的视频 video.setAttribute('data-task-completed', 'true'); video.taskCompleted = true; if (!video.paused) { video.pause(); console.log(`⏸️ [视频${index + 1}] 已暂停播放 (任务点已完成)`); } video.muted = true; video.volume = 0.01; video.autoplay = true; } } } } catch (e) { // 跨域错误,忽略 } } } else { // 检查aria-label const taskIcon = container.querySelector('.ans-job-icon'); if (taskIcon) { const ariaLabel = taskIcon.getAttribute('aria-label'); if (ariaLabel && ariaLabel.includes('已完成')) { videoState.isCompleted = true; // 立即暂停已完成任务点的视频 const videoIframe = container.querySelector('iframe.ans-insertvideo-online'); if (videoIframe) { try { const videoDoc = videoIframe.contentDocument; if (videoDoc) { const video = videoDoc.querySelector('video'); if (video) { // 检查是否已经处理过 const alreadyProcessed = video.getAttribute('data-task-completed') === 'true'; if (!alreadyProcessed) { // 标记为已完成任务点的视频 video.setAttribute('data-task-completed', 'true'); video.taskCompleted = true; if (!video.paused) { video.pause(); console.log(`⏸️ [视频${index + 1}] 已暂停播放 (任务点已完成)`); } video.muted = true; video.volume = 0.01; video.autoplay = true; } } } } catch (e) { // 跨域错误,忽略 } } } else { // 任务点未完成,检查视频状态 const videoIframe = container.querySelector('iframe.ans-insertvideo-online'); if (videoIframe) { try { const videoDoc = videoIframe.contentDocument; if (videoDoc) { const video = videoDoc.querySelector('video'); if (video) { if (!video.paused && !video.ended) { videoState.isPlaying = true; playingVideoFound = true; } else if (video.paused && !video.ended) { videoState.needsPlay = true; } } } } catch (e) { console.log(`❌ [视频${index + 1}] 跨域无法访问`); } } } } else { // 没有找到任务点图标,标记为需要跳过 console.log(`❓ [视频${index + 1}] 未找到任务点图标,标记为需要跳过`); videoState.noTaskIcon = true; } } } catch (error) { console.log(`❌ [视频${index + 1}] 检查失败:`, error.message); } videoStates.push(videoState); }); // 检查是否有视频没有任务点图标 const videosWithoutTaskIcon = videoStates.filter(state => state.noTaskIcon); if (videosWithoutTaskIcon.length > 0) { console.log(`❓ [视频处理] 没有任务点图标,标记为完成,让主循环控制跳转`); // 不再自动跳转,让主循环检测任务完成状态后跳转 return true; } // 如果有视频正在播放,跳过所有操作 if (playingVideoFound) { return true; // 返回true表示有视频在处理中 } // 处理需要播放的视频(只处理第一个未完成的) const firstUncompletedVideo = videoStates.find(state => state.needsPlay); if (firstUncompletedVideo) { console.log(`▶️ [视频处理] 开始播放视频${firstUncompletedVideo.index}`); const success = playVideo(firstUncompletedVideo.container, firstUncompletedVideo.index); if (success) { processedAny = true; } } return processedAny; } // 播放指定的视频 function playVideo(container, videoIndex) { try { const videoIframe = container.querySelector('iframe.ans-insertvideo-online'); if (!videoIframe) { console.log(`❌ [视频${videoIndex}] 未找到视频iframe`); return false; } // 显示视频倍速控制面板 showVideoSpeedPanel(); let videoDoc = null; try { videoDoc = videoIframe.contentDocument; } catch (e) { console.log(`❌ [视频${videoIndex}] 无法访问iframe内容,跨域限制`); return false; } if (!videoDoc) { console.log(`❌ [视频${videoIndex}] iframe文档为空`); return false; } const video = videoDoc.querySelector('video'); const playButton = videoDoc.querySelector('.vjs-big-play-button'); if (!video && !playButton) { console.log(`❌ [视频${videoIndex}] 未找到视频元素`); return false; } if (video) { video.muted = true; video.volume = 0.01; video.autoplay = true; // 开始播放(通过用户交互) if (video.paused && !video.ended) { // 尝试多种播放按钮选择器 const playButtons = [ videoDoc.querySelector('.vjs-big-play-button'), videoDoc.querySelector('.vjs-play-control'), playButton // 原来的选择器 ].filter(btn => btn && btn.offsetParent !== null); if (playButtons.length > 0) { playButtons[0].click(); console.log(`✅ [视频${videoIndex}] 通过点击播放按钮开始播放(非静音,1%音量)`); console.log(`🔍 [视频${videoIndex}] 使用的按钮选择器:`, playButtons[0].className); // 延迟应用倍速设置,等待播放开始 setTimeout(() => { applySavedSpeedToVideo(video); }, 500); } else { video.play().then(() => { console.log(`✅ [视频${videoIndex}] 直接播放成功(非静音,1%音量)`); // 应用保存的倍速设置 applySavedSpeedToVideo(video); }).catch((error) => { console.log(`❌ [视频${videoIndex}] 自动播放被阻止,需要用户交互: ${error.message}`); console.log(`💡 [视频${videoIndex}] 建议:在控制台执行 document.querySelector(".vjs-big-play-button").click()`); }); } return true; } } return false; } catch (error) { console.log(`❌ [视频${videoIndex}] 播放失败:`, error.message); return false; } } // 处理文档iframe(PPT等) function handleDocumentFrames(docFrames) { if (!docFrames || docFrames.length === 0) { return false; } console.log(`📄 [文档处理] 开始处理 ${docFrames.length} 个文档iframe`); let processedAny = false; docFrames.forEach((frame, index) => { if (!frame.accessible || !frame.doc) { console.log(`❌ [文档${index + 1}] iframe不可访问,跳过`); return; } try { const doc = frame.doc; const iframeWindow = frame.iframe.contentWindow; // 检查是否是PPT文档 const isPPT = frame.src.includes('pan-yz.chaoxing.com/screen/file') || frame.src.includes('/screen/file') || doc.querySelector('.ppt-container') || doc.querySelector('.document-viewer') || doc.querySelector('canvas'); if (isPPT) { console.log(`📄 [PPT${index + 1}] 开始自动滚动`); // 启动PPT自动滚动 startPPTAutoScroll(doc, iframeWindow, index + 1); processedAny = true; } else { processedAny = true; } } catch (error) { console.log(`❌ [文档${index + 1}] 处理失败:`, error.message); } }); return processedAny; } // PPT自动滚动功能 - 基于真实PPT结构 function startPPTAutoScroll(doc, iframeWindow, docIndex) { if (!doc || !iframeWindow) { return; } // 查找PPT页面元素 const fileBox = doc.querySelector('.fileBox'); const pageItems = doc.querySelectorAll('.fileBox ul li'); if (!fileBox || pageItems.length === 0) { console.log(`❌ PPT${docIndex} 未找到PPT页面结构`); return; } console.log(`📄 PPT${docIndex} 找到${pageItems.length}页内容`); // 自动滚动到底部 const autoScrollToBottom = () => { try { // 获取文档高度 const docHeight = Math.max( doc.body.scrollHeight, doc.body.offsetHeight, doc.documentElement.clientHeight, doc.documentElement.scrollHeight, doc.documentElement.offsetHeight ); console.log(`📏 PPT${docIndex} 文档高度: ${docHeight}px`); // 滚动到底部 iframeWindow.scrollTo({ top: docHeight, behavior: 'smooth' }); // 备用方法:直接设置scrollTop setTimeout(() => { doc.documentElement.scrollTop = docHeight; doc.body.scrollTop = docHeight; // 触发滚动事件 const scrollEvent = new iframeWindow.Event('scroll', { bubbles: true }); iframeWindow.dispatchEvent(scrollEvent); console.log(`📜 PPT${docIndex} 滚动到底部完成`); }, 1000); // 检查是否完成 setTimeout(() => { const currentScrollTop = Math.max( doc.documentElement.scrollTop || 0, doc.body.scrollTop || 0 ); const windowHeight = iframeWindow.innerHeight || doc.documentElement.clientHeight; const isAtBottom = (currentScrollTop + windowHeight) >= (docHeight - 10); console.log(`✅ PPT${docIndex} 滚动检查: ${isAtBottom ? '已到底部' : '未到底部'}`); if (isAtBottom) { // 手动触发完成消息 try { if (iframeWindow.parent && iframeWindow.parent !== iframeWindow) { iframeWindow.parent.postMessage({ isFinished: 1 }, '*'); console.log(`📤 PPT${docIndex} 发送完成消息`); } } catch (e) { console.log(`⚠️ PPT${docIndex} 发送完成消息失败:`, e.message); } // PPT完成后,不再自动跳转,让主循环控制 console.log(`🎉 PPT${docIndex} 浏览完成,等待主循环检测任务完成状态`); // 主循环会检测到所有任务完成后自动跳转 } }, 2000); } catch (error) { console.log(`❌ PPT${docIndex} 滚动失败:`, error.message); } }; // 延迟执行,等待PPT加载完成 setTimeout(() => { autoScrollToBottom(); }, 500); } // 简化的任务完成检查 function checkTaskCompletion() { const taskIcons = document.querySelectorAll('.ans-job-icon'); let totalTasks = 0; let completedTasks = 0; taskIcons.forEach((icon) => { totalTasks++; const ariaLabel = icon.getAttribute('aria-label') || ''; if (ariaLabel.includes('已完成')) { completedTasks++; } }); // 如果没有找到任务点图标,检查是否有iframe内容 if (totalTasks === 0) { const iframes = document.querySelectorAll('iframe'); const hasContent = iframes.length > 0; if (!hasContent) { // 如果没有任何内容,认为页面已完成 console.log('ℹ️ [任务检查] 页面无任务点图标且无iframe内容,认为已完成'); return { total: 0, completed: 0, isAllCompleted: true, details: [] }; } else { // 有iframe内容但没有任务点图标,检查内容是否已处理完成 console.log('ℹ️ [任务检查] 页面无任务点图标但有iframe内容,检查处理状态'); // 如果不在答题状态,且有iframe内容,可能内容已完成 // 这种情况下,我们依赖iframe内容的处理结果来判断 if (!GLOBAL_STATE.isAnswering && !GLOBAL_STATE.isChapterTesting) { console.log('ℹ️ [任务检查] 非答题状态,可能内容已完成,认为页面已完成'); return { total: 1, // 假设有1个任务 completed: 1, // 已完成 isAllCompleted: true, details: [] }; } } } const isAllCompleted = totalTasks > 0 && completedTasks === totalTasks; return { total: totalTasks, completed: completedTasks, isAllCompleted: isAllCompleted, details: [] }; } // 进入下一节 function goToNextSection() { console.log('🔍 [下一节] 开始查找下一节按钮...'); // 检查是否在iframe中,如果是则使用主页面的document const isInIframe = window !== window.top; const targetDoc = isInIframe ? window.top.document : document; try { // 在正确的document中查找下一节按钮 const nextButton = targetDoc.querySelector('#prevNextFocusNext'); console.log('🔍 [下一节] 在', isInIframe ? '主页面' : '当前页面', '查找按钮:', !!nextButton); if (nextButton) { console.log('🔍 [下一节] 找到按钮,详情:', { id: nextButton.id, className: nextButton.className, display: nextButton.style.display, visible: nextButton.offsetParent !== null, onclick: nextButton.getAttribute('onclick'), textContent: nextButton.textContent?.trim() }); // 检查按钮是否可用 const isVisible = nextButton.offsetParent !== null; const isDisplayed = nextButton.style.display !== 'none'; if (isVisible && isDisplayed) { console.log('✅ [下一节] 找到可用的下一节按钮,准备点击'); setTimeout(() => { try { nextButton.click(); console.log('✅ [下一节] 已点击下一节按钮'); } catch (error) { console.log('❌ [下一节] 点击失败:', error); // 尝试触发onclick事件 const onclickAttr = nextButton.getAttribute('onclick'); if (onclickAttr) { console.log('🔄 [下一节] 尝试执行onclick事件:', onclickAttr); try { // 在正确的window上下文中执行 const targetWindow = isInIframe ? window.top : window; targetWindow.eval(onclickAttr); console.log('✅ [下一节] onclick事件执行成功'); } catch (evalError) { console.log('❌ [下一节] onclick事件执行失败:', evalError); } } } }, 2000); return true; } else { console.log('❌ [下一节] 按钮不可用:', { visible: isVisible, displayed: isDisplayed }); } } else { console.log('❌ [下一节] 未找到 #prevNextFocusNext 按钮'); } } catch (error) { console.log('❌ [下一节] 访问主页面失败(跨域限制):', error); // 如果跨域限制,尝试通过postMessage通信 if (isInIframe) { console.log('🔄 [下一节] 尝试通过postMessage通信'); window.top.postMessage({ type: 'CLICK_NEXT_SECTION', selector: '#prevNextFocusNext' }, '*'); } } return false; } // 检查当前页面是否为学习页面 function isStudyPage() { const url = window.location.href; return url.includes('/knowledge/cards') && url.includes('mooc1.chaoxing.com') && url.includes('clazzid') && url.includes('courseid'); } // 自动检测和播放视频(仅在学习页面) if (isStudyPage()) { let nextSectionTriggered = false; // 防止重复触发下一节 setTimeout(() => { controlIframeVideo(); }, 3000); setTimeout(() => { controlIframeVideo(); }, 6000); window.mainIntervalId = setInterval(async () => { controlIframeVideo(); const taskStatus = checkTaskCompletion(); // 2. 如果所有任务已完成,直接跳转下一节 if (taskStatus.isAllCompleted && !nextSectionTriggered) { console.log('✅ [自动跳转] 所有任务已完成,跳转下一节'); nextSectionTriggered = true; goToNextSection(); return; } // 3. 如果任务未完成,处理iframe内容 if (!taskStatus.isAllCompleted && !GLOBAL_STATE.isAnswering && !GLOBAL_STATE.isChapterTesting) { const iframeResult = await processIframeContent(); // 如果没有找到任何iframe内容,直接跳转 if (iframeResult.reason === 'no_frames_next_section') { console.log('🔄 [自动跳转] 无内容需要处理,跳转下一节'); nextSectionTriggered = true; goToNextSection(); return; } // 显示处理结果,但不立即跳转,等待所有任务完成 if (iframeResult.processed) { console.log(`📝 [内容处理] 已处理: ${iframeResult.type},等待所有任务完成`); } } // 4. 重置跳转标记(如果任务未完成) if (!taskStatus.isAllCompleted) { nextSectionTriggered = false; } // 5. 简化的状态显示 console.log('🔄 [状态检查] =================='); console.log(`📋 [任务点] ${taskStatus.completed}/${taskStatus.total} ${taskStatus.isAllCompleted ? '✅完成' : '❌未完成'}`); console.log(`📝 [处理状态] ${GLOBAL_STATE.isChapterTesting ? '答题中' : '空闲'}`); console.log('================================'); }, 3000); // 每3秒检查一次 } // 添加postMessage监听器(用于iframe通信) if (window === window.top) { // 在主页面监听来自iframe的消息 window.addEventListener('message', function (event) { if (event.data && event.data.type === 'CLICK_NEXT_SECTION') { console.log('📨 [postMessage] 收到iframe的下一节请求'); const nextButton = document.querySelector(event.data.selector); if (nextButton && nextButton.offsetParent !== null) { setTimeout(() => { nextButton.click(); console.log('✅ [postMessage] 已点击下一节按钮'); }, 1000); } } }); } })();