// ==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')) ||
`
`;
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();
}