// ==UserScript== // @name 答题助手|超星学习通|💯自动答题|▶️一键操作|🏆超全题库(每日更新、自动收录) // @namespace http://tampermonkey.net/ // @version 0.3.6.8 // @description 支持 超星学习通 平台的章节测试、作业、考试一键自动答题;对接百亿题库,覆盖海量题型,正确率高、速度快! // @description 进群反馈QQ:923349555 // @author 艾凌科技工作室 // @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://scriptcat.org/lib/668/1.0/TyprMd5.js // @match https://mooc1.chaoxing.com/* // @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 = { 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; } const hasQuestionElements = document.querySelector('[class*="question"], [id*="question"], .exam, .test, .quiz'); if (hasQuestionElements) { currentSite = SITES.CHAOXING; logMessage(`[站点检测] 通用题目页面,使用: ${currentSite.name}`, 'warning'); return currentSite; } currentSite = SITES.CHAOXING; 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 getScriptAnnouncements() { const cacheKey = 'script_announcements'; return await this._throttleRequest(cacheKey, async () => { try { const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/script-announcements`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, timeout: SERVER_CONFIG.timeout }); const result = await response.json(); if (result.success && result.data && result.data.length > 0) { // 只返回活跃公告的纯文本内容,避免样式冲突 const activeAnnouncement = result.data.find(a => a.is_active) || result.data[0]; return activeAnnouncement ? activeAnnouncement.content : ''; } return ''; } catch (error) { console.warn('获取脚本公告失败:', error); return ''; } }, 300000); // 5分钟缓存 }, 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) { 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 = ` ⚠️ 重要提示
新用户默认1000次查询机会,请充值后使用答题功能。
请妥善保存您的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 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 if (questionData.stem && ( questionData.stem.includes('简答') || questionData.stem.includes('简述') || questionData.stem.includes('简单题') || questionData.stem.includes('请说明') || questionData.stem.includes('请解释') )) { questionData.questionType = '简单题'; } else if (questionData.stem && ( questionData.stem.includes('计算') || questionData.stem.includes('求解') || questionData.stem.includes('计算题') || questionData.stem.includes('求值') || questionData.stem.includes('=') || /\d+[\+\-\*\/]\d+/.test(questionData.stem) )) { questionData.questionType = '计算题'; } else if (questionData.stem && ( questionData.stem.includes('编程') || questionData.stem.includes('代码') || questionData.stem.includes('程序') || questionData.stem.includes('编程题') || questionData.stem.includes('function') || questionData.stem.includes('def ') || questionData.stem.includes('class ') )) { 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', '.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 []; } 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 = '判断题'; } else if (textContent.includes('简单题') || textContent.includes('简答') || textContent.includes('简述')) { questionData.questionType = '简单题'; } else if (textContent.includes('计算题') || textContent.includes('计算') || textContent.includes('求解')) { questionData.questionType = '计算题'; } else if (textContent.includes('编程题') || textContent.includes('编程') || textContent.includes('代码') || textContent.includes('程序')) { 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(); } } 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 = `

${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'); } } 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 = `

${answerText}

`; 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 = `

${answerText}

`; 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 = `

