您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
已完美兼容、学习通、学起plus 完美应付测试,全自动答题;后续需要完善其他平台请进群进行反馈
// ==UserScript== // @name 答题助手|超星学习通|学起plus|💯自动答题|▶️一键操作|🏆超全题库(每日更新、自动收录) // @namespace http://tampermonkey.net/ // @version 0.3.6.6 // @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/* // @match https://mooc2-ans.chaoxing.com/* // @require https://greasyfork.org/scripts/445293/code/TyprMd5.js // @match https://mooc1.chaoxing.com/* // @license This script is protected. You may not copy, modify, or redistribute it without explicit permission. // @match https://mooc2-ans.chaoxing.com/* // @resource Table https://www.forestpolice.org/ttf/2.0/table.json // @run-at document-start // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant unsafeWindow // @connect * // ==/UserScript== (function () { 'use strict'; // iframe内部的postMessage处理(用于跨域通信) if (window !== window.top) { // 当前在iframe中,监听来自父页面的消息 window.addEventListener('message', function(event) { if (event.data && event.data.source === 'chapter_list_handler') { if (event.data.type === 'FIND_PENDING_TASKS') { // 查找待完成任务点 const chapterElements = document.querySelectorAll('.chapter_item, [onclick*="toOld"], .catalog_title'); let hasElements = false; for (const element of chapterElements) { const pendingTask = element.querySelector('.catalog_jindu .bntHoverTips') || element.querySelector('.bntHoverTips') || element.querySelector('[class*="catalog_points"]'); if (pendingTask && pendingTask.textContent.includes('待完成任务点')) { hasElements = true; break; } } // 响应父页面 const response = { type: 'PENDING_TASKS_FOUND', hasElements: hasElements, elementsCount: chapterElements.length }; event.source.postMessage(response, '*'); } else if (event.data.type === 'CLICK_FIRST_PENDING_TASK') { // 点击第一个待完成任务点 const chapterElements = document.querySelectorAll('.chapter_item, [onclick*="toOld"], .catalog_title'); for (const element of chapterElements) { const pendingTask = element.querySelector('.catalog_jindu .bntHoverTips') || element.querySelector('.bntHoverTips') || element.querySelector('[class*="catalog_points"]'); if (pendingTask && pendingTask.textContent.includes('待完成任务点')) { const clickableElement = element.querySelector('[onclick]') || element.closest('[onclick]') || element; setTimeout(() => { clickableElement.click(); console.log('✅ [iframe] 已点击待完成任务点章节'); }, 500); break; } } } } }); } const originalAddEventListener = EventTarget.prototype.addEventListener; const blockedEvents = ['visibilitychange', 'blur', 'focusout', 'mouseleave', 'beforeunload', 'pagehide']; EventTarget.prototype.addEventListener = function(type, listener, options) { if (blockedEvents.includes(type)) { return; } return originalAddEventListener.call(this, type, listener, options); }; try { Object.defineProperty(document, 'hidden', { get: () => false, configurable: true }); Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true }); Object.defineProperty(document, 'webkitHidden', { get: () => false, configurable: true }); Object.defineProperty(document, 'mozHidden', { get: () => false, configurable: true }); Object.defineProperty(document, 'msHidden', { get: () => false, configurable: true }); Object.defineProperty(document, 'webkitVisibilityState', { get: () => 'visible', configurable: true }); Object.defineProperty(document, 'mozVisibilityState', { get: () => 'visible', configurable: true }); Object.defineProperty(document, 'msVisibilityState', { get: () => 'visible', configurable: true }); } catch (e) { } document.hasFocus = () => true; // 完全按照正确版本添加removeEventListener拦截 const oldRemove = EventTarget.prototype.removeEventListener; EventTarget.prototype.removeEventListener = function(...args) { if (args.length !== 0) { const eventType = args[0]; if (blockedEvents.includes(eventType)) { console.log(`[${new Date().toLocaleTimeString()}] [防切屏] 阻止移除 ${eventType} 监听器`); return; // 不允许移除 } } return oldRemove.call(this, ...args); }; // 添加正确版本的全局变量 const processedIframes = new WeakSet(); function injectHooksToDocument(doc, context = 'main') { if (!doc || doc._hooksInjected) return; try { const docWindow = doc.defaultView || doc.parentWindow; if (docWindow && docWindow.EventTarget) { const originalAdd = docWindow.EventTarget.prototype.addEventListener; docWindow.EventTarget.prototype.addEventListener = function(type, listener, options) { if (blockedEvents.includes(type)) { return; } return originalAdd.call(this, type, listener, options); }; } Object.defineProperty(doc, 'hidden', { get: () => false, configurable: true }); Object.defineProperty(doc, 'visibilityState', { get: () => 'visible', configurable: true }); doc.hasFocus = () => true; doc._hooksInjected = true; } catch (e) { } } // 添加正确版本的processIframes函数 function processIframes(doc = document, context = 'main', depth = 0) { if (depth > 5) return; try { const iframes = doc.querySelectorAll('iframe'); iframes.forEach((iframe, index) => { if (processedIframes.has(iframe)) return; try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (!iframeDoc) return; const iframeContext = `${context}-iframe-${index}`; // 注入防切屏钩子到iframe injectHooksToDocument(iframeDoc, iframeContext); // 递归处理嵌套iframe processIframes(iframeDoc, iframeContext, depth + 1); processedIframes.add(iframe); } catch (e) { // 跨域限制,忽略 } }); } catch (e) { console.warn(`[${new Date().toLocaleTimeString()}] [iframe检测] iframe处理失败:`, e); } } const clearWindowHandlers = () => { if (window.onblur !== null) { window.onblur = null; } if (window.onfocus !== null) { window.onfocus = null; } if (window.onbeforeunload !== null) { window.onbeforeunload = null; } }; setInterval(clearWindowHandlers, 5000); // 窗口处理器清理间隔调整为5秒 clearWindowHandlers(); const pageWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; const pageDocument = pageWindow.document; pageWindow._paq = []; 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; } originalSrcSetter.call(this, value); }, configurable: true }); return script; } return originalCreateElement.call(pageDocument, tagName); }; pageDocument.onkeydown = null; pageDocument.addEventListener('keydown', function (e) { 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); pageWindow.alert = function (message) { if (message && message.includes("请勿打开控制台")) { return; } }; pageWindow.close = function () { }; 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, }, }; let currentSite = null; function detectSite() { const currentHost = window.location.hostname; const currentUrl = window.location.href; // 检测已知站点 for (const key in SITES) { if (currentHost.includes(SITES[key].host)) { currentSite = SITES[key]; logMessage(`[站点检测] 已识别: ${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') || currentUrl.includes('exam')) { currentSite = SITES.CHAOXING; 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 { 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) { 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.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); }, 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; } }, 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); }, async getValidToken() { let token = this.getToken(); 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(); } } 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', 'X-User-Token': 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 = ` <div id="token-dialog" style=" position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; z-index: 10000; font-family: Arial, sans-serif; "> <div style=" background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); max-width: 500px; width: 90%; text-align: center; "> <h3 style="margin: 0 0 20px 0; color: #333;">🎯 TK星球答题系统</h3> <p style="color: #666; margin: 0 0 25px 0; line-height: 1.5;"> 请选择您的Token获取方式: </p> <div style="margin: 20px 0;"> <button id="create-new-token" style=" background: #007bff; color: white; border: none; padding: 12px 30px; margin: 0 10px; border-radius: 5px; cursor: pointer; font-size: 16px; transition: background 0.3s; " onmouseover="this.style.background='#0056b3'" onmouseout="this.style.background='#007bff'"> 🆕 生成新Token </button> <button id="input-existing-token" style=" background: #28a745; color: white; border: none; padding: 12px 30px; margin: 0 10px; border-radius: 5px; cursor: pointer; font-size: 16px; transition: background 0.3s; " onmouseover="this.style.background='#1e7e34'" onmouseout="this.style.background='#28a745'"> 📝 填写已有Token </button> </div> <div id="token-input-area" style="display: none; margin-top: 20px;"> <input type="text" id="token-input" placeholder="请输入您的Token" style=" width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box; margin-bottom: 15px; " /> <button id="verify-token" style=" background: #17a2b8; color: white; border: none; padding: 10px 25px; margin: 0 10px; border-radius: 5px; cursor: pointer; font-size: 14px; " onmouseover="this.style.background='#117a8b'" onmouseout="this.style.background='#17a2b8'"> 验证Token </button> <button id="cancel-input" style=" background: #6c757d; color: white; border: none; padding: 10px 25px; margin: 0 10px; border-radius: 5px; cursor: pointer; font-size: 14px; " onmouseover="this.style.background='#545b62'" onmouseout="this.style.background='#6c757d'"> 取消 </button> </div> <div id="dialog-message" style=" margin-top: 15px; padding: 10px; border-radius: 5px; display: none; "></div> </div> </div> `; 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) { showMessage(`Token生成成功!`, 'success'); 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 = ` ⚠️ <strong>重要提示</strong><br/> 新用户默认1000次查询机会,请充值后使用答题功能。<br/> 请妥善保存您的Token,它是您的唯一凭证! `; 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'; } }); inputBtn.addEventListener('click', () => { inputArea.style.display = 'block'; tokenInput.focus(); }); 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'; }); tokenInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { verifyBtn.click(); } }); }); } }; function handleSubmitConfirmDialog() { const documents = [ document, window.parent?.document, window.top?.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; } 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; const checkInterval = setInterval(() => { checkCount++; const dialogHandled = handleSubmitConfirmDialog(); if (dialogHandled || checkCount >= maxChecks) { clearInterval(checkInterval); } }, 1000); } 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 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(); } const contentEls = stemElement.querySelectorAll('.din'); if (contentEls.length > 1) { const contentEl = contentEls[1]; if (contentEl) { const tableSpan = contentEl.querySelector('table span'); if (tableSpan) { questionData.stem = tableSpan.textContent.trim(); } else { const pEl = contentEl.querySelector('p'); if (pEl) { questionData.stem = pEl.textContent.trim(); } else { 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', 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; 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 { 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: '' }; let questionId = questionEl.getAttribute('data') || questionEl.id?.replace('sigleQuestionDiv_', '') || ''; if (!questionId) { const optionWithQid = questionEl.querySelector('[qid]'); if (optionWithQid) { questionId = optionWithQid.getAttribute('qid'); } } questionData.questionId = questionId; if (isHomeworkPage) { const typeNameAttr = questionEl.getAttribute('typename'); if (typeNameAttr) { questionData.questionType = typeNameAttr; console.log(`[作业页面] 从typename属性获取题型: ${typeNameAttr}`); } } else if (isExamPage) { const markNameEl = questionEl.querySelector('h3.mark_name'); if (markNameEl) { const typeScoreSpan = markNameEl.querySelector('span.colorShallow'); if (typeScoreSpan) { const typeScoreText = typeScoreSpan.textContent.trim(); 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}`); } } } } const markNameEl = questionEl.querySelector('h3.mark_name'); if (markNameEl) { const titleText = markNameEl.childNodes[0]?.textContent?.trim() || ''; const numberMatch = titleText.match(/^(\d+)\./); if (numberMatch) { questionData.number = numberMatch[1]; } let stemText = ''; if (isExamPage) { const stemDiv = markNameEl.querySelector('div[style*="overflow:hidden"]'); if (stemDiv) { stemText = stemDiv.textContent.trim(); } else { const fullText = markNameEl.textContent.trim(); stemText = fullText.replace(/^\d+\.\s*/, ''); const typePattern = /^\s*\((单选题|多选题|判断题|填空题)(?:,\s*[^)]+)?\)\s*/; stemText = stemText.replace(typePattern, '').trim(); } } else if (isHomeworkPage) { const fullText = markNameEl.textContent.trim(); 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 = '单选题'; } } } const answerContainer = questionEl.querySelector('.stem_answer'); const hasInputElements = questionEl.querySelectorAll('input[type="text"], textarea').length > 0; 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 => { 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 = ''; const pElement = contentDiv.querySelector('p'); if (pElement) { content = pElement.textContent.trim(); 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 { const stemAnswerEl = questionEl.querySelector('.stem_answer'); if (stemAnswerEl) { const answerContainers = stemAnswerEl.querySelectorAll('.Answer'); answerContainers.forEach((answerContainer, index) => { 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; let ueditorInstanceName = null; if (iframe && iframe.src) { const match = iframe.src.match(/ueditorInstant(\d+)/); if (match) { ueditorInstanceName = `ueditorInstant${match[1]}`; } } let iframeBody = null; try { if (iframe && iframe.contentDocument && iframe.contentDocument.body) { iframeBody = iframe.contentDocument.body; } } catch (e) { } 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 }); } } }); 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 }); } } 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"]' ]; 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; } 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 }); 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); 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'); // 更新答题窗口状态 addTestLog(`开始考试答题,共 ${questions.length} 道题目`, 'info'); { const typeCount = {}; questions.forEach(q => { const t = q.questionType || '未知题型'; typeCount[t] = (typeCount[t] || 0) + 1; }); const typeStats = Object.entries(typeCount).map(([t, c]) => `${t}:${c}`).join(' '); if (typeStats) addTestLog(`题型:${typeStats}`, 'info'); } updateTestProgress(0, questions.length); const results = []; for (let i = 0; i < questions.length; i++) { // 更新答题窗口进度 updateTestProgress(i + 1, questions.length); 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(); 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'); 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; } } 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 === '判断题') { if (answer === 'T' || answer === 'true' || answer === '对') { answersToSelect = ['T']; } else if (answer === 'F' || answer === 'false' || answer === '错') { answersToSelect = ['F']; } else { 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) { successCount++; continue; } try { const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null; if (isHomeworkPage) { if (typeof pageWindow.addMultipleChoice === 'function') { pageWindow.addMultipleChoice(targetOption.element); } else { targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); } } else { const optionQid = targetOption.qid || questionId; let success = false; if (typeof pageWindow.saveMultiSelect === 'function' && optionQid) { try { pageWindow.saveMultiSelect(targetOption.element, optionQid); success = true; } catch (e) { console.log(`[超星] saveMultiSelect失败:`, e.message); } } if (!success && typeof pageWindow.addMultipleChoice === 'function') { try { pageWindow.addMultipleChoice(targetOption.element); success = true; } catch (e) { console.log(`[超星] addMultipleChoice失败:`, e.message); } } 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)); } } catch (clickError) { } } else { } } const success = successCount > 0; if (success) { } else { } 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) { successCount++; return; } try { const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null; if (isHomeworkPage) { if (typeof pageWindow.addChoice === 'function') { pageWindow.addChoice(targetOption.element); logMessage(`✅ [超星] 通过addChoice()选择选项 ${ans}`, 'success'); } else { targetOption.element.click(); logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success'); } } else { const optionQid = targetOption.qid || questionId; 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') { const editorId = option.editorId; logMessage(`🔍 [填空题] UEditor ID: ${editorId}`, 'info'); let ueditorSuccess = false; if (option.iframeBody) { try { option.iframeBody.innerHTML = `<p>${answerText}<br></p>`; 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'); } } if (typeof window.UE !== 'undefined') { let editor = null; if (window.UE.getEditor) { editor = window.UE.getEditor(editorId); } 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) { editor.execCommand('inserthtml', answerText); logMessage(`✅ [填空题] UEditor execCommand填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } else if (editor && editor.body) { editor.body.innerHTML = `<p>${answerText}<br></p>`; logMessage(`✅ [填空题] UEditor body操作填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } else { logMessage(`⚠️ [填空题] UEditor实例方法不可用,尝试其他方法`, 'warning'); } } else { logMessage(`⚠️ [填空题] UE对象不存在,尝试其他方法`, 'warning'); } if (!ueditorSuccess && option.iframe) { try { const iframe = option.iframe; logMessage(`🔍 [填空题] 重新尝试获取iframe body: ${iframe.id}`, 'info'); const tryGetIframeBody = (attempts = 0) => { try { if (iframe.contentDocument && iframe.contentDocument.body) { const iframeBody = iframe.contentDocument.body; if (iframeBody.contentEditable === 'true' || iframeBody.classList.contains('view')) { iframeBody.innerHTML = `<p>${answerText}<br></p>`; 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'); } if (attempts < 2) { setTimeout(() => tryGetIframeBody(attempts + 1), 200); } return false; }; tryGetIframeBody(); } catch (error) { logMessage(`⚠️ [填空题] iframe重新获取失败: ${error.message}`, 'warning'); } } 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); const inputEvent = new Event('input', { bubbles: true }); iframeBody.dispatchEvent(inputEvent); logMessage(`✅ [填空题] 模拟操作填空${index + 1}已填入: ${answerText}`, 'success'); ueditorSuccess = true; successCount++; } } } catch (error) { logMessage(`⚠️ [填空题] 模拟操作失败: ${error.message}`, 'warning'); } } 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 = `<p>${answerText}<br></p>`; 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); }); 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; } } async function selectAnswer(questionIndex, answer) { if (!currentSite) detectSite(); if (currentSite && typeof currentSite.selectAnswer === 'function') { const result = currentSite.selectAnswer(questionIndex, answer); if (result && typeof result.then === 'function') { return await result; } return result; } return false; } 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'); // 更新答题窗口 updateCurrentQuestion(questionData.stem, questionData.questionType); addTestLog(`正在处理第 ${questionIndex + 1} 题`, 'info'); 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'); // 更新答题窗口 updateCurrentAnswer(answer); addTestLog(`获取到答案: ${answer}`, 'success'); const selectSuccess = await selectAnswer(questionIndex, answer); if (selectSuccess) { logMessage(`✅ 第 ${questionIndex + 1} 题答题成功,选择答案${answer}`, 'success'); addTestLog(`第 ${questionIndex + 1} 题答题成功,选择答案${answer}`, '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'); if (currentUrl.includes('exam-ans/exam/test/reVersionTestStartNew')) { logMessage('📄 检测到考试开始页面,查找整卷预览按钮...', 'info'); // 显示考试答题窗口 showExamWindow(); addTestLog('检测到考试开始页面', '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'); // 显示考试答题窗口 showExamWindow(); addTestLog('检测到考试预览页面,开始答题', '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(); // 已删除悬浮窗调用 window.addEventListener('unhandledrejection', event => { if (event.reason && event.reason.message && event.reason.message.includes('429')) { logMessage('⚠️ 请求过于频繁,请稍后重试', 'warning'); } }); detectSite(); const tokenResult = await TokenManager.initialize(); if (!tokenResult.hasToken) { logMessage('❌ Token未设置,请先配置Token', 'error'); // 继续尝试自动启动逻辑(用户可在后续弹窗中完成Token) } // 检测到答题页面,启动答题功能 if (currentSite) { logMessage('📝 答题页面检测成功,自动答题功能已启动', 'success'); logMessage('📝 章节测验自动答题功能已启用', 'info'); logMessage('📝 作业自动答题功能已启用', 'info'); logMessage('📝 考试自动答题功能已启用', 'info'); // 不再提前返回,继续执行自动启动逻辑 } 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); } function updateTaskStatus() { // 简化的任务状态更新,只显示答题相关状态 let currentTask = '空闲'; if (GLOBAL_STATE.isAnswering) { currentTask = '答题中'; } else if (GLOBAL_STATE.isChapterTesting) { currentTask = '章节测试'; } // 如果页面上有任务状态显示元素,更新它们 const currentTaskEl = document.getElementById('currentTask'); if (currentTaskEl) { currentTaskEl.textContent = currentTask; } } // 创建答题状态窗口(支持章节测试和考试) function createAnswerWindow(windowType = 'chapter') { const windowId = 'answerWindow'; if (document.getElementById(windowId)) { return; // 窗口已存在 } // 根据窗口类型设置标题 const windowTitle = windowType === 'exam' ? '📝 考试答题助手' : '📝 章节测试答题助手'; const windowDiv = document.createElement('div'); windowDiv.id = windowId; windowDiv.innerHTML = ` <div class="test-window-header"> <span id="windowTitle">${windowTitle}</span> <button class="test-window-close">×</button> </div> <div class="test-window-content"> <div class="announcement-info"> <div class="announcement-title">📢 公告</div> <div class="announcement-content"> 有问题及时反馈群号: <span class="group-number">923349555</span> <div id="scriptAnnouncement" style="margin-top:8px;color:#1f2937;"></div> </div> </div> <div class="token-info"> <span id="tokenCount">Token: 加载中...</span> </div> <div class="progress-info"> <span id="progressInfo">准备中...</span> </div> <div class="current-question"> <div class="question-title">当前题目:</div> <div id="currentQuestionText">等待开始...</div> </div> <div class="current-answer"> <div class="answer-title">选择答案:</div> <div id="currentAnswerText">-</div> </div> <div class="test-log"> <div class="log-title">答题日志:</div> <div id="testLogContent"></div> </div> </div> `; // 添加样式 const style = document.createElement('style'); style.textContent = ` #answerWindow { position: fixed; top: 20px; right: 20px; width: 350px; max-height: 550px; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: Arial, sans-serif; font-size: 12px; overflow: hidden; } .test-window-header { background: #f5f5f5; padding: 10px 15px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; font-weight: bold; cursor: move; } .test-window-close { background: none; border: none; font-size: 18px; cursor: pointer; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 3px; } .test-window-close:hover { background: #e0e0e0; } .test-window-content { padding: 15px; max-height: 470px; overflow-y: auto; } .announcement-info { background: #fff3cd; border: 1px solid #ffeaa7; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; font-size: 11px; } .announcement-title { font-weight: bold; color: #856404; margin-bottom: 4px; } .announcement-content { color: #856404; line-height: 1.4; } .group-number { font-weight: bold; color: #d63384; cursor: pointer; text-decoration: underline; } .group-number:hover { color: #b02a5b; } .token-info { background: #e8f4fd; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; font-weight: bold; color: #0066cc; } .progress-info { background: #f0f8ff; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; font-weight: bold; color: #333; } .current-question, .current-answer { margin-bottom: 12px; padding: 8px; border: 1px solid #eee; border-radius: 4px; background: #fafafa; } .question-title, .answer-title, .log-title { font-weight: bold; margin-bottom: 5px; color: #333; } #currentQuestionText { color: #555; line-height: 1.4; max-height: 60px; overflow-y: auto; } #currentAnswerText { color: #007bff; font-weight: bold; } .test-log { margin-top: 10px; border-top: 1px solid #eee; padding-top: 10px; } #testLogContent { max-height: 120px; overflow-y: auto; font-size: 11px; line-height: 1.3; color: #666; background: #f9f9f9; padding: 8px; border-radius: 4px; border: 1px solid #eee; } .log-entry { margin-bottom: 3px; padding: 2px 0; } .log-success { color: #28a745; } .log-error { color: #dc3545; } .log-info { color: #17a2b8; } `; document.head.appendChild(style); document.body.appendChild(windowDiv); // 绑定事件 bindAnswerWindowEvents(); // 更新Token信息 updateTokenDisplay(); // 拉取脚本公告 try { fetch('/portal/api/announcements?type=script', { credentials: 'include' }) .then(r => r.json()) .then(d => { if (d && d.success && d.data && d.data.content) { const el = document.getElementById('scriptAnnouncement'); if (el) el.innerHTML = d.data.content.replace(/\n/g, '<br>'); } }) .catch(() => {}); } catch (e) {} } // 绑定答题窗口事件 function bindAnswerWindowEvents() { const testWindow = document.getElementById('answerWindow'); const closeBtn = testWindow.querySelector('.test-window-close'); const header = testWindow.querySelector('.test-window-header'); // 关闭按钮 closeBtn.addEventListener('click', () => { testWindow.style.display = 'none'; }); // 拖拽功能 let isDragging = false; let dragOffset = { x: 0, y: 0 }; header.addEventListener('mousedown', (e) => { isDragging = true; const rect = testWindow.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; header.style.cursor = 'grabbing'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - dragOffset.x; const y = e.clientY - dragOffset.y; const maxX = window.innerWidth - testWindow.offsetWidth; const maxY = window.innerHeight - testWindow.offsetHeight; testWindow.style.left = Math.max(0, Math.min(x, maxX)) + 'px'; testWindow.style.top = Math.max(0, Math.min(y, maxY)) + 'px'; testWindow.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; header.style.cursor = 'move'; } }); // 群号点击复制功能 const groupNumber = testWindow.querySelector('.group-number'); if (groupNumber) { groupNumber.addEventListener('click', () => { const groupNum = '923349555'; if (navigator.clipboard) { navigator.clipboard.writeText(groupNum).then(() => { const originalText = groupNumber.textContent; groupNumber.textContent = '已复制!'; setTimeout(() => { groupNumber.textContent = originalText; }, 2000); }).catch(() => { alert(`请手动复制群号: ${groupNum}`); }); } else { // 降级方案 const textArea = document.createElement('textarea'); textArea.value = groupNum; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); const originalText = groupNumber.textContent; groupNumber.textContent = '已复制!'; setTimeout(() => { groupNumber.textContent = originalText; }, 2000); } catch (err) { alert(`请手动复制群号: ${groupNum}`); } document.body.removeChild(textArea); } }); } } // 更新Token显示 async function updateTokenDisplay() { try { console.log('[Token显示] 开始获取用户信息...'); const userInfo = await TokenManager.getUserInfo(); console.log('[Token显示] 获取到用户信息:', userInfo); const tokenElement = document.getElementById('tokenCount'); if (tokenElement && userInfo) { // 使用remainingCount或remaining_queries字段 const remainingQueries = userInfo.remainingCount || userInfo.remaining_queries || 0; tokenElement.textContent = `Token: ${remainingQueries} 次`; console.log(`[Token显示] 剩余查询次数: ${remainingQueries}`); } else { if (tokenElement) { tokenElement.textContent = 'Token: 无法获取用户信息'; } console.warn('[Token显示] 用户信息获取失败, userInfo:', userInfo); } } catch (error) { console.error('[Token显示] 获取失败:', error); const tokenElement = document.getElementById('tokenCount'); if (tokenElement) { if (error.message.includes('Token未设置')) { tokenElement.textContent = 'Token: 未设置'; } else if (error.message.includes('Token无效')) { tokenElement.textContent = 'Token: 无效或过期'; } else { tokenElement.textContent = `Token: 获取失败 (${error.message})`; } } } } // 更新答题进度 function updateTestProgress(current, total) { const progressElement = document.getElementById('progressInfo'); if (progressElement) { progressElement.textContent = `进度: ${current}/${total} (${Math.round(current/total*100)}%)`; } } // 更新当前题目 function updateCurrentQuestion(questionText, questionType) { const questionElement = document.getElementById('currentQuestionText'); if (questionElement) { const displayText = questionText.length > 100 ? questionText.substring(0, 100) + '...' : questionText; questionElement.textContent = `[${questionType}] ${displayText}`; } } // 更新当前答案 function updateCurrentAnswer(answer) { const answerElement = document.getElementById('currentAnswerText'); if (answerElement) { answerElement.textContent = answer || '-'; } } // 添加日志条目 function addTestLog(message, type = 'info') { const logContent = document.getElementById('testLogContent'); if (logContent) { const logEntry = document.createElement('div'); logEntry.className = `log-entry log-${type}`; logEntry.textContent = `${new Date().toLocaleTimeString()} - ${message}`; logContent.appendChild(logEntry); logContent.scrollTop = logContent.scrollHeight; // 限制日志条目数量 const entries = logContent.querySelectorAll('.log-entry'); if (entries.length > 20) { entries[0].remove(); } } } // 显示答题窗口(通用) function showAnswerWindow(windowType = 'chapter') { createAnswerWindow(windowType); const testWindow = document.getElementById('answerWindow'); if (testWindow) { testWindow.style.display = 'block'; const windowTitle = windowType === 'exam' ? '考试答题窗口已启动' : '章节测试窗口已启动'; addTestLog(windowTitle, 'info'); } } // 显示章节测试窗口(兼容旧函数名) function showChapterTestWindow() { showAnswerWindow('chapter'); } // 显示考试窗口 function showExamWindow() { showAnswerWindow('exam'); } // 更新窗口标题 function updateWindowTitle(windowType) { const titleElement = document.getElementById('windowTitle'); if (titleElement) { const title = windowType === 'exam' ? '📝 考试答题助手' : '📝 章节测试答题助手'; titleElement.textContent = title; } } 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; 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++) { 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); 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 => { 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 }; }; window.applyChaoxingFontDecryptOriginal = function () { try { 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} 个加密元素`); let processedCount = 0; fontSecretElements.forEach((element, index) => { try { const originalText = element.textContent || ''; 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 { 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) { decodedCount++; } } } console.log(`✅ 批量解码完成,共处理 ${decodedCount} 个文本`); return true; } catch (error) { console.log(`❌ 批量解码失败: ${error.message}`); return false; } }; 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 ? frame.iframe.contentWindow : window; const completedStatus = doc.querySelector('.testTit_status_complete'); if (completedStatus && completedStatus.textContent.includes('已完成')) { console.log('✅ [章节测验] 检测到已完成状态,跳过答题'); return 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('🚀 [章节测验] 开始答题流程'); // 显示答题窗口 showChapterTestWindow(); addTestLog(`开始处理章节测验,共 ${questions.length} 道题目`, 'info'); updateTestProgress(0, questions.length); 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}`); // 更新答题窗口 updateTestProgress(i + 1, questions.length); updateCurrentQuestion(content, typeText); addTestLog(`正在处理第 ${i + 1} 题`, 'info'); 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 }); } }); 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}`); // 更新答题窗口 updateCurrentAnswer(answer); addTestLog(`获取到答案: ${answer}`, 'success'); 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}`); addTestLog(`填空题第${blankIndex + 1}空填入: ${answerText}`, 'success'); const editorTextarea = blankItem.querySelector('textarea[name*="answerEditor"]'); if (editorTextarea) { const editorId = editorTextarea.id; try { let fillSuccess = false; 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) { } } if (!fillSuccess) { editorTextarea.value = answerText; const events = ['input', 'change', 'blur', 'keyup']; events.forEach(eventType => { try { 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; } 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}`); addTestLog(`判断题已选择: ${text}`, 'success'); 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)) { if (typeof iframeWindow.addMultipleChoice === 'function') { try { iframeWindow.addMultipleChoice(opt); console.log(` 🎯 已选择多选项: ${label}`); addTestLog(`多选题已选择: ${label}`, 'success'); } catch (error) { opt.click(); console.log(` 🎯 已选择多选项: ${label} (备用)`); addTestLog(`多选题已选择: ${label} (备用方式)`, 'success'); } } else { opt.click(); console.log(` 🎯 已选择多选项: ${label}`); addTestLog(`多选题已选择: ${label}`, 'success'); } } } } 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}`); addTestLog(`单选题已选择: ${label}`, 'success'); break; } } } } } catch (error) { console.log(` ❌ 答题异常: ${error.message}`); addTestLog(`答题异常: ${error.message}`, 'error'); } await new Promise(resolve => setTimeout(resolve, 1000)); } console.log('✅ [章节测验] 答题完成,准备提交'); addTestLog('所有题目答题完成,准备提交测验', 'success'); updateCurrentQuestion('答题完成', '准备提交'); updateCurrentAnswer('等待提交...'); GLOBAL_STATE.isAnswering = false; setTimeout(() => { if (iframeWindow.btnBlueSubmit) { iframeWindow.btnBlueSubmit(); console.log('✅ [自动提交] 测验提交成功'); addTestLog('测验已自动提交', 'success'); updateCurrentAnswer('已提交'); setTimeout(() => { monitorSubmitDialog(); }, 500); } }, 2000); return true; } return false; } // 检测是否为章节测验页面 function isChapterTestPage() { const url = window.location.href; // 章节测验页面特征 return url.includes('/mooc-ans/work/doHomeWorkNew/') || url.includes('/mooc-ans/api/work/') || url.includes('/ananas/modules/work/') || // 也检查页面DOM特征 document.querySelector('.singleQuesId') || document.querySelector('.CeYan h3') || document.querySelector('.questionLi') || document.querySelector('.newZy_TItle'); } // 答题系统启动 console.log(`📝 [答题系统] 超星学习通答题助手已启动`); if (isChapterTestPage()) { console.log(`📝 [系统启动] 检测到章节测验页面,启动答题系统`); // 立即显示答题窗口 showChapterTestWindow(); addTestLog('检测到章节测验页面,答题助手已启动', 'info'); setTimeout(async () => { console.log(`📝 [章节测验] 页面加载完成,开始处理测验`); // 查找章节测验iframe const testFrames = []; const iframes = document.querySelectorAll('iframe'); for (const iframe of iframes) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) { // 检查是否是章节测验iframe if (iframeDoc.querySelector('.singleQuesId') || iframeDoc.querySelector('.CeYan h3') || iframeDoc.querySelector('.questionLi')) { testFrames.push({ iframe: iframe, doc: iframeDoc, accessible: true, type: 'CHAPTER_TEST' }); } } } catch (e) { // 跨域iframe,跳过 } } // 如果没有找到iframe,检查当前页面是否直接包含测验 if (testFrames.length === 0) { if (document.querySelector('.singleQuesId') || document.querySelector('.CeYan h3') || document.querySelector('.questionLi')) { testFrames.push({ iframe: null, doc: document, accessible: true, type: 'CHAPTER_TEST' }); } } if (testFrames.length > 0) { console.log(`📝 [章节测验] 找到 ${testFrames.length} 个测验框架,开始处理`); addTestLog(`找到 ${testFrames.length} 个测验框架,开始处理`, 'info'); await handleChapterTest(testFrames); } else { console.log(`❌ [章节测验] 未找到测验内容`); addTestLog('未找到测验内容,请检查页面', 'error'); updateCurrentQuestion('未找到测验内容', '检查页面'); } }, 2000); } else { console.log(`❓ [系统启动] 未识别的页面类型`); console.log(`🔍 [调试信息] 当前URL: ${window.location.href}`); console.log(`🔍 [调试信息] 页面标题: ${document.title}`); } console.log('✅ 系统优化 答题助手已启动,专注于章节测验、作业和考试功能'); // 添加全局函数,方便手动调用 window.showChapterTestWindow = showChapterTestWindow; window.showExamWindow = showExamWindow; window.showAnswerWindow = showAnswerWindow; window.addTestLog = addTestLog; window.updateTestProgress = updateTestProgress; window.updateCurrentQuestion = updateCurrentQuestion; window.updateCurrentAnswer = updateCurrentAnswer; window.updateWindowTitle = updateWindowTitle; })();