// ==UserScript== // @name U助手-AI版 // @namespace http://tampermonkey.net/ // @version 3.1.9 // @description 支持题库模式和ai模式解决U校园AI版的刷题烦恼,集成自动录音功能,挂机模式支持自动录音 // @author 恶搞之家 // @match *://ucontent.unipus.cn/* // @match *://uexercise.unipus.cn/* // @match *://birdflock.unipus.cn/* // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_addStyle // @grant unsafeWindow // @run-at document-start // @connect kimi.moonshot.cn // @connect eghome.textile668.cn // @require https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.min.js // @require https://eghome.textile668.cn/static/u-helper-points.js?v=20260508c // @require https://eghome.textile668.cn/static/u-helper-ai.js?v=20260519aipool4 // @require https://eghome.textile668.cn/static/u-helper-discussion.js?v=20260522prod6 // @require https://eghome.textile668.cn/static/u-helper-templates.js?v=20260508d // @require https://eghome.textile668.cn/static/u-helper-announcement.js?v=20260508d // @require https://eghome.textile668.cn/static/u-helper-bank.js?v=20260508d // @require https://eghome.textile668.cn/static/u-helper-recording.js?v=20260509y // @require https://eghome.textile668.cn/static/u-helper-keepalive.js?v=20260509y // @require https://eghome.textile668.cn/static/u-helper-timing.js?v=20260519y // @require https://eghome.textile668.cn/static/u-helper-delay.js?v=20260519y // @require https://eghome.textile668.cn/static/u-helper-study-duration.js?v=20260529y // @require https://eghome.textile668.cn/static/u-helper-autorefresh.js?v=20260509y // @require https://eghome.textile668.cn/static/u-helper-skip.js?v=20260509y // @require https://eghome.textile668.cn/static/u-helper-popup-guard.js?v=20260509y // @require https://eghome.textile668.cn/static/u-helper-classic-ucampus.js?v=20260509y // @resource U_HELPER_CSS https://eghome.textile668.cn/static/u-helper-ui.css?v=20260508a60520 // ==/UserScript== function injectExternalStyle() { try { if (typeof GM_getResourceText !== 'undefined' && typeof GM_addStyle !== 'undefined') { var css = GM_getResourceText('U_HELPER_CSS'); if (css) GM_addStyle(css); } } catch (_) {} } injectExternalStyle(); var U_HELPER_DEBUG = localStorage.getItem('u-helper-debug') === 'true'; var ULogger = { debug: function () { if (!U_HELPER_DEBUG) return; console.log.apply(console, arguments); }, info: function () { if (!U_HELPER_DEBUG) return; console.log.apply(console, arguments); }, warn: function () { if (!U_HELPER_DEBUG) return; console.warn.apply(console, arguments); }, error: function () { console.error.apply(console, arguments); } }; window.UHelperDebug = { enable: function () { localStorage.setItem('u-helper-debug', 'true'); location.reload(); }, disable: function () { localStorage.removeItem('u-helper-debug'); location.reload(); }, status: function () { return localStorage.getItem('u-helper-debug') === 'true'; } }; function maskUserId(id) { id = String(id || ''); if (id.length <= 8) return '***'; return id.slice(0, 4) + '***' + id.slice(-4); } try { if (typeof GM_addStyle !== 'undefined') { GM_addStyle(` .uh-delay-status { font-size: 12px; color: #64748b; padding: 8px 10px; border-radius: 10px; background: rgba(255,255,255,0.35); border: 1px solid rgba(200,210,230,0.25); line-height: 1.6; } .uh-delay-grid { display: grid; grid-template-columns: 1fr 72px 72px; gap: 8px; align-items: center; } .uh-delay-grid input { width: 100%; text-align: center; } .uh-delay-mode-row { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 10px; align-items: center; } .uh-delay-mode-row .u-helper-label { white-space: nowrap; } `); } } catch (_) {} try { var _oldProv = localStorage.getItem('u-discussion-ai-provider'); if (!localStorage.getItem('u-ai-provider') && _oldProv) { localStorage.setItem('u-ai-provider', _oldProv); } localStorage.removeItem('u-discussion-ai-provider'); localStorage.removeItem('u-discussion-ai-model'); localStorage.removeItem('u-ai-model'); } catch (_) {} function getUI() { return window.UHelperTemplates || null; } function safeToast(msg, type, pos) { var ui = getUI(); if (ui && typeof ui.toast === 'function') { ui.toast(msg, type, pos); return; } if (msg === undefined) return; if (type === undefined) type = 'info'; if (pos === undefined) pos = 'center'; var div = document.createElement('div'); div.className = 'u-toast u-toast-' + pos + ' u-toast-' + type; div.textContent = msg; document.body.appendChild(div); setTimeout(function() { div.style.opacity='0'; setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 500); }, 3000); } function safeRender(name, data) { var ui = getUI(); return (ui && typeof ui.render === 'function') ? ui.render(name, data) : ''; } function removeEl(id) { var el = document.getElementById(id); if (el) el.remove(); } function showToast(message, type, position) { if (type === undefined) type = 'info'; if (position === undefined) position = 'center'; var div = document.createElement('div'); div.className = 'u-toast u-toast-' + position + ' u-toast-' + type; div.textContent = message; document.body.appendChild(div); setTimeout(function() { div.style.opacity = '0'; div.style.transition = 'opacity 0.5s'; setTimeout(function() { if (div.parentNode) div.remove(); }, 500); }, 3000); } const API_CONFIG = { BASE_URL: "https://eghome.textile668.cn/api", ENDPOINTS: { SAVE_TRUTH: "/save-truth", POINTS: "/points", KIMI_CHAT: "/kimi", AI_CHAT: "/ai/chat", AI_POOL_CHAT: "/ai-pool/chat", AI_POOL_JOB: "/ai-pool/job", SOLVE: "/solve-question", GET_ANSWERS: "/get-answers", INJECT: "/inject" } }; const getApiUrl = (endpoint) => `${API_CONFIG.BASE_URL}${endpoint}`; function apiPost(endpoint, body = {}, timeout = 15000) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: getApiUrl(endpoint), headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(body), timeout, onload: function (res) { try { resolve(JSON.parse(res.responseText || '{}')); } catch (e) { reject(new Error('INVALID_JSON')); } }, onerror: function () { reject(new Error('NETWORK_ERROR')); }, ontimeout: function () { reject(new Error('TIMEOUT')); } }); }); } function apiGet(endpoint, timeout = 15000) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: getApiUrl(endpoint), headers: { 'Content-Type': 'application/json' }, timeout, onload: function (res) { try { resolve(JSON.parse(res.responseText || '{}')); } catch (e) { reject(new Error('INVALID_JSON')); } }, onerror: function () { reject(new Error('NETWORK_ERROR')); }, ontimeout: function () { reject(new Error('TIMEOUT')); } }); }); } function shouldUseAIPool(payload) { try { if (localStorage.getItem('u-ai-pool-disabled') === 'true') { return false; } var provider = ''; if (payload && payload.provider) { provider = String(payload.provider).toLowerCase(); } else { provider = String(localStorage.getItem('u-ai-provider') || 'kimi').toLowerCase(); } return provider === 'siliconflow'; } catch (_) { return false; } } function createAIPoolError(message, code, noFallback) { var err = new Error(message || code || 'AI池任务失败'); err.code = code || ''; err.noFallback = !!noFallback; return err; } function shouldFallbackFromAIPoolError(code) { var noFallbackCodes = [ 'AI_QUEUE_FULL', 'AI_JOB_ALREADY_RUNNING', 'INSUFFICIENT_POINTS', 'POINT_DEDUCT_FAILED', 'POINT_QUERY_FAILED', 'MISSING_USER_ID', 'MISSING_MESSAGES' ]; return noFallbackCodes.indexOf(code) === -1; } async function callLegacyAIChat(payload, options) { if (options && typeof options.onStatus === 'function') { options.onStatus('正在使用旧AI接口...'); } return await apiPost(API_CONFIG.ENDPOINTS.AI_CHAT, payload, options && options.timeout ? options.timeout : 60000); } async function callAIWithQueue(payload, options) { options = options || {}; if (!shouldUseAIPool(payload)) { return await callLegacyAIChat(payload, options); } var createRes; try { if (options.onStatus) options.onStatus('正在创建AI池任务...'); createRes = await apiPost(API_CONFIG.ENDPOINTS.AI_POOL_CHAT, payload, 30000); } catch (e) { safeToast('AI池连接失败,已切回旧AI接口', 'warning'); return await callLegacyAIChat(payload, options); } if (createRes && createRes.answer) { return createRes; } if (!createRes || !createRes.success || !createRes.jobId) { var code = createRes && (createRes.code || createRes.error); if (!shouldFallbackFromAIPoolError(code)) { throw createAIPoolError( (createRes && createRes.message) || code || 'AI池任务创建失败', code, true ); } safeToast('AI池暂不可用,已切回旧AI接口', 'warning'); return await callLegacyAIChat(payload, options); } var jobId = createRes.jobId; var startAt = Date.now(); var maxWaitMs = options.maxWaitMs || 90000; var pollFailCount = 0; while (true) { if (Date.now() - startAt > maxWaitMs) { throw new Error('AI生成超时,请稍后重试'); } await sleep(1500); var job; try { job = await apiGet(API_CONFIG.ENDPOINTS.AI_POOL_JOB + '/' + encodeURIComponent(jobId), 15000); pollFailCount = 0; } catch (e) { pollFailCount++; if (pollFailCount >= 3) { safeToast('AI池查询异常,已切回旧AI接口', 'warning'); return await callLegacyAIChat(payload, options); } continue; } if (job.status === 'queued') { if (options.onStatus) options.onStatus('AI排队中,第 ' + (job.position || '-') + ' 位'); continue; } if (job.status === 'running') { if (options.onStatus) options.onStatus('AI生成中...'); continue; } if (job.status === 'success') { if (job.points !== undefined && window.UHelperPoints && typeof window.UHelperPoints.setPoints === 'function') { window.UHelperPoints.setPoints(job.points); if (typeof syncPointsState === 'function') syncPointsState(); if (typeof updatePointsDisplay === 'function') updatePointsDisplay(); } return job; } if (job.status === 'failed' || job.success === false) { var failCode = job.code || job.error; if (!shouldFallbackFromAIPoolError(failCode)) { throw createAIPoolError( job.message || failCode || 'AI池任务失败', failCode, true ); } safeToast('AI池任务失败,已切回旧AI接口', 'warning'); return await callLegacyAIChat(payload, options); } } } if (U_HELPER_DEBUG) { window.callAIWithQueue = callAIWithQueue; window.shouldUseAIPool = shouldUseAIPool; window.isAIPoolEnabled = function () { return shouldUseAIPool(); }; } const SubmitInterceptor = { CACHE_KEY: '__UCAMPUS_ANSWER_CACHE__', init() { ULogger.debug('[拦截器] 初始化网络监听 (支持多Section)...'); this.setupHook(); }, showToast(msg) { var div = document.createElement('div'); div.innerHTML = msg; div.className = 'u-toast u-toast-center u-toast-success'; document.body.appendChild(div); setTimeout(function() { div.style.opacity='0'; setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 500); }, 3000); }, fillRecursively(targetNode, answerQueue) { let count = 0; if (targetNode.children && Array.isArray(targetNode.children)) { for (const child of targetNode.children) { count += this.fillRecursively(child, answerQueue); } } else if (targetNode.hasOwnProperty('value')) { const standard = answerQueue.shift(); if (standard) { let val = standard.value || standard.answer || standard.answers; if (val) { if (!Array.isArray(val)) val = [val]; targetNode.value = val; count++; } } } return count; }, mergeAnswers(payloadStr, answerData) { try { const payload = JSON.parse(payloadStr); let truthObj = answerData; if (typeof truthObj === 'string') { try { truthObj = JSON.parse(truthObj); } catch(e) {} } if (!truthObj || !truthObj.children) return null; let answerQueue = JSON.parse(JSON.stringify(truthObj.children)); if (!payload.quesDatas || payload.quesDatas.length === 0) return null; let totalFilled = 0; for (let i = 0; i < payload.quesDatas.length; i++) { const section = payload.quesDatas[i]; if (!section.answer) continue; let targetAnswerObj = JSON.parse(section.answer); const filledInThisSection = this.fillRecursively(targetAnswerObj, answerQueue); totalFilled += filledInThisSection; if (filledInThisSection > 0) { section.answer = JSON.stringify(targetAnswerObj); } } if (payload.usedTime !== undefined) { payload.usedTime = Math.floor(Math.random() * (120 - 60 + 1)) + 60; } return { newJson: JSON.stringify(payload), count: totalFilled }; } catch (e) { ULogger.error('[拦截器] 合并错误:', e); return null; } }, setupHook() { const XHR = unsafeWindow.XMLHttpRequest; const originalXHROpen = XHR.prototype.open; const originalXHRSend = XHR.prototype.send; const _this = this; XHR.prototype.open = function(method, url) { this._method = method; this._url = url; originalXHROpen.apply(this, arguments); }; XHR.prototype.send = function(data) { const xhr = this; if (this._method === 'POST' && this._url && this._url.includes('/course/api/v3/newExploration/submit')) { const cachedAnswer = unsafeWindow[_this.CACHE_KEY]; if (cachedAnswer && typeof data === 'string') { const result = _this.mergeAnswers(data, cachedAnswer); if (result) { _this.showToast(`🚀 答案自动修正成功 (${result.count}空)`); originalXHRSend.call(xhr, result.newJson); return; } } } originalXHRSend.call(this, data); }; } }; let loadedQuestionBank = null; let currentTopicUsedAnswers = new Set(); let lastActiveTopicName = ''; let multiPageMode = { isActive: false, exerciseId: null, pageIndex: 0, totalAnswers: [], lastUrl: '' }; function getSkipPageType() { const videoSelectors = [ 'div.video-material-wrapper', 'div.question-video-player', 'video[src]', '.video-js', '.video-container', '.vjs-tech', '.video-player-container', '.prism-player', '.video-material' ]; for (const sel of videoSelectors) { if (document.querySelector(sel)) { if (isQuestionPage()) { ULogger.debug(`[页面检测] 🎬 视频选择器 "${sel}" 命中,但同时检测到题目元素,判定为题目页面,不跳过。`); break; } ULogger.debug(`[页面检测] 🎬 检测到视频页面 (选择器: ${sel}),返回类型: video`); return 'video'; } } const readingSelectors = [ 'div.material-content', '.reading-material', '.article-content', '.passage-content', '.reading-content', '.material-text', '.article-body', '.passage-body' ]; for (const sel of readingSelectors) { if (document.querySelector(sel)) { if (isQuestionPage()) { ULogger.debug(`[页面检测] 📖 阅读选择器 "${sel}" 命中,但同时检测到题目元素,判定为题目页面,不跳过。`); break; } ULogger.debug(`[页面检测] 📖 检测到阅读/文章页面 (选择器: ${sel}),返回类型: reading`); return 'reading'; } } const infoSelectors = [ '.info-page', '[class*="intro-page"]', '.courseware-info', '.course-intro', '.unit-intro', '.lesson-intro', '.page-info', '.info-content' ]; for (const sel of infoSelectors) { if (document.querySelector(sel)) { if (isQuestionPage()) { ULogger.debug(`[页面检测] ℹ️ 信息页选择器 "${sel}" 命中,但同时检测到题目元素,判定为题目页面,不跳过。`); break; } ULogger.debug(`[页面检测] ℹ️ 检测到信息页 (选择器: ${sel}),返回类型: info`); return 'info'; } } ULogger.debug(`[页面检测] ✅ 页面未命中任何非答题类型,getSkipPageType = null`); return null; } function shouldSkipPage() { return getSkipPageType() !== null; } async function handleSkipPageHangLoop() { const pageType = getSkipPageType(); if (!pageType) return false; ULogger.debug(`[挂机] 当前为非答题页面(${pageType}),跳过答案消耗`); if (pageType === 'video') { await handleVideoPageHang(); } else { await handleReadingOrInfoPageHang(pageType); } return true; } async function handleVideoPageHang() { let videos = Array.from(document.querySelectorAll('video')).filter(v => v.duration > 0 && !v.ended); if (videos.length === 0) { ULogger.debug('[挂机] 🎬 视频页面但未找到有效视频元素,尝试直接导航...'); await navigateAfterSkipPage(); return; } ULogger.debug(`[挂机] 🎬 检测到 ${videos.length} 个视频,开始自动播放...`); const savedSpeed = localStorage.getItem('u-video-speed') || '2.0'; videos.forEach(v => { if (v.paused) { v.muted = true; v.play().catch(e => ULogger.warn('[挂机] 视频自动播放被浏览器拦截:', e)); } v.playbackRate = parseFloat(savedSpeed); }); if (window.UHelperDelay && typeof window.UHelperDelay.markPageEnter === 'function') { window.UHelperDelay.markPageEnter('video'); } if (window.UHelperStudyDuration && typeof window.UHelperStudyDuration.markPageEnter === 'function' && window.UHelperStudyDuration.isEnabled()) { window.UHelperStudyDuration.markPageEnter(); } var delayConfig = window.UHelperDelay && window.UHelperDelay.getConfig ? window.UHelperDelay.getConfig() : null; var accurateDelayMode = delayConfig && delayConfig.mode === 'accurate'; var accurateTiming = window.UHelperTiming && typeof window.UHelperTiming.isAccurateMode === 'function' && window.UHelperTiming.isAccurateMode(); var blockVideoSkip = accurateDelayMode || accurateTiming; if (window.__videoSkipEnabled && !blockVideoSkip) { if (window.UHelperDelay && typeof window.UHelperDelay.waitUntilCanLeave === 'function') { await window.UHelperDelay.waitUntilCanLeave('video', { reason: 'video-before-skip', maxWait: 120000 }); } ULogger.debug('[挂机] 🎬 视频跳过模式已开启,停留达标后跳到末尾...'); videos.forEach(v => { if (v.duration > 0) v.currentTime = v.duration; }); await sleep(1000); ULogger.debug('[挂机] 视频播放完成,继续下一步'); await navigateAfterSkipPage(); return; } else if (window.__videoSkipEnabled && blockVideoSkip) { ULogger.debug('[UHelperDelay] 准确时长/准确延迟模式已开启,阻止视频直接跳过'); } ULogger.debug(`[挂机] 🎬 等待视频播放完成(倍速: ${savedSpeed}x)...`); await new Promise(resolve => { const checkTimer = setInterval(() => { if (window.__videoSkipEnabled && !blockVideoSkip) { const remaining = Array.from(document.querySelectorAll('video')).filter(v => v.duration > 0 && !v.ended); remaining.forEach(v => { if (v.duration > 0) v.currentTime = v.duration; }); clearInterval(checkTimer); resolve(); return; } const remaining = Array.from(document.querySelectorAll('video')).filter(v => v.duration > 0 && !v.ended); if (remaining.length === 0) { clearInterval(checkTimer); resolve(); } }, 2000); setTimeout(() => { clearInterval(checkTimer); resolve(); }, 1800000); }); ULogger.debug('[挂机] 视频播放完成,继续下一步'); await navigateAfterSkipPage(); } async function handleReadingOrInfoPageHang(pageType) { const label = pageType === 'reading' ? '阅读/文章' : '信息'; ULogger.debug(`[挂机] 📖 ${label}页面,开始等待页面变化...`); if (window.UHelperDelay && typeof window.UHelperDelay.markPageEnter === 'function') { window.UHelperDelay.markPageEnter(pageType); } if (window.UHelperStudyDuration && typeof window.UHelperStudyDuration.markPageEnter === 'function' && window.UHelperStudyDuration.isEnabled()) { window.UHelperStudyDuration.markPageEnter(); } const startUrl = location.href; const startTime = Date.now(); const MAX_WAIT = 600000; if (window.UHelperDelay && typeof window.UHelperDelay.waitUntilCanLeave === 'function') { ULogger.debug(`[挂机] 📖 等待停留时间达标...`); await new Promise(resolve => { const waitTimer = setInterval(() => { if (location.href !== startUrl || isQuestionPage() || document.querySelector('video[src], .video-js, .video-container')) { clearInterval(waitTimer); resolve(); return; } if (window.UHelperDelay.canLeavePage(pageType)) { clearInterval(waitTimer); resolve(); return; } if (Date.now() - startTime > MAX_WAIT) { clearInterval(waitTimer); resolve(); return; } }, 1000); }); } const NAV_ATTEMPT_INTERVAL = 15000; let lastNavAttempt = 0; await new Promise(resolve => { const checkTimer = setInterval(() => { if (location.href !== startUrl) { ULogger.debug(`[挂机] 📖 检测到 URL 变化,${label}页面已切换。`); clearInterval(checkTimer); resolve(); return; } if (isQuestionPage()) { ULogger.debug(`[挂机] 📖 检测到题目元素出现,页面已变为题目页。`); clearInterval(checkTimer); resolve(); return; } if (document.querySelector('video[src], .video-js, .video-container')) { ULogger.debug(`[挂机] 📖 检测到视频元素出现,页面类型已变化。`); clearInterval(checkTimer); resolve(); return; } const now = Date.now(); if (now - lastNavAttempt > NAV_ATTEMPT_INTERVAL) { lastNavAttempt = now; const clicked = tryClickNavigationButton(); if (clicked) { ULogger.debug(`[挂机] 📖 尝试点击导航按钮,等待页面响应...`); setTimeout(() => { if (location.href !== startUrl || isQuestionPage()) { clearInterval(checkTimer); resolve(); } }, 3000); } } if (Date.now() - startTime > MAX_WAIT) { ULogger.warn(`[挂机] ⏰ ${label}页面等待超时(${MAX_WAIT / 1000}秒),强制继续。`); clearInterval(checkTimer); resolve(); } }, 3000); }); var afterNavDelay = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('pageReady') : 2000; await sleep(afterNavDelay); const newType = getSkipPageType(); if (newType) { ULogger.debug(`[挂机] 📖 页面变化后仍为非答题页面(${newType}),继续挂机循环...`); autoRunTimeoutId = setTimeout(runNextStep, 3000); } else { ULogger.debug(`[挂机] 📖 页面已变为题目页,继续正常答题流程。`); var nextDelay = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 2000; autoRunTimeoutId = setTimeout(runNextStep, nextDelay); } } async function navigateAfterSkipPage() { await delayBeforeNavigate('navigateAfterSkipPage'); if (!shouldSkipPage()) { ULogger.debug('[挂机] 页面已自动切换到题目页,继续正常流程。'); var d1 = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 2000; autoRunTimeoutId = setTimeout(runNextStep, d1); return; } ULogger.debug('[挂机] 尝试在非题目页面进行导航...'); await waitAfterSubmitAssessment('navigateAfterSkipPage'); const nextBtn = findFooterButtonByText('下一题'); if (nextBtn) { ULogger.debug('[挂机] 点击 "下一题" 按钮'); nextBtn.click(); var d2 = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 5000; autoRunTimeoutId = setTimeout(runNextStep, d2); return; } const navigatedSub = await navigateToNextSubTopic(); if (navigatedSub) { var d3 = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 5000; autoRunTimeoutId = setTimeout(runNextStep, d3); return; } const navigatedToc = await navigateToNextTocItem(); if (navigatedToc) { var d4 = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 5000; autoRunTimeoutId = setTimeout(runNextStep, d4); return; } const clicked = tryClickNavigationButton(); if (clicked) { var d5 = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('afterNavigate') : 5000; autoRunTimeoutId = setTimeout(runNextStep, d5); return; } ULogger.debug('[挂机] 非题目页面导航未找到可用操作,3 秒后重试...'); var retryDelay = window.UHelperDelay && typeof window.UHelperDelay.getNextStepDelay === 'function' ? window.UHelperDelay.getNextStepDelay('retry') : 3000; autoRunTimeoutId = setTimeout(runNextStep, retryDelay); } function tryClickNavigationButton() { const navButtonTexts = ['下一页', '继续学习', '继续', '下一步', 'Next', 'Continue']; for (const text of navButtonTexts) { const btn = findFooterButtonByText(text); if (btn) { ULogger.debug(`[挂机] 🔘 点击导航按钮: "${text}"`); btn.click(); return true; } } const allClickable = document.querySelectorAll('button, a.ant-btn, a[href], .ant-btn'); for (const el of allClickable) { const text = (el.textContent || '').trim(); if (navButtonTexts.some(t => text.includes(t))) { const rect = el.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { ULogger.debug(`[挂机] 🔘 点击页面导航元素: "${text}"`); el.click(); return true; } } } return false; } function isQuestionPage() { const questionSelectors = [ '.itest-section', '.itest-danxuan', 'input[qindex]', 'input[type="radio"][qindex]', 'input.blankinput[qindex]', 'div.option', '.option.isNotReview', 'input.fill-blank--bc-input-DelG1', '.fe-scoop input:not([type="hidden"])', '.comp-abs-input input', 'textarea.question-inputbox-input', '.question-inputbox-input', 'textarea.question-textarea-content', 'textarea.writing--textarea-36VPs', 'textarea.scoopFill_textarea', '.question-common-abs-scoop .fe-scoop[data-scoop-index]', '.comp-scoop-reply-dropdown-selection-overflow .fe-scoop[data-scoop-index]', '.question-abs-basic-scoop-content .fe-scoop[data-scoop-index]', '.fe-scoop[data-scoop-index] .ant-dropdown-trigger', '#sortableListWrapper', '.sortable-list-wrapper', 'div.sequence-pc--sequence-container-33roc', 'div[class*="sequence-pc--sequence-container"]', '.MultipleChoice--checkbox-item-34A_-', 'ul[class*="single-choice"]', '.question-common-abs-reply', '.question-common-abs-banked-cloze', '.css-danxuan.row', '.question-video-popup' ]; let totalQuestions = 0; let detectedTypes = []; for (const sel of questionSelectors) { const elements = document.querySelectorAll(sel); if (elements.length > 0) { totalQuestions += elements.length; detectedTypes.push(`${sel}(${elements.length})`); } } if (typeof analyzePageQuestions === 'function') { try { const analysis = analyzePageQuestions(); if (analysis && analysis.count > 0 && analysis.type !== 'unknown') { if (!detectedTypes.some(t => t.includes('analyzePageQuestions'))) { detectedTypes.push(`analyzePageQuestions:${analysis.type}(${analysis.count})`); } if (totalQuestions === 0) totalQuestions = analysis.count; } } catch (e) { } } const isQuestion = totalQuestions > 0; ULogger.debug(`[页面检测] 🔍 题目页面检测: ${isQuestion ? '是' : '否'} | 题目元素总数: ${totalQuestions} | 检测到的类型: [${detectedTypes.join(', ')}]`); return isQuestion; } Object.defineProperty(window, 'autoRefreshEnabled', { get: function () { return window.UHelperAutoRefresh ? window.UHelperAutoRefresh.isEnabled() : false; }, set: function (val) { } }); window.__refreshInterval = window.UHelperAutoRefresh ? window.UHelperAutoRefresh.getIntervalMinutes() : 30; window.__refreshAfterPopupBlock = window.UHelperAutoRefresh ? window.UHelperAutoRefresh.getRefreshAfterPopupBlock() : false; window.isAutoModeRunning = false; window.initiatePayFromScript = function (amount, buyType) { if (window.UHelperPoints && typeof window.UHelperPoints.pay === 'function') { return window.UHelperPoints.pay(amount, buyType); } ULogger.warn('[支付] UHelperPoints 未加载'); }; (function setupQuestionInterceptor() { 'use strict'; ULogger.debug('🔧 V2脚本: 正在初始化题目数据拦截器...'); const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this._interceptUrl = url; this._interceptMethod = method; originalXHROpen.apply(this, arguments); }; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(data) { const currentXHR = this; const originalOnReadyStateChange = this.onreadystatechange; this.onreadystatechange = function() { if (currentXHR.readyState === 4 && currentXHR.status === 200) { try { const responseJson = JSON.parse(currentXHR.responseText); if (responseJson && responseJson.code === 0 && responseJson.content && responseJson.k) { if (responseJson.content.startsWith && responseJson.content.startsWith('unipus.')) { window.__interceptedQuestionData = { encryptedContent: responseJson.content, decryptionKey: responseJson.k, url: currentXHR._interceptUrl, timestamp: Date.now() }; window.dispatchEvent(new CustomEvent('questionDataIntercepted', { detail: window.__interceptedQuestionData })); } } } catch (e) { } } if (originalOnReadyStateChange) { originalOnReadyStateChange.apply(currentXHR, arguments); } }; originalXHRSend.apply(this, arguments); }; const originalFetch = window.fetch; window.fetch = function(...args) { const [resource] = args; let url = ''; if (typeof resource === 'string') { url = resource; } else if (resource instanceof Request) { url = resource.url; } return originalFetch.apply(this, args).then(async response => { const clonedResponse = response.clone(); try { const responseJson = await clonedResponse.json(); if (responseJson && responseJson.code === 0 && responseJson.content && responseJson.k) { if (responseJson.content.startsWith && responseJson.content.startsWith('unipus.')) { ULogger.debug('[题目拦截] 捕获到加密题目数据', '长度:', responseJson.content.length); window.__interceptedQuestionData = { encryptedContent: responseJson.content, decryptionKey: responseJson.k, url: url, timestamp: Date.now() }; window.dispatchEvent(new CustomEvent('questionDataIntercepted', { detail: window.__interceptedQuestionData })); } } } catch (e) { } return response; }); }; ULogger.debug('✅ V2脚本: 题目数据拦截器已启动'); window.addEventListener('questionDataIntercepted', (e) => { const data = e.detail; ULogger.debug('📢 V2脚本: 收到题目数据拦截事件,已更新 __interceptedQuestionData'); }); })(); (function setupSubmitInterceptor() { 'use strict'; const SERVER_URL = getApiUrl(API_CONFIG.ENDPOINTS.SAVE_TRUTH); function getPageKey() { try { let unitId = window.location.href.split('/').pop().split('?')[0]; let courseId = ''; const hash = window.location.hash; const hashMatch = hash.match(/course-v2:[^+]+\+([^+]+)\+/); if (hashMatch && hashMatch[1]) { courseId = hashMatch[1]; } else { const urlParams = new URLSearchParams(window.location.search); courseId = urlParams.get('courseId') || urlParams.get('cid'); } return courseId ? `${courseId}_${unitId}` : unitId; } catch (e) { return 'unknown_key'; } } function reportTruthToServer(responseText, sourceUrl) { if (!responseText || !responseText.includes('unipus.')) return; const urlKey = getPageKey(); ULogger.debug(`📤 [${sourceUrl}] 捕获数据, Key: ${urlKey}`); GM_xmlhttpRequest({ method: 'POST', url: SERVER_URL, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ rawResponse: responseText, pageKey: urlKey }), onload: function(res) { if (res.status === 200) ULogger.debug('✅'); } }); } const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this._interceptUrl = url; originalXHROpen.apply(this, arguments); }; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(data) { const currentXHR = this; const originalOnReadyStateChange = this.onreadystatechange; this.onreadystatechange = function() { if (currentXHR.readyState === 4 && currentXHR.status === 200) { try { if (currentXHR._interceptUrl && currentXHR._interceptUrl.includes('/course/api/v3/newExploration/submit')) { reportTruthToServer(currentXHR.responseText, 'XHR'); } } catch (e) {} } if (originalOnReadyStateChange) originalOnReadyStateChange.apply(currentXHR, arguments); }; originalXHRSend.apply(this, arguments); }; const originalFetch = window.fetch; window.fetch = function(...args) { let url = args[0] instanceof Request ? args[0].url : args[0]; return originalFetch.apply(this, args).then(async response => { const cloned = response.clone(); try { if (url && url.includes('/course/api/v3/newExploration/submit')) { const text = await cloned.text(); reportTruthToServer(text, 'Fetch'); } } catch (e) {} return response; }); }; ULogger.debug('✅ 拦截器就绪'); })(); const POINTS_PER_QUESTION = (window.UHelperPoints && window.UHelperPoints.getCost()) || 2; let userPoints = (window.UHelperPoints && window.UHelperPoints.getPoints()) || 0; let userId = (window.UHelperPoints && window.UHelperPoints.getUserId()) || ''; function syncPointsState() { if (window.UHelperPoints) { userId = window.UHelperPoints.getUserId(); userPoints = window.UHelperPoints.getPoints(); } } const SkipManager = { get storageKey() { return 'u-helper-skipped-chapters'; }, getSkippedList: function () { return window.UHelperSkip.getSkippedList(); }, saveSkippedList: function (list) { return window.UHelperSkip.saveSkippedList(list); }, shouldSkip: function (chapterName) { return window.UHelperSkip.shouldSkip(chapterName); }, initPanel: function (contentContainer) { return window.UHelperSkip.initPanel(contentContainer); } }; const keepAliveSystem = { start: function () { return window.UHelperKeepAlive.start(); }, stop: function () { return window.UHelperKeepAlive.stop(); }, toggle: function () { return window.UHelperKeepAlive.toggle(); }, updateButton: function () { return window.UHelperKeepAlive.updateButton(); }, get isRunning() { return window.UHelperKeepAlive.isRunning(); } }; if (window.UHelperKeepAlive && typeof window.UHelperKeepAlive.init === 'function') { window.UHelperKeepAlive.init({ safeToast }); } if (window.UHelperTiming && typeof window.UHelperTiming.init === 'function') { window.UHelperTiming.init({ safeToast: safeToast, isAutoRunning: function () { return !!window.isAutoModeRunning; }, getPageType: function () { if (typeof getSkipPageType === 'function') { return getSkipPageType() || (typeof isQuestionPage === 'function' && isQuestionPage() ? 'question' : 'normal'); } return 'unknown'; } }); } if (window.UHelperDelay && typeof window.UHelperDelay.init === 'function') { window.UHelperDelay.init({ safeToast: safeToast, getPageType: function () { if (typeof getSkipPageType === 'function') { return getSkipPageType() || (typeof isQuestionPage === 'function' && isQuestionPage() ? 'question' : 'normal'); } return 'unknown'; }, isAutoRunning: function () { return !!window.isAutoModeRunning; }, getTimingState: function () { return window.UHelperTiming && typeof window.UHelperTiming.getState === 'function' ? window.UHelperTiming.getState() : null; }, isAccurateTimingMode: function () { return window.UHelperTiming && typeof window.UHelperTiming.isAccurateMode === 'function' && window.UHelperTiming.isAccurateMode(); }, reviveNow: function (reason) { if (window.UHelperTiming && typeof window.UHelperTiming.reviveNow === 'function') { window.UHelperTiming.reviveNow(reason || 'delay_wait'); } } }); } if (window.UHelperAutoRefresh && typeof window.UHelperAutoRefresh.init === 'function') { window.UHelperAutoRefresh.init({ safeToast }); } if (window.UHelperStudyDuration && typeof window.UHelperStudyDuration.init === 'function') { window.UHelperStudyDuration.init({ safeToast: safeToast }); } if (window.UHelperPopupGuard && typeof window.UHelperPopupGuard.init === 'function') { window.UHelperPopupGuard.init({ safeToast: safeToast, getRefreshAfterPopupBlock: function () { return window.UHelperAutoRefresh ? window.UHelperAutoRefresh.getRefreshAfterPopupBlock() : !!window.__refreshAfterPopupBlock; }, setRefreshAfterPopupBlock: function (val) { if (window.UHelperAutoRefresh && typeof window.UHelperAutoRefresh.setRefreshAfterPopupBlock === 'function') { window.UHelperAutoRefresh.setRefreshAfterPopupBlock(val); } else { window.__refreshAfterPopupBlock = !!val; } } }); } async function refreshPoints() { syncPointsState(); var ok = await window.UHelperPoints.refresh(); syncPointsState(); return ok; } if (U_HELPER_DEBUG) { window.refreshPoints = refreshPoints; } function updatePointsDisplay() { window.UHelperPoints.updateDisplay(); syncPointsState(); } window.updateUI = updatePointsDisplay; function createPointsDisplay() { updatePointsDisplay(); } async function initPointsSystem() { createPointsDisplay(); await refreshPoints(); } function startPointsSystemOnce() { if (window.UHelperPoints && typeof window.UHelperPoints.start === 'function') { window.UHelperPoints.start(); } syncPointsState(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startPointsSystemOnce); } else { startPointsSystemOnce(); } async function checkAndDeductPoints() { syncPointsState(); var ok = await window.UHelperPoints.deduct(POINTS_PER_QUESTION); syncPointsState(); return ok; } let useKimiAI = localStorage.getItem('useKimiAI') === 'true'; function showPointsPackages() { return window.UHelperPoints.showPackages(); } window.showPointsPackages = showPointsPackages; if (window.UHelperPoints && typeof window.UHelperPoints.init === 'function') { window.UHelperPoints.init({ apiPost, API_CONFIG, getUI, safeToast, updateHighScoreLockState: function () { if (window.updateHighScoreLockState) window.updateHighScoreLockState(); } }); window.UHelperPoints.start(); syncPointsState(); } if (window.UHelperAI && typeof window.UHelperAI.init === 'function') { window.UHelperAI.init({ apiPost, apiGet, callAIWithQueue, API_CONFIG, getAIProvider: function () { return localStorage.getItem('u-ai-provider') || 'kimi'; }, getUserId: function () { return window.UHelperPoints ? window.UHelperPoints.getUserId() : userId; }, getUserPoints: function () { return window.UHelperPoints ? window.UHelperPoints.getPoints() : userPoints; }, setUserPoints: function (points) { if (window.UHelperPoints) { window.UHelperPoints.setPoints(points); } syncPointsState(); }, getUseKimiAI: function () { return useKimiAI; }, getPointsPerQuestion: function () { return window.UHelperPoints ? window.UHelperPoints.getCost() : POINTS_PER_QUESTION; }, updatePointsDisplay: function () { updatePointsDisplay(); }, showPointsPackages: function () { showPointsPackages(); }, getInterceptedQuestionData: function () { return window.__interceptedQuestionData; }, clearInterceptedQuestionData: function () { window.__interceptedQuestionData = null; } }); } async function askKimi(question, retryCount = 3, retryDelay = 1000) { if (!window.UHelperAI || typeof window.UHelperAI.ask !== 'function') { ULogger.error('[AI] UHelperAI 未加载或未初始化'); return null; } return await window.UHelperAI.ask(question, retryCount, retryDelay); } async function askKimiWithEncryptedData(encryptedContent, decryptionKey, retryCount = 3, retryDelay = 1000) { if (!window.UHelperAI || typeof window.UHelperAI.askEncrypted !== 'function') return null; return await window.UHelperAI.askEncrypted(encryptedContent, decryptionKey, retryCount, retryDelay); } async function askKimiLegacy(question, retryCount = 2, retryDelay = 1000) { if (!window.UHelperAI || typeof window.UHelperAI.askLegacy !== 'function') return null; return await window.UHelperAI.askLegacy(question, retryCount, retryDelay); } function analyzeQuestionLocally(question) { if (!window.UHelperAI || typeof window.UHelperAI.analyzeLocal !== 'function') return null; return window.UHelperAI.analyzeLocal(question); } async function askKimiWithTimeout(question, timeoutMs, retryCount, retryDelay) { if (window.UHelperDiscussion) return null; return null; } async function waitForDiscussionElements(maxWaitMs, pollInterval) { if (window.UHelperDiscussion) return { title: '', content: '', titleEl: null, contentEl: null }; return { title: '', content: '', titleEl: null, contentEl: null }; } if (window.UHelperDiscussion && typeof window.UHelperDiscussion.init === 'function') { window.UHelperDiscussion.init({ logger: ULogger, askKimi: askKimi, safeToast: safeToast, getUseKimiAI: function () { return useKimiAI; }, getUserPoints: function () { return window.UHelperPoints ? window.UHelperPoints.getPoints() : userPoints; }, getDefaultComment: function () { return localStorage.getItem('u-default-comment') || 'This topic is very meaningful. I think it helps me understand the lesson better.'; }, apiPost: apiPost, apiGet: apiGet, callAIWithQueue: callAIWithQueue, API_CONFIG: API_CONFIG, getUserId: function () { return window.UHelperPoints ? window.UHelperPoints.getUserId() : userId; }, setUserPoints: function (points) { if (window.UHelperPoints && typeof window.UHelperPoints.setPoints === 'function') { window.UHelperPoints.setPoints(points); } if (typeof syncPointsState === 'function') syncPointsState(); if (typeof updatePointsDisplay === 'function') updatePointsDisplay(); }, getAIProvider: function () { return localStorage.getItem('u-ai-provider') || 'kimi'; } }); } if (window.UHelperAnnouncement && typeof window.UHelperAnnouncement.init === 'function') { window.UHelperAnnouncement.init({ apiPost, API_CONFIG, getApiUrl, safeToast, showRecordNotification: typeof showRecordNotification === 'function' ? showRecordNotification : null }); } window.showAnnouncement = function (isAutoCheck) { if (window.UHelperAnnouncement && typeof window.UHelperAnnouncement.show === 'function') { return window.UHelperAnnouncement.show(isAutoCheck); } ULogger.warn('[公告] UHelperAnnouncement 未加载'); }; if (window.UHelperBank && typeof window.UHelperBank.init === 'function') { window.UHelperBank.init({ apiPost, API_CONFIG, getApiUrl, getUserId: function () { return window.UHelperPoints ? window.UHelperPoints.getUserId() : userId; }, safeToast, getUI, initiatePayFromScript: window.initiatePayFromScript }); } function applyBankCoverImages() { if (window.UHelperBank && typeof window.UHelperBank.applyCovers === 'function') { window.UHelperBank.applyCovers(); } } function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function exposeDebugGlobals() { try { var G = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; G.UHelperPoints = window.UHelperPoints; G.UHelperAI = window.UHelperAI; G.UHelperTemplates = window.UHelperTemplates; G.UHelperAnnouncement = window.UHelperAnnouncement; G.UHelperBank = window.UHelperBank; G.UHelperRecording = window.UHelperRecording; G.UHelperKeepAlive = window.UHelperKeepAlive; G.UHelperAutoRefresh = window.UHelperAutoRefresh; G.UHelperSkip = window.UHelperSkip; G.UHelperPopupGuard = window.UHelperPopupGuard; G.UHelperDiscussion = window.UHelperDiscussion; if (typeof askKimi === 'function') G.askKimi = askKimi; if (typeof refreshPoints === 'function') G.refreshPoints = refreshPoints; if (typeof updatePointsDisplay === 'function') G.updatePointsDisplay = updatePointsDisplay; if (typeof checkAndDeductPoints === 'function') G.checkAndDeductPoints = checkAndDeductPoints; if (typeof startAutoRefresh === 'function') G.startAutoRefresh = startAutoRefresh; if (typeof stopAutoRefresh === 'function') G.stopAutoRefresh = stopAutoRefresh; if (typeof setupDOMPopupInterception === 'function') G.setupDOMPopupInterception = setupDOMPopupInterception; if (typeof handleRecordingQuestions === 'function') G.handleRecordingQuestions = handleRecordingQuestions; if (typeof showProducts === 'function') G.showProducts = showProducts; if (typeof SkipManager !== 'undefined') G.SkipManager = SkipManager; if (typeof keepAliveSystem !== 'undefined') G.keepAliveSystem = keepAliveSystem; if (typeof callAIWithQueue === 'function') G.callAIWithQueue = callAIWithQueue; if (typeof shouldUseAIPool === 'function') G.shouldUseAIPool = shouldUseAIPool; G.isAIPoolEnabled = function () { return shouldUseAIPool(); }; ULogger.debug('[U助手调试] 已暴露模块到页面 window'); } catch (e) { ULogger.warn('[U助手调试] 暴露模块失败:', e); } } if (U_HELPER_DEBUG) { exposeDebugGlobals(); } function setupRecordingHijack() { return window.UHelperRecording.setupRecordingHijack(); } function setupURLHijack() { return window.UHelperRecording.setupURLHijack(); } function setupAudioSrcHijack() { return window.UHelperRecording.setupAudioSrcHijack(); } function monitorRecordButton() { return window.UHelperRecording.monitorRecordButton(); } function monitorReplayAudio() { return window.UHelperRecording.monitorReplayAudio(); } function handleRecordingQuestions() { return window.UHelperRecording.handleRecordingQuestions(); } function handleVocabularyRecording() { return window.UHelperRecording.handleVocabularyRecording(); } function handleSentenceRecitationExercise() { return window.UHelperRecording.handleSentenceRecitationExercise(); } function processSentenceRecording(recordButton, sentenceContainer, sentenceIndex) { return window.UHelperRecording.processSentenceRecording(recordButton, sentenceContainer, sentenceIndex); } function handleRoleSelection() { return window.UHelperRecording.handleRoleSelection(); } function handleRolePlayExercise() { return window.UHelperRecording.handleRolePlayExercise(); } function processRecordingQuestion(recordButton) { return window.UHelperRecording.processRecordingQuestion(recordButton); } function findSampleAudio(recordButton) { return window.UHelperRecording.findSampleAudio(recordButton); } function downloadAndSaveAudio(audioUrl, recordButton) { return window.UHelperRecording.downloadAndSaveAudio(audioUrl, recordButton); } function autoStopRecording() { return window.UHelperRecording.autoStopRecording(); } function waitForScoreAppear() { return window.UHelperRecording.waitForScoreAppear(); } function showRecordNotification(message, type) { return window.UHelperRecording.showRecordNotification ? window.UHelperRecording.showRecordNotification(message, type) : safeToast(message, type || 'info', 'center'); } function createCollapsibleSection(title, storageKey) { const section = document.createElement('div'); section.className = 'uh-section'; const header = document.createElement('div'); header.className = 'uh-section-header'; const titleEl = document.createElement('div'); titleEl.className = 'uh-section-title'; titleEl.textContent = title; const icon = document.createElement('div'); icon.className = 'uh-collapse-icon'; icon.innerHTML = ''; const content = document.createElement('div'); content.className = 'uh-section-content'; header.appendChild(titleEl); header.appendChild(icon); section.appendChild(header); section.appendChild(content); if (title.includes('AI')) { const pointsSection = document.createElement('div'); pointsSection.className = 'uh-points-card'; const pointsHeader = document.createElement('div'); pointsHeader.className = 'uh-points-header'; const pointsDisplay = document.createElement('div'); pointsDisplay.className = 'uh-points-display'; const pointsValue = document.createElement('span'); pointsValue.id = 'currentPointsDisplay'; pointsValue.className = 'uh-points-value'; pointsValue.textContent = userPoints; const pointsLabel = document.createElement('span'); pointsLabel.className = 'uh-points-label'; pointsLabel.textContent = '积分'; pointsDisplay.appendChild(pointsValue); pointsDisplay.appendChild(pointsLabel); const buyPoints = document.createElement('button'); buyPoints.onclick = () => showPointsPackages(); buyPoints.className = 'uh-buy-points-btn'; buyPoints.innerHTML = '充值'; pointsHeader.appendChild(pointsDisplay); pointsHeader.appendChild(buyPoints); const usageInfo = document.createElement('div'); usageInfo.className = 'uh-usage-info'; const costIcon = document.createElement('span'); costIcon.textContent = '💡'; const costText = document.createElement('span'); costText.textContent = '每次AI答题消耗 ' + POINTS_PER_QUESTION + ' 积分'; usageInfo.appendChild(costIcon); usageInfo.appendChild(costText); pointsSection.appendChild(pointsHeader); pointsSection.appendChild(usageInfo); content.appendChild(pointsSection); } const isCollapsed = localStorage.getItem(storageKey) === 'true'; if (isCollapsed) { content.style.display = 'none'; icon.style.transform = 'rotate(-90deg)'; section.dataset.collapsed = 'true'; } header.addEventListener('click', () => { const collapsed = section.dataset.collapsed === 'true'; if (collapsed) { content.style.display = ''; icon.style.transform = 'rotate(0deg)'; section.dataset.collapsed = 'false'; localStorage.setItem(storageKey, 'false'); } else { content.style.display = 'none'; icon.style.transform = 'rotate(-90deg)'; section.dataset.collapsed = 'true'; localStorage.setItem(storageKey, 'true'); } }); return { section, content }; } function createFloatingButton() { const styles = ` :root { --u-bg-color: rgba(255, 255, 255, 0.55); --u-blur-bg-color: rgba(255, 255, 255, 0.45); --u-border-color: rgba(255, 255, 255, 0.45); --u-text-color-primary: #1e2132; --u-text-color-secondary: #5a6078; --u-text-color-tertiary: #9498ae; --u-accent-color: #8080d8; --u-accent-color-dark: #6e6ae0; --u-success-color: #5cb88a; --u-danger-color: #e06868; --u-warning-color: #daa63a; --u-font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, sans-serif; --u-border-radius-lg: 16px; --u-border-radius-md: 12px; --u-border-radius-sm: 8px; --u-shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.07), 0 4px 12px rgba(0, 0, 0, 0.03); --u-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.03); } /* 补充外部 CSS 没有的属性 */ .u-helper-container { background: linear-gradient(135deg, rgba(235, 240, 250, 0.85) 0%, rgba(245, 247, 252, 0.82) 50%, rgba(240, 244, 252, 0.85) 100%) !important; backdrop-filter: blur(40px) saturate(200%) !important; -webkit-backdrop-filter: blur(40px) saturate(200%) !important; border: 1px solid rgba(200, 210, 230, 0.5) !important; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06), 0 20px 48px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(200, 210, 230, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.9) !important; color: #1e2132; overflow: hidden; } .u-helper-container.u-minimized-state { width: 360px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05), 0 12px 32px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(255, 255, 255, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.8) !important; } .u-helper-container.u-minimized-state .u-helper-logo-icon { display: none; } .u-helper-container.u-minimized-state .u-helper-title span { font-size: 14px; } /* ── 内容区 ── */ .u-helper-content { padding: 14px 14px 16px; flex: 1 1 auto; max-height: calc(90vh - 100px); overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 12px; box-sizing: border-box; position: relative; z-index: 1; } .u-helper-content.u-minimized { display: none !important; } .u-helper-content::-webkit-scrollbar { width: 5px; } .u-helper-content::-webkit-scrollbar-track { background: transparent; margin: 4px 0; } .u-helper-content::-webkit-scrollbar-thumb { background: linear-gradient(180deg, rgba(110,106,224,0.15), rgba(149,136,240,0.1)); border-radius: 10px; } .u-helper-content::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, rgba(110,106,224,0.28), rgba(149,136,240,0.2)); } .u-helper-title-bar { flex-shrink: 0; overflow: hidden; background: linear-gradient(180deg, rgba(220, 228, 245, 0.6) 0%, rgba(235, 240, 250, 0.4) 100%); border-bottom: 1px solid rgba(200, 210, 230, 0.4); } .u-helper-title { display: flex; align-items: center; gap: 10px; } .u-helper-logo-icon { filter: drop-shadow(0 1px 3px rgba(0,0,0,0.08)); transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.25s ease; color: var(--u-primary); animation: u-logo-breathe 3s ease-in-out infinite; } @keyframes u-logo-breathe { 0%, 100% { transform: scale(1); filter: drop-shadow(0 1px 3px rgba(0,0,0,0.08)); } 50% { transform: scale(1.04); filter: drop-shadow(0 2px 6px rgba(0,0,0,0.12)); } } .u-helper-logo-icon:hover { animation: u-logo-spin 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); transform: rotate(12deg) scale(1.12); } @keyframes u-logo-spin { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(20deg) scale(1.15); } 100% { transform: rotate(12deg) scale(1.12); } } .u-helper-control-btn { background: rgba(255,255,255,0.25); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.35); color: #1e2132; font-size: 16px; cursor: pointer; padding: 5px 11px; border-radius: 8px; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); line-height: 1; display: flex; align-items: center; justify-content: center; } .u-helper-control-btn:hover { background: rgba(255,255,255,0.45); transform: scale(1.08); box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .u-helper-control-btn:active { transform: scale(0.95); background: rgba(255,255,255,0.1); } /* ── 可折叠 section ── */ .uh-section { display: flex; flex-direction: column; flex-shrink: 0; background: linear-gradient(135deg, rgba(240, 244, 252, 0.7) 0%, rgba(248, 250, 255, 0.65) 100%); border-radius: 12px; border: 1px solid rgba(200, 210, 230, 0.35); overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03), 0 1px 3px rgba(0, 0, 0, 0.02); transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); position: relative; } .uh-section::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, #8080d8, #9490e0, #b0ace8); border-radius: 12px 12px 0 0; opacity: 0; transition: opacity 0.25s ease; } .uh-section:hover::before { opacity: 1; } .uh-section:hover { border-color: rgba(110, 106, 224, 0.2); box-shadow: 0 4px 16px rgba(110, 106, 224, 0.08), 0 2px 6px rgba(0, 0, 0, 0.03); transform: translateY(-1px); } .uh-section-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; min-height: 48px; padding: 0 14px; user-select: none; background: transparent; transition: all 0.15s ease; position: relative; color: #1e2132; } .uh-section-header:hover { background: rgba(255, 255, 255, 0.05); } .uh-section-header::after { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 0; background: linear-gradient(180deg, #8080d8, #9490e0); border-radius: 0 2px 2px 0; transition: height 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } .uh-section-header:hover::after { height: 60%; } .uh-section-title { font-size: 13px; font-weight: 700; color: #2d3142; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .uh-collapse-icon { flex-shrink: 0; color: #64748b; transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.15s ease; display: flex; align-items: center; } .uh-section-header:hover .uh-collapse-icon { color: #8080d8; } .uh-section-content { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; color: #5a6078; } /* ── 积分卡片 ── */ .uh-points-card { padding: 6px 0 0; } .uh-points-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } .uh-points-display { display: flex; align-items: baseline; gap: 6px; } .uh-points-value { font-size: 38px; font-weight: 900; background: linear-gradient(135deg, #8080d8 0%, #9490e0 50%, #b0ace8 100%); background-size: 200% 200%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; letter-spacing: -1.5px; animation: u-points-shimmer 3s ease-in-out infinite; filter: drop-shadow(0 1px 2px rgba(110,106,224,0.3)); } @keyframes u-points-shimmer { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .uh-points-label { font-size: 14px; font-weight: 600; color: #9498ae; } .uh-buy-points-btn { padding: 9px 18px; background: linear-gradient(135deg, #8080d8, #9490e0); color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 6px; box-shadow: 0 4px 16px rgba(110,106,224,0.35); transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); position: relative; overflow: hidden; } .uh-buy-points-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left 0.6s ease; } .uh-buy-points-btn:hover::before { left: 100%; } .uh-buy-points-btn:hover { transform: translateY(-3px) scale(1.02); box-shadow: 0 8px 24px rgba(110,106,224,0.45), 0 0 20px rgba(110,106,224,0.15); } .uh-usage-info { font-size: 12px; color: #9498ae; display: flex; align-items: center; gap: 6px; padding: 10px 14px; background: linear-gradient(135deg, rgba(110,106,224,0.06), rgba(149,136,240,0.04)); border-radius: 8px; border: 1px solid rgba(110,106,224,0.08); transition: all 0.25s ease; } .uh-usage-info:hover { background: linear-gradient(135deg, rgba(110,106,224,0.1), rgba(149,136,240,0.06)); } .u-helper-input-row { display: flex; align-items: center; justify-content: space-between; } .u-helper-label { font-size: 14px; color: var(--u-text-color-secondary); font-weight: 500; } .u-helper-input { width: 80px; padding: 9px 12px; border-radius: var(--u-border-radius-sm); border: 1.5px solid rgba(255, 255, 255, 0.1); text-align: center; background-color: rgba(255, 255, 255, 0.4); color: #2d3142; font-size: 14px; font-weight: 500; transition: all 0.25s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.05); outline: none; } .u-helper-input:hover { border-color: rgba(110, 106, 224, 0.3); } .u-helper-input:focus { outline: none; border-color: var(--u-accent-color); box-shadow: 0 0 0 3px rgba(110, 106, 224, 0.15), 0 2px 6px rgba(0,0,0,0.06); transform: translateY(-1px); } .u-helper-select-group { display: flex; align-items: center; gap: 8px; } .u-helper-select { flex-grow: 1; padding: 9px 12px; border: 1.5px solid rgba(255, 255, 255, 0.1); border-radius: var(--u-border-radius-md); font-size: 14px; font-weight: 500; background-color: rgba(255, 255, 255, 0.4); color: #2d3142; cursor: pointer; width: 100%; min-width: 0; box-shadow: 0 1px 3px rgba(0,0,0,0.05); transition: all 0.25s ease; outline: none; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; padding-right: 30px; } .u-helper-select:hover { border-color: rgba(110, 106, 224, 0.3); box-shadow: 0 2px 6px rgba(0,0,0,0.06); } .u-helper-select:focus { outline: none; border-color: var(--u-accent-color); box-shadow: 0 0 0 3px rgba(110, 106, 224, 0.15), 0 2px 6px rgba(0,0,0,0.06); transform: translateY(-1px); } /* 开关样式 */ .u-helper-switch { position: relative; display: inline-block; width: 46px; height: 26px; background-color: #334155; border-radius: 26px; cursor: pointer; transition: all 0.25s ease; flex-shrink: 0; box-shadow: inset 0 1px 3px rgba(0,0,0,0.06); } .u-helper-switch.active { background: linear-gradient(135deg, #8080d8, #9490e0); box-shadow: inset 0 1px 3px rgba(0,0,0,0.06), 0 0 12px rgba(110,106,224,0.12); } .u-helper-switch-slider { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; background-color: white; border-radius: 50%; transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); box-shadow: 0 2px 6px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.1); } .u-helper-switch.active .u-helper-switch-slider { transform: translateX(20px); } .u-helper-delete-btn { background: rgba(255, 255, 255, 0.06); color: var(--u-text-color-tertiary); border: none; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); flex-shrink: 0; display: none; } .u-helper-delete-btn:hover { background-color: var(--u-danger-color); color: white; transform: scale(1.1) rotate(90deg); } .u-helper-btn { padding: 12px 18px; border: none; border-radius: var(--u-border-radius-md); font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; box-sizing: border-box; box-shadow: var(--u-shadow-md); position: relative; overflow: hidden; } /* Shimmer sweep on hover */ .u-helper-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent); transition: left 0.6s ease; pointer-events: none; } .u-helper-btn:hover::before { left: 100%; } .u-helper-btn:hover { transform: translateY(-3px); box-shadow: var(--u-shadow-lg); } .u-helper-btn:active { transform: translateY(0) scale(0.97); box-shadow: var(--u-shadow-md); } .u-helper-btn-primary { background: linear-gradient(135deg, #8080d8, #9490e0); color: white; box-shadow: 0 2px 8px rgba(110, 106, 224, 0.2), 0 4px 14px rgba(110, 106, 224, 0.15); } .u-helper-btn-primary:hover { box-shadow: 0 4px 16px rgba(110, 106, 224, 0.3), 0 8px 24px rgba(110, 106, 224, 0.2); } .u-helper-btn-success { background: linear-gradient(135deg, #34d399, #6ee7b7); color: #064e3b; box-shadow: 0 2px 8px rgba(52, 211, 153, 0.2), 0 4px 14px rgba(52, 211, 153, 0.15); } .u-helper-btn-success:hover { box-shadow: 0 4px 16px rgba(52, 211, 153, 0.3), 0 8px 24px rgba(52, 211, 153, 0.2); } .u-helper-btn-warning { background: linear-gradient(135deg, #d4940f, #e8b24c); color: white; box-shadow: 0 4px 14px rgba(245, 158, 11, 0.3); } .u-helper-btn-danger { background: linear-gradient(135deg, #dc4a4a, #e06848); color: white; box-shadow: 0 4px 14px rgba(220, 74, 74, 0.3); } .u-helper-btn-secondary { background: rgba(255, 255, 255, 0.5); color: var(--u-text-color-secondary); border: 1.5px solid rgba(255, 255, 255, 0.08); } .u-helper-btn-secondary:hover { background: rgba(255, 255, 255, 0.65); border-color: rgba(255, 255, 255, 0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.06); } .u-helper-info-display { padding: 14px; background: rgba(255, 255, 255, 0.5); border-radius: var(--u-border-radius-md); font-size: 13px; color: #2d3142; width: 100%; max-height: 250px; overflow-y: auto; overflow-x: hidden; overflow-wrap: break-word; white-space: pre-wrap; line-height: 1.5; display: none; border: 1px solid rgba(255, 255, 255, 0.5); box-sizing: border-box; opacity: 0; transform: translateY(-10px) scale(0.98); transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .u-helper-info-display.u-visible { display: block; opacity: 1; transform: translateY(0) scale(1); } .u-helper-info-display::-webkit-scrollbar { width: 5px; } .u-helper-info-display::-webkit-scrollbar-track { background: transparent; } .u-helper-info-display::-webkit-scrollbar-thumb { background: linear-gradient(180deg, rgba(110,106,224,0.25), rgba(149,136,240,0.2)); border-radius: 10px; } .u-helper-info-display::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, rgba(110,106,224,0.4), rgba(149,136,240,0.35)); } .u-helper-footer { width: 100%; padding: 12px 0 12px; margin-top: 6px; border-top: 1px solid rgba(255, 255, 255, 0.4); font-size: 11px; color: #64748b; text-align: center; font-weight: 700; cursor: default; user-select: none; letter-spacing: 1px; text-transform: uppercase; flex-shrink: 0; } .u-ripple { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.4); transform: scale(0); animation: u-ripple-anim 0.6s linear; pointer-events: none; } @keyframes u-ripple-anim { to { transform: scale(4); opacity: 0; } } @keyframes slideDown { from { transform: translateX(-50%) translateY(-100%); opacity: 0; } to { transform: translateX(-50%) translateY(0); opacity: 1; } } @keyframes slideUp { from { transform: translateX(-50%) translateY(0); opacity: 1; } to { transform: translateX(-50%) translateY(-100%); opacity: 0; } } .product-dialog { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(225, 232, 245, 0.55); backdrop-filter: blur(10px) saturate(160%); -webkit-backdrop-filter: blur(10px) saturate(160%); display: flex; justify-content: center; align-items: center; z-index: 10001; animation: dialogFadeIn 0.3s ease; } @keyframes dialogFadeIn { from { opacity: 0; backdrop-filter: blur(0px); } to { opacity: 1; backdrop-filter: blur(10px); } } .product-dialog-content { background: linear-gradient(135deg, rgba(235, 240, 250, 0.92) 0%, rgba(245, 247, 252, 0.90) 50%, rgba(240, 244, 252, 0.92) 100%); backdrop-filter: blur(32px) saturate(180%); -webkit-backdrop-filter: blur(32px) saturate(180%); padding: 28px; border-radius: 20px; width: 90%; max-width: 800px; max-height: 90vh; overflow: hidden; border: 1px solid rgba(200, 210, 230, 0.5); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04), 0 16px 48px rgba(0, 0, 0, 0.08), 0 32px 80px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.9); animation: dialogSlideIn 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); } .product-dialog-content::-webkit-scrollbar { width: 5px; } .product-dialog-content::-webkit-scrollbar-thumb { background: linear-gradient(180deg, rgba(110,106,224,0.15), rgba(149,136,240,0.1)); border-radius: 10px; } @keyframes dialogSlideIn { from { opacity: 0; transform: translateY(16px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } .product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin: 20px 0; } .product-card { border: 1.5px solid rgba(255, 255, 255, 0.5); border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); background: rgba(255, 255, 255, 0.55); position: relative; overflow: hidden; } .product-card::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(110,106,224,0.03), rgba(149,136,240,0.03)); opacity: 0; transition: opacity 0.3s; } .product-card:hover::before { opacity: 1; } .product-card:hover { transform: translateY(-6px) scale(1.01); box-shadow: 0 12px 32px rgba(110, 106, 224, 0.12), 0 4px 8px rgba(0,0,0,0.06); border-color: rgba(110, 106, 224, 0.3); } .product-card.selected { border: 2px solid #8080d8; background: rgba(110, 106, 224, 0.1); box-shadow: 0 0 0 3px rgba(110, 106, 224, 0.15), 0 8px 24px rgba(110, 106, 224, 0.12); } .product-image { width: 100%; height: 150px; object-fit: cover; border-radius: 8px; margin-bottom: 10px; transition: transform 0.3s; } .product-card:hover .product-image { transform: scale(1.02); } .product-title { font-size: 18px; font-weight: 800; margin-bottom: 10px; color: #2d3142; } .product-price { font-size: 20px; font-weight: 900; background: linear-gradient(135deg, #8080d8, #9490e0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 10px; } .product-description { color: #9498ae; margin-bottom: 15px; font-size: 13px; line-height: 1.5; } .dialog-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .dialog-button { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); position: relative; overflow: hidden; } .dialog-button::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: left 0.6s ease; } .dialog-button:hover::before { left: 100%; } .dialog-button.primary { background: linear-gradient(135deg, #6e6ae0, #8b87e8); color: white; box-shadow: 0 2px 8px rgba(110, 106, 224, 0.2), 0 4px 14px rgba(110, 106, 224, 0.15); } .dialog-button.primary:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(110, 106, 224, 0.3), 0 8px 24px rgba(110, 106, 224, 0.2); } .dialog-button.secondary { background: linear-gradient(135deg, rgba(240, 244, 252, 0.7), rgba(248, 250, 255, 0.65)); color: #5a6078; border: 1px solid rgba(200, 210, 230, 0.4); } .dialog-button.secondary:hover { background: linear-gradient(135deg, rgba(235, 240, 250, 0.85), rgba(245, 247, 252, 0.8)); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .product-list { display: flex; flex-direction: column; gap: 10px; margin: 20px 0; max-height: 400px; overflow-y: auto; padding-right: 4px; } .product-list::-webkit-scrollbar { width: 5px; } .product-list::-webkit-scrollbar-thumb { background: linear-gradient(180deg, rgba(110,106,224,0.15), rgba(149,136,240,0.1)); border-radius: 10px; } .product-item { border: 1px solid rgba(200, 210, 230, 0.4); border-radius: 12px; padding: 16px 20px; cursor: pointer; transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); background: linear-gradient(135deg, rgba(240, 244, 252, 0.65) 0%, rgba(248, 250, 255, 0.6) 100%); position: relative; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02); } .product-item::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(110,106,224,0.04), rgba(149,136,240,0.03)); opacity: 0; transition: opacity 0.3s; } .product-item:hover::before { opacity: 1; } .product-item:hover { border-color: rgba(110, 106, 224, 0.3); background: linear-gradient(135deg, rgba(230, 235, 250, 0.8) 0%, rgba(240, 244, 252, 0.75) 100%); transform: translateX(6px) translateY(-2px); box-shadow: 0 8px 24px rgba(110, 106, 224, 0.1), 0 2px 6px rgba(0,0,0,0.04); } .product-item.selected { border-color: rgba(110, 106, 224, 0.5); background: linear-gradient(135deg, rgba(110, 106, 224, 0.08) 0%, rgba(139, 135, 232, 0.05) 100%); box-shadow: 0 4px 14px rgba(110, 106, 224, 0.15), 0 0 0 2px rgba(110,106,224,0.1); } .product-item.selected::before { content: '✓'; position: absolute; right: 16px; top: 50%; transform: translateY(-50%); width: 28px; height: 28px; background: linear-gradient(135deg, #8080d8, #9490e0); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px; box-shadow: 0 4px 12px rgba(110, 106, 224, 0.3); animation: checkBounce 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes checkBounce { 0% { transform: translateY(-50%) scale(0); } 60% { transform: translateY(-50%) scale(1.2); } 100% { transform: translateY(-50%) scale(1); } } .product-item .product-title { font-size: 15px; font-weight: 700; color: #1e2132; margin-bottom: 6px; line-height: 1.4; } .product-item .product-description { font-size: 12px; color: #7a8094; margin-bottom: 8px; line-height: 1.5; } .product-item .product-price { font-size: 18px; font-weight: 900; background: linear-gradient(135deg, #6e6ae0, #8b87e8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .product-dialog-title { font-size: 22px; font-weight: 900; color: #1e2132; margin-bottom: 8px; text-align: center; letter-spacing: -0.3px; } .product-dialog-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(200, 210, 230, 0.3); } .keep-alive-switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; } .keep-alive-switch input { opacity: 0; width: 0; height: 0; } .keep-alive-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #334155; transition: all 0.25s ease; border-radius: 26px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.06); } .keep-alive-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.1); } /* ── 外部 CSS 兼容 ── */ .u-helper-content .u-helper-section { margin-bottom: 0; } input:checked + .keep-alive-slider { background: linear-gradient(135deg, #8080d8, #9490e0); box-shadow: inset 0 1px 3px rgba(0,0,0,0.06), 0 0 12px rgba(110,106,224,0.12); } input:checked + .keep-alive-slider:before { transform: translateX(20px); } /* ── Selection color ── */ ::selection { background: rgba(110, 106, 224, 0.3); color: #2d3142; } `; const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = styles; document.head.appendChild(styleSheet); const container = document.createElement('div'); container.className = 'u-helper-container'; setTimeout(() => { container.classList.add('u-visible'); }, 100); const titleBar = document.createElement('div'); titleBar.className = 'u-helper-title-bar'; titleBar.innerHTML = (typeof getUHelperTemplate === 'function' && getUHelperTemplate('titlebar')) || `
U U-Egao
📢 公告
`; setTimeout(() => { const noticeBtn = document.getElementById('u-notice-btn'); if (noticeBtn) { noticeBtn.onmouseover = () => { noticeBtn.style.background = 'linear-gradient(135deg, rgba(110, 106, 224, 0.18) 0%, rgba(139, 135, 232, 0.12) 100%)'; noticeBtn.style.transform = 'translateY(-1px)'; noticeBtn.style.boxShadow = '0 4px 12px rgba(110, 106, 224, 0.1)'; noticeBtn.style.borderColor = 'rgba(110, 106, 224, 0.3)'; }; noticeBtn.onmouseout = () => { noticeBtn.style.background = 'linear-gradient(135deg, rgba(110, 106, 224, 0.12) 0%, rgba(139, 135, 232, 0.08) 100%)'; noticeBtn.style.transform = 'translateY(0)'; noticeBtn.style.boxShadow = 'none'; noticeBtn.style.borderColor = 'rgba(110, 106, 224, 0.2)'; }; noticeBtn.onmousedown = (e) => e.stopPropagation(); } }, 800); const buttonContainer = document.createElement('div'); const minimizeButton = document.createElement('button'); minimizeButton.innerHTML = ''; minimizeButton.className = 'u-helper-control-btn'; minimizeButton.title = '收起面板'; const contentContainer = document.createElement('div'); contentContainer.className = 'u-helper-content'; const { section: aiConfigSection, content: aiConfigContent } = createCollapsibleSection('🤖 AI助手', 'u-collapse-ai'); const aiToggleContainer = document.createElement('div'); aiToggleContainer.className = 'u-ai-toggle-container'; const aiToggleLabel = document.createElement('label'); aiToggleLabel.className = 'u-ai-toggle-label'; const aiToggle = document.createElement('input'); aiToggle.type = 'checkbox'; aiToggle.checked = useKimiAI; aiToggle.className = 'u-ai-toggle-checkbox'; aiToggle.onchange = (e) => { useKimiAI = e.target.checked; localStorage.setItem('useKimiAI', useKimiAI.toString()); const statusText = aiToggleContainer.querySelector('.ai-status'); if (statusText) { statusText.textContent = useKimiAI ? '已启用' : '已禁用'; statusText.style.color = useKimiAI ? '#10B981' : '#94a3b8'; statusText.style.background = useKimiAI ? 'rgba(16, 185, 129, 0.1)' : 'rgba(148, 163, 184, 0.1)'; statusText.style.boxShadow = useKimiAI ? '0 0 12px rgba(16, 185, 129, 0.1)' : 'none'; } }; aiToggleLabel.appendChild(aiToggle); aiToggleLabel.appendChild(document.createTextNode('启用 AI 答题')); const statusIndicator = document.createElement('span'); statusIndicator.className = 'ai-status'; statusIndicator.textContent = useKimiAI ? '已启用' : '已禁用'; statusIndicator.className = 'ai-status'; aiToggleContainer.appendChild(aiToggleLabel); aiToggleContainer.appendChild(statusIndicator); aiConfigContent.appendChild(aiToggleContainer); const aiTip = document.createElement('div'); aiTip.style.cssText = ` margin-top: 10px; padding: 10px 14px; background: linear-gradient(135deg, rgba(245, 158, 11, 0.06), rgba(251, 191, 36, 0.06)); border-left: 3px solid #d4940f; border-radius: 8px; font-size: 12px; color: #92400e; line-height: 1.6; `; aiTip.innerHTML = ` 💡 提示:
• 有在线题库时,答题用题库,评论用AI
• 无在线题库时,答题用AI,评论用默认文本 `; aiConfigContent.appendChild(aiTip); const aiProviderRow = document.createElement('div'); aiProviderRow.className = 'u-helper-input-row u-ai-provider-row'; const aiProviderLabel = document.createElement('label'); aiProviderLabel.className = 'u-helper-label'; aiProviderLabel.textContent = 'AI服务'; const aiProviderSelect = document.createElement('select'); aiProviderSelect.className = 'u-helper-select'; [ { value: 'kimi', label: 'Kimi' }, { value: 'siliconflow', label: 'DeepSeekV4(推荐)' } ].forEach(function (p) { const opt = document.createElement('option'); opt.value = p.value; opt.textContent = p.label; if ((localStorage.getItem('u-ai-provider') || 'kimi') === p.value) { opt.selected = true; } aiProviderSelect.appendChild(opt); }); aiProviderSelect.addEventListener('change', function () { localStorage.setItem('u-ai-provider', aiProviderSelect.value); safeToast('AI服务已切换: ' + aiProviderSelect.options[aiProviderSelect.selectedIndex].text, 'info'); }); aiProviderRow.appendChild(aiProviderLabel); aiProviderRow.appendChild(aiProviderSelect); aiConfigContent.appendChild(aiProviderRow); const aiProviderHelp = document.createElement('div'); aiProviderHelp.className = 'uh-inline-help'; aiProviderHelp.textContent = ''; aiConfigContent.appendChild(aiProviderHelp); const defaultCommentContainer = document.createElement('div'); defaultCommentContainer.style.cssText = ` margin-top: 10px; padding: 14px; background: rgba(255, 255, 255, 0.45); border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.5); `; const defaultCommentLabel = document.createElement('div'); defaultCommentLabel.className = 'u-helper-label'; defaultCommentLabel.textContent = '默认评论文本'; defaultCommentLabel.style.marginBottom = '8px'; const defaultCommentInput = document.createElement('input'); defaultCommentInput.type = 'text'; defaultCommentInput.className = 'u-helper-input'; defaultCommentInput.style.width = '100%'; defaultCommentInput.placeholder = '输入默认评论内容(如:Hello)'; defaultCommentInput.value = localStorage.getItem('u-default-comment'); defaultCommentInput.addEventListener('input', () => { localStorage.setItem('u-default-comment', defaultCommentInput.value); }); const defaultCommentTip = document.createElement('div'); defaultCommentTip.style.cssText = ` margin-top: 6px; font-size: 12px; color: #9498ae; line-height: 1.5; `; defaultCommentTip.textContent = '当AI未启用或无在线题库时,将使用此文本作为评论内容'; defaultCommentContainer.appendChild(defaultCommentLabel); defaultCommentContainer.appendChild(defaultCommentInput); defaultCommentContainer.appendChild(defaultCommentTip); aiConfigContent.appendChild(defaultCommentContainer); const silentSubmitContainer = document.createElement('div'); silentSubmitContainer.className = 'u-helper-input-row'; silentSubmitContainer.style.marginTop = '15px'; silentSubmitContainer.style.paddingTop = '12px'; silentSubmitContainer.style.borderTop = '1px dashed rgba(255,255,255,0.08)'; const silentSubmitLabel = document.createElement('label'); silentSubmitLabel.className = 'u-helper-label'; silentSubmitLabel.innerHTML = '⚡️ 启用高级提交 (强力)'; silentSubmitLabel.title = '开启后,AI或题库的答案将直接进行提交,跳过所有弹窗,实现满分/高分秒过。'; const silentSubmitSwitch = document.createElement('div'); silentSubmitSwitch.className = 'u-helper-switch'; silentSubmitSwitch.innerHTML = '
'; const savedSilentMode = localStorage.getItem('u-silent-submit-mode') === 'true'; if (savedSilentMode) { silentSubmitSwitch.classList.add('active'); } silentSubmitSwitch.addEventListener('click', function() { const isActive = this.classList.contains('active'); if (isActive) { this.classList.remove('active'); localStorage.setItem('u-silent-submit-mode', 'false'); if (typeof unsafeWindow !== 'undefined') { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = null; } ULogger.debug('🚫 高级提交已关闭,内存缓存已清除'); showRecordNotification('🚫 已关闭高级提交 (恢复普通模式)', 'info'); } else { this.classList.add('active'); localStorage.setItem('u-silent-submit-mode', 'true'); showRecordNotification('⚡️ 已开启高级提交', 'success'); } }); silentSubmitContainer.appendChild(silentSubmitLabel); silentSubmitContainer.appendChild(silentSubmitSwitch); aiConfigContent.appendChild(silentSubmitContainer); const onlineBankSection = document.createElement('div'); onlineBankSection.style.marginTop = '10px'; const bankSelectorLabel = document.createElement('div'); bankSelectorLabel.className = 'u-helper-label'; bankSelectorLabel.textContent = '在线题库选择'; bankSelectorLabel.style.marginBottom = '8px'; const bankSelectorGroup = document.createElement('div'); bankSelectorGroup.className = 'u-helper-select-group'; const bankSelector = document.createElement('select'); bankSelector.id = 'online-bank-selector'; bankSelector.className = 'u-helper-select'; const refreshBanksBtn = document.createElement('button'); refreshBanksBtn.innerHTML = '🔄'; refreshBanksBtn.title = '刷新题库列表'; refreshBanksBtn.className = 'u-btn u-btn-secondary'; refreshBanksBtn.style.cssText = 'flex-shrink:0;width:38px;height:38px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:8px;'; bankSelectorGroup.appendChild(bankSelector); bankSelectorGroup.appendChild(refreshBanksBtn); onlineBankSection.appendChild(bankSelectorLabel); onlineBankSection.appendChild(bankSelectorGroup); const checkOnlineBankBtn = document.createElement('button'); checkOnlineBankBtn.className = 'u-helper-btn u-helper-btn-secondary'; checkOnlineBankBtn.style.marginTop = '10px'; checkOnlineBankBtn.style.fontSize = '13px'; checkOnlineBankBtn.style.padding = '8px 14px'; checkOnlineBankBtn.style.borderRadius = '10px'; checkOnlineBankBtn.innerHTML = '🔍 核对在线题库与课程是否匹配'; checkOnlineBankBtn.title = '点击对比当前选中的在线题库与网页标题是否一致'; checkOnlineBankBtn.onclick = () => { const selector = document.getElementById('online-bank-selector'); const selectedBankName = selector ? selector.value : ''; if (!selectedBankName) { alert('❌ 你还没有选择在线题库!\n请先点击下拉框选择一个题库。'); return; } const breadcrumbs = document.querySelectorAll('.pc-break-crumb-text'); let pageContextText = ''; if (breadcrumbs.length >= 2) { pageContextText = breadcrumbs[0].textContent.trim(); } else if (breadcrumbs.length === 1) { pageContextText = breadcrumbs[0].textContent.trim(); } else { pageContextText = document.title.split('-')[0].trim(); } if (!pageContextText) { alert('⚠️ 无法获取当前页面的课程名称,请确保你已进入课程学习页面。'); return; } const normalize = (str) => { return str.toLowerCase() .replace(/\.json$/i, '') .replace(/[()()\[\]【】\s\-_]/g, '') .replace(/第[一二三四五六七八九十\d]+版/g, ''); }; const cleanBankName = normalize(selectedBankName); const cleanPageText = normalize(pageContextText); ULogger.debug(`[在线核对] 题库清洗后: ${cleanBankName}`); ULogger.debug(`[在线核对] 页面清洗后: ${cleanPageText}`); if (cleanPageText.includes(cleanBankName) || cleanBankName.includes(cleanPageText)) { alert(`✅ 匹配成功!\n\n在线题库:${selectedBankName}\n当前课程:${pageContextText}\n\n书名一致,可以放心使用。`); } else { alert(`⚠️ 警告:题库可能不匹配!\n\n🔴 你选择的:【${selectedBankName}】\n🟢 页面课程:【${pageContextText}】\n\n请检查:\n1. 教材名称是否一致?\n2. 级别/册数(如 1 vs 2)是否一致?`); } }; onlineBankSection.appendChild(checkOnlineBankBtn); const multiPageStatusDiv = document.createElement('div'); multiPageStatusDiv.id = 'multi-page-status'; multiPageStatusDiv.className = 'u-status-bar'; multiPageStatusDiv.innerHTML = `
多页教材模式
状态: 未启用
进度: -
答案总数: -
`; onlineBankSection.appendChild(multiPageStatusDiv); const updateOnlineBankList = async () => { const uid = localStorage.getItem('userId'); const bankSelector = document.getElementById('online-bank-selector'); if (!uid) { bankSelector.innerHTML = ''; return; } bankSelector.innerHTML = ''; try { const authorized_banks = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://eghome.textile668.cn/api/get-my-authorizations', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ uid }), onload: res => { const data = JSON.parse(res.responseText); if (data.status === 'success') resolve(data.authorized_banks); else reject(new Error(data.message)); }, onerror: err => reject(new Error('网络请求失败')) }); }); bankSelector.innerHTML = ''; const offOption = document.createElement('option'); offOption.value = ""; offOption.textContent = "--- 关闭在线题库 ---"; bankSelector.appendChild(offOption); if (!authorized_banks || authorized_banks.length === 0) { const noAuthOption = document.createElement('option'); noAuthOption.value = ""; noAuthOption.textContent = "无可用题库"; noAuthOption.disabled = true; bankSelector.appendChild(noAuthOption); return; } authorized_banks.forEach(bankName => { const option = document.createElement('option'); option.value = bankName; option.textContent = bankName; bankSelector.appendChild(option); }); let pageCourseName = ""; const breadcrumbs = document.querySelectorAll('.pc-break-crumb-text'); if (breadcrumbs.length >= 1) { pageCourseName = breadcrumbs[0].textContent.trim(); if(breadcrumbs.length >= 2 && pageCourseName.length < 4) { pageCourseName = breadcrumbs[1].textContent.trim(); } } else { pageCourseName = document.title.split('-')[0].trim(); } ULogger.debug(`[自动匹配] 当前页面课程名: "${pageCourseName}"`); const normalize = (str) => { return (str || '').toLowerCase() .replace(/\.json$/i, '') .replace(/[()()\[\]【】\s\-_]/g, '') .replace(/第[一二三四五六七八九十\d]+版/g, ''); }; const cleanPageName = normalize(pageCourseName); let bestMatch = null; if (cleanPageName && cleanPageName.length > 2) { for (const bankName of authorized_banks) { const cleanBankName = normalize(bankName); if (cleanPageName.includes(cleanBankName) || cleanBankName.includes(cleanPageName)) { bestMatch = bankName; ULogger.debug(`[自动匹配] 发现匹配题库: "${bankName}"`); break; } } } if (bestMatch) { bankSelector.value = bestMatch; localStorage.setItem('selectedOnlineBank', bestMatch); const matchTip = document.createElement('span'); matchTip.textContent = '成功'; matchTip.style.cssText = 'color: #52c41a; font-size: 12px; margin-left: 8px; font-weight: bold; transition: opacity 0.5s;'; const parent = bankSelector.parentNode; const existingTip = parent.querySelector('span'); if(existingTip) existingTip.remove(); parent.appendChild(matchTip); setTimeout(() => { if (matchTip) { matchTip.style.opacity = '0'; setTimeout(() => matchTip.remove(), 500); } }, 2000); } else { const lastSelected = localStorage.getItem('selectedOnlineBank'); if (lastSelected && authorized_banks.includes(lastSelected)) { bankSelector.value = lastSelected; ULogger.debug(`[自动匹配] 未找到匹配项,回退到上次选择: ${lastSelected}`); } } } catch (error) { bankSelector.innerHTML = ``; } }; refreshBanksBtn.onclick = updateOnlineBankList; bankSelector.onchange = () => { localStorage.setItem('selectedOnlineBank', bankSelector.value); if (window.updateHighScoreLockState) window.updateHighScoreLockState(); }; setTimeout(updateOnlineBankList, 1000); const { section: fileManagementSection, content: fileManagementContent } = createCollapsibleSection('📚 题库管理', 'u-collapse-file'); const buyBankLink = document.createElement('a'); buyBankLink.href = 'javascript:void(0)'; buyBankLink.textContent = '🛒 点我购买题库'; buyBankLink.style.cssText = 'display: block; text-align: center; margin: 12px 0; color: #6e6ae0; text-decoration: underline; cursor: pointer; font-weight: 600; font-size: 14px; transition: color 0.2s;'; buyBankLink.onclick = function () { if (window.UHelperBank && typeof window.UHelperBank.showProducts === 'function') { window.UHelperBank.showProducts(); } else { alert('题库购买模块未加载,请刷新页面后重试。'); } }; fileManagementContent.appendChild(buyBankLink); fileManagementContent.appendChild(onlineBankSection); const { section: delaySettingsContainer, content: delaySettingsContent } = createCollapsibleSection('⏱️ 自动化延迟', 'u-collapse-delay'); if (window.UHelperDelay && typeof window.UHelperDelay.initPanel === 'function') { window.UHelperDelay.initPanel(delaySettingsContent); } else { delaySettingsContent.innerHTML = '
自动化延迟模块未加载
'; } let isDragging = false; let currentX = 0; let currentY = 0; let initialX; let initialY; let xOffset = 0; let yOffset = 0; let dragRaf = null; function dragStart(e) { if (e.type === "mousedown" && e.button !== 0) return; initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; container.style.willChange = 'transform'; container.style.cursor = 'grabbing'; container.style.transition = 'none'; document.body.style.userSelect = 'none'; document.body.style.webkitUserSelect = 'none'; e.preventDefault(); } function dragEnd(e) { if (!isDragging) return; isDragging = false; initialX = currentX; initialY = currentY; container.style.willChange = ''; container.style.cursor = ''; container.style.transition = ''; document.body.style.userSelect = ''; document.body.style.webkitUserSelect = ''; if (dragRaf) { cancelAnimationFrame(dragRaf); dragRaf = null; } } function drag(e) { if (!isDragging) return; e.preventDefault(); if (dragRaf) cancelAnimationFrame(dragRaf); dragRaf = requestAnimationFrame(() => { currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; container.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; }); } titleBar.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag, { passive: false }); document.addEventListener('mouseup', dragEnd); let isMinimized = false; minimizeButton.addEventListener('click', () => { isMinimized = !isMinimized; if (isMinimized) { contentContainer.style.display = 'none'; container.style.width = '360px'; container.style.overflow = 'hidden'; container.style.maxHeight = 'none'; container.style.height = 'auto'; container.classList.add('u-minimized-state'); } else { contentContainer.style.display = ''; container.style.width = ''; container.style.overflow = ''; container.style.maxHeight = ''; container.style.height = ''; container.classList.remove('u-minimized-state'); } minimizeButton.innerHTML = isMinimized ? '' : ''; minimizeButton.title = isMinimized ? '展开面板' : '收起面板'; }); const autoSelectButton = document.createElement('button'); autoSelectButton.className = 'u-helper-btn u-helper-btn-primary'; autoSelectButton.innerHTML = '自动选择答案'; autoSelectButton.style.fontSize = '15px'; autoSelectButton.style.fontWeight = '700'; autoSelectButton.style.padding = '14px 18px'; autoSelectButton.style.borderRadius = '10px'; const autoRunButton = document.createElement('button'); autoRunButton.id = 'auto-run-btn'; autoRunButton.className = 'u-helper-btn u-helper-btn-success'; autoRunButton.innerHTML = '开始挂机'; autoRunButton.style.fontSize = '15px'; autoRunButton.style.fontWeight = '700'; autoRunButton.style.padding = '14px 18px'; autoRunButton.style.borderRadius = '10px'; autoSelectButton.addEventListener('click', () => { autoSelectAnswers(); }); autoRunButton.addEventListener('click', () => { toggleAutoRun(); }); const addRippleEffect = (button) => { button.addEventListener('mousedown', (e) => { const rect = button.getBoundingClientRect(); const ripple = document.createElement('span'); ripple.className = 'u-ripple'; ripple.style.left = `${e.clientX - rect.left}px`; ripple.style.top = `${e.clientY - rect.top}px`; button.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); }; [autoSelectButton, autoRunButton].forEach(addRippleEffect); const footerBar = document.createElement('div'); footerBar.className = 'u-helper-footer'; footerBar.innerHTML = 'By U-Egao'; buttonContainer.appendChild(minimizeButton); titleBar.appendChild(buttonContainer); contentContainer.appendChild(aiConfigSection); const { section: discussionSection, content: discussionContent } = createCollapsibleSection('💬 讨论区 / 评论区', 'u-collapse-discussion'); if (window.UHelperDiscussion && typeof window.UHelperDiscussion.initPanel === 'function') { window.UHelperDiscussion.initPanel(discussionContent); } else { discussionContent.innerHTML = '
讨论区模块未加载
'; } contentContainer.appendChild(discussionSection); contentContainer.appendChild(fileManagementSection); contentContainer.appendChild(delaySettingsContainer); const { section: voiceSettingsSection, content: voiceSettingsContent } = createCollapsibleSection('🎵 语音设置', 'u-collapse-voice'); const voiceToggleContainer = document.createElement('div'); voiceToggleContainer.style.cssText = ` display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(52, 211, 153, 0.04) 100%); border-radius: 10px; border: 1px solid rgba(16, 185, 129, 0.1); margin-bottom: 12px; transition: all 0.25s ease; position: relative; overflow: hidden; `; const voiceToggleLabel = document.createElement('label'); voiceToggleLabel.style.cssText = ` display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 14px; font-weight: 500; color: var(--u-text-color-primary); `; const voiceToggle = document.createElement('input'); voiceToggle.type = 'checkbox'; voiceToggle.checked = localStorage.getItem('autoPlayRecordEnabled') === 'true'; voiceToggle.style.cssText = ` width: 18px; height: 18px; cursor: pointer; `; voiceToggle.onchange = (e) => { window.__autoPlayRecordEnabled = e.target.checked; localStorage.setItem('autoPlayRecordEnabled', e.target.checked.toString()); const statusText = voiceToggleContainer.querySelector('.voice-status'); if (statusText) { statusText.textContent = e.target.checked ? '已启用' : '已禁用'; statusText.style.color = e.target.checked ? '#10B981' : '#94a3b8'; statusText.style.background = e.target.checked ? 'rgba(16, 185, 129, 0.1)' : 'rgba(148, 163, 184, 0.1)'; statusText.style.boxShadow = e.target.checked ? '0 0 12px rgba(16, 185, 129, 0.1)' : 'none'; } showRecordNotification(e.target.checked ? '✅ 自动录音已启用' : '⚠️ 自动录音已禁用', e.target.checked ? 'success' : 'info'); }; voiceToggleLabel.appendChild(voiceToggle); voiceToggleLabel.appendChild(document.createTextNode('启用自动录音')); const voiceStatusIndicator = document.createElement('span'); voiceStatusIndicator.className = 'voice-status'; voiceStatusIndicator.textContent = voiceToggle.checked ? '已启用' : '已禁用'; voiceStatusIndicator.style.cssText = ` font-size: 12px; font-weight: 700; color: ${voiceToggle.checked ? '#10B981' : '#94a3b8'}; padding: 4px 14px; border-radius: 20px; background: ${voiceToggle.checked ? 'rgba(16, 185, 129, 0.1)' : 'rgba(148, 163, 184, 0.1)'}; box-shadow: ${voiceToggle.checked ? '0 0 12px rgba(16, 185, 129, 0.1)' : 'none'}; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); `; voiceToggleContainer.appendChild(voiceToggleLabel); voiceToggleContainer.appendChild(voiceStatusIndicator); voiceSettingsContent.appendChild(voiceToggleContainer); const cloudScoreContainer = document.createElement('div'); cloudScoreContainer.style.cssText = ` display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; margin-top: 10px; margin-bottom: 10px; background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%); border-radius: 10px; border: 1px solid rgba(110, 106, 224, 0.1); transition: all 0.3s; position: relative; overflow: hidden; `; const cloudScoreLabel = document.createElement('label'); cloudScoreLabel.id = 'cloud-score-label-wrapper'; cloudScoreLabel.style.cssText = `display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 14px; font-weight: bold; color: #4338ca; transition: all 0.3s;`; const cloudScoreToggle = document.createElement('input'); cloudScoreToggle.type = 'checkbox'; cloudScoreToggle.id = 'u-cloud-score-toggle'; window.__enableOralScoreInjection = localStorage.getItem('u-cloud-score') === 'true'; cloudScoreToggle.checked = window.__enableOralScoreInjection; cloudScoreToggle.onchange = (e) => { window.__enableOralScoreInjection = e.target.checked; localStorage.setItem('u-cloud-score', e.target.checked.toString()); const msg = e.target.checked ? '☁️ 高分模式已开启' : '🛑 高分模式已关闭'; showRecordNotification(msg, e.target.checked ? 'success' : 'info'); }; cloudScoreLabel.appendChild(cloudScoreToggle); cloudScoreLabel.appendChild(document.createTextNode('高分模式🥇')); cloudScoreContainer.appendChild(cloudScoreLabel); voiceSettingsContent.appendChild(cloudScoreContainer); window.updateHighScoreLockState = function() { const toggle = document.getElementById('u-cloud-score-toggle'); const wrapper = document.getElementById('cloud-score-label-wrapper'); if (!toggle || !wrapper) return; let rawPoints = window.userPoints; if (rawPoints === undefined || rawPoints === null) { rawPoints = localStorage.getItem('userPoints'); } const currentPoints = parseInt(rawPoints || 0); const selectedBank = localStorage.getItem('selectedOnlineBank'); const hasValidBank = selectedBank && selectedBank.trim() !== ""; ULogger.debug(`[高分模式锁状态] 当前积分: ${currentPoints}, 是否大于2: ${currentPoints > 2}`); const canEnable = currentPoints > 2 || hasValidBank; if (canEnable) { toggle.disabled = false; wrapper.style.opacity = '1'; wrapper.style.cursor = 'pointer'; wrapper.style.filter = 'none'; wrapper.title = '点击开启/关闭'; } else { if (toggle.checked) { toggle.checked = false; window.__enableOralScoreInjection = false; localStorage.setItem('u-cloud-score', 'false'); } toggle.disabled = true; wrapper.style.opacity = '0.5'; wrapper.style.cursor = 'not-allowed'; wrapper.style.filter = 'grayscale(100%)'; wrapper.title = `当前积分(${currentPoints})不足,需大于2分或连接题库`; } }; setTimeout(window.updateHighScoreLockState, 500); const audioTypeContainer = document.createElement('div'); audioTypeContainer.style.cssText = ` display: flex; gap: 10px; margin-bottom: 12px; `; const britishBtn = document.createElement('button'); britishBtn.textContent = '🇬🇧 英音'; britishBtn.className = 'u-helper-btn u-helper-btn-secondary'; britishBtn.style.cssText = ` flex: 1; padding: 10px; font-size: 14px; font-weight: 600; transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); border-radius: 10px; `; const americanBtn = document.createElement('button'); americanBtn.textContent = '🇺🇸 美音'; americanBtn.className = 'u-helper-btn u-helper-btn-secondary'; americanBtn.style.cssText = ` flex: 1; padding: 10px; font-size: 14px; font-weight: 600; transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); border-radius: 10px; `; const selectedAudioType = localStorage.getItem('selectedAudioType') || 'british'; window.__selectedAudioType = selectedAudioType; function updateAudioTypeButtons() { if (window.__selectedAudioType === 'british') { britishBtn.style.background = 'linear-gradient(135deg, #34d399, #6ee7b7)'; britishBtn.style.color = '#064e3b'; britishBtn.style.boxShadow = '0 4px 14px rgba(52, 211, 153, 0.3)'; americanBtn.style.background = 'rgba(255, 255, 255, 0.5)'; americanBtn.style.color = '#94a3b8'; americanBtn.style.boxShadow = 'none'; } else { americanBtn.style.background = 'linear-gradient(135deg, #34d399, #6ee7b7)'; americanBtn.style.color = '#064e3b'; americanBtn.style.boxShadow = '0 4px 14px rgba(52, 211, 153, 0.3)'; britishBtn.style.background = 'rgba(255, 255, 255, 0.5)'; britishBtn.style.color = '#94a3b8'; britishBtn.style.boxShadow = 'none'; } } updateAudioTypeButtons(); britishBtn.addEventListener('click', () => { window.__selectedAudioType = 'british'; localStorage.setItem('selectedAudioType', 'british'); updateAudioTypeButtons(); showRecordNotification('✅ 已选择英音', 'success'); }); americanBtn.addEventListener('click', () => { window.__selectedAudioType = 'american'; localStorage.setItem('selectedAudioType', 'american'); updateAudioTypeButtons(); showRecordNotification('✅ 已选择美音', 'success'); }); audioTypeContainer.appendChild(britishBtn); audioTypeContainer.appendChild(americanBtn); voiceSettingsContent.appendChild(audioTypeContainer); const voiceDelayContainer = document.createElement('div'); voiceDelayContainer.className = 'u-helper-input-row'; voiceDelayContainer.style.marginBottom = '12px'; const voiceDelayLabel = document.createElement('label'); voiceDelayLabel.className = 'u-helper-label'; voiceDelayLabel.textContent = '播放延迟'; const voiceDelaySelector = document.createElement('select'); voiceDelaySelector.id = 'voice-delay-selector'; voiceDelaySelector.className = 'u-helper-select'; voiceDelaySelector.style.width = '120px'; [ { value: '500', text: '0.5秒(快速)' }, { value: '1000', text: '1秒(推荐)' }, { value: '1500', text: '1.5秒(稳定)' }, { value: '2000', text: '2秒(保守)' } ].forEach(option => { const opt = document.createElement('option'); opt.value = option.value; opt.textContent = option.text; voiceDelaySelector.appendChild(opt); }); voiceDelaySelector.value = localStorage.getItem('voiceDelayTime') || '1000'; voiceDelaySelector.addEventListener('change', () => { localStorage.setItem('voiceDelayTime', voiceDelaySelector.value); showRecordNotification(`⏱️ 延迟时间已设置为 ${parseInt(voiceDelaySelector.value)/1000}秒`, 'info'); }); voiceDelayContainer.appendChild(voiceDelayLabel); voiceDelayContainer.appendChild(voiceDelaySelector); voiceSettingsContent.appendChild(voiceDelayContainer); const recordDurationContainer = document.createElement('div'); recordDurationContainer.className = 'u-helper-input-row'; const recordDurationLabel = document.createElement('label'); recordDurationLabel.className = 'u-helper-label'; recordDurationLabel.textContent = '录音时长'; const recordDurationSelector = document.createElement('select'); recordDurationSelector.className = 'u-helper-select'; recordDurationSelector.innerHTML = ` `; const savedRecordDuration = localStorage.getItem('u-helper-record-duration') || '3'; recordDurationSelector.value = savedRecordDuration; window.__recordDuration = parseInt(savedRecordDuration); recordDurationSelector.addEventListener('change', function() { const duration = this.value; localStorage.setItem('u-helper-record-duration', duration); window.__recordDuration = parseInt(duration); ULogger.debug('[录音设置] 录音时长已设置为:', duration + '秒'); }); recordDurationContainer.appendChild(recordDurationLabel); recordDurationContainer.appendChild(recordDurationSelector); voiceSettingsContent.appendChild(recordDurationContainer); contentContainer.appendChild(voiceSettingsSection); const { section: refreshSettingsSection, content: refreshSettingsContent } = createCollapsibleSection('🔄 自动刷新', 'u-collapse-refresh'); const refreshToggleContainer = document.createElement('div'); refreshToggleContainer.className = 'u-helper-input-row'; refreshToggleContainer.style.marginBottom = '12px'; const refreshToggleLabel = document.createElement('label'); refreshToggleLabel.className = 'u-helper-label'; refreshToggleLabel.textContent = '启用自动刷新'; const refreshToggleSwitch = document.createElement('div'); refreshToggleSwitch.className = 'u-helper-switch'; refreshToggleSwitch.innerHTML = '
'; const savedRefreshEnabled = localStorage.getItem('u-helper-auto-refresh') === 'true'; if (savedRefreshEnabled) { refreshToggleSwitch.classList.add('active'); } refreshToggleSwitch.addEventListener('click', function() { const isActive = this.classList.contains('active'); if (isActive) { this.classList.remove('active'); localStorage.setItem('u-helper-auto-refresh', 'false'); stopAutoRefresh(); } else { this.classList.add('active'); localStorage.setItem('u-helper-auto-refresh', 'true'); startAutoRefresh(); } }); refreshToggleContainer.appendChild(refreshToggleLabel); refreshToggleContainer.appendChild(refreshToggleSwitch); refreshSettingsContent.appendChild(refreshToggleContainer); const resumeRunContainer = document.createElement('div'); resumeRunContainer.className = 'u-helper-input-row'; resumeRunContainer.style.marginBottom = '12px'; resumeRunContainer.style.paddingTop = '10px'; resumeRunContainer.style.borderTop = '1px dashed rgba(255,255,255,0.08)'; const resumeRunLabel = document.createElement('label'); resumeRunLabel.className = 'u-helper-label'; resumeRunLabel.textContent = '刷新后保持挂机'; resumeRunLabel.title = '勾选后,如果页面被刷新,脚本会自动继续执行挂机任务'; const resumeRunSwitch = document.createElement('div'); resumeRunSwitch.className = 'u-helper-switch'; resumeRunSwitch.innerHTML = '
'; const savedResumeState = localStorage.getItem('u-helper-resume-after-refresh') === 'true'; if (savedResumeState) { resumeRunSwitch.classList.add('active'); } resumeRunSwitch.addEventListener('click', function() { const isActive = this.classList.contains('active'); if (isActive) { this.classList.remove('active'); localStorage.setItem('u-helper-resume-after-refresh', 'false'); showRefreshNotification('🚫 已禁用刷新后自动恢复', 'info'); } else { this.classList.add('active'); localStorage.setItem('u-helper-resume-after-refresh', 'true'); showRefreshNotification('✅ 已启用刷新后自动恢复', 'success'); } }); resumeRunContainer.appendChild(resumeRunLabel); resumeRunContainer.appendChild(resumeRunSwitch); refreshSettingsContent.appendChild(resumeRunContainer); const refreshIntervalContainer = document.createElement('div'); refreshIntervalContainer.className = 'u-helper-input-row'; refreshIntervalContainer.style.marginBottom = '12px'; const refreshIntervalLabel = document.createElement('label'); refreshIntervalLabel.className = 'u-helper-label'; refreshIntervalLabel.textContent = '刷新间隔'; const refreshIntervalSelector = document.createElement('select'); refreshIntervalSelector.className = 'u-helper-select'; refreshIntervalSelector.innerHTML = ` `; const savedRefreshInterval = localStorage.getItem('u-helper-refresh-interval') || '30'; refreshIntervalSelector.value = savedRefreshInterval; if (window.UHelperAutoRefresh) window.UHelperAutoRefresh.setIntervalMinutes(parseFloat(savedRefreshInterval)); refreshIntervalSelector.addEventListener('change', function() { const interval = this.value; localStorage.setItem('u-helper-refresh-interval', interval); if (window.UHelperAutoRefresh) { window.UHelperAutoRefresh.setIntervalMinutes(parseFloat(interval)); } let displayTime; if (parseFloat(interval) < 1) { displayTime = `${parseFloat(interval) * 60}秒`; } else { displayTime = `${interval}分钟`; } ULogger.debug('[自动刷新] 刷新间隔已设置为:', displayTime); if (window.UHelperAutoRefresh && window.UHelperAutoRefresh.isEnabled()) { startAutoRefresh(); } showRefreshNotification(`⏱️ 刷新间隔已设置为 ${displayTime}`, 'info'); }); refreshIntervalContainer.appendChild(refreshIntervalLabel); refreshIntervalContainer.appendChild(refreshIntervalSelector); refreshSettingsContent.appendChild(refreshIntervalContainer); const popupRefreshContainer = document.createElement('div'); popupRefreshContainer.className = 'u-helper-input-row'; popupRefreshContainer.style.marginBottom = '12px'; const popupRefreshLabel = document.createElement('label'); popupRefreshLabel.className = 'u-helper-label'; popupRefreshLabel.textContent = '拦截弹窗后刷新'; const popupRefreshSwitch = document.createElement('div'); popupRefreshSwitch.className = 'u-helper-switch'; popupRefreshSwitch.innerHTML = '
'; const savedPopupRefresh = localStorage.getItem('u-helper-popup-refresh') === 'true'; if (savedPopupRefresh) { popupRefreshSwitch.classList.add('active'); if (window.UHelperAutoRefresh) window.UHelperAutoRefresh.setRefreshAfterPopupBlock(true); } popupRefreshSwitch.addEventListener('click', function() { const isActive = this.classList.contains('active'); if (isActive) { this.classList.remove('active'); localStorage.setItem('u-helper-popup-refresh', 'false'); if (window.UHelperAutoRefresh) window.UHelperAutoRefresh.setRefreshAfterPopupBlock(false); showRefreshNotification('🚫 已禁用弹窗拦截后刷新', 'info'); } else { this.classList.add('active'); localStorage.setItem('u-helper-popup-refresh', 'true'); if (window.UHelperAutoRefresh) window.UHelperAutoRefresh.setRefreshAfterPopupBlock(true); showRefreshNotification('✅ 已启用弹窗拦截后刷新', 'success'); } }); popupRefreshContainer.appendChild(popupRefreshLabel); popupRefreshContainer.appendChild(popupRefreshSwitch); refreshSettingsContent.appendChild(popupRefreshContainer); contentContainer.appendChild(refreshSettingsSection); const { section: timingSettingsSection, content: timingSettingsContent } = createCollapsibleSection('⏱️ 时长保证(防漏时长)', 'u-section-timing-collapsed'); if (window.UHelperTiming && typeof window.UHelperTiming.initPanel === 'function') { window.UHelperTiming.initPanel(timingSettingsContent, { keepAliveSystem: keepAliveSystem }); } else { timingSettingsContent.innerHTML = '
学习时长模块未加载
'; } contentContainer.appendChild(timingSettingsSection); const { section: studyDurationSection, content: studyDurationContent } = createCollapsibleSection('📚 挂时长模式(不答题)', 'u-collapse-study-duration'); if (window.UHelperStudyDuration && typeof window.UHelperStudyDuration.initPanel === 'function') { window.UHelperStudyDuration.initPanel(studyDurationContent, { safeToast: safeToast }); } else { studyDurationContent.innerHTML = '
学习时长模块未加载
'; } contentContainer.appendChild(studyDurationSection); const { section: videoSettingsSection, content: videoSettingsContent } = createCollapsibleSection('🎬 视频设置', 'u-collapse-video'); const videoSkipContainer = document.createElement('div'); videoSkipContainer.className = 'u-helper-input-row'; videoSkipContainer.style.marginBottom = '12px'; const videoSkipLabel = document.createElement('label'); videoSkipLabel.className = 'u-helper-label'; videoSkipLabel.textContent = '自动跳过视频'; const videoSkipSwitch = document.createElement('div'); videoSkipSwitch.className = 'u-helper-switch'; videoSkipSwitch.innerHTML = '
'; window.__videoSkipEnabled = localStorage.getItem('u-video-skip') === 'true'; if (window.__videoSkipEnabled) { videoSkipSwitch.classList.add('active'); } videoSkipSwitch.addEventListener('click', function() { const isActive = this.classList.contains('active'); if (isActive) { this.classList.remove('active'); localStorage.setItem('u-video-skip', 'false'); window.__videoSkipEnabled = false; ULogger.debug('[视频助手] 自动跳过已关闭'); } else { this.classList.add('active'); localStorage.setItem('u-video-skip', 'true'); window.__videoSkipEnabled = true; ULogger.debug('[视频助手] 自动跳过已开启'); document.querySelectorAll('video').forEach(v => { if (!v.ended && v.duration > 0) { v.currentTime = v.duration; } }); } }); videoSkipContainer.appendChild(videoSkipLabel); videoSkipContainer.appendChild(videoSkipSwitch); videoSettingsContent.appendChild(videoSkipContainer); const videoSpeedContainer = document.createElement('div'); videoSpeedContainer.className = 'u-helper-input-row'; const videoSpeedLabel = document.createElement('label'); videoSpeedLabel.className = 'u-helper-label'; videoSpeedLabel.textContent = '播放速度'; const videoSpeedSelector = document.createElement('select'); videoSpeedSelector.className = 'u-helper-select'; videoSpeedSelector.style.width = '120px'; ['1.0', '1.25', '1.5', '2.0', '2.5', '3.0'].forEach(speed => { const option = document.createElement('option'); option.value = speed; option.textContent = `${speed}x`; videoSpeedSelector.appendChild(option); }); videoSpeedSelector.value = localStorage.getItem('u-video-speed') || '2.0'; videoSpeedSelector.addEventListener('change', () => { const newSpeed = parseFloat(videoSpeedSelector.value); localStorage.setItem('u-video-speed', newSpeed); document.querySelectorAll('video[data-handled-by-script="true"]').forEach(v => { v.playbackRate = newSpeed; ULogger.debug(`[视频助手] 已将视频速度调整为 ${newSpeed}x`); }); }); videoSpeedContainer.appendChild(videoSpeedLabel); videoSpeedContainer.appendChild(videoSpeedSelector); videoSettingsContent.appendChild(videoSpeedContainer); contentContainer.appendChild(videoSettingsSection); const { section: skipSection, content: skipContent } = createCollapsibleSection('🚫 章节过滤', 'u-collapse-skip'); if (typeof SkipManager !== 'undefined' && SkipManager.initPanel) { SkipManager.initPanel(skipContent); } else { skipContent.innerHTML = '
请先在脚本头部添加 SkipManager 代码
'; } contentContainer.appendChild(skipSection); const actionButtonsWrapper = document.createElement('div'); actionButtonsWrapper.style.cssText = ` display: flex; flex-direction: column; gap: 8px; margin-top: 4px; `; actionButtonsWrapper.appendChild(autoSelectButton); actionButtonsWrapper.appendChild(autoRunButton); actionButtonsWrapper.classList.add('u-helper-action-bar'); container.appendChild(titleBar); container.appendChild(contentContainer); container.appendChild(actionButtonsWrapper); container.appendChild(footerBar); document.body.appendChild(container); } function processSpecialNumbering(answers) { const result = []; for (let i = 0; i < answers.length; i++) { let answer = answers[i]; let lines = answer.split(/\n/).map(line => line.trim()).filter(Boolean); let processedLines = []; for (let j = 0; j < lines.length; j++) { let currentLine = lines[j]; let nextLine = j + 1 < lines.length ? lines[j + 1] : ''; if (/^\d+$/.test(currentLine) && nextLine.match(/^[00][\.\、\)]/)) { const prefix = currentLine; const match = nextLine.match(/^[00]([\.\、\)].*)/); if (match) { processedLines.push(`${prefix}${match[1]}`); j++; } else { processedLines.push(currentLine); } } else { processedLines.push(currentLine); } } result.push(processedLines.join('\n')); } return result; } function formatAnswer(answer) { if (!answer) return ''; const lines = answer.split(/\n/).map(line => line.trim()).filter(Boolean); return '\n' + lines.join('\n') + '\n'; } function deduplicateAnswers(answers) { const uniqueAnswers = new Set(); const result = []; for (const answer of answers) { const lowerAnswer = answer.toLowerCase(); if (!uniqueAnswers.has(lowerAnswer)) { uniqueAnswers.add(lowerAnswer); result.push(answer); } } return result; } function getAnswerType(answerGroup) { if (!answerGroup || typeof answerGroup !== 'string') return 'unknown'; const lines = answerGroup.split('\n').map(l => l.trim()).filter(Boolean); if (lines.length === 0) return 'unknown'; const allLinesAreChoiceFormat = lines.every(line => { const cleanedAnswer = line.replace(/^\d+[\.\、\)]+\s*/, '').trim(); return /^[A-ZА-Я](?:\s*,\s*[A-ZА-Я])*$/.test(cleanedAnswer); }); if (allLinesAreChoiceFormat) { return 'choice'; } return 'fill-in'; } function getAnswerDelay() { if (window.UHelperDelay && typeof window.UHelperDelay.getConfig === 'function') { var cfg = window.UHelperDelay.getConfig(); var min = cfg.answerMin || 1; var max = cfg.answerMax || 2; var delaySec = Math.floor(Math.random() * (max - min + 1)) + min; return delaySec * 1000; } const baseDelay = parseInt(localStorage.getItem('u-answer-delay') || '800'); const randomFactor = 0.8 + Math.random() * 0.4; return Math.floor(baseDelay * randomFactor); } function getPageDelay() { return parseInt(localStorage.getItem('u-page-delay') || '3500'); } function simulateHumanBehavior(element) { if (!element) return; const mouseEvents = [ new MouseEvent('mouseover', { bubbles: true }), new MouseEvent('mouseenter', { bubbles: true }), ]; const focusEvents = [ new Event('focus', { bubbles: true }), new Event('focusin', { bubbles: true }), ]; setTimeout(() => { mouseEvents.forEach(event => element.dispatchEvent(event)); }, Math.random() * 200); setTimeout(() => { focusEvents.forEach(event => element.dispatchEvent(event)); element.focus(); }, Math.random() * 200 + 100); } async function handleSpecialFillInQuestions() { try { ULogger.debug('检查特殊填空题结构...'); const scoopContainers = document.querySelectorAll('.fe-scoop'); if (!scoopContainers || scoopContainers.length === 0) { ULogger.debug('未找到特殊填空题结构'); return false; } const hasDropdownTrigger = Array.from(scoopContainers).some(el => el.querySelector('.ant-dropdown-trigger')); const hasInputOnly = Array.from(scoopContainers).some(el => el.querySelector('input, textarea')); if (hasDropdownTrigger && !hasInputOnly) { ULogger.debug('[AI] 检测到 scoop 下拉选择题,跳过特殊填空题处理(由 scoop-dropdown 逻辑处理)'); return false; } if (hasDropdownTrigger && hasInputOnly) { ULogger.debug('[AI] 检测到混合 scoop(下拉+输入),跳过特殊填空题处理'); return false; } ULogger.debug(`找到 ${scoopContainers.length} 个特殊填空题`); const directions = document.querySelector(".layout-direction-container, .abs-direction"); const directionsText = directions ? directions.textContent.trim() : ''; const availableWords = []; const optionPlaceholders = document.querySelectorAll('.option-placeholder'); optionPlaceholders.forEach(placeholder => { const word = placeholder.textContent.trim(); if (word) { availableWords.push(word); ULogger.debug('找到可用填空词:', word); } }); if (availableWords.length === 0) { const wordsList = document.querySelectorAll('.words-color'); wordsList.forEach(word => { const wordText = word.textContent.trim(); if (wordText) { availableWords.push(wordText); ULogger.debug('从words-color找到可用填空词:', wordText); } }); } const questions = []; scoopContainers.forEach((container, index) => { const numberElement = container.querySelector('.question-number'); const number = numberElement ? numberElement.textContent.trim() : (index + 1).toString(); const paragraphElement = container.closest('p'); const contextText = paragraphElement ? paragraphElement.textContent.trim() : ''; questions.push({ number: number, context: contextText, type: 'special-fill-in', container: container }); }); if (questions.length === 0) { ULogger.debug('未能提取填空题信息'); return false; } let prompt = `请帮我完成以下填空题。\n指示:${directionsText}\n\n`; if (availableWords.length > 0) { prompt += "可用词汇(答案必须从以下词汇中选择):\n"; availableWords.forEach(word => { prompt += `- ${word}\n`; }); prompt += "\n"; } questions.forEach(q => { prompt += `${q.number}. ${q.context}\n`; }); prompt += "\n请按照以下格式回答每个题目:\n"; prompt += "1. [填空答案]\n2. [填空答案] ...\n"; if (availableWords.length > 0) { prompt += "注意:答案必须从上面列出的可用词汇中选择。\n"; } prompt += "注意:只需提供填空的单词或短语,无需解释。\n"; ULogger.debug('生成的AI提示:', prompt); ULogger.debug('正在请求AI回答...'); const aiAnswer = await askKimi(prompt); if (!aiAnswer) { ULogger.debug('未能获取AI答案'); return false; } const answers = aiAnswer.split('\n').filter(line => /^\d+\./.test(line)); ULogger.debug('AI答案:', answers); for (let i = 0; i < questions.length; i++) { const question = questions[i]; const answerLine = answers[i]; if (!answerLine) continue; const answer = answerLine.replace(/^\d+\.\s*/, '').trim(); ULogger.debug(`准备填写题目 ${question.number} 的答案:`, answer); const inputSelectors = [ '.scoop-input-wrapper input', 'input', '.comp-abs-input input', '.input-user-answer input' ]; let input = null; for (const selector of inputSelectors) { input = question.container.querySelector(selector); if (input) { ULogger.debug(`找到填空输入框,使用选择器: ${selector}`); break; } } if (input) { await simulateHumanBehavior(input); input.value = answer + '\n'; ULogger.debug(`已添加换行符: ${answer}\\n`); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); } else { ULogger.debug('未找到填空题输入框'); } await new Promise(resolve => setTimeout(resolve, getAnswerDelay())); } return true; } catch (error) { ULogger.error('处理特殊填空题时发生错误:', error); return false; } } async function parseConsoleQuestions() { try { await new Promise(resolve => setTimeout(resolve, 1000)); const directions = document.querySelector(".layout-direction-container, .abs-direction"); const directionsText = directions ? directions.textContent.trim() : ''; ULogger.debug('题目指示:', directionsText); ULogger.debug('尝试从控制台输出获取题目信息'); const questions = []; const questionContainers = [ '.question-common-abs-question-container', '.question-wrap', '.question-basic', '.layoutBody-container.has-reply', '.question-material-banked-cloze.question-abs-question' ]; let mainContainer = null; for (const selector of questionContainers) { const container = document.querySelector(selector); if (container) { mainContainer = container; ULogger.debug(`找到题目容器: ${selector}`); break; } } if (!mainContainer) { ULogger.debug('未找到题目容器,使用默认查询'); mainContainer = document; } const availableWords = []; const optionPlaceholders = mainContainer.querySelectorAll('.option-placeholder'); optionPlaceholders.forEach(placeholder => { const word = placeholder.textContent.trim(); if (word) { availableWords.push(word); ULogger.debug('找到可用填空词:', word); } }); const questionSelectors = [ '.question-common-abs-reply', '.question-inputbox', '.question', '[class*="question"]', '[class*="stem"]' ]; let questionElements = []; for (const selector of questionSelectors) { const elements = mainContainer.querySelectorAll(selector); if (elements && elements.length > 0) { questionElements = Array.from(elements); ULogger.debug(`使用选择器 ${selector} 找到题目数量: ${elements.length}`); break; } } questionElements.forEach((element, index) => { let questionText = ''; if (element.querySelector('.fe-scoop, .scoop-input-wrapper')) { const paragraphs = element.querySelectorAll('p'); if (paragraphs.length > 0) { questionText = Array.from(paragraphs) .map(p => p.textContent.trim()) .join(' '); ULogger.debug(`从段落中提取填空题文本: ${questionText.substring(0, 50)}${questionText.length > 50 ? '...' : ''}`); } } if (!questionText) { const contentSelectors = [ '.question-inputbox-header', '.component-htmlview', 'p', '.title', '[class*="content"]', 'strong + span', '.words-color' ]; for (const selector of contentSelectors) { const contentElements = element.querySelectorAll(selector); if (contentElements && contentElements.length > 0) { questionText = Array.from(contentElements) .map(el => el.textContent.trim()) .join(' '); ULogger.debug(`使用选择器 ${selector} 找到题目文本`); break; } } } if (!questionText) { questionText = element.textContent.trim(); ULogger.debug('使用元素自身文本作为题目内容'); } questionText = questionText .replace(/\s+/g, ' ') .replace(/^\d+\.\s*/, '') .trim(); let number = (index + 1).toString(); const numberElement = element.querySelector('strong, [class*="number"], [class*="index"]'); if (numberElement) { const numberMatch = numberElement.textContent.match(/\d+/); if (numberMatch) { number = numberMatch[0]; } } let type = 'unknown'; if (element.querySelector('.fe-scoop, .scoop-input-wrapper, .comp-abs-input, .input-user-answer') || element.classList.contains('fill-blank-reply') || element.querySelector('span.question-number')) { type = 'fill-in'; ULogger.debug(`题目 ${number} 是填空题 (通过特殊类识别)`); } else if (element.querySelector('textarea')) { type = 'text'; ULogger.debug(`题目 ${number} 是文本题`); } else if (element.querySelector('input[type="text"]')) { type = 'fill-in'; ULogger.debug(`题目 ${number} 是填空题`); } else if (element.querySelector('input[type="radio"]')) { type = 'single-choice'; ULogger.debug(`题目 ${number} 是单选题`); } else if (element.querySelector('input[type="checkbox"]') || element.querySelector('.MultipleChoice--checkbox-item-34A_-')) { type = 'multiple-choice'; ULogger.debug(`题目 ${number} 是多选题`); } else if (element.querySelector('ul.single-choice--options-29v2W, ul[class*="single-choice"]')) { type = 'single-choice'; ULogger.debug(`题目 ${number} 是单选题 (普通版)`); } else if (element.querySelector('.option-wrap, .option.isNotReview, .caption')) { const options = element.querySelectorAll('.option-wrap, .option.isNotReview'); if (options.length > 0) { const titleElement = element.querySelector('.ques-title, .component-htmlview.ques-title'); const titleText = titleElement ? titleElement.textContent : ''; if (titleText.includes('多选') || titleText.includes('所有') || titleText.includes('多个') || titleText.includes('multiple')) { type = 'multiple-choice'; ULogger.debug(`题目 ${number} 是多选题 (通过选项和标题识别)`); } else { type = 'single-choice'; ULogger.debug(`题目 ${number} 是单选题 (通过选项识别)`); } } } else { const parentElement = element.parentElement; if (parentElement && ( parentElement.querySelector('.fe-scoop') || parentElement.querySelector('.scoop-input-wrapper') || parentElement.querySelector('.comp-abs-input') )) { type = 'fill-in'; ULogger.debug(`题目 ${number} 是填空题 (通过父元素识别)`); } else if (parentElement && parentElement.querySelector('.option-wrap, .option.isNotReview, .caption')) { type = 'single-choice'; ULogger.debug(`题目 ${number} 是选择题 (通过父元素识别)`); } else { type = 'text'; ULogger.debug(`题目 ${number} 类型未知,默认为文本题`); } } questions.push({ number: number, text: questionText, type: type, element: element }); }); let prompt = `请帮我完成以下题目。\n指示:${directionsText}\n\n`; questions.forEach(q => { let typeDesc = ''; switch (q.type) { case 'single-choice': typeDesc = '【单选题】'; break; case 'multiple-choice': typeDesc = '【多选题】'; break; case 'fill-in': typeDesc = '【填空题】'; break; case 'text': typeDesc = '【文本题】'; break; default: typeDesc = ''; } prompt += `${q.number}. ${typeDesc}${q.text}\n`; }); prompt += "\n请按照以下格式回答每个题目:\n"; prompt += "1. [答案]\n2. [答案] ...\n\n"; prompt += "注意事项:\n"; prompt += "- 只需提供答案,无需解释\n"; prompt += "- 单选题请直接回答选项内容,例如 'energy' 或 'future'\n"; prompt += "- 多选题请直接回答选项内容,用逗号分隔,例如 'energy, future'\n"; prompt += "- 填空题直接提供单词或短语\n"; prompt += "- 文本题提供完整句子或段落\n"; ULogger.debug('生成的AI提示:', prompt); return { prompt: prompt, questions: questions }; } catch (error) { ULogger.error('解析题目时发生错误:', error); return null; } } function isTopWindow() { return window.top === window.self; } function isIframeWindow() { return window.top !== window.self; } var _classicModule = window.UHelperClassicUCampus; function getClassicUCampusPageRole() { return _classicModule ? _classicModule.getClassicUCampusPageRole() : 'normal'; } function isClassicUCampusOuterPage() { return _classicModule ? _classicModule.isClassicUCampusOuterPage() : false; } function getClassicUnitTestIframe() { return document.querySelector('iframe#iframe, iframe[src*="uexercise.unipus.cn"], iframe[src*="enter_unit_test"]'); } function getCurrentUHelperUid() { return _classicModule ? _classicModule.getCurrentUHelperUid() : ( window.__U_HELPER_SYNCED_UID__ || localStorage.getItem('u-helper-synced-uid') || localStorage.getItem('userId') || window.userId || '' ); } function syncUidToClassicIframe() { return _classicModule ? _classicModule.syncUidToClassicIframe() : false; } function hideOuterPanelForClassicUCampus() { if (_classicModule) _classicModule.hideOuterPanelForClassicUCampus(); } function setupClassicUidSyncListener() { if (_classicModule) _classicModule.setupClassicUidSyncListener(); } async function waitForSyncedUid(timeout) { return _classicModule ? _classicModule.waitForSyncedUid(timeout) : null; } async function waitForClassicExerciseId(timeout) { return _classicModule ? _classicModule.waitForClassicExerciseId(timeout) : null; } function isClassicUCampusIframeQuestionPage() { return _classicModule ? _classicModule.isClassicUCampusIframeQuestionPage() : false; } function analyzeClassicUCampusIframeQuestions() { return _classicModule ? _classicModule.analyzeClassicUCampusIframeQuestions() : { count: 0, questions: [] }; } async function enterClassicUCampusOuterTestIfNeeded() { return _classicModule ? _classicModule.enterClassicUCampusOuterTestIfNeeded() : false; } async function waitForClassicUCampusIframe(timeout) { return _classicModule ? _classicModule.waitForClassicUCampusIframe(timeout) : false; } function sendClassicAnswersToIframe(answers, source) { var iframe = getClassicUnitTestIframe(); if (!iframe || !iframe.contentWindow) return false; iframe.contentWindow.postMessage({ type: 'U_HELPER_CLASSIC_FILL_ANSWERS', source: source || 'outer', answers: answers }, '*'); return true; } function extractClassicExerciseIdFromOuterUrl(urlStr) { return _classicModule ? _classicModule.extractClassicExerciseIdFromOuterUrl(urlStr) : null; } function getClassicUCampusExerciseId() { return _classicModule ? _classicModule.getClassicUCampusExerciseId() : null; } async function waitForClassicUCampusTestReady(timeout) { return _classicModule ? _classicModule.waitForClassicUCampusTestReady(timeout) : false; } function normalizeClassicAnswers(answers) { if (Array.isArray(answers)) return answers; if (answers && typeof answers === 'object') { return Object.keys(answers).sort(function(a, b) { return Number(a) - Number(b); }).map(function(k) { return answers[k]; }); } return []; } function classicLetterToRawIndex(answer) { if (answer == null) return null; var s = String(answer).trim().toUpperCase(); if (!/^[A-Z]$/.test(s)) return null; return s.charCodeAt(0) - 65; } function normalizeText(text) { return String(text == null ? '' : text).replace(/\s+/g, ' ').replace(/^[A-Z][\.、\)]\s*/i, '').trim().toLowerCase(); } function findClassicOptionByAnswer(question, answer) { return _classicModule ? _classicModule.findClassicOptionByAnswer ? _classicModule.findClassicOptionByAnswer(question, answer) : null : null; } async function fillClassicUCampusIframeAnswers(answers) { return _classicModule ? _classicModule.fillClassicUCampusIframeAnswers(answers) : false; } function buildClassicUCampusPromptFromAnalysis(analysis) { return analysis.questions.map(function(q) { if (q.questionType === 'single') { return [q.qindex + '. ' + (q.text || '')].concat( q.options.map(function(opt) { return opt.letter + '. ' + opt.text.replace(/^\s*[A-Z][\.、\)]\s*/, ''); }) ).join('\n'); } if (q.questionType === 'blank') { return q.qindex + '. [填空题]'; } return q.qindex + '. ' + (q.text || ''); }).join('\n\n'); } async function autoSelectAnswers() { try { ULogger.debug('开始自动选择答案'); if (window.UHelperStudyDuration && window.UHelperStudyDuration.isEnabled()) { ULogger.debug('[自动答题] 学习时长模式已启用,跳过自动答题'); return false; } if (typeof shouldSkipPage === 'function' && shouldSkipPage()) { ULogger.debug('[自动答题] 🛑 检测到非答题页面(视频/阅读/信息页),跳过答案消耗。'); return false; } if (window.UHelperClassicUCampus && typeof window.UHelperClassicUCampus.handleAutoSelect === 'function') { var _classicHandled = await window.UHelperClassicUCampus.handleAutoSelect(); if (_classicHandled) { return true; } } var _classicRoleEarly = typeof getClassicUCampusPageRole === 'function' ? getClassicUCampusPageRole() : 'normal'; if (_classicRoleEarly === 'outer_course_page') { ULogger.debug('[普通U校园外层] 当前是外层页面'); await enterClassicUCampusOuterTestIfNeeded(); await waitForClassicUCampusIframe(); hideOuterPanelForClassicUCampus(); syncUidToClassicIframe(); ULogger.debug('[普通U校园外层] 已处理完毕(面板已隐藏、UID 已同步),由 iframe 自行查题库答题'); return true; } if (_classicRoleEarly === 'unit_test_iframe_page') { ULogger.debug('[普通U校园iframe] 当前是 iframe 题目页,等待试卷加载...'); if (typeof waitForClassicUCampusTestReady === 'function') { await waitForClassicUCampusTestReady(); } ULogger.debug('[普通U校园iframe] 试卷已就绪,等待外层 UID 同步...'); var _syncedUid = await waitForSyncedUid(3000); var _syncedExerciseId = await waitForClassicExerciseId(3000); ULogger.debug('[普通U校园iframe] 当前用于查询题库的 UID:', _syncedUid); ULogger.debug('[普通U校园iframe] 当前用于查询题库的 exerciseId:', _syncedExerciseId); var _iframeBank = document.getElementById('online-bank-selector')?.value; if (!_iframeBank) { var _iframeStored = localStorage.getItem('selectedOnlineBank'); if (_iframeStored && _iframeStored !== '') _iframeBank = _iframeStored; } if (_iframeBank && _syncedUid) { var _iframeExerciseId = _syncedExerciseId || getClassicUCampusExerciseId(); ULogger.debug('[普通U校园iframe] 使用普通U校园 exerciseId:', _iframeExerciseId); ULogger.debug('[普通U校园iframe] 查询在线题库:', { uid: _syncedUid, course: _iframeBank, id: _iframeExerciseId, platform: 'classic_ucampus' }); try { var _iframeAnswers = await new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: 'POST', url: getApiUrl(API_CONFIG.ENDPOINTS.GET_ANSWERS), headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ uid: _syncedUid, courseName: _iframeBank, id: _iframeExerciseId, platform: 'classic_ucampus', source: 'classic_ucampus_iframe' }), onload: function(res) { var data = JSON.parse(res.responseText); if (res.status >= 200 && res.status < 300 && data.status === 'success') { resolve(data.answers); } else { reject(new Error(data.message || '服务器错误: ' + res.status)); } }, onerror: function() { reject(new Error('网络请求失败')); } }); }); if (Array.isArray(_iframeAnswers) && _iframeAnswers.length > 0) { ULogger.debug('[普通U校园iframe] 获取到答案,开始填写...'); var _iframeOk = await fillClassicUCampusIframeAnswers(_iframeAnswers, 'iframe_online_bank'); ULogger.debug('[普通U校园iframe] 答题完成:', _iframeOk); } else { ULogger.warn('[普通U校园iframe] 题库返回答案为空或格式错误'); } } catch (_iframeErr) { ULogger.warn('[普通U校园iframe] 查询题库失败:', _iframeErr.message); } } else { ULogger.debug('[普通U校园iframe] 未选择在线题库或 UID 未同步,跳过自动答题'); } return true; } const allOptions = document.querySelectorAll('.option.isNotReview, div.option'); let answeredCount = 0; let totalQuestions = 0; if (allOptions.length > 0) { const firstCaption = allOptions[0]?.querySelector('.caption'); const firstLetter = firstCaption ? firstCaption.textContent.trim() : 'A'; const isSpecialType = !['A', 'B', 'C', 'D', 'E', 'F'].includes(firstLetter); if (isSpecialType) { totalQuestions = Math.floor(allOptions.length / 2); } else { totalQuestions = Array.from(allOptions).filter(opt => { const caption = opt.querySelector('.caption'); return caption && caption.textContent.trim() === 'A'; }).length; } answeredCount = Array.from(allOptions).filter(opt => opt.classList.contains('selected') || opt.classList.contains('active') || opt.querySelector('input[type="radio"]:checked') ).length; ULogger.debug(`📊 答题状态检查: 已答 ${answeredCount}/${totalQuestions} 题`); if (answeredCount >= totalQuestions && totalQuestions > 0) { ULogger.debug('✅ 所有题目已答完,跳过重复答题'); return true; } } let selectedBank = document.getElementById('online-bank-selector')?.value; if (!selectedBank) { const stored = localStorage.getItem('selectedOnlineBank'); if (stored && stored !== "") { selectedBank = stored; ULogger.debug(`[防扣分机制] 界面下拉框未就绪,强制使用本地缓存配置: "${selectedBank}"`); } } if (selectedBank) { ULogger.debug(`[在线题库] 模式启动,选择的题库: "${selectedBank}"`); const uid = localStorage.getItem('userId'); const urlIds = (window.location.hash.split('/courseware/')[1] || '').split('/').filter(Boolean); let exerciseId = null; const isNormalVersion = Array.from(document.scripts).some(s => /pc-release-/.test(s.src)); if (isNormalVersion) { ULogger.debug('[版本检测] 检测为普通版'); exerciseId = urlIds.length > 1 ? urlIds[urlIds.length - 2] : urlIds[0]; } else { ULogger.debug('[版本检测] 检测为AI版'); exerciseId = urlIds[urlIds.length - 1]; } if (!exerciseId && urlIds.length > 0) { exerciseId = urlIds[urlIds.length - 1]; ULogger.debug('[备用方案] 采用最后一个ID:', exerciseId); } const isU_G_Format = exerciseId && /u\d+g\d+$/.test(exerciseId); const activeTask = document.querySelector('.pc-header-task-activity'); const isPracticingTask = activeTask && (activeTask.textContent || activeTask.innerText).trim() === 'Practicing'; const hasScoopSelect = !!document.querySelector('.fe-scoop'); const _classicRole = typeof getClassicUCampusPageRole === 'function' ? getClassicUCampusPageRole() : 'normal'; const isClassicIframe = _classicRole === 'unit_test_iframe_page' || (typeof isClassicUCampusIframeQuestionPage === 'function' && isClassicUCampusIframeQuestionPage()); const isClassicOuterPage = _classicRole === 'outer_course_page'; if (isClassicIframe) { ULogger.debug('[普通U校园iframe] 当前是普通 U 校园测试页,禁止进入多页教材逻辑'); } if (isClassicOuterPage) { ULogger.debug('[普通U校园外层] 当前是外层页面,将发送答案到 iframe'); } const isMultiPageCandidate = !!(isU_G_Format || isPracticingTask); const isSameExerciseContinuing = multiPageMode.isActive && multiPageMode.exerciseId === exerciseId && Array.isArray(multiPageMode.totalAnswers) && multiPageMode.totalAnswers.length > 0; const isMultiPageExercise = !isClassicIframe && !isClassicOuterPage && (isMultiPageCandidate || isSameExerciseContinuing); ULogger.debug('[分支判定]', { isClassicIframe, isMultiPageCandidate, isSameExerciseContinuing, isMultiPageExercise, exerciseId, url: location.href, radioQindexCount: document.querySelectorAll('input[type="radio"][qindex]').length, blankCount: document.querySelectorAll('input.blankinput[qindex]').length }); if (isMultiPageExercise) { const reason = isSameExerciseContinuing ? '同一练习继续答题' : (isU_G_Format ? `ID格式 (${exerciseId})` : `Practicing任务`); ULogger.debug(`[多页教材] 检测到多页教材模式 (原因: ${reason})`); if (!multiPageMode.isActive || multiPageMode.exerciseId !== exerciseId) { multiPageMode.pageIndex = 0; multiPageMode.totalAnswers = []; multiPageMode.isActive = true; multiPageMode.exerciseId = exerciseId; ULogger.debug(`[多页教材] 新练习开始,重置页面索引为 0`); } else { ULogger.debug(`[多页教材] 当前页面索引: ${multiPageMode.pageIndex}`); } } else { multiPageMode.isActive = false; multiPageMode.exerciseId = null; multiPageMode.pageIndex = 0; multiPageMode.totalAnswers = []; } if (!uid) { alert('无法获取用户UID,无法使用在线题库。'); return false; } if (!exerciseId) { var _promptRole = typeof getClassicUCampusPageRole === 'function' ? getClassicUCampusPageRole() : 'normal'; if (_promptRole === 'unit_test_iframe_page' || (typeof isClassicUCampusIframeQuestionPage === 'function' && isClassicUCampusIframeQuestionPage())) { exerciseId = typeof getClassicUCampusExerciseId === 'function' ? getClassicUCampusExerciseId() : 'classic_unknown'; ULogger.debug('[普通U校园iframe] 自动提取 exerciseId:', exerciseId); } else { const userInputId = prompt('无法自动识别练习ID,请手动输入练习ID(格式如:u3g162):', 'u3g162'); if (userInputId && /^u\d+g\d+$/i.test(userInputId)) { exerciseId = userInputId.toLowerCase(); ULogger.debug(`[在线题库] 用户手动输入ID: ${exerciseId}`); } else { alert('练习ID格式不正确或用户取消输入,无法查询在线题库。'); return false; } } } ULogger.debug(`[在线题库] 查询参数: UID=${uid}, Course=${selectedBank}, ID=${exerciseId}`); try { const answers = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: getApiUrl(API_CONFIG.ENDPOINTS.GET_ANSWERS), headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ uid: uid, courseName: selectedBank, id: exerciseId }), onload: res => { const data = JSON.parse(res.responseText); if (res.status >= 200 && res.status < 300 && data.status === 'success') { resolve(data.answers); } else { reject(new Error(data.message || `服务器错误: ${res.status}`)); } }, onerror: err => reject(new Error('网络请求失败')) }); }); ULogger.debug('[在线题库] 获取到答案:', answers); ULogger.debug('[在线题库] 答案类型:', typeof answers, '是否为数组:', Array.isArray(answers)); if (!Array.isArray(answers)) { ULogger.error('[在线题库] 错误:服务器返回的answers不是数组!', answers); alert('题库数据格式错误,请检查服务器返回的数据。'); return false; } if (isClassicOuterPage) { ULogger.debug('[普通U校园外层] 命中普通 U 校园外层页面,发送答案到 iframe'); ULogger.debug('[普通U校园外层] 答案:', answers); await enterClassicUCampusOuterTestIfNeeded(); const _iframeOk = await waitForClassicUCampusIframe(); if (_iframeOk) { sendClassicAnswersToIframe(answers, 'online_bank'); ULogger.debug('[普通U校园外层] 答案已发送到 iframe'); } else { ULogger.warn('[普通U校园外层] iframe 未就绪,无法发送答案'); } return true; } if (isClassicIframe) { ULogger.debug('[普通U校园iframe] 命中普通 U 校园测试页,跳过多页教材逻辑'); ULogger.debug('[普通U校园iframe] fill 收到原始答案:', answers); const _classicAnalysis = analyzeClassicUCampusIframeQuestions(); ULogger.debug('[普通U校园iframe] 题目分析:', _classicAnalysis); const ok = await fillClassicUCampusIframeAnswers(answers); if (ok) { ULogger.debug('[普通U校园iframe] 在线题库答案填写完成'); return true; } ULogger.warn('[普通U校园iframe] 在线题库填写失败,fallback 到原逻辑'); } if (isMultiPageExercise) { if (typeof shouldSkipPage === 'function' && shouldSkipPage()) { ULogger.debug('[多页教材] 🛑 当前为非答题页面,不消耗答案队列,不推进 pageIndex。'); updateMultiPageStatus(); return false; } if (!multiPageMode.isActive || !multiPageMode.totalAnswers.length) { multiPageMode.isActive = true; multiPageMode.totalAnswers = answers.slice(); multiPageMode.pageIndex = 0; multiPageMode.lastUrl = location.href; ULogger.debug(`[多页教材] 存储了 ${multiPageMode.totalAnswers.length} 个答案供后续使用`); } updateMultiPageStatus(); const page = getCurrentPageAnswersForMultiPage(answers); if (answers.length > 0) { const isSilentMode = localStorage.getItem('u-silent-submit-mode') === 'true'; if (isSilentMode) { const formattedForInterceptor = { children: page.currentAnswers.map(ans => { return { value: Array.isArray(ans) ? ans : [ans] }; }) }; unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = formattedForInterceptor; showRecordNotification('⚡️ 答案已就绪 (将在提交时自动修正)', 'success'); } else { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = null; } } if (page.currentAnswers.length > 0) { ULogger.debug(`[多页教材] 使用第 ${page.start + 1} 到第 ${page.end} 个答案:`, page.currentAnswers); const ok = await fillAnswersForMultiPage(page.currentAnswers); if (ok !== false) { multiPageMode.pageIndex = page.end; ULogger.debug(`[多页教材] ✅ 答题完成,页面索引已更新为: ${multiPageMode.pageIndex}`); ULogger.debug(`[多页教材] 下次答题将从第 ${multiPageMode.pageIndex + 1} 个答案开始`); updateMultiPageStatus(); } return true; } else { ULogger.warn(`[多页教材] 页面索引 ${multiPageMode.pageIndex} 超出答案数组范围 (${multiPageMode.totalAnswers.length})`); return false; } } else { if (answers.length > 0) { const isSilentMode = localStorage.getItem('u-silent-submit-mode') === 'true'; if (isSilentMode) { const formattedForInterceptor = { children: answers.map(ans => { return { value: Array.isArray(ans) ? ans : [ans] }; }) }; unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = formattedForInterceptor; showRecordNotification('⚡️ 答案已就绪 (将在提交时自动修正)', 'success'); } else { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = null; } } await fillAnswersFromArray(answers); } return true; } catch (error) { alert(`[在线题库] 查询失败: ${error.message}`); return false; } } ULogger.debug('AI状态:', { useKimiAI }); if (useKimiAI) { const specialFillInResult = await handleSpecialFillInQuestions(); if (specialFillInResult) { ULogger.debug('已成功处理特殊填空题'); return true; } } if (useKimiAI) { ULogger.debug('使用AI模式答题'); if (typeof isClassicUCampusIframeQuestionPage === 'function' && isClassicUCampusIframeQuestionPage()) { ULogger.debug('[普通U校园iframe] AI模式:检测到经典 U 校圆 iframe 测试页'); var _classicAnalysis = analyzeClassicUCampusIframeQuestions(); ULogger.debug('[普通U校园iframe] 题目分析:', _classicAnalysis); if (_classicAnalysis.count === 0) { ULogger.warn('[普通U校园iframe] 未检测到题目,fallback 到通用 AI 流程'); } else { var _classicPrompt = buildClassicUCampusPromptFromAnalysis(_classicAnalysis); ULogger.debug('[普通U校园iframe] 生成的干净 prompt (不含参考答案):', _classicPrompt); ULogger.debug('[普通U校园iframe] 正在请求 AI 回答...'); var _classicAiAnswer = await askKimi(_classicPrompt); if (!_classicAiAnswer) { ULogger.warn('[普通U校园iframe] AI 未返回答案,fallback 到通用 AI 流程'); } else { var _classicAnswers = []; var _classicLines = _classicAiAnswer.split('\n').filter(function(line) { return /^\d+\./.test(line.trim()); }); if (_classicLines.length > 0) { _classicAnswers = _classicLines.map(function(line) { return line.replace(/^\d+\.\s*/, '').trim(); }); } else { var _classicComma = _classicAiAnswer.split(/,\s*/).filter(function(part) { return /^\d+\./.test(part.trim()); }); if (_classicComma.length > 0) { _classicAnswers = _classicComma.map(function(part) { return part.replace(/^\d+\.\s*/, '').trim(); }); } else { _classicAnswers = [_classicAiAnswer.trim()]; } } ULogger.debug('[普通U校园iframe] AI答案(解析后):', _classicAnswers); ULogger.debug('[普通U校园iframe] 题目数量:', _classicAnalysis.count); if (_classicAnswers && _classicAnswers.length > 0) { var _isSilent = localStorage.getItem('u-silent-submit-mode') === 'true'; if (_isSilent) { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = { children: _classicAnswers.map(function(ans) { return { value: [ans] }; }) }; } else { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = null; } } ULogger.debug('[普通U校园iframe] fill 收到原始答案:', _classicAnswers); var _classicOk = await fillClassicUCampusIframeAnswers(_classicAnswers); if (_classicOk) { ULogger.debug('[普通U校园iframe] AI答案填写完成'); return true; } ULogger.warn('[普通U校园iframe] AI填写失败,fallback 到通用 AI 流程'); } } } const questionInfo = await parseConsoleQuestions(); if (!questionInfo) { ULogger.debug('无法获取题目信息'); return false; } ULogger.debug('正在请求AI回答...'); const aiAnswer = await askKimi(questionInfo.prompt); if (!aiAnswer) { ULogger.debug('未能获取AI答案'); return false; } let answers = []; const lineAnswers = aiAnswer.split('\n').filter(line => /^\d+\./.test(line.trim())); if (lineAnswers.length > 0) { answers = lineAnswers.map(line => line.replace(/^\d+\.\s*/, '').trim()); } else { const commaAnswers = aiAnswer.split(/,\s*/).filter(part => /^\d+\./.test(part.trim())); if (commaAnswers.length > 0) { answers = commaAnswers.map(part => part.replace(/^\d+\.\s*/, '').trim()); } else { answers = [aiAnswer.trim()]; } } ULogger.debug('AI答案(解析后):', answers); ULogger.debug('题目数量:', questionInfo.questions.length); if (answers && answers.length > 0) { const isSilentMode = localStorage.getItem('u-silent-submit-mode') === 'true'; if (isSilentMode) { const formattedForInterceptor = { children: answers.map(ans => ({ value: [ans] })) }; unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = formattedForInterceptor; } else { unsafeWindow.__UCAMPUS_ANSWER_CACHE__ = null; } } if (typeof isClassicUCampusIframeQuestionPage === 'function' && isClassicUCampusIframeQuestionPage()) { ULogger.debug('[普通U校园iframe] AI通用路径命中经典 iframe,跳过多页教材逻辑'); ULogger.debug('[普通U校园iframe] fill 收到原始答案:', answers); const _aiClassicAnalysis2 = analyzeClassicUCampusIframeQuestions(); ULogger.debug('[普通U校园iframe] 题目分析:', _aiClassicAnalysis2); const ok2 = await fillClassicUCampusIframeAnswers(answers); if (ok2) { ULogger.debug('[普通U校园iframe] AI答案填写完成'); return true; } ULogger.warn('[普通U校园iframe] AI填写失败,fallback 到统一填写器'); } const _isClassicIframeForUnified = typeof isClassicUCampusIframeQuestionPage === 'function' && isClassicUCampusIframeQuestionPage(); if (_isClassicIframeForUnified) { ULogger.debug('[普通U校园iframe] 统一填写器阶段:经典 iframe 页面,跳过 analyzePageQuestions / fillAnswersForMultiPage'); } try { const analysis = _isClassicIframeForUnified ? null : analyzePageQuestions(); if (!_isClassicIframeForUnified) { ULogger.debug('[AI模式] 统一填写器识别题型:', analysis); ULogger.debug('[AI模式] 统一填写器收到答案:', answers); } if ( analysis && ['standard_choice', 'special_choice', 'fill_in', 'containers', 'sorting', 'classic_sorting'].includes(analysis.type) ) { ULogger.debug('[AI模式] 使用统一填写器处理:', { type: analysis.type, count: analysis.count, answers }); await fillAnswersForMultiPage(answers); return true; } else { ULogger.debug('[AI模式] 统一填写器未匹配到已知题型,回退到逐题处理'); } } catch (unifiedErr) { ULogger.warn('[AI模式] 统一填写器执行异常,回退到逐题处理:', unifiedErr.message); } ULogger.debug('📝 开始逐题处理模式(支持混合题型)...'); await new Promise(resolve => setTimeout(resolve, 500)); for (let i = 0; i < questionInfo.questions.length; i++) { try { const question = questionInfo.questions[i]; const answerLine = answers[i] || (i < answers.length ? answers[i] : ''); if (!answerLine) { ULogger.debug(`题目 ${i + 1} 没有对应答案,跳过`); continue; } const answer = String(answerLine).replace(/^\d+\.\s*/, '').trim(); ULogger.debug(`准备填写题目 ${question.number} 的答案:`, answer); if (window.UHelperDelay && typeof window.UHelperDelay.beforeFillAnswer === 'function') { await window.UHelperDelay.beforeFillAnswer('fill-answer'); } else { await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 200)); } const questionElement = question.element; if (!questionElement) { ULogger.debug(`题目 ${question.number} 没有找到对应的DOM元素`); continue; } if (question.type === 'fill-in') { const inputSelectors = [ 'input.fill-blank--bc-input-DelG1', 'input[type="text"]', '.comp-abs-input input', '.input-user-answer input', '.scoop-input-wrapper input', '.fe-scoop input', 'input' ]; let input = null; for (const selector of inputSelectors) { input = questionElement.querySelector(selector); if (input) { ULogger.debug(`找到填空输入框,使用选择器: ${selector}`); break; } } if (input) { ULogger.debug(`填写填空题答案: ${answer}`); await simulateHumanBehavior(input); input.value = answer + '\n'; ULogger.debug(`已添加换行符: ${answer}\\n`); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); const submitButton = findSubmitButton(questionElement); if (submitButton) { ULogger.debug('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { ULogger.debug('未找到填空题输入框'); } } else if (question.type === 'text') { const textareaSelectors = [ 'textarea.writing--textarea-36VPs', 'textarea.scoopFill_textarea', 'textarea.question-inputbox-input', 'textarea.question-textarea-content', 'textarea', '.question-inputbox-input-container textarea', '.question-inputbox-body textarea' ]; let textarea = null; for (const selector of textareaSelectors) { textarea = questionElement.querySelector(selector); if (textarea) break; } if (textarea) { ULogger.debug(`找到文本框,填写答案: ${answer.substring(0, 20)}${answer.length > 20 ? '...' : ''}`); await simulateHumanBehavior(textarea); const typeText = async (text, element) => { const delay = () => Math.floor(Math.random() * 30) + 10; element.value = ''; element.dispatchEvent(new Event('input', { bubbles: true })); let currentText = ''; for (let i = 0; i < text.length; i++) { await new Promise(resolve => setTimeout(resolve, delay())); currentText += text[i]; element.value = currentText; element.dispatchEvent(new Event('input', { bubbles: true })); } currentText += '\n'; element.value = currentText; ULogger.debug(`已添加换行符: ${text}\\n`); element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); }; await typeText(answer, textarea); } else { ULogger.debug('未找到文本框元素'); } } else if (question.type === 'single-choice') { ULogger.debug('处理单选题,答案:', answer); const cleanAnswer = answer.trim(); ULogger.debug('清理后的答案:', cleanAnswer); let targetOption = null; let targetLetter = null; let foundByText = false; const isClassicVersion = questionElement.querySelector('ul.single-choice--options-29v2W, ul[class*="single-choice"]'); let optionsArray = []; const isSingleLetter = cleanAnswer.length === 1 && /^[A-Za-z]$/.test(cleanAnswer); if (isClassicVersion) { ULogger.debug('检测到普通U校园版题目结构'); const optionsList = questionElement.querySelectorAll('ul[class*="single-choice"] li label'); optionsArray = Array.from(optionsList); if (isSingleLetter) { const letter = cleanAnswer.toUpperCase(); ULogger.debug(`答案是单个字母 "${letter}",优先通过字母匹配`); for (const label of optionsArray) { const indexSpan = label.querySelector('span[class*="index"]'); if (indexSpan && indexSpan.textContent.trim().replace('.', '') === letter) { targetOption = label.querySelector('input[type="radio"]'); targetLetter = letter; ULogger.debug(`✅ 通过选项字母找到选项 ${letter}`); break; } } } if (!targetOption) { const cleanAnswerLower = cleanAnswer.toLowerCase(); for (let i = 0; i < optionsArray.length; i++) { const label = optionsArray[i]; const input = label.querySelector('input[type="radio"]'); const indexSpan = label.querySelector('span[class*="index"]'); const contentDiv = label.querySelector('div.html-view[class*="content"]'); if (input && indexSpan && contentDiv) { const letter = indexSpan.textContent.trim().replace('.', ''); const contentText = contentDiv.textContent.trim().toLowerCase(); ULogger.debug(`选项 ${letter}: "${contentText.substring(0, 30)}..."`); if (cleanAnswerLower.length > 1 && (contentText === cleanAnswerLower || contentText.includes(cleanAnswerLower))) { targetOption = input; targetLetter = letter; foundByText = true; ULogger.debug(`找到匹配的选项 ${letter}: "${contentText.substring(0, 30)}..."`); break; } } } } } else { const options = questionElement.querySelectorAll('.option.isNotReview'); optionsArray = Array.from(options); if (isSingleLetter) { const letter = cleanAnswer.toUpperCase(); ULogger.debug(`答案是单个字母 "${letter}",优先通过字母匹配`); for (const option of optionsArray) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === letter) { targetOption = option; targetLetter = letter; ULogger.debug(`✅ 通过选项字母找到选项 ${letter}`); break; } } } if (!targetOption) { const cleanAnswerLower = cleanAnswer.toLowerCase(); for (let i = 0; i < optionsArray.length; i++) { const option = optionsArray[i]; const content = option.querySelector('.component-htmlview.content'); const caption = option.querySelector('.caption'); if (content && caption) { const contentText = content.textContent.trim().toLowerCase(); const letter = caption.textContent.trim(); ULogger.debug(`选项 ${letter}: "${contentText.substring(0, 30)}..."`); if (cleanAnswerLower.length > 1 && (contentText === cleanAnswerLower || contentText.includes(cleanAnswerLower))) { targetOption = option; targetLetter = letter; foundByText = true; ULogger.debug(`找到匹配的选项 ${letter}: "${contentText.substring(0, 30)}..."`); break; } } } } } if (targetOption && targetLetter) { ULogger.debug(`✅ 准备点击选项 ${targetLetter} (${foundByText ? '通过文本匹配' : '通过字母匹配'})`); await simulateHumanBehavior(targetOption); if (isClassicVersion && targetOption.tagName === 'INPUT') { ULogger.debug('普通版:直接选中 radio 按钮'); targetOption.checked = true; targetOption.dispatchEvent(new Event('change', { bubbles: true })); targetOption.dispatchEvent(new Event('click', { bubbles: true })); } else { targetOption.click(); } ULogger.debug(`✅ 已选中选项 ${targetLetter}`); const submitButton = findSubmitButton(questionElement); if (submitButton) { ULogger.debug('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { ULogger.debug(`❌ 未找到匹配的选项: ${cleanAnswer}`); } } else if (question.type === 'multiple-choice') { ULogger.debug('处理多选题,答案:', answer); let options = questionElement.querySelectorAll('.option.isNotReview'); ULogger.debug('AI版选项数量:', options.length); if (options.length === 0) { options = questionElement.querySelectorAll('.MultipleChoice--checkbox-item-34A_-'); ULogger.debug('普通版选项容器数量:', options.length); if (options.length === 0) { options = questionElement.querySelectorAll('input[type="checkbox"]'); ULogger.debug('复选框数量:', options.length); } } const optionsArray = Array.from(options); ULogger.debug('最终选项数组长度:', optionsArray.length); const foundOptions = []; let answerString = answer; if (Array.isArray(answer)) { answerString = answer.join(','); } else if (typeof answer === 'object') { answerString = JSON.stringify(answer); } ULogger.debug('处理后的答案字符串:', answerString); const letterMatches = answerString.match(/[A-Z]/gi); if (letterMatches && letterMatches.length > 0) { ULogger.debug('检测到选项字母答案:', letterMatches); for (const option of optionsArray) { let caption, letter, text; caption = option.querySelector('.caption'); if (caption) { letter = caption.textContent.trim(); text = option.querySelector('.component-htmlview.content')?.textContent.trim() || ''; } else { const optLabel = option.querySelector('.MultipleChoice--checkbox-opt-2F4xY'); if (optLabel) { letter = optLabel.textContent.trim().replace('.', ''); text = option.querySelector('.html-view')?.textContent.trim() || ''; } else if (option.type === 'checkbox') { letter = option.value; const label = option.closest('label') || option.parentElement.querySelector('label'); text = label ? label.textContent.trim() : ''; } } ULogger.debug(`检查选项 - 字母: "${letter}", 文本: "${text ? text.substring(0, 30) : 'null'}"`); if (letter && letterMatches.includes(letter.toUpperCase())) { foundOptions.push({ option: option, letter: letter, text: text }); ULogger.debug(`✓ 找到字母匹配的选项 ${letter}`); } } } if (foundOptions.length === 0) { let answerForTextMatch = answerString; const answerParts = answerForTextMatch.split(/[,,、;;\s]+/).filter(part => part.trim().length > 0); ULogger.debug('答案拆分为多个部分:', answerParts); for (let i = 0; i < optionsArray.length; i++) { const option = optionsArray[i]; let content, caption, contentText, letter; content = option.querySelector('.component-htmlview.content'); caption = option.querySelector('.caption'); if (content && caption) { contentText = content.textContent.trim().toLowerCase(); letter = caption.textContent.trim(); } else { const optLabel = option.querySelector('.MultipleChoice--checkbox-opt-2F4xY'); const htmlView = option.querySelector('.html-view'); if (optLabel && htmlView) { letter = optLabel.textContent.trim().replace('.', ''); contentText = htmlView.textContent.trim().toLowerCase(); } else if (option.type === 'checkbox') { letter = option.value; const label = option.closest('label') || option.parentElement.querySelector('label'); contentText = label ? label.textContent.trim().toLowerCase() : ''; } } if (contentText && letter) { ULogger.debug(`选项 ${letter}: "${contentText}"`); for (const part of answerParts) { const cleanPart = part.trim().toLowerCase(); if (contentText === cleanPart || contentText.includes(cleanPart) || cleanPart.includes(contentText)) { foundOptions.push({ option: option, letter: letter, text: contentText }); ULogger.debug(`找到文本匹配的选项 ${letter}: "${contentText}"`); break; } } } } } if (foundOptions.length > 0) { ULogger.debug(`找到 ${foundOptions.length} 个匹配的选项`); for (const option of optionsArray) { if (option.classList.contains('selected')) { option.click(); await new Promise(resolve => setTimeout(resolve, 100)); } } for (const found of foundOptions) { ULogger.debug(`准备点击选项 ${found.letter}: "${found.text}"`); await simulateHumanBehavior(found.option); if (found.option.type === 'checkbox') { found.option.checked = true; found.option.dispatchEvent(new Event('change', { bubbles: true })); found.option.dispatchEvent(new Event('click', { bubbles: true })); } else { const checkbox = found.option.querySelector('input[type="checkbox"]'); if (checkbox) { checkbox.checked = true; checkbox.dispatchEvent(new Event('change', { bubbles: true })); checkbox.dispatchEvent(new Event('click', { bubbles: true })); } else { found.option.click(); } } ULogger.debug(`已点击选项 ${found.letter}`); if (window.UHelperDelay && typeof window.UHelperDelay.beforeFillAnswer === 'function') { await window.UHelperDelay.beforeFillAnswer('click-option'); } else { await new Promise(resolve => setTimeout(resolve, Math.random() * 300 + 100)); } } const submitButton = findSubmitButton(questionElement); if (submitButton) { ULogger.debug('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { ULogger.debug('未找到匹配的选项:', answer); } } await new Promise(resolve => setTimeout(resolve, getAnswerDelay())); } catch (questionError) { ULogger.error(`处理题目 ${i + 1} 时发生错误:`, questionError.message); ULogger.debug('继续处理下一道题...'); } } ULogger.debug('✅ AI答题循环完成'); return true; } const contentInfo = await getContentInfo(); if (!contentInfo || !contentInfo.answers || contentInfo.answers.length === 0) { ULogger.debug('%c未找到匹配的答案,可能为主观题,将自动跳过。', 'color: #f44336; font-weight: bold;'); return false; } if (contentInfo.activeTopicName !== lastActiveTopicName) { currentTopicUsedAnswers.clear(); lastActiveTopicName = contentInfo.activeTopicName; ULogger.debug('%c检测到主题切换,已重置答案使用记录', 'color: #2196F3;'); } const textareas = document.querySelectorAll('textarea.question-inputbox-input'); if (textareas && textareas.length > 0) { ULogger.debug('%c检测到文本框题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); ULogger.debug('%c使用新答案组 (文本题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in') { selectedAnswer = answer; ULogger.debug('%c所有文本题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { ULogger.debug('%c未找到适用于当前文本题的答案组。', 'color: #f44336;'); return false; } const answerMatches = selectedAnswer.match(/\d+[\.\、\) ][\s\S]*?(?=\d+[\.\、\) ]|$)/g); if (!answerMatches) { ULogger.debug('%c无法解析答案格式', 'color: #f44336;'); return false; } for (let index = 0; index < textareas.length; index++) { const textarea = textareas[index]; try { if (answerMatches[index]) { const rawAnswer = answerMatches[index] .replace(/^\d+[\.\、\) ]\s*/, '') .trim(); const parts = rawAnswer.split('|||'); let answer = ''; let answerType = ''; if (parts.length > 1) { answer = parts[0].trim(); answerType = '填空题'; } else { answer = parts[0].trim(); answerType = '文本题'; } await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(textarea); let currentText = ''; const answerText = answer + '\n'; let charIndex = 0; const typeNextChar = () => { if (charIndex < answerText.length) { currentText += answerText.charAt(charIndex); textarea.value = currentText; textarea.dispatchEvent(new Event('input', { bubbles: true })); const typingDelay = 30 + Math.random() * 80; charIndex++; setTimeout(typeNextChar, typingDelay); } else { textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); } }; typeNextChar(); }, getAnswerDelay() + index * 500); }); ULogger.debug(`%c第 ${index + 1} 题 (${answerType}) 已填写答案: ${answer}`, 'color: #2196F3;'); } else { ULogger.debug(`%c第 ${index + 1} 题未找到对应答案`, 'color: #f44336;'); } } catch (error) { ULogger.error(`填写第 ${index + 1} 题时发生错误:`, error); } } return true; } const fillInBlanks = document.querySelectorAll('.fe-scoop'); if (fillInBlanks && fillInBlanks.length > 0) { ULogger.debug('%c检测到填空题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); ULogger.debug('%c使用新答案组 (填空题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in') { selectedAnswer = answer; ULogger.debug('%c所有填空题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { ULogger.debug('%c未找到适用于当前填空题的答案组。', 'color: #f44336;'); return false; } const answerLines = selectedAnswer.split('\n').map(line => line.trim()).filter(Boolean); if (!answerLines || answerLines.length === 0) { ULogger.debug('%c无法解析答案格式', 'color: #f44336;'); return false; } const fillInAnswers = answerLines.map(line => { return line.replace(/^\d+[\.\、\) ]\s*/, '').trim(); }).filter(answer => { return !/^[A-Z]$/.test(answer); }); if (fillInAnswers.length < fillInBlanks.length) { ULogger.debug(`%c警告:找到的填空答案数量 (${fillInAnswers.length}) 少于页面空格数量 (${fillInBlanks.length})。`, 'color: #FFA500;'); } for (let index = 0; index < fillInBlanks.length; index++) { const blank = fillInBlanks[index]; try { const inputContainer = blank.querySelector('.comp-abs-input'); const input = inputContainer ? inputContainer.querySelector('input') : null; if (input && fillInAnswers[index]) { const rawAnswer = fillInAnswers[index]; const answer = rawAnswer.split('|||')[0].trim(); await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(input); let currentText = ''; const answerText = answer + '\n'; let charIndex = 0; const typeNextChar = () => { if (charIndex < answerText.length) { currentText += answerText.charAt(charIndex); input.value = currentText; input.dispatchEvent(new Event('input', { bubbles: true })); const typingDelay = 30 + Math.random() * 80; charIndex++; setTimeout(typeNextChar, typingDelay); } else { input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); } }; typeNextChar(); }, getAnswerDelay() + index * 500); }); ULogger.debug(`%c第 ${index + 1} 题已填写答案: ${answer}`, 'color: #2196F3;'); } else { ULogger.debug(`%c第 ${index + 1} 题未找到输入框或有效答案`, 'color: #f44336;'); } } catch (error) { ULogger.error(`填写第 ${index + 1} 题时发生错误:`, error); } } return true; } const matchingWrapper = document.querySelector('#sortableListWrapper'); if (matchingWrapper) { ULogger.debug('%c检测到匹配/排序题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); const iframe = document.querySelector('iframe#pc-sequence-iframe'); const doc = iframe ? (iframe.contentDocument || iframe.contentWindow.document) : document; const inputs = doc.querySelectorAll('input[type="text"], input.answer-item-input'); if (!inputs || inputs.length === 0) { ULogger.debug('%c在此类匹配题中未找到输入框。', 'color: #f44336;'); return false; } let selectedAnswer = null; for (const answer of contentInfo.answers) { if (!currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); ULogger.debug('%c使用新答案组', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { ULogger.debug('%c所有答案组都已使用,将重用第一组答案', 'color: #FFA500;'); selectedAnswer = contentInfo.answers[0]; } const answerMatches = selectedAnswer.match(/\d+[\.\、\)]\s*[A-Z]/g); if (!answerMatches) { ULogger.debug('%c无法解析匹配题答案格式 (e.g., "1) D 2) C ...")', 'color: #f44336;'); return false; } const answers = answerMatches.map(ans => ans.replace(/\d+[\.\、\)]\s*/, '').trim()); for (let index = 0; index < inputs.length; index++) { const input = inputs[index]; if (answers[index]) { await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(input); input.value = answers[index]; input.dispatchEvent(new Event('input', { bubbles: true })); setTimeout(() => { input.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); }, 100 + Math.random() * 200); }, getAnswerDelay() + index * 400); }); ULogger.debug(`%c匹配题 ${index + 1} 已填写答案: ${answers[index]}`, 'color: #2196F3;'); } } return true; } const choiceContainer = document.querySelector('.question-common-abs-choice'); const optionDivs = document.querySelectorAll('div.option'); if (!choiceContainer && (!optionDivs || optionDivs.length === 0)) { ULogger.debug('%c当前页面既不是选择题也不是填空题也不是文本框题', 'color: #f44336; font-weight: bold;'); return false; } const questions = choiceContainer ? document.querySelectorAll('.question-common-abs-reply') : document.querySelectorAll('.question-common-abs-banked-cloze'); if (!questions || questions.length === 0) { ULogger.debug('%c未找到题目', 'color: #f44336; font-weight: bold;'); return false; } ULogger.debug('%c开始自动选择答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'choice' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); ULogger.debug('%c使用新答案组 (选择题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'choice') { selectedAnswer = answer; ULogger.debug('%c所有选择题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { ULogger.debug('%c未找到适用于当前选择题的答案组。', 'color: #f44336;'); return false; } for (let questionIndex = 0; questionIndex < questions.length; questionIndex++) { const question = questions[questionIndex]; await new Promise(resolve => setTimeout(resolve, getAnswerDelay() * (1 + questionIndex * 0.5) + Math.random() * 300)); const options = choiceContainer ? question.querySelectorAll('.option.isNotReview') : question.querySelectorAll('div.option'); if (!options || options.length === 0) { ULogger.debug(`%c第 ${questionIndex + 1} 题未找到选项`, 'color: #f44336;'); continue; } const answerPattern = /(\d+)[\.\、\)]\s*([A-K](?:\s*,\s*[A-K])*)/g; const answers = []; let match; answerPattern.lastIndex = 0; while ((match = answerPattern.exec(selectedAnswer)) !== null) { const questionNum = parseInt(match[1]); const answerChoices = match[2].split(/\s*,\s*/); answers[questionNum - 1] = answerChoices; } if (answers.length > 0 && questionIndex < answers.length) { const answerChoices = answers[questionIndex]; if (answerChoices && answerChoices.length > 0) { ULogger.debug(`%c第 ${questionIndex + 1} 题检测到答案: ${answerChoices.join(', ')}`, 'color: #2196F3;'); for (let letterIndex = 0; letterIndex < answerChoices.length; letterIndex++) { const letter = answerChoices[letterIndex]; let targetOption = null; for (let i = 0; i < options.length; i++) { const caption = options[i].querySelector('.caption'); if (caption && caption.textContent.trim() === letter) { targetOption = options[i]; break; } } if (targetOption) { await new Promise(resolve => { setTimeout(() => { const isSelected = targetOption.classList.contains('selected'); if (!isSelected) { targetOption.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); setTimeout(() => { targetOption.click(); ULogger.debug(`%c第 ${questionIndex + 1} 题已选择选项 ${letter}`, 'color: #2196F3;'); setTimeout(resolve, 100 + Math.random() * 200); }, 100 + Math.random() * 300); } else { ULogger.debug(`%c第 ${questionIndex + 1} 题选项 ${letter} 已经被选中`, 'color: #FFA500;'); resolve(); } }, getAnswerDelay() + letterIndex * 200); }); } else { ULogger.debug(`%c第 ${questionIndex + 1} 题未找到选项 ${letter}`, 'color: #f44336;'); } } } else { ULogger.debug(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;'); } } else { ULogger.debug(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;'); } } return true; } catch (error) { ULogger.error('自动选择答案时发生错误:', error); return false; } } async function getContentInfo() { try { const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span'); const navigationPath = Array.from(breadcrumbs).map(span => span.textContent.trim()); const chapterContent = navigationPath.length >= 2 ? navigationPath[1] : '未找到章节内容'; const topicElement = document.querySelector(".pc-header-tasks-container .pc-task.pc-header-task-activity"); const topicElementSecond = document.querySelector(".pc-header-tasks-container .pc-task.pc-header-task-activity.pc-task-last"); const topicFirstPart = topicElement ? topicElement.textContent.trim() : ''; const topicSecondPart = topicElementSecond ? topicElementSecond.textContent.trim() : ''; const topicContent = [topicFirstPart, topicSecondPart].filter(Boolean).join(' : '); const finalTopicContent = topicContent || '未找到主题内容'; const tabContainer = document.querySelector('.pc-tab-container'); const allTopics = tabContainer ? tabContainer.querySelectorAll('.ant-col') : []; const totalTopics = allTopics.length; const allTopicNames = []; allTopics.forEach((topic, index) => { const topicDiv = topic.querySelector('.pc-tab-view-container'); if (topicDiv) { const isActive = topic.classList.contains('pc-header-tab-activity'); allTopicNames.push({ index: index + 1, name: topicDiv.textContent.trim(), isActive: isActive }); } }); let activeTopicName = ''; let nextTopicName = ''; let foundActive = false; for (let i = 0; i < allTopicNames.length; i++) { if (foundActive) { nextTopicName = allTopicNames[i].name; break; } if (allTopicNames[i].isActive) { activeTopicName = allTopicNames[i].name; foundActive = true; } } ULogger.debug('%c当前页面信息', 'color: #2196F3; font-weight: bold; font-size: 14px;'); ULogger.debug('导航路径:', navigationPath); ULogger.debug('章节内容:', chapterContent); ULogger.debug('主题内容:', finalTopicContent); ULogger.debug('主题总数:', totalTopics); ULogger.debug('当前选中的主题:', activeTopicName || '未找到选中的主题'); ULogger.debug('下一个主题:', nextTopicName || '没有下一个主题'); ULogger.debug('\n%c所有主题列表', 'color: #2196F3; font-weight: bold; font-size: 14px;'); allTopicNames.forEach(topic => { ULogger.debug(`${topic.index}. ${topic.name} ${topic.isActive ? '(当前选中)' : ''}`); }); return { chapter: chapterContent, topic: finalTopicContent, totalTopics: totalTopics, activeTopicName: activeTopicName, nextTopicName: nextTopicName, allTopics: allTopicNames, answers: [] }; } catch (error) { ULogger.error('获取内容时发生错误:', error); return null; } } let isAutoRunning = localStorage.getItem('u-auto-running') === 'true'; let autoRunTimeoutId = null; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); if (window.UHelperRecording && typeof window.UHelperRecording.init === 'function') { window.UHelperRecording.init({ safeToast: safeToast, sleep: sleep }); } function updateAutoRunButtonUI() { const btn = document.getElementById('auto-run-btn'); if (!btn) return; if (isAutoRunning) { btn.innerHTML = '停止挂机'; btn.style.background = 'linear-gradient(135deg, #F59E0B 0%, #EF4444 100%)'; btn.className = 'u-helper-btn u-helper-btn-danger'; } else { btn.innerHTML = '开始挂机'; btn.style.background = 'linear-gradient(135deg, #10B981 0%, #06B6D4 100%)'; btn.className = 'u-helper-btn u-helper-btn-success'; } } function findFooterButtonByText(text) { const selectors = [ '#pc-foot a, #pc-foot button', '#footerContainer button', '.submit-bar-pc--btn-1_Xvo', 'button[class*="submit"]', 'button[class*="btn"]' ]; for (const selector of selectors) { const footerButtons = document.querySelectorAll(selector); for (const btn of footerButtons) { if (btn.textContent.replace(/\s/g, '').includes(text)) { return btn; } } } return null; } function findSubmitButton(container = document) { const selectors = [ 'button[type="submit"]', 'button[class*="submit"]', 'button[class*="confirm"]', '.submit-bar-pc--btn-1_Xvo', '.btns-submit button.submit-btn', 'button.submit-btn', ]; for (const selector of selectors) { try { const button = container.querySelector(selector); if (button) { const buttonText = (button.textContent || '').toLowerCase(); if (buttonText.includes('提交') || buttonText.includes('submit') || buttonText.includes('确定') || buttonText.includes('确认') || selector.includes('submit') || selector.includes('confirm')) { return button; } } } catch (e) { ULogger.warn('选择器执行失败:', selector, e.message); } } const footerContainer = container.querySelector('#footerContainer') || document.querySelector('#footerContainer'); if (footerContainer) { const footerButtons = footerContainer.querySelectorAll('button'); for (const button of footerButtons) { const text = (button.textContent || '').trim(); if (text.includes('提交') || text === 'Submit' || text === '确定' || text === '确认') { return button; } } } const allButtons = container.querySelectorAll('button'); for (const button of allButtons) { const text = (button.textContent || '').trim(); if (text === '提交' || text === 'Submit' || text === '确定' || text === '确认') { return button; } } return null; } function handleSubmitConfirmDialog() { const dialogSelectors = [ '[class*="dialog"]', '[role="dialog"]', '.modal', '[class*="modal"]' ]; for (const selector of dialogSelectors) { const dialog = document.querySelector(selector); if (dialog && dialog.style.display !== 'none' && dialog.offsetParent !== null) { const dialogText = dialog.textContent; if (dialogText.includes('确认要提交') || dialogText.includes('小U只记录你第一次答题的得分') || dialogText.includes('确认提交') || dialogText.includes('submit')) { ULogger.debug('[提交确认] 检测到提交确认弹窗'); const confirmButtons = dialog.querySelectorAll('button'); for (const button of confirmButtons) { const buttonText = button.textContent.trim().toLowerCase(); if (buttonText === '确认' || buttonText === 'confirm' || buttonText === 'ok' || buttonText === '提交') { ULogger.debug('[提交确认] 找到确认按钮,准备点击'); setTimeout(() => { button.click(); ULogger.debug('[提交确认] 已点击确认按钮'); }, 500 + Math.random() * 500); return true; } } } } } return false; } function setupSubmitConfirmHandler() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.matches && ( node.matches('[class*="dialog"]') || node.matches('[role="dialog"]') || node.matches('.modal') || node.matches('[class*="modal"]') )) { setTimeout(() => { handleSubmitConfirmDialog(); }, 100); } const dialogs = node.querySelectorAll && node.querySelectorAll('[class*="dialog"], [role="dialog"], .modal, [class*="modal"]'); if (dialogs && dialogs.length > 0) { setTimeout(() => { handleSubmitConfirmDialog(); }, 100); } } }); } }); }); observer.observe(document.body, { childList: true, subtree: true }); ULogger.debug('[提交确认] 已启动提交确认弹窗监听器'); } async function handleVocabularyCards() { ULogger.debug('[挂机] 检测到词汇卡片学习页面,开始自动点击下一个...'); let cardCount = 0; while (cardCount < 300) { const nextButton = document.querySelector("#main-content > div > div > div > div.layoutBody-container > div > div > div.vocContainer > div.vocActions > div.action.next"); if (!nextButton || nextButton.classList.contains('disabled')) { ULogger.debug(`[挂机] 词汇卡片处理完成,共处理 ${cardCount} 个卡片。准备跳转到下一任务。`); return; } if (window.__autoPlayRecordEnabled) { ULogger.debug(`[挂机] 检查第 ${cardCount + 1} 个词汇卡片是否有录音题...`); const recordingHandled = await handleVocabularyRecording(); if (recordingHandled) { ULogger.debug(`[挂机] 第 ${cardCount + 1} 个词汇卡片的录音题已处理完成`); await waitForScoreAppear(); } } await sleep(500 + Math.random() * 1000); nextButton.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(100 + Math.random() * 200); ULogger.debug(`[挂机] 点击第 ${cardCount + 1} 个词汇卡片的"下一个"按钮`); nextButton.click(); cardCount++; await sleep(800 + Math.random() * 500); } ULogger.debug(`[挂机] 已达到最大处理数量限制 (${cardCount})`); } async function waitForVideosToEnd() { let videos = Array.from(document.querySelectorAll('video')).filter(v => !v.ended && v.duration > 0); if (videos.length === 0) { ULogger.debug('[挂机] 页面上没有需要等待的视频,继续执行。'); return; } ULogger.debug(`[挂机] 检测到 ${videos.length} 个视频,正在监控播放状态...`); videos.forEach(v => { if (v.paused) { v.muted = true; v.play().catch(e => ULogger.warn('[挂机] 视频播放请求被拦截:', e)); } const savedSpeed = localStorage.getItem('u-video-speed') || '2.0'; v.playbackRate = parseFloat(savedSpeed); }); return new Promise((resolve) => { const checkTimer = setInterval(() => { const currentVideos = Array.from(document.querySelectorAll('video')).filter(v => !v.ended && v.duration > 0); if (currentVideos.length === 0) { ULogger.debug('[挂机] 视频已全部处理完毕(结束或移除)。'); clearInterval(checkTimer); resolve(); } var _accurateTiming5c = window.UHelperTiming && typeof window.UHelperTiming.isAccurateMode === 'function' && window.UHelperTiming.isAccurateMode(); if (window.__videoSkipEnabled && !_accurateTiming5c) { currentVideos.forEach(v => { if (v.duration > 0) v.currentTime = v.duration; }); } }, 2000); setTimeout(() => { clearInterval(checkTimer); resolve(); }, 600000); }); } function getCurrentDelayPageType() { try { if (typeof getSkipPageType === 'function') { var t = getSkipPageType(); if (t) return t; } if (document.querySelector('.discussion-title, .question-common-abs-material, textarea.question-inputbox-input')) { return 'discussion'; } if (typeof isQuestionPage === 'function' && isQuestionPage()) { return 'question'; } if (document.querySelector('video[src], .video-js, .video-container')) { return 'video'; } return 'normal'; } catch (_) { return 'normal'; } } function getLegacyAnswerDelayMs() { var n = parseInt(localStorage.getItem('u-answer-delay') || '800', 10); if (!isFinite(n) || n < 100) n = 800; return n; } function getLegacyPageDelayMs() { var n = parseInt(localStorage.getItem('u-page-delay') || '3500', 10); if (!isFinite(n) || n < 500) n = 3500; return n; } async function delayBeforeFillAnswer(reason) { if (!window.isAutoModeRunning) return; if (window.UHelperDelay && typeof window.UHelperDelay.beforeFillAnswer === 'function') { ULogger.debug('[UHelperDelay] beforeFillAnswer start:', reason || ''); await window.UHelperDelay.beforeFillAnswer(reason || 'before_fill_answer'); ULogger.debug('[UHelperDelay] beforeFillAnswer end'); return; } await sleep(getLegacyAnswerDelayMs()); } async function delayBeforeSubmit(questionType) { if (!window.isAutoModeRunning) return; if (window.UHelperDelay && typeof window.UHelperDelay.beforeSubmit === 'function') { ULogger.debug('[UHelperDelay] beforeSubmit start:', questionType || 'question'); await window.UHelperDelay.beforeSubmit(questionType || 'question'); ULogger.debug('[UHelperDelay] beforeSubmit end'); return; } await sleep(getLegacyAnswerDelayMs()); } async function delayAfterSubmit() { if (!window.isAutoModeRunning) return; if (window.UHelperDelay && typeof window.UHelperDelay.afterSubmit === 'function') { ULogger.debug('[UHelperDelay] afterSubmit start'); await window.UHelperDelay.afterSubmit(); ULogger.debug('[UHelperDelay] afterSubmit end'); return; } await sleep(2500); } async function delayBeforeNavigate(reason) { if (!window.isAutoModeRunning) return; var pageType = getCurrentDelayPageType(); if (window.UHelperDelay && typeof window.UHelperDelay.beforeNavigate === 'function') { ULogger.debug('[UHelperDelay] beforeNavigate start:', pageType, reason || ''); await window.UHelperDelay.beforeNavigate(pageType, reason || 'before_navigate'); ULogger.debug('[UHelperDelay] beforeNavigate end'); return; } await sleep(getLegacyPageDelayMs()); } async function waitPageStayGate(reason) { if (!window.isAutoModeRunning) return; var pageType = getCurrentDelayPageType(); if (window.UHelperDelay && typeof window.UHelperDelay.markPageEnter === 'function') { window.UHelperDelay.markPageEnter(pageType); } if (window.UHelperDelay && typeof window.UHelperDelay.waitUntilCanLeave === 'function') { ULogger.debug('[UHelperDelay] 页面停留闸门 start:', pageType, reason || ''); await window.UHelperDelay.waitUntilCanLeave(pageType, { reason: reason || 'auto_page_stay_gate', maxWait: 600000 }); ULogger.debug('[UHelperDelay] 页面停留闸门 end:', pageType); } } function hasAssessmentPending() { try { var text = document.body ? (document.body.innerText || '') : ''; var keywords = [ '评测中', '评分中', '正在评测', '正在评分', '正在提交', '提交中', '请稍候', 'loading' ]; if (keywords.some(function (k) { return text.includes(k); })) { return true; } var loadingEls = document.querySelectorAll( '.ant-spin-spinning, ' + '.ant-spin, ' + '.loading, ' + '.spinner, ' + '[class*="loading"], ' + '[class*="spinner"], ' + '[class*="evaluat"], ' + '[class*="score"]' ); for (var i = 0; i < loadingEls.length; i++) { var el = loadingEls[i]; var rect = el.getBoundingClientRect(); var style = window.getComputedStyle(el); var visible = rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; if (visible) { var t = (el.innerText || el.textContent || '').trim(); if (!t || /评测|评分|加载|提交|loading/i.test(t)) { return true; } } } return false; } catch (e) { ULogger.warn('[提交后评测等待] 检测异常:', e); return false; } } async function waitAfterSubmitAssessment(reason) { if (!window.isAutoModeRunning) return; var minWait = 5000 + Math.floor(Math.random() * 5000); var maxWait = 20000; var start = Date.now(); ULogger.debug('[提交后评测等待] 开始:', { reason: reason || '', minWait: minWait }); await sleep(minWait); while (Date.now() - start < maxWait) { if (!hasAssessmentPending()) { ULogger.debug('[提交后评测等待] 评测已结束,继续流程'); return; } ULogger.debug('[提交后评测等待] 仍在评测中,继续等待...'); await sleep(1000); } ULogger.warn('[提交后评测等待] 等待超时,允许继续,避免卡死'); } async function runNextStep() { if (!isAutoRunning) return; ULogger.debug("runNextStep: 开始执行"); if (window.UHelperStudyDuration && window.UHelperStudyDuration.isEnabled()) { ULogger.debug('[学习时长] 学习时长模式已启用,跳过自动答题流程'); autoRunTimeoutId = setTimeout(runNextStep, 10000); return; } await waitPageStayGate('runNextStep_start'); if ( window.UHelperTiming && typeof window.UHelperTiming.shouldPauseAutoFlow === 'function' && window.UHelperTiming.shouldPauseAutoFlow() ) { ULogger.debug('[UHelperTiming] 自动流程暂停:', window.UHelperTiming.getState ? window.UHelperTiming.getState().pausedReason : ''); autoRunTimeoutId = setTimeout(runNextStep, 5000); return; } if ( window.UHelperDelay && typeof window.UHelperDelay.shouldPauseBeforeStep === 'function' && window.UHelperDelay.shouldPauseBeforeStep() ) { ULogger.debug('[UHelperDelay] 自动流程暂停(页面隐藏或计时状态非STATE_START)'); var delayRetry = window.UHelperDelay.getNextStepDelay ? window.UHelperDelay.getNextStepDelay('retry') : 3000; autoRunTimeoutId = setTimeout(runNextStep, delayRetry); return; } if (typeof handleSkipPageHangLoop === 'function' && await handleSkipPageHangLoop()) { return; } if (window.UHelperDiscussion && typeof window.UHelperDiscussion.handle === 'function') { const discussionHandled = await window.UHelperDiscussion.handle(); if (discussionHandled) { ULogger.debug("评论区已处理,等待1.5秒后尝试进入下一页..."); await sleep(1500); } } else { if (typeof handleGenericCommentSection === 'function' && await handleGenericCommentSection()) { ULogger.debug("评论区已处理,等待1.5秒后尝试进入下一页..."); await sleep(1500); } } if (await handleVocabularyCards()) { ULogger.debug("词汇卡片已处理"); } try { const vocContainer = document.querySelector("#main-content > div > div > div > div.layoutBody-container > div > div > div.vocContainer"); if (vocContainer) { await handleVocabularyCards(); } else { let recordingHandled = false; if (window.__autoPlayRecordEnabled) { recordingHandled = await handleRecordingQuestions(); if (recordingHandled) { ULogger.debug('[挂机] 录音题已处理,准备提交...'); } } ULogger.debug('[挂机] 尝试自动选择答案...'); await delayBeforeFillAnswer('before_auto_select_answers'); const foundAnswers = await autoSelectAnswers(); if (foundAnswers || recordingHandled) { ULogger.debug('[挂机] 页面内容已处理 (录音或答题),准备提交。'); ULogger.debug('[挂机] 准备提交,等待提交前延迟...'); await delayBeforeSubmit('question'); ULogger.debug('[挂机] 提交前页面切换延迟...'); await delayBeforeNavigate('before_submit_click'); const submitButton = findFooterButtonByText('提交'); if (submitButton) { submitButton.click(); await waitAfterSubmitAssessment('after_submit_click'); await delayAfterSubmit(); } else { ULogger.debug('[挂机] 未找到提交按钮,可能已提交或无需提交'); } } else { ULogger.debug('[挂机] 未找到答案且无录音操作,可能为主观题,将直接尝试导航。'); await sleep(500); } } await waitForVideosToEnd(); ULogger.debug('[挂机] 内容处理/提交完毕,查找下一步操作进行导航...'); const nextQuestionButton = findFooterButtonByText('下一题'); if (nextQuestionButton) { await waitAfterSubmitAssessment('before_next_question'); ULogger.debug('[挂机] 准备点击下一题,等待跳转延迟...'); await delayBeforeNavigate('before_next_question'); ULogger.debug('[挂机] 操作: 点击 "下一题"'); nextQuestionButton.click(); autoRunTimeoutId = setTimeout(runNextStep, 5000); return; } await waitAfterSubmitAssessment('before_navigate_sub_topic'); const navigatedToSubTopic = await navigateToNextSubTopic(); if (navigatedToSubTopic) { autoRunTimeoutId = setTimeout(runNextStep, 5000); return; } await waitAfterSubmitAssessment('before_navigate_toc'); const navigatedByToc = await navigateToNextTocItem(); if (navigatedByToc) { autoRunTimeoutId = setTimeout(runNextStep, 5000); return; } ULogger.debug('[挂机] 结束: 未找到任何可执行的导航操作。'); isAutoRunning = false; updateAutoRunButtonUI(); } catch (error) { ULogger.error('[挂机] 执行步骤时发生错误:', error); isAutoRunning = false; updateAutoRunButtonUI(); } } async function navigateToNextSubTopic() { ULogger.debug('[挂机] 尝试导航到下一个子主题 (横向Tab)...'); const tabSystems = [ { name: "子任务Tabs", containerSelector: '.pc-header-tasks-container', tabSelector: '.pc-task', activeClass: 'pc-header-task-activity' }, { name: "主任务Tabs", containerSelector: '.pc-tab-container', tabSelector: '.ant-col', activeClass: 'pc-header-tab-activity' } ]; for (const system of tabSystems) { const container = document.querySelector(system.containerSelector); if (!container) continue; const tabs = Array.from(container.querySelectorAll(system.tabSelector)); if (tabs.length <= 1) continue; const activeIndex = tabs.findIndex(tab => tab.classList.contains(system.activeClass)); if (activeIndex === -1) continue; if (activeIndex + 1 < tabs.length) { const nextTab = tabs[activeIndex + 1]; const clickable = nextTab.querySelector('.pc-tab-view-container') || nextTab; const nextTabName = (clickable.textContent || nextTab.title || '未知').trim(); ULogger.debug(`[挂机] 准备导航到下一个子主题: "${nextTabName}",等待跳转延迟...`); await delayBeforeNavigate('before_tab_click'); clickable.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(200); clickable.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); await sleep(50); clickable.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); clickable.click(); return true; } else { ULogger.debug(`[挂机] 在 ${system.name} 中已是最后一个Tab。`); } } ULogger.debug('[挂机] 未找到可切换的下一个子主题。'); return false; } async function navigateToNextTocItem() { ULogger.debug('[挂机] 🔄 正在寻找跳转路径...'); let tocItems = []; let activeIndex = -1; let nextItemIsSkipped = false; const activeElement = document.querySelector('.pc-menu-activity') || document.querySelector('li.group.active') || document.querySelector('.pc-slider-menu-node.active'); if (activeElement) { const tocContainer = activeElement.closest('.pc-slider-content-menu, .pc-slier-menu-container') || activeElement.closest('.menu--u3menu-3Xu4h') || document.querySelector('#sidemenu'); if (tocContainer) { const allItems = Array.from(tocContainer.querySelectorAll( 'div[data-role="node"], div[data-role="micro"], li.group.courseware' )); tocItems = allItems.filter(item => item.offsetParent !== null); activeIndex = tocItems.indexOf(activeElement); if (activeIndex !== -1 && activeIndex + 1 < tocItems.length) { const checkNextItem = tocItems[activeIndex + 1]; const nameEl = checkNextItem.querySelector('span') || checkNextItem; const checkName = (nameEl.textContent || '').trim(); if (typeof SkipManager !== 'undefined' && SkipManager.shouldSkip(checkName)) { ULogger.debug(`[挂机] 🛡️ 预判检测: 下一章 "${checkName}" 在跳过列表中。`); nextItemIsSkipped = true; } } } } const continueBtn = document.querySelector('.question-common-course-page .btn, .question-common-course-page a'); if (continueBtn && continueBtn.offsetParent !== null && continueBtn.textContent.includes('继续学习')) { if (nextItemIsSkipped) { ULogger.debug('[挂机] ⚠️ 下一章需跳过,因此忽略"继续学习"按钮,转为目录强制跳转。'); } else { ULogger.debug('[挂机] 👉 发现"继续学习"按钮且下一章安全,等待跳转延迟...'); await delayBeforeNavigate('before_continue_learning'); continueBtn.click(); return true; } } if (activeIndex !== -1) { let targetIndex = activeIndex + 1; while (targetIndex < tocItems.length) { const nextItem = tocItems[targetIndex]; const nameEl = nextItem.querySelector('.pc-menu-node-name') || nextItem.querySelector('span') || nextItem.querySelector('.name a') || nextItem; const rawName = nameEl.textContent || ''; const nextItemName = rawName.trim().split('\n')[0]; if (typeof SkipManager !== 'undefined' && SkipManager.shouldSkip(nextItemName)) { ULogger.debug(`[挂机] 🚫 章节 "${nextItemName}" 在跳过列表中,自动跳过...`); targetIndex++; continue; } ULogger.debug(`[挂机] 🟢 锁定目标章节: "${nextItemName}"`); ULogger.debug(`[挂机] ⏳ 等待跳转延迟后进入...`); await delayBeforeNavigate('before_toc_click'); nextItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(500); nextItem.click(); const innerSpan = nextItem.querySelector('span'); if (innerSpan) innerSpan.click(); return true; } ULogger.warn('[挂机] ⚠️ 已遍历完剩余所有目录,未找到可执行章节 (均在跳过列表中)'); } ULogger.debug('[挂机] ⚠️ 目录导航未执行,尝试点击页面底部"下一页"按钮...'); const nextBtnSelectors = [ '.next-page-btn', '.btn-next', '.next-step', '.lay-page-next', '.layout-pagination .next', '.test-bottom-next', '.ant-btn-primary', 'button', 'a', 'div.btn', 'span.btn' ]; for (let sel of nextBtnSelectors) { const btns = document.querySelectorAll(sel); for (let btn of btns) { if (btn && btn.offsetParent !== null && !btn.classList.contains('disabled')) { const text = btn.textContent.trim(); if (['下一页', '下一题', 'Next', 'Next Page', '确定', '提交'].some(k => text === k || text.includes(k))) { if (btn.className.includes('pre') || btn.className.includes('prev')) continue; if (nextItemIsSkipped) { ULogger.warn('[挂机] 🚫 警告:下一页可能是跳过章节,但目录跳转失败。暂停操作以防误入。'); return false; } ULogger.debug(`[挂机] 👉 [备用策略] 发现按钮 (${text}),等待跳转延迟...`); await delayBeforeNavigate('before_next_page_btn'); btn.click(); return true; } } } } ULogger.error('[挂机] ❌ 结束: 无法找到任何跳转路径'); return false; } function toggleAutoRun() { if (window.UHelperStudyDuration && window.UHelperStudyDuration.isEnabled()) { safeToast('⚠️ 学习时长模式已启用,请先关闭后再启动挂机模式。', 'warning'); return; } isAutoRunning = !isAutoRunning; localStorage.setItem('u-auto-running', isAutoRunning.toString()); updateAutoRunButtonUI(); if (isAutoRunning) { ULogger.debug('自动挂机已启动...'); window.isAutoModeRunning = true; if (window.UHelperDelay && typeof window.UHelperDelay.markPageEnter === 'function') { window.UHelperDelay.markPageEnter(getCurrentDelayPageType()); } runNextStep(); } else { if (autoRunTimeoutId) { clearTimeout(autoRunTimeoutId); autoRunTimeoutId = null; } window.isAutoModeRunning = false; ULogger.debug('自动挂机已手动停止。'); } } function autoResumeIfNeeded() { if (!isAutoRunning) return; ULogger.debug('[自动恢复] ♻️ 检测到刷新前处于挂机状态,准备恢复...'); window.isAutoModeRunning = true; updateAutoRunButtonUI(); showRecordNotification('⏳ 等待页面资源加载...', 'info'); let checkCount = 0; const maxChecks = 60; const waitForPageReady = () => { const hasMenu = document.querySelector('.pc-slier-menu-container') || document.querySelector('.pc-menu-node-name'); const hasActiveItem = document.querySelector('.pc-menu-activity'); const hasContent = document.querySelector('.layoutBody-container'); const isLoading = document.querySelector('.ant-spin-spinning'); if ((hasActiveItem || hasMenu || hasContent) && !isLoading) { ULogger.debug(`[自动恢复] ✅ 页面加载完毕 (耗时 ${checkCount}s),3秒后继续执行...`); showRecordNotification('🚀 页面就绪,继续挂机', 'success'); setTimeout(() => { runNextStep(); }, 3000); } else { checkCount++; if (checkCount > maxChecks) { ULogger.warn('[自动恢复] ⚠️ 等待超时,尝试强制启动...'); showRecordNotification('⚠️ 等待超时,强制继续...', 'warning'); runNextStep(); } else { ULogger.debug(`[自动恢复] 页面未完全就绪 (目录: ${!!hasMenu}, 选中项: ${!!hasActiveItem}, Loading: ${!!isLoading})... ${checkCount}/${maxChecks}`); setTimeout(waitForPageReady, 1000); } } }; setTimeout(waitForPageReady, 1000); } function handleVideo(video) { if (video.dataset.handledByScript) return; video.dataset.handledByScript = 'true'; ULogger.debug('[视频助手] 发现视频,开始处理:', video); const setPlaybackRate = () => { const targetSpeed = parseFloat(localStorage.getItem('u-video-speed') || '2.0'); if (video.playbackRate !== targetSpeed) { video.playbackRate = targetSpeed; ULogger.debug(`[视频助手] 视频倍速已设置为 ${targetSpeed}x`); } }; const attemptPlay = () => { video.muted = true; const playPromise = video.play(); if (playPromise !== undefined) { playPromise.then(() => { ULogger.debug('[视频助手] 视频已自动播放。'); setPlaybackRate(); }).catch(error => { ULogger.warn('[视频助手] 自动播放失败,可能是浏览器策略限制。等待用户交互后再次尝试。'); const playOnInteraction = () => { video.play(); setPlaybackRate(); document.body.removeEventListener('click', playOnInteraction, true); }; document.body.addEventListener('click', playOnInteraction, { once: true, capture: true }); }); } }; if (video.readyState >= 3) { attemptPlay(); } else { video.addEventListener('canplay', attemptPlay, { once: true }); } video.addEventListener('ratechange', () => { setTimeout(setPlaybackRate, 100); }); video.addEventListener('playing', setPlaybackRate); video.addEventListener('pause', () => { setTimeout(() => { const popup = document.querySelector('.question-video-popup'); if (popup && popup.offsetParent !== null) { ULogger.debug('[视频助手] 视频暂停,检测到弹窗问题,开始处理...'); handleVideoPopupQuestions(popup); } }, 200); }); } function setupVideoHandler() { document.querySelectorAll('video').forEach(handleVideo); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === 'VIDEO') { handleVideo(node); } else if (node.querySelectorAll) { node.querySelectorAll('video').forEach(handleVideo); } } }); } }); observer.observe(document.body, { childList: true, subtree: true }); ULogger.debug('已启动视频自动播放和倍速调整功能。'); } async function handleVideoPopupQuestions(popupElement) { if (popupElement.dataset.handledByRandomSelect) return; popupElement.dataset.handledByRandomSelect = 'true'; ULogger.debug('[视频弹题助手] 检测到视频中的弹窗问题,准备随机选择...'); await sleep(500); const options = popupElement.querySelectorAll('.option.isNotReview'); if (options.length > 0) { await sleep(1000 + Math.random() * 1500); const randomIndex = Math.floor(Math.random() * options.length); const randomOption = options[randomIndex]; const optionCaption = randomOption.querySelector('.caption')?.textContent.trim() || `选项 ${randomIndex + 1}`; ULogger.debug(`[视频弹题助手] 随机选择选项: ${optionCaption}`); randomOption.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(150 + Math.random() * 200); randomOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); await sleep(50 + Math.random() * 50); randomOption.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); randomOption.click(); ULogger.debug('[视频弹题助手] 已成功点击选项。'); await sleep(500 + Math.random() * 300); const confirmButton = popupElement.querySelector('button.submit-btn'); if (confirmButton && !confirmButton.disabled) { ULogger.debug('[视频弹题助手] 找到"确定"按钮,正在点击...'); confirmButton.click(); ULogger.debug('[视频弹题助手] 已点击"确定"按钮。'); } else { ULogger.debug('[视频弹题助手] 未找到或"确定"按钮不可用。'); } } else { ULogger.debug('[视频弹题助手] 未在弹窗中找到可选选项。'); } } function setupVideoPopupObserver() { const observer = new MutationObserver(() => { const popup = document.querySelector('.question-video-popup'); if (popup && popup.offsetParent === null) { if (popup.dataset.handledByRandomSelect) { ULogger.debug('[视频弹题助手] 弹窗已隐藏,重置处理标记以便下次使用。'); delete popup.dataset.handledByRandomSelect; } } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] }); ULogger.debug('已启动视频内弹窗问题状态监视器。'); } async function handleDiscussionPage() { return window.UHelperDiscussion ? window.UHelperDiscussion.handleDiscussionPage() : false; } function setupPopupHandler() { ULogger.debug('🛡️ 全局弹窗拦截器已启动 (MutationObserver版)'); const checkAndClickModal = (modalNode) => { if (!modalNode || modalNode.style?.display === 'none') return; const confirmContent = modalNode.querySelector('.ant-modal-confirm-content'); if (confirmContent && confirmContent.textContent.includes('本单元仅记录第一次作答的得分')) { const okBtn = modalNode.querySelector('.system-info-cloud-ok-button') || modalNode.querySelector('.ant-btn-primary'); if (okBtn) { ULogger.debug(`[自动确认] ⚡️ 秒杀 "本单元仅记录" 弹窗`); okBtn.click(); return; } } const confirmButton = modalNode.querySelector('.ant-btn-primary'); if (confirmButton && confirmButton.offsetParent !== null) { const rawText = confirmButton.textContent || confirmButton.innerText || ''; const buttonText = rawText.replace(/\s/g, ''); const confirmKeywords = ['确定', '确认', '我知道了', '知道了', 'OK', '好的', '继续', '提交']; const shouldClick = confirmKeywords.some(keyword => buttonText.includes(keyword)); if (shouldClick) { ULogger.debug(`[自动确认] ⚡️ 捕获通用弹窗,点击按钮: "${rawText.trim()}"`); confirmButton.click(); } } }; const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType !== 1) return; if (node.classList && node.classList.contains('ant-modal-wrap')) { checkAndClickModal(node); } else if (node.querySelector) { const modal = node.querySelector('.ant-modal-wrap'); if (modal) { checkAndClickModal(modal); } } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); document.querySelectorAll('.ant-modal-wrap').forEach(checkAndClickModal); } function setupAnswerInterceptor() { const SERVER_API = getApiUrl(API_CONFIG.ENDPOINTS.INJECT); const TARGET_URL_KEYWORD = '/course/api/v3/newExploration/submit'; function hasRecordButtonOnPage() { const btn = document.querySelector('.record-icon, .button-record, .record-fill-icon'); return btn !== null; } const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype._originalSend = originalXHRSend; XMLHttpRequest.prototype.open = function(method, url, ...args) { this._url = url; this._method = method; return originalXHROpen.apply(this, [method, url, ...args]); }; XMLHttpRequest.prototype.send = function(data) { const isTargetUrl = this._url && this._url.includes(TARGET_URL_KEYWORD); const isFeatureEnabled = window.__enableOralScoreInjection; if (isFeatureEnabled && isTargetUrl && hasRecordButtonOnPage()) { ULogger.debug('[U助手-云端] 🎤 检测到页面有录音按钮,强制进行云端改分...'); GM_xmlhttpRequest({ method: "POST", url: SERVER_API, headers: { "Content-Type": "application/json" }, data: data, onload: (response) => { if (response.status === 200) { try { const resData = JSON.parse(response.responseText); if (resData && resData.quesDatas) { showRecordNotification('☁️ 高分成功!', 'success'); const finalPayload = JSON.stringify(resData); XMLHttpRequest.prototype._originalSend.call(this, finalPayload); return; } } catch (e) { ULogger.error('[U助手-云端] 解析失败:', e); } } else { showRecordNotification('❌ 云端服务器错误', 'error'); } XMLHttpRequest.prototype._originalSend.call(this, data); }, onerror: (err) => { ULogger.error('[U助手-云端] 网络请求错误:', err); XMLHttpRequest.prototype._originalSend.call(this, data); } }); return; } if (this._url && window.__sortableAnswers__ && (this._url.includes('submit') || this._url.includes('answer'))) { try { let payload = (typeof data === 'string') ? JSON.parse(data) : data; let modified = false; if (payload && payload.quesDatas && Array.isArray(payload.quesDatas)) { payload.quesDatas.forEach((quesData) => { if (!quesData.answer) return; let answerObj = quesData.answer; let isStringified = false; if (typeof quesData.answer === 'string') { try { answerObj = JSON.parse(quesData.answer); isStringified = true; } catch (e) { return; } } if (answerObj && answerObj.children && Array.isArray(answerObj.children)) { const correctAnswers = window.__sortableAnswers__.answers; if(correctAnswers && correctAnswers.length > 0) { answerObj.children = correctAnswers.map(letter => ({ value: [letter], isDone: true })); quesData.answer = isStringified ? JSON.stringify(answerObj) : answerObj; modified = true; } } }); if (modified) { data = JSON.stringify(payload); delete window.__sortableAnswers__; ULogger.debug('[拦截器] 排序题本地修正成功'); } } } catch (e) { ULogger.error(e); } } return originalXHRSend.apply(this, [data]); }; const originalFetch = window.fetch; window.fetch = async function(url, options = {}) { return originalFetch.apply(this, [url, options]); }; ULogger.debug('[U助手] 智能拦截器 V7 (UI判定版) 已就绪'); } window.addEventListener('load', () => { setupAnswerInterceptor(); createFloatingButton(); SubmitInterceptor.init(); setupPopupHandler(); setupMultiPageNavigationListener(); initRecordingFeatures(); setupSubmitConfirmHandler(); if (window.UHelperClassicUCampus) { window.UHelperClassicUCampus.init({ apiPost: apiPost, API_CONFIG: API_CONFIG, getApiUrl: getApiUrl, safeToast: safeToast, getUserId: function () { return (window.UHelperPoints && window.UHelperPoints.getUserId && window.UHelperPoints.getUserId()) || window.userId || localStorage.getItem('userId'); }, getSelectedBankName: function () { var el = document.getElementById('online-bank-selector'); if (el && el.value) return el.value; return localStorage.getItem('selectedOnlineBank') || ''; }, queryOnlineBankAnswers: async function (params) { return new Promise(function(resolve, reject) { GM_xmlhttpRequest({ method: 'POST', url: getApiUrl(API_CONFIG.ENDPOINTS.GET_ANSWERS), headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ uid: params.uid, courseName: params.course, id: params.id, platform: params.platform, source: 'classic_ucampus_iframe' }), onload: function(res) { var data = JSON.parse(res.responseText); if (res.status >= 200 && res.status < 300 && data.status === 'success') { resolve(data.answers); } else { reject(new Error(data.message || '服务器错误: ' + res.status)); } }, onerror: function() { reject(new Error('网络请求失败')); } }); }); } }); } else { if (typeof isClassicUCampusOuterPage === 'function' && isClassicUCampusOuterPage()) { hideOuterPanelForClassicUCampus(); syncUidToClassicIframe(); } if (typeof getClassicUCampusPageRole === 'function' && getClassicUCampusPageRole() === 'unit_test_iframe_page') { setupClassicUidSyncListener(); } } if (typeof SkipManager !== 'undefined' && SkipManager.initPanel) { } setTimeout(() => { const isResumeEnabled = localStorage.getItem('u-helper-resume-after-refresh') === 'true'; const wasAutoRunning = localStorage.getItem('u-auto-running') === 'true'; ULogger.debug(`[启动检查] 上次状态: ${wasAutoRunning}, 恢复开关: ${isResumeEnabled}`); if (isResumeEnabled && wasAutoRunning) { ULogger.debug('[自动恢复] ✅ 检测到需要恢复挂机,开始等待页面加载...'); window.isAutoModeRunning = true; isAutoRunning = true; updateAutoRunButtonUI(); showRecordNotification('⏳ 等待页面资源加载...', 'info'); let checkCount = 0; const maxChecks = 30; const waitForPageReady = () => { const hasMenu = document.querySelector('.pc-menu-node-name') || document.querySelector('.pc-slier-menu-container'); const hasQuestion = document.querySelector('.question-common-abs-reply') || document.querySelector('.question-wrap'); const hasContent = document.querySelector('.layoutBody-container'); const isLoading = document.querySelector('.ant-spin-spinning'); if ((hasMenu || hasQuestion || hasContent) && !isLoading) { ULogger.debug(`[自动恢复] 🎉 页面加载完毕 (耗时 ${checkCount}s),继续挂机!`); showRecordNotification('🚀 页面就绪,继续挂机', 'success'); setTimeout(runNextStep, 1000); } else { checkCount++; if (checkCount > maxChecks) { ULogger.warn('[自动恢复] ⚠️ 等待超时,尝试强制启动...'); showRecordNotification('⚠️ 加载超时,强制尝试...', 'warning'); runNextStep(); } else { ULogger.debug(`[自动恢复] 页面未就绪 (Loading: ${!!isLoading}, Menu: ${!!hasMenu})... ${checkCount}/${maxChecks}`); setTimeout(waitForPageReady, 1000); } } }; waitForPageReady(); } else if (wasAutoRunning && !isResumeEnabled) { ULogger.debug('[自动恢复] 🛑 刷新后恢复开关未开启,停止挂机。'); localStorage.setItem('u-auto-running', 'false'); isAutoRunning = false; window.isAutoModeRunning = false; updateAutoRunButtonUI(); } }, 1500); setTimeout(() => { if (typeof showAnnouncement === 'function') { showAnnouncement(true); } }, 2000); }); function fireMouse(el, type) { if (!el) return; var rect = el.getBoundingClientRect(); var opts = { bubbles: true, cancelable: true, view: window, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, buttons: 1 }; try { el.dispatchEvent(new MouseEvent(type, opts)); } catch (_) { try { el.dispatchEvent(new Event(type, { bubbles: true, cancelable: true })); } catch (e) {} } } function normalizeOptionText(text) { return String(text || '') .replace(/\s+/g, ' ') .replace(/[,。;;]+$/g, '') .trim() .toLowerCase(); } function normalizeScoopAnswerText(text) { text = String(text || '').trim(); text = text .replace(/^\(?\d+\)?[.、)\s]+/, '') .replace(/^[A-Z][.、)\s]+/i, '') .replace(/[,。;;]+$/g, '') .trim(); var lower = text.toLowerCase(); if (/\bmake\b/.test(lower)) return 'make'; if (/\bdo\b/.test(lower)) return 'do'; return lower.replace(/\s+/g, ' ').trim(); } function normalizeScoopAnswers(rawAnswers) { var list = []; if (Array.isArray(rawAnswers)) { list = rawAnswers; } else { var raw = String(rawAnswers || ''); list = raw .split(/\n|;|;|,/) .map(function (s) { return s.trim(); }) .filter(Boolean); } return list .map(normalizeScoopAnswerText) .filter(Boolean); } async function waitForVisibleScoopMenu(timeoutMs) { var start = Date.now(); while (Date.now() - start < timeoutMs) { var menus = Array.from(document.querySelectorAll( '.ant-dropdown-menu.scoop-select, ' + '.ant-dropdown.scoop-select-dropdown .ant-dropdown-menu, ' + '.ant-select-dropdown:not(.ant-select-dropdown-hidden), ' + '.rc-virtual-list' )); for (var k = 0; k < menus.length; k++) { var menu = menus[k]; var dropdown = menu.closest('.ant-dropdown, .ant-select-dropdown'); if (dropdown && dropdown.classList.contains('ant-dropdown-hidden')) { continue; } var style = window.getComputedStyle(menu); if (style.display === 'none' || style.visibility === 'hidden') { continue; } var rect = menu.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { return menu; } } await sleep(100); } return null; } async function fillOneScoopDropdown(scoop, answer, index) { var trigger = scoop.querySelector('.ant-dropdown-trigger.user-answer, .ant-dropdown-trigger'); if (!trigger) { ULogger.warn('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空未找到 trigger,跳过'); return; } var currentEl = trigger.querySelector('.user-answer-text, .ant-select-selection-item'); var currentText = currentEl ? currentEl.textContent.trim() : ''; if (currentText && currentText !== '点击选择' && currentText !== 'Click to select' && currentText.toLowerCase() === answer.toLowerCase()) { ULogger.debug('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空已选择正确答案: ' + currentText + ',跳过'); return; } scoop.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(300); fireMouse(trigger, 'mouseover'); fireMouse(trigger, 'mousedown'); fireMouse(trigger, 'mouseup'); fireMouse(trigger, 'click'); var menu = await waitForVisibleScoopMenu(3000); if (!menu) { ULogger.warn('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空菜单未展开,重试...'); fireMouse(trigger, 'click'); menu = await waitForVisibleScoopMenu(3000); } if (!menu) { ULogger.error('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空菜单仍未展开,跳过'); return; } var options = Array.from(menu.querySelectorAll( 'li.select-option, .ant-dropdown-menu-item, .ant-select-item-option, div.ant-select-item' )); ULogger.debug('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空,目标答案: ' + answer); ULogger.debug('[在线题库][scoop-dropdown] 菜单选项: ' + options.map(function(o) { return o.textContent.trim(); }).join(', ')); var targetOption = null; for (var j = 0; j < options.length; j++) { var opt = options[j]; var optText = normalizeOptionText(opt.textContent); var ans = normalizeOptionText(answer); var menuId = opt.getAttribute('data-menu-id') || ''; if (optText === ans || optText.indexOf(ans) !== -1 || ans.indexOf(optText) !== -1 || (ans.length === 1 && menuId.toUpperCase().endsWith('-' + ans.toUpperCase()))) { targetOption = opt; break; } } if (targetOption) { fireMouse(targetOption, 'mouseover'); fireMouse(targetOption, 'mousedown'); fireMouse(targetOption, 'mouseup'); fireMouse(targetOption, 'click'); if (typeof targetOption.click === 'function') targetOption.click(); await sleep(500); var updatedText = trigger.querySelector('.user-answer-text'); var selectedText = updatedText ? updatedText.textContent.trim() : ''; ULogger.debug('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空选择完成: ' + selectedText); if (!updatedText || selectedText === '点击选择' || selectedText === 'Click to select') { ULogger.warn('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空 UI 未更新,可能选择未生效'); } } else { ULogger.error('[在线题库][scoop-dropdown] 第 ' + (index + 1) + ' 空未找到匹配选项: "' + answer + '"'); document.body.click(); await sleep(300); } } async function fillScoopDropdownAnswers(rawAnswers) { var scoops = Array.from(document.querySelectorAll( '.question-common-abs-scoop .fe-scoop[data-scoop-index], ' + '.comp-scoop-reply-dropdown-selection-overflow .fe-scoop[data-scoop-index], ' + '.question-abs-basic-scoop-content .fe-scoop[data-scoop-index], ' + '.fe-scoop[data-scoop-index]' )).filter(function (el) { return !!el.querySelector('.ant-dropdown-trigger.user-answer, .ant-dropdown-trigger'); }); scoops.sort(function (a, b) { return Number(a.getAttribute('data-scoop-index') || 0) - Number(b.getAttribute('data-scoop-index') || 0); }); var answers = normalizeScoopAnswers(rawAnswers); ULogger.debug('[在线题库][scoop-dropdown] 空位数量: ' + scoops.length); ULogger.debug('[在线题库][scoop-dropdown] 答案: ' + JSON.stringify(answers)); var count = Math.min(scoops.length, answers.length); for (var i = 0; i < count; i++) { await fillOneScoopDropdown(scoops[i], answers[i], i); await sleep(300 + Math.random() * 300); } ULogger.debug('[在线题库][scoop-dropdown] 处理完成'); } if (U_HELPER_DEBUG) { window.debugScoopDropdown = function () { var scoops = Array.from(document.querySelectorAll('.fe-scoop[data-scoop-index]')); return scoops.map(function (s, i) { var trigger = s.querySelector('.ant-dropdown-trigger'); var current = trigger && trigger.querySelector('.user-answer-text'); var hiddenOptions = Array.from(s.querySelectorAll('i p')).map(function (p) { return p.textContent.trim(); }); return { i: i, index: s.getAttribute('data-scoop-index'), hasTrigger: !!trigger, currentText: current ? current.textContent.trim() : '', hiddenOptions: hiddenOptions }; }); }; } if (U_HELPER_DEBUG) { window.testFillScoopDropdown = async function (answers) { return await fillScoopDropdownAnswers(answers || ['make', 'make', 'do', 'make']); }; } async function fillAnswersFromArray(answers) { const questions = document.querySelectorAll('.question-common-abs-reply, .question-common-abs-banked-cloze'); if (questions.length === 0) { ULogger.warn('[在线题库] 未在页面上找到题目容器。'); return; } if (questions.length !== answers.length) { ULogger.warn(`[在线题库] 警告:页面上有 ${questions.length} 道题,但获取到 ${answers.length} 个答案,可能不匹配。`); } for (let i = 0; i < answers.length; i++) { if (i >= questions.length) break; const question = questions[i]; const answerLetter = answers[i]; const options = question.querySelectorAll('.option.isNotReview, div.option'); let targetOption = null; for (const option of options) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === answerLetter) { targetOption = option; break; } } if (targetOption) { ULogger.debug(`[在线题库] 第 ${i + 1} 题,选择答案: ${answerLetter}`); targetOption.click(); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } else { ULogger.error(`[在线题库] 第 ${i + 1} 题,未找到选项: ${answerLetter}`); } } } async function fillAnswersFromArray(answers) { ULogger.debug('[在线题库] 开始填写答案,共', answers.length, '题'); const sortableWrapper = document.querySelector('#sortableListWrapper, .sortable-list-wrapper'); if (sortableWrapper) { const questions = sortableWrapper.querySelectorAll('.sortable-list-question-no'); const options = sortableWrapper.querySelectorAll('.sequence-reply-view-item-text'); const answerMap = {}; for (let i = 0; i < Math.min(answers.length, questions.length); i++) { answerMap[i] = answers[i].trim().toUpperCase(); } const hiddenInputs = sortableWrapper.querySelectorAll('input[type="hidden"], input[name*="answer"], input[name*="sequence"]'); if (hiddenInputs.length > 0) { hiddenInputs.forEach((input, idx) => { if (answerMap[idx]) { input.value = answerMap[idx]; } }); } const optionMap = new Map(); for (const option of options) { const optionText = option.textContent.trim(); const match = optionText.match(/^([A-Z])\./); if (match) { optionMap.set(match[1], option); } } const correctOrder = []; for (let i = 0; i < Math.min(answers.length, questions.length); i++) { const answerLetter = answerMap[i]; const targetQuestion = questions[i]; correctOrder.push(targetQuestion); const targetOption = optionMap.get(answerLetter); if (targetOption) { correctOrder.push(targetOption); } } while (sortableWrapper.firstChild) { sortableWrapper.removeChild(sortableWrapper.firstChild); } correctOrder.forEach((element) => { sortableWrapper.appendChild(element); }); for (let i = 0; i < Math.min(answers.length, questions.length); i++) { const answerLetter = answerMap[i]; const targetOption = optionMap.get(answerLetter); if (!targetOption) continue; const rect = targetOption.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const touchstartEvent = new TouchEvent('touchstart', { bubbles: true, cancelable: true, touches: [new Touch({ identifier: i, target: targetOption, clientX: centerX, clientY: centerY })] }); targetOption.dispatchEvent(touchstartEvent); await sleep(50); const touchendEvent = new TouchEvent('touchend', { bubbles: true, cancelable: true, changedTouches: [new Touch({ identifier: i, target: targetOption, clientX: centerX, clientY: centerY + 2 })] }); targetOption.dispatchEvent(touchendEvent); await sleep(50); } const finalEvents = ['change', 'input', 'update', 'sort']; finalEvents.forEach(eventType => { sortableWrapper.dispatchEvent(new Event(eventType, { bubbles: true })); }); window.__sortableAnswers__ = { answers: answers, answerMap: answerMap, timestamp: Date.now() }; ULogger.debug('[在线题库] 拖动排序题已完成'); await sleep(500); return; } var scoopDropdowns = Array.from(document.querySelectorAll( '.question-common-abs-scoop .fe-scoop[data-scoop-index], ' + '.comp-scoop-reply-dropdown-selection-overflow .fe-scoop[data-scoop-index], ' + '.question-abs-basic-scoop-content .fe-scoop[data-scoop-index], ' + '.fe-scoop[data-scoop-index]' )).filter(function (el) { return !!el.querySelector('.ant-dropdown-trigger.user-answer, .ant-dropdown-trigger'); }); if (scoopDropdowns.length > 0) { ULogger.debug('[在线题库][scoop-dropdown] 检测到 ' + scoopDropdowns.length + ' 个 scoop 下拉空'); await fillScoopDropdownAnswers(answers); return; } const feScoopTriggers = document.querySelectorAll('.fe-scoop[data-scoop-index]'); if (feScoopTriggers.length > 0) { const hasInputs = Array.from(feScoopTriggers).some(el => el.querySelector('input, textarea')); if (hasInputs) { ULogger.debug(`[在线题库] 检测到 ${feScoopTriggers.length} 个 fe-scoop 填空题(无下拉),交给普通填空题处理`); } } const dropdownSelectors = [ '.ant-select:not(.ant-select-disabled)', '.ant-select-selector' ]; let initialDropdownQuestions = null; let dropdownType = ''; for (const selector of dropdownSelectors) { const elements = document.querySelectorAll(selector); if (elements.length > 0) { initialDropdownQuestions = elements; dropdownType = selector; ULogger.debug(`[在线题库] 检测到 ${elements.length} 个下拉选择题 (${selector})`); break; } } if (initialDropdownQuestions && initialDropdownQuestions.length > 0) { const questionsToProcess = Math.min(initialDropdownQuestions.length, answers.length); for (let i = 0; i < questionsToProcess; i++) { const currentTriggers = document.querySelectorAll(dropdownType); if (i >= currentTriggers.length) { ULogger.error(`[在线题库] 错误:无法找到第 ${i + 1} 题,作答中止。`); break; } const trigger = currentTriggers[i]; const answerText = answers[i].trim(); const currentAnswerElement = trigger.querySelector('.user-answer-text, .ant-select-selection-item'); if (currentAnswerElement) { const currentText = currentAnswerElement.textContent.trim(); if (currentText !== '点击选择' && currentText !== 'Click to select' && currentText !== '') { if (currentText === answerText || currentText.toLowerCase() === answerText.toLowerCase() || currentText === answerText.toUpperCase()) { ULogger.debug(`[在线题库] 第 ${i + 1} 题已选择正确答案: ${currentText}`); continue; } } } ULogger.debug(`[在线题库] 第 ${i + 1} 题,准备选择: ${answerText}`); await simulateHumanBehavior(trigger); trigger.click(); await new Promise(resolve => setTimeout(resolve, 600)); const menuSelectors = [ '.ant-dropdown-menu.scoop-select', '.ant-select-dropdown:not(.ant-select-dropdown-hidden)', '.rc-virtual-list', '.ant-select-item-option' ]; let targetOption = null; for (const menuSelector of menuSelectors) { const allMenus = document.querySelectorAll(menuSelector); for (const menu of allMenus) { const menuStyle = window.getComputedStyle(menu); if (menuStyle.display === 'none' || menuStyle.visibility === 'hidden') { continue; } const optionSelectors = [ 'li.select-option', '.ant-select-item-option', 'div.ant-select-item' ]; for (const optSelector of optionSelectors) { const options = menu.querySelectorAll(optSelector); if (options.length === 0) continue; targetOption = Array.from(options).find(opt => { const text = opt.textContent.trim(); const innerText = opt.innerText?.trim() || ''; if (text.toLowerCase() === answerText.toLowerCase()) return true; if (innerText.toLowerCase() === answerText.toLowerCase()) return true; if (answerText.length === 1) { const answerLetter = answerText.toUpperCase(); if (text === answerLetter || text.startsWith(answerLetter + '.') || text.startsWith(answerLetter + ' ')) { return true; } } if (answerText.length === 1) { const menuId = opt.getAttribute('data-menu-id'); if (menuId && menuId.endsWith('-' + answerText.toUpperCase())) { return true; } } if (text.includes(answerText) || answerText.includes(text)) { return true; } return false; }); if (targetOption) break; } if (targetOption) break; } if (targetOption) break; } if (targetOption) { ULogger.debug(`[在线题库] 找到选项: ${targetOption.textContent.trim()}`); await simulateHumanBehavior(targetOption); targetOption.click(); await new Promise(r => setTimeout(r, 400)); const updatedTriggers = document.querySelectorAll(dropdownType); if (i < updatedTriggers.length) { const updatedTrigger = updatedTriggers[i]; const updatedAnswerElement = updatedTrigger.querySelector('.user-answer-text, .ant-select-selection-item'); if (updatedAnswerElement) { const selectedText = updatedAnswerElement.textContent.trim(); if (selectedText !== '点击选择' && selectedText !== 'Click to select') { ULogger.debug(`[在线题库] ✓ 第 ${i + 1} 题选择成功: ${selectedText}`); } } } } else { ULogger.error(`[在线题库] 第 ${i + 1} 题未找到选项: ${answerText}`); ULogger.debug(`[在线题库] 正在查找的答案文本: "${answerText}"`); const escEvent = new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true }); document.dispatchEvent(escEvent); await new Promise(r => setTimeout(r, 300)); } } ULogger.debug('[在线题库] 所有下拉选择题已完成'); return; } const fillInInputs = document.querySelectorAll('.fe-scoop input, .comp-abs-input input, textarea.question-inputbox-input, .question-inputbox-input, textarea.question-textarea-content'); if (fillInInputs.length > 0) { ULogger.debug(`[在线题库] 检测到 ${fillInInputs.length} 个填空题`); for (let i = 0; i < fillInInputs.length; i++) { if (i >= answers.length) break; const input = fillInInputs[i]; const answer = answers[i]; if (input.value === answer) { continue; } await simulateHumanBehavior(input); input.focus(); const isTextarea = input.tagName.toLowerCase() === 'textarea'; const answerWithNewline = answer + '\n'; try { if (isTextarea) { const nativeTextareaSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeTextareaSetter.call(input, answerWithNewline); } else { const nativeInputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; nativeInputSetter.call(input, answerWithNewline); } } catch (e) { input.value = answerWithNewline; } input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } return; } const allOptions = document.querySelectorAll('.option.isNotReview, div.option'); ULogger.debug(`[在线题库] 检测到 ${allOptions.length} 个选项,识别为选择题`); const questionGroups = []; let currentGroup = []; const firstCaption = allOptions[0]?.querySelector('.caption'); const firstLetter = firstCaption ? firstCaption.textContent.trim() : 'A'; const isSpecialType = !['A', 'B', 'C', 'D', 'E', 'F'].includes(firstLetter); ULogger.debug(`[在线题库] 题型检测: ${isSpecialType ? '特殊题型(' + firstLetter + ')' : '标准题型(A/B/C/D)'}`); if (isSpecialType) { for (let i = 0; i < allOptions.length; i += 2) { const group = []; if (allOptions[i]) group.push(allOptions[i]); if (allOptions[i + 1]) group.push(allOptions[i + 1]); if (group.length > 0) { questionGroups.push(group); } } ULogger.debug(`[在线题库] 特殊题型分组: ${questionGroups.length} 道题`); } else { allOptions.forEach((option) => { const caption = option.querySelector('.caption'); if (caption) { const letter = caption.textContent.trim(); if (letter === 'A' && currentGroup.length > 0) { questionGroups.push([...currentGroup]); currentGroup = []; } currentGroup.push(option); } }); if (currentGroup.length > 0) { questionGroups.push(currentGroup); } ULogger.debug(`[在线题库] 标准题型分组: ${questionGroups.length} 道题`); } if (questionGroups.length > 0) { if (questionGroups.length !== answers.length) { ULogger.warn(`[在线题库] 警告:识别出 ${questionGroups.length} 道题,但获取到 ${answers.length} 个答案。`); } const questionsToProcess = Math.min(questionGroups.length, answers.length); for (let i = 0; i < questionsToProcess; i++) { const optionsGroup = questionGroups[i]; const rawAnswer = answers[i]; let answersToSelect = []; if (rawAnswer.includes(',')) { answersToSelect = rawAnswer.split(',').map(letter => letter.trim().toUpperCase()); } else { answersToSelect = [rawAnswer.trim().toUpperCase()]; } for (const answerLetter of answersToSelect) { let targetOption = null; for (const option of optionsGroup) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === answerLetter) { targetOption = option; break; } } if (targetOption) { if (targetOption.classList.contains('selected')) { continue; } await simulateHumanBehavior(targetOption); targetOption.click(); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } } } return; } } function getCurrentPathFromData() { try { const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span'); if (breadcrumbs.length === 0) { ULogger.warn('未能找到面包屑(breadcrumb)元素, 路径可能不完整。'); } const navigationPath = Array.from(breadcrumbs).map(span => span.textContent.trim()); const activeTopicElement = document.querySelector('.pc-header-tab-activity'); const activeTopicName = activeTopicElement ? activeTopicElement.textContent.trim() : ''; const subTopicElement = document.querySelector('.pc-task.pc-header-task-activity'); const subTopicName = subTopicElement ? subTopicElement.textContent.trim() : ''; const pathParts = [...navigationPath]; if (activeTopicName && !pathParts.includes(activeTopicName)) { pathParts.push(activeTopicName); } if (subTopicName && !pathParts.includes(subTopicName)) { pathParts.push(subTopicName); } const fullPath = pathParts.join(' > '); ULogger.debug('生成的当前页面路径:', fullPath); return fullPath; } catch (error) { ULogger.error('获取当前页面路径时出错:', error); return null; } } async function handleGenericCommentSection() { return window.UHelperDiscussion ? window.UHelperDiscussion.handleGenericCommentSection() : false; } function analyzePageQuestions() { const result = { count: 1, type: 'unknown', elements: [] }; ULogger.debug(`[多页教材] 🔍 开始分析页面题目结构...`); const sortableWrapper = document.querySelector('#sortableListWrapper, .sortable-list-wrapper'); if (sortableWrapper) { const sortableItems = sortableWrapper.querySelectorAll('.sortable-list-question-no'); result.count = sortableItems.length > 0 ? sortableItems.length : 1; result.type = 'sorting'; result.elements = [sortableWrapper]; ULogger.debug(`[多页教材] ✅ 通过排序容器检测到 1 个拖动排序题,需要 ${result.count} 个答案。`); return result; } const classicSortingContainer = document.querySelector('div.sequence-pc--sequence-container-33roc, div[class*="sequence-pc--sequence-container"]'); if (classicSortingContainer) { const sortableItems = classicSortingContainer.querySelectorAll('div.sequence-pc-card--item-3CfJy, div[class*="sequence-pc-card--item"]'); result.count = sortableItems.length > 0 ? sortableItems.length : 1; result.type = 'classic_sorting'; result.elements = [classicSortingContainer]; ULogger.debug(`[多页教材] ✅ 检测到普通U校园版拖动排序题,需要 ${result.count} 个答案。`); return result; } const scoopDropdowns = Array.from(document.querySelectorAll( '.question-common-abs-scoop .fe-scoop[data-scoop-index], ' + '.comp-scoop-reply-dropdown-selection-overflow .fe-scoop[data-scoop-index], ' + '.question-abs-basic-scoop-content .fe-scoop[data-scoop-index], ' + '.fe-scoop[data-scoop-index]' )).filter(function (el) { return !!el.querySelector('.ant-dropdown-trigger.user-answer, .ant-dropdown-trigger'); }); if (scoopDropdowns.length > 0) { result.count = scoopDropdowns.length; result.type = 'scoop-dropdown'; result.elements = scoopDropdowns; ULogger.debug('[多页教材] ✅ 检测到 scoop 下拉选择题,需要 ' + result.count + ' 个答案。'); return result; } const fillInInputs = document.querySelectorAll('input.fill-blank--bc-input-DelG1, .fe-scoop input:not([type="hidden"]), .comp-abs-input input, textarea.question-inputbox-input, .question-inputbox-input, textarea.question-textarea-content, textarea.writing--textarea-36VPs, textarea.scoopFill_textarea'); if (fillInInputs.length > 0) { result.count = fillInInputs.length; result.type = 'fill_in'; result.elements = Array.from(fillInInputs); ULogger.debug(`[多页教材] ✅ 通过输入框检测到 ${result.count} 个填空题`); return result; } const itestSections = document.querySelectorAll('.itest-section'); if (itestSections.length > 0) { const allRadioInputs = document.querySelectorAll('.itest-danxuan input[type="radio"]'); const allTextInputs = document.querySelectorAll('.blankinput'); const totalQuestions = new Set(); allRadioInputs.forEach(input => { const qindex = input.getAttribute('qindex'); if (qindex) totalQuestions.add(qindex); }); allTextInputs.forEach(input => { const qindex = input.getAttribute('qindex'); if (qindex) totalQuestions.add(qindex); }); result.count = totalQuestions.size; result.type = 'itest_mixed'; result.elements = [document.querySelector('#all-content') || document.body]; ULogger.debug(`[多页教材] ✅ 检测到itest混合题型,包含 ${result.count} 个题目`); ULogger.debug(`[多页教材] 单选题数量: ${allRadioInputs.length}, 填空题数量: ${allTextInputs.length}`); return result; } const multipleChoiceContainers = document.querySelectorAll('.MultipleChoice--checkbox-item-34A_-'); if (multipleChoiceContainers.length > 0) { result.count = 1; result.type = 'multiple_choice'; result.elements = [document.querySelector('.questions--question-3Yw9p') || document.body]; ULogger.debug(`[多页教材] ✅ 检测到普通U校园版多选题,包含 ${multipleChoiceContainers.length} 个选项`); return result; } const classicChoiceContainers = document.querySelectorAll('ul[class*="single-choice"]'); if (classicChoiceContainers.length > 0) { result.count = classicChoiceContainers.length; result.type = 'classic_choice'; result.elements = Array.from(classicChoiceContainers); ULogger.debug(`[多页教材] ✅ 检测到普通U校园版 ${result.count} 个单选题`); return result; } const allOptions = document.querySelectorAll('.option.isNotReview, div.option'); if (allOptions.length > 0) { const questionGroups = []; let currentGroup = []; const firstCaption = allOptions[0]?.querySelector('.caption'); const firstLetter = firstCaption ? firstCaption.textContent.trim() : 'A'; const isSpecialType = !['A', 'B', 'C', 'D', 'E', 'F'].includes(firstLetter); if (isSpecialType) { for (let i = 0; i < allOptions.length; i += 2) { const group = [allOptions[i], allOptions[i + 1]].filter(Boolean); if (group.length > 0) questionGroups.push(group); } result.type = 'special_choice'; } else { allOptions.forEach((option) => { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === 'A' && currentGroup.length > 0) { questionGroups.push([...currentGroup]); currentGroup = []; } currentGroup.push(option); }); if (currentGroup.length > 0) questionGroups.push(currentGroup); result.type = 'standard_choice'; } if (questionGroups.length > 0) { result.count = questionGroups.length; result.elements = questionGroups; ULogger.debug(`[多页教材] ✅ 通过选择题分组检测到 ${result.count} 个题目 (${result.type})`); return result; } } const questionContainers = document.querySelectorAll('.question-common-abs-reply, .question-common-abs-banked-cloze'); if (questionContainers.length > 0) { result.count = questionContainers.length; result.type = 'containers'; result.elements = Array.from(questionContainers); ULogger.debug(`[多页教材] ⚠️ 回退到容器检测,找到 ${result.count} 个题目容器`); return result; } ULogger.warn(`[多页教材] ⚠️ 无法准确检测到页面题目,默认为1个`); return result; } function getCurrentPageQuestionCount() { return analyzePageQuestions().count; } function getCurrentPageAnswersForMultiPage(allAnswers) { const sourceAnswers = (multiPageMode.totalAnswers && multiPageMode.totalAnswers.length > 0) ? multiPageMode.totalAnswers : (Array.isArray(allAnswers) ? allAnswers : []); const analysis = analyzePageQuestions(); const count = Math.max(1, analysis.count || getCurrentPageQuestionCount() || 1); const start = Number(multiPageMode.pageIndex || 0); const end = start + count; const currentAnswers = sourceAnswers.slice(start, end); ULogger.debug('[多页教材] 当前页答案切片:', { pageIndex: start, count, end, currentAnswers, sourceTotal: sourceAnswers.length }); return { start, end, count, currentAnswers }; } async function fillAnswersForMultiPage(answers) { const questionAnalysis = analyzePageQuestions(); const { count, type, elements } = questionAnalysis; ULogger.debug('[多页教材] fillAnswersForMultiPage 收到答案:', answers); ULogger.debug(`[多页教材] 📝 开始填写 ${answers.length} 个答案到 ${count} 个 ${type} 类型的题目`); if (answers.length < count) { ULogger.warn(`[多页教材] ⚠️ 答案数量 (${answers.length}) 与题目数量 (${count}) 不匹配`); } const questionsToProcess = Math.min(count, answers.length); switch (type) { case 'sorting': ULogger.debug(`[多页教材] 检测到排序题,使用在线题库逻辑处理`); await fillAnswersFromArray(answers); break; case 'classic_sorting': ULogger.debug(`[多页教材] 检测到普通U校园版排序题`); await fillClassicSortingQuestions(elements[0], answers); break; case 'containers': await fillContainerQuestions(elements, answers, questionsToProcess); break; case 'scoop-dropdown': ULogger.debug('[多页教材] 检测到 scoop 下拉选择题'); await fillScoopDropdownAnswers(answers); break; case 'fill_in': await fillInputQuestions(elements, answers, questionsToProcess); break; case 'standard_choice': case 'special_choice': await fillChoiceQuestions(elements, answers, questionsToProcess, type === 'special_choice'); break; case 'classic_choice': await fillClassicChoiceQuestions(elements, answers, questionsToProcess); break; case 'multiple_choice': ULogger.debug(`[多页教材] 检测到普通U校园版多选题`); await fillMultipleChoiceQuestions(elements[0], answers[0]); break; case 'itest_mixed': ULogger.debug(`[多页教材] 检测到itest混合题型`); await fillItestMixedQuestions(elements[0], answers); break; default: ULogger.warn(`[多页教材] ⚠️ 未知题目类型: ${type},尝试使用原始填答逻辑`); await fillAnswersFromArray(answers); break; } ULogger.debug(`[多页教材] ✅ 完成填写 ${questionsToProcess} 个题目`); } async function fillItestMixedQuestions(questionElement, answers) { ULogger.debug(`[多页教材] 开始处理itest混合题型,答案数量:`, answers.length); const allInputs = []; const radioInputs = document.querySelectorAll('.itest-danxuan input[type="radio"]'); radioInputs.forEach(input => { const qindex = parseInt(input.getAttribute('qindex')); if (qindex && !isNaN(qindex)) { if (!allInputs[qindex - 1]) { allInputs[qindex - 1] = { type: 'radio', elements: [] }; } allInputs[qindex - 1].elements.push(input); } }); const textInputs = document.querySelectorAll('.blankinput'); textInputs.forEach(input => { const qindex = parseInt(input.getAttribute('qindex')); if (qindex && !isNaN(qindex)) { allInputs[qindex - 1] = { type: 'text', elements: [input] }; } }); ULogger.debug(`[多页教材] 收集到 ${allInputs.filter(Boolean).length} 个题目`); for (let i = 0; i < Math.min(allInputs.length, answers.length); i++) { const questionData = allInputs[i]; const answer = answers[i]; if (!questionData || !answer) continue; ULogger.debug(`[多页教材] 处理第 ${i + 1} 题,类型: ${questionData.type},答案:`, answer); if (questionData.type === 'radio') { await fillItestRadioQuestion(questionData.elements, answer, i + 1); } else if (questionData.type === 'text') { await fillItestTextQuestion(questionData.elements[0], answer, i + 1); } if (window.UHelperDelay && typeof window.UHelperDelay.beforeFillAnswer === 'function') { await window.UHelperDelay.beforeFillAnswer('itest-mixed'); } else { await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 200)); } } ULogger.debug('[多页教材] itest混合题型处理完成'); } async function fillItestRadioQuestion(radioElements, answer, questionNum) { ULogger.debug(`[多页教材] 处理第 ${questionNum} 题单选题,答案: ${answer}`); let targetLetter = ''; if (typeof answer === 'string') { const letterMatch = answer.match(/[A-Z]/i); if (letterMatch) { targetLetter = letterMatch[0].toUpperCase(); } } if (!targetLetter) { ULogger.warn(`[多页教材] 第 ${questionNum} 题无法解析答案: ${answer}`); return; } for (const radio of radioElements) { const label = radio.closest('label'); if (label) { const labelText = label.textContent.trim(); const optionMatch = labelText.match(/^\s*([A-Z])\./); if (optionMatch && optionMatch[1] === targetLetter) { ULogger.debug(`[多页教材] 第 ${questionNum} 题选择选项 ${targetLetter}`); radio.checked = true; radio.dispatchEvent(new Event('change', { bubbles: true })); radio.dispatchEvent(new Event('click', { bubbles: true })); break; } } } } async function fillItestTextQuestion(textInput, answer, questionNum) { ULogger.debug(`[多页教材] 处理第 ${questionNum} 题填空题,答案: ${answer}`); if (textInput && answer) { const answerWithNewline = answer.toString() + '\n'; textInput.value = answerWithNewline; textInput.dispatchEvent(new Event('input', { bubbles: true })); textInput.dispatchEvent(new Event('change', { bubbles: true })); ULogger.debug(`[多页教材] 第 ${questionNum} 题填入答案: ${answer} (已添加换行符)`); } } async function fillMultipleChoiceQuestions(questionElement, answer) { ULogger.debug(`[多页教材] 开始处理多选题,答案:`, answer); let answerString = answer; if (Array.isArray(answer)) { answerString = answer.join(','); } else if (typeof answer === 'object') { answerString = JSON.stringify(answer); } ULogger.debug('处理后的答案字符串:', answerString); const options = document.querySelectorAll('.MultipleChoice--checkbox-item-34A_-'); ULogger.debug('找到的选项数量:', options.length); const letterMatches = answerString.match(/[A-Z]/gi); if (!letterMatches || letterMatches.length === 0) { ULogger.warn('[多页教材] 未能从答案中提取到有效字母'); return; } ULogger.debug('需要选择的选项:', letterMatches); for (const option of options) { const optLabel = option.querySelector('.MultipleChoice--checkbox-opt-2F4xY'); if (optLabel) { const letter = optLabel.textContent.trim().replace('.', ''); if (letterMatches.includes(letter.toUpperCase())) { ULogger.debug(`[多页教材] 选择选项 ${letter}`); const checkbox = option.querySelector('input[type="checkbox"]'); if (checkbox) { checkbox.checked = true; checkbox.dispatchEvent(new Event('change', { bubbles: true })); checkbox.dispatchEvent(new Event('click', { bubbles: true })); if (window.UHelperDelay && typeof window.UHelperDelay.beforeFillAnswer === 'function') { await window.UHelperDelay.beforeFillAnswer('multiple-choice'); } else { await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300)); } } } } } ULogger.debug('[多页教材] 多选题处理完成'); } async function fillContainerQuestions(containers, answers, questionsToProcess) { for (let i = 0; i < questionsToProcess; i++) { const container = containers[i]; const answer = answers[i]; const options = container.querySelectorAll('.option.isNotReview, div.option'); if (options.length > 0) { let targetOption = null; for (const option of options) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === answer.trim()) { targetOption = option; break; } } if (targetOption && !targetOption.classList.contains('selected')) { ULogger.debug(`[多页教材] 第 ${i + 1} 题选择: ${answer}`); await simulateHumanBehavior(targetOption); targetOption.click(); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } } } } async function fillInputQuestions(inputs, answers, questionsToProcess) { for (let i = 0; i < questionsToProcess; i++) { const input = inputs[i]; const answer = answers[i]; if (input.value === answer) { continue; } ULogger.debug(`[多页教材] 第 ${i + 1} 题填写: ${answer}`); await simulateHumanBehavior(input); input.focus(); const isTextarea = input.tagName.toLowerCase() === 'textarea'; const answerWithNewline = answer + '\n'; try { if (isTextarea) { const nativeTextareaSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeTextareaSetter.call(input, answerWithNewline); } else { const nativeInputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; nativeInputSetter.call(input, answerWithNewline); } } catch (e) { input.value = answerWithNewline; } input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } } async function fillDropdownQuestions(triggers, answers, questionsToProcess) { for (let i = 0; i < questionsToProcess; i++) { const trigger = triggers[i]; const answer = answers[i]; ULogger.debug(`[多页教材] 第 ${i + 1} 题下拉选择: ${answer}`); const currentAnswerElement = trigger.querySelector('.user-answer-text, .ant-select-selection-item'); if (currentAnswerElement) { const currentText = currentAnswerElement.textContent.trim(); if (currentText === answer.trim() || currentText.toLowerCase() === answer.toLowerCase()) { ULogger.debug(`[多页教材] 第 ${i + 1} 题已选择正确答案: ${currentText}`); continue; } } await simulateHumanBehavior(trigger); trigger.click(); await new Promise(resolve => setTimeout(resolve, 600)); const menuSelectors = [ '.ant-dropdown-menu.scoop-select', '.ant-select-dropdown:not(.ant-select-dropdown-hidden)', '.rc-virtual-list' ]; let targetOption = null; for (const menuSelector of menuSelectors) { const allMenus = document.querySelectorAll(menuSelector); for (const menu of allMenus) { if (window.getComputedStyle(menu).display === 'none') continue; const options = menu.querySelectorAll('li.select-option, .ant-select-item-option, div.ant-select-item'); targetOption = Array.from(options).find(opt => { const text = opt.textContent.trim(); return text.toLowerCase() === answer.toLowerCase() || text === answer || (answer.length === 1 && (text === answer.toUpperCase() || text.startsWith(answer.toUpperCase() + '.'))); }); if (targetOption) break; } if (targetOption) break; } if (targetOption) { ULogger.debug(`[多页教材] 找到选项: ${targetOption.textContent.trim()}`); await simulateHumanBehavior(targetOption); targetOption.click(); await new Promise(r => setTimeout(r, 400)); } else { ULogger.error(`[多页教材] 第 ${i + 1} 题未找到选项: ${answer}`); } } } async function fillChoiceQuestions(questionGroups, answers, questionsToProcess, isSpecialType) { for (let i = 0; i < questionsToProcess; i++) { const optionsGroup = questionGroups[i]; const answer = answers[i]; ULogger.debug(`[多页教材] 第 ${i + 1} 题选择 (${isSpecialType ? '特殊题型' : '标准题型'}): ${answer}`); let answersToSelect = []; if (answer.includes(',')) { answersToSelect = answer.split(',').map(letter => letter.trim().toUpperCase()); } else { answersToSelect = [answer.trim().toUpperCase()]; } for (const answerLetter of answersToSelect) { let targetOption = null; for (const option of optionsGroup) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === answerLetter) { targetOption = option; break; } } if (targetOption && !targetOption.classList.contains('selected')) { await simulateHumanBehavior(targetOption); targetOption.click(); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } } } } async function fillClassicSortingQuestions(container, answers) { ULogger.debug(`[多页教材] 开始处理普通U校园版排序题,答案:`, answers); const sortableItems = container.querySelectorAll('div.sequence-pc-card--item-3CfJy, div[class*="sequence-pc-card--item"]'); if (sortableItems.length === 0) { ULogger.warn('[多页教材] 未找到可排序的项目'); return; } const answerMap = {}; for (let i = 0; i < answers.length; i++) { answerMap[i] = answers[i].trim().toUpperCase(); } const optionMap = new Map(); sortableItems.forEach(item => { const captionDiv = item.querySelector('div.sequence-pc-card--caption-item-1Z3e-, div[class*="sequence-pc-card--caption-item"]'); if (captionDiv) { const letter = captionDiv.textContent.trim().replace('.', ''); optionMap.set(letter, item); ULogger.debug(`[多页教材] 找到排序项: ${letter}`); } }); const rows = container.querySelectorAll('table.sequence-pc--sequence-table-1vFDc tbody tr, table[class*="sequence-pc--sequence-table"] tbody tr'); if (rows.length === 0) { ULogger.warn('[多页教材] 未找到表格行'); return; } const correctOrder = []; for (let i = 0; i < answers.length; i++) { const answerLetter = answerMap[i]; for (const row of rows) { const captionDiv = row.querySelector('div.sequence-pc-card--caption-item-1Z3e-, div[class*="sequence-pc-card--caption-item"]'); if (captionDiv) { const letter = captionDiv.textContent.trim().replace('.', ''); if (letter === answerLetter) { correctOrder.push(row); break; } } } } const tbody = container.querySelector('table.sequence-pc--sequence-table-1vFDc tbody, table[class*="sequence-pc--sequence-table"] tbody'); if (!tbody) { ULogger.warn('[多页教材] 未找到 tbody 元素'); return; } while (tbody.firstChild) { tbody.removeChild(tbody.firstChild); } correctOrder.forEach((row) => { tbody.appendChild(row); }); for (let i = 0; i < answers.length; i++) { const answerLetter = answerMap[i]; const targetOption = optionMap.get(answerLetter); if (!targetOption) continue; const rect = targetOption.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const touchstartEvent = new TouchEvent('touchstart', { bubbles: true, cancelable: true, touches: [new Touch({ identifier: i, target: targetOption, clientX: centerX, clientY: centerY })] }); targetOption.dispatchEvent(touchstartEvent); await sleep(50); const touchendEvent = new TouchEvent('touchend', { bubbles: true, cancelable: true, changedTouches: [new Touch({ identifier: i, target: targetOption, clientX: centerX, clientY: centerY + 2 })] }); targetOption.dispatchEvent(touchendEvent); await sleep(50); } const finalEvents = ['change', 'input', 'update', 'sort']; finalEvents.forEach(eventType => { container.dispatchEvent(new Event(eventType, { bubbles: true })); }); window.__sortableAnswers__ = { answers: answers, answerMap: answerMap, timestamp: Date.now() }; ULogger.debug('[多页教材] 普通U校园版排序题处理完成'); await sleep(500); } async function fillClassicChoiceQuestions(choiceContainers, answers, questionsToProcess) { for (let i = 0; i < questionsToProcess; i++) { const container = choiceContainers[i]; const answer = answers[i]; ULogger.debug(`[多页教材] 第 ${i + 1} 题选择 (普通版): ${answer}`); const cleanAnswer = answer.trim().toLowerCase(); const labels = container.querySelectorAll('li label'); let targetInput = null; let targetLetter = null; const isLetterAnswer = cleanAnswer.length === 1 && /^[a-z]$/i.test(cleanAnswer); if (isLetterAnswer) { const answerLetter = cleanAnswer.toUpperCase(); for (const label of labels) { const input = label.querySelector('input[type="radio"]'); const indexSpan = label.querySelector('span[class*="index"]'); if (input && indexSpan) { const letter = indexSpan.textContent.trim().replace('.', ''); if (letter === answerLetter) { targetInput = input; targetLetter = letter; ULogger.debug(`[多页教材] 通过字母匹配找到选项 ${letter}`); break; } } } } else { for (const label of labels) { const input = label.querySelector('input[type="radio"]'); const indexSpan = label.querySelector('span[class*="index"]'); const contentDiv = label.querySelector('div.html-view[class*="content"]'); if (input && indexSpan && contentDiv) { const letter = indexSpan.textContent.trim().replace('.', ''); const contentText = contentDiv.textContent.trim().toLowerCase(); if (contentText === cleanAnswer || contentText.includes(cleanAnswer) || cleanAnswer.includes(contentText)) { targetInput = input; targetLetter = letter; ULogger.debug(`[多页教材] 通过内容匹配找到选项 ${letter}`); break; } } } } if (targetInput && !targetInput.checked) { await simulateHumanBehavior(targetInput); targetInput.checked = true; targetInput.dispatchEvent(new Event('change', { bubbles: true })); targetInput.dispatchEvent(new Event('click', { bubbles: true })); ULogger.debug(`[多页教材] 已选中选项 ${targetLetter}`); await new Promise(r => setTimeout(r, 200 + Math.random() * 200)); } else if (targetInput && targetInput.checked) { ULogger.debug(`[多页教材] 选项 ${targetLetter} 已经被选中`); } else { ULogger.warn(`[多页教材] 未找到匹配的选项: ${answer}`); } } } function updateMultiPageStatus() { const statusDiv = document.getElementById('multi-page-status'); const activeSpan = document.getElementById('multi-page-active'); const indexSpan = document.getElementById('multi-page-index'); const totalSpan = document.getElementById('multi-page-total'); if (!statusDiv || !activeSpan || !indexSpan || !totalSpan) return; if (multiPageMode.isActive) { statusDiv.style.display = 'block'; activeSpan.textContent = '已启用'; activeSpan.style.color = '#28a745'; indexSpan.textContent = `已用答案: ${multiPageMode.pageIndex}`; totalSpan.textContent = multiPageMode.totalAnswers.length; } else { statusDiv.style.display = 'none'; } } function setupMultiPageNavigationListener() { ULogger.debug('[多页教材] 设置导航监听器...'); document.addEventListener('click', (event) => { const target = event.target; const isNextButton = target && ( target.textContent?.includes('下一题') || target.textContent?.includes('Next') || target.textContent?.includes('下一页') || target.textContent?.includes('继续') || target.classList?.contains('next-btn') || target.classList?.contains('btn-next') || target.classList?.contains('next') || target.closest('button')?.textContent?.includes('下一题') || target.closest('button')?.textContent?.includes('Next') || target.closest('.btn')?.textContent?.includes('下一题') ); if (isNextButton && multiPageMode.isActive) { ULogger.debug('[多页教材] 检测到下一题按钮点击。页面索引已在答题后更新,当前索引:', multiPageMode.pageIndex); ULogger.debug('[多页教材] 等待页面内容更新...'); } }); window.multiPageNext = function() { if (multiPageMode.isActive) { ULogger.debug(`[多页教材] 手动触发答题,当前索引: ${multiPageMode.pageIndex}`); autoSelectAnswers(); } else { ULogger.debug('[多页教材] 当前不在多页模式,无法使用此功能'); } }; window.multiPageReset = function() { if (multiPageMode.isActive) { multiPageMode.pageIndex = 0; ULogger.debug('[多页教材] 已重置页面索引为 0'); updateMultiPageStatus(); } }; window.multiPageStatus = function() { ULogger.debug('[多页教材] 当前状态:', { isActive: multiPageMode.isActive, exerciseId: multiPageMode.exerciseId, pageIndex: multiPageMode.pageIndex, totalAnswers: multiPageMode.totalAnswers.length, allAnswers: multiPageMode.totalAnswers }); if (multiPageMode.isActive) { const questionAnalysis = analyzePageQuestions(); ULogger.debug('[多页教材] 当前页面分析:', questionAnalysis); const remainingAnswers = multiPageMode.totalAnswers.slice(multiPageMode.pageIndex); ULogger.debug('[多页教材] 剩余答案:', remainingAnswers); } }; window.multiPageDebug = function() { ULogger.debug('=== 多页教材调试信息 ==='); const analysis = analyzePageQuestions(); ULogger.debug('页面题目分析:', analysis); ULogger.debug('元素统计:'); ULogger.debug('- 题目容器:', document.querySelectorAll('.question-common-abs-reply, .question-common-abs-banked-cloze').length); ULogger.debug('- 填空输入框:', document.querySelectorAll('.fe-scoop input:not([type="hidden"]), textarea.question-inputbox-input').length); ULogger.debug('- 下拉框:', document.querySelectorAll('.fe-scoop[data-scoop-index], .ant-dropdown-trigger').length); ULogger.debug('- 选择题选项:', document.querySelectorAll('.option.isNotReview, div.option').length); ULogger.debug('- A选项数量:', Array.from(document.querySelectorAll('.option.isNotReview, div.option')).filter(opt => { const caption = opt.querySelector('.caption'); return caption && caption.textContent.trim() === 'A'; }).length); if (multiPageMode.isActive) { ULogger.debug('多页模式状态:', multiPageMode); ULogger.debug('下次将使用的答案:', multiPageMode.totalAnswers.slice(multiPageMode.pageIndex, multiPageMode.pageIndex + analysis.count)); } ULogger.debug('=== 调试信息结束 ==='); return { analysis, multiPageMode, nextAnswers: multiPageMode.isActive ? multiPageMode.totalAnswers.slice(multiPageMode.pageIndex, multiPageMode.pageIndex + analysis.count) : [] }; }; ULogger.debug('[多页教材] 导航监听器设置完成。可使用 multiPageNext() 手动切换到下一页'); } function startAutoRefresh() { return window.UHelperAutoRefresh.start(); } function stopAutoRefresh() { return window.UHelperAutoRefresh.stop(); } function showRefreshNotification(message, type) { return window.UHelperAutoRefresh.showNotification(message, type); } function initAutoRefresh() { if (window.UHelperAutoRefresh && typeof window.UHelperAutoRefresh.initAutoRefresh === 'function') { window.UHelperAutoRefresh.initAutoRefresh(); } } function setupPopupInterception() { const originalAlert = window.alert; const originalConfirm = window.confirm; const originalPrompt = window.prompt; window.alert = function(message) { ULogger.debug('[弹窗拦截] alert弹窗:', message); if (message && !message.includes('匹配成功') && !message.includes('不匹配') && ( message.includes('在线题库') || message.includes('查询失败') || message.includes('未找到') || message.includes('在该题库中未找到此练习的答案') ) ) { ULogger.debug('[弹窗拦截] 🚫 检测到题库查询失败弹窗,自动拦截'); showRefreshNotification('🚫 题库错误或该题目为主观题/口语题,没有标准答案', 'info'); setTimeout(() => { ULogger.debug('[弹窗拦截] ⏭️ 2秒延迟后继续挂机流程'); continueAutoMode(); }, 2000); return true; } return originalAlert(message); }; window.confirm = function(message) { ULogger.debug('[弹窗拦截] confirm弹窗:', message); if (message && ( message.includes('在线题库') || message.includes('查询失败') || message.includes('未找到') )) { ULogger.debug('[弹窗拦截] 🚫 检测到题库相关确认弹窗,自动确认'); return true; } return originalConfirm(message); }; window.prompt = function(message, defaultValue) { ULogger.debug('[弹窗拦截] prompt弹窗:', message); if (message && ( message.includes('在线题库') || message.includes('查询失败') )) { ULogger.debug('[弹窗拦截] 🚫 检测到题库相关输入弹窗,自动返回默认值'); return defaultValue || ''; } return originalPrompt(message, defaultValue); }; ULogger.debug('[弹窗拦截] ✅ 弹窗拦截功能已启用'); } function continueAutoMode() { if (window.isAutoModeRunning) { ULogger.debug('[弹窗拦截] 🔄 继续执行自动挂机模式...'); const continueButtons = document.querySelectorAll(` .next-btn, .continue-btn, .btn-next, [class*="next"], [class*="continue"], .ant-btn-primary, .el-button--primary `); if (continueButtons.length > 0) { ULogger.debug('[弹窗拦截] 🖱️ 找到继续按钮,自动点击'); continueButtons[0].click(); } else if (window.__refreshAfterPopupBlock) { ULogger.debug('[弹窗拦截] 🔄 未找到继续按钮,用户已启用刷新选项,准备刷新页面'); setTimeout(() => { location.reload(); }, 1000); } else { ULogger.debug('[弹窗拦截] ⏸️ 未找到继续按钮,用户已禁用刷新选项,停止自动操作'); } } } function setupDOMPopupInterception() { if (window.UHelperPopupGuard && typeof window.UHelperPopupGuard.start === 'function') { return window.UHelperPopupGuard.start(); } ULogger.warn('[弹窗拦截] UHelperPopupGuard 未加载'); } function checkExistingPopups() { if (window.UHelperPopupGuard && typeof window.UHelperPopupGuard.scanExisting === 'function') { return window.UHelperPopupGuard.scanExisting(); } } function initRecordingFeatures() { window.__autoPlayRecordEnabled = localStorage.getItem('autoPlayRecordEnabled') === 'true'; window.__selectedAudioType = localStorage.getItem('selectedAudioType') || 'british'; if (window.UHelperRecording && typeof window.UHelperRecording.start === 'function') { window.UHelperRecording.start(); } ULogger.debug('[U-record] ✅ 录音功能初始化完成'); ULogger.debug('[U-record] 自动录音状态:', window.__autoPlayRecordEnabled ? '已启用' : '已禁用'); ULogger.debug('[U-record] 音频类型:', window.__selectedAudioType === 'british' ? '英音' : '美音'); initAutoRefresh(); setupPopupInterception(); setupDOMPopupInterception(); if (U_HELPER_DEBUG) { window.debugRecording = function() { ULogger.debug('=== 录音功能调试信息 ==='); ULogger.debug('自动录音状态:', window.__autoPlayRecordEnabled); ULogger.debug('音频类型:', window.__selectedAudioType); if (window.UHelperRecording && window.UHelperRecording.getState) { var state = window.UHelperRecording.getState(); ULogger.debug('录音时长:', state.recordDuration + 's'); ULogger.debug('音频缓存:', state.audioCacheSize, '个'); ULogger.debug('Blob缓存:', state.audioBlobCacheSize, '个'); ULogger.debug('虚拟麦克风:', state.isProcessingVirtualMic ? '使用中' : '空闲'); } var allButtons = document.querySelectorAll('.record-icon, .record-fill-icon, .button-record, [class*="record"], .microphone-btn, .mic-button'); ULogger.debug('找到的录音按钮:', allButtons.length); var audios = document.querySelectorAll('audio[src]'); ULogger.debug('找到的音频元素:', audios.length); var containers = document.querySelectorAll('.oral-study-sentence, .question-common-abs-reply, .question-vocabulary, .vocContainer'); ULogger.debug('找到的题目容器:', containers.length); ULogger.debug('=== 调试信息结束 ==='); ULogger.debug('提示:可以在控制台运行 debugRecording() 查看录音相关元素'); }; } } document.addEventListener('DOMContentLoaded', () => { try { if (window.UHelperTemplates) { unsafeWindow.UHelperTemplates = window.UHelperTemplates; } } catch (e) {} setupMultiPageNavigationListener(); autoResumeIfNeeded(); if (U_HELPER_DEBUG) { window.testSubmitButton = function() { ULogger.debug('=== 测试提交按钮查找 ==='); const submitBtn = findSubmitButton(); ULogger.debug('通用查找结果:', submitBtn); const footerBtn = findFooterButtonByText('提交'); ULogger.debug('底部按钮查找结果:', footerBtn); const dialogHandled = handleSubmitConfirmDialog(); ULogger.debug('提交确认弹窗处理结果:', dialogHandled); const allButtons = document.querySelectorAll('button'); ULogger.debug('页面中所有按钮:'); allButtons.forEach((btn, index) => { const text = btn.textContent.trim(); const classes = btn.className; if (text.includes('提交') || text.includes('Submit') || classes.includes('submit') || text.includes('确认') || text.includes('确定')) { ULogger.debug(` ${index}: "${text}" - 类名: ${classes}`); } }); const dialogs = document.querySelectorAll('[class*="dialog"], [role="dialog"], .modal, [class*="modal"]'); ULogger.debug('页面中的弹窗元素:', dialogs.length); dialogs.forEach((dialog, index) => { if (dialog.offsetParent !== null) { ULogger.debug(` 弹窗 ${index}: 可见 - ${dialog.className}`); ULogger.debug(` 内容: ${dialog.textContent.substring(0, 100)}...`); } }); return { submitBtn, footerBtn, dialogHandled }; }; } if (U_HELPER_DEBUG) { window.testItestQuestions = function() { ULogger.debug('=== 测试itest题型结构 ==='); const itestSections = document.querySelectorAll('.itest-section'); ULogger.debug('找到itest sections:', itestSections.length); itestSections.forEach((section, index) => { const title = section.querySelector('.title'); const sectionType = section.getAttribute('sectiontype'); ULogger.debug(`Section ${index + 1}: ${title ? title.textContent : 'No title'}, Type: ${sectionType}`); }); const radioInputs = document.querySelectorAll('.itest-danxuan input[type="radio"]'); ULogger.debug('找到单选题选项:', radioInputs.length); const radioQuestions = new Set(); radioInputs.forEach(input => { const qindex = input.getAttribute('qindex'); const qoo = input.getAttribute('qoo'); if (qindex) radioQuestions.add(qindex); const label = input.closest('label'); if (label && parseInt(qindex) <= 5) { ULogger.debug(`题目 ${qindex}: ${label.textContent.trim().substring(0, 50)}...`); ULogger.debug(` qoo属性: ${qoo}`); } }); ULogger.debug('单选题题目数量:', radioQuestions.size); const textInputs = document.querySelectorAll('.blankinput'); ULogger.debug('找到填空题:', textInputs.length); const textQuestions = new Set(); textInputs.forEach((input, index) => { const qindex = input.getAttribute('qindex'); if (qindex) textQuestions.add(qindex); if (index < 5) { ULogger.debug(`填空题 ${qindex}: width=${input.style.width}`); } }); ULogger.debug('填空题题目数量:', textQuestions.size); const allQuestions = new Set([...radioQuestions, ...textQuestions]); ULogger.debug('总题目数量:', allQuestions.size); return { itestSections, radioInputs, textInputs, radioQuestions: radioQuestions.size, textQuestions: textQuestions.size, totalQuestions: allQuestions.size }; }; } if (U_HELPER_DEBUG) { window.debugClassicUCampus = function() { if (window.UHelperClassicUCampus && typeof window.UHelperClassicUCampus.debug === 'function') { return window.UHelperClassicUCampus.debug(); } return { role: typeof getClassicUCampusPageRole === 'function' ? getClassicUCampusPageRole() : 'unknown', isTopWindow: window.top === window.self, isIframeWindow: window.top !== window.self, url: location.href, host: location.hostname, currentUid: typeof getCurrentUHelperUid === 'function' ? getCurrentUHelperUid() : null, syncedUid: window.__U_HELPER_SYNCED_UID__ || localStorage.getItem('u-helper-synced-uid'), classicExerciseId: typeof getClassicUCampusExerciseId === 'function' ? getClassicUCampusExerciseId() : null }; }; } if (U_HELPER_DEBUG) { window.testMultipleChoice = function() { ULogger.debug('=== 测试多选题处理 ==='); const multipleChoiceContainers = document.querySelectorAll('.MultipleChoice--checkbox-item-34A_-'); ULogger.debug('找到普通U校园版多选题选项:', multipleChoiceContainers.length); multipleChoiceContainers.forEach((container, index) => { const checkbox = container.querySelector('input[type="checkbox"]'); const optLabel = container.querySelector('.MultipleChoice--checkbox-opt-2F4xY'); const htmlView = container.querySelector('.html-view'); ULogger.debug(`选项 ${index + 1}:`); ULogger.debug(` 复选框:`, checkbox); ULogger.debug(` 选项标签:`, optLabel ? optLabel.textContent.trim() : 'null'); ULogger.debug(` 内容:`, htmlView ? htmlView.textContent.trim().substring(0, 50) + '...' : 'null'); }); const allCheckboxes = document.querySelectorAll('input[type="checkbox"]'); ULogger.debug('页面中所有复选框:', allCheckboxes.length); const testAnswers = [ ['B', 'D'], 'B,D', 'BD', { answer: ['B', 'D'] } ]; ULogger.debug('=== 测试答案格式处理 ==='); testAnswers.forEach((answer, index) => { let answerString = answer; if (Array.isArray(answer)) { answerString = answer.join(','); } else if (typeof answer === 'object') { answerString = JSON.stringify(answer); } ULogger.debug(`测试答案 ${index + 1}:`, answer, '-> 处理后:', answerString); const letterMatches = answerString.match(/[A-Z]/gi); ULogger.debug(` 提取的字母:`, letterMatches); }); return { multipleChoiceContainers, allCheckboxes }; }; } initRecordingFeatures(); setupSubmitConfirmHandler(); window.addEventListener('message', async function(event) { var data = event.data || {}; if (!data || data.type !== 'U_HELPER_CLASSIC_FILL_ANSWERS') return; if (typeof getClassicUCampusPageRole !== 'function' || getClassicUCampusPageRole() !== 'unit_test_iframe_page') { ULogger.debug('[普通U校园iframe] 收到答案但当前不是题目页,忽略'); return; } ULogger.debug('[普通U校园iframe] 收到外层答案:', data.answers); if (typeof waitForClassicUCampusTestReady === 'function') { await waitForClassicUCampusTestReady(); } if (typeof fillClassicUCampusIframeAnswers === 'function') { var ok = await fillClassicUCampusIframeAnswers(data.answers, data.source || 'message'); ULogger.debug('[普通U校园iframe] 外层答案填写结果:', ok); } }); }); if (document.readyState === 'loading') { } else { setupMultiPageNavigationListener(); initRecordingFeatures(); setupSubmitConfirmHandler(); autoResumeIfNeeded(); }