${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); }); 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 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 { // 创建答题窗口(必须在最开始创建,确保顶层窗口可见) createAnswerWindow('chapter'); 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 createAnswerWindow(windowType = 'chapter') { const windowId = 'answerWindow'; // 策略:只在顶层页面创建窗口 // 1. 如果是顶层窗口 → 直接创建 // 2. 如果是iframe → 尝试在顶层创建 let targetDocument = document; let targetWindow = window; let creationContext = '当前页面'; // 尝试访问顶层窗口 if (window !== window.top) { if (canAccessTop()) { // 同源iframe,可以访问顶层 targetDocument = window.top.document; targetWindow = window.top; creationContext = '顶层页面'; console.log('✅ [答题窗口] iframe检测:可访问顶层,将在顶层创建窗口'); } else { // 跨域iframe,无法访问顶层,放弃创建 console.log('⚠️ [答题窗口] iframe检测:跨域限制,跳过创建(避免在iframe内创建)'); return; } } else { // 当前就是顶层窗口 console.log('✅ [答题窗口] 检测到顶层窗口,准备创建'); } // 检查是否已存在窗口 if (targetDocument.getElementById(windowId)) { console.log('ℹ️ [答题窗口] 窗口已存在于' + creationContext); return; } console.log(`🎨 [答题窗口] 开始在${creationContext}创建答题窗口`); // 根据窗口类型设置标题 const windowTitle = windowType === 'exam' ? '📝 考试答题助手' : '📝 章节测试答题助手'; const windowDiv = targetDocument.createElement('div'); windowDiv.id = windowId; windowDiv.innerHTML = `
${windowTitle}
💡
点击测试页面,自动开始答题
批量刷课神器
一键自动完成所有课程
剩余次数
加载中...
`; // 添加样式 const style = document.createElement('style'); style.textContent = ` #answerWindow { position: fixed; top: 20px; right: 20px; width: 400px; background: #ffffff; border: 2px solid #e0e0e0; border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; overflow: hidden; animation: slideIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); } @keyframes slideIn { from { transform: translateX(100%) scale(0.9); opacity: 0; } to { transform: translateX(0) scale(1); opacity: 1; } } .test-window-header { background: #ffffff; color: #333; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; font-weight: 600; font-size: 15px; cursor: move; } .test-window-close { background: #f5f5f5; border: none; font-size: 18px; color: #666; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: all 0.2s; } .test-window-close:hover { background: #e0e0e0; color: #333; } .test-window-content { padding: 20px; background: #ffffff; } .usage-tip { background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%); border-radius: 10px; padding: 14px 16px; margin-bottom: 16px; display: flex; align-items: center; gap: 12px; animation: tipPulse 2s ease-in-out infinite; } @keyframes tipPulse { 0%, 100% { box-shadow: 0 2px 10px rgba(255, 154, 158, 0.3); } 50% { box-shadow: 0 4px 20px rgba(255, 154, 158, 0.5); } } .tip-icon { font-size: 24px; flex-shrink: 0; animation: tipBounce 2s ease-in-out infinite; } @keyframes tipBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } .tip-text { color: #fff; font-size: 14px; font-weight: 600; line-height: 1.5; text-shadow: 0 1px 2px rgba(0,0,0,0.1); } .announcement-info { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3); transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); display: flex; align-items: center; gap: 15px; } .announcement-clickable { cursor: pointer; position: relative; } .announcement-clickable:hover { transform: translateY(-3px); box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4); } .announcement-icon { font-size: 32px; animation: pulse 2s ease-in-out infinite; flex-shrink: 0; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } } .announcement-main { flex: 1; } .announcement-title { font-weight: 700; color: #ffffff; margin-bottom: 4px; font-size: 16px; letter-spacing: 0.3px; } .announcement-subtitle { color: rgba(255, 255, 255, 0.9); font-size: 12px; margin-bottom: 8px; line-height: 1.4; } .announcement-link { display: flex; align-items: center; justify-content: space-between; background: rgba(255, 255, 255, 0.2); border-radius: 8px; padding: 8px 12px; backdrop-filter: blur(10px); } .link-text { font-weight: 600; color: #ffffff; font-size: 13px; letter-spacing: 0.5px; } .link-arrow { color: #ffffff; font-size: 18px; font-weight: bold; animation: moveRight 1s ease-in-out infinite; } @keyframes moveRight { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(5px); } } .token-info { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 10px; padding: 16px; display: flex; justify-content: space-between; align-items: center; } .info-label { color: #666; font-size: 13px; font-weight: 500; } .info-value { color: #333; font-size: 16px; font-weight: 700; } .feedback-info { background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px; padding: 14px 16px; margin-top: 12px; display: flex; align-items: center; gap: 12px; transition: all 0.3s; } .feedback-info:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(168, 237, 234, 0.4); } .feedback-icon { font-size: 22px; flex-shrink: 0; } .feedback-text { color: #333; font-size: 13px; font-weight: 500; line-height: 1.5; } .group-number { color: #667eea; font-weight: 700; font-size: 15px; background: rgba(255, 255, 255, 0.8); padding: 2px 8px; border-radius: 4px; cursor: pointer; user-select: all; } .group-number:hover { background: rgba(255, 255, 255, 1); color: #5568d3; } `; // 将样式和窗口添加到目标页面 if (targetDocument.head && targetDocument.body) { targetDocument.head.appendChild(style); targetDocument.body.appendChild(windowDiv); console.log(`✅ [答题窗口] 窗口已成功添加到${creationContext}`); } else { console.error('❌ [答题窗口] 无法访问目标页面'); return; } // 绑定事件 bindAnswerWindowEvents(targetDocument, targetWindow); // 更新Token信息 updateTokenDisplay(); // 加载脚本公告(可选,现在公告已硬编码到HTML中) // loadScriptAnnouncements(); } // 绑定答题窗口事件 function bindAnswerWindowEvents(targetDocument, targetWindow) { // 如果没有传入参数,使用安全的跨域检测函数 const doc = targetDocument || getTopDocument(); const win = targetWindow || getTopWindow(); const testWindow = doc.getElementById('answerWindow'); if (!testWindow) { console.error('❌ [答题窗口] 无法找到答题窗口元素'); return; } const closeBtn = testWindow.querySelector('.test-window-close'); const header = testWindow.querySelector('.test-window-header'); const announcementInfo = testWindow.querySelector('.announcement-info'); console.log('🔧 [答题窗口] 开始绑定事件'); // 关闭按钮 if (closeBtn) { closeBtn.addEventListener('click', () => { testWindow.style.display = 'none'; }); } // 点击公告跳转到批量刷课网站 if (announcementInfo) { announcementInfo.addEventListener('click', () => { win.open('http://xxt.toptk.xyz/app', '_blank'); logMessage('🚀 已打开批量刷课平台', 'success'); }); // 添加鼠标悬停提示 announcementInfo.title = '点击打开批量刷课平台'; } // 点击群号复制到剪贴板 const groupNumber = testWindow.querySelector('.group-number'); if (groupNumber) { groupNumber.addEventListener('click', (e) => { e.stopPropagation(); // 防止触发父元素的点击事件 const groupNum = '923349555'; // 尝试使用现代的 Clipboard API if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(groupNum).then(() => { logMessage('✅ 群号已复制到剪贴板: ' + groupNum, 'success'); // 临时改变样式表示已复制 const originalText = groupNumber.textContent; groupNumber.textContent = '已复制!'; setTimeout(() => { groupNumber.textContent = originalText; }, 1500); }).catch(() => { logMessage('❌ 复制失败,请手动复制群号: ' + groupNum, 'error'); }); } else { // 降级方案:使用传统方法 const textarea = doc.createElement('textarea'); textarea.value = groupNum; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; doc.body.appendChild(textarea); textarea.select(); try { doc.execCommand('copy'); logMessage('✅ 群号已复制到剪贴板: ' + groupNum, 'success'); const originalText = groupNumber.textContent; groupNumber.textContent = '已复制!'; setTimeout(() => { groupNumber.textContent = originalText; }, 1500); } catch (err) { logMessage('❌ 复制失败,请手动复制群号: ' + groupNum, 'error'); } doc.body.removeChild(textarea); } }); // 添加鼠标悬停提示 groupNumber.title = '点击复制群号'; } // 拖拽功能 let isDragging = false; let dragOffset = { x: 0, y: 0 }; if (header) { 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(); }); } doc.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - dragOffset.x; const y = e.clientY - dragOffset.y; const maxX = win.innerWidth - testWindow.offsetWidth; const maxY = win.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'; }); doc.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; if (header) header.style.cursor = 'move'; } }); console.log('✅ [答题窗口] 事件绑定完成'); } // 跨域检测缓存(只检测一次,避免重复错误消息) let topAccessCache = null; let topAccessChecked = false; // 检测是否可以访问顶层窗口(只检测一次) function canAccessTop() { if (topAccessChecked) { return topAccessCache; } topAccessChecked = true; try { // 如果就是顶层窗口,直接返回true if (!window.top || window.top === window) { topAccessCache = false; return false; } // 尝试访问顶层窗口的document来测试跨域 const test = window.top.document.body; topAccessCache = true; console.log('✅ [窗口检测] 可访问顶层窗口,悬浮窗将显示在页面最上层'); return true; } catch (e) { // 跨域时会抛出SecurityError topAccessCache = false; console.log('ℹ️ [窗口检测] 检测到iframe跨域限制,悬浮窗将显示在当前框架内(这是正常的安全机制)'); return false; } } // 获取顶层document的辅助函数(带跨域检测) function getTopDocument() { // 先检查是否是顶层窗口 if (window === window.top) { return document; } // 如果能访问顶层窗口,使用顶层document if (canAccessTop()) { return window.top.document; } // 否则使用当前document(跨域情况) return document; } // 获取顶层window的辅助函数(带跨域检测) function getTopWindow() { if (window === window.top) { return window; } if (canAccessTop()) { return window.top; } return window; } // 加载脚本公告 async function loadScriptAnnouncements() { try { console.log('[脚本公告] 开始获取脚本公告...'); const announcementContent = await TokenManager.getScriptAnnouncements(); // 使用顶层document查找元素 const topDoc = getTopDocument(); // 注意:我们现在优先显示批量刷课公告,服务器公告作为次要信息 // 如果需要显示服务器公告,可以添加到下方 if (announcementContent && announcementContent.trim()) { // 在批量刷课公告下方添加服务器公告 const announcementContentEl = topDoc.querySelector('.announcement-content'); if (announcementContentEl) { // 保持批量刷课公告,并在下方添加服务器额外公告 const currentContent = announcementContentEl.innerHTML; announcementContentEl.innerHTML = currentContent + `
📢 ${announcementContent}
`; console.log('[脚本公告] 服务器公告已追加到顶层窗口'); } } console.log('[脚本公告] 公告处理完成'); } catch (error) { console.warn('[脚本公告] 加载失败:', error); } } // 更新Token显示 async function updateTokenDisplay() { try { console.log('[Token显示] 开始获取用户信息...'); const userInfo = await TokenManager.getUserInfo(); console.log('[Token显示] 获取到用户信息:', userInfo); // 使用顶层document查找元素 const topDoc = getTopDocument(); const tokenElement = topDoc.getElementById('tokenCount'); if (tokenElement && userInfo) { // 使用remainingCount或remaining_queries字段 const remainingQueries = userInfo.remainingCount || userInfo.remaining_queries || 0; tokenElement.textContent = remainingQueries; console.log(`[Token显示] 剩余查询次数: ${remainingQueries}`); } else { if (tokenElement) { tokenElement.textContent = '—'; } console.warn('[Token显示] 用户信息获取失败, userInfo:', userInfo); } } catch (error) { console.error('[Token显示] 获取失败:', error); const topDoc = getTopDocument(); const tokenElement = topDoc.getElementById('tokenCount'); if (tokenElement) { tokenElement.textContent = '—'; } } } // 更新答题进度 function updateTestProgress(current, total) { const topDoc = getTopDocument(); const progressElement = topDoc.getElementById('progressInfo'); if (progressElement) { progressElement.textContent = `进度: ${current}/${total} (${Math.round(current / total * 100)}%)`; } } // 更新当前题目 function updateCurrentQuestion(questionText, questionType) { const topDoc = getTopDocument(); const questionElement = topDoc.getElementById('currentQuestionText'); if (questionElement) { const displayText = questionText.length > 100 ? questionText.substring(0, 100) + '...' : questionText; questionElement.textContent = `[${questionType}] ${displayText}`; } } // 更新当前答案 function updateCurrentAnswer(answer) { const topDoc = getTopDocument(); const answerElement = topDoc.getElementById('currentAnswerText'); if (answerElement) { answerElement.textContent = answer || '-'; } } // 更新题目计数 function updateQuestionCount() { try { // 获取当前页面的题目数量(注意:这里查找的是题目所在页面,不是顶层页面) let questionCount = 0; // 尝试不同的选择器来获取题目数量 const questionSelectors = [ '.questionLi', // 超星学习通 '.TiMu', // 超星学习通 '.question-item', // 通用 '[class*="question"]' // 通用 ]; for (const selector of questionSelectors) { const elements = document.querySelectorAll(selector); if (elements.length > 0) { questionCount = elements.length; break; } } // 更新显示(使用顶层document) const topDoc = getTopDocument(); const countElement = topDoc.getElementById('question-count'); if (countElement) { countElement.textContent = `题目总数: ${questionCount}`; } // 更新答题窗口中的题目计数 const progressElement = topDoc.getElementById('progressInfo'); if (progressElement && questionCount > 0) { const currentText = progressElement.textContent || ''; if (!currentText.includes('/')) { progressElement.textContent = `进度: 0/${questionCount} (0%)`; } } logMessage(`📊 [题目统计] 当前页面共找到 ${questionCount} 道题目`, 'info'); } catch (error) { logMessage(`❌ [题目统计] 更新题目计数失败: ${error.message}`, 'error'); } } // 添加日志条目 function addTestLog(message, type = 'info') { const topDoc = getTopDocument(); const logContent = topDoc.getElementById('testLogContent'); if (logContent) { const logEntry = topDoc.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 topDoc = getTopDocument(); const testWindow = topDoc.getElementById('answerWindow'); if (testWindow) { testWindow.style.display = 'block'; const windowTitle = windowType === 'exam' ? '考试答题窗口已启动' : '章节测试窗口已启动'; addTestLog(windowTitle, 'info'); console.log('✅ [答题窗口] 窗口已显示在顶层页面'); } } // 显示章节测试窗口(兼容旧函数名) function showChapterTestWindow() { showAnswerWindow('chapter'); } // 显示考试窗口 function showExamWindow() { showAnswerWindow('exam'); } // 更新窗口标题 function updateWindowTitle(windowType) { const topDoc = getTopDocument(); const titleElement = topDoc.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.updateQuestionCount = updateQuestionCount; window.updateWindowTitle = updateWindowTitle; })(); // 添加全局函数,方便手动调用 window.showChapterTestWindow = showChapterTestWindow; window.showExamWindow = showExamWindow; window.showAnswerWindow = showAnswerWindow; window.addTestLog = addTestLog; window.updateTestProgress = updateTestProgress; window.updateCurrentQuestion = updateCurrentQuestion; window.updateCurrentAnswer = updateCurrentAnswer; window.updateQuestionCount = updateQuestionCount; window.updateWindowTitle = updateWindowTitle;