// ==UserScript==
// @name U助手-AI版
// @namespace http://tampermonkey.net/
// @version 2.0.2
// @description 支持题库模式和ai模式解决U校园AI版的刷题烦恼,集成自动录音功能,挂机模式支持自动录音
// @author 恶搞之家
// @match *://ucontent.unipus.cn/*
// @match *://birdflock.unipus.cn/*
// @grant GM_xmlhttpRequest
// @connect kimi.moonshot.cn
// @connect 42.194.169.45
// @connect *
// @require https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.min.js
// ==/UserScript==
let loadedQuestionBank = null;
let currentTopicUsedAnswers = new Set();
let lastActiveTopicName = '';
let multiPageMode = {
isActive: false,
exerciseId: null,
pageIndex: 0,
totalAnswers: [],
lastUrl: ''
};
let recordedAudioBlob = null;
let recordedAudioUrl = null;
let currentRecordButton = null;
let currentQuestionContainer = null;
const audioCache = new Map();
const audioBlobCache = new Map();
window.__recordDuration = 3;
let autoRefreshInterval = null;
let autoRefreshEnabled = false;
window.__refreshInterval = 30;
window.isAutoModeRunning = false;
window.__refreshAfterPopupBlock = false;
(function setupQuestionInterceptor() {
'use strict';
console.log('🔧 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) {
// 忽略非 JSON 响应
}
}
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.')) {
console.log('🎯 V2脚本 [Fetch]: 拦截到加密题目数据!');
console.log(' - URL:', url);
console.log(' - 密文长度:', responseJson.content.length);
console.log(' - 解密密钥:', responseJson.k);
window.__interceptedQuestionData = {
encryptedContent: responseJson.content,
decryptionKey: responseJson.k,
url: url,
timestamp: Date.now()
};
window.dispatchEvent(new CustomEvent('questionDataIntercepted', {
detail: window.__interceptedQuestionData
}));
}
}
} catch (e) {
// 忽略非 JSON 响应
}
return response;
});
};
console.log('✅ V2脚本: 题目数据拦截器已启动');
window.addEventListener('questionDataIntercepted', (e) => {
const data = e.detail;
console.log('📢 V2脚本: 收到题目数据拦截事件,已更新 __interceptedQuestionData');
});
})();
const POINTS_API_ENDPOINT = 'http://42.194.169.45:8848/api/points';
const POINTS_PER_QUESTION = 2;
let userPoints = 0;
let userId = localStorage.getItem('userId');
class SimpleKeepAliveSystem {
constructor() {
this.isRunning = false;
this.domTimer = null;
this.networkTimer = null;
this.wsTimer = null;
this.dialogObserver = null;
this.wsConnections = [];
this.config = {
domInterval: 30000,
networkInterval: 45000,
wsInterval: 35000
};
this.setupWebSocketHook();
this.setupDialogObserver();
}
start() {
if (this.isRunning) return;
this.isRunning = true;
console.log('[保活系统] 启动');
this.domTimer = setInterval(() => {
this.simulateUserActivity();
}, this.config.domInterval + Math.random() * 3000);
this.networkTimer = setInterval(() => {
this.sendNetworkPing();
}, this.config.networkInterval + Math.random() * 5000);
this.wsTimer = setInterval(() => {
this.sendWebSocketPing();
}, this.config.wsInterval + Math.random() * 3000);
this.updateButton();
}
stop() {
if (!this.isRunning) return;
this.isRunning = false;
console.log('[保活系统] 停止');
if (this.domTimer) {
clearInterval(this.domTimer);
this.domTimer = null;
}
if (this.networkTimer) {
clearInterval(this.networkTimer);
this.networkTimer = null;
}
if (this.wsTimer) {
clearInterval(this.wsTimer);
this.wsTimer = null;
}
this.updateButton();
}
toggle() {
if (this.isRunning) {
this.stop();
} else {
this.start();
}
}
simulateUserActivity() {
try {
const activities = ['mousemove', 'scroll', 'keydown'];
const activity = activities[Math.floor(Math.random() * activities.length)];
switch (activity) {
case 'mousemove':
const mouseEvent = new MouseEvent('mousemove', {
bubbles: true,
clientX: Math.random() * window.innerWidth,
clientY: Math.random() * window.innerHeight
});
document.dispatchEvent(mouseEvent);
break;
case 'scroll':
window.scrollBy(0, Math.random() * 10 - 5);
break;
case 'keydown':
const keyEvent = new KeyboardEvent('keydown', {
bubbles: true,
key: 'Control'
});
document.dispatchEvent(keyEvent);
break;
}
console.log('[保活系统] DOM活动:', activity);
} catch (error) {
console.error('[保活系统] DOM活动失败:', error);
}
}
sendNetworkPing() {
try {
fetch(window.location.href, { method: 'HEAD' })
.then(() => console.log('[保活系统] 网络ping成功'))
.catch(error => console.error('[保活系统] 网络ping失败:', error));
} catch (error) {
console.error('[保活系统] 网络ping异常:', error);
}
}
sendWebSocketPing() {
try {
this.wsConnections.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('2');
console.log('[保活系统] WebSocket ping发送');
}
});
} catch (error) {
console.error('[保活系统] WebSocket ping失败:', error);
}
}
setupWebSocketHook() {
const originalWebSocket = window.WebSocket;
const self = this;
window.WebSocket = function(...args) {
const ws = new originalWebSocket(...args);
self.wsConnections.push(ws);
ws.addEventListener('close', () => {
const index = self.wsConnections.indexOf(ws);
if (index > -1) {
self.wsConnections.splice(index, 1);
}
});
return ws;
};
}
setupDialogObserver() {
this.dialogObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const dialogKeywords = ['长时间未操作', '继续使用', '继续学习', '超时', 'timeout', '会话已结束'];
const buttonKeywords = ['确定', '继续', 'OK'];
let isDialog = false;
const pTags = node.querySelectorAll ? node.querySelectorAll('p') : [];
for (const p of pTags) {
const pText = p.textContent || '';
if (dialogKeywords.some(keyword => pText.includes(keyword))) {
isDialog = true;
break;
}
}
if (!isDialog) {
const nodeText = node.textContent || '';
if (dialogKeywords.some(keyword => nodeText.includes(keyword))) {
isDialog = true;
}
}
if (isDialog) {
console.log('[保活系统] 检测到超时对话框,准备自动处理...');
setTimeout(() => {
const buttons = node.querySelectorAll('button, .btn, [role="button"]');
for (const btn of buttons) {
const btnText = btn.textContent || '';
if (buttonKeywords.some(keyword => btnText.includes(keyword))) {
console.log(`[保活系统] 找到匹配按钮: "${btnText}",尝试点击。`);
btn.click();
return;
}
}
}, 300);
}
}
});
});
});
if (document.body) {
this.dialogObserver.observe(document.body, {
childList: true,
subtree: true
});
}
}
updateButton() {
const checkbox = document.getElementById('keepAliveToggle');
if (checkbox) {
checkbox.checked = this.isRunning;
}
}
}
const keepAliveSystem = new SimpleKeepAliveSystem();
async function refreshPoints() {
console.log('开始刷新积分...');
console.log('当前用户ID:', userId);
console.log('当前POINTS_API_ENDPOINT:', POINTS_API_ENDPOINT);
if (!userId) {
console.error('错误: 用户ID为空');
return false;
}
try {
console.log('发送请求到:', `${POINTS_API_ENDPOINT}/deduct`);
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: `${POINTS_API_ENDPOINT}/deduct`,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
userId: userId,
points: 0
}),
onload: function(response) {
console.log('收到响应:', response.responseText);
resolve(response);
},
onerror: function(error) {
console.error('请求失败:', error);
reject(error);
}
});
});
console.log('解析响应...');
const data = JSON.parse(response.responseText);
console.log('解析后的数据:', data);
if (data.success) {
const newPoints = data.remainingPoints || data.points || 0;
console.log('服务器返回的积分:', newPoints);
if (newPoints !== userPoints) {
console.log('积分发生变化:', userPoints, '->', newPoints);
userPoints = newPoints;
localStorage.setItem('userPoints', userPoints);
}
updatePointsDisplay();
console.log('积分更新完成');
return true;
} else {
console.error('服务器返回失败:', data.message || '未知错误');
return false;
}
} catch (error) {
console.error('刷新积分过程中出错:', error);
return false;
}
}
window.refreshPoints = refreshPoints;
window.updateUI = updateUI;
function updateUI() {
const currentPointsDisplay = document.getElementById('currentPointsDisplay');
if (currentPointsDisplay) {
currentPointsDisplay.textContent = userPoints;
}
const rechargeModal = document.getElementById('rechargeModal');
if (rechargeModal) {
const pointsInfo = rechargeModal.querySelector('p:nth-child(2)');
if (pointsInfo) {
pointsInfo.textContent = `当前积分:${userPoints}`;
}
}
console.log('UI已更新,当前积分:', userPoints);
if (window.updateHighScoreLockState) window.updateHighScoreLockState();
}
setInterval(refreshPoints, 30000);
setInterval(updateUI, 4000);
refreshPoints().then(() => {
console.log('初始化完成,当前积分:', userPoints);
updateUI();
});
if (!userId) {
userId = 'UID' + Date.now() + Math.random().toString(36).substr(2, 6);
localStorage.setItem('userId', userId);
}
function createPointsDisplay() {
updatePointsDisplay();
}
function updatePointsDisplay() {
const aiPointsDisplay = document.querySelector('.u-helper-section-content .points-display');
if (aiPointsDisplay) {
aiPointsDisplay.textContent = `当前积分: ${userPoints}`;
}
const rechargeModal = document.getElementById('rechargeModal');
if (rechargeModal) {
const pointsInfo = rechargeModal.querySelector('p:nth-child(2)');
if (pointsInfo) {
pointsInfo.textContent = `当前积分:${userPoints}`;
}
}
window.updateUI && window.updateUI();
console.log('积分显示已更新:', userPoints);
}
function showRechargeModal(amount = 10) {
const QR_CODE_4_9 = 'data:image/png;base64,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';
const QR_CODE_9_9 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAHZBAMAAACsj/XDAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAAHYcAAB2HAY/l8WUAAAASUExURf7+/gMDA9DLwqCin2BgXxO+Z3C8Jk4AAB0lSURBVHja7F0JmqowDMbqAUQ5QKVzACwcAB7c/0yPLnQzZRNZtJ35HFQM/f3TNE1CJ4pCCy200EILLbTQQgtt0RYPt5KddwLeuLM3Luppwp4ifuhcpGKvZezoaR9x0Q9b6s2WqkW7vcYj+3+dCvj6NuBYwawUzKdP9GjA+rtcGPCSDDuAy7cB753hZ2D4yxl2zNf3M7y4Sh/MSpe/Noavv2Gls2UYxvMZxj4/TPcqQp1PdFPv6o/9OdeAaMg6KUKqo0GRLRXZr53dLxR3p2jRUHt4AEe9gPUHTq+fNzrkB1zZvXr2MIDAfpwhqZBop51mAS7fBhyPBxyNBYyiEQyfNmL4XcBIq/QxGK6WY9g1ivMYxqMYxhurdPLbYzja8xiuAsNHYvi2qdFakOEmsZy5zMdw3MSV02LDwUtZYy8KcQlrlL+WWFKNS7EWJ3F7QiX/iM/Wuptcaj/DhS20HARcAQ4fxHB/SyAPHQNrxBHt7o06etcl0ArgOm7dUXoZ7m8rA459gFdlGAWGv5zhMIY3YPi6EsPxXhguV2I4OQ7D8Xcx/IZKOwzf864VUC8b1vjb0GtYSb3nVIrR+O/2U/E53XSHnaefZVg/vUyiOetdkvTHR0Gp747hsQxvBjiBAAeGlx/DnwZ8mQT4mxlONhrDt8DwcoDxcgxfj2+lt2J4cZV+TFFptNYYPteFdJbyWrk+2t1SR0nNDrlA5ZEJn6tRn+NXunfPCu1aJU17kVq5W4mWz18s2reLtRg+ASqn88N3J7QIJcTHthsgFYyjfHYM9wNOnGj59HK/uYA3YjjZH8NlYDiM4cBwGMPfxXAFOEYLjeFlAEeTAA8zXNgBpMzH8Lkp6rryqnQONChAxSUkzkVZq4U71zbtyQnANW/t5dlvXTsqnTeNktaekb2ZH4bWDSBgb8t6c+0RIPrsLJS8yvOJCoATsIx4H/DV18kbUNW4GuDrMOD7ggw7onfC8OVzDF8mAa62YTj5eoY/PYZ3x/Dlc2P4snOGz9syHG/D8HZjeJ5K574Gelo0T/NC+UTiiGcCuZOjvSrniPeK2ClEIZC9lokjSqkWnWuPTAXJqA6X6f7XcO9J/vQA7m+QL+11eMGaOWdJ43itj5GiESR6bP+nAC6nAPb3SgPuX3lOE119APBCvYIAL/RdBoY3ZBgBgLOdM/xBld4nw7tW6X0yHAOKvNC0FBgeAlwNt1L0oKrif6wu/a/tdHWXR0lc8yOnV7yAnRfOiwJ5BbhgL2qub+zdokmS5EbaI871XQtkR4RdmB+lvHReiFaAm1Zek/S0uCqj2e1kJ3H9d5dCDEC+9Ig0uBMQdeV/tjnKtwpg0GENgL8I8D0wvAngS1DpoNJBpd8FnPyaSi8IeJJD2p/sQiNq8oaz1kZodEC0s4IoB6OWqwCuRq5LvgbwOwzfv4rhw6r0fSbD5a+N4euvjeEyjOHA8P3XpiW/aAwAzsalS71fvvNRyOuLRjDgzRQ/RpIbjUiIV4MBy6UBn0eonCP6fcBO7mYngOM1AC/M8GOnDO9TpY/I8F4B/5zRigPDAXBQaeWpoSE3H2pnb4rP62T1uwgXb5h2OH6HuOh/fV+o45rOBOyvSZsO+OQNxI8IWD5tDTp9APBlacDvMKwAo88BPnuj5cswnHw9w7sBjI6k0usxnHy90ToHhn+B4fskhqstGdZ1Xlg91R3ilWipqBSr4qQUHWL1aKo/d72LKrSfKj+iSmoitnbtROvqtL9E7PdqVbqxz5GqkW/Fok5NXLZrkqaqYk+qhEvV0T1kP7VpxkDqtvSlhr0OdQQBxj6H+gFIhUoPY6gwDRJdTsgP494w714ARwHw9wJOAsNLAX4eF3AWGA6Afw2w2ghM3MXI73T8pzdjtTebyHRNmjqFf+7MT1ECRQcS4I5LLqayAevzYnV2rUQjYl1JP801YGJLFR4qE1OOIzcDzBeUH4aWTKg/zZzZDu/Dd959pl4OB0ShgOoxAT9/DXAVAAfAAXAA/K1W+pfmYelQ1Y2xH5fYQZX96C0iMhsw946kk1U3hbHLl3eLDO1zOZ6W3gpf+29iiwy2Txh74DuCNbnuda122WdviD3FtNS/uu42xmASet2tqjdJ7IRRR+/yFY30pQf2+YU6DGW1n4D85QBfFgR8nQQ46gV89YRpd8VwP+AEAlytDnivDJfHZzj+NYaTEQzb0x76QpX+dYb7pe4O8Ix5uOr15ADA2l1x/mVQrFwf7WSdnf8W5HRNb+vl+FziKdtmVXyO7bcqXbyiEJuqsg1gG+NKtQqNFTyw1p5n/N/H9jX5VPhvhdjBNW+ke1XXhXK0msxD7oiaPChgeenXO6+v/uzNOju59gG9yYAVxPBqaUQufATgeATgEljczAUczwccbwB4IYbxfhl+ApalH/B95wwnKzN8MJUuf81o7ZPhcukxvBOG11PpbHOGc3ujVcME6W1T7WQib/8cwDqFqBOTvGnAhdpv/08dQQ2pLgmB1N6l1Yl9ZSqF6MivbU8RoMHr8HrL8Ub70pOaMyOd+/PDkW+MPKaWHh4JcAUAPgXA36bSWWA4AA6Awxj+LoZZgXrilK13rTLK1mNWda4L5PV1zlVSVesAFuX6qjl1/E9WE09Tq/3F3VazkYdmKMKr6+NOvvyt9c+00umNtD/st7uFAAbcH7rM7A733qkxAvDVBnzzA56Ekz20LaWUPyjdORBgNAkvTak4opTmHHq6KeDHLMAjMTOolEqkHLQ2D/5teHbHcIoFYoRHINY2pl395Rw9CHjPKj1l+Eq8jGP9LXwpYKnB7SMhHWhuvcjhAOPRRoupNB+9RFpqTvyhGcbGo9dwMYYp6VBvxnD2PmCkHrxw+cykCKaKYXkLJ4oi7AVc2YApO3ckYCjgGclrwanhmzfpMN5ocbACpGSY/8Ww/2ZtlgG1SRFgGHA0E/ArqXhgHKtRTEw3/TIT8GM4AgxEeN8AbCo1GkBL5B/JcDeGZwCelNRYkmGsWEUdu+gFtFwrMLtMlMliftd8hqupKu0sL5dgGAmK+0x0aphoMUOtxvCyKo1fcAOaTbn3QYlwqalQ6sOptEUxVtBxj49J5BpxSYbLVQEjYwyzI+xClitDYbOYcZY0p3QBKy0ZxmupNLY8D2xZMM+KSULmw/qYY1iSyteISLtcGELLdFkgJrZKf87xcBzKyOeVnoDolhvi0k6locc+J1OYLBnTkk4X9sVC/f9gAOLqMc6XHgH42l/KYvCINddcpZEv7EG42eKTM1kK8GlZwKc+wFKZkbLSWHGOAP+Dqqgln4n3yfAAYPTqYAL8UqL+ErmUIHtV6T7AIpol+UWGlUag69GNYSIgH3EMi9HbWWVsPAKYO2a7xWK+LmA8j2HLSqNuzOKXyMcrzWJFTIxwgAP4vjnDt8ExjIyBy5hF2PQxX2KXcs1Edz0t9TCMDDfSXA1rT+QleCmsl5yRDzkPM9BRp71Y/ojXsWeVSGU8YJVpqZNoe6UIPgcgF59ffWkxEyHcF7MkymgJvKk9hu+Af+kylPqy2jwk95iwUCyHHeoY6hXgYCDN78tYFrk0hprK5fE0wNgPeMjdWgywtFkMIEJqHGNNO+R/UKXaxwNsmS1smC7sDU4LH+ugDCOt0kgSnXYzE+hNdyyTOWN4EcDPd8ew9CxTJ34JU0y6WUnOSwccw7gz0cJMd9FppBxMi1wVCaBGxONYY9hZMCHTZL0Ws3T5UhnlSo9ptLA01GrNoAhGvhC1nJA3A5zNBczXhDhVaBHuL/pQC2Hxm08DHM0HrP8Tt3DpjP1Yu0K10inW0iVq9pGhzDi17LVHqynRHqabH+YnVapzDmDdYbOjTZWUE+I8/bd7eKvTItO1xEaqAXmS4yobbtW4jEmIT+rrtU+RlwBszEMy5oFNbwQBSTUxEYuJaSHAazEss0jqV7YoTX1r4lTaaVmnlR+RYZUlNdxn6U9jk2JiRvK649UYXgwwSnGXcpAeB/bm0lTmQQ/p1RhezGgpR9pZQ3iWx1RHPsjYwrRdqbSTQevsFMJgSo2asVoO+2BGS1ppZbKQswjGPi9LrYjHFJduqNIIGMPKNhEzMd7xa3kfVJV6CJeLklXGsNqvNdd6XefEvKnxaQu5Q7u0ikiSMckWuWGwOGgKLoaN+dgK4mnRzhYZ+hZR3SoA8Jm83GwJFfHFvbdLnOxKvDOQeRCGGRV5w4CSXKkyqV9TxGqpJMeyN2rpdrjfBl3HFfBMB3zx5Jbah5oWDE9eq3FcNPQFMXF43zvgM5wubecf0g7Qf5xW2g1gklN4xURk7oGY09JbgMuPMmxlHrqcYZGm538t0qLlWXrSOalfFobaTrtWegAw3oVKI70sbAGnNaf1Xzc556QBsmm2FTveGO4aA8zsVc0HMeP8Ly8K6i25PMgYfgGsJqK6JZMBzmkufc2cQoOY2lPUERlGzLngw7UF3c7GglbC/pLaSiECfB/QSnM8eVFLlc7pn9yAgQHPe6pMlwQ8bKX51qjOB0jTbcDlAE5q2ZonpNKMzvofd7Xao5Q0ckcvkNSu4kFqNQQYsT3OhM9Vq903+FO2SWuuudKb7g8z7L3Joz8XDhWmtZgJW+MWzEYRRmve2SXsUkzHqDR4O61TPzil9PBtwK9WumY+VJ4WFFmQkJip0p7qh2mAq+0Bc0f6zGhlyvwKL/fXLkEM32cyXK6p0qnU45zBI1QFABBNXxkmh1dpNif98VXDPwlYRe/y7i3wbq05gD+h0uU0hlHHcLtaYiqdU5R293kwdf6jQJ0HTfut9N5U2q3TYiulnIPOGUhMjPH7wjAdqdLJWipdTgWM+Uqf+1ctukJwx3NqdUe+jVSviQlstNAklX78Z+/a1pTFlSgGH8ADDxBN73tM7HsQ5n5/u7fv/ypDDuRkhZOIoDD9/dNtt5DlqiQrVZXKtIOW1I1Xei3PlFXSmVkgiQP47Dlopxu0isKuC1YYwD+8OH7qerJMFTEj5hLjblJuOlIyRTJfQxCKFDho0HKt2gC2ypcZ75YH+KoLlOV2qytpB9Tz7xMQ3wCqvGlnGimKs5yFmbLUes1AbuAgTR4Bg/FhaJ8p1OpTt2XECIDV6oHVg1ThZUB4gKkpd0C7ZPG8AvDlSYaRnHKrf65CO2PDJFfZNzCAqD+PhTJMHIaxnI4wZNImbWm5Js05ZvKfs6Sa+/IkUAwybAXHFwdYRVQ0wwzLQUmMYmdbTJtEWmPSC+zDMn+HCGJJ7eLg3qwrI3yachdQeplEHqXlUkwanTWPle4gYtCqpm9KypJB09JZp0p3SS59GeB0KGAknHgc6xlxhgU+wrEKFw/RgKm1TALdtP0AX7oBtorW6++Md8iLHkrpQ2v1YkJ8pVFC2itN+VhdKaybUNG1e7ay7JslrKitLYkJiMeuyLKOojQVZaHooQH8o9uVBfx/htdLe8z1AEVZjElX4lK4Zn8kw8gJod4Yg3oxfSjGtoW0dHN8OGs136AMHw5YTrcCsB60sEzwEExTcqVecMneajoZ4HwUwKgOI4lQsARsxfxJIV6iThycWF4PEgacvIJhPALDUluK2edHMawz8EjVFcn5ytyRijw4AKY16Sf7sLpKWgtMK+GQ3Ljr1iyJycPURDEQiZ2xSUd6B1pZGJGhNwT8UMZFSOFWH9I7xM+BogfzHrSUtrwWarax910qwNTJ7yDnBpM+vqwPj8Sw3rDzcxPDk5tAS4or5dXvHlaFyrFFz2SBfVjm3JHCc2AxUnFbVnBZQe2aJRo2OEpPy3DCK7Sa2x2EVVYv5gcgbR3bCeJq1JIZK5WSrr7kMUKVaCqZl5jmTMhYZ06QfZLItHtimin+Zu8YYaKT4/eQy7dPH84Awg/NDJi8fxEgLaTXjwmleL0W/KeC6tx/KxO+tm6MI/jQdN8lD4n/S2tUvJnhzJXh0OIB3sbDc7IEo6zgOljzRygFah/W/h8UYYSgxYPvlx0FMNSHBwEW3knChKOUyw7SWNuy9sK71hdkOJofwzKSVOllSrtVHRZ4vTTVaRl+1qQ71sQz1Tsqer3hZQrAY/fh7hc9PzQLTWDSY/Vh3LKb1K2XJv4BpstNgOG5jtJqV2kr2dQUDfeu04QMPwm4a3FaYsdHoZa9oQ/fGm8SZBjhbnjljGTpcNeoj1yDdDBpFAR8qMZ+jLv1YSh5ANpCEEORhz5V01V7KskR64rpcFajp5ux+1pwGbDrxjAUWoYAbwcC1qsG/cmxv/v9/ssYqpkOb22BdtL2BtwhPWRMwE7B8AjF7K6u/7FAGicIeHqGB5s0kUsl1Xk13upi0cQMX17BMIEnJWW9Nl6NeLtwhsljVLhe7sd390o9ubXAPgzs0BKrCwnl1wP8hzWa2THcApi42xqIXiERS0WT+x2k+GeOfbjRpJ2yQsQKIcnKyrU/5/cBMNMQX8fwfoJ5mDjeaOV0QvED3vufbt3YDAc3DgQjrZ7Xch/cxiMMWe8qtIg+G5+WeAtAMNaAO+95iAJrrikBO7XQpLOOuGdaCOYgvHHd4OMsAR8DgE18kFi5K9Sx6Ic5ieMlv3XIfVEMU31UlnMWTe2uVrPwL8AvU714rgyDgKmO+qqzDeT5Bkp0mFM7XMB/1QAd0/u9TiVdDMPUji0QINm/zufALmAeWxJCE82ZYbgPVzwyU1SZOLuyRE14a8xSYvqP8l1Z4tvFMGylx5rSpGddoITY9XfNmEWZwotq3ZUupg8Tz/HsHRemmNZrQwWYyy0hodmvAzhZgknbJd9rV+xZD1ZWJUsD+C+Kf3n2F4p/7y8F7L3VS8K7uFil8nUT847eytVObq9PvnP0lS45RG2TrmahmMcfjK5OQQioOQvHexNq/awkYNScv4fgrXK2lqZeYWHDNNE5DtKkayWdysOkjCfgv0PSjh4D4qgL4BDD5iabJsDUmpNUtSxKCbFUpjiPxhm0/i8eipi/QgwCRl0YBjhuBhxMSW1j2KyJqJmMiVpL6M2VVh++p9hbGKfjMByNwXAzYGo8dFSdeaeUJJVFhtVpNM6gVXXiSlHaGgQPAZx2A4xHBey4r2h9DJw6obSek2SPFjanAEYu3ns0K4bDfRhZ0lmNxRbD5qQwgd7S0n+pi5czjhZg0gj5iyNT+a6uJcUYI+L4TmJr6T8G+TyCDcbPmPSYDEd68xVRx+zKQzrVxWqGhdcSwy68hjFrfoMWis5uYr+EVh8DJw4aZkxADzkAzKj9AsDmGGtzmjjPB8tTnR9mbTHl9Vt3+hc/wUy5ftfvPeTFqxok1sUxb1Fpuymrn03TxXeGq+s+scrO5lkT1/uQlt4B0jSQGshDsogPOOJL/idUKTbf2rIAMwAwcZx44e20nnFm3aLiEZBSOhxw7ysOduHPBIwAm/7TD+gHOO0B2DPpdDqGAU+8XL9s2+t4RO4Amw0HPKFJR9F/wEiLCaZ9WB9+6MV/kmC0n9KkpwSMPFVJkZfyEC5r8QTD7wKMHximD1k8/Uz6NMik06lNup6NGa0XDTEAGI1s0mbvoq5ekRS6NkQQMDLbIsVl7loCl/ksf8ryJuV0ZcbyuTqRGInNloQawKairPlR/OlVb8AU20H5ZmW5O/RY3YClTQw3y+ggYC803rL/xOkt4onxP0xJRLMmRJ2K8eLQeHvqVp/mPYAxYdopar3zxYDfyTDgbNvsP4dhZzwULn2MoOZ8JsOH8Mc/d4bRIMBgsKx7BfHlMQwfTL9dAsMDAaffxvBxWsC6JsXVqBfpSwQKcAnACd9CmBmtq4uPWYUwSq3TjH7b62IZ1ft55dXyeoVKpPCKFkWubl39pVFyXgkO8yNxC1xcr9Xzc9XWssialgzNzl0oSLqHouJQsn5Q6yapIznCd+16XVpTD/sDPowI2DPqzecD3uf7ZLdohlFPwI8j44cz/Dj2r4A/GPBmBbwCfgawKDVhRFaz0tpyfZRrwLERVLxehXVQkLmh0Vfms2SlfKO61155n4xMqxQWv19uwuyetPIu81vsAk5cF1oo0cP7mDKA5kPQxRxUvZDxnACuoSMk4qCvMgLiw5duDssg4N0K+MMBrya9MrwyvAIeDvjYC3C6MgyKFBOWMy6iXCsVr7y8Bxh5FVNL93gg86PRXNgFnOgC++I7EU6MTSl8XqTVv6t3GcEnSrp6TU8fyfXctM0f2AagwZe+btNQ19Bzc20y1GGnA9TWXV/Apz6AwXIq4yQXjAg4uJrpDzh5BvDK8BOAs1bA+SiAl8EwHg/wMvrwq006/yTAcX+G00/qw7M3aZ4yLgeaPE+SxAAWVVpvOiM+Saov8dZtnueysphJi+elU+XHlMhSqm7TyupmSRCwKgCb2LnrAqYoykx4pn7hAT4DleVktVTVGAXzh785a/X65R3XHeDBZs2ZchDg5vv7AZyg+Pdavevh5hwIOJ4YcPTlgC/LBxz3Zzh1oa8Mr314BbycPnz5tj48NsMIIbAItAFMGgFbfp5b/ZINGAcAHwHA6FmG29PlfH3q5RzFHYLEzQ8xqncbSGdqUHLBGWkT4HpEwPtegHdA0zbLAvw8w2MARkti+HmT3n2vSZ/mBvgDBq159OF6l8w6Sq8mPbFJPzVK40GDVtCDnHVzArZJt2boQS62wbd0+Cyz1gH6bYB33wb4UxnOv43hfGV4hgyn7ekdax9eR+kZ9GH07QxHEVjQ9OGS7+eFsk6A9AOPvcJu09JQoEC1AT0k5h1x7RpMcGv0EOU9UbdczauZ8LFXzYB3wEMgmZ509UvvB5rkVICzEQHn7wGcvI1hyKTfzTCeEPDXMbwC/vRBK5sl4K8z6TdMS1ZVU/hS6WIeYHWyd15f4hkyj2yfqOw0ftZ4bvLUBOArf80sxJLcuUSK2knf/4fXjj2q7LRkX5p0NAO4TEo7nc4DHD8eJN7VtbgB1ohQ5jAK2nUaCj17TzJRYbAmXvOtoXVJ1Bdw9jRgL40zbQS8Aaw5hjzAzYB3wwG/l+HjQIbfCnggw9sODOcvAJyNyPClD8Pxm/rwrBnez5zh6UbptQ+/sQ+nb+/DLHTlAMOx3sA4gGHi1p44uqW/UqMv9T5L6jHsbbs0W0TFQwq31GzW6qaNQoIXkuEDGG5ePESh+HC/6sPt+SzNgHdPA847Lh6ixsVJMhXgJTGczpzh3bcxnH1bHx6d4XRleMEM77+oDyMIMH7SpFlhX2UKMHwsrqpYGPUAa8ETZrgo7ML4sjrY0XlNCiOh5C4Qw6KumFX2VdZ7LeX/CvkZ6fbnrYAhBe0xbK5DVy66CvY9EB/uUoy36w1D6w4I8K4dcMvWk3yWgFeGV4a/BfDzDHdt3+FTGP66Prz/tlE6X5ZJ+0FSrbTioq7VahVQFUrMyCG3cquUQ8nVqdJav3YtmesuS+w695V48m6o1JdTft8UI3vCpD2G+2XiefHhoJu2s0PUuzCgpS/jMvw84Ox1gLMxAGfLAHzpCzhfGV4Ww/nK8BwYvry4D6crw3NgeDnzcKIdT0idyVgLSi/qaFSPEUEyT0tXqf8xNVxFgVZ+srh4reDfnfQvpAcsCFjUZsX61oVzRmVz9LADwzvXlwpqXciNCrlpPdfiBhKx7h+joGRO232hw/pwl9UMBHg/S8Arw29ieLsyvPbhleFxAa99uDfD8zTpkpdT5V9SJqUhhk2BVhBwvlf58SJBXlxIZMl7gMVtLjzT/abz4C2GRYFWfQep6USCvvxt8qgvxZMu1bNlCj/S78VPxofDAeugteDGW59CqYfh6iBQW6GAOCCo+2cADADc4dZQ6uH2TYBfyDAEePN2wLsXmzTE8GExDCcjMhx/HsNjmvTl9QwfnmH41D5KtwTrPoDhw8eP0tnYfTjtBhiPx/CAQesUYjjuZdJ9+nDzlbV7KeNmGd3La3nscP8ONDV6LXtHlQcD7uqX7gI4ehXgbGzA6cwBz5VhvBiGs29jOP22Puzpq9EBn9ZR+n2AL+u09O/oXUoPIqDAMApGwSgYBaNgpAIA4IJ25/WYzxQAAAAASUVORK5CYII=';
const QR_CODE_24_9 ='data:image/png;base64,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'
const getQRCodeUrl = (amount) => {
const amountNum = parseFloat(amount);
console.log('选择收款码, 金额:', amountNum);
if(amountNum === 4.9) return QR_CODE_4_9;
if(amountNum === 9.9) return QR_CODE_9_9;
if(amountNum === 24.9) return QR_CODE_24_9;
return QR_CODE_4_9;
};
const existingModal = document.getElementById('rechargeModal');
if (existingModal) existingModal.remove();
if (!document.getElementById('u-recharge-scrollbar-style')) {
const style = document.createElement('style');
style.id = 'u-recharge-scrollbar-style';
style.textContent = `
.u-recharge-scroll::-webkit-scrollbar { width: 5px; }
.u-recharge-scroll::-webkit-scrollbar-track { background: transparent; }
.u-recharge-scroll::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 10px; }
.u-recharge-scroll::-webkit-scrollbar-thumb:hover { background: #bfbfbf; }
`;
document.head.appendChild(style);
}
const modalHtml = `
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
setTimeout(() => {
const qrContainer = document.getElementById("uidQrcode");
if (qrContainer) {
qrContainer.innerHTML = '';
try {
new QRCode(qrContainer, {
text: userId,
width: 90,
height: 90,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.L
});
} catch (error) {
console.error('QR Code error:', error);
qrContainer.innerHTML = '加载失败';
}
}
const closeBtn = document.getElementById('closeModalBtn');
const checkBtn = document.getElementById('checkStatusBtn');
const modal = document.getElementById('rechargeModal');
if(closeBtn) {
closeBtn.onmouseover = () => closeBtn.style.background = '#f5f5f5';
closeBtn.onmouseout = () => closeBtn.style.background = 'white';
closeBtn.onclick = () => modal && modal.remove();
}
if(checkBtn) {
checkBtn.onmouseover = () => checkBtn.style.background = '#40a9ff';
checkBtn.onmouseout = () => checkBtn.style.background = '#1890ff';
checkBtn.onclick = async () => {
try {
checkBtn.disabled = true;
checkBtn.style.background = '#91d5ff';
checkBtn.textContent = '提交中...';
const pointsToAdd = amount === 4.9 ? 100 : amount === 9.9 ? 220 : 600;
GM_xmlhttpRequest({
method: 'POST',
url: 'http://42.194.169.45:8850/api/submit-point-order', // 端口需对应你第一步修改的服务器端口
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
uid: userId,
amount: amount,
points: pointsToAdd
}),
onload: function(res) {
const data = JSON.parse(res.responseText);
if (data.status === 'success' || data.status === 'info') {
alert('✅ 订单提交成功!\n审核后积分将自动到账。\n无需重复点击。');
const modal = document.getElementById('rechargeModal');
if(modal) modal.remove();
} else {
alert('提交失败: ' + data.message);
}
checkBtn.disabled = false;
checkBtn.style.background = '#1890ff';
checkBtn.textContent = '检查支付状态';
},
onerror: function(err) {
alert('网络错误,无法连接服务器');
checkBtn.disabled = false;
checkBtn.textContent = '检查支付状态';
}
});
} catch(error) {
console.error('提交订单失败:', error);
checkBtn.disabled = false;
}
};
}
}, 100);
const orderInfo = {
userId: userId,
amount: amount,
timestamp: Date.now(),
points: amount === 4.9 ? 100 : amount === 9.9 ? 220 : 600
};
localStorage.setItem('pendingOrder', JSON.stringify(orderInfo));
}
async function checkPaymentStatus() {
const success = await refreshPoints();
if (success) {
const modal = document.getElementById('rechargeModal');
if (modal) {
modal.remove();
}
alert('充值成功!积分已到账');
localStorage.removeItem('pendingOrder');
} else {
alert('管理员正在处理您的订单,请稍后再试!\n如超过10分钟未到账,请联系管理员并提供您的UID。');
}
}
async function checkAndDeductPoints() {
try {
console.log(`🔄 正在请求服务器扣除积分...`);
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: `${POINTS_API_ENDPOINT}/deduct`,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
userId: userId
}),
timeout: 10000,
onload: resolve,
onerror: reject,
ontimeout: () => reject(new Error('请求超时'))
});
});
const data = JSON.parse(response.responseText);
if (data.success) {
userPoints = data.remainingPoints;
localStorage.setItem('userPoints', userPoints);
updatePointsDisplay();
console.log(`✅ 积分扣除成功,扣除了 ${data.deducted || POINTS_PER_QUESTION} 积分,剩余: ${userPoints}`);
return true;
} else {
console.log(`❌ 服务器返回: ${data.message}`);
alert(data.message || '积分不足,请充值!');
showPointsPackages();
return false;
}
} catch (error) {
console.error('❌ 积分扣除请求失败:', error);
alert('积分系统异常,请稍后再试!');
return false;
}
}
const KIMI_API_ENDPOINT = 'http://42.194.169.45:8848/api/kimi';
const AI_SOLVE_ENDPOINT = 'http://42.194.169.45:8848/api/solve-question';
let useKimiAI = localStorage.getItem('useKimiAI') === 'true';
let interceptedQuestionData = null;
async function initPointsSystem() {
createPointsDisplay();
window.showRechargeModal = showRechargeModal;
window.checkPaymentStatus = checkPaymentStatus;
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: `${POINTS_API_ENDPOINT}/deduct`,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
userId: userId,
points: 0
}),
onload: resolve,
onerror: reject
});
});
const data = JSON.parse(response.responseText);
if (data.success) {
userPoints = data.remainingPoints;
localStorage.setItem('userPoints', userPoints);
updatePointsDisplay();
}
} catch (error) {
console.error('获取积分失败:', error);
}
}
document.addEventListener('DOMContentLoaded', initPointsSystem);
function showPointsPackages() {
const packages = [
{
amount: '4.9',
title: '体验套餐',
points: '100',
times: '50',
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
accentColor: '#667eea'
},
{
amount: '9.9',
title: '进阶套餐',
points: '220',
times: '110',
bonus: '+20',
gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
accentColor: '#f5576c'
},
{
amount: '24.9',
title: '尊享套餐',
points: '600',
times: '300',
bonus: '+100',
gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
accentColor: '#00f2fe'
}
];
const modalHtml = `
选择充值套餐
您的充值UID
${userId}
💡 点击即可复制,充值时请备注此UID
${packages.map((pkg, index) => `
${pkg.title}
约 ${pkg.times} 次AI答题${pkg.bonus ? ' · 赠送' + pkg.bonus + '积分' : ''}
¥${pkg.amount}
${pkg.points}积分
`).join('')}
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
const options = document.querySelectorAll('.package-option');
options.forEach(option => {
const accent = option.querySelector('.pkg-accent');
option.addEventListener('mouseover', () => {
option.style.borderColor = '#cbd5e0';
option.style.transform = 'translateX(4px)';
option.style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)';
accent.style.opacity = '1';
});
option.addEventListener('mouseout', () => {
option.style.borderColor = '#e2e8f0';
option.style.transform = 'translateX(0)';
option.style.boxShadow = 'none';
accent.style.opacity = '0';
});
const amount = option.getAttribute('data-amount');
option.onclick = () => {
document.getElementById('pointsPackagesModal').remove();
showRechargeModal(parseFloat(amount));
};
});
}
window.showPointsPackages = () => {
showPointsPackages();
};
const KIMI_MODEL_NAME = "kimi-k2-0711-preview";
const KIMI_SYSTEM_PROMPT = `你是一个专业的英语教学助手,擅长分析英语题目。请注意:
1. 阅读整个页面的所有题目和选项
2. 给出每道题的答案,格式为:1.A, 2.B 这样的形式
3. 只使用实际存在的选项字母
4. 理解题目类型,特别是"not collocate"类型表示选择不正确的搭配
5. 确保答案数量与题目数量一致
6. 如果是填空题,直接给出单词或短语答案
7. 如果遇到不确定的答案,说明原因并给出最可能的选项`;
async function askKimi(question, retryCount = 3, retryDelay = 1000) {
console.log('🤖 V2脚本: askKimi 被调用');
if (!useKimiAI) {
console.log('❌ V2脚本: AI 模式未开启');
alert('请先开启 AI 答题功能');
return null;
}
console.log('✅ V2脚本: AI 模式已开启');
if (userPoints < POINTS_PER_QUESTION) {
console.log(`❌ V2脚本: 积分不足 (当前: ${userPoints}, 需要: ${POINTS_PER_QUESTION})`);
alert(`积分不足!每次AI答题需要 ${POINTS_PER_QUESTION} 积分,当前积分:${userPoints}`);
showPointsPackages();
return null;
}
console.log(`✅ V2脚本: 积分充足 (${userPoints})`);
const interceptedData = window.__interceptedQuestionData;
const hasValidInterceptedData = interceptedData &&
interceptedData.encryptedContent &&
interceptedData.decryptionKey &&
(Date.now() - interceptedData.timestamp) < 60000;
if (hasValidInterceptedData) {
console.log('🆕 V2脚本: 使用新流程 - 发送密文到服务端解密');
console.log(' - 密文长度:', interceptedData.encryptedContent.length);
console.log(' - 解密密钥:', interceptedData.decryptionKey);
return await askKimiWithEncryptedData(
interceptedData.encryptedContent,
interceptedData.decryptionKey,
retryCount,
retryDelay
);
}
console.log('⚠️ V2脚本: 未检测到密文数据,使用旧流程(发送题目文本)');
return await askKimiLegacy(question, retryCount, retryDelay);
}
async function askKimiWithEncryptedData(encryptedContent, decryptionKey, retryCount = 3, retryDelay = 1000) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
console.log(`🔄 V2脚本: 正在调用新 AI 接口... (第${attempt}次尝试)`);
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: AI_SOLVE_ENDPOINT,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
userId: userId,
encryptedContent: encryptedContent,
decryptionKey: decryptionKey
}),
timeout: 60000,
onload: function(res) {
resolve(res);
},
onerror: function(error) {
console.error('❌ V2脚本: 新接口请求错误:', error);
reject(error);
},
ontimeout: function() {
console.error('❌ V2脚本: 新接口请求超时');
reject(new Error('请求超时'));
}
});
});
if (response.status >= 200 && response.status < 300 && response.responseText) {
const data = JSON.parse(response.responseText);
console.log('📥 V2脚本: 服务端响应:', data);
if (data.error === 'INSUFFICIENT_POINTS') {
console.log('❌ V2脚本: 服务端检测 - 积分不足');
userPoints = data.currentPoints || 0;
localStorage.setItem('userPoints', userPoints);
updatePointsDisplay();
alert(`积分不足!服务端验证失败,当前积分:${userPoints}`);
showPointsPackages();
return null;
}
if (data.success && data.answer) {
console.log('✅ V2脚本: AI 答题成功!');
console.log(' - 请求ID:', data.requestId);
console.log(' - 答案:', data.answer);
console.log(' - 剩余积分:', data.points);
userPoints = data.points;
localStorage.setItem('userPoints', userPoints);
updatePointsDisplay();
window.__interceptedQuestionData = null;
return data.answer;
}
if (!data.success) {
console.error('❌ V2脚本: 服务端返回错误:', data.message);
if (attempt === retryCount) {
alert('AI 答题失败: ' + (data.message || '未知错误'));
return null;
}
}
} else {
console.error(`❌ V2脚本: HTTP 错误 ${response.status}`);
if (attempt === retryCount) {
return null;
}
}
if (attempt < retryCount) {
const delay = retryDelay * Math.pow(2, attempt - 1);
console.log(`⏳ V2脚本: 等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
} catch (error) {
console.error(`❌ V2脚本: 第${attempt}次尝试失败:`, error);
if (attempt === retryCount) {
alert('AI 答题失败: ' + error.message);
return null;
}
const delay = retryDelay * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return null;
}
/**
* 旧流程:发送格式化的题目文本到 /api/kimi 接口
* 保留此函数用于兼容没有拦截到密文的情况
*/
async function askKimiLegacy(question, retryCount = 3, retryDelay = 1000) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
console.log(`🔄 V2脚本 [旧流程]: 正在调用 KIMI API... (第${attempt}次尝试)`);
const messages = [
{
role: "system",
content: KIMI_SYSTEM_PROMPT
},
{
role: "user",
content: question
}
];
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: KIMI_API_ENDPOINT,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
userId: userId,
messages: messages,
temperature: 0.6
}),
timeout: 30000,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data.error === 'INSUFFICIENT_POINTS') {
alert(`积分不足!每次AI答题需要${POINTS_PER_QUESTION}积分,当前积分:${userPoints}`);
showPointsPackages();
reject(new Error('积分不足'));
return;
}
if (data.points !== undefined) {
userPoints = data.points;
localStorage.setItem('userPoints', userPoints);
updatePointsDisplay();
}
} catch(e) {}
resolve(response);
},
onerror: function(error) {
console.error('KIMI API请求错误:', error);
reject(error);
},
ontimeout: function() {
console.error('KIMI API请求超时');
reject(new Error('请求超时'));
}
});
});
if (response.status >= 200 && response.status < 300 && response.responseText) {
try {
const data = JSON.parse(response.responseText);
console.log('KIMI API响应:', data);
if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) {
return data.choices[0].message.content;
} else {
throw new Error('API响应格式不正确');
}
} catch (error) {
console.error('解析KIMI响应失败:', error);
if (attempt === retryCount) throw error;
}
} else {
let errorMessage = `API请求失败 (HTTP ${response.status})`;
try {
if (response.responseText) {
const errorData = JSON.parse(response.responseText);
errorMessage += `: ${JSON.stringify(errorData)}`;
}
} catch (e) {}
console.error(errorMessage);
if (attempt === retryCount) {
console.log('所有重试都失败,使用本地分析替代API响应');
return await analyzeQuestionLocally(question);
}
}
if (attempt < retryCount) {
const delay = retryDelay * Math.pow(2, attempt - 1);
console.log(`等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
} catch (error) {
console.error(`第${attempt}次尝试失败:`, error);
if (attempt === retryCount) {
console.log('所有重试都失败,使用本地分析替代API响应');
return await analyzeQuestionLocally(question);
}
const delay = retryDelay * Math.pow(2, attempt - 1);
console.log(`等待 ${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return null;
}
async function analyzeQuestionLocally(question) {
console.log('开始本地分析题目');
const questionLines = question.split('\n');
const questionText = questionLines.find(line =>
line.includes('Identify') ||
line.includes('Choose') ||
line.includes('Select') ||
line.includes('which')
) || questionLines[0];
console.log('题目文本:', questionText);
const options = [];
let optionLetters = '';
questionLines.forEach(line => {
const optionMatch = line.match(/^([A-D])[\.:\)\s]\s*(.+)$/);
if (optionMatch) {
const [, letter, text] = optionMatch;
options.push({ letter, text });
console.log(`选项 ${letter}: ${text}`);
}
});
let simulatedAnswer = '';
if (questionText.includes('not collocate') ||
questionText.includes('do not') ||
questionText.includes('incorrect') ||
questionText.includes('wrong') ||
questionText.includes('false')) {
console.log('识别为"查找不正确"类型题目');
simulatedAnswer = 'B';
optionLetters = 'B';
} else if (questionText.includes('collocate') ||
questionText.includes('match') ||
questionText.includes('pair') ||
questionText.includes('correct') ||
questionText.includes('true')) {
console.log('识别为"查找正确"类型题目');
simulatedAnswer = 'A, C, D';
optionLetters = 'ACD';
} else {
console.log('无法确定题目类型,返回所有选项');
optionLetters = options.map(opt => opt.letter).join(', ');
simulatedAnswer = optionLetters;
}
console.log('本地分析生成的答案:', simulatedAnswer);
return simulatedAnswer;
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function setupRecordingHijack() {
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = async function(constraints) {
console.log('[自动播放录制器] getUserMedia 被调用');
if (constraints.audio && window.__autoPlayRecordEnabled) {
const sampleAudioElement = findSampleAudio(currentRecordButton);
if (sampleAudioElement) {
console.log('[自动播放录制器] 🎯 发现示例音频,开始创建虚拟麦克风流');
try {
const wavBlob = audioBlobCache.get(currentQuestionContainer);
if (!wavBlob) {
console.error('[自动播放录制器] 缓存中未找到音频Blob');
showRecordNotification('❌ 缓存中未找到音频', 'error');
return originalGetUserMedia(constraints);
}
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const arrayBuffer = await wavBlob.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const sourceNode = audioContext.createBufferSource();
sourceNode.buffer = audioBuffer;
const destinationNode = audioContext.createMediaStreamDestination();
sourceNode.connect(destinationNode);
sourceNode.connect(audioContext.destination);
console.log('[自动播放录制器] ⏳ 等待录音器准备...');
showRecordNotification('⏳ 等待录音器准备...', 'info');
const delaySelector = document.getElementById('voice-delay-selector');
const delayTime = delaySelector ? parseInt(delaySelector.value) : 1000;
setTimeout(() => {
sourceNode.start();
console.log('[自动播放录制器] ✅ 虚拟麦克风已开始播放示例音频');
showRecordNotification('🎤 正在通过虚拟麦克风录音...', 'success');
setTimeout(() => {
showRecordNotification('✅ 音频播放完成,可以停止录音了', 'success');
setTimeout(() => {
autoStopRecording();
}, 1000);
}, audioBuffer.duration * 1000 + 500);
}, delayTime);
return destinationNode.stream;
} catch (error) {
console.error('[自动播放录制器] 创建虚拟麦克风流失败:', error);
showRecordNotification('❌ 创建虚拟流失败', 'error');
}
}
}
console.log('[自动播放录制器] 使用真实麦克风');
return originalGetUserMedia(constraints);
};
console.log('[自动播放录制器] ✅ getUserMedia 已劫持');
}
async function downloadAndSaveAudio(audioUrl, recordButton) {
try {
console.log('[自动播放录制器] 下载示例音频:', audioUrl);
const response = await fetch(audioUrl);
const blob = await response.blob();
const wavBlob = await convertToWAV(blob);
recordedAudioBlob = wavBlob;
recordedAudioUrl = URL.createObjectURL(wavBlob);
if (recordButton) {
const questionContainer = recordButton.closest('.oral-study-sentence') ||
recordButton.closest('.question-common-abs-reply') ||
recordButton.closest('.question-vocabulary');
if (questionContainer) {
audioCache.set(questionContainer, recordedAudioUrl);
audioBlobCache.set(questionContainer, wavBlob);
console.log('[自动播放录制器] ✅ 音频已保存到当前题目');
}
}
console.log('[自动播放录制器] ✅ 音频已保存');
} catch (error) {
console.error('[自动播放录制器] 下载音频失败:', error);
}
}
async function convertToWAV(blob) {
try {
const arrayBuffer = await blob.arrayBuffer();
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
let channelData;
if (audioBuffer.numberOfChannels === 1) {
channelData = audioBuffer.getChannelData(0);
} else {
const left = audioBuffer.getChannelData(0);
const right = audioBuffer.getChannelData(1);
channelData = new Float32Array(left.length);
for (let i = 0; i < left.length; i++) {
channelData[i] = (left[i] + right[i]) / 2;
}
}
const resampled = resampleAudio(channelData, audioBuffer.sampleRate, 16000);
const wavBuffer = encodeWAV(resampled, 16000);
return new Blob([wavBuffer], { type: 'audio/wav' });
} catch (error) {
console.error('[自动播放录制器] 转换失败,使用原始音频:', error);
return blob;
}
}
function resampleAudio(audioData, fromSampleRate, toSampleRate) {
if (fromSampleRate === toSampleRate) return audioData;
const ratio = fromSampleRate / toSampleRate;
const newLength = Math.round(audioData.length / ratio);
const result = new Float32Array(newLength);
for (let i = 0; i < newLength; i++) {
const position = i * ratio;
const index = Math.floor(position);
const fraction = position - index;
if (index + 1 < audioData.length) {
result[i] = audioData[index] * (1 - fraction) + audioData[index + 1] * fraction;
} else {
result[i] = audioData[index];
}
}
return result;
}
function encodeWAV(samples, sampleRate) {
const buffer = new ArrayBuffer(44 + samples.length * 2);
const view = new DataView(buffer);
const writeString = (offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
writeString(0, 'RIFF');
view.setUint32(4, 36 + samples.length * 2, true);
writeString(8, 'WAVE');
writeString(12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 1, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(36, 'data');
view.setUint32(40, samples.length * 2, true);
let offset = 44;
for (let i = 0; i < samples.length; i++) {
const s = Math.max(-1, Math.min(1, samples[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
offset += 2;
}
return buffer;
}
function findSampleAudio(recordButton) {
const questionContainer = recordButton ?
recordButton.closest('.oral-study-sentence') ||
recordButton.closest('.question-common-abs-reply') ||
recordButton.closest('.question-vocabulary') ||
recordButton.closest('.vocContainer') ||
recordButton.closest('.layoutBody-container.has-reply') :
null;
if (questionContainer) {
console.log('[自动播放录制器] 找到题目容器:', questionContainer.className);
if (questionContainer.classList.contains('vocContainer')) {
const vocAudio = questionContainer.querySelector('audio[src], .audio-player audio, .sound-btn audio, [class*="audio"] audio');
if (vocAudio && vocAudio.src) {
console.log('[自动播放录制器] 找到词汇卡片的音频');
return vocAudio;
}
const audioButtons = questionContainer.querySelectorAll('.sound-btn, .audio-btn, [class*="sound"], [class*="audio"]');
for (const btn of audioButtons) {
const audio = btn.querySelector('audio') || btn.parentElement.querySelector('audio');
if (audio && audio.src) {
console.log('[自动播放录制器] 找到词汇卡片按钮关联的音频');
return audio;
}
}
}
if (questionContainer.classList.contains('question-vocabulary')) {
const vocabularyAudio = questionContainer.querySelector('.question-audio audio');
if (vocabularyAudio && vocabularyAudio.src) {
console.log('[自动播放录制器] 找到词汇练习的单词发音');
return vocabularyAudio;
}
const soundAudio = questionContainer.querySelector('.soundWrap .question-audio audio');
if (soundAudio && soundAudio.src) {
console.log('[自动播放录制器] 找到词汇练习的soundWrap音频');
return soundAudio;
}
}
if (questionContainer.classList.contains('layoutBody-container') && questionContainer.classList.contains('has-reply')) {
const sentenceContainer = recordButton.closest('.oral-study-sentence');
if (sentenceContainer) {
console.log('[自动播放录制器] 检测到句子跟读练习');
const originAudio = sentenceContainer.querySelector('.question-audio.audio-origin audio[src]');
if (originAudio && originAudio.src) {
console.log('[自动播放录制器] 找到句子跟读练习的示例音频(audio-origin)');
return originAudio;
}
const audioType = window.__selectedAudioType || 'british';
const sampleAudioContainer = sentenceContainer.querySelector('.sample-audio');
if (sampleAudioContainer) {
let targetAudio = null;
if (audioType === 'british') {
const britishItem = sampleAudioContainer.querySelector('.item:first-child');
if (britishItem) {
targetAudio = britishItem.querySelector('audio');
if (targetAudio && targetAudio.src) {
console.log('[自动播放录制器] 找到句子跟读练习的英音示例');
return targetAudio;
}
}
} else {
const americanItem = sampleAudioContainer.querySelector('.item:last-child');
if (americanItem) {
targetAudio = americanItem.querySelector('audio');
if (targetAudio && targetAudio.src) {
console.log('[自动播放录制器] 找到句子跟读练习的美音示例');
return targetAudio;
}
}
}
const anyAudio = sampleAudioContainer.querySelector('audio[src]');
if (anyAudio && anyAudio.src) {
console.log('[自动播放录制器] 找到句子跟读练习的示例音频(任意类型)');
return anyAudio;
}
}
console.log('[自动播放录制器] ⚠️ 未在当前句子中找到示例音频');
return null;
}
}
const originAudio = questionContainer.querySelector('.question-audio.audio-origin audio[src]');
if (originAudio && originAudio.src) {
console.log('[自动播放录制器] 找到题目的示例音频(audio-origin)');
return originAudio;
}
const audioType = window.__selectedAudioType || 'british';
if (audioType === 'british') {
const britishAudio = questionContainer.querySelector('.sample-audio .item:first-child audio');
if (britishAudio && britishAudio.src) {
console.log('[自动播放录制器] 找到当前题目的英音示例');
return britishAudio;
}
} else {
const americanAudio = questionContainer.querySelector('.sample-audio .item:last-child audio');
if (americanAudio && americanAudio.src) {
console.log('[自动播放录制器] 找到当前题目的美音示例');
return americanAudio;
}
}
const anyAudio = questionContainer.querySelector('.sample-audio audio');
if (anyAudio && anyAudio.src) {
console.log('[自动播放录制器] 找到当前题目的示例音频');
return anyAudio;
}
}
console.log('[自动播放录制器] ⚠️ 未找到当前题目的示例音频');
return null;
}
function monitorRecordButton() {
document.addEventListener('click', async (e) => {
const recordIcon = e.target.closest('.record-icon') ||
e.target.closest('.record-fill-icon') ||
e.target.closest('.button-record');
if (recordIcon) {
currentRecordButton = recordIcon;
currentQuestionContainer = recordIcon.closest('.oral-study-sentence') ||
recordIcon.closest('.question-common-abs-reply') ||
recordIcon.closest('.question-vocabulary') ||
recordIcon.closest('.vocContainer') ||
recordIcon.closest('.layoutBody-container.has-reply');
console.log('[自动播放录制器] 检测到录音按钮点击');
if (window.__autoPlayRecordEnabled) {
const sampleAudio = findSampleAudio(recordIcon);
if (sampleAudio) {
showRecordNotification('⏳ 正在准备音频...', 'info');
await downloadAndSaveAudio(sampleAudio.src, recordIcon);
console.log('[自动播放录制器] ✅ 音频已预先加载并缓存');
} else {
console.log('[自动播放录制器] ⚠️ 未找到当前题目的示例音频 (已静默)');
}
}
}
}, true);
}
function monitorReplayAudio() {
setInterval(() => {
if (window.__autoPlayRecordEnabled) {
const questionContainers = document.querySelectorAll('.oral-study-sentence, .question-common-abs-reply, .question-vocabulary, .vocContainer, .layoutBody-container.has-reply .oral-study-sentence');
questionContainers.forEach(container => {
const replayAudio = container.querySelector('.audio-replay audio') ||
container.querySelector('.question-audio audio-player audio');
const cachedUrl = audioCache.get(container);
if (replayAudio && cachedUrl && replayAudio.src !== cachedUrl) {
console.log('[自动播放录制器] 🎯 定期检查:替换题目回放音频');
replayAudio.src = cachedUrl;
replayAudio.load();
const controlBox = container.querySelector('.audio-replay .audio-control-box') ||
container.querySelector('.audio-control-box');
if (controlBox) {
controlBox.classList.remove('disabled');
controlBox.style.display = 'flex';
}
}
});
}
checkForAudioCompleteNotification();
}, 500);
}
function checkForAudioCompleteNotification() {
const notifications = document.querySelectorAll('div, span, p, .notification, .toast, .message');
for (const notification of notifications) {
const text = notification.textContent || notification.innerText || '';
if (text.includes('音频播放完成') ||
text.includes('可以停止录音') ||
text.includes('录音完成') ||
text.includes('播放完成')) {
const rect = notification.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0 &&
rect.top >= 0 && rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth;
if (isVisible) {
console.log('[自动停止录音] 🔔 检测到音频播放完成提示:', text);
const recordingButton = document.querySelector('.record-icon.recording, .button-record.recording, .record-fill-icon.recording');
if (recordingButton) {
console.log('[自动停止录音] 🎤 发现正在录音,准备自动停止...');
setTimeout(() => {
autoStopRecording();
}, 1000);
notification.setAttribute('data-auto-handled', 'true');
break;
}
}
}
}
}
function setupURLHijack() {
const originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(obj) {
if (window.__autoPlayRecordEnabled &&
recordedAudioUrl &&
obj instanceof Blob &&
obj.type &&
(obj.type.includes('audio') || obj.type.includes('webm'))) {
console.log('[自动播放录制器] 🎯 拦截回放 URL,返回示例音频');
return recordedAudioUrl;
}
return originalCreateObjectURL(obj);
};
}
function setupAudioSrcHijack() {
const originalAudioSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'src');
Object.defineProperty(HTMLMediaElement.prototype, 'src', {
get: function() {
return originalAudioSrcDescriptor.get.call(this);
},
set: function(value) {
if (window.__autoPlayRecordEnabled &&
recordedAudioUrl &&
this.closest('.audio-replay') &&
typeof value === 'string' &&
value.startsWith('blob:')) {
console.log('[自动播放录制器] 🎯 拦截回放 src,使用示例音频');
originalAudioSrcDescriptor.set.call(this, recordedAudioUrl);
return;
}
originalAudioSrcDescriptor.set.call(this, value);
}
});
}
function autoStopRecording() {
console.log('[自动停止录音] 开始查找并停止录音...');
let recordingButton = null;
recordingButton = document.querySelector('.record-icon.recording, .button-record.recording, .record-fill-icon.recording');
if (!recordingButton && currentRecordButton) {
recordingButton = currentRecordButton;
}
if (!recordingButton) {
const allRecordButtons = document.querySelectorAll(`
.record-icon, .button-record, .record-fill-icon,
[class*="record"]:not(.audio-replay),
.microphone-btn, .mic-button
`);
for (const btn of allRecordButtons) {
if (btn.classList.contains('recording') ||
btn.closest('.recording') ||
btn.getAttribute('aria-pressed') === 'true' ||
btn.style.color === 'red' ||
btn.querySelector('.recording')) {
recordingButton = btn;
break;
}
}
}
if (recordingButton) {
console.log('[自动停止录音] ✅ 找到录音按钮,执行停止录音');
showRecordNotification('⏹️ 自动停止录音', 'info');
recordingButton.click();
setTimeout(() => {
console.log('[自动停止录音] 录音已停止');
showRecordNotification('✅ 录音已自动停止', 'success');
}, 500);
return true;
} else {
console.warn('[自动停止录音] ⚠️ 未找到正在录音的按钮');
showRecordNotification('⚠️ 未找到录音按钮', 'info');
return false;
}
}
function showRecordNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: ${type === 'success' ? '#48bb78' : type === 'error' ? '#f56565' : '#4299e1'};
color: white;
padding: 15px 30px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 100000;
font-size: 14px;
font-weight: 600;
animation: slideDown 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideUp 0.3s ease';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
function startAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
const intervalMinutes = window.__refreshInterval || 30;
const intervalMs = intervalMinutes * 60 * 1000;
let displayTime;
if (intervalMinutes < 1) {
displayTime = `${intervalMinutes * 60}秒`;
} else {
displayTime = `${intervalMinutes}分钟`;
}
console.log(`[自动刷新] 已启动,间隔: ${displayTime}`);
showRefreshNotification(`🔄 自动刷新已启动 (${displayTime}间隔)`, 'info');
autoRefreshInterval = setInterval(() => {
console.log('[自动刷新] 执行页面刷新...');
showRefreshNotification('🔄 正在刷新页面...', 'info');
setTimeout(() => {
location.reload();
}, 1000);
}, intervalMs);
autoRefreshEnabled = true;
}
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
autoRefreshEnabled = false;
console.log('[自动刷新] 已停止');
showRefreshNotification('⏹️ 自动刷新已停止', 'info');
}
function showRefreshNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: bold;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: slideDown 0.3s ease;
max-width: 300px;
`;
switch(type) {
case 'success':
notification.style.background = 'linear-gradient(135deg, #10B981, #059669)';
break;
case 'error':
notification.style.background = 'linear-gradient(135deg, #EF4444, #DC2626)';
break;
case 'info':
default:
notification.style.background = 'linear-gradient(135deg, #3B82F6, #2563EB)';
break;
}
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideUp 0.3s ease';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
async function handleVocabularyRecording() {
console.log('[挂机录音] 开始检测词汇卡片录音按钮...');
const activeSlide = document.querySelector('.swiper-slide-active');
if (!activeSlide) {
console.log('[挂机录音] 未找到当前活跃的词汇卡片');
return false;
}
const recordButton = activeSlide.querySelector('.record-fill-icon, .record-icon, .button-record');
if (!recordButton) {
console.log('[挂机录音] 当前词汇卡片没有录音按钮');
return false;
}
if (recordButton.hasAttribute('data-auto-handled')) {
console.log('[挂机录音] 当前录音按钮已处理过');
return false;
}
console.log('[挂机录音] 🎤 发现录音按钮,开始自动录音...');
try {
recordButton.setAttribute('data-auto-handled', 'true');
showRecordNotification('🎤 开始自动录音...', 'info');
recordButton.click();
console.log('[挂机录音] 📹 已点击录音按钮');
await sleep(500);
const recordDuration = window.__recordDuration || 3;
console.log(`[挂机录音] ⏱️ 录音${recordDuration}秒...`);
await sleep(recordDuration * 1000);
recordButton.click();
console.log('[挂机录音] ⏹️ 已停止录音');
showRecordNotification('✅ 录音完成,等待评分...', 'success');
return true;
} catch (error) {
console.error('[挂机录音] 处理词汇录音时出错:', error);
showRecordNotification('❌ 录音处理出错', 'error');
return false;
}
}
async function waitForScoreAppear() {
console.log('[挂机录音] 等待分数出现...');
const maxWaitTime = 10000;
const checkInterval = 200;
let waitedTime = 0;
return new Promise((resolve) => {
const checkScore = () => {
const scoreElement = document.querySelector('.practice-score .score, .score-wrapper .score');
if (scoreElement && scoreElement.textContent.trim()) {
const score = scoreElement.textContent.trim();
console.log(`[挂机录音] ✅ 分数已出现: ${score}`);
showRecordNotification(`📊 得分: ${score}`, 'success');
resolve(true);
return;
}
waitedTime += checkInterval;
if (waitedTime >= maxWaitTime) {
console.log('[挂机录音] ⚠️ 等待分数超时');
showRecordNotification('⚠️ 等待分数超时', 'info');
resolve(false);
return;
}
setTimeout(checkScore, checkInterval);
};
setTimeout(checkScore, checkInterval);
});
}
async function handleSentenceRecitationExercise() {
console.log('[挂机录音] 开始检测句子跟读练习...');
const exerciseContainer = document.querySelector('.layoutBody-container.has-reply');
if (!exerciseContainer) {
console.log('[挂机录音] 未找到句子跟读练习容器');
return false;
}
const sentenceContainers = exerciseContainer.querySelectorAll('.oral-study-sentence');
if (sentenceContainers.length === 0) {
console.log('[挂机录音] 未找到句子容器');
return false;
}
console.log(`[挂机录音] 发现 ${sentenceContainers.length} 个句子,开始逐个录音...`);
let handledCount = 0;
for (let i = 0; i < sentenceContainers.length; i++) {
const sentenceContainer = sentenceContainers[i];
const recordButton = sentenceContainer.querySelector('.record-icon:not([data-auto-handled]), .button-record:not([data-auto-handled])');
if (!recordButton) {
console.log(`[挂机录音] 第 ${i + 1} 个句子没有录音按钮,跳过`);
continue;
}
if (recordButton.hasAttribute('data-auto-handled')) {
console.log(`[挂机录音] 第 ${i + 1} 个句子已处理过,跳过`);
continue;
}
console.log(`[挂机录音] 处理第 ${i + 1}/${sentenceContainers.length} 个句子`);
try {
recordButton.setAttribute('data-auto-handled', 'true');
const success = await processSentenceRecording(recordButton, sentenceContainer, i + 1);
if (success) {
handledCount++;
console.log(`[挂机录音] 第 ${i + 1} 个句子录音成功`);
} else {
console.warn(`[挂机录音] 第 ${i + 1} 个句子录音失败`);
}
} catch (error) {
console.error(`[挂机录音] 处理第 ${i + 1} 个句子时出错:`, error);
}
if (i < sentenceContainers.length - 1) {
await sleep(1500 + Math.random() * 1000);
}
}
if (handledCount > 0) {
console.log(`[挂机录音] ✅ 成功录制了 ${handledCount} 个句子`);
showRecordNotification(`✅ 自动完成 ${handledCount} 个句子录音`, 'success');
return true;
}
return false;
}
async function processSentenceRecording(recordButton, sentenceContainer, sentenceIndex) {
return new Promise(async (resolve) => {
try {
console.log(`[挂机录音] 🎤 开始录制第 ${sentenceIndex} 个句子...`);
const sampleAudio = findSampleAudio(recordButton);
let hasSample = false;
if (sampleAudio) {
currentRecordButton = recordButton;
currentQuestionContainer = sentenceContainer;
await downloadAndSaveAudio(sampleAudio.src, recordButton);
hasSample = true;
console.log(`[挂机录音] ✅ 第 ${sentenceIndex} 句已加载示例音频`);
} else {
console.log(`[挂机录音] ⚠️ 第 ${sentenceIndex} 句未找到示例音频,将执行“盲录”模式 (服务器会修正分数)`);
}
recordButton.click();
await sleep(500);
let recordingStarted = false;
let recordingFinished = false;
let timeoutId = null;
const checkRecordingStart = () => {
const isRecording = recordButton.classList.contains('recording') ||
sentenceContainer.querySelector('.recording') ||
document.querySelector('.recording');
if (isRecording && !recordingStarted) {
recordingStarted = true;
console.log(`[挂机录音] 📹 第 ${sentenceIndex} 个句子录音已开始`);
const cachedBlob = audioBlobCache.get(sentenceContainer);
if (hasSample && cachedBlob) {
const tempAudio = new Audio(URL.createObjectURL(cachedBlob));
tempAudio.addEventListener('loadedmetadata', () => {
const duration = tempAudio.duration;
const delayTime = parseInt(document.getElementById('voice-delay-selector')?.value || '1000');
const totalTime = duration * 1000 + delayTime + 1000;
timeoutId = setTimeout(() => {
if (!recordingFinished) {
stopAndFinish();
}
}, totalTime);
});
tempAudio.load();
} else {
const recordDuration = window.__recordDuration || 3;
const defaultTime = recordDuration * 1000;
console.log(`[挂机录音] 无原声/盲录模式,将在 ${recordDuration} 秒后停止`);
timeoutId = setTimeout(() => {
if (!recordingFinished) {
stopAndFinish();
}
}, defaultTime);
}
}
};
const stopAndFinish = () => {
console.log(`[挂机录音] ⏹️ 第 ${sentenceIndex} 个句子停止录音`);
recordButton.click();
recordingFinished = true;
setTimeout(() => {
console.log(`[挂机录音] ✅ 第 ${sentenceIndex} 个句子录音完成`);
resolve(true);
}, 1000);
};
const checkInterval = setInterval(() => {
if (recordingFinished) {
clearInterval(checkInterval);
if (timeoutId) clearTimeout(timeoutId);
return;
}
checkRecordingStart();
}, 200);
setTimeout(() => {
if (!recordingFinished) {
console.warn(`[挂机录音] 第 ${sentenceIndex} 个句子录音超时强制结束`);
clearInterval(checkInterval);
if (timeoutId) clearTimeout(timeoutId);
if(recordingStarted) recordButton.click();
resolve(true);
}
}, 15000);
} catch (error) {
console.error(`[挂机录音] 处理第 ${sentenceIndex} 个句子录音时出错:`, error);
resolve(true);
}
});
}
async function handleRecordingQuestions() {
console.log('[挂机录音] 开始检测录音题...');
if (document.querySelector('.layoutBody-container.has-reply .oral-study-sentence')) {
console.log('[挂机录音] 检测到句子跟读练习,使用专门处理函数');
return await handleSentenceRecitationExercise();
}
const recordButtons = document.querySelectorAll(`
.record-icon:not(.recording):not([data-auto-handled]),
.record-fill-icon:not(.recording):not([data-auto-handled]),
.button-record:not(.recording):not([data-auto-handled]),
[class*="record"]:not(.recording):not([data-auto-handled]):not(.audio-replay),
.microphone-btn:not(.recording):not([data-auto-handled]),
.mic-button:not(.recording):not([data-auto-handled])
`);
const validRecordButtons = Array.from(recordButtons).filter(btn => {
if (btn.closest('.audio-replay') ||
btn.closest('.playback') ||
btn.classList.contains('play') ||
btn.classList.contains('pause')) {
return false;
}
const container = btn.closest('.oral-study-sentence, .question-common-abs-reply, .question-vocabulary, .vocContainer, .layoutBody-container.has-reply');
if (!container) {
return false;
}
if (container.classList.contains('vocContainer')) {
const rect = btn.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
const vocCard = btn.closest('.voc-card, .vocabulary-card, [class*="card"]');
if (vocCard) {
const cardRect = vocCard.getBoundingClientRect();
return cardRect.width > 0 && cardRect.height > 0;
}
}
return true;
});
if (validRecordButtons.length === 0) {
console.log('[挂机录音] 未发现录音题');
return false;
}
console.log(`[挂机录音] 发现 ${validRecordButtons.length} 个录音题,开始逐个处理...`);
let handledCount = 0;
for (let i = 0; i < validRecordButtons.length; i++) {
const recordButton = validRecordButtons[i];
recordButton.setAttribute('data-auto-handled', 'true');
console.log(`[挂机录音] 处理第 ${i + 1}/${validRecordButtons.length} 个录音题`);
try {
const success = await processRecordingQuestion(recordButton);
if (success) {
handledCount++;
console.log(`[挂机录音] 第 ${i + 1} 个录音题处理成功`);
} else {
console.warn(`[挂机录音] 第 ${i + 1} 个录音题处理失败`);
}
} catch (error) {
console.error(`[挂机录音] 处理第 ${i + 1} 个录音题时出错:`, error);
}
if (i < validRecordButtons.length - 1) {
await sleep(1000 + Math.random() * 1000);
}
}
if (handledCount > 0) {
console.log(`[挂机录音] ✅ 成功处理了 ${handledCount} 个录音题`);
showRecordNotification(`✅ 自动完成 ${handledCount} 个录音题`, 'success');
return true;
}
return false;
}
async function processRecordingQuestion(recordButton) {
return new Promise(async (resolve) => {
try {
currentRecordButton = recordButton;
currentQuestionContainer = recordButton.closest('.oral-study-sentence') ||
recordButton.closest('.question-common-abs-reply') ||
recordButton.closest('.question-vocabulary') ||
recordButton.closest('.vocContainer') ||
recordButton.closest('.layoutBody-container.has-reply');
if (!currentQuestionContainer) {
console.warn('[挂机录音] 无法找到题目容器,尝试盲录');
}
console.log('[挂机录音] 🎤 开始录音题自动处理...');
const sampleAudio = findSampleAudio(recordButton);
let hasSample = false;
if (sampleAudio) {
await downloadAndSaveAudio(sampleAudio.src, recordButton);
hasSample = true;
} else {
console.log('[挂机录音] 无示例音频,进入强制录音模式...');
}
recordButton.click();
await sleep(500);
let recordingStarted = false;
let recordingFinished = false;
let timeoutId = null;
const checkRecordingStart = () => {
const isRecording = recordButton.classList.contains('recording') ||
recordButton.closest('.question-container')?.querySelector('.recording') ||
document.querySelector('.recording');
if (isRecording && !recordingStarted) {
recordingStarted = true;
console.log('[挂机录音] 📹 录音已开始');
const cachedBlob = currentQuestionContainer ? audioBlobCache.get(currentQuestionContainer) : null;
if (hasSample && cachedBlob) {
const tempAudio = new Audio(URL.createObjectURL(cachedBlob));
tempAudio.addEventListener('loadedmetadata', () => {
const duration = tempAudio.duration;
const delayTime = parseInt(document.getElementById('voice-delay-selector')?.value || '1000');
const totalTime = duration * 1000 + delayTime + 1000;
console.log(`[挂机录音] 跟读模式,时长: ${duration.toFixed(2)}s`);
timeoutId = setTimeout(() => finishRecording(), totalTime);
});
tempAudio.load();
} else {
const recordDuration = window.__recordDuration || 3;
const defaultTime = recordDuration * 1000;
console.log(`[挂机录音] 强制录音模式,固定时长: ${recordDuration}s`);
timeoutId = setTimeout(() => finishRecording(), defaultTime);
}
}
};
const finishRecording = () => {
if (!recordingFinished) {
console.log('[挂机录音] ⏹️ 自动停止录音');
recordButton.click();
recordingFinished = true;
setTimeout(() => {
console.log('[挂机录音] ✅ 录音题处理完成');
resolve(true);
}, 1000);
}
};
const statusCheckInterval = setInterval(() => {
if (recordingFinished) {
clearInterval(statusCheckInterval);
return;
}
checkRecordingStart();
const isStillRecording = recordButton.classList.contains('recording') ||
recordButton.closest('.question-container')?.querySelector('.recording') ||
document.querySelector('.recording');
if (recordingStarted && !isStillRecording && !recordingFinished) {
recordingFinished = true;
clearInterval(statusCheckInterval);
if (timeoutId) clearTimeout(timeoutId);
console.log('[挂机录音] ✅ 录音自然结束');
resolve(true);
}
}, 200);
setTimeout(() => {
if (!recordingFinished) {
recordingFinished = true;
clearInterval(statusCheckInterval);
if (timeoutId) clearTimeout(timeoutId);
console.warn('[挂机录音] ⚠️ 录音超时强制跳过');
recordButton.click();
resolve(true);
}
}, 15000);
} catch (error) {
console.error('[挂机录音] 处理录音题时出错:', error);
resolve(true);
}
});
}
function createCollapsibleSection(title, storageKey) {
const section = document.createElement('div');
section.className = 'u-helper-section u-collapsible';
const header = document.createElement('div');
header.className = 'u-helper-section-header';
const titleEl = document.createElement('div');
titleEl.className = 'u-helper-section-title';
titleEl.textContent = title;
const sectionContent = document.createElement('div');
sectionContent.className = 'u-helper-section-content';
header.appendChild(titleEl);
section.appendChild(header);
section.appendChild(sectionContent);
if (title.includes('AI')) {
const pointsSection = document.createElement('div');
pointsSection.style.cssText = `
margin: -12px -12px 12px;
padding: 15px;
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
border-bottom: 1px solid rgba(0,0,0,0.05);
`;
const pointsHeader = document.createElement('div');
pointsHeader.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
`;
const pointsDisplay = document.createElement('div');
pointsDisplay.style.cssText = `
display: flex;
align-items: baseline;
gap: 8px;
`;
const pointsValue = document.createElement('span');
pointsValue.id = 'currentPointsDisplay';
pointsValue.style.cssText = `
font-size: 36px;
font-weight: bold;
color: #1890ff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI";
`;
pointsValue.textContent = userPoints;
const pointsLabel = document.createElement('span');
pointsLabel.style.cssText = `
font-size: 15px;
font-weight: 600;
color: #666;
`;
pointsLabel.textContent = '积分';
pointsDisplay.appendChild(pointsValue);
pointsDisplay.appendChild(pointsLabel);
const buyPoints = document.createElement('button');
buyPoints.onclick = () => window.showPointsPackages();
buyPoints.style.cssText = `
padding: 8px 18px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3);
`;
buyPoints.innerHTML = `
充值
`;
buyPoints.onmouseover = () => buyPoints.style.background = '#40a9ff';
buyPoints.onmouseout = () => buyPoints.style.background = '#1890ff';
pointsHeader.appendChild(pointsDisplay);
pointsHeader.appendChild(buyPoints);
const usageInfo = document.createElement('div');
usageInfo.style.cssText = `
font-size: 14px;
font-weight: 500;
color: #666;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: rgba(24,144,255,0.1);
border-radius: 6px;
`;
const costIcon = document.createElement('span');
costIcon.textContent = '💡';
costIcon.style.fontSize = '16px';
const costText = document.createElement('span');
costText.textContent = `每次AI答题消耗 ${POINTS_PER_QUESTION} 积分`;
usageInfo.appendChild(costIcon);
usageInfo.appendChild(costText);
pointsSection.appendChild(pointsHeader);
pointsSection.appendChild(usageInfo);
sectionContent.appendChild(pointsSection);
}
const icon = document.createElement('div');
icon.className = 'u-collapse-icon';
icon.innerHTML = '';
const content = document.createElement('div');
content.className = 'u-helper-section-content';
header.appendChild(titleEl);
header.appendChild(icon);
section.appendChild(header);
section.appendChild(content);
const isCollapsed = localStorage.getItem(storageKey) === 'true';
if (isCollapsed) {
section.classList.add('u-collapsed');
}
header.addEventListener('click', () => {
const collapsed = section.classList.toggle('u-collapsed');
localStorage.setItem(storageKey, collapsed);
});
return { section, content };
}
function createFloatingButton() {
const styles = `
:root {
--u-bg-color: rgba(255, 255, 255, 0.75);
--u-blur-bg-color: rgba(255, 255, 255, 0.5);
--u-border-color: rgba(0, 0, 0, 0.08);
--u-text-color-primary: #1a202c;
--u-text-color-secondary: #4a5568;
--u-text-color-tertiary: #718096;
--u-accent-color: #4299e1;
--u-accent-color-dark: #3182ce;
--u-success-color: #38a169;
--u-danger-color: #e53e3e;
--u-warning-color: #dd6b20;
--u-font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
--u-border-radius-lg: 16px;
--u-border-radius-md: 12px;
--u-border-radius-sm: 8px;
--u-shadow-lg: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--u-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.u-helper-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
display: flex;
flex-direction: column;
font-family: var(--u-font-family);
background: var(--u-bg-color);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border-radius: var(--u-border-radius-lg);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: var(--u-shadow-lg);
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
width: 320px;
opacity: 0;
transform: translateY(-20px) scale(0.95);
color: var(--u-text-color-primary);
}
.u-helper-container.u-visible {
opacity: 1;
transform: translateY(0) scale(1);
}
.u-helper-title-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 18px;
background-color: var(--u-blur-bg-color);
font-weight: 700;
cursor: move;
user-select: none;
font-size: 16px;
border-bottom: 1px solid var(--u-border-color);
flex-shrink: 0;
}
.u-helper-title {
display: flex;
align-items: center;
gap: 10px;
}
.u-helper-logo-icon {
animation: logoBreath 3s ease-in-out infinite;
transition: transform 0.3s ease;
}
.u-helper-logo-icon:hover {
animation: logoSpin 0.6s ease-in-out;
transform: scale(1.1);
}
@keyframes logoBreath {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.85;
transform: scale(1.05);
}
}
@keyframes logoSpin {
from {
transform: rotate(0deg) scale(1.1);
}
to {
transform: rotate(360deg) scale(1.1);
}
}
.u-helper-control-btn {
background: transparent;
border: none;
color: var(--u-text-color-secondary);
font-size: 20px;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
height: 28px;
width: 28px;
border-radius: var(--u-border-radius-sm);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.u-helper-control-btn:hover {
background-color: rgba(0, 0, 0, 0.08);
transform: scale(1.15);
}
.u-helper-control-btn:active {
transform: scale(0.95);
}
.u-helper-content {
display: flex;
flex-direction: column;
gap: 18px;
padding: 18px;
width: 100%;
box-sizing: border-box;
max-height: calc(100vh - 150px);
overflow-y: auto;
overflow-x: hidden;
opacity: 1;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.3s ease,
padding 0.3s ease;
}
.u-helper-content.u-minimized {
max-height: 0;
opacity: 0;
padding-top: 0;
padding-bottom: 0;
overflow: hidden;
}
.u-helper-section {
display: flex;
flex-direction: column;
padding: 14px;
background: rgba(255, 255, 255, 0.6);
border-radius: var(--u-border-radius-md);
}
.u-helper-section-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.u-helper-section.u-collapsible > .u-helper-section-header {
cursor: pointer;
margin: -12px;
padding: 12px;
}
.u-helper-section-content {
padding-top: 12px;
display: flex;
flex-direction: column;
gap: 12px;
max-height: 2000px;
opacity: 1;
overflow: hidden;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.3s ease,
padding 0.3s ease;
}
.u-collapse-icon {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
color: var(--u-text-color-tertiary);
}
.u-collapsible.u-collapsed .u-collapse-icon {
transform: rotate(-90deg);
}
.u-collapsible.u-collapsed .u-helper-section-content {
max-height: 0;
opacity: 0;
padding-top: 0;
padding-bottom: 0;
}
.u-helper-section-title {
font-size: 15px;
color: var(--u-text-color-primary);
font-weight: 700;
}
.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: 8px 10px;
border-radius: var(--u-border-radius-sm);
border: 1px solid var(--u-border-color);
text-align: center;
background-color: #fff;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
box-shadow: var(--u-shadow-md);
}
.u-helper-input:focus {
outline: none;
border-color: var(--u-accent-color);
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
}
.u-helper-file-upload-btn {
padding: 11px 18px;
border: 1px solid transparent;
border-radius: var(--u-border-radius-md);
font-size: 14px;
font-weight: 600;
color: var(--u-accent-color-dark);
background: rgba(66, 153, 225, 0.1);
cursor: pointer;
text-align: center;
transition: all 0.3s ease;
width: 100%;
box-sizing: border-box;
}
.u-helper-file-upload-btn:hover {
background: rgba(66, 153, 225, 0.2);
border-color: rgba(66, 153, 225, 0.3);
transform: translateY(-2px);
box-shadow: var(--u-shadow-md);
}
.u-helper-select-group {
display: flex;
align-items: center;
gap: 8px;
}
.u-helper-select {
flex-grow: 1;
padding: 9px 12px;
border: 1px solid var(--u-border-color);
border-radius: var(--u-border-radius-md);
font-size: 14px;
font-weight: 500;
background-color: #fff;
cursor: pointer;
width: 100%;
min-width: 0;
box-shadow: var(--u-shadow-md);
transition: all 0.2s;
}
.u-helper-select:focus {
outline: none;
border-color: var(--u-accent-color);
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
}
/* 开关样式 */
.u-helper-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
background-color: #ccc;
border-radius: 24px;
cursor: pointer;
transition: background-color 0.3s;
}
.u-helper-switch.active {
background-color: var(--u-accent-color);
}
.u-helper-switch-slider {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.u-helper-switch.active .u-helper-switch-slider {
transform: translateX(20px);
}
.u-helper-delete-btn {
background: rgba(0, 0, 0, 0.05);
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.2s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
box-sizing: border-box;
box-shadow: var(--u-shadow-md);
}
.u-helper-btn:hover {
transform: translateY(-2px);
box-shadow: var(--u-shadow-lg);
}
.u-helper-btn:active {
transform: translateY(0) scale(0.98);
box-shadow: var(--u-shadow-md);
}
.u-helper-btn-primary {
background: var(--u-accent-color);
color: white;
}
.u-helper-btn-primary:hover {
background: var(--u-accent-color-dark);
}
.u-helper-btn-success {
background: var(--u-success-color);
color: white;
}
.u-helper-btn-warning {
background: var(--u-warning-color);
color: white;
}
.u-helper-btn-danger {
background: var(--u-danger-color);
color: white;
}
.u-helper-btn-secondary {
background: #fff;
color: var(--u-text-color-secondary);
border: 1px solid var(--u-border-color);
}
.u-helper-btn-secondary:hover {
background: #f7fafc;
}
.u-helper-info-display {
padding: 12px;
background: var(--u-blur-bg-color);
border-radius: var(--u-border-radius-md);
font-size: 13px;
color: var(--u-text-color-primary);
width: 100%;
max-height: 250px;
overflow-y: auto;
overflow-x: hidden;
overflow-wrap: break-word;
white-space: pre-wrap;
line-height: 1.4;
display: none;
border: 1px solid rgba(255, 255, 255, 0.8);
box-sizing: border-box;
opacity: 0;
transform: translateY(-10px) scale(0.98);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.u-helper-info-display.u-visible {
display: block;
opacity: 1;
transform: translateY(0) scale(1);
}
.u-helper-info-display::-webkit-scrollbar { width: 6px; }
.u-helper-info-display::-webkit-scrollbar-track { background: transparent; }
.u-helper-info-display::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 3px; }
.u-helper-info-display::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); }
.u-helper-footer {
width: 100%;
padding: 10px 0;
margin-top: 10px;
border-top: 1px solid var(--u-border-color);
font-size: 13px;
color: var(--u-text-color-tertiary);
text-align: center;
font-weight: 600;
cursor: default;
user-select: none;
}
.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(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10001;
}
.product-dialog-content {
background: white;
padding: 20px;
border-radius: 12px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.product-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.product-card.selected {
border: 2px solid #4299e1;
background: #ebf8ff;
}
.product-image {
width: 100%;
height: 150px;
object-fit: cover;
border-radius: 4px;
margin-bottom: 10px;
}
.product-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.product-price {
font-size: 20px;
color: #e53e3e;
margin-bottom: 10px;
}
.product-description {
color: #666;
margin-bottom: 15px;
}
.dialog-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.dialog-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.dialog-button.primary {
background: #4299e1;
color: white;
}
.dialog-button.primary:hover {
background: #3182ce;
}
.dialog-button.secondary {
background: #e2e8f0;
color: #4a5568;
}
.dialog-button.secondary:hover {
background: #cbd5e0;
}
.product-list {
display: flex;
flex-direction: column;
gap: 12px;
margin: 20px 0;
max-height: 400px;
overflow-y: auto;
}
.product-item {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 16px 20px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
position: relative;
}
.product-item:hover {
border-color: #4299e1;
background: #f7fafc;
transform: translateX(4px);
box-shadow: 0 2px 8px rgba(66, 153, 225, 0.15);
}
.product-item.selected {
border-color: #4299e1;
background: linear-gradient(135deg, #ebf8ff 0%, #f0f9ff 100%);
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.2);
}
.product-item.selected::before {
content: '✓';
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
background: #4299e1;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
}
.product-item .product-title {
font-size: 16px;
font-weight: 600;
color: #2d3748;
margin-bottom: 8px;
}
.product-item .product-description {
font-size: 14px;
color: #718096;
margin-bottom: 8px;
}
.product-item .product-price {
font-size: 18px;
font-weight: bold;
color: #e53e3e;
}
.product-dialog-title {
font-size: 20px;
font-weight: bold;
color: #2d3748;
margin-bottom: 8px;
text-align: center;
}
.product-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
}
.keep-alive-switch {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
}
.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: #ccc;
transition: .4s;
border-radius: 22px;
}
.keep-alive-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .keep-alive-slider {
background-color: #52c41a;
}
input:checked + .keep-alive-slider:before {
transform: translateX(18px);
}
`;
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 = `
`;
setTimeout(() => {
const noticeBtn = document.getElementById('u-notice-btn');
if (noticeBtn) {
noticeBtn.onmouseover = () => {
noticeBtn.style.background = '#ffadd2';
noticeBtn.style.color = 'white';
};
noticeBtn.onmouseout = () => {
noticeBtn.style.background = '#fff0f6';
noticeBtn.style.color = '#eb2f96';
};
noticeBtn.onclick = (e) => {
e.stopPropagation();
e.preventDefault();
if (typeof showAnnouncement === 'function') {
showAnnouncement();
} else {
alert('公告功能正在初始化,请稍后再试...');
}
};
noticeBtn.onmousedown = (e) => e.stopPropagation();
}
}, 800);
const buttonContainer = document.createElement('div');
const minimizeButton = document.createElement('button');
minimizeButton.innerHTML = '⚊';
minimizeButton.className = 'u-helper-control-btn';
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.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background: linear-gradient(135deg, rgba(66, 153, 225, 0.1) 0%, rgba(49, 130, 206, 0.1) 100%);
border-radius: 8px;
border: 1px solid rgba(66, 153, 225, 0.2);
`;
const aiToggleLabel = document.createElement('label');
aiToggleLabel.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: var(--u-text-color-primary);
`;
const aiToggle = document.createElement('input');
aiToggle.type = 'checkbox';
aiToggle.checked = useKimiAI;
aiToggle.style.cssText = `
width: 18px;
height: 18px;
cursor: pointer;
`;
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' : '#6B7280';
statusText.style.background = useKimiAI ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.1)';
}
};
aiToggleLabel.appendChild(aiToggle);
aiToggleLabel.appendChild(document.createTextNode('启用 AI 答题'));
const statusIndicator = document.createElement('span');
statusIndicator.className = 'ai-status';
statusIndicator.textContent = useKimiAI ? '已启用' : '已禁用';
statusIndicator.style.cssText = `
font-size: 12px;
font-weight: 600;
color: ${useKimiAI ? '#10B981' : '#6B7280'};
padding: 4px 12px;
border-radius: 12px;
background: ${useKimiAI ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.1)'};
`;
aiToggleContainer.appendChild(aiToggleLabel);
aiToggleContainer.appendChild(statusIndicator);
aiConfigContent.appendChild(aiToggleContainer);
const aiTip = document.createElement('div');
aiTip.style.cssText = `
margin-top: 10px;
padding: 8px 12px;
background: rgba(255, 193, 7, 0.1);
border-left: 3px solid #FFC107;
border-radius: 4px;
font-size: 12px;
color: #666;
line-height: 1.5;
`;
aiTip.innerHTML = `
💡 提示:
• 有在线题库时,答题用题库,评论用AI
• 无在线题库时,答题用AI,评论用默认文本
`;
aiConfigContent.appendChild(aiTip);
const defaultCommentContainer = document.createElement('div');
defaultCommentContainer.style.cssText = `
margin-top: 10px;
padding: 12px;
background: rgba(255, 255, 255, 0.6);
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.08);
`;
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: #666;
line-height: 1.4;
`;
defaultCommentTip.textContent = '当AI未启用或无在线题库时,将使用此文本作为评论内容';
defaultCommentContainer.appendChild(defaultCommentLabel);
defaultCommentContainer.appendChild(defaultCommentInput);
defaultCommentContainer.appendChild(defaultCommentTip);
aiConfigContent.appendChild(defaultCommentContainer);
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.style.cssText = `
flex-shrink: 0; width: 36px; height: 36px; border: 1px solid var(--u-border-color);
background: #fff; border-radius: var(--u-border-radius-md); cursor: pointer;
display: flex; align-items: center; justify-content: center;
`;
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 = '8px';
checkOnlineBankBtn.style.fontSize = '13px';
checkOnlineBankBtn.style.padding = '6px 12px';
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);
console.log(`[在线核对] 题库清洗后: ${cleanBankName}`);
console.log(`[在线核对] 页面清洗后: ${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.style.cssText = `
margin-top: 10px;
padding: 8px 12px;
background-color: #f8f9fa;
border-radius: var(--u-border-radius-sm);
border-left: 3px solid #007bff;
font-size: 12px;
color: #495057;
display: none;
`;
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: 'http://42.194.169.45:8849/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();
}
console.log(`[自动匹配] 当前页面课程名: "${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;
console.log(`[自动匹配] 发现匹配题库: "${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;
console.log(`[自动匹配] 未找到匹配项,回退到上次选择: ${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 fileInputButton = document.createElement('label');
fileInputButton.htmlFor = 'question-bank-uploader';
fileInputButton.textContent = '上传新题库 (.json)';
fileInputButton.className = 'u-helper-file-upload-btn';
const buyBankLink = document.createElement('a');
buyBankLink.href = 'javascript:void(0)';
buyBankLink.textContent = '点我购买题库';
buyBankLink.style.cssText = 'display: block; text-align: center; margin: 10px 0; color: #4299e1; text-decoration: underline; cursor: pointer;';
buyBankLink.onclick = async () => {
let products = [];
try {
products = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: 'http://42.194.169.45:8890/api/products',
onload: function(response) {
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
resolve(data);
} catch (e) {
reject(new Error('解析JSON失败'));
}
} else {
reject(new Error(`HTTP error! status: ${response.status}`));
}
},
onerror: function(error) {
reject(new Error('网络请求失败'));
}
});
});
} catch (error) {
console.error("Could not fetch products:", error);
alert("无法加载题库列表,请确保API服务器正在运行。");
return;
}
let selectedProduct = null;
const dialog = document.createElement('div');
dialog.className = 'product-dialog';
const content = document.createElement('div');
content.className = 'product-dialog-content';
content.innerHTML = `
📚 选择题库
您的充值UID
${userId}
💡 支付时请备注此UID
${products.map(p => `
${p.title}
${p.description}
${p.price}
`).join('')}
未找到包含该关键词的题库
`;
dialog.appendChild(content);
document.body.appendChild(dialog);
const productItems = content.querySelectorAll('.product-item');
const confirmButton = content.querySelector('#confirmButton');
const searchInput = content.querySelector('#bankSearchInput');
const noResultTip = content.querySelector('#noResultTip');
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value.toLowerCase().trim();
let hasResult = false;
productItems.forEach(item => {
const title = item.getAttribute('data-search-text');
if (title.includes(keyword)) {
item.style.display = 'block';
hasResult = true;
} else {
item.style.display = 'none';
}
});
if (noResultTip) {
noResultTip.style.display = hasResult ? 'none' : 'block';
}
});
productItems.forEach(item => {
item.addEventListener('click', () => {
productItems.forEach(i => i.classList.remove('selected'));
item.classList.add('selected');
selectedProduct = products.find(p => p.id === item.dataset.id);
confirmButton.disabled = false;
});
});
content.querySelector('#cancelButton').addEventListener('click', () => {
document.body.removeChild(dialog);
});
confirmButton.addEventListener('click', async () => {
if (!selectedProduct) return;
document.body.removeChild(dialog);
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10001;
`;
const modalContent = document.createElement('div');
modalContent.style.cssText = `
background: white;
padding: 20px;
border-radius: 12px;
text-align: center;
max-width: 90%;
width: 400px;
max-height: 90vh; /* 关键:限制最大高度为视口高度的90% */
overflow-y: auto; /* 关键:内容过多时显示滚动条 */
box-sizing: border-box; /* 确保padding包含在宽度内 */
position: relative; /* 相对定位 */
`;
const PAYMENT_QR_CODE_BASE64 = 'data:image/jpeg;base64,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';
modalContent.innerHTML = `
💳 请使用微信扫码支付
已选择题库:
${selectedProduct.title}
${selectedProduct.price}
您的充值UID
${userId}
💡 点击复制,支付时请在备注填写此UID
⚠️ 重要提示:
🔑 付款时请在备注栏填写上方显示的UID
⏱️ 支付完成后,题库权限将自动开通
`;
modal.appendChild(modalContent);
document.body.appendChild(modal);
modal.appendChild(modalContent);
document.body.appendChild(modal);
const cancelPaymentBtn = modalContent.querySelector('#cancelPayment');
const confirmPaymentBtn = modalContent.querySelector('#confirmPayment');
cancelPaymentBtn.onclick = () => {
document.body.removeChild(modal);
};
confirmPaymentBtn.onclick = () => {
const uid = userId;
const courseName = selectedProduct.title;
const price = selectedProduct.price;
if (uid && courseName) {
GM_xmlhttpRequest({
method: 'POST',
url: 'http://42.194.169.45:8850/api/payment-request',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
uid: uid,
courseName: courseName,
price: price,
timestamp: new Date().toISOString(),
status: 'pending'
}),
onload: function(response) {
try {
const result = JSON.parse(response.responseText);
if (result.status === 'success') {
console.log('✅ 支付信息已提交到授权管理系统');
} else {
console.error('❌ 提交支付信息失败:', result.message);
}
} catch (e) {
console.error('❌ 解析服务器响应失败:', e);
}
},
onerror: function(error) {
console.error('❌ 发送支付信息到服务器失败:', error);
}
});
}
document.body.removeChild(modal);
alert('感谢您的购买!支付信息已提交,管理员将尽快为您授权。');
};
});
};
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.id = 'question-bank-uploader';
fileInput.accept = '.json';
fileInput.style.display = 'none';
const localBankSelectorGroup = document.createElement('div');
localBankSelectorGroup.className = 'u-helper-select-group';
const localBankSelector = document.createElement('select');
localBankSelector.className = 'u-helper-select';
const clearSelectedBankButton = document.createElement('button');
clearSelectedBankButton.innerHTML = '';
clearSelectedBankButton.title = '删除选中的题库';
clearSelectedBankButton.className = 'u-helper-delete-btn';
localBankSelectorGroup.appendChild(localBankSelector);
localBankSelectorGroup.appendChild(clearSelectedBankButton);
fileManagementContent.appendChild(fileInputButton);
fileManagementContent.appendChild(buyBankLink);
fileManagementContent.appendChild(fileInput);
fileManagementContent.appendChild(onlineBankSection);
const localBankLabel = document.createElement('div');
localBankLabel.className = 'u-helper-label';
localBankLabel.textContent = '本地题库选择';
localBankLabel.style.marginTop = '15px';
localBankLabel.style.marginBottom = '8px';
fileManagementContent.appendChild(localBankLabel);
fileManagementContent.appendChild(localBankSelectorGroup);
const { section: delaySettingsContainer, content: delaySettingsContent } = createCollapsibleSection('⏱️ 自动化延迟', 'u-collapse-delay');
const answerDelayContainer = document.createElement('div');
answerDelayContainer.className = 'u-helper-input-row';
const answerDelayLabel = document.createElement('label');
answerDelayLabel.textContent = '答案填入延迟';
answerDelayLabel.className = 'u-helper-label';
const answerDelayInput = document.createElement('input');
answerDelayInput.type = 'number';
answerDelayInput.min = '100';
answerDelayInput.max = '5000';
answerDelayInput.value = localStorage.getItem('u-answer-delay') || '800';
answerDelayInput.className = 'u-helper-input';
answerDelayInput.addEventListener('change', () => {
localStorage.setItem('u-answer-delay', answerDelayInput.value);
});
const pageDelayContainer = document.createElement('div');
pageDelayContainer.className = 'u-helper-input-row';
const pageDelayLabel = document.createElement('label');
pageDelayLabel.textContent = '页面切换延迟';
pageDelayLabel.className = 'u-helper-label';
const pageDelayInput = document.createElement('input');
pageDelayInput.type = 'number';
pageDelayInput.min = '1000';
pageDelayInput.max = '10000';
pageDelayInput.value = localStorage.getItem('u-page-delay') || '3500';
pageDelayInput.className = 'u-helper-input';
pageDelayInput.addEventListener('change', () => {
localStorage.setItem('u-page-delay', pageDelayInput.value);
});
answerDelayContainer.appendChild(answerDelayLabel);
answerDelayContainer.appendChild(answerDelayInput);
pageDelayContainer.appendChild(pageDelayLabel);
pageDelayContainer.appendChild(pageDelayInput);
delaySettingsContent.appendChild(answerDelayContainer);
delaySettingsContent.appendChild(pageDelayContainer);
const keepAliveContainer = document.createElement('div');
keepAliveContainer.className = 'u-helper-input-row';
keepAliveContainer.style.marginTop = '10px';
keepAliveContainer.style.paddingTop = '10px';
keepAliveContainer.style.borderTop = '1px solid #e8e8e8';
keepAliveContainer.style.display = 'flex';
keepAliveContainer.style.justifyContent = 'space-between';
keepAliveContainer.style.alignItems = 'center';
const keepAliveLabel = document.createElement('label');
keepAliveLabel.textContent = '自动保活';
keepAliveLabel.className = 'u-helper-label';
keepAliveLabel.title = '防止学习超时,自动模拟用户活动';
const keepAliveSwitch = document.createElement('label');
keepAliveSwitch.className = 'keep-alive-switch';
const keepAliveCheckbox = document.createElement('input');
keepAliveCheckbox.type = 'checkbox';
keepAliveCheckbox.id = 'keepAliveToggle';
const keepAliveSlider = document.createElement('span');
keepAliveSlider.className = 'keep-alive-slider';
keepAliveSwitch.appendChild(keepAliveCheckbox);
keepAliveSwitch.appendChild(keepAliveSlider);
keepAliveCheckbox.addEventListener('change', () => {
keepAliveSystem.toggle();
});
keepAliveContainer.appendChild(keepAliveLabel);
keepAliveContainer.appendChild(keepAliveSwitch);
delaySettingsContent.appendChild(keepAliveContainer);
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
function dragStart(e) {
if (e.type === "mousedown") {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
isDragging = true;
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
function drag(e) {
if (isDragging) {
e.preventDefault();
if (e.clientX) {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
container.style.transform = `translate(${currentX}px, ${currentY}px)`;
}
}
}
titleBar.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
let isMinimized = false;
minimizeButton.addEventListener('click', () => {
isMinimized = !isMinimized;
if (isMinimized) {
contentContainer.classList.add('u-minimized');
} else {
contentContainer.classList.remove('u-minimized');
}
minimizeButton.innerHTML = isMinimized ? '+' : '⚊';
container.style.width = isMinimized ? 'auto' : '320px';
});
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const fileContent = e.target.result;
JSON.parse(fileContent);
const collection = getBankCollection();
collection[file.name] = fileContent;
saveBankCollection(collection);
localStorage.setItem('u-question-bank-selected', file.name);
console.log(`题库 "${file.name}" 已成功添加并保存。`);
updateBankSelectorUI();
fileInput.value = '';
} catch (error) {
console.error('加载题库文件失败:', error);
alert('加载失败,请检查文件是否为有效的JSON格式。');
}
};
reader.readAsText(file, 'UTF-8');
}
});
localBankSelector.addEventListener('change', () => {
const selectedFile = localBankSelector.value;
if (selectedFile) {
const collection = getBankCollection();
const fileContent = collection[selectedFile];
if(fileContent) {
try {
loadedQuestionBank = JSON.parse(fileContent);
localStorage.setItem('u-question-bank-selected', selectedFile);
console.log(`已切换到题库: ${selectedFile}`);
} catch(e) {
console.error(`解析缓存的题库 ${selectedFile} 失败:`, e);
loadedQuestionBank = null;
}
}
}
});
clearSelectedBankButton.addEventListener('click', () => {
const selectedFile = localBankSelector.value;
if (selectedFile && confirm(`确定要删除题库 "${selectedFile}" 吗?此操作不可撤销。`)) {
const collection = getBankCollection();
delete collection[selectedFile];
saveBankCollection(collection);
const lastSelected = localStorage.getItem('u-question-bank-selected');
if (lastSelected === selectedFile) {
localStorage.removeItem('u-question-bank-selected');
loadedQuestionBank = null;
}
console.log(`题库 "${selectedFile}" 已被删除。`);
updateBankSelectorUI();
}
});
function getBankCollection() {
const stored = localStorage.getItem('u-question-bank-collection');
return stored ? JSON.parse(stored) : {};
}
function saveBankCollection(collection) {
localStorage.setItem('u-question-bank-collection', JSON.stringify(collection));
}
function updateBankSelectorUI() {
const collection = getBankCollection();
const lastSelected = localStorage.getItem('u-question-bank-selected');
const bankNames = Object.keys(collection);
localBankSelector.innerHTML = '';
loadedQuestionBank = null;
if (bankNames.length === 0) {
const option = document.createElement('option');
option.textContent = '暂无题库';
option.disabled = true;
localBankSelector.appendChild(option);
clearSelectedBankButton.style.display = 'none';
} else {
bankNames.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
localBankSelector.appendChild(option);
});
const selectedFile = bankNames.includes(lastSelected) ? lastSelected : bankNames[0];
localBankSelector.value = selectedFile;
try {
loadedQuestionBank = JSON.parse(collection[selectedFile]);
localStorage.setItem('u-question-bank-selected', selectedFile);
console.log(`已自动加载题库: ${selectedFile}`);
} catch(e) {
console.error(`解析缓存的题库 ${selectedFile} 失败:`, e);
}
clearSelectedBankButton.style.display = 'flex';
}
}
updateBankSelectorUI();
const autoSelectButton = document.createElement('button');
autoSelectButton.className = 'u-helper-btn u-helper-btn-primary';
autoSelectButton.innerHTML = '自动选择答案';
autoSelectButton.style.fontSize = '15px';
autoSelectButton.style.fontWeight = '600';
autoSelectButton.style.padding = '12px 18px';
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 = '600';
autoRunButton.style.padding = '12px 18px';
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 恶搞之家';
buttonContainer.appendChild(minimizeButton);
titleBar.appendChild(buttonContainer);
contentContainer.appendChild(aiConfigSection);
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: 12px;
background: linear-gradient(135deg, rgba(72, 187, 120, 0.1) 0%, rgba(56, 161, 105, 0.1) 100%);
border-radius: 8px;
border: 1px solid rgba(72, 187, 120, 0.2);
margin-bottom: 12px;
`;
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' : '#6B7280';
statusText.style.background = e.target.checked ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.1)';
}
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: 600;
color: ${voiceToggle.checked ? '#10B981' : '#6B7280'};
padding: 4px 12px;
border-radius: 12px;
background: ${voiceToggle.checked ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.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: 12px; margin-top: 10px; margin-bottom: 10px;
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-radius: 8px; border: 1px solid #90caf9;
transition: all 0.3s;
`;
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: #1565c0; 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;
const currentPoints = parseInt(window.userPoints || 0);
const selectedBank = localStorage.getItem('selectedOnlineBank');
const hasValidBank = selectedBank && selectedBank.trim() !== "";
const canEnable = currentPoints > 0 || 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 = '积分不足且未连接题库,无法开启';
}
};
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.3s;
`;
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.3s;
`;
const selectedAudioType = localStorage.getItem('selectedAudioType') || 'british';
window.__selectedAudioType = selectedAudioType;
function updateAudioTypeButtons() {
if (window.__selectedAudioType === 'british') {
britishBtn.style.background = '#48bb78';
britishBtn.style.color = 'white';
americanBtn.style.background = '#fff';
americanBtn.style.color = 'var(--u-text-color-secondary)';
} else {
americanBtn.style.background = '#48bb78';
americanBtn.style.color = 'white';
britishBtn.style.background = '#fff';
britishBtn.style.color = 'var(--u-text-color-secondary)';
}
}
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);
console.log('[录音设置] 录音时长已设置为:', 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');
autoRefreshEnabled = true;
}
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 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;
window.__refreshInterval = parseFloat(savedRefreshInterval);
refreshIntervalSelector.addEventListener('change', function() {
const interval = this.value;
localStorage.setItem('u-helper-refresh-interval', interval);
window.__refreshInterval = parseFloat(interval);
let displayTime;
if (parseFloat(interval) < 1) {
displayTime = `${parseFloat(interval) * 60}秒`;
} else {
displayTime = `${interval}分钟`;
}
console.log('[自动刷新] 刷新间隔已设置为:', displayTime);
if (autoRefreshEnabled) {
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');
window.__refreshAfterPopupBlock = true;
}
popupRefreshSwitch.addEventListener('click', function() {
const isActive = this.classList.contains('active');
if (isActive) {
this.classList.remove('active');
localStorage.setItem('u-helper-popup-refresh', 'false');
window.__refreshAfterPopupBlock = false;
showRefreshNotification('🚫 已禁用弹窗拦截后刷新', 'info');
} else {
this.classList.add('active');
localStorage.setItem('u-helper-popup-refresh', 'true');
window.__refreshAfterPopupBlock = true;
showRefreshNotification('✅ 已启用弹窗拦截后刷新', 'success');
}
});
popupRefreshContainer.appendChild(popupRefreshLabel);
popupRefreshContainer.appendChild(popupRefreshSwitch);
refreshSettingsContent.appendChild(popupRefreshContainer);
contentContainer.appendChild(refreshSettingsSection);
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;
console.log('[视频助手] 自动跳过已关闭');
} else {
this.classList.add('active');
localStorage.setItem('u-video-skip', 'true');
window.__videoSkipEnabled = true;
console.log('[视频助手] 自动跳过已开启');
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;
console.log(`[视频助手] 已将视频速度调整为 ${newSpeed}x`);
});
});
videoSpeedContainer.appendChild(videoSpeedLabel);
videoSpeedContainer.appendChild(videoSpeedSelector);
videoSettingsContent.appendChild(videoSpeedContainer);
contentContainer.appendChild(videoSettingsSection);
contentContainer.appendChild(autoSelectButton);
contentContainer.appendChild(autoRunButton);
contentContainer.appendChild(footerBar);
container.appendChild(titleBar);
container.appendChild(contentContainer);
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 searchAnswersInJson(jsonContent, chapterContent, topicContent) {
try {
function formatJsonAnswer(answerObj) {
try {
let lines = [];
if (Array.isArray(answerObj)) {
lines = answerObj.map((item, index) => {
const trimmedItem = item.toString().trim();
return /^\d+[\.\))]/.test(trimmedItem) ? trimmedItem : `${index + 1}) ${trimmedItem}`;
});
} else if (typeof answerObj === 'object' && answerObj !== null) {
const allKeys = Object.keys(answerObj).sort((a, b) => parseInt(a) - parseInt(b));
allKeys.forEach(key => {
let value = answerObj[key];
if (typeof value === 'object' && value !== null) value = Object.values(value).join(' ');
const formattedKey = key.trim().replace(/\.$/, ')');
lines.push(`${formattedKey} ${value}`);
});
} else {
return [];
}
if (lines.some(line => line.includes('|||'))) {
const fillInAnswers = [], textAnswers = [];
lines.forEach(line => {
const parts = line.split('|||');
const prefix = (line.match(/^(\d+[\.\))]\s*)/) || [''])[0];
if (parts.length > 1) {
fillInAnswers.push(prefix + parts[0].replace(/^(\d+[\.\))]\s*)/, '').trim());
textAnswers.push(prefix + parts[1].trim());
} else {
fillInAnswers.push(line);
}
});
const groups = [];
if (fillInAnswers.length > 0) groups.push(fillInAnswers.join('\n'));
if (textAnswers.length > 0) groups.push(textAnswers.join('\n'));
return groups;
}
const numberCounts = lines.reduce((acc, line) => {
const match = line.match(/^(\d+)/);
if (match) acc[match[1]] = (acc[match[1]] || 0) + 1;
return acc;
}, {});
const hasDuplicates = Object.values(numberCounts).some(count => count > 1);
if (hasDuplicates) {
console.log("检测到重复题号,启用特殊分组逻辑。");
const group1 = [];
const group2 = [];
const spares = [];
const seenNumbers = new Set();
lines.forEach(line => {
const numMatch = line.match(/^(\d+)/);
if (numMatch) {
const num = numMatch[1];
if (numberCounts[num] > 1) {
if (seenNumbers.has(num)) {
group1.push(line);
} else {
group2.push(line);
seenNumbers.add(num);
}
} else {
spares.push(line);
}
}
});
const finalGroup1 = [...group1, ...spares].join('\n');
const finalGroup2 = [...group2, ...spares].join('\n');
console.log("分组完成,生成两组包含多余项的答案。");
return [finalGroup2, finalGroup1];
}
return [lines.join('\n')];
} catch (error) {
console.error('格式化答案时出错:', error);
return [];
}
}
console.log('--- 开始在JSON题库中搜索答案 ---');
const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span');
const navigationPath = Array.from(breadcrumbs).map(span => span.textContent.trim());
const unitName = navigationPath.length > 1 ? navigationPath[1] : '';
const sectionName = navigationPath.length > 2 ? navigationPath[2] : '';
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() : '';
console.log(`导航路径: ${navigationPath.join(' > ')}`);
console.log(`搜索范围: 单元='${unitName}', 区域='${sectionName}'`);
console.log(`搜索关键字: 主题='${activeTopicName}', 子主题='${subTopicName}'`);
let targetUnit = null;
let targetUnitKey = null;
if (unitName) {
const normalizedUnitName = unitName.toLowerCase().replace(/['\s-]/g, '');
for (const [key, value] of Object.entries(jsonContent)) {
const normalizedKey = key.toLowerCase().replace(/['\s-]/g, '');
if (normalizedKey.includes(normalizedUnitName)) {
targetUnit = value;
targetUnitKey = key;
console.log(`成功匹配到单元: "${key}"`);
break;
}
}
}
if (!targetUnit) {
console.log('在题库中未能匹配到当前单元。');
return [];
}
let searchScope = targetUnit;
let scopeName = targetUnitKey;
console.log(`搜索范围已设置为整个单元: "${scopeName}",以提高定位准确性。`);
const searchKeywords = [...new Set([activeTopicName, subTopicName])].filter(Boolean).map(s => s.toLowerCase());
const foundAnswers = [];
function searchRecursive(obj, path = []) {
if (!obj || typeof obj !== 'object') return;
const isAnswerObject = Array.isArray(obj) || Object.keys(obj).some(k => /^\d+[\.\))]?$/.test(k));
if (isAnswerObject) {
const currentPathStr = path.join(' ').toLowerCase();
const allKeywordsMatch = searchKeywords.every(keyword => currentPathStr.includes(keyword));
if (allKeywordsMatch) {
const formattedAnswers = formatJsonAnswer(obj);
if (formattedAnswers.length > 0) {
foundAnswers.push(...formattedAnswers);
console.log(`在路径 [${path.join(' -> ')}] 下找到 ${formattedAnswers.length} 组匹配答案`);
}
}
return;
}
for (const [key, value] of Object.entries(obj)) {
searchRecursive(value, [...path, key]);
}
}
console.log(`开始在 "${scopeName}" 中搜索, 关键字: ${searchKeywords.join(', ')}`);
searchRecursive(searchScope, []);
console.log(`--- 搜索完成, 找到 ${foundAnswers.length} 组答案 ---`);
const uniqueAnswers = [...new Set(foundAnswers)];
if (uniqueAnswers.length > 0) {
console.log('找到的唯一答案:', uniqueAnswers);
}
return uniqueAnswers;
} catch (error) {
console.error('JSON题库搜索错误:', error);
return [];
}
}
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() {
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 {
console.log('检查特殊填空题结构...');
const scoopContainers = document.querySelectorAll('.fe-scoop');
if (!scoopContainers || scoopContainers.length === 0) {
console.log('未找到特殊填空题结构');
return false;
}
console.log(`找到 ${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);
console.log('找到可用填空词:', word);
}
});
if (availableWords.length === 0) {
const wordsList = document.querySelectorAll('.words-color');
wordsList.forEach(word => {
const wordText = word.textContent.trim();
if (wordText) {
availableWords.push(wordText);
console.log('从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) {
console.log('未能提取填空题信息');
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";
console.log('生成的AI提示:', prompt);
console.log('正在请求AI回答...');
const aiAnswer = await askKimi(prompt);
if (!aiAnswer) {
console.log('未能获取AI答案');
return false;
}
const answers = aiAnswer.split('\n').filter(line => /^\d+\./.test(line));
console.log('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();
console.log(`准备填写题目 ${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) {
console.log(`找到填空输入框,使用选择器: ${selector}`);
break;
}
}
if (input) {
await simulateHumanBehavior(input);
input.value = answer + '\n';
console.log(`已添加换行符: ${answer}\\n`);
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('blur', { bubbles: true }));
} else {
console.log('未找到填空题输入框');
}
await new Promise(resolve => setTimeout(resolve, getAnswerDelay()));
}
return true;
} catch (error) {
console.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() : '';
console.log('题目指示:', directionsText);
console.log('尝试从控制台输出获取题目信息');
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;
console.log(`找到题目容器: ${selector}`);
break;
}
}
if (!mainContainer) {
console.log('未找到题目容器,使用默认查询');
mainContainer = document;
}
const availableWords = [];
const optionPlaceholders = mainContainer.querySelectorAll('.option-placeholder');
optionPlaceholders.forEach(placeholder => {
const word = placeholder.textContent.trim();
if (word) {
availableWords.push(word);
console.log('找到可用填空词:', 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);
console.log(`使用选择器 ${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(' ');
console.log(`从段落中提取填空题文本: ${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(' ');
console.log(`使用选择器 ${selector} 找到题目文本`);
break;
}
}
}
if (!questionText) {
questionText = element.textContent.trim();
console.log('使用元素自身文本作为题目内容');
}
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';
console.log(`题目 ${number} 是填空题 (通过特殊类识别)`);
} else if (element.querySelector('textarea')) {
type = 'text';
console.log(`题目 ${number} 是文本题`);
} else if (element.querySelector('input[type="text"]')) {
type = 'fill-in';
console.log(`题目 ${number} 是填空题`);
} else if (element.querySelector('input[type="radio"]')) {
type = 'single-choice';
console.log(`题目 ${number} 是单选题`);
} else if (element.querySelector('input[type="checkbox"]') ||
element.querySelector('.MultipleChoice--checkbox-item-34A_-')) {
type = 'multiple-choice';
console.log(`题目 ${number} 是多选题`);
} else if (element.querySelector('ul.single-choice--options-29v2W, ul[class*="single-choice"]')) {
type = 'single-choice';
console.log(`题目 ${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';
console.log(`题目 ${number} 是多选题 (通过选项和标题识别)`);
} else {
type = 'single-choice';
console.log(`题目 ${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';
console.log(`题目 ${number} 是填空题 (通过父元素识别)`);
} else if (parentElement && parentElement.querySelector('.option-wrap, .option.isNotReview, .caption')) {
type = 'single-choice';
console.log(`题目 ${number} 是选择题 (通过父元素识别)`);
} else {
type = 'text';
console.log(`题目 ${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";
console.log('生成的AI提示:', prompt);
return {
prompt: prompt,
questions: questions
};
} catch (error) {
console.error('解析题目时发生错误:', error);
return null;
}
}
async function autoSelectAnswers() {
try {
console.log('开始自动选择答案');
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;
console.log(`📊 答题状态检查: 已答 ${answeredCount}/${totalQuestions} 题`);
if (answeredCount >= totalQuestions && totalQuestions > 0) {
console.log('✅ 所有题目已答完,跳过重复答题');
return true;
}
}
let selectedBank = document.getElementById('online-bank-selector')?.value;
if (!selectedBank) {
const stored = localStorage.getItem('selectedOnlineBank');
if (stored && stored !== "") {
selectedBank = stored;
console.log(`[防扣分机制] 界面下拉框未就绪,强制使用本地缓存配置: "${selectedBank}"`);
}
}
if (selectedBank) {
console.log(`[在线题库] 模式启动,选择的题库: "${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) {
console.log('[版本检测] 检测为普通版');
exerciseId = urlIds.length > 1 ? urlIds[urlIds.length - 2] : urlIds[0];
} else {
console.log('[版本检测] 检测为AI版');
exerciseId = urlIds[urlIds.length - 1];
}
if (!exerciseId && urlIds.length > 0) {
exerciseId = urlIds[urlIds.length - 1];
console.log('[备用方案] 采用最后一个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 isMultiPageExercise = isU_G_Format || isPracticingTask;
if (isMultiPageExercise) {
const reason = isU_G_Format ? `ID格式 (${exerciseId})` : `Practicing任务`;
console.log(`[多页教材] 检测到多页教材模式 (原因: ${reason})`);
if (multiPageMode.exerciseId !== exerciseId) {
multiPageMode.pageIndex = 0;
multiPageMode.totalAnswers = [];
multiPageMode.isActive = true;
multiPageMode.exerciseId = exerciseId;
console.log(`[多页教材] 新练习开始,重置页面索引为 0`);
} else {
console.log(`[多页教材] 当前页面索引: ${multiPageMode.pageIndex}`);
}
} else {
multiPageMode.isActive = false;
multiPageMode.exerciseId = null;
multiPageMode.pageIndex = 0;
multiPageMode.totalAnswers = [];
}
if (!uid) {
alert('无法获取用户UID,无法使用在线题库。');
return false;
}
if (!exerciseId) {
const userInputId = prompt('无法自动识别练习ID,请手动输入练习ID(格式如:u3g162):', 'u3g162');
if (userInputId && /^u\d+g\d+$/i.test(userInputId)) {
exerciseId = userInputId.toLowerCase();
console.log(`[在线题库] 用户手动输入ID: ${exerciseId}`);
} else {
alert('练习ID格式不正确或用户取消输入,无法查询在线题库。');
return false;
}
}
console.log(`[在线题库] 查询参数: UID=${uid}, Course=${selectedBank}, ID=${exerciseId}`);
try {
const answers = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: 'http://42.194.169.45:8849/api/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('网络请求失败'))
});
});
console.log('[在线题库] 获取到答案:', answers);
console.log('[在线题库] 答案类型:', typeof answers, '是否为数组:', Array.isArray(answers));
if (!Array.isArray(answers)) {
console.error('[在线题库] 错误:服务器返回的answers不是数组!', answers);
alert('题库数据格式错误,请检查服务器返回的数据。');
return false;
}
if (isMultiPageExercise) {
if (multiPageMode.totalAnswers.length === 0) {
multiPageMode.totalAnswers = [...answers];
console.log(`[多页教材] 存储了 ${answers.length} 个答案供后续使用`);
}
updateMultiPageStatus();
const currentPageQuestionCount = getCurrentPageQuestionCount();
console.log(`[多页教材] 当前页面检测到 ${currentPageQuestionCount} 个题目`);
const startIndex = multiPageMode.pageIndex;
const endIndex = startIndex + currentPageQuestionCount;
const currentPageAnswers = multiPageMode.totalAnswers.slice(startIndex, endIndex);
if (currentPageAnswers.length > 0) {
console.log(`[多页教材] 使用第 ${startIndex + 1} 到第 ${endIndex} 个答案:`, currentPageAnswers);
await fillAnswersForMultiPage(currentPageAnswers);
multiPageMode.pageIndex += currentPageQuestionCount;
console.log(`[多页教材] ✅ 答题完成,页面索引已更新为: ${multiPageMode.pageIndex}`);
console.log(`[多页教材] 下次答题将从第 ${multiPageMode.pageIndex + 1} 个答案开始`);
updateMultiPageStatus();
} else {
console.warn(`[多页教材] 页面索引 ${multiPageMode.pageIndex} 超出答案数组范围 (${multiPageMode.totalAnswers.length})`);
return false;
}
} else {
await fillAnswersFromArray(answers);
}
return true;
} catch (error) {
alert(`[在线题库] 查询失败: ${error.message}`);
return false;
}
}
console.log('AI状态:', { useKimiAI });
if (useKimiAI) {
const specialFillInResult = await handleSpecialFillInQuestions();
if (specialFillInResult) {
console.log('已成功处理特殊填空题');
return true;
}
}
if (useKimiAI) {
console.log('使用AI模式答题');
const questionInfo = await parseConsoleQuestions();
if (!questionInfo) {
console.log('无法获取题目信息');
return false;
}
console.log('正在请求AI回答...');
const aiAnswer = await askKimi(questionInfo.prompt);
if (!aiAnswer) {
console.log('未能获取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()];
}
}
console.log('AI答案(解析后):', answers);
console.log('题目数量:', questionInfo.questions.length);
console.log('📝 开始逐题处理模式(支持混合题型)...');
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) {
console.log(`题目 ${i + 1} 没有对应答案,跳过`);
continue;
}
const answer = String(answerLine).replace(/^\d+\.\s*/, '').trim();
console.log(`准备填写题目 ${question.number} 的答案:`, answer);
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300));
const questionElement = question.element;
if (!questionElement) {
console.log(`题目 ${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) {
console.log(`找到填空输入框,使用选择器: ${selector}`);
break;
}
}
if (input) {
console.log(`填写填空题答案: ${answer}`);
await simulateHumanBehavior(input);
input.value = answer + '\n';
console.log(`已添加换行符: ${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) {
console.log('找到提交按钮,点击提交');
await new Promise(resolve => setTimeout(resolve, 300));
submitButton.click();
}
} else {
console.log('未找到填空题输入框');
}
} 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) {
console.log(`找到文本框,填写答案: ${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;
console.log(`已添加换行符: ${text}\\n`);
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
};
await typeText(answer, textarea);
} else {
console.log('未找到文本框元素');
}
} else if (question.type === 'single-choice') {
console.log('处理单选题,答案:', answer);
const cleanAnswer = answer.trim();
console.log('清理后的答案:', 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) {
console.log('检测到普通U校园版题目结构');
const optionsList = questionElement.querySelectorAll('ul[class*="single-choice"] li label');
optionsArray = Array.from(optionsList);
if (isSingleLetter) {
const letter = cleanAnswer.toUpperCase();
console.log(`答案是单个字母 "${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;
console.log(`✅ 通过选项字母找到选项 ${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();
console.log(`选项 ${letter}: "${contentText.substring(0, 30)}..."`);
if (cleanAnswerLower.length > 1 && (contentText === cleanAnswerLower || contentText.includes(cleanAnswerLower))) {
targetOption = input;
targetLetter = letter;
foundByText = true;
console.log(`找到匹配的选项 ${letter}: "${contentText.substring(0, 30)}..."`);
break;
}
}
}
}
} else {
const options = questionElement.querySelectorAll('.option.isNotReview');
optionsArray = Array.from(options);
if (isSingleLetter) {
const letter = cleanAnswer.toUpperCase();
console.log(`答案是单个字母 "${letter}",优先通过字母匹配`);
for (const option of optionsArray) {
const caption = option.querySelector('.caption');
if (caption && caption.textContent.trim() === letter) {
targetOption = option;
targetLetter = letter;
console.log(`✅ 通过选项字母找到选项 ${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();
console.log(`选项 ${letter}: "${contentText.substring(0, 30)}..."`);
if (cleanAnswerLower.length > 1 && (contentText === cleanAnswerLower || contentText.includes(cleanAnswerLower))) {
targetOption = option;
targetLetter = letter;
foundByText = true;
console.log(`找到匹配的选项 ${letter}: "${contentText.substring(0, 30)}..."`);
break;
}
}
}
}
}
if (targetOption && targetLetter) {
console.log(`✅ 准备点击选项 ${targetLetter} (${foundByText ? '通过文本匹配' : '通过字母匹配'})`);
await simulateHumanBehavior(targetOption);
if (isClassicVersion && targetOption.tagName === 'INPUT') {
console.log('普通版:直接选中 radio 按钮');
targetOption.checked = true;
targetOption.dispatchEvent(new Event('change', { bubbles: true }));
targetOption.dispatchEvent(new Event('click', { bubbles: true }));
} else {
targetOption.click();
}
console.log(`✅ 已选中选项 ${targetLetter}`);
const submitButton = findSubmitButton(questionElement);
if (submitButton) {
console.log('找到提交按钮,点击提交');
await new Promise(resolve => setTimeout(resolve, 300));
submitButton.click();
}
} else {
console.log(`❌ 未找到匹配的选项: ${cleanAnswer}`);
}
} else if (question.type === 'multiple-choice') {
console.log('处理多选题,答案:', answer);
let options = questionElement.querySelectorAll('.option.isNotReview');
console.log('AI版选项数量:', options.length);
if (options.length === 0) {
options = questionElement.querySelectorAll('.MultipleChoice--checkbox-item-34A_-');
console.log('普通版选项容器数量:', options.length);
if (options.length === 0) {
options = questionElement.querySelectorAll('input[type="checkbox"]');
console.log('复选框数量:', options.length);
}
}
const optionsArray = Array.from(options);
console.log('最终选项数组长度:', optionsArray.length);
const foundOptions = [];
let answerString = answer;
if (Array.isArray(answer)) {
answerString = answer.join(',');
} else if (typeof answer === 'object') {
answerString = JSON.stringify(answer);
}
console.log('处理后的答案字符串:', answerString);
const letterMatches = answerString.match(/[A-Z]/gi);
if (letterMatches && letterMatches.length > 0) {
console.log('检测到选项字母答案:', 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() : '';
}
}
console.log(`检查选项 - 字母: "${letter}", 文本: "${text ? text.substring(0, 30) : 'null'}"`);
if (letter && letterMatches.includes(letter.toUpperCase())) {
foundOptions.push({
option: option,
letter: letter,
text: text
});
console.log(`✓ 找到字母匹配的选项 ${letter}`);
}
}
}
if (foundOptions.length === 0) {
let answerForTextMatch = answerString;
const answerParts = answerForTextMatch.split(/[,,、;;\s]+/).filter(part => part.trim().length > 0);
console.log('答案拆分为多个部分:', 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) {
console.log(`选项 ${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
});
console.log(`找到文本匹配的选项 ${letter}: "${contentText}"`);
break;
}
}
}
}
}
if (foundOptions.length > 0) {
console.log(`找到 ${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) {
console.log(`准备点击选项 ${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();
}
}
console.log(`已点击选项 ${found.letter}`);
await new Promise(resolve => setTimeout(resolve, Math.random() * 300 + 100));
}
const submitButton = findSubmitButton(questionElement);
if (submitButton) {
console.log('找到提交按钮,点击提交');
await new Promise(resolve => setTimeout(resolve, 300));
submitButton.click();
}
} else {
console.log('未找到匹配的选项:', answer);
}
}
await new Promise(resolve => setTimeout(resolve, getAnswerDelay()));
} catch (questionError) {
console.error(`处理题目 ${i + 1} 时发生错误:`, questionError.message);
console.log('继续处理下一道题...');
}
}
console.log('✅ AI答题循环完成');
return true;
}
const contentInfo = await getContentInfo();
if (!contentInfo || !contentInfo.answers || contentInfo.answers.length === 0) {
console.log('%c未找到匹配的答案,可能为主观题,将自动跳过。', 'color: #f44336; font-weight: bold;');
return false;
}
if (contentInfo.activeTopicName !== lastActiveTopicName) {
currentTopicUsedAnswers.clear();
lastActiveTopicName = contentInfo.activeTopicName;
console.log('%c检测到主题切换,已重置答案使用记录', 'color: #2196F3;');
}
const textareas = document.querySelectorAll('textarea.question-inputbox-input');
if (textareas && textareas.length > 0) {
console.log('%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);
console.log('%c使用新答案组 (文本题型)', 'color: #2196F3;', answer);
break;
}
}
if (!selectedAnswer) {
for (const answer of contentInfo.answers) {
if (getAnswerType(answer) === 'fill-in') {
selectedAnswer = answer;
console.log('%c所有文本题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;');
break;
}
}
}
if (!selectedAnswer) {
console.log('%c未找到适用于当前文本题的答案组。', 'color: #f44336;');
return false;
}
const answerMatches = selectedAnswer.match(/\d+[\.\、\) ][\s\S]*?(?=\d+[\.\、\) ]|$)/g);
if (!answerMatches) {
console.log('%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);
});
console.log(`%c第 ${index + 1} 题 (${answerType}) 已填写答案: ${answer}`, 'color: #2196F3;');
} else {
console.log(`%c第 ${index + 1} 题未找到对应答案`, 'color: #f44336;');
}
} catch (error) {
console.error(`填写第 ${index + 1} 题时发生错误:`, error);
}
}
return true;
}
const fillInBlanks = document.querySelectorAll('.fe-scoop');
if (fillInBlanks && fillInBlanks.length > 0) {
console.log('%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);
console.log('%c使用新答案组 (填空题型)', 'color: #2196F3;', answer);
break;
}
}
if (!selectedAnswer) {
for (const answer of contentInfo.answers) {
if (getAnswerType(answer) === 'fill-in') {
selectedAnswer = answer;
console.log('%c所有填空题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;');
break;
}
}
}
if (!selectedAnswer) {
console.log('%c未找到适用于当前填空题的答案组。', 'color: #f44336;');
return false;
}
const answerLines = selectedAnswer.split('\n').map(line => line.trim()).filter(Boolean);
if (!answerLines || answerLines.length === 0) {
console.log('%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) {
console.log(`%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);
});
console.log(`%c第 ${index + 1} 题已填写答案: ${answer}`, 'color: #2196F3;');
} else {
console.log(`%c第 ${index + 1} 题未找到输入框或有效答案`, 'color: #f44336;');
}
} catch (error) {
console.error(`填写第 ${index + 1} 题时发生错误:`, error);
}
}
return true;
}
const matchingWrapper = document.querySelector('#sortableListWrapper');
if (matchingWrapper) {
console.log('%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) {
console.log('%c在此类匹配题中未找到输入框。', 'color: #f44336;');
return false;
}
let selectedAnswer = null;
for (const answer of contentInfo.answers) {
if (!currentTopicUsedAnswers.has(answer)) {
selectedAnswer = answer;
currentTopicUsedAnswers.add(answer);
console.log('%c使用新答案组', 'color: #2196F3;', answer);
break;
}
}
if (!selectedAnswer) {
console.log('%c所有答案组都已使用,将重用第一组答案', 'color: #FFA500;');
selectedAnswer = contentInfo.answers[0];
}
const answerMatches = selectedAnswer.match(/\d+[\.\、\)]\s*[A-Z]/g);
if (!answerMatches) {
console.log('%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);
});
console.log(`%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)) {
console.log('%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) {
console.log('%c未找到题目', 'color: #f44336; font-weight: bold;');
return false;
}
console.log('%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);
console.log('%c使用新答案组 (选择题型)', 'color: #2196F3;', answer);
break;
}
}
if (!selectedAnswer) {
for (const answer of contentInfo.answers) {
if (getAnswerType(answer) === 'choice') {
selectedAnswer = answer;
console.log('%c所有选择题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;');
break;
}
}
}
if (!selectedAnswer) {
console.log('%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) {
console.log(`%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) {
console.log(`%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();
console.log(`%c第 ${questionIndex + 1} 题已选择选项 ${letter}`, 'color: #2196F3;');
setTimeout(resolve, 100 + Math.random() * 200);
}, 100 + Math.random() * 300);
} else {
console.log(`%c第 ${questionIndex + 1} 题选项 ${letter} 已经被选中`, 'color: #FFA500;');
resolve();
}
}, getAnswerDelay() + letterIndex * 200);
});
} else {
console.log(`%c第 ${questionIndex + 1} 题未找到选项 ${letter}`, 'color: #f44336;');
}
}
} else {
console.log(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;');
}
} else {
console.log(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;');
}
}
return true;
} catch (error) {
console.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;
}
}
console.log('%c当前页面信息', 'color: #2196F3; font-weight: bold; font-size: 14px;');
console.log('导航路径:', navigationPath);
console.log('章节内容:', chapterContent);
console.log('主题内容:', finalTopicContent);
console.log('主题总数:', totalTopics);
console.log('当前选中的主题:', activeTopicName || '未找到选中的主题');
console.log('下一个主题:', nextTopicName || '没有下一个主题');
console.log('\n%c所有主题列表', 'color: #2196F3; font-weight: bold; font-size: 14px;');
allTopicNames.forEach(topic => {
console.log(`${topic.index}. ${topic.name} ${topic.isActive ? '(当前选中)' : ''}`);
});
let answers = [];
try {
console.log('\n%c正在从题库搜索答案...', 'color: #4CAF50; font-weight: bold; font-size: 14px;');
if (!loadedQuestionBank) {
console.log('%c请先上传题库文件。', 'color: #f44336; font-weight: bold;');
return {
chapter: chapterContent,
topic: finalTopicContent,
totalTopics: totalTopics,
activeTopicName: activeTopicName,
nextTopicName: nextTopicName,
allTopics: allTopicNames,
answers: []
};
}
const questionBankData = { type: 'json', content: loadedQuestionBank };
answers = await searchAnswers(questionBankData, chapterContent, finalTopicContent, nextTopicName);
console.log('\n%c匹配到的答案', 'color: #4CAF50; font-weight: bold; font-size: 14px;');
if (answers.length === 0) {
console.log('%c未找到匹配的答案', 'color: #f44336;');
} else {
answers.forEach((answer, index) => {
console.log(`\n%c答案 ${index + 1}:`, 'color: #4CAF50; font-weight: bold;');
console.log(formatAnswer(answer));
});
}
} catch (error) {
console.error('获取或搜索题库时发生错误:', error);
}
return {
chapter: chapterContent,
topic: finalTopicContent,
totalTopics: totalTopics,
activeTopicName: activeTopicName,
nextTopicName: nextTopicName,
allTopics: allTopicNames,
answers: answers
};
} catch (error) {
console.error('获取内容时发生错误:', error);
return null;
}
}
async function searchAnswers(questionBankData, chapterContent, topicContent, nextTopicName) {
try {
chapterContent = chapterContent.trim();
topicContent = topicContent.trim();
if (!questionBankData || questionBankData.type !== 'json' || !questionBankData.content) {
console.log('题库数据无效或不是JSON格式');
return [];
}
console.log('使用JSON格式题库进行搜索');
return searchAnswersInJson(questionBankData.content, chapterContent, topicContent);
} catch (error) {
console.error('搜索答案时发生错误:', error);
return [];
}
}
let isAutoRunning = localStorage.getItem('u-auto-running') === 'true';
let autoRunTimeoutId = null;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
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) {
console.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')) {
console.log('[提交确认] 检测到提交确认弹窗');
const confirmButtons = dialog.querySelectorAll('button');
for (const button of confirmButtons) {
const buttonText = button.textContent.trim().toLowerCase();
if (buttonText === '确认' ||
buttonText === 'confirm' ||
buttonText === 'ok' ||
buttonText === '提交') {
console.log('[提交确认] 找到确认按钮,准备点击');
setTimeout(() => {
button.click();
console.log('[提交确认] 已点击确认按钮');
}, 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
});
console.log('[提交确认] 已启动提交确认弹窗监听器');
}
async function handleVocabularyCards() {
console.log('[挂机] 检测到词汇卡片学习页面,开始自动点击下一个...');
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')) {
console.log(`[挂机] 词汇卡片处理完成,共处理 ${cardCount} 个卡片。准备跳转到下一任务。`);
return;
}
if (window.__autoPlayRecordEnabled) {
console.log(`[挂机] 检查第 ${cardCount + 1} 个词汇卡片是否有录音题...`);
const recordingHandled = await handleVocabularyRecording();
if (recordingHandled) {
console.log(`[挂机] 第 ${cardCount + 1} 个词汇卡片的录音题已处理完成`);
await waitForScoreAppear();
}
}
await sleep(500 + Math.random() * 1000);
nextButton.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
await sleep(100 + Math.random() * 200);
console.log(`[挂机] 点击第 ${cardCount + 1} 个词汇卡片的"下一个"按钮`);
nextButton.click();
cardCount++;
await sleep(800 + Math.random() * 500);
}
console.log(`[挂机] 已达到最大处理数量限制 (${cardCount})`);
}
async function waitForVideosToEnd() {
const videos = Array.from(document.querySelectorAll('video')).filter(v => !v.ended && v.duration > 0);
if (videos.length === 0) {
console.log('[挂机] 页面上没有需要等待的视频,继续执行。');
return;
}
console.log(`[挂机] 检测到 ${videos.length} 个视频,等待播放完毕...`);
const videoPromises = videos.map(video => {
return new Promise(resolve => {
if (video.ended) {
resolve();
return;
}
const onEnded = () => {
console.log('[挂机] 一个视频已播放完毕。');
video.removeEventListener('ended', onEnded);
resolve();
};
video.addEventListener('ended', onEnded);
const observer = new MutationObserver(() => {
if (!document.body.contains(video)) {
console.log('[挂机] 一个视频已从页面移除,视为播放结束。');
observer.disconnect();
video.removeEventListener('ended', onEnded);
resolve();
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
});
await Promise.all(videoPromises);
console.log('[挂机] 所有视频均已播放完毕。');
}
async function runNextStep() {
if (!isAutoRunning) return;
console.log("runNextStep: 开始执行");
if (await handleGenericCommentSection()) {
console.log("评论区已处理,等待1.5秒后尝试进入下一页...");
await sleep(1500);
}
if (await handleVocabularyCards()) {
console.log("词汇卡片已处理");
}
try {
const vocContainer = document.querySelector("#main-content > div > div > div > div.layoutBody-container > div > div > div.vocContainer");
const discussionTextarea = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-textArea-container > div.ant-input-textarea.ant-input-textarea-show-count.discussion-cloud-bottom-textArea > textarea");
if (vocContainer) {
await handleVocabularyCards();
} else if (discussionTextarea) {
await handleDiscussionPage();
}
else {
let recordingHandled = false;
if (window.__autoPlayRecordEnabled) {
recordingHandled = await handleRecordingQuestions();
if (recordingHandled) {
console.log('[挂机] 录音题已处理,准备提交...');
}
}
console.log('[挂机] 尝试自动选择答案...');
const foundAnswers = await autoSelectAnswers();
if (foundAnswers || recordingHandled) {
console.log('[挂机] 页面内容已处理 (录音或答题),准备提交。');
await sleep(getAnswerDelay());
const submitButton = findFooterButtonByText('提交');
if (submitButton) {
const waitTime = getPageDelay();
console.log(`[挂机] 任务完成,强制停留 ${waitTime}ms (模拟检查) 后点击提交...`);
await sleep(waitTime);
submitButton.click();
await sleep(2500);
} else {
console.log('[挂机] 未找到提交按钮,可能已提交或无需提交');
}
} else {
console.log('[挂机] 未找到答案且无录音操作,可能为主观题,将直接尝试导航。');
await sleep(500);
}
}
await waitForVideosToEnd();
console.log('[挂机] 内容处理/提交完毕,查找下一步操作进行导航...');
const userPageDelay = getPageDelay();
const nextQuestionButton = findFooterButtonByText('下一题');
if (nextQuestionButton) {
console.log(`[挂机] 本页任务完成,强制停留 ${userPageDelay}ms 后点击"下一题"...`);
await sleep(userPageDelay);
console.log('[挂机] 操作: 点击 "下一题"');
nextQuestionButton.click();
autoRunTimeoutId = setTimeout(runNextStep, 5000);
return;
}
const navigatedToSubTopic = await navigateToNextSubTopic();
if (navigatedToSubTopic) {
autoRunTimeoutId = setTimeout(runNextStep, 5000);
return;
}
const navigatedByToc = await navigateToNextTocItem();
if (navigatedByToc) {
autoRunTimeoutId = setTimeout(runNextStep, 5000);
return;
}
console.log('[挂机] 结束: 未找到任何可执行的导航操作。');
isAutoRunning = false;
updateAutoRunButtonUI();
} catch (error) {
console.error('[挂机] 执行步骤时发生错误:', error);
isAutoRunning = false;
updateAutoRunButtonUI();
}
}
async function navigateToNextSubTopic() {
console.log('[挂机] 尝试导航到下一个子主题 (横向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();
const userPageDelay = getPageDelay();
console.log(`[挂机] 准备导航到下一个子主题: "${nextTabName}",强制停留 ${userPageDelay}ms...`);
await sleep(userPageDelay);
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 {
console.log(`[挂机] 在 ${system.name} 中已是最后一个Tab。`);
}
}
console.log('[挂机] 未找到可切换的下一个子主题。');
return false;
}
async function navigateToNextTocItem() {
console.log('[挂机] 正在尝试通过目录导航到下一项...');
let tocItems = [];
let activeIndex = -1;
let tocContainer = null;
const activeElement = document.querySelector('.pc-menu-activity');
if (activeElement) {
tocContainer = activeElement.closest('.pc-slier-menu-container, .pc-slider-menu-container');
if (tocContainer) {
const allItems = Array.from(tocContainer.querySelectorAll('div[data-role="micro"], div[data-role="node"]'));
tocItems = allItems.filter(item => item.offsetParent !== null);
activeIndex = tocItems.indexOf(activeElement);
}
}
if (activeIndex !== -1 && activeIndex + 1 < tocItems.length) {
const nextItem = tocItems[activeIndex + 1];
const nameEl = nextItem.querySelector('.pc-menu-node-name') || nextItem;
const nextItemName = nameEl.textContent.trim().split('\n')[0] || '未知项';
const userPageDelay = getPageDelay();
console.log(`[挂机] 🟢 目录定位成功! 下一项: "${nextItemName}" (索引: ${activeIndex + 1})`);
console.log(`[挂机] ⏳ 强制停留 ${userPageDelay}ms 后点击...`);
await sleep(userPageDelay);
nextItem.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
await sleep(300);
nextItem.click();
return true;
}
console.warn('[挂机] ⚠️ 目录导航无法执行 (可能是最后一项或定位失败),尝试点击页面底部“下一页”按钮...');
const nextBtns = Array.from(document.querySelectorAll('button, div, span, a'));
const targetNextBtn = nextBtns.find(el => {
const text = el.textContent ? el.textContent.trim() : '';
const isVisible = el.offsetParent !== null;
return (text === '下一页' || text === 'Next' || (el.className && typeof el.className === 'string' && el.className.includes('next')))
&& isVisible && !el.disabled && !el.classList.contains('disabled');
});
if (targetNextBtn) {
const userPageDelay = getPageDelay();
console.log(`[挂机] 🟢 备用方案生效: 找到了“下一页”按钮,${userPageDelay}ms 后点击。`);
await sleep(userPageDelay);
targetNextBtn.click();
return true;
}
// 情况 C: 彻底没招了
console.error('[挂机] ❌ 结束: 目录无下一项,且找不到下一页按钮。');
return false;
}
function toggleAutoRun() {
isAutoRunning = !isAutoRunning;
localStorage.setItem('u-auto-running', isAutoRunning.toString());
updateAutoRunButtonUI();
if (isAutoRunning) {
console.log('自动挂机已启动...');
window.isAutoModeRunning = true;
runNextStep();
} else {
if (autoRunTimeoutId) {
clearTimeout(autoRunTimeoutId);
autoRunTimeoutId = null;
}
window.isAutoModeRunning = false;
console.log('自动挂机已手动停止。');
}
}
function autoResumeIfNeeded() {
setTimeout(() => {
if (isAutoRunning) {
console.log('[自动恢复] 检测到刷新前处于挂机状态,自动恢复挂机模式...');
window.isAutoModeRunning = true;
updateAutoRunButtonUI();
runNextStep();
}
}, 1000);
}
function handleVideo(video) {
if (video.dataset.handledByScript) return;
video.dataset.handledByScript = 'true';
console.log('[视频助手] 发现视频,开始处理:', video);
const setPlaybackRate = () => {
const targetSpeed = parseFloat(localStorage.getItem('u-video-speed') || '2.0');
if (video.playbackRate !== targetSpeed) {
video.playbackRate = targetSpeed;
console.log(`[视频助手] 视频倍速已设置为 ${targetSpeed}x`);
}
};
const attemptPlay = () => {
video.muted = true;
const playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(() => {
console.log('[视频助手] 视频已自动播放。');
setPlaybackRate();
}).catch(error => {
console.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) {
console.log('[视频助手] 视频暂停,检测到弹窗问题,开始处理...');
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
});
console.log('已启动视频自动播放和倍速调整功能。');
}
async function handleVideoPopupQuestions(popupElement) {
if (popupElement.dataset.handledByRandomSelect) return;
popupElement.dataset.handledByRandomSelect = 'true';
console.log('[视频弹题助手] 检测到视频中的弹窗问题,准备随机选择...');
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}`;
console.log(`[视频弹题助手] 随机选择选项: ${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();
console.log('[视频弹题助手] 已成功点击选项。');
await sleep(500 + Math.random() * 300);
const confirmButton = popupElement.querySelector('button.submit-btn');
if (confirmButton && !confirmButton.disabled) {
console.log('[视频弹题助手] 找到"确定"按钮,正在点击...');
confirmButton.click();
console.log('[视频弹题助手] 已点击"确定"按钮。');
} else {
console.log('[视频弹题助手] 未找到或"确定"按钮不可用。');
}
} else {
console.log('[视频弹题助手] 未在弹窗中找到可选选项。');
}
}
function setupVideoPopupObserver() {
const observer = new MutationObserver(() => {
const popup = document.querySelector('.question-video-popup');
if (popup && popup.offsetParent === null) {
if (popup.dataset.handledByRandomSelect) {
console.log('[视频弹题助手] 弹窗已隐藏,重置处理标记以便下次使用。');
delete popup.dataset.handledByRandomSelect;
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class', 'hidden']
});
console.log('已启动视频内弹窗问题状态监视器。');
}
async function handleDiscussionPage() {
const textarea = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-textArea-container > div.ant-input-textarea.ant-input-textarea-show-count.discussion-cloud-bottom-textArea > textarea");
const submitButton = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-btns > div > div.btns-submit.student-btns-submit > button");
if (submitButton && !submitButton.disabled) {
let commentText = localStorage.getItem('u-default-comment');
const selectedBank = document.getElementById('online-bank-selector')?.value;
const hasOnlineBank = selectedBank && selectedBank.trim() !== '';
if (useKimiAI && hasOnlineBank) {
console.log('[讨论区] AI模式已启用且有在线题库,评论使用AI分析...');
try {
const discussionTitle = document.querySelector('#top .discussion-title p, #top .discussion-title');
const titleText = discussionTitle ? discussionTitle.textContent.trim() : '';
const discussionQuestions = document.querySelector('#top .question-common-abs-material .component-htmlview, #top .text-material-wrapper');
const questionsText = discussionQuestions ? discussionQuestions.textContent.trim() : '';
if (titleText || questionsText) {
console.log('[讨论区] 讨论标题:', titleText);
console.log('[讨论区] 讨论问题:', questionsText);
const prompt = `请帮我回答以下讨论问题。请用英语回答,回答要简洁、有深度,大约50-100词。
讨论主题:${titleText}
讨论问题:
${questionsText}
要求:
1. 用英语回答
2. 回答要有实质性内容,不要太简单
3. 回答要自然、像是真实学生的想法
4. 如果有多个问题,简要回答每个问题
5. 总字数控制在50-100词左右
6. 不要加标题或序号,直接给出答案段落`;
const aiResponse = await askKimi(prompt);
if (aiResponse && aiResponse.trim()) {
commentText = aiResponse.trim();
console.log('[讨论区] AI生成的评论:', commentText);
} else {
console.log('[讨论区] AI未返回有效内容,使用默认评论');
}
} else {
console.log('[讨论区] 未找到讨论内容,使用默认评论');
}
} catch (error) {
console.error('[讨论区] AI分析失败:', error);
console.log('[讨论区] 使用默认评论');
}
} else if (useKimiAI && !hasOnlineBank) {
console.log(`[讨论区] AI已启用但无在线题库,为节省积分用于答题,评论使用默认文本 "${commentText}"`);
} else {
console.log(`[讨论区] AI未启用,使用默认评论文本 "${commentText}"`);
}
await sleep(1000 + Math.random() * 500);
textarea.dispatchEvent(new Event('focus', { bubbles: true }));
const nativeTextareaSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
nativeTextareaSetter.call(textarea, commentText);
textarea.dispatchEvent(new Event('input', { bubbles: true }));
await sleep(200);
textarea.dispatchEvent(new Event('blur', { bubbles: true }));
console.log('[讨论区] 已输入评论:', commentText.substring(0, 50) + (commentText.length > 50 ? '...' : ''));
await sleep(500 + Math.random() * 500);
if (!submitButton.disabled) {
submitButton.click();
console.log('[讨论区] 已点击发布按钮');
await sleep(2500);
}
} else {
console.log('[讨论区] 发布按钮不可用或未找到,跳过');
}
}
function setupPopupHandler() {
setInterval(() => {
document.querySelectorAll('.ant-modal-wrap:not([style*="display: none"])').forEach(modal => {
const confirmContent = modal.querySelector('.ant-modal-confirm-content');
if (confirmContent && confirmContent.textContent.includes('本单元仅记录第一次作答的得分')) {
const okBtn = modal.querySelector('.system-info-cloud-ok-button') || modal.querySelector('.ant-btn-primary');
if (okBtn) {
console.log(`[自动确认] 发现提交确认弹窗,点击确定`);
okBtn.click();
return;
}
}
const confirmButton = modal.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) {
console.log(`[自动确认] 发现弹窗,点击 "${rawText.trim()}"`);
confirmButton.click();
}
}
});
}, 1000);
console.log('已启动全局弹窗自动确认处理器。');
}
function setupAnswerInterceptor() {
const SERVER_API = 'http://42.194.169.45:8787/api/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()) {
console.log('[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) {
console.error('[U助手-云端] 解析失败:', e);
}
} else {
showRecordNotification('❌ 云端服务器错误', 'error');
}
XMLHttpRequest.prototype._originalSend.call(this, data);
},
onerror: (err) => {
console.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__;
console.log('[拦截器] 排序题本地修正成功');
}
}
} catch (e) { console.error(e); }
}
return originalXHRSend.apply(this, [data]);
};
const originalFetch = window.fetch;
window.fetch = async function(url, options = {}) {
return originalFetch.apply(this, [url, options]);
};
console.log('[U助手] 智能拦截器 V7 (UI判定版) 已就绪');
}
window.addEventListener('load', () => {
createFloatingButton();
setupPopupHandler();
setupVideoHandler();
setupVideoPopupObserver();
setupAnswerInterceptor();
});
/**
* 新增:根据答案数组自动填写选择题
* @param {string[]} answers - e.g., ["D", "C", "A"]
*/
async function fillAnswersFromArray(answers) {
const questions = document.querySelectorAll('.question-common-abs-reply, .question-common-abs-banked-cloze');
if (questions.length === 0) {
console.warn('[在线题库] 未在页面上找到题目容器。');
return;
}
if (questions.length !== answers.length) {
console.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) {
console.log(`[在线题库] 第 ${i + 1} 题,选择答案: ${answerLetter}`);
targetOption.click();
await new Promise(r => setTimeout(r, 200 + Math.random() * 200));
} else {
console.error(`[在线题库] 第 ${i + 1} 题,未找到选项: ${answerLetter}`);
}
}
}
async function fillAnswersFromArray(answers) {
console.log('[在线题库] 开始填写答案,共', 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()
};
console.log('[在线题库] 拖动排序题已完成');
await sleep(500);
return;
}
const feScoopTriggers = document.querySelectorAll('.fe-scoop[data-scoop-index]');
if (feScoopTriggers.length > 0) {
const hasTriggers = Array.from(feScoopTriggers).some(el => el.querySelector('.ant-dropdown-trigger'));
const hasInputs = Array.from(feScoopTriggers).some(el => el.querySelector('input, textarea'));
if (!hasTriggers && hasInputs) {
console.log(`[在线题库] 检测到 ${feScoopTriggers.length} 个 fe-scoop 填空题,跳过下拉选择题处理`);
} else if (hasTriggers) {
console.log(`[在线题库] 检测到 ${feScoopTriggers.length} 个 fe-scoop 下拉选择题`);
const questionsToProcess = Math.min(feScoopTriggers.length, answers.length);
for (let i = 0; i < questionsToProcess; i++) {
const scoopElement = feScoopTriggers[i];
const trigger = scoopElement.querySelector('.ant-dropdown-trigger');
if (!trigger) {
console.warn(`[在线题库] 第 ${i + 1} 题未找到触发器,跳过`);
continue;
}
const answerText = answers[i].trim();
const currentAnswerElement = trigger.querySelector('.user-answer-text');
if (currentAnswerElement) {
const currentText = currentAnswerElement.textContent.trim();
if (currentText !== '点击选择' && currentText !== 'Click to select' && currentText !== '') {
if (currentText.toLowerCase() === answerText.toLowerCase()) {
console.log(`[在线题库] 第 ${i + 1} 题已选择正确答案: ${currentText}`);
continue;
}
}
}
console.log(`[在线题库] 第 ${i + 1} 题,准备选择: ${answerText}`);
scoopElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
await new Promise(r => setTimeout(r, 300));
await simulateHumanBehavior(trigger);
trigger.click();
await new Promise(r => setTimeout(r, 800));
const allMenus = document.querySelectorAll('.ant-dropdown-menu.scoop-select');
let targetOption = null;
for (const menu of allMenus) {
const menuStyle = window.getComputedStyle(menu);
const menuParent = menu.closest('.ant-dropdown');
if (menuParent && !menuParent.classList.contains('ant-dropdown-hidden')) {
console.log(`[在线题库] 找到可见菜单`);
const options = menu.querySelectorAll('li.select-option');
console.log(`[在线题库] 菜单中有 ${options.length} 个选项`);
for (const opt of options) {
const optText = opt.textContent.trim();
const menuId = opt.getAttribute('data-menu-id');
console.log(`[在线题库] 检查选项: "${optText}", data-menu-id: ${menuId}`);
if (optText.toLowerCase() === answerText.toLowerCase() ||
optText.includes(answerText) ||
answerText.includes(optText)) {
targetOption = opt;
console.log(`[在线题库] ✓ 找到匹配选项: ${optText}`);
break;
}
if (answerText.length === 1 && menuId) {
const letter = answerText.toUpperCase();
if (menuId.endsWith('-' + letter)) {
targetOption = opt;
console.log(`[在线题库] ✓ 通过字母找到选项: ${optText}`);
break;
}
}
}
if (targetOption) break;
}
}
if (targetOption) {
console.log(`[在线题库] 准备点击选项: ${targetOption.textContent.trim()}`);
await simulateHumanBehavior(targetOption);
targetOption.click();
await new Promise(r => setTimeout(r, 500));
const updatedAnswerElement = trigger.querySelector('.user-answer-text');
if (updatedAnswerElement) {
const selectedText = updatedAnswerElement.textContent.trim();
console.log(`[在线题库] ✓ 第 ${i + 1} 题选择完成: ${selectedText}`);
}
} else {
console.error(`[在线题库] ✗ 第 ${i + 1} 题未找到匹配选项: "${answerText}"`);
document.body.click();
await new Promise(r => setTimeout(r, 300));
}
}
console.log('[在线题库] fe-scoop 下拉选择题处理完成');
return;
}
}
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;
console.log(`[在线题库] 检测到 ${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) {
console.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()) {
console.log(`[在线题库] 第 ${i + 1} 题已选择正确答案: ${currentText}`);
continue;
}
}
}
console.log(`[在线题库] 第 ${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) {
console.log(`[在线题库] 找到选项: ${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') {
console.log(`[在线题库] ✓ 第 ${i + 1} 题选择成功: ${selectedText}`);
}
}
}
} else {
console.error(`[在线题库] 第 ${i + 1} 题未找到选项: ${answerText}`);
console.log(`[在线题库] 正在查找的答案文本: "${answerText}"`);
const escEvent = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
keyCode: 27,
bubbles: true
});
document.dispatchEvent(escEvent);
await new Promise(r => setTimeout(r, 300));
}
}
console.log('[在线题库] 所有下拉选择题已完成');
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) {
console.log(`[在线题库] 检测到 ${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');
console.log(`[在线题库] 检测到 ${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);
console.log(`[在线题库] 题型检测: ${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);
}
}
console.log(`[在线题库] 特殊题型分组: ${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);
}
console.log(`[在线题库] 标准题型分组: ${questionGroups.length} 道题`);
}
if (questionGroups.length > 0) {
if (questionGroups.length !== answers.length) {
console.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;
}
}
async function searchAnswers(questionBankData, chapterContent, topicContent, nextTopicName) {
try {
chapterContent = chapterContent.trim();
topicContent = topicContent.trim();
if (!questionBankData || questionBankData.type !== 'json' || !questionBankData.content) {
console.log('题库数据无效或不是JSON格式');
return [];
}
console.log('使用JSON格式题库进行搜索');
return searchAnswersInJson(questionBankData.content, chapterContent, topicContent);
} catch (error) {
console.error('搜索答案时发生错误:', error);
return [];
}
}
function getCurrentPathFromData() {
try {
const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span');
if (breadcrumbs.length === 0) {
console.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(' > ');
console.log('生成的当前页面路径:', fullPath);
return fullPath;
} catch (error) {
console.error('获取当前页面路径时出错:', error);
return null;
}
}
async function handleGenericCommentSection() {
const commentTextArea = document.querySelector('textarea.ant-input[placeholder="我来评论"]');
if (!commentTextArea) {
return false;
}
const submitButton = document.querySelector('.btns-submit button.submit-btn');
if (!submitButton) {
return false;
}
if (!submitButton.disabled && !commentTextArea.value) {
console.log('[自动评论] 检测到评论框,准备输入...');
} else if (submitButton.disabled) {
console.log('[自动评论] 检测到评论框,准备输入...');
} else {
console.log('[自动评论] 评论框已有内容或状态异常,跳过。');
return false;
}
let textToType = localStorage.getItem('u-default-comment');
const selectedBank = document.getElementById('online-bank-selector')?.value;
const hasOnlineBank = selectedBank && selectedBank.trim() !== '';
if (useKimiAI && hasOnlineBank) {
console.log('[自动评论] AI模式已启用且有在线题库,评论使用AI分析...');
try {
const discussionTitle = document.querySelector('#top .discussion-title, .discussion-title');
const titleText = discussionTitle ? discussionTitle.textContent.trim() : '';
const discussionContent = document.querySelector('#top .question-common-abs-material, #top .text-material-wrapper, .question-common-abs-material');
const contentText = discussionContent ? discussionContent.textContent.trim() : '';
if (titleText || contentText) {
console.log('[自动评论] 找到讨论内容');
const prompt = `请帮我回答以下讨论问题。请用英语回答,回答要简洁、自然,大约50-100词。
${titleText ? `讨论主题:${titleText}` : ''}
${contentText ? `讨论内容:\n${contentText}` : ''}
要求:
1. 用英语回答
2. 回答要简洁、有实质性内容
3. 回答要自然、像是真实学生的想法
4. 总字数控制在50-100词左右
5. 不要加标题或序号,直接给出答案段落`;
const aiResponse = await askKimi(prompt);
if (aiResponse && aiResponse.trim()) {
textToType = aiResponse.trim();
console.log('[自动评论] AI生成的评论:', textToType.substring(0, 50) + '...');
}
} else {
console.log('[自动评论] 未找到讨论内容,使用默认评论');
}
} catch (error) {
console.error('[自动评论] AI分析失败:', error);
}
} else if (useKimiAI && !hasOnlineBank) {
console.log(`[自动评论] AI已启用但无在线题库,为节省积分用于答题,评论使用默认文本 "${textToType}"`);
} else {
console.log(`[自动评论] AI未启用,使用默认评论文本 "${textToType}"`);
}
commentTextArea.focus();
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
nativeInputValueSetter.call(commentTextArea, textToType);
commentTextArea.dispatchEvent(new Event('input', { bubbles: true }));
console.log('[自动评论] 已输入评论:', textToType.substring(0, 50) + (textToType.length > 50 ? '...' : ''));
await new Promise(resolve => {
const checkButton = () => {
if (!submitButton.disabled) {
console.log('[自动评论] 发布按钮已激活');
resolve();
} else {
setTimeout(checkButton, 200);
}
};
checkButton();
});
await sleep(500);
console.log('[自动评论] 点击发布按钮...');
submitButton.click();
return true;
}
function analyzePageQuestions() {
const result = {
count: 1,
type: 'unknown',
elements: []
};
console.log(`[多页教材] 🔍 开始分析页面题目结构...`);
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];
console.log(`[多页教材] ✅ 通过排序容器检测到 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];
console.log(`[多页教材] ✅ 检测到普通U校园版拖动排序题,需要 ${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);
console.log(`[多页教材] ✅ 通过输入框检测到 ${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];
console.log(`[多页教材] ✅ 检测到itest混合题型,包含 ${result.count} 个题目`);
console.log(`[多页教材] 单选题数量: ${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];
console.log(`[多页教材] ✅ 检测到普通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);
console.log(`[多页教材] ✅ 检测到普通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;
console.log(`[多页教材] ✅ 通过选择题分组检测到 ${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);
console.log(`[多页教材] ⚠️ 回退到容器检测,找到 ${result.count} 个题目容器`);
return result;
}
console.warn(`[多页教材] ⚠️ 无法准确检测到页面题目,默认为1个`);
return result;
}
function getCurrentPageQuestionCount() {
return analyzePageQuestions().count;
}
async function fillAnswersForMultiPage(answers) {
const questionAnalysis = analyzePageQuestions();
const { count, type, elements } = questionAnalysis;
console.log(`[多页教材] 📝 开始填写 ${answers.length} 个答案到 ${count} 个 ${type} 类型的题目`);
if (answers.length < count) {
console.warn(`[多页教材] ⚠️ 答案数量 (${answers.length}) 与题目数量 (${count}) 不匹配`);
}
const questionsToProcess = Math.min(count, answers.length);
switch (type) {
case 'sorting':
console.log(`[多页教材] 检测到排序题,使用在线题库逻辑处理`);
await fillAnswersFromArray(answers);
break;
case 'classic_sorting':
console.log(`[多页教材] 检测到普通U校园版排序题`);
await fillClassicSortingQuestions(elements[0], answers);
break;
case 'containers':
await fillContainerQuestions(elements, answers, questionsToProcess);
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':
console.log(`[多页教材] 检测到普通U校园版多选题`);
await fillMultipleChoiceQuestions(elements[0], answers[0]);
break;
case 'itest_mixed':
console.log(`[多页教材] 检测到itest混合题型`);
await fillItestMixedQuestions(elements[0], answers);
break;
default:
console.warn(`[多页教材] ⚠️ 未知题目类型: ${type},尝试使用原始填答逻辑`);
await fillAnswersFromArray(answers);
break;
}
console.log(`[多页教材] ✅ 完成填写 ${questionsToProcess} 个题目`);
}
async function fillItestMixedQuestions(questionElement, answers) {
console.log(`[多页教材] 开始处理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] };
}
});
console.log(`[多页教材] 收集到 ${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;
console.log(`[多页教材] 处理第 ${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);
}
await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 200));
}
console.log('[多页教材] itest混合题型处理完成');
}
async function fillItestRadioQuestion(radioElements, answer, questionNum) {
console.log(`[多页教材] 处理第 ${questionNum} 题单选题,答案: ${answer}`);
let targetLetter = '';
if (typeof answer === 'string') {
const letterMatch = answer.match(/[A-Z]/i);
if (letterMatch) {
targetLetter = letterMatch[0].toUpperCase();
}
}
if (!targetLetter) {
console.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) {
console.log(`[多页教材] 第 ${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) {
console.log(`[多页教材] 处理第 ${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 }));
console.log(`[多页教材] 第 ${questionNum} 题填入答案: ${answer} (已添加换行符)`);
}
}
async function fillMultipleChoiceQuestions(questionElement, answer) {
console.log(`[多页教材] 开始处理多选题,答案:`, answer);
let answerString = answer;
if (Array.isArray(answer)) {
answerString = answer.join(',');
} else if (typeof answer === 'object') {
answerString = JSON.stringify(answer);
}
console.log('处理后的答案字符串:', answerString);
const options = document.querySelectorAll('.MultipleChoice--checkbox-item-34A_-');
console.log('找到的选项数量:', options.length);
const letterMatches = answerString.match(/[A-Z]/gi);
if (!letterMatches || letterMatches.length === 0) {
console.warn('[多页教材] 未能从答案中提取到有效字母');
return;
}
console.log('需要选择的选项:', 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())) {
console.log(`[多页教材] 选择选项 ${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 }));
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300));
}
}
}
}
console.log('[多页教材] 多选题处理完成');
}
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')) {
console.log(`[多页教材] 第 ${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;
}
console.log(`[多页教材] 第 ${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];
console.log(`[多页教材] 第 ${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()) {
console.log(`[多页教材] 第 ${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) {
console.log(`[多页教材] 找到选项: ${targetOption.textContent.trim()}`);
await simulateHumanBehavior(targetOption);
targetOption.click();
await new Promise(r => setTimeout(r, 400));
} else {
console.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];
console.log(`[多页教材] 第 ${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) {
console.log(`[多页教材] 开始处理普通U校园版排序题,答案:`, answers);
const sortableItems = container.querySelectorAll('div.sequence-pc-card--item-3CfJy, div[class*="sequence-pc-card--item"]');
if (sortableItems.length === 0) {
console.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);
console.log(`[多页教材] 找到排序项: ${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) {
console.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) {
console.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()
};
console.log('[多页教材] 普通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];
console.log(`[多页教材] 第 ${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;
console.log(`[多页教材] 通过字母匹配找到选项 ${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;
console.log(`[多页教材] 通过内容匹配找到选项 ${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 }));
console.log(`[多页教材] 已选中选项 ${targetLetter}`);
await new Promise(r => setTimeout(r, 200 + Math.random() * 200));
} else if (targetInput && targetInput.checked) {
console.log(`[多页教材] 选项 ${targetLetter} 已经被选中`);
} else {
console.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() {
console.log('[多页教材] 设置导航监听器...');
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) {
console.log('[多页教材] 检测到下一题按钮点击。页面索引已在答题后更新,当前索引:', multiPageMode.pageIndex);
console.log('[多页教材] 等待页面内容更新...');
}
});
window.multiPageNext = function() {
if (multiPageMode.isActive) {
console.log(`[多页教材] 手动触发答题,当前索引: ${multiPageMode.pageIndex}`);
autoSelectAnswers();
} else {
console.log('[多页教材] 当前不在多页模式,无法使用此功能');
}
};
window.multiPageReset = function() {
if (multiPageMode.isActive) {
multiPageMode.pageIndex = 0;
console.log('[多页教材] 已重置页面索引为 0');
updateMultiPageStatus();
}
};
window.multiPageStatus = function() {
console.log('[多页教材] 当前状态:', {
isActive: multiPageMode.isActive,
exerciseId: multiPageMode.exerciseId,
pageIndex: multiPageMode.pageIndex,
totalAnswers: multiPageMode.totalAnswers.length,
allAnswers: multiPageMode.totalAnswers
});
if (multiPageMode.isActive) {
const questionAnalysis = analyzePageQuestions();
console.log('[多页教材] 当前页面分析:', questionAnalysis);
const remainingAnswers = multiPageMode.totalAnswers.slice(multiPageMode.pageIndex);
console.log('[多页教材] 剩余答案:', remainingAnswers);
}
};
window.multiPageDebug = function() {
console.log('=== 多页教材调试信息 ===');
const analysis = analyzePageQuestions();
console.log('页面题目分析:', analysis);
console.log('元素统计:');
console.log('- 题目容器:', document.querySelectorAll('.question-common-abs-reply, .question-common-abs-banked-cloze').length);
console.log('- 填空输入框:', document.querySelectorAll('.fe-scoop input:not([type="hidden"]), textarea.question-inputbox-input').length);
console.log('- 下拉框:', document.querySelectorAll('.fe-scoop[data-scoop-index], .ant-dropdown-trigger').length);
console.log('- 选择题选项:', document.querySelectorAll('.option.isNotReview, div.option').length);
console.log('- 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) {
console.log('多页模式状态:', multiPageMode);
console.log('下次将使用的答案:', multiPageMode.totalAnswers.slice(multiPageMode.pageIndex, multiPageMode.pageIndex + analysis.count));
}
console.log('=== 调试信息结束 ===');
return {
analysis,
multiPageMode,
nextAnswers: multiPageMode.isActive ? multiPageMode.totalAnswers.slice(multiPageMode.pageIndex, multiPageMode.pageIndex + analysis.count) : []
};
};
console.log('[多页教材] 导航监听器设置完成。可使用 multiPageNext() 手动切换到下一页');
}
function initAutoRefresh() {
const savedRefreshEnabled = localStorage.getItem('u-helper-auto-refresh') === 'true';
const savedRefreshInterval = localStorage.getItem('u-helper-refresh-interval') || '30';
const savedPopupRefresh = localStorage.getItem('u-helper-popup-refresh') === 'true';
window.__refreshInterval = parseFloat(savedRefreshInterval);
window.__refreshAfterPopupBlock = savedPopupRefresh;
if (savedRefreshEnabled) {
autoRefreshEnabled = true;
startAutoRefresh();
console.log('[自动刷新] ✅ 自动刷新功能已启动');
} else {
console.log('[自动刷新] 自动刷新功能已禁用');
}
console.log('[弹窗拦截] 拦截后刷新设置:', window.__refreshAfterPopupBlock ? '已启用' : '已禁用');
}
function setupPopupInterception() {
const originalAlert = window.alert;
const originalConfirm = window.confirm;
const originalPrompt = window.prompt;
window.alert = function(message) {
console.log('[弹窗拦截] alert弹窗:', message);
if (message &&
!message.includes('匹配成功') &&
!message.includes('不匹配') &&
(
message.includes('在线题库') ||
message.includes('查询失败') ||
message.includes('未找到') ||
message.includes('在该题库中未找到此练习的答案')
)
) {
console.log('[弹窗拦截] 🚫 检测到题库查询失败弹窗,自动拦截');
showRefreshNotification('🚫 题库错误或该题目为主观题/口语题,没有标准答案', 'info');
setTimeout(() => {
console.log('[弹窗拦截] ⏭️ 2秒延迟后继续挂机流程');
continueAutoMode();
}, 2000);
return true;
}
return originalAlert(message);
};
window.confirm = function(message) {
console.log('[弹窗拦截] confirm弹窗:', message);
if (message && (
message.includes('在线题库') ||
message.includes('查询失败') ||
message.includes('未找到')
)) {
console.log('[弹窗拦截] 🚫 检测到题库相关确认弹窗,自动确认');
return true;
}
return originalConfirm(message);
};
window.prompt = function(message, defaultValue) {
console.log('[弹窗拦截] prompt弹窗:', message);
if (message && (
message.includes('在线题库') ||
message.includes('查询失败')
)) {
console.log('[弹窗拦截] 🚫 检测到题库相关输入弹窗,自动返回默认值');
return defaultValue || '';
}
return originalPrompt(message, defaultValue);
};
console.log('[弹窗拦截] ✅ 弹窗拦截功能已启用');
}
function continueAutoMode() {
if (window.isAutoModeRunning) {
console.log('[弹窗拦截] 🔄 继续执行自动挂机模式...');
const continueButtons = document.querySelectorAll(`
.next-btn, .continue-btn, .btn-next,
[class*="next"], [class*="continue"],
.ant-btn-primary, .el-button--primary
`);
if (continueButtons.length > 0) {
console.log('[弹窗拦截] 🖱️ 找到继续按钮,自动点击');
continueButtons[0].click();
} else if (window.__refreshAfterPopupBlock) {
console.log('[弹窗拦截] 🔄 未找到继续按钮,用户已启用刷新选项,准备刷新页面');
setTimeout(() => {
location.reload();
}, 1000);
} else {
console.log('[弹窗拦截] ⏸️ 未找到继续按钮,用户已禁用刷新选项,停止自动操作');
}
}
}
window.showAnnouncement = function() {
if(typeof showModal === 'function') showModal('正在获取公告列表...', 'loading');
GM_xmlhttpRequest({
method: 'GET',
url: 'http://42.194.169.45:7777/api/get-announcements',
onload: (response) => {
try {
const res = JSON.parse(response.responseText);
if (res.status === 'success' && Array.isArray(res.data)) {
window.renderAnnouncementModal(res.data);
} else {
window.renderAnnouncementModal([]);
}
} catch (e) {
console.error(e);
window.renderAnnouncementModal([{ title: '数据错误', content: '无法解析服务器数据', date: new Date() }]);
}
},
onerror: (err) => {
window.renderAnnouncementModal([{ title: '连接失败', content: '无法连接到公告服务器', date: new Date() }]);
},
ontimeout: () => {
alert('请求超时');
}
});
};
window.renderAnnouncementModal = function(list) {
const existing = document.getElementById('u-announcement-modal');
if (existing) existing.remove();
const loadingModal = document.getElementById('u-helper-modal');
if (loadingModal) loadingModal.style.display = 'none';
let listHtml = '';
if (!list || list.length === 0) {
listHtml = `暂无历史公告
`;
} else {
list.forEach(item => {
const content = (item.content || '').replace(/\n/g, '
');
const time = item.date ? new Date(item.date).toLocaleString('zh-CN', {
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'
}) : '';
const isPinned = item.isPinned;
const pinBadge = isPinned ?
`置顶` : '';
const cardStyle = isPinned ?
`background: #fff1f0; border-bottom: 1px solid #ffccc7; padding: 15px 10px; border-radius: 8px;` :
`border-bottom: 1px dashed #eee; padding: 15px 5px;`;
listHtml += `
${pinBadge}${item.title}
${time}
${content}
`;
});
}
const modalHtml = `
🔔 公告列表 (${list ? list.length : 0})
✕
${listHtml}
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
};
setTimeout(() => {
const btn = document.getElementById('u-notice-btn');
if(btn) {
btn.onclick = (e) => {
e.stopPropagation(); e.preventDefault();
if (typeof window.showAnnouncement === 'function') {
window.showAnnouncement();
} else {
alert('正在连接服务器,请再点一次...');
}
};
}
}, 1000);
function setupDOMPopupInterception() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.classList && (
node.classList.contains('product-dialog') ||
node.closest('.product-dialog')
)) {
console.log('[弹窗拦截] 🛡️ 检测到购买弹窗,放行。');
return;
}
const popupSelectors = [
'.ant-modal', '.el-dialog', '.modal', '.popup',
'[class*="dialog"]', '[class*="modal"]', '[class*="popup"]',
'[class*="Dialog"]', '[class*="Modal"]', '[class*="Popup"]'
];
let isPopup = false;
for (const selector of popupSelectors) {
if (node.matches && node.matches(selector)) {
isPopup = true;
break;
}
if (node.querySelector && node.querySelector(selector)) {
isPopup = true;
break;
}
}
if (!isPopup) {
const style = node.style || {};
const hasPopupStyle = (
style.position === 'fixed' ||
style.position === 'absolute'
) && (
style.zIndex && parseInt(style.zIndex) > 1000
);
const hasPopupContent = node.textContent && (
node.textContent.includes('本单元学习时间') ||
node.textContent.includes('是否必修') ||
(node.textContent.includes('学习时间') && node.textContent.includes('必修'))
);
if (hasPopupStyle || hasPopupContent) {
isPopup = true;
}
}
if (isPopup) {
const textContent = node.textContent || '';
console.log('[弹窗拦截] 检测到DOM弹窗:', textContent);
if (textContent.includes('在线题库') ||
textContent.includes('查询失败') ||
textContent.includes('未找到') ||
textContent.includes('在该题库中未找到此练习的答案')) {
console.log('[弹窗拦截] 🚫 检测到题库查询失败DOM弹窗,准备自动关闭');
setTimeout(() => {
const closeButtons = node.querySelectorAll(`
.ant-btn-primary, .el-button--primary, .btn-primary,
.confirm-btn, .ok-btn, .close-btn,
[class*="confirm"], [class*="ok"], [class*="close"]
`);
if (closeButtons.length > 0) {
console.log('[弹窗拦截] 🖱️ 找到关闭按钮,自动点击');
closeButtons[0].click();
} else {
console.log('[弹窗拦截] 🗑️ 未找到关闭按钮,直接移除弹窗');
node.remove();
}
continueAutoMode();
}, 2000);
showRefreshNotification('🚫 题库错误或该题目为主观题/口语题,没有标准答案', 'info');
}
else if (textContent.includes('本单元学习时间') ||
textContent.includes('是否必修') ||
(textContent.includes('学习时间') && textContent.includes('必修'))) {
console.log('[弹窗拦截] 🚫 检测到学习时间弹窗,准备自动关闭');
setTimeout(() => {
const confirmButtons = node.querySelectorAll(`
.ant-btn-primary, .el-button--primary, .btn-primary,
[class*="confirm"], [class*="ok"], button[type="button"]
`);
let confirmButton = null;
const allButtons = node.querySelectorAll('button, span, div[role="button"]');
for (const btn of allButtons) {
if (btn.textContent && btn.textContent.trim() === '确定') {
confirmButton = btn;
break;
}
}
if (confirmButton) {
console.log('[弹窗拦截] 🖱️ 找到确定按钮,自动点击');
confirmButton.click();
} else if (confirmButtons.length > 0) {
console.log('[弹窗拦截] 🖱️ 找到确认按钮,自动点击');
confirmButtons[0].click();
} else {
const closeButton = node.querySelector('.dialog-header-pc--close-yD7oN, [class*="close"]');
if (closeButton) {
console.log('[弹窗拦截] 🖱️ 找到关闭按钮,自动点击');
closeButton.click();
} else {
console.log('[弹窗拦截] 🗑️ 未找到按钮,直接移除弹窗');
node.remove();
}
}
continueAutoMode();
}, 1000);
showRefreshNotification('🚫 检测到学习时间弹窗,1秒后自动关闭', 'info');
}
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('[弹窗拦截] ✅ DOM弹窗监听器已启动');
const checkExistingPopups = () => {
const existingPopups = document.querySelectorAll(`
.ant-modal, .el-dialog, .modal, .popup,
[class*="dialog"], [class*="modal"], [class*="popup"],
[class*="Dialog"], [class*="Modal"], [class*="Popup"]
`);
existingPopups.forEach(popup => {
const textContent = popup.textContent || '';
if (textContent.includes('本单元学习时间') ||
textContent.includes('是否必修') ||
(textContent.includes('学习时间') && textContent.includes('必修'))) {
console.log('[弹窗拦截] 🚫 发现现有学习时间弹窗,准备自动关闭');
let confirmButton = null;
const allButtons = popup.querySelectorAll('button, span, div[role="button"]');
for (const btn of allButtons) {
if (btn.textContent && btn.textContent.trim() === '确定') {
confirmButton = btn;
break;
}
}
if (confirmButton) {
console.log('[弹窗拦截] 🖱️ 找到确定按钮,自动点击');
confirmButton.click();
showRefreshNotification('🚫 自动关闭学习时间弹窗', 'info');
} else {
const closeButton = popup.querySelector('.dialog-header-pc--close-yD7oN, [class*="close"]');
if (closeButton) {
console.log('[弹窗拦截] 🖱️ 找到关闭按钮,自动点击');
closeButton.click();
showRefreshNotification('🚫 自动关闭学习时间弹窗', 'info');
}
}
}
});
};
setTimeout(checkExistingPopups, 1000);
setInterval(checkExistingPopups, 5000);
}
function initRecordingFeatures() {
window.__autoPlayRecordEnabled = localStorage.getItem('autoPlayRecordEnabled') === 'true';
window.__selectedAudioType = localStorage.getItem('selectedAudioType') || 'british';
setupRecordingHijack();
setupURLHijack();
setupAudioSrcHijack();
monitorRecordButton();
monitorReplayAudio();
console.log('[U-record] ✅ 录音功能初始化完成');
console.log('[U-record] 自动录音状态:', window.__autoPlayRecordEnabled ? '已启用' : '已禁用');
console.log('[U-record] 音频类型:', window.__selectedAudioType === 'british' ? '英音' : '美音');
initAutoRefresh();
setupPopupInterception();
setupDOMPopupInterception();
window.debugRecording = function() {
console.log('=== 录音功能调试信息 ===');
console.log('自动录音状态:', window.__autoPlayRecordEnabled);
console.log('音频类型:', window.__selectedAudioType);
const allButtons = document.querySelectorAll(`
.record-icon, .record-fill-icon, .button-record,
[class*="record"], .microphone-btn, .mic-button
`);
console.log('找到的录音按钮:', allButtons.length);
allButtons.forEach((btn, index) => {
console.log(`按钮 ${index + 1}:`, btn.className, btn);
});
const activeSlide = document.querySelector('.swiper-slide-active');
if (activeSlide) {
console.log('当前活跃词汇卡片:', activeSlide);
const recordBtn = activeSlide.querySelector('.record-fill-icon, .record-icon, .button-record');
console.log('当前卡片录音按钮:', recordBtn);
const scoreElement = document.querySelector('.practice-score .score, .score-wrapper .score');
console.log('分数元素:', scoreElement);
if (scoreElement) {
console.log('当前分数:', scoreElement.textContent.trim());
}
}
const audios = document.querySelectorAll('audio[src]');
console.log('找到的音频元素:', audios.length);
audios.forEach((audio, index) => {
console.log(`音频 ${index + 1}:`, audio.src);
});
const containers = document.querySelectorAll('.oral-study-sentence, .question-common-abs-reply, .question-vocabulary, .vocContainer');
console.log('找到的题目容器:', containers.length);
containers.forEach((container, index) => {
console.log(`容器 ${index + 1}:`, container.className);
});
console.log('=== 调试信息结束 ===');
console.log('提示:可以在控制台运行 debugRecording() 查看录音相关元素');
};
}
document.addEventListener('DOMContentLoaded', () => {
setupMultiPageNavigationListener();
autoResumeIfNeeded();
window.testSubmitButton = function() {
console.log('=== 测试提交按钮查找 ===');
const submitBtn = findSubmitButton();
console.log('通用查找结果:', submitBtn);
const footerBtn = findFooterButtonByText('提交');
console.log('底部按钮查找结果:', footerBtn);
const dialogHandled = handleSubmitConfirmDialog();
console.log('提交确认弹窗处理结果:', dialogHandled);
const allButtons = document.querySelectorAll('button');
console.log('页面中所有按钮:');
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('确定')) {
console.log(` ${index}: "${text}" - 类名: ${classes}`);
}
});
const dialogs = document.querySelectorAll('[class*="dialog"], [role="dialog"], .modal, [class*="modal"]');
console.log('页面中的弹窗元素:', dialogs.length);
dialogs.forEach((dialog, index) => {
if (dialog.offsetParent !== null) {
console.log(` 弹窗 ${index}: 可见 - ${dialog.className}`);
console.log(` 内容: ${dialog.textContent.substring(0, 100)}...`);
}
});
return { submitBtn, footerBtn, dialogHandled };
};
window.testItestQuestions = function() {
console.log('=== 测试itest题型结构 ===');
const itestSections = document.querySelectorAll('.itest-section');
console.log('找到itest sections:', itestSections.length);
itestSections.forEach((section, index) => {
const title = section.querySelector('.title');
const sectionType = section.getAttribute('sectiontype');
console.log(`Section ${index + 1}: ${title ? title.textContent : 'No title'}, Type: ${sectionType}`);
});
const radioInputs = document.querySelectorAll('.itest-danxuan input[type="radio"]');
console.log('找到单选题选项:', 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) {
console.log(`题目 ${qindex}: ${label.textContent.trim().substring(0, 50)}...`);
console.log(` qoo属性: ${qoo}`);
}
});
console.log('单选题题目数量:', radioQuestions.size);
const textInputs = document.querySelectorAll('.blankinput');
console.log('找到填空题:', textInputs.length);
const textQuestions = new Set();
textInputs.forEach((input, index) => {
const qindex = input.getAttribute('qindex');
if (qindex) textQuestions.add(qindex);
if (index < 5) {
console.log(`填空题 ${qindex}: width=${input.style.width}`);
}
});
console.log('填空题题目数量:', textQuestions.size);
const allQuestions = new Set([...radioQuestions, ...textQuestions]);
console.log('总题目数量:', allQuestions.size);
return {
itestSections,
radioInputs,
textInputs,
radioQuestions: radioQuestions.size,
textQuestions: textQuestions.size,
totalQuestions: allQuestions.size
};
};
window.testMultipleChoice = function() {
console.log('=== 测试多选题处理 ===');
const multipleChoiceContainers = document.querySelectorAll('.MultipleChoice--checkbox-item-34A_-');
console.log('找到普通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');
console.log(`选项 ${index + 1}:`);
console.log(` 复选框:`, checkbox);
console.log(` 选项标签:`, optLabel ? optLabel.textContent.trim() : 'null');
console.log(` 内容:`, htmlView ? htmlView.textContent.trim().substring(0, 50) + '...' : 'null');
});
const allCheckboxes = document.querySelectorAll('input[type="checkbox"]');
console.log('页面中所有复选框:', allCheckboxes.length);
const testAnswers = [
['B', 'D'],
'B,D',
'BD',
{ answer: ['B', 'D'] }
];
console.log('=== 测试答案格式处理 ===');
testAnswers.forEach((answer, index) => {
let answerString = answer;
if (Array.isArray(answer)) {
answerString = answer.join(',');
} else if (typeof answer === 'object') {
answerString = JSON.stringify(answer);
}
console.log(`测试答案 ${index + 1}:`, answer, '-> 处理后:', answerString);
const letterMatches = answerString.match(/[A-Z]/gi);
console.log(` 提取的字母:`, letterMatches);
});
return { multipleChoiceContainers, allCheckboxes };
};
initRecordingFeatures();
setupSubmitConfirmHandler();
});
if (document.readyState === 'loading') {
} else {
setupMultiPageNavigationListener();
initRecordingFeatures();
setupSubmitConfirmHandler();
autoResumeIfNeeded();
}