// ==UserScript==
// @name 答题助手|超星学习通|💯自动答题|▶️一键操作|🏆超全题库(每日更新、自动收录)
// @namespace http://tampermonkey.net/
// @version 0.3.6.8
// @description 支持 超星学习通 平台的章节测试、作业、考试一键自动答题;对接百亿题库,覆盖海量题型,正确率高、速度快!
// @description 进群反馈QQ:923349555
// @author 艾凌科技工作室
// @match *://mooc1-2.chaoxing.com/exam-ans/mooc2/exam/*
// @match *://mooc1.chaoxing.com/mooc-ans/mooc2/work/*
// @match *://mooc1-api.chaoxing.com/exam-ans/mooc2/exam*
// @match *://mooc1.chaoxing.com/mycourse/studentstudy*
// @match https://mooc1.chaoxing.com/mooc-ans/knowledge/*
// @match https://mooc2-ans.chaoxing.com/*
// @require https://scriptcat.org/lib/668/1.0/TyprMd5.js
// @match https://mooc1.chaoxing.com/*
// @match https://mooc2-ans.chaoxing.com/*
// @resource Table https://www.forestpolice.org/ttf/2.0/table.json
// @run-at document-start
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant unsafeWindow
// @connect *
// ==/UserScript==
(function () {
'use strict';
// iframe内部的postMessage处理(用于跨域通信)
if (window !== window.top) {
// 当前在iframe中,监听来自父页面的消息
window.addEventListener('message', function (event) {
if (event.data && event.data.source === 'chapter_list_handler') {
if (event.data.type === 'FIND_PENDING_TASKS') {
// 查找待完成任务点
const chapterElements = document.querySelectorAll('.chapter_item, [onclick*="toOld"], .catalog_title');
let hasElements = false;
for (const element of chapterElements) {
const pendingTask = element.querySelector('.catalog_jindu .bntHoverTips') ||
element.querySelector('.bntHoverTips') ||
element.querySelector('[class*="catalog_points"]');
if (pendingTask && pendingTask.textContent.includes('待完成任务点')) {
hasElements = true;
break;
}
}
// 响应父页面
const response = {
type: 'PENDING_TASKS_FOUND',
hasElements: hasElements,
elementsCount: chapterElements.length
};
event.source.postMessage(response, '*');
} else if (event.data.type === 'CLICK_FIRST_PENDING_TASK') {
// 点击第一个待完成任务点
const chapterElements = document.querySelectorAll('.chapter_item, [onclick*="toOld"], .catalog_title');
for (const element of chapterElements) {
const pendingTask = element.querySelector('.catalog_jindu .bntHoverTips') ||
element.querySelector('.bntHoverTips') ||
element.querySelector('[class*="catalog_points"]');
if (pendingTask && pendingTask.textContent.includes('待完成任务点')) {
const clickableElement = element.querySelector('[onclick]') ||
element.closest('[onclick]') ||
element;
setTimeout(() => {
clickableElement.click();
console.log('✅ [iframe] 已点击待完成任务点章节');
}, 500);
break;
}
}
}
}
});
}
const originalAddEventListener = EventTarget.prototype.addEventListener;
const blockedEvents = ['visibilitychange', 'blur', 'focusout', 'mouseleave', 'beforeunload', 'pagehide'];
EventTarget.prototype.addEventListener = function (type, listener, options) {
if (blockedEvents.includes(type)) {
return;
}
return originalAddEventListener.call(this, type, listener, options);
};
try {
Object.defineProperty(document, 'hidden', {
get: () => false,
configurable: true
});
Object.defineProperty(document, 'visibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(document, 'webkitHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(document, 'mozHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(document, 'msHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(document, 'webkitVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(document, 'mozVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(document, 'msVisibilityState', {
get: () => 'visible',
configurable: true
});
} catch (e) {
}
document.hasFocus = () => true;
// 完全按照正确版本添加removeEventListener拦截
const oldRemove = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function (...args) {
if (args.length !== 0) {
const eventType = args[0];
if (blockedEvents.includes(eventType)) {
console.log(`[${new Date().toLocaleTimeString()}] [防切屏] 阻止移除 ${eventType} 监听器`);
return; // 不允许移除
}
}
return oldRemove.call(this, ...args);
};
// 添加正确版本的全局变量
const processedIframes = new WeakSet();
function injectHooksToDocument(doc, context = 'main') {
if (!doc || doc._hooksInjected) return;
try {
const docWindow = doc.defaultView || doc.parentWindow;
if (docWindow && docWindow.EventTarget) {
const originalAdd = docWindow.EventTarget.prototype.addEventListener;
docWindow.EventTarget.prototype.addEventListener = function (type, listener, options) {
if (blockedEvents.includes(type)) {
return;
}
return originalAdd.call(this, type, listener, options);
};
}
Object.defineProperty(doc, 'hidden', {
get: () => false,
configurable: true
});
Object.defineProperty(doc, 'visibilityState', {
get: () => 'visible',
configurable: true
});
doc.hasFocus = () => true;
doc._hooksInjected = true;
} catch (e) {
}
}
// 添加正确版本的processIframes函数
function processIframes(doc = document, context = 'main', depth = 0) {
if (depth > 5) return;
try {
const iframes = doc.querySelectorAll('iframe');
iframes.forEach((iframe, index) => {
if (processedIframes.has(iframe)) return;
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (!iframeDoc) return;
const iframeContext = `${context}-iframe-${index}`;
// 注入防切屏钩子到iframe
injectHooksToDocument(iframeDoc, iframeContext);
// 递归处理嵌套iframe
processIframes(iframeDoc, iframeContext, depth + 1);
processedIframes.add(iframe);
} catch (e) {
// 跨域限制,忽略
}
});
} catch (e) {
console.warn(`[${new Date().toLocaleTimeString()}] [iframe检测] iframe处理失败:`, e);
}
}
const clearWindowHandlers = () => {
if (window.onblur !== null) {
window.onblur = null;
}
if (window.onfocus !== null) {
window.onfocus = null;
}
if (window.onbeforeunload !== null) {
window.onbeforeunload = null;
}
};
setInterval(clearWindowHandlers, 5000); // 窗口处理器清理间隔调整为5秒
clearWindowHandlers();
const pageWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
const pageDocument = pageWindow.document;
pageWindow._paq = [];
const originalCreateElement = pageDocument.createElement;
pageDocument.createElement = function (tagName) {
if (tagName.toLowerCase() === 'script') {
const script = originalCreateElement.call(pageDocument, tagName);
const originalSrcSetter = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src').set;
Object.defineProperty(script, 'src', {
set: function (value) {
if (value.includes('piwik.js')) {
return;
}
originalSrcSetter.call(this, value);
},
configurable: true
});
return script;
}
return originalCreateElement.call(pageDocument, tagName);
};
pageDocument.onkeydown = null;
pageDocument.addEventListener('keydown', function (e) {
if (e.keyCode === 123 ||
(e.ctrlKey && e.shiftKey && e.keyCode === 73) ||
(e.shiftKey && e.keyCode === 121)) {
e.stopImmediatePropagation();
return;
}
}, true);
pageWindow.oncontextmenu = null;
pageWindow.addEventListener('contextmenu', function (e) {
e.stopImmediatePropagation();
}, true);
pageWindow.alert = function (message) {
if (message && message.includes("请勿打开控制台")) {
return;
}
};
pageWindow.close = function () {
};
Object.defineProperty(pageWindow, 'console', {
value: pageWindow.console,
writable: false,
configurable: false
});
const SERVER_CONFIG = {
apiUrl: 'https://www.toptk.xyz/api',
answerApiUrl: 'https://www.toptk.xyz/api',
timeout: 30000
};
const GLOBAL_STATE = {
isAnswering: false,
isChapterTesting: false,
lastAnswerTime: 0
};
const SITES = {
CHAOXING: {
name: '超星学习通',
host: 'mooc1.chaoxing.com',
getQuestions: getChaoxingQuestions,
selectAnswer: selectChaoxingAnswer,
},
};
let currentSite = null;
function detectSite() {
const currentHost = window.location.hostname;
const currentUrl = window.location.href;
// 检测已知站点
for (const key in SITES) {
if (currentHost.includes(SITES[key].host)) {
currentSite = SITES[key];
logMessage(`[站点检测] 已识别: ${currentSite.name}`, 'success');
return currentSite;
}
}
if (currentHost.includes('chaoxing') ||
currentUrl.includes('chaoxing') ||
document.querySelector('.questionLi') ||
document.querySelector('.mark_name') ||
document.querySelector('[typename]') ||
document.querySelector('.workTextWrap') ||
document.title.includes('超星') ||
document.title.includes('学习通') ||
currentUrl.includes('work') ||
currentUrl.includes('exam')) {
currentSite = SITES.CHAOXING;
let pageType = '未知';
if (currentUrl.includes('/exam-ans/mooc2/exam/')) {
pageType = '考试';
currentSite.pageType = 'exam';
} else if (currentUrl.includes('/mooc-ans/mooc2/work/')) {
pageType = '作业';
currentSite.pageType = 'homework';
} else if (currentUrl.includes('/mooc-ans/work/doHomeWorkNew/')) {
pageType = '章节测验';
currentSite.pageType = 'chapter_test';
} else if (currentUrl.includes('/mooc-ans/api/work/')) {
pageType = '章节测验';
currentSite.pageType = 'chapter_test';
} else if (currentUrl.includes('/ananas/modules/work/')) {
pageType = '章节测验';
currentSite.pageType = 'chapter_test';
} else {
const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null;
pageType = isHomeworkPage ? '作业' : '考试';
currentSite.pageType = isHomeworkPage ? 'homework' : 'exam';
}
logMessage(`[站点检测] 通过特征识别: ${currentSite.name} - ${pageType}页面`, 'success');
return currentSite;
}
const hasQuestionElements = document.querySelector('[class*="question"], [id*="question"], .exam, .test, .quiz');
if (hasQuestionElements) {
currentSite = SITES.CHAOXING;
logMessage(`[站点检测] 通用题目页面,使用: ${currentSite.name}`, 'warning');
return currentSite;
}
currentSite = SITES.CHAOXING;
logMessage(`[站点检测] 未识别当前站点, 使用默认解析器: ${currentSite.name}`, 'warning');
return currentSite;
}
function gmFetch(url, options = {}) {
return new Promise((resolve, reject) => {
const {
method = 'GET',
headers = {},
body = null,
timeout = SERVER_CONFIG.timeout
} = options;
GM_xmlhttpRequest({
method: method,
url: url,
headers: headers,
data: body,
timeout: timeout,
onload: function (response) {
const result = {
ok: response.status >= 200 && response.status < 300,
status: response.status,
statusText: response.statusText,
json: () => {
try {
return Promise.resolve(JSON.parse(response.responseText));
} catch (error) {
return Promise.reject(new Error(`Invalid JSON response: ${error.message}`));
}
},
text: () => Promise.resolve(response.responseText)
};
resolve(result);
},
onerror: function (error) {
reject(new Error(`Request failed: ${error.error || 'Network error'}`));
},
ontimeout: function () {
reject(new Error('Request timeout'));
}
});
});
}
const TokenManager = {
TOKEN_KEY: 'user_token',
_requestCache: new Map(),
_lastRequestTime: 0,
_minRequestInterval: 500,
async _throttleRequest(key, requestFn, cacheTime = 30000) {
const now = Date.now();
if (this._requestCache.has(key)) {
const cached = this._requestCache.get(key);
if (now - cached.timestamp < cacheTime) {
return cached.result;
}
}
let actualInterval = this._minRequestInterval;
if (key.includes('validate') || key.includes('verify')) {
actualInterval = 200;
} else if (key.includes('check') || key.includes('status')) {
actualInterval = 100;
}
const timeSinceLastRequest = now - this._lastRequestTime;
if (timeSinceLastRequest < actualInterval) {
const waitTime = actualInterval - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, waitTime));
}
this._lastRequestTime = Date.now();
try {
const result = await requestFn();
this._requestCache.set(key, {
result: result,
timestamp: Date.now()
});
return result;
} catch (error) {
if (!error.message.includes('429') && !error.message.includes('网络')) {
this._requestCache.delete(key);
}
throw error;
}
},
getToken() {
return localStorage.getItem(this.TOKEN_KEY);
},
setToken(token) {
localStorage.setItem(this.TOKEN_KEY, token);
},
clearToken() {
localStorage.removeItem(this.TOKEN_KEY);
this._requestCache.clear();
},
async checkVisitorStatus() {
const cacheKey = 'check_visitor_status';
return await this._throttleRequest(cacheKey, async () => {
const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/check-visitor`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
timeout: SERVER_CONFIG.timeout
});
if (!response.ok) {
throw new Error(`检测访问者状态失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
if (result.success) {
if (result.data.isNewUser && result.data.userToken) {
this.setToken(result.data.userToken);
return {
success: true,
hasToken: true,
token: result.data.userToken,
userInfo: result.data.userInfo,
message: result.data.message
};
}
if (!result.data.isNewUser && result.data.userToken) {
this.setToken(result.data.userToken);
return {
success: true,
hasToken: true,
token: result.data.userToken,
userInfo: result.data.userInfo,
message: result.data.message
};
}
}
if (result.data && result.data.needsToken) {
return {
success: false,
needsToken: true,
message: result.data.message || '请输入您的用户Token'
};
}
throw new Error(result.message || '检测访问者状态失败');
}, 120000);
},
async verifyUserToken(userToken) {
const cacheKey = `verify_${userToken.substring(0, 16)}`;
return await this._throttleRequest(cacheKey, async () => {
const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/verify-token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userToken: userToken
}),
timeout: SERVER_CONFIG.timeout
});
if (!response.ok) {
let errorMessage = `Token验证失败: ${response.status} ${response.statusText}`;
try {
const result = await response.json();
errorMessage = result.message || errorMessage;
} catch (parseError) {
try {
const errorText = await response.text();
errorMessage = errorText || errorMessage;
} catch (textError) {
}
}
throw new Error(errorMessage);
}
const result = await response.json();
if (!result.success) {
const errorMessage = result.message || 'Token验证失败';
throw new Error(errorMessage);
}
this.setToken(result.data.userToken);
return {
success: true,
token: result.data.userToken,
userInfo: result.data.userInfo,
message: result.data.message
};
}, 300000);
},
async promptUserToken() {
try {
const userToken = prompt(
'🔐 请输入您的用户Token\n\n' +
'如果您是首次使用,系统会在首次访问时自动为您创建Token。\n' +
'如果您已有Token,请输入完整的64位Token字符串:'
);
if (!userToken) {
throw new Error('用户取消输入Token');
}
if (userToken.length !== 64) {
throw new Error('Token格式不正确,应为64位字符串');
}
return await this.verifyUserToken(userToken);
} catch (error) {
throw error;
}
},
async _validateToken(token) {
const cacheKey = `validate_${token.substring(0, 16)}`;
return await this._throttleRequest(cacheKey, async () => {
const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/info`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-User-Token': token
},
timeout: SERVER_CONFIG.timeout
});
if (!response.ok) {
return false;
}
const result = await response.json();
return result.success;
}, 60000);
},
async getValidToken() {
let token = this.getToken();
if (token) {
const isValid = await this._validateToken(token);
if (isValid) {
return token;
}
this.clearToken();
}
try {
const result = await this.initialize();
if (result.success && result.hasToken) {
return this.getToken();
}
} catch (error) {
}
throw new Error('Token获取失败,请刷新页面重试');
},
async getUserInfo() {
try {
const token = await this.getValidToken();
const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/user/info`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-User-Token': token
},
timeout: SERVER_CONFIG.timeout
});
if (!response.ok) {
throw new Error(`获取用户信息失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '获取用户信息失败');
}
return result.userInfo;
} catch (error) {
throw error;
}
},
// 获取脚本公告
async getScriptAnnouncements() {
const cacheKey = 'script_announcements';
return await this._throttleRequest(cacheKey, async () => {
try {
const response = await gmFetch(`${SERVER_CONFIG.apiUrl}/script-announcements`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
timeout: SERVER_CONFIG.timeout
});
const result = await response.json();
if (result.success && result.data && result.data.length > 0) {
// 只返回活跃公告的纯文本内容,避免样式冲突
const activeAnnouncement = result.data.find(a => a.is_active) || result.data[0];
return activeAnnouncement ? activeAnnouncement.content : '';
}
return '';
} catch (error) {
console.warn('获取脚本公告失败:', error);
return '';
}
}, 300000); // 5分钟缓存
},
async initialize() {
try {
const storedToken = this.getToken();
if (storedToken) {
const isValid = await this._validateToken(storedToken);
if (isValid) {
return {
success: true,
hasToken: true,
token: storedToken,
message: '欢迎回来!Token已自动加载。'
};
} else {
this.clearToken();
}
}
return await this.showTokenSelectionDialog();
} catch (error) {
return {
success: false,
error: error.message,
message: '初始化失败,请刷新页面重试'
};
}
},
async showTokenSelectionDialog() {
return new Promise((resolve) => {
const dialogHTML = `
🎯 TK星球答题系统
请选择您的Token获取方式:
`;
document.body.insertAdjacentHTML('beforeend', dialogHTML);
const dialog = document.getElementById('token-dialog');
const createBtn = document.getElementById('create-new-token');
const inputBtn = document.getElementById('input-existing-token');
const inputArea = document.getElementById('token-input-area');
const tokenInput = document.getElementById('token-input');
const verifyBtn = document.getElementById('verify-token');
const cancelBtn = document.getElementById('cancel-input');
const messageDiv = document.getElementById('dialog-message');
const showMessage = (message, type = 'info') => {
messageDiv.style.display = 'block';
messageDiv.textContent = message;
if (type === 'error') {
messageDiv.style.background = '#f8d7da';
messageDiv.style.color = '#721c24';
messageDiv.style.border = '1px solid #f5c6cb';
} else if (type === 'success') {
messageDiv.style.background = '#d4edda';
messageDiv.style.color = '#155724';
messageDiv.style.border = '1px solid #c3e6cb';
} else {
messageDiv.style.background = '#d1ecf1';
messageDiv.style.color = '#0c5460';
messageDiv.style.border = '1px solid #bee5eb';
}
};
const closeDialog = () => {
dialog.remove();
};
createBtn.addEventListener('click', async () => {
try {
createBtn.disabled = true;
createBtn.textContent = '生成中...';
showMessage('正在生成新Token...', 'info');
const result = await this.checkVisitorStatus();
if (result.success && result.hasToken) {
showMessage(`Token生成成功!`, 'success');
const tokenDisplay = document.createElement('div');
tokenDisplay.style.cssText = `
background: #f8f9fa;
border: 2px solid #28a745;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
font-family: monospace;
font-size: 12px;
word-break: break-all;
cursor: pointer;
text-align: center;
`;
tokenDisplay.textContent = result.token;
tokenDisplay.title = '点击复制Token';
tokenDisplay.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(result.token);
showMessage('Token已复制到剪贴板!', 'success');
} catch (err) {
const textArea = document.createElement('textarea');
textArea.value = result.token;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showMessage('Token已复制到剪贴板!', 'success');
}
});
const rechargeNotice = document.createElement('div');
rechargeNotice.style.cssText = `
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
font-size: 14px;
color: #856404;
text-align: center;
`;
rechargeNotice.innerHTML = `
⚠️ 重要提示
新用户默认1000次查询机会,请充值后使用答题功能。
请妥善保存您的Token,它是您的唯一凭证!
`;
messageDiv.parentNode.insertBefore(tokenDisplay, messageDiv.nextSibling);
messageDiv.parentNode.insertBefore(rechargeNotice, tokenDisplay.nextSibling);
createBtn.textContent = '开始答题';
createBtn.onclick = () => {
closeDialog();
resolve({
success: true,
hasToken: true,
token: result.token,
message: '新Token已生成,您可以开始答题了!'
});
};
} else {
throw new Error(result.message || 'Token生成失败');
}
} catch (error) {
showMessage('生成Token失败: ' + error.message, 'error');
createBtn.disabled = false;
createBtn.textContent = '🆕 生成新Token';
}
});
inputBtn.addEventListener('click', () => {
inputArea.style.display = 'block';
tokenInput.focus();
});
verifyBtn.addEventListener('click', async () => {
const token = tokenInput.value.trim();
if (!token) {
showMessage('请输入Token', 'error');
return;
}
try {
verifyBtn.disabled = true;
verifyBtn.textContent = '验证中...';
showMessage('正在验证Token...', 'info');
const result = await this.verifyUserToken(token);
if (result.success) {
showMessage('Token验证成功!', 'success');
setTimeout(() => {
closeDialog();
resolve({
success: true,
hasToken: true,
token: result.token,
message: 'Token验证成功,您可以开始答题了!'
});
}, 1500);
} else {
throw new Error(result.message || 'Token验证失败');
}
} catch (error) {
showMessage('Token验证失败: ' + error.message, 'error');
verifyBtn.disabled = false;
verifyBtn.textContent = '验证Token';
}
});
cancelBtn.addEventListener('click', () => {
inputArea.style.display = 'none';
tokenInput.value = '';
messageDiv.style.display = 'none';
});
tokenInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
verifyBtn.click();
}
});
});
}
};
function handleSubmitConfirmDialog() {
const documents = [
document,
window.parent?.document,
window.top?.document
].filter(doc => doc);
let confirmDialog = null;
let foundInDocument = null;
for (const doc of documents) {
try {
confirmDialog = doc.querySelector('.popBottom');
if (confirmDialog) {
foundInDocument = doc;
break;
}
} catch (error) {
}
}
if (!confirmDialog) {
return false;
}
const targetDoc = foundInDocument || document;
const popContent = targetDoc.querySelector('#popcontent');
if (!popContent || !popContent.textContent.includes('确认提交')) {
return false;
}
const submitBtn = targetDoc.querySelector('#popok');
if (!submitBtn) {
return false;
}
try {
submitBtn.click();
console.log('✅ [提交确认] 已点击提交按钮');
GLOBAL_STATE.lastAnswerTime = Date.now();
GLOBAL_STATE.isAnswering = false;
GLOBAL_STATE.isChapterTesting = false;
console.log('📝 [提交确认] 章节测验已完成,状态已重置');
return true;
} catch (error) {
console.warn('❌ [提交确认] 点击提交按钮失败:', error);
return false;
}
}
function monitorSubmitDialog() {
let checkCount = 0;
const maxChecks = 10;
const checkInterval = setInterval(() => {
checkCount++;
const dialogHandled = handleSubmitConfirmDialog();
if (dialogHandled || checkCount >= maxChecks) {
clearInterval(checkInterval);
}
}, 1000);
}
function getChaoxingQuestions() {
try {
logMessage('🔍 [超星] 开始解析题目...', 'info');
const questions = [];
const currentUrl = window.location.href;
let pageType = '未知';
let isExamPage = false;
let isHomeworkPage = false;
let isChapterTestPage = false;
if (currentUrl.includes('/exam-ans/mooc2/exam/')) {
pageType = '考试';
isExamPage = true;
} else if (currentUrl.includes('/mooc-ans/mooc2/work/')) {
pageType = '作业';
isHomeworkPage = true;
} else if (currentUrl.includes('/mooc-ans/work/doHomeWorkNew/') ||
currentUrl.includes('/mooc-ans/api/work/') ||
currentUrl.includes('/ananas/modules/work/')) {
pageType = '章节测验';
isChapterTestPage = true;
} else {
const hasTypenameAttr = document.querySelector('.questionLi[typename]') !== null;
if (hasTypenameAttr) {
pageType = '作业';
isHomeworkPage = true;
} else {
pageType = '考试';
isExamPage = true;
}
}
const questionElements = document.querySelectorAll('.questionLi');
if (questionElements.length === 0) {
logMessage('⚠️ [超星] 未找到题目元素 (.questionLi),请确认页面结构。', 'warning');
return [];
}
logMessage(`[超星] 发现 ${questionElements.length} 个题目容器 (${pageType}页面)。`, 'info');
questionElements.forEach((questionEl, index) => {
try {
const questionData = {
type: '超星学习通',
questionType: '未知题型',
number: index + 1,
stem: '',
options: [],
score: '',
questionId: ''
};
let questionId = questionEl.getAttribute('data') ||
questionEl.id?.replace('sigleQuestionDiv_', '') || '';
if (!questionId) {
const optionWithQid = questionEl.querySelector('[qid]');
if (optionWithQid) {
questionId = optionWithQid.getAttribute('qid');
}
}
questionData.questionId = questionId;
if (isHomeworkPage) {
const typeNameAttr = questionEl.getAttribute('typename');
if (typeNameAttr) {
questionData.questionType = typeNameAttr;
console.log(`[作业页面] 从typename属性获取题型: ${typeNameAttr}`);
}
} else if (isExamPage) {
const markNameEl = questionEl.querySelector('h3.mark_name');
if (markNameEl) {
const typeScoreSpan = markNameEl.querySelector('span.colorShallow');
if (typeScoreSpan) {
const typeScoreText = typeScoreSpan.textContent.trim();
const match = typeScoreText.match(/\(([^,)]+)(?:,\s*([^)]+))?\)/);
if (match) {
questionData.questionType = match[1].trim();
if (match[2]) {
questionData.score = match[2].trim();
}
console.log(`[考试页面] 从span.colorShallow获取题型: ${questionData.questionType}`);
}
}
}
}
const markNameEl = questionEl.querySelector('h3.mark_name');
if (markNameEl) {
const titleText = markNameEl.childNodes[0]?.textContent?.trim() || '';
const numberMatch = titleText.match(/^(\d+)\./);
if (numberMatch) {
questionData.number = numberMatch[1];
}
let stemText = '';
if (isExamPage) {
const stemDiv = markNameEl.querySelector('div[style*="overflow:hidden"]');
if (stemDiv) {
stemText = stemDiv.textContent.trim();
} else {
const fullText = markNameEl.textContent.trim();
stemText = fullText.replace(/^\d+\.\s*/, '');
const typePattern = /^\s*\((单选题|多选题|判断题|填空题)(?:,\s*[^)]+)?\)\s*/;
stemText = stemText.replace(typePattern, '').trim();
}
} else if (isHomeworkPage) {
const fullText = markNameEl.textContent.trim();
stemText = fullText.replace(/^\d+\.\s*/, '');
const typePattern = /^\s*\((单选题|多选题|判断题|填空题)(?:,\s*[^)]+)?\)\s*/;
stemText = stemText.replace(typePattern, '').trim();
}
questionData.stem = stemText;
if (!questionData.questionType || questionData.questionType === '未知题型') {
if (questionData.stem && (
questionData.stem.includes('____') ||
questionData.stem.includes('()') ||
questionData.stem.includes('()') ||
questionData.stem.includes('_____') ||
questionData.stem.includes('填空') ||
questionData.stem.includes('空白')
)) {
questionData.questionType = '填空题';
} else if (questionData.stem && (
questionData.stem.includes('简答') ||
questionData.stem.includes('简述') ||
questionData.stem.includes('简单题') ||
questionData.stem.includes('请说明') ||
questionData.stem.includes('请解释')
)) {
questionData.questionType = '简单题';
} else if (questionData.stem && (
questionData.stem.includes('计算') ||
questionData.stem.includes('求解') ||
questionData.stem.includes('计算题') ||
questionData.stem.includes('求值') ||
questionData.stem.includes('=') ||
/\d+[\+\-\*\/]\d+/.test(questionData.stem)
)) {
questionData.questionType = '计算题';
} else if (questionData.stem && (
questionData.stem.includes('编程') ||
questionData.stem.includes('代码') ||
questionData.stem.includes('程序') ||
questionData.stem.includes('编程题') ||
questionData.stem.includes('function') ||
questionData.stem.includes('def ') ||
questionData.stem.includes('class ')
)) {
questionData.questionType = '编程题';
} else {
questionData.questionType = '单选题';
}
}
}
const answerContainer = questionEl.querySelector('.stem_answer');
const hasInputElements = questionEl.querySelectorAll('input[type="text"], textarea').length > 0;
const hasBlankItems = questionEl.querySelectorAll('.blankItemDiv').length > 0;
const hasUEditor = questionEl.querySelectorAll('textarea[name*="answerEditor"]').length > 0;
const hasTiankongSize = questionEl.querySelector('input[name*="tiankongsize"]');
const typeElement = questionEl.querySelector('.colorShallow');
const typeText = typeElement ? typeElement.textContent : '';
const isBlankQuestionByType = typeText.includes('填空题') || typeText.includes('【填空题】');
if ((hasInputElements || hasBlankItems || hasUEditor || hasTiankongSize || isBlankQuestionByType) &&
questionData.questionType !== '填空题') {
questionData.questionType = '填空题';
}
if (questionData.questionType !== '填空题') {
if (answerContainer) {
const optionElements = answerContainer.querySelectorAll('.clearfix.answerBg');
optionElements.forEach(optionEl => {
const labelSpan = optionEl.querySelector('.num_option, .num_option_dx');
const contentDiv = optionEl.querySelector('.answer_p');
if (labelSpan && contentDiv) {
let label = labelSpan.textContent.trim();
let content = '';
const pElement = contentDiv.querySelector('p');
if (pElement) {
content = pElement.textContent.trim();
if (questionData.questionType === '判断题') {
if (content === '对') {
label = 'T';
content = '正确';
} else if (content === '错') {
label = 'F';
content = '错误';
}
}
} else {
content = contentDiv.textContent.trim();
}
questionData.options.push({
label: label,
content: content,
element: optionEl,
dataValue: labelSpan.getAttribute('data') || label,
qid: labelSpan.getAttribute('qid') || questionData.questionId,
isMultipleChoice: questionData.questionType === '多选题'
});
}
});
}
} else {
const stemAnswerEl = questionEl.querySelector('.stem_answer');
if (stemAnswerEl) {
const answerContainers = stemAnswerEl.querySelectorAll('.Answer');
answerContainers.forEach((answerContainer, index) => {
const textareaEl = answerContainer.querySelector('textarea[name*="answerEditor"]');
const iframe = answerContainer.querySelector('iframe');
const ueditorContainer = answerContainer.querySelector('.edui-editor');
if (textareaEl) {
const editorId = textareaEl.id || textareaEl.name;
let ueditorInstanceName = null;
if (iframe && iframe.src) {
const match = iframe.src.match(/ueditorInstant(\d+)/);
if (match) {
ueditorInstanceName = `ueditorInstant${match[1]}`;
}
}
let iframeBody = null;
try {
if (iframe && iframe.contentDocument && iframe.contentDocument.body) {
iframeBody = iframe.contentDocument.body;
}
} catch (e) {
}
questionData.options.push({
label: `填空${index + 1}`,
content: '',
element: textareaEl,
dataValue: '',
isFillInBlank: true,
inputType: 'ueditor',
editorId: editorId,
ueditorContainer: ueditorContainer,
iframe: iframe,
iframeBody: iframeBody,
ueditorInstanceName: ueditorInstanceName,
answerContainer: answerContainer
});
} else {
const inputEl = answerContainer.querySelector('input[type="text"], textarea');
if (inputEl) {
questionData.options.push({
label: `填空${index + 1}`,
content: '',
element: inputEl,
dataValue: '',
isFillInBlank: true,
inputType: 'normal',
answerContainer: answerContainer
});
}
}
});
if (answerContainers.length === 0) {
const inputElements = stemAnswerEl.querySelectorAll('input[type="text"], textarea');
inputElements.forEach((inputEl, index) => {
questionData.options.push({
label: `填空${index + 1}`,
content: '',
element: inputEl,
dataValue: '',
isFillInBlank: true,
inputType: 'normal'
});
});
}
}
if (questionData.options.length === 0) {
questionData.options.push({
label: '填空1',
content: '',
element: null,
dataValue: '',
isFillInBlank: true,
isVirtual: true
});
}
}
if (questionData.stem && (questionData.options.length > 0 || questionData.questionType === '填空题')) {
questions.push(questionData);
} else {
logMessage(`⚠️ [超星] 第 ${index + 1} 题数据不完整,跳过`, 'warning');
}
} catch (e) {
logMessage(`❌ [超星] 解析第 ${index + 1} 题时出错: ${e.message}`, 'error');
}
});
if (questions.length > 0) {
logMessage(`✅ [超星] 成功解析 ${questions.length} 道题目`, 'success');
const typeCount = {};
questions.forEach(q => {
typeCount[q.questionType] = (typeCount[q.questionType] || 0) + 1;
});
const typeStats = Object.entries(typeCount)
.map(([type, count]) => `${type}:${count}`)
.join(' ');
logMessage(`📊 [超星] 题型分布: ${typeStats}`, 'info');
} else {
logMessage('❌ [超星] 未能解析到任何题目', 'error');
}
return questions;
} catch (error) {
logMessage(`❌ [超星] 题目解析失败: ${error.message}`, 'error');
return [];
}
}
function getExamQuestions() {
try {
if (!currentSite) {
detectSite();
}
const selectors = [
'.questionLi',
'.question-item',
'.exam-question',
'[class*="question"]'
];
let foundElements = [];
let usedSelector = '';
for (const selector of selectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
foundElements = elements;
usedSelector = selector;
break;
}
}
if (foundElements.length === 0) {
return [];
}
if (currentSite && typeof currentSite.getQuestions === 'function') {
console.log('🔍 [调试] 调用站点专用解析函数:', currentSite.name);
const questions = currentSite.getQuestions();
console.log('🔍 [调试] 解析结果:', questions.length, '道题目');
return questions;
} else {
console.warn('⚠️ [调试] 站点解析函数不存在,尝试通用解析');
return tryGenericParsing(foundElements, usedSelector);
}
} catch (error) {
console.error('❌ [调试] 获取题目时出错:', error);
return [];
}
}
function tryGenericParsing(elements, selector) {
const questions = [];
elements.forEach((el, index) => {
try {
const questionData = {
type: '通用解析',
questionType: '未知题型',
number: index + 1,
stem: '',
options: [],
score: ''
};
const textContent = el.textContent.trim();
if (textContent.length > 10) {
questionData.stem = textContent.substring(0, 200) + (textContent.length > 200 ? '...' : '');
if (textContent.includes('单选') || textContent.includes('Single')) {
questionData.questionType = '单选题';
} else if (textContent.includes('多选') || textContent.includes('Multiple')) {
questionData.questionType = '多选题';
} else if (textContent.includes('判断') || textContent.includes('True') || textContent.includes('False')) {
questionData.questionType = '判断题';
} else if (textContent.includes('简单题') || textContent.includes('简答') || textContent.includes('简述')) {
questionData.questionType = '简单题';
} else if (textContent.includes('计算题') || textContent.includes('计算') || textContent.includes('求解')) {
questionData.questionType = '计算题';
} else if (textContent.includes('编程题') || textContent.includes('编程') || textContent.includes('代码') || textContent.includes('程序')) {
questionData.questionType = '编程题';
}
questions.push(questionData);
console.log(`🔍 [调试] 通用解析题目 ${index + 1}:`, questionData.questionType, questionData.stem.substring(0, 50) + '...');
}
} catch (error) {
console.warn(`⚠️ [调试] 通用解析第 ${index + 1} 题失败:`, error.message);
}
});
console.log(`✅ [调试] 通用解析完成,共 ${questions.length} 道题目`);
return questions;
}
async function callCloudAPI(questionData) {
try {
const token = await TokenManager.getValidToken();
const requestData = {
question: questionData.stem,
questionType: questionData.questionType,
options: questionData.options.map(opt => ({
label: opt.label,
content: opt.content
}))
};
if (questionData.questionType === '填空题') {
requestData.options = [];
requestData.fillInBlank = true;
}
const response = await gmFetch(`${SERVER_CONFIG.answerApiUrl}/answer`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-User-Token': token
},
body: JSON.stringify(requestData),
timeout: SERVER_CONFIG.timeout
});
if (response.status === 429) {
logMessage('⚠️ 请求过于频繁,等待10秒后继续...', 'warning');
await new Promise(resolve => {
let remaining = 10;
const countdownInterval = setInterval(() => {
if (remaining <= 0) {
clearInterval(countdownInterval);
resolve();
return;
}
const statusElement = document.getElementById('question-count');
if (statusElement) {
statusElement.textContent = `⏳ 限流等待中... ${remaining}s`;
}
remaining--;
}, 1000);
});
throw new Error('429_RATE_LIMIT_HANDLED');
}
if (!response.ok) {
if (response.status === 401) {
TokenManager.clearToken();
throw new Error('Token无效或已过期,请刷新页面重新获取Token');
}
let errorText = `API请求失败: ${response.status} ${response.statusText}`;
try {
const errorResponse = await response.json();
if (errorResponse.message) {
errorText = `API请求失败: ${errorResponse.message}`;
}
} catch (jsonError) {
try {
const textResponse = await response.text();
if (textResponse) {
errorText = `API请求失败: ${response.status} ${response.statusText} - ${textResponse.substring(0, 200)}`;
}
} catch (textError) {
}
}
throw new Error(errorText);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '云端分析失败');
}
if (result.quota) {
logMessage(result.quota.message, 'info');
}
result.data.cached = result.cached || false;
result.data.responseTime = result.data.responseTime || 0;
return result.data;
} catch (error) {
console.error('❌ [客户端API] 调用失败:', error.message);
if (error.message.includes('Token') || error.message.includes('token')) {
logMessage('❌ Token验证失败,请刷新页面重新获取Token', 'error');
throw new Error('Token验证失败,请刷新页面重新获取Token');
}
throw error;
}
}
async function autoAnswerAllQuestions(delay = 1000) {
try {
if (GLOBAL_STATE.isAnswering || GLOBAL_STATE.isChapterTesting) {
logMessage('⏸️ 已有答题任务在进行中,请稍后再试', 'warning');
return [];
}
GLOBAL_STATE.isAnswering = true;
const questions = getExamQuestions();
if (!questions || !Array.isArray(questions) || questions.length === 0) {
logMessage('❌ 批量答题失败: 未找到题目', 'error');
return [];
}
logMessage(`🚀 开始自动答题,共 ${questions.length} 道题目`, 'info');
// 更新答题窗口状态
addTestLog(`开始考试答题,共 ${questions.length} 道题目`, 'info');
{
const typeCount = {};
questions.forEach(q => {
const t = q.questionType || '未知题型';
typeCount[t] = (typeCount[t] || 0) + 1;
});
const typeStats = Object.entries(typeCount).map(([t, c]) => `${t}:${c}`).join(' ');
if (typeStats) addTestLog(`题型:${typeStats}`, 'info');
}
updateTestProgress(0, questions.length);
const results = [];
for (let i = 0; i < questions.length; i++) {
// 更新答题窗口进度
updateTestProgress(i + 1, questions.length);
const statusElement = document.getElementById('question-count');
if (statusElement) {
statusElement.textContent = `📝 答题进度: ${i + 1}/${questions.length} (${Math.round((i + 1) / questions.length * 100)}%)`;
}
try {
const result = await answerSingleQuestion(i);
if (result) {
results.push(result);
} else {
logMessage(`❌ 第 ${i + 1} 题答题失败`, 'error');
}
} catch (error) {
logMessage(`❌ 第 ${i + 1} 题出错: ${error.message}`, 'error');
}
if (i < questions.length - 1) {
await new Promise(resolve => {
let remaining = Math.ceil(delay / 1000);
const countdownInterval = setInterval(() => {
if (remaining <= 0) {
clearInterval(countdownInterval);
resolve();
return;
}
if (statusElement) {
statusElement.textContent = `⏳ 等待中... ${remaining}s (第${i + 2}题准备中)`;
}
remaining--;
}, 1000);
});
}
}
setTimeout(() => {
updateQuestionCount();
}, 1000);
if (results.length === questions.length) {
logMessage(`🎉 自动答题完成!全部成功 (${results.length}/${questions.length})`, 'success');
} else {
logMessage(`⚠️ 自动答题完成,成功: ${results.length}/${questions.length}`, 'warning');
}
return results;
} catch (error) {
logMessage(`❌ 批量答题失败: ${error.message}`, 'error');
return [];
} finally {
GLOBAL_STATE.isAnswering = false;
GLOBAL_STATE.lastAnswerTime = Date.now();
}
}
function selectChaoxingAnswer(questionIndex, answer) {
try {
logMessage(`🎯 [超星] 选择第 ${questionIndex + 1} 题答案: ${answer}`, 'info');
const questions = getExamQuestions();
const questionData = questions[questionIndex];
if (!questionData) {
logMessage(`❌ [超星] 第 ${questionIndex + 1} 题数据未找到`, 'error');
return false;
}
if (questionData.questionType === '填空题') {
return selectFillInBlankAnswer(questionData, answer);
}
let answersToSelect = [];
if (questionData.questionType === '判断题') {
if (answer === 'T' || answer === 'true' || answer === '对') {
answersToSelect = ['T'];
} else if (answer === 'F' || answer === 'false' || answer === '错') {
answersToSelect = ['F'];
} else {
const option = questionData.options.find(opt => opt.label === answer);
if (option && option.dataValue) {
answersToSelect = [option.dataValue === 'true' ? 'T' : 'F'];
} else {
answersToSelect = [answer];
}
}
} else {
answersToSelect = [...answer.toUpperCase()];
}
let successCount = 0;
const questionId = questionData.questionId;
if (questionData.questionType === '多选题') {
return new Promise(async (resolve) => {
for (let i = 0; i < answersToSelect.length; i++) {
const ans = answersToSelect[i];
const targetOption = questionData.options.find(opt => opt.label === ans);
if (targetOption && targetOption.element) {
const isSelected = targetOption.element.classList.contains('hasBeenTo') ||
targetOption.element.classList.contains('selected') ||
targetOption.element.querySelector('.num_option, .num_option_dx')?.classList.contains('hasBeenTo');
if (isSelected) {
successCount++;
continue;
}
try {
const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null;
if (isHomeworkPage) {
if (typeof pageWindow.addMultipleChoice === 'function') {
pageWindow.addMultipleChoice(targetOption.element);
} else {
targetOption.element.click();
logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success');
}
} else {
const optionQid = targetOption.qid || questionId;
let success = false;
if (typeof pageWindow.saveMultiSelect === 'function' && optionQid) {
try {
pageWindow.saveMultiSelect(targetOption.element, optionQid);
success = true;
} catch (e) {
console.log(`[超星] saveMultiSelect失败:`, e.message);
}
}
if (!success && typeof pageWindow.addMultipleChoice === 'function') {
try {
pageWindow.addMultipleChoice(targetOption.element);
success = true;
} catch (e) {
console.log(`[超星] addMultipleChoice失败:`, e.message);
}
}
if (!success) {
try {
targetOption.element.click();
logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success');
success = true;
} catch (e) {
console.log(`[超星] click失败:`, e.message);
}
}
if (!success) {
logMessage(`❌ [超星] 所有方法都失败,无法选择多选题选项 ${ans}`, 'error');
}
}
targetOption.element.style.backgroundColor = '#e8f5e8';
targetOption.element.style.border = '2px solid #4ade80';
setTimeout(() => {
targetOption.element.style.backgroundColor = '';
targetOption.element.style.border = '';
}, 2000);
successCount++;
if (i < answersToSelect.length - 1) {
await new Promise(resolve => setTimeout(resolve, 300));
}
} catch (clickError) {
}
} else {
}
}
const success = successCount > 0;
if (success) {
} else {
}
resolve(success);
});
} else {
answersToSelect.forEach(ans => {
let targetOption = null;
if (questionData.questionType === '判断题') {
targetOption = questionData.options.find(opt =>
opt.label === ans ||
(ans === 'T' && (opt.dataValue === 'true' || opt.content === '正确' || opt.content === '对')) ||
(ans === 'F' && (opt.dataValue === 'false' || opt.content === '错误' || opt.content === '错'))
);
} else {
targetOption = questionData.options.find(opt => opt.label === ans);
}
if (targetOption && targetOption.element) {
const isSelected = targetOption.element.classList.contains('hasBeenTo') ||
targetOption.element.classList.contains('selected') ||
targetOption.element.querySelector('.num_option, .num_option_dx')?.classList.contains('hasBeenTo');
if (isSelected) {
successCount++;
return;
}
try {
const isHomeworkPage = document.querySelector('.questionLi[typename]') !== null;
if (isHomeworkPage) {
if (typeof pageWindow.addChoice === 'function') {
pageWindow.addChoice(targetOption.element);
logMessage(`✅ [超星] 通过addChoice()选择选项 ${ans}`, 'success');
} else {
targetOption.element.click();
logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success');
}
} else {
const optionQid = targetOption.qid || questionId;
if (typeof pageWindow.saveSingleSelect === 'function' && optionQid) {
pageWindow.saveSingleSelect(targetOption.element, optionQid);
logMessage(`✅ [超星] 通过saveSingleSelect()选择选项 ${ans}`, 'success');
} else {
targetOption.element.click();
logMessage(`✅ [超星] 通过click()选择选项 ${ans}`, 'success');
}
}
targetOption.element.style.backgroundColor = '#e8f5e8';
targetOption.element.style.border = '2px solid #4ade80';
setTimeout(() => {
targetOption.element.style.backgroundColor = '';
targetOption.element.style.border = '';
}, 2000);
successCount++;
} catch (clickError) {
logMessage(`❌ [超星] 点击选项 ${ans} 失败: ${clickError.message}`, 'error');
console.error('[超星] 点击错误详情:', clickError);
}
} else {
logMessage(`⚠️ [超星] 未找到答案选项 '${ans}'`, 'warning');
console.log('[超星] 可用选项:', questionData.options.map(opt => `${opt.label}(${opt.content})`));
}
});
}
const success = successCount > 0;
if (success) {
logMessage(`✅ [超星] 第 ${questionIndex + 1} 题答案选择完成 (${successCount}/${answersToSelect.length})`, 'success');
} else {
logMessage(`❌ [超星] 第 ${questionIndex + 1} 题答案选择失败`, 'error');
}
return success;
} catch (error) {
logMessage(`❌ [超星] 选择答案时出错: ${error.message}`, 'error');
console.error('[超星] 答案选择错误:', error);
return false;
}
}
function selectFillInBlankAnswer(questionData, answer) {
try {
logMessage(`📝 [填空题] 填入答案: ${answer}`, 'info');
const fillInBlankOptions = questionData.options.filter(opt => opt.isFillInBlank);
if (fillInBlankOptions.length === 0) {
logMessage(`❌ [填空题] 未找到输入框`, 'error');
return false;
}
let successCount = 0;
let answers = [];
if (answer.includes('|')) {
answers = answer.split('|').map(a => a.trim());
logMessage(`📝 [填空题] 使用|分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info');
} else if (answer.includes(',')) {
answers = answer.split(',').map(a => a.trim());
logMessage(`📝 [填空题] 使用,分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info');
} else if (answer.includes(',')) {
answers = answer.split(',').map(a => a.trim());
logMessage(`📝 [填空题] 使用,分隔,解析出${answers.length}个答案: ${answers.join(', ')}`, 'info');
} else {
answers = [answer.trim()];
logMessage(`📝 [填空题] 单个答案: ${answers[0]}`, 'info');
}
fillInBlankOptions.forEach((option, index) => {
if (index >= answers.length) {
logMessage(`⚠️ [填空题] 填空${index + 1}没有对应答案,跳过`, 'warning');
return;
}
const answerText = answers[index];
logMessage(`📝 [填空题] 准备填入填空${index + 1}: "${answerText}"`, 'info');
if (option.element) {
try {
if (option.inputType === 'ueditor') {
const editorId = option.editorId;
logMessage(`🔍 [填空题] UEditor ID: ${editorId}`, 'info');
let ueditorSuccess = false;
if (option.iframeBody) {
try {
option.iframeBody.innerHTML = `${answerText}
`;
const inputEvent = new Event('input', { bubbles: true });
option.iframeBody.dispatchEvent(inputEvent);
logMessage(`✅ [填空题] 直接操作iframeBody填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
} catch (error) {
logMessage(`⚠️ [填空题] 直接操作iframeBody失败: ${error.message}`, 'warning');
}
}
if (typeof window.UE !== 'undefined') {
let editor = null;
if (window.UE.getEditor) {
editor = window.UE.getEditor(editorId);
}
if (!editor && window.UE.instants && option.ueditorInstanceName) {
editor = window.UE.instants[option.ueditorInstanceName];
}
logMessage(`🔍 [填空题] UEditor实例状态: ${editor ? '找到' : '未找到'} (ID: ${editorId})`, 'info');
if (editor && editor.setContent) {
editor.setContent(answerText);
logMessage(`✅ [填空题] UEditor setContent填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
} else if (editor && editor.execCommand) {
editor.execCommand('inserthtml', answerText);
logMessage(`✅ [填空题] UEditor execCommand填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
} else if (editor && editor.body) {
editor.body.innerHTML = `${answerText}
`;
logMessage(`✅ [填空题] UEditor body操作填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
} else {
logMessage(`⚠️ [填空题] UEditor实例方法不可用,尝试其他方法`, 'warning');
}
} else {
logMessage(`⚠️ [填空题] UE对象不存在,尝试其他方法`, 'warning');
}
if (!ueditorSuccess && option.iframe) {
try {
const iframe = option.iframe;
logMessage(`🔍 [填空题] 重新尝试获取iframe body: ${iframe.id}`, 'info');
const tryGetIframeBody = (attempts = 0) => {
try {
if (iframe.contentDocument && iframe.contentDocument.body) {
const iframeBody = iframe.contentDocument.body;
if (iframeBody.contentEditable === 'true' || iframeBody.classList.contains('view')) {
iframeBody.innerHTML = `${answerText}
`;
const events = ['input', 'change', 'keyup', 'blur'];
events.forEach(eventType => {
try {
const event = new iframe.contentWindow.Event(eventType, { bubbles: true });
iframeBody.dispatchEvent(event);
} catch (e) {
}
});
logMessage(`✅ [填空题] iframe重新获取填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
return true;
} else {
logMessage(`⚠️ [填空题] iframe body不可编辑`, 'warning');
}
}
} catch (e) {
logMessage(`⚠️ [填空题] iframe访问失败 (尝试${attempts + 1}): ${e.message}`, 'warning');
}
if (attempts < 2) {
setTimeout(() => tryGetIframeBody(attempts + 1), 200);
}
return false;
};
tryGetIframeBody();
} catch (error) {
logMessage(`⚠️ [填空题] iframe重新获取失败: ${error.message}`, 'warning');
}
}
if (!ueditorSuccess && option.iframe) {
try {
const iframe = option.iframe;
if (iframe.contentDocument && iframe.contentWindow) {
const iframeDoc = iframe.contentDocument;
const iframeBody = iframeDoc.body;
if (iframeBody) {
iframeBody.innerHTML = '';
const p = iframeDoc.createElement('p');
p.textContent = answerText;
p.appendChild(iframeDoc.createElement('br'));
iframeBody.appendChild(p);
const inputEvent = new Event('input', { bubbles: true });
iframeBody.dispatchEvent(inputEvent);
logMessage(`✅ [填空题] 模拟操作填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
}
}
} catch (error) {
logMessage(`⚠️ [填空题] 模拟操作失败: ${error.message}`, 'warning');
}
}
if (!ueditorSuccess && option.iframe) {
try {
const iframe = option.iframe;
logMessage(`🔍 [填空题] 使用保存的iframe引用: ${iframe.id}`, 'info');
const setIframeContent = () => {
try {
if (iframe.contentDocument && iframe.contentDocument.body) {
const body = iframe.contentDocument.body;
body.innerHTML = `${answerText}
`;
if (iframe.contentWindow) {
const inputEvent = new iframe.contentWindow.Event('input', { bubbles: true });
body.dispatchEvent(inputEvent);
}
logMessage(`✅ [填空题] 使用iframe引用填空${index + 1}已填入: ${answerText}`, 'success');
ueditorSuccess = true;
successCount++;
return true;
}
} catch (e) {
logMessage(`⚠️ [填空题] iframe内容设置失败: ${e.message}`, 'warning');
}
return false;
};
if (!setIframeContent()) {
setTimeout(setIframeContent, 200);
}
} catch (error) {
logMessage(`⚠️ [填空题] iframe引用操作失败: ${error.message}`, 'warning');
}
}
} else {
const element = option.element;
logMessage(`🔍 [填空题] 元素类型: ${element.tagName}, name: ${element.name}, id: ${element.id}`, 'info');
element.value = answerText;
if (element.value === answerText) {
logMessage(`✅ [填空题] 值设置成功: ${element.value}`, 'info');
} else {
logMessage(`❌ [填空题] 值设置失败,期望: ${answerText}, 实际: ${element.value}`, 'error');
}
const events = ['input', 'change', 'blur', 'keyup'];
events.forEach(eventType => {
const event = new Event(eventType, { bubbles: true, cancelable: true });
element.dispatchEvent(event);
});
element.focus();
setTimeout(() => {
element.blur();
}, 100);
logMessage(`✅ [填空题] 普通填空${index + 1}已填入: ${answerText}`, 'success');
successCount++;
}
if (option.element.style) {
option.element.style.backgroundColor = '#e8f5e8';
option.element.style.border = '2px solid #4ade80';
setTimeout(() => {
option.element.style.backgroundColor = '';
option.element.style.border = '';
}, 2000);
}
setTimeout(() => {
const currentValue = option.element.value;
if (currentValue === answerText) {
logMessage(`✅ [填空题] 验证成功,填空${index + 1}当前值: ${currentValue}`, 'success');
} else {
logMessage(`❌ [填空题] 验证失败,填空${index + 1}期望: ${answerText}, 实际: ${currentValue}`, 'error');
logMessage(`💡 [填空题] 请手动检查并填入答案: ${answerText}`, 'warning');
}
}, 1000);
} catch (error) {
logMessage(`❌ [填空题] 填空${index + 1}填入失败: ${error.message}`, 'error');
console.error(`[填空题] 详细错误:`, error);
}
} else if (option.isVirtual) {
logMessage(`📝 [填空题] 虚拟填空${index + 1}答案: ${answerText}`, 'info');
logMessage(`💡 [填空题] 请手动将答案"${answerText}"填入对应位置`, 'warning');
successCount++;
} else {
logMessage(`❌ [填空题] 填空${index + 1}没有找到输入框`, 'error');
}
});
const success = successCount > 0;
if (success) {
logMessage(`✅ [填空题] 答案填入完成 (${successCount}/${fillInBlankOptions.length})`, 'success');
} else {
logMessage(`❌ [填空题] 答案填入失败`, 'error');
}
return success;
} catch (error) {
logMessage(`❌ [填空题] 填入答案时出错: ${error.message}`, 'error');
return false;
}
}
async function selectAnswer(questionIndex, answer) {
if (!currentSite) detectSite();
if (currentSite && typeof currentSite.selectAnswer === 'function') {
const result = currentSite.selectAnswer(questionIndex, answer);
if (result && typeof result.then === 'function') {
return await result;
}
return result;
}
return false;
}
function logMessage(message, type = 'info') {
const logArea = document.getElementById('log-display');
if (!logArea) return;
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const colors = {
'info': '#6c757d',
'success': '#28a745',
'warning': '#ffc107',
'error': '#dc3545',
'question': '#007bff',
'answer': '#17a2b8'
};
const logEntry = document.createElement('div');
logEntry.style.cssText = `
margin-bottom: 8px;
padding: 8px 12px;
border-radius: 6px;
background: white;
border-left: 3px solid ${colors[type] || colors.info};
font-size: 12px;
line-height: 1.4;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
`;
const timeSpan = document.createElement('span');
timeSpan.style.cssText = `
color: #6c757d;
font-weight: 500;
margin-right: 8px;
`;
timeSpan.textContent = `[${timestamp}]`;
const messageSpan = document.createElement('span');
messageSpan.style.color = colors[type] || colors.info;
messageSpan.textContent = message;
logEntry.appendChild(timeSpan);
logEntry.appendChild(messageSpan);
logArea.appendChild(logEntry);
logArea.scrollTop = logArea.scrollHeight;
if (logArea.children.length > 50) {
logArea.removeChild(logArea.firstChild);
}
}
function logQuestionAnswer(question, answer, questionType = '') {
const logArea = document.getElementById('log-display');
if (!logArea) return;
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
const logEntry = document.createElement('div');
logEntry.style.cssText = `
margin-bottom: 12px;
padding: 12px;
border-radius: 8px;
background: linear-gradient(135deg, #f8f9ff 0%, #fff 100%);
border: 1px solid #e3f2fd;
font-size: 12px;
line-height: 1.5;
`;
const timeSpan = document.createElement('div');
timeSpan.style.cssText = `
color: #666;
font-weight: 500;
margin-bottom: 6px;
font-size: 11px;
`;
timeSpan.textContent = `[${timestamp}] ${questionType}`;
const questionDiv = document.createElement('div');
questionDiv.style.cssText = `
color: #333;
margin-bottom: 6px;
font-weight: 500;
`;
questionDiv.textContent = question.length > 80 ? question.substring(0, 80) + '...' : question;
const answerDiv = document.createElement('div');
answerDiv.style.cssText = `
color: #28a745;
font-weight: 600;
padding: 4px 8px;
background: rgba(40, 167, 69, 0.1);
border-radius: 4px;
display: inline-block;
`;
answerDiv.textContent = `答案:${answer}`;
logEntry.appendChild(timeSpan);
logEntry.appendChild(questionDiv);
logEntry.appendChild(answerDiv);
logArea.appendChild(logEntry);
logArea.scrollTop = logArea.scrollHeight;
if (logArea.children.length > 50) {
logArea.removeChild(logArea.firstChild);
}
}
async function answerSingleQuestion(questionIndex) {
try {
const questions = getExamQuestions();
if (!questions || questionIndex >= questions.length) {
logMessage(`❌ 第 ${questionIndex + 1} 题: 题目不存在`, 'error');
return null;
}
const questionData = questions[questionIndex];
logMessage(`📝 正在答第 ${questionIndex + 1} 题: ${questionData.questionType}`, 'info');
// 更新答题窗口
updateCurrentQuestion(questionData.stem, questionData.questionType);
addTestLog(`正在处理第 ${questionIndex + 1} 题`, 'info');
const apiResponse = await callCloudAPI(questionData);
if (!apiResponse || !apiResponse.answer) {
logMessage(`❌ 第 ${questionIndex + 1} 题: 未获取到答案`, 'error');
return null;
}
const answer = apiResponse.answer.trim();
logMessage(`💡 第 ${questionIndex + 1} 题答案: ${answer}`, 'success');
// 更新答题窗口
updateCurrentAnswer(answer);
addTestLog(`获取到答案: ${answer}`, 'success');
const selectSuccess = await selectAnswer(questionIndex, answer);
if (selectSuccess) {
logMessage(`✅ 第 ${questionIndex + 1} 题答题成功,选择答案${answer}`, 'success');
addTestLog(`第 ${questionIndex + 1} 题答题成功,选择答案${answer}`, 'success');
return {
questionIndex: questionIndex + 1,
answer: answer,
success: true
};
} else {
logMessage(`❌ 第 ${questionIndex + 1} 题: 答案选择失败`, 'error');
return null;
}
} catch (error) {
logMessage(`❌ 第 ${questionIndex + 1} 题答题异常: ${error.message}`, 'error');
return null;
}
}
async function autoStartAnswering() {
try {
await new Promise(resolve => setTimeout(resolve, 1500));
const currentUrl = window.location.href;
logMessage(`🔍 当前页面URL: ${currentUrl}`, 'info');
if (currentUrl.includes('exam-ans/exam/test/reVersionTestStartNew')) {
logMessage('📄 检测到考试开始页面,查找整卷预览按钮...', 'info');
// 显示考试答题窗口
showExamWindow();
addTestLog('检测到考试开始页面', 'info');
const previewButton = document.querySelector('.sub-button a.completeBtn');
if (previewButton && previewButton.textContent.includes('整卷预览')) {
logMessage('📄 找到整卷预览按钮,将自动点击...', 'info');
setTimeout(() => {
if (typeof pageWindow.topreview === 'function') {
logMessage('⚡️ 直接调用页面函数 topreview()。', 'info');
pageWindow.topreview();
} else {
logMessage('⚠️ 页面函数 topreview 不存在,回退到模拟点击。', 'warning');
previewButton.click();
}
}, 1500);
return;
} else {
logMessage('❌ 未找到整卷预览按钮', 'warning');
}
}
else if (currentUrl.includes('exam-ans/mooc2/exam/preview')) {
logMessage('📝 检测到答题预览页面,开始自动答题...', 'info');
// 显示考试答题窗口
showExamWindow();
addTestLog('检测到考试预览页面,开始答题', 'info');
const questions = getExamQuestions();
if (questions && questions.length > 0) {
logMessage(`发现 ${questions.length} 道题目,开始自动答题`, 'success');
const results = await autoAnswerAllQuestions(2000);
if (results.length > 0) {
logMessage(`自动答题完成,成功 ${results.length} 题`, 'success');
} else {
logMessage('自动答题未成功,请检查页面', 'warning');
}
} else {
logMessage('未发现题目,可能页面还在加载中', 'info');
}
}
else {
logMessage('🔍 其他页面,使用通用检测逻辑...', 'info');
if (currentSite && currentSite.name === '超星学习通') {
const previewButton = document.querySelector('.sub-button a.completeBtn');
if (previewButton && previewButton.textContent.includes('整卷预览')) {
logMessage('📄 检测到单题模式,将自动点击"整卷预览"...', 'info');
setTimeout(() => {
if (typeof pageWindow.topreview === 'function') {
logMessage('⚡️ 直接调用页面函数 topreview()。', 'info');
pageWindow.topreview();
} else {
logMessage('⚠️ 页面函数 topreview 不存在,回退到模拟点击。', 'warning');
previewButton.click();
}
}, 1500);
return;
}
}
const questions = getExamQuestions();
if (questions && questions.length > 0) {
logMessage(`发现 ${questions.length} 道题目,开始自动答题`, 'success');
const results = await autoAnswerAllQuestions(2000);
if (results.length > 0) {
logMessage(`自动答题完成,成功 ${results.length} 题`, 'success');
} else {
logMessage('自动答题未成功,请检查页面', 'warning');
}
} else {
logMessage('未发现题目,可能不是答题页面', 'info');
}
}
} catch (error) {
logMessage(`自动答题启动失败: ${error.message}`, 'error');
}
}
async function initializeApp() {
try {
// 创建答题窗口(必须在最开始创建,确保顶层窗口可见)
createAnswerWindow('chapter');
window.addEventListener('unhandledrejection', event => {
if (event.reason && event.reason.message && event.reason.message.includes('429')) {
logMessage('⚠️ 请求过于频繁,请稍后重试', 'warning');
}
});
detectSite();
const tokenResult = await TokenManager.initialize();
if (!tokenResult.hasToken) {
logMessage('❌ Token未设置,请先配置Token', 'error');
// 继续尝试自动启动逻辑(用户可在后续弹窗中完成Token)
}
// 检测到答题页面,启动答题功能
if (currentSite) {
logMessage('📝 答题页面检测成功,自动答题功能已启动', 'success');
logMessage('📝 章节测验自动答题功能已启用', 'info');
logMessage('📝 作业自动答题功能已启用', 'info');
logMessage('📝 考试自动答题功能已启用', 'info');
// 不再提前返回,继续执行自动启动逻辑
}
logMessage('✅ 云端AI答题助手启动完成', 'success');
await autoStartAnswering();
} catch (error) {
logMessage(`❌ 初始化失败: ${error.message}`, 'error');
}
}
if (pageWindow.AI_ASSISTANT_INITIALIZED) {
return;
}
pageWindow.AI_ASSISTANT_INITIALIZED = true;
if (pageDocument.readyState === 'loading') {
pageDocument.addEventListener('DOMContentLoaded', () => {
setTimeout(initializeApp, 800);
});
} else {
setTimeout(initializeApp, 800);
}
// 创建答题状态窗口(支持章节测试和考试)
function createAnswerWindow(windowType = 'chapter') {
const windowId = 'answerWindow';
// 策略:只在顶层页面创建窗口
// 1. 如果是顶层窗口 → 直接创建
// 2. 如果是iframe → 尝试在顶层创建
let targetDocument = document;
let targetWindow = window;
let creationContext = '当前页面';
// 尝试访问顶层窗口
if (window !== window.top) {
if (canAccessTop()) {
// 同源iframe,可以访问顶层
targetDocument = window.top.document;
targetWindow = window.top;
creationContext = '顶层页面';
console.log('✅ [答题窗口] iframe检测:可访问顶层,将在顶层创建窗口');
} else {
// 跨域iframe,无法访问顶层,放弃创建
console.log('⚠️ [答题窗口] iframe检测:跨域限制,跳过创建(避免在iframe内创建)');
return;
}
} else {
// 当前就是顶层窗口
console.log('✅ [答题窗口] 检测到顶层窗口,准备创建');
}
// 检查是否已存在窗口
if (targetDocument.getElementById(windowId)) {
console.log('ℹ️ [答题窗口] 窗口已存在于' + creationContext);
return;
}
console.log(`🎨 [答题窗口] 开始在${creationContext}创建答题窗口`);
// 根据窗口类型设置标题
const windowTitle = windowType === 'exam' ? '📝 考试答题助手' : '📝 章节测试答题助手';
const windowDiv = targetDocument.createElement('div');
windowDiv.id = windowId;
windowDiv.innerHTML = `
⚡
批量刷课神器
一键自动完成所有课程
xxt.toptk.xyz/app
→
`;
// 添加样式
const style = document.createElement('style');
style.textContent = `
#answerWindow {
position: fixed;
top: 20px;
right: 20px;
width: 400px;
background: #ffffff;
border: 2px solid #e0e0e0;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: hidden;
animation: slideIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes slideIn {
from {
transform: translateX(100%) scale(0.9);
opacity: 0;
}
to {
transform: translateX(0) scale(1);
opacity: 1;
}
}
.test-window-header {
background: #ffffff;
color: #333;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
font-size: 15px;
cursor: move;
}
.test-window-close {
background: #f5f5f5;
border: none;
font-size: 18px;
color: #666;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s;
}
.test-window-close:hover {
background: #e0e0e0;
color: #333;
}
.test-window-content {
padding: 20px;
background: #ffffff;
}
.usage-tip {
background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);
border-radius: 10px;
padding: 14px 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 12px;
animation: tipPulse 2s ease-in-out infinite;
}
@keyframes tipPulse {
0%, 100% {
box-shadow: 0 2px 10px rgba(255, 154, 158, 0.3);
}
50% {
box-shadow: 0 4px 20px rgba(255, 154, 158, 0.5);
}
}
.tip-icon {
font-size: 24px;
flex-shrink: 0;
animation: tipBounce 2s ease-in-out infinite;
}
@keyframes tipBounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-3px);
}
}
.tip-text {
color: #fff;
font-size: 14px;
font-weight: 600;
line-height: 1.5;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.announcement-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
display: flex;
align-items: center;
gap: 15px;
}
.announcement-clickable {
cursor: pointer;
position: relative;
}
.announcement-clickable:hover {
transform: translateY(-3px);
box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4);
}
.announcement-icon {
font-size: 32px;
animation: pulse 2s ease-in-out infinite;
flex-shrink: 0;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.15);
}
}
.announcement-main {
flex: 1;
}
.announcement-title {
font-weight: 700;
color: #ffffff;
margin-bottom: 4px;
font-size: 16px;
letter-spacing: 0.3px;
}
.announcement-subtitle {
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
margin-bottom: 8px;
line-height: 1.4;
}
.announcement-link {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 8px 12px;
backdrop-filter: blur(10px);
}
.link-text {
font-weight: 600;
color: #ffffff;
font-size: 13px;
letter-spacing: 0.5px;
}
.link-arrow {
color: #ffffff;
font-size: 18px;
font-weight: bold;
animation: moveRight 1s ease-in-out infinite;
}
@keyframes moveRight {
0%, 100% {
transform: translateX(0);
}
50% {
transform: translateX(5px);
}
}
.token-info {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 10px;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.info-label {
color: #666;
font-size: 13px;
font-weight: 500;
}
.info-value {
color: #333;
font-size: 16px;
font-weight: 700;
}
.feedback-info {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-radius: 10px;
padding: 14px 16px;
margin-top: 12px;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.3s;
}
.feedback-info:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(168, 237, 234, 0.4);
}
.feedback-icon {
font-size: 22px;
flex-shrink: 0;
}
.feedback-text {
color: #333;
font-size: 13px;
font-weight: 500;
line-height: 1.5;
}
.group-number {
color: #667eea;
font-weight: 700;
font-size: 15px;
background: rgba(255, 255, 255, 0.8);
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
user-select: all;
}
.group-number:hover {
background: rgba(255, 255, 255, 1);
color: #5568d3;
}
`;
// 将样式和窗口添加到目标页面
if (targetDocument.head && targetDocument.body) {
targetDocument.head.appendChild(style);
targetDocument.body.appendChild(windowDiv);
console.log(`✅ [答题窗口] 窗口已成功添加到${creationContext}`);
} else {
console.error('❌ [答题窗口] 无法访问目标页面');
return;
}
// 绑定事件
bindAnswerWindowEvents(targetDocument, targetWindow);
// 更新Token信息
updateTokenDisplay();
// 加载脚本公告(可选,现在公告已硬编码到HTML中)
// loadScriptAnnouncements();
}
// 绑定答题窗口事件
function bindAnswerWindowEvents(targetDocument, targetWindow) {
// 如果没有传入参数,使用安全的跨域检测函数
const doc = targetDocument || getTopDocument();
const win = targetWindow || getTopWindow();
const testWindow = doc.getElementById('answerWindow');
if (!testWindow) {
console.error('❌ [答题窗口] 无法找到答题窗口元素');
return;
}
const closeBtn = testWindow.querySelector('.test-window-close');
const header = testWindow.querySelector('.test-window-header');
const announcementInfo = testWindow.querySelector('.announcement-info');
console.log('🔧 [答题窗口] 开始绑定事件');
// 关闭按钮
if (closeBtn) {
closeBtn.addEventListener('click', () => {
testWindow.style.display = 'none';
});
}
// 点击公告跳转到批量刷课网站
if (announcementInfo) {
announcementInfo.addEventListener('click', () => {
win.open('http://xxt.toptk.xyz/app', '_blank');
logMessage('🚀 已打开批量刷课平台', 'success');
});
// 添加鼠标悬停提示
announcementInfo.title = '点击打开批量刷课平台';
}
// 点击群号复制到剪贴板
const groupNumber = testWindow.querySelector('.group-number');
if (groupNumber) {
groupNumber.addEventListener('click', (e) => {
e.stopPropagation(); // 防止触发父元素的点击事件
const groupNum = '923349555';
// 尝试使用现代的 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(groupNum).then(() => {
logMessage('✅ 群号已复制到剪贴板: ' + groupNum, 'success');
// 临时改变样式表示已复制
const originalText = groupNumber.textContent;
groupNumber.textContent = '已复制!';
setTimeout(() => {
groupNumber.textContent = originalText;
}, 1500);
}).catch(() => {
logMessage('❌ 复制失败,请手动复制群号: ' + groupNum, 'error');
});
} else {
// 降级方案:使用传统方法
const textarea = doc.createElement('textarea');
textarea.value = groupNum;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
doc.body.appendChild(textarea);
textarea.select();
try {
doc.execCommand('copy');
logMessage('✅ 群号已复制到剪贴板: ' + groupNum, 'success');
const originalText = groupNumber.textContent;
groupNumber.textContent = '已复制!';
setTimeout(() => {
groupNumber.textContent = originalText;
}, 1500);
} catch (err) {
logMessage('❌ 复制失败,请手动复制群号: ' + groupNum, 'error');
}
doc.body.removeChild(textarea);
}
});
// 添加鼠标悬停提示
groupNumber.title = '点击复制群号';
}
// 拖拽功能
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
if (header) {
header.addEventListener('mousedown', (e) => {
isDragging = true;
const rect = testWindow.getBoundingClientRect();
dragOffset.x = e.clientX - rect.left;
dragOffset.y = e.clientY - rect.top;
header.style.cursor = 'grabbing';
e.preventDefault();
});
}
doc.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const x = e.clientX - dragOffset.x;
const y = e.clientY - dragOffset.y;
const maxX = win.innerWidth - testWindow.offsetWidth;
const maxY = win.innerHeight - testWindow.offsetHeight;
testWindow.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
testWindow.style.top = Math.max(0, Math.min(y, maxY)) + 'px';
testWindow.style.right = 'auto';
});
doc.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
if (header) header.style.cursor = 'move';
}
});
console.log('✅ [答题窗口] 事件绑定完成');
}
// 跨域检测缓存(只检测一次,避免重复错误消息)
let topAccessCache = null;
let topAccessChecked = false;
// 检测是否可以访问顶层窗口(只检测一次)
function canAccessTop() {
if (topAccessChecked) {
return topAccessCache;
}
topAccessChecked = true;
try {
// 如果就是顶层窗口,直接返回true
if (!window.top || window.top === window) {
topAccessCache = false;
return false;
}
// 尝试访问顶层窗口的document来测试跨域
const test = window.top.document.body;
topAccessCache = true;
console.log('✅ [窗口检测] 可访问顶层窗口,悬浮窗将显示在页面最上层');
return true;
} catch (e) {
// 跨域时会抛出SecurityError
topAccessCache = false;
console.log('ℹ️ [窗口检测] 检测到iframe跨域限制,悬浮窗将显示在当前框架内(这是正常的安全机制)');
return false;
}
}
// 获取顶层document的辅助函数(带跨域检测)
function getTopDocument() {
// 先检查是否是顶层窗口
if (window === window.top) {
return document;
}
// 如果能访问顶层窗口,使用顶层document
if (canAccessTop()) {
return window.top.document;
}
// 否则使用当前document(跨域情况)
return document;
}
// 获取顶层window的辅助函数(带跨域检测)
function getTopWindow() {
if (window === window.top) {
return window;
}
if (canAccessTop()) {
return window.top;
}
return window;
}
// 加载脚本公告
async function loadScriptAnnouncements() {
try {
console.log('[脚本公告] 开始获取脚本公告...');
const announcementContent = await TokenManager.getScriptAnnouncements();
// 使用顶层document查找元素
const topDoc = getTopDocument();
// 注意:我们现在优先显示批量刷课公告,服务器公告作为次要信息
// 如果需要显示服务器公告,可以添加到下方
if (announcementContent && announcementContent.trim()) {
// 在批量刷课公告下方添加服务器公告
const announcementContentEl = topDoc.querySelector('.announcement-content');
if (announcementContentEl) {
// 保持批量刷课公告,并在下方添加服务器额外公告
const currentContent = announcementContentEl.innerHTML;
announcementContentEl.innerHTML = currentContent + `
📢 ${announcementContent}
`;
console.log('[脚本公告] 服务器公告已追加到顶层窗口');
}
}
console.log('[脚本公告] 公告处理完成');
} catch (error) {
console.warn('[脚本公告] 加载失败:', error);
}
}
// 更新Token显示
async function updateTokenDisplay() {
try {
console.log('[Token显示] 开始获取用户信息...');
const userInfo = await TokenManager.getUserInfo();
console.log('[Token显示] 获取到用户信息:', userInfo);
// 使用顶层document查找元素
const topDoc = getTopDocument();
const tokenElement = topDoc.getElementById('tokenCount');
if (tokenElement && userInfo) {
// 使用remainingCount或remaining_queries字段
const remainingQueries = userInfo.remainingCount || userInfo.remaining_queries || 0;
tokenElement.textContent = remainingQueries;
console.log(`[Token显示] 剩余查询次数: ${remainingQueries}`);
} else {
if (tokenElement) {
tokenElement.textContent = '—';
}
console.warn('[Token显示] 用户信息获取失败, userInfo:', userInfo);
}
} catch (error) {
console.error('[Token显示] 获取失败:', error);
const topDoc = getTopDocument();
const tokenElement = topDoc.getElementById('tokenCount');
if (tokenElement) {
tokenElement.textContent = '—';
}
}
}
// 更新答题进度
function updateTestProgress(current, total) {
const topDoc = getTopDocument();
const progressElement = topDoc.getElementById('progressInfo');
if (progressElement) {
progressElement.textContent = `进度: ${current}/${total} (${Math.round(current / total * 100)}%)`;
}
}
// 更新当前题目
function updateCurrentQuestion(questionText, questionType) {
const topDoc = getTopDocument();
const questionElement = topDoc.getElementById('currentQuestionText');
if (questionElement) {
const displayText = questionText.length > 100 ?
questionText.substring(0, 100) + '...' : questionText;
questionElement.textContent = `[${questionType}] ${displayText}`;
}
}
// 更新当前答案
function updateCurrentAnswer(answer) {
const topDoc = getTopDocument();
const answerElement = topDoc.getElementById('currentAnswerText');
if (answerElement) {
answerElement.textContent = answer || '-';
}
}
// 更新题目计数
function updateQuestionCount() {
try {
// 获取当前页面的题目数量(注意:这里查找的是题目所在页面,不是顶层页面)
let questionCount = 0;
// 尝试不同的选择器来获取题目数量
const questionSelectors = [
'.questionLi', // 超星学习通
'.TiMu', // 超星学习通
'.question-item', // 通用
'[class*="question"]' // 通用
];
for (const selector of questionSelectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
questionCount = elements.length;
break;
}
}
// 更新显示(使用顶层document)
const topDoc = getTopDocument();
const countElement = topDoc.getElementById('question-count');
if (countElement) {
countElement.textContent = `题目总数: ${questionCount}`;
}
// 更新答题窗口中的题目计数
const progressElement = topDoc.getElementById('progressInfo');
if (progressElement && questionCount > 0) {
const currentText = progressElement.textContent || '';
if (!currentText.includes('/')) {
progressElement.textContent = `进度: 0/${questionCount} (0%)`;
}
}
logMessage(`📊 [题目统计] 当前页面共找到 ${questionCount} 道题目`, 'info');
} catch (error) {
logMessage(`❌ [题目统计] 更新题目计数失败: ${error.message}`, 'error');
}
}
// 添加日志条目
function addTestLog(message, type = 'info') {
const topDoc = getTopDocument();
const logContent = topDoc.getElementById('testLogContent');
if (logContent) {
const logEntry = topDoc.createElement('div');
logEntry.className = `log-entry log-${type}`;
logEntry.textContent = `${new Date().toLocaleTimeString()} - ${message}`;
logContent.appendChild(logEntry);
logContent.scrollTop = logContent.scrollHeight;
// 限制日志条目数量
const entries = logContent.querySelectorAll('.log-entry');
if (entries.length > 20) {
entries[0].remove();
}
}
}
// 显示答题窗口(通用)
function showAnswerWindow(windowType = 'chapter') {
createAnswerWindow(windowType);
const topDoc = getTopDocument();
const testWindow = topDoc.getElementById('answerWindow');
if (testWindow) {
testWindow.style.display = 'block';
const windowTitle = windowType === 'exam' ? '考试答题窗口已启动' : '章节测试窗口已启动';
addTestLog(windowTitle, 'info');
console.log('✅ [答题窗口] 窗口已显示在顶层页面');
}
}
// 显示章节测试窗口(兼容旧函数名)
function showChapterTestWindow() {
showAnswerWindow('chapter');
}
// 显示考试窗口
function showExamWindow() {
showAnswerWindow('exam');
}
// 更新窗口标题
function updateWindowTitle(windowType) {
const topDoc = getTopDocument();
const titleElement = topDoc.getElementById('windowTitle');
if (titleElement) {
const title = windowType === 'exam' ? '📝 考试答题助手' : '📝 章节测试答题助手';
titleElement.textContent = title;
}
}
function base64ToUint8Array(base64) {
var data = window.atob(base64);
var buffer = new Uint8Array(data.length);
for (var i = 0; i < data.length; ++i) {
buffer[i] = data.charCodeAt(i);
}
return buffer;
}
function chaoxingFontDecrypt(doc = document) {
var $tip = Array.from(doc.querySelectorAll('style')).find(style =>
(style.textContent || style.innerHTML || '').includes('font-cxsecret')
);
if (!$tip) return false;
var fontText = $tip.textContent || $tip.innerHTML;
var fontMatch = fontText.match(/base64,([\w\W]+?)'/);
if (!fontMatch || !fontMatch[1]) return false;
var font = Typr.parse(base64ToUint8Array(fontMatch[1]))[0];
var table = JSON.parse(GM_getResourceText('Table'));
var match = {};
for (var i = 19968; i < 40870; i++) {
var glyph = Typr.U.codeToGlyph(font, i);
if (!glyph) continue;
var path = Typr.U.glyphToPath(font, glyph);
var pathMD5 = md5(JSON.stringify(path)).slice(24);
if (table[pathMD5]) {
match[i] = table[pathMD5];
}
}
var elements = doc.querySelectorAll('.font-cxsecret');
elements.forEach(function (element) {
var html = element.innerHTML;
for (var key in match) {
if (match[key]) {
var keyChar = String.fromCharCode(key);
var valueChar = String.fromCharCode(match[key]);
var regex = new RegExp(keyChar, 'g');
html = html.replace(regex, valueChar);
}
}
element.innerHTML = html;
element.classList.remove('font-cxsecret');
});
return elements.length > 0;
}
window.restartMainInterval = function () {
if (window.mainIntervalId) {
clearInterval(window.mainIntervalId);
}
};
window.restorePage = function () {
let restoredCount = 0;
const decryptedElements = document.querySelectorAll('[data-decrypted="true"]');
decryptedElements.forEach(element => {
element.classList.add('font-cxsecret');
element.removeAttribute('data-decrypted');
restoredCount++;
});
const originalDecryptedElements = document.querySelectorAll('[data-decrypted-original="true"]');
originalDecryptedElements.forEach(element => {
element.classList.add('font-cxsecret');
element.removeAttribute('data-decrypted-original');
element.style.background = '';
element.style.borderColor = '';
restoredCount++;
});
const inlineDecryptedElements = document.querySelectorAll('[data-decrypted-inline="true"]');
inlineDecryptedElements.forEach(element => {
element.removeAttribute('data-decrypted-inline');
restoredCount++;
});
return {
restoredCount: restoredCount,
success: restoredCount > 0
};
};
window.applyChaoxingFontDecryptOriginal = function () {
try {
const allStyles = document.querySelectorAll('style');
let $tip = null;
for (const style of allStyles) {
const content = style.textContent || style.innerHTML || '';
if (content.includes('font-cxsecret')) {
$tip = style;
break;
}
}
if (!$tip) {
console.log('ℹ️ [原版解密] 未找到font-cxsecret样式');
return false;
}
console.log('✅ [原版解密] 找到font-cxsecret样式');
const fontSecretElements = document.querySelectorAll('.font-cxsecret');
if (fontSecretElements.length === 0) {
console.log('ℹ️ [原版解密] 未找到.font-cxsecret元素');
return false;
}
console.log(`✅ [原版解密] 找到 ${fontSecretElements.length} 个加密元素`);
let processedCount = 0;
fontSecretElements.forEach((element, index) => {
try {
const originalText = element.textContent || '';
element.classList.remove('font-cxsecret');
element.setAttribute('data-decrypted-original', 'true');
const newText = element.textContent || '';
console.log(` 元素 ${index + 1}: "${originalText.substring(0, 30)}..." → "${newText.substring(0, 30)}..."`);
processedCount++;
} catch (error) {
console.log(`⚠️ [原版解密] 处理元素 ${index + 1} 失败:`, error.message);
}
});
return processedCount > 0;
} catch (error) {
console.log('❌ [原版解密] 执行失败:', error.message);
return false;
}
};
window.decodePageTexts = async function () {
console.log('🔄 [批量解码] 开始解码页面中的所有乱码文本...');
try {
const mapping = await buildAIDecodingMapping();
if (Object.keys(mapping).length === 0) {
console.log('⚠️ 未获取到有效的字符映射表');
return false;
}
const elements = document.querySelectorAll('.fontLabel, .after, .CeYan *');
let decodedCount = 0;
for (const element of elements) {
const originalText = element.textContent || '';
if (originalText && /[\u5600-\u56FF]/.test(originalText)) {
let decodedText = originalText;
for (const [garbled, decoded] of Object.entries(mapping)) {
decodedText = decodedText.replace(new RegExp(garbled, 'g'), decoded);
}
if (decodedText !== originalText) {
decodedCount++;
}
}
}
console.log(`✅ 批量解码完成,共处理 ${decodedCount} 个文本`);
return true;
} catch (error) {
console.log(`❌ 批量解码失败: ${error.message}`);
return false;
}
};
async function handleChapterTest(testFrames) {
for (const frame of testFrames) {
if (!frame.accessible || !frame.doc) {
console.log('❌ [章节测验] iframe不可访问,跳过');
continue;
}
const doc = frame.doc;
const iframeWindow = frame.iframe ? frame.iframe.contentWindow : window;
const completedStatus = doc.querySelector('.testTit_status_complete');
if (completedStatus && completedStatus.textContent.includes('已完成')) {
console.log('✅ [章节测验] 检测到已完成状态,跳过答题');
return true;
}
const completedDiv = doc.querySelector('.fr.testTit_status.testTit_status_complete');
if (completedDiv && completedDiv.textContent.includes('已完成')) {
console.log('✅ [章节测验] 检测到已完成状态(方式2),跳过答题');
return true;
}
chaoxingFontDecrypt(doc);
const questions = doc.querySelectorAll('.singleQuesId');
console.log(`📄 共 ${questions.length} 道题目`);
if (questions.length === 0) {
continue;
}
GLOBAL_STATE.isChapterTesting = true;
GLOBAL_STATE.isAnswering = true;
console.log('🚀 [章节测验] 开始答题流程');
// 显示答题窗口
showChapterTestWindow();
addTestLog(`开始处理章节测验,共 ${questions.length} 道题目`, 'info');
updateTestProgress(0, questions.length);
for (let i = 0; i < questions.length; i++) {
const qEl = questions[i];
const typeText = qEl.querySelector('.newZy_TItle')?.innerText || '未知类型';
let content = qEl.querySelector('.fontLabel')?.innerText?.trim() || '';
content = content.replace(/【[^】]*题】/g, '').trim();
console.log(`📝 [${i + 1}/${questions.length}] ${typeText}`);
// 更新答题窗口
updateTestProgress(i + 1, questions.length);
updateCurrentQuestion(content, typeText);
addTestLog(`正在处理第 ${i + 1} 题`, 'info');
const options = qEl.querySelectorAll('li');
const optionsData = [];
const cleanQuestionType = typeText.replace(/【|】/g, '');
options.forEach(opt => {
let spanElement = null;
let label = '';
if (cleanQuestionType.includes('多选题')) {
spanElement = opt.querySelector('span.num_option_dx');
} else {
spanElement = opt.querySelector('span.num_option');
}
label = spanElement?.innerText || '';
const aElement = opt.querySelector('a.after');
const text = aElement?.innerText || '';
const dataValue = spanElement?.getAttribute('data') || '';
if (label && text) {
optionsData.push({
label: label,
content: text,
value: dataValue,
element: opt,
questionType: cleanQuestionType
});
}
});
try {
const cleanQuestionType = typeText.replace(/【|】/g, '');
const questionData = {
stem: content,
questionType: cleanQuestionType,
options: optionsData
};
const apiResponse = await callCloudAPI(questionData);
if (apiResponse && apiResponse.answer) {
const answer = apiResponse.answer.trim();
console.log(` ✅ 答案: ${answer}`);
// 更新答题窗口
updateCurrentAnswer(answer);
addTestLog(`获取到答案: ${answer}`, 'success');
console.log(`🎯 [答题] 选择答案: ${answer}`);
if (cleanQuestionType.includes('填空题')) {
console.log(`📝 [填空题] 开始处理填空题`);
const blankItems = qEl.querySelectorAll('.blankItemDiv');
console.log(`📝 [填空题] 找到 ${blankItems.length} 个填空项`);
let answerParts = [];
if (typeof answer === 'string') {
if (answer.includes('|')) {
answerParts = answer.split('|');
} else if (answer.includes(';')) {
answerParts = answer.split(';');
} else if (answer.includes(';')) {
answerParts = answer.split(';');
} else if (answer.includes(',')) {
answerParts = answer.split(',');
} else if (answer.includes(',')) {
answerParts = answer.split(',');
} else {
answerParts = [answer];
}
} else {
answerParts = [answer];
}
console.log(`📝 [填空题] 答案分割结果:`, answerParts);
blankItems.forEach((blankItem, blankIndex) => {
if (blankIndex < answerParts.length) {
const answerText = answerParts[blankIndex].trim();
console.log(`📝 [填空题] 第${blankIndex + 1}空填入: ${answerText}`);
addTestLog(`填空题第${blankIndex + 1}空填入: ${answerText}`, 'success');
const editorTextarea = blankItem.querySelector('textarea[name*="answerEditor"]');
if (editorTextarea) {
const editorId = editorTextarea.id;
try {
let fillSuccess = false;
if (iframeWindow && iframeWindow.UE && iframeWindow.UE.getEditor) {
try {
const editor = iframeWindow.UE.getEditor(editorId);
if (editor && editor.setContent) {
editor.setContent(answerText);
fillSuccess = true;
}
} catch (ueError) {
}
}
if (!fillSuccess) {
editorTextarea.value = answerText;
const events = ['input', 'change', 'blur', 'keyup'];
events.forEach(eventType => {
try {
const event = new (iframeWindow || window).Event(eventType, { bubbles: true });
editorTextarea.dispatchEvent(event);
} catch (eventError) {
const event = doc.createEvent('Event');
event.initEvent(eventType, true, true);
editorTextarea.dispatchEvent(event);
}
});
fillSuccess = true;
}
if (!fillSuccess) {
const inpDiv = blankItem.querySelector('.InpDIV');
if (inpDiv) {
inpDiv.innerHTML = answerText;
inpDiv.textContent = answerText;
fillSuccess = true;
}
}
} catch (error) {
}
}
}
});
} else if (cleanQuestionType.includes('判断题')) {
for (const opt of options) {
const text = opt.querySelector('a')?.innerText || '';
if ((answer === 'T' && (text === '对' || text === '正确' || text === '是')) ||
(answer === 'F' && (text === '错' || text === '错误' || text === '否'))) {
opt.click();
console.log(` 🎯 已选择: ${text}`);
addTestLog(`判断题已选择: ${text}`, 'success');
break;
}
}
} else if (cleanQuestionType.includes('多选题')) {
for (const opt of options) {
const spanElement = opt.querySelector('span.num_option_dx');
const label = spanElement?.innerText || '';
if (answer.includes(label)) {
if (typeof iframeWindow.addMultipleChoice === 'function') {
try {
iframeWindow.addMultipleChoice(opt);
console.log(` 🎯 已选择多选项: ${label}`);
addTestLog(`多选题已选择: ${label}`, 'success');
} catch (error) {
opt.click();
console.log(` 🎯 已选择多选项: ${label} (备用)`);
addTestLog(`多选题已选择: ${label} (备用方式)`, 'success');
}
} else {
opt.click();
console.log(` 🎯 已选择多选项: ${label}`);
addTestLog(`多选题已选择: ${label}`, 'success');
}
}
}
} else {
for (const opt of options) {
const spanElement = opt.querySelector('span.num_option');
const label = spanElement?.innerText || '';
if (answer.includes(label)) {
opt.click();
console.log(` 🎯 已选择: ${label}`);
addTestLog(`单选题已选择: ${label}`, 'success');
break;
}
}
}
}
} catch (error) {
console.log(` ❌ 答题异常: ${error.message}`);
addTestLog(`答题异常: ${error.message}`, 'error');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('✅ [章节测验] 答题完成,准备提交');
addTestLog('所有题目答题完成,准备提交测验', 'success');
updateCurrentQuestion('答题完成', '准备提交');
updateCurrentAnswer('等待提交...');
GLOBAL_STATE.isAnswering = false;
setTimeout(() => {
if (iframeWindow.btnBlueSubmit) {
iframeWindow.btnBlueSubmit();
console.log('✅ [自动提交] 测验提交成功');
addTestLog('测验已自动提交', 'success');
updateCurrentAnswer('已提交');
setTimeout(() => {
monitorSubmitDialog();
}, 500);
}
}, 2000);
return true;
}
return false;
}
// 检测是否为章节测验页面
function isChapterTestPage() {
const url = window.location.href;
// 章节测验页面特征
return url.includes('/mooc-ans/work/doHomeWorkNew/') ||
url.includes('/mooc-ans/api/work/') ||
url.includes('/ananas/modules/work/') ||
// 也检查页面DOM特征
document.querySelector('.singleQuesId') ||
document.querySelector('.CeYan h3') ||
document.querySelector('.questionLi') ||
document.querySelector('.newZy_TItle');
}
// 答题系统启动
console.log(`📝 [答题系统] 超星学习通答题助手已启动`);
if (isChapterTestPage()) {
console.log(`📝 [系统启动] 检测到章节测验页面,启动答题系统`);
// 立即显示答题窗口
showChapterTestWindow();
addTestLog('检测到章节测验页面,答题助手已启动', 'info');
setTimeout(async () => {
console.log(`📝 [章节测验] 页面加载完成,开始处理测验`);
// 查找章节测验iframe
const testFrames = [];
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc) {
// 检查是否是章节测验iframe
if (iframeDoc.querySelector('.singleQuesId') ||
iframeDoc.querySelector('.CeYan h3') ||
iframeDoc.querySelector('.questionLi')) {
testFrames.push({
iframe: iframe,
doc: iframeDoc,
accessible: true,
type: 'CHAPTER_TEST'
});
}
}
} catch (e) {
// 跨域iframe,跳过
}
}
// 如果没有找到iframe,检查当前页面是否直接包含测验
if (testFrames.length === 0) {
if (document.querySelector('.singleQuesId') ||
document.querySelector('.CeYan h3') ||
document.querySelector('.questionLi')) {
testFrames.push({
iframe: null,
doc: document,
accessible: true,
type: 'CHAPTER_TEST'
});
}
}
if (testFrames.length > 0) {
console.log(`📝 [章节测验] 找到 ${testFrames.length} 个测验框架,开始处理`);
addTestLog(`找到 ${testFrames.length} 个测验框架,开始处理`, 'info');
await handleChapterTest(testFrames);
} else {
console.log(`❌ [章节测验] 未找到测验内容`);
addTestLog('未找到测验内容,请检查页面', 'error');
updateCurrentQuestion('未找到测验内容', '检查页面');
}
}, 2000);
} else {
console.log(`❓ [系统启动] 未识别的页面类型`);
console.log(`🔍 [调试信息] 当前URL: ${window.location.href}`);
console.log(`🔍 [调试信息] 页面标题: ${document.title}`);
}
console.log('✅ 系统优化 答题助手已启动,专注于章节测验、作业和考试功能');
// 添加全局函数,方便手动调用
window.showChapterTestWindow = showChapterTestWindow;
window.showExamWindow = showExamWindow;
window.showAnswerWindow = showAnswerWindow;
window.addTestLog = addTestLog;
window.updateTestProgress = updateTestProgress;
window.updateCurrentQuestion = updateCurrentQuestion;
window.updateCurrentAnswer = updateCurrentAnswer;
window.updateQuestionCount = updateQuestionCount;
window.updateWindowTitle = updateWindowTitle;
})();
// 添加全局函数,方便手动调用
window.showChapterTestWindow = showChapterTestWindow;
window.showExamWindow = showExamWindow;
window.showAnswerWindow = showAnswerWindow;
window.addTestLog = addTestLog;
window.updateTestProgress = updateTestProgress;
window.updateCurrentQuestion = updateCurrentQuestion;
window.updateCurrentAnswer = updateCurrentAnswer;
window.updateQuestionCount = updateQuestionCount;
window.updateWindowTitle = updateWindowTitle;