// ==UserScript==
// @name 【限时免费使用题库】超星学习通学起plus视频作业助手
// @namespace http://tampermonkey.net/
// @version 0.3.6.2
// @description 【限时免费使用题库】【超星学习通】【学起plus】的自动挂机视频、章节测试、文档、直播、作业、考试;百亿题库;正确率高,限时免费使用
// @description 进群反馈QQ:923349555
// @author DevTools Helper
// @match *://exam.chinaedu.net/*
// @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/*
// @require https://scriptcat.org/lib/668/1.0/TyprMd5.js
// @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';
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;
Object.defineProperty(HTMLMediaElement.prototype, 'muted', {
get() {
return false;
},
set(value) {
this._actualMuted = value;
if (value === true) {
setTimeout(() => {
if (this._actualMuted === true) {
this._actualMuted = false;
}
}, 100);
}
return true;
}
});
let videoMonitorInterval = null;
let iframeMonitorInterval = null;
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);
};
}
// === 应用防切屏设置到iframe的document ===
Object.defineProperty(doc, 'hidden', {
get: () => false,
configurable: true
});
Object.defineProperty(doc, 'visibilityState', {
get: () => 'visible',
configurable: true
});
// 支持各种浏览器前缀
Object.defineProperty(doc, 'webkitHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(doc, 'mozHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(doc, 'msHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(doc, 'webkitVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(doc, 'mozVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(doc, 'msVisibilityState', {
get: () => 'visible',
configurable: true
});
doc.hasFocus = () => true;
// === 应用防切屏设置到iframe的window对象 ===
if (docWindow) {
// 页面可见性API
Object.defineProperty(docWindow.document, 'hidden', {
get: () => false,
configurable: true
});
Object.defineProperty(docWindow.document, 'visibilityState', {
get: () => 'visible',
configurable: true
});
// 支持各种浏览器前缀
Object.defineProperty(docWindow.document, 'webkitHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(docWindow.document, 'mozHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(docWindow.document, 'msHidden', {
get: () => false,
configurable: true
});
Object.defineProperty(docWindow.document, 'webkitVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(docWindow.document, 'mozVisibilityState', {
get: () => 'visible',
configurable: true
});
Object.defineProperty(docWindow.document, 'msVisibilityState', {
get: () => 'visible',
configurable: true
});
// 焦点状态
docWindow.document.hasFocus = () => true;
if (docWindow.focus) {
docWindow.focus = () => true;
}
// 视频静音状态(如果iframe内有HTMLMediaElement)
if (docWindow.HTMLMediaElement) {
Object.defineProperty(docWindow.HTMLMediaElement.prototype, 'muted', {
get() {
return false;
},
set(value) {
this._actualMuted = value;
if (value === true) {
setTimeout(() => {
if (this._actualMuted === true) {
this._actualMuted = false;
}
}, 100);
}
return true;
}
});
}
// 拦截页面可见性变化事件
const blockedVisibilityEvents = ['visibilitychange', 'webkitvisibilitychange', 'mozvisibilitychange', 'msvisibilitychange'];
if (docWindow.addEventListener) {
const originalAddEventListener = docWindow.addEventListener;
docWindow.addEventListener = function(type, listener, options) {
if (blockedVisibilityEvents.includes(type.toLowerCase())) {
console.log(`🛡️ [防切屏] 阻止iframe监听${type}事件`);
return; // 不添加监听器
}
return originalAddEventListener.call(this, type, listener, options);
};
}
// 拦截document的事件监听
if (docWindow.document && docWindow.document.addEventListener) {
const originalDocAddEventListener = docWindow.document.addEventListener;
docWindow.document.addEventListener = function(type, listener, options) {
if (blockedVisibilityEvents.includes(type.toLowerCase())) {
console.log(`🛡️ [防切屏] 阻止iframe document监听${type}事件`);
return; // 不添加监听器
}
return originalDocAddEventListener.call(this, type, listener, options);
};
}
console.log(`🛡️ [防切屏] iframe window对象防切屏设置已应用: ${context}`);
}
doc._hooksInjected = true;
console.log(`🛡️ [防切屏] iframe document防切屏设置已应用: ${context}`);
} catch (e) {
console.warn(`🛡️ [防切屏] 应用到${context}失败:`, e.message);
}
}
function monitorVideos(doc, context = 'main') {
try {
const videos = doc.querySelectorAll('video');
videos.forEach((video, index) => {
if (video._videoMonitored) return;
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
video.addEventListener('pause', function() {
if (!this.ended && this.readyState >= 2) {
// 禁用旧的视频控制逻辑,改为使用handleVideoFrames系统
const shouldPlay = false; // checkIfVideoShouldPlay(this, `${context}-video-${index}`);
if (shouldPlay) {
setTimeout(() => {
this.play().catch(e => {
console.warn(`[${new Date().toLocaleTimeString()}] [${context}] 恢复播放失败:`, e);
});
}, 100);
} else {
console.log(`[${new Date().toLocaleTimeString()}] [${context}] 视频 #${index} 等待轮到播放`);
}
}
});
if (video.paused && !video.ended && video.readyState >= 2) {
// 禁用旧的视频控制逻辑,改为使用handleVideoFrames系统
const shouldPlay = false; // checkIfVideoShouldPlay(video, `${context}-video-${index}`);
if (shouldPlay) {
video.play().catch(e => {
console.warn(`[${new Date().toLocaleTimeString()}] [${context}] 初始播放失败:`, e);
});
}
}
video._videoMonitored = true;
});
} catch (e) {
}
}
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}`;
injectHooksToDocument(iframeDoc, iframeContext);
monitorVideos(iframeDoc, iframeContext);
processIframes(iframeDoc, iframeContext, depth + 1);
processedIframes.add(iframe);
} catch (e) {
}
});
} catch (e) {
console.warn(`[${new Date().toLocaleTimeString()}] [iframe检测] iframe处理失败:`, e);
}
}
function startAdvancedVideoMonitoring() {
console.log(`[${new Date().toLocaleTimeString()}] [监控系统] 启动高级视频监控系统`);
if (videoMonitorInterval) clearInterval(videoMonitorInterval);
if (iframeMonitorInterval) clearInterval(iframeMonitorInterval);
monitorVideos(document, 'main');
processIframes(document, 'main');
videoMonitorInterval = setInterval(() => {
monitorVideos(document, 'main');
}, 3000);
iframeMonitorInterval = setInterval(() => {
processIframes(document, 'main');
}, 5000);
}
// 禁用旧的视频监控系统,改为使用handleVideoFrames系统
// if (document.readyState === 'loading') {
// document.addEventListener('DOMContentLoaded', startAdvancedVideoMonitoring);
// } else {
// startAdvancedVideoMonitoring();
// }
//
// window.addEventListener('load', startAdvancedVideoMonitoring);
const originalSetTimeout = window.setTimeout;
window.setTimeout = function(callback, delay) {
const callbackStr = callback.toString();
const pauseDetectionKeywords = ['pause', 'video'];
const stateDetectionKeywords = ['hidden', 'muted', 'visibilityState'];
if (pauseDetectionKeywords.every(keyword => callbackStr.includes(keyword)) &&
stateDetectionKeywords.some(keyword => callbackStr.includes(keyword))) {
console.log(`[${new Date().toLocaleTimeString()}] [防暂停] 拦截学习通暂停检测 setTimeout`);
return originalSetTimeout(() => {}, delay);
}
return originalSetTimeout.apply(this, arguments);
};
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, 2000);
clearWindowHandlers();
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 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);
const originalSetInterval = pageWindow.setInterval;
pageWindow.setInterval = function (callback, interval) {
const callbackStr = callback.toString();
const detectionKeywords = [
'window.outerWidth',
'window.outerHeight',
'detectZoom',
'getBrowserType',
'请勿打开控制台'
];
const pauseDetectionKeywords = ['pause', 'video'];
const stateDetectionKeywords = ['hidden', 'muted', 'visibilityState'];
if (detectionKeywords.some(keyword => callbackStr.includes(keyword))) {
return -1;
}
if (pauseDetectionKeywords.every(keyword => callbackStr.includes(keyword)) &&
stateDetectionKeywords.some(keyword => callbackStr.includes(keyword))) {
console.log(`[${new Date().toLocaleTimeString()}] [防暂停] 拦截学习通暂停检测 setInterval`);
return originalSetInterval(() => {}, interval);
}
return originalSetInterval.apply(this, arguments);
};
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,
currentPlayingVideoIndex: null // 当前播放的视频索引
};
const SITES = {
XUEQI: {
name: '学起',
host: 'exam.chinaedu.net',
getQuestions: getXueqiQuestions,
selectAnswer: selectXueqiAnswer,
},
CHAOXING: {
name: '超星学习通',
host: 'mooc1.chaoxing.com',
getQuestions: getChaoxingQuestions,
selectAnswer: selectChaoxingAnswer,
},
CHAOXING_STUDY: {
name: '超星学习通-课程学习',
host: 'mooc1.chaoxing.com',
path: '/mycourse/studentstudy',
autoStudy: true,
getQuestions: () => [],
selectAnswer: () => false,
}
};
let currentSite = null;
function detectSite() {
const currentHost = window.location.hostname;
const currentUrl = window.location.href;
if (currentUrl.includes('/mycourse/studentstudy')) {
currentSite = SITES.CHAOXING_STUDY;
logMessage(`[站点检测] 检测到学习页面: ${currentSite.name}`, 'success');
return currentSite;
}
for (const key in SITES) {
if (SITES[key].autoStudy) continue;
if (currentHost.includes(SITES[key].host)) {
currentSite = SITES[key];
logMessage(`[站点检测] 已识别: ${currentSite.name}`, 'success');
return currentSite;
}
}
if (currentHost.includes('chaoxing') &&
!currentUrl.includes('/exam-ans/') &&
!currentUrl.includes('/mooc-ans/')) {
const hasJobIcon = document.querySelector('.ans-job-icon');
const hasNextButton = document.querySelector('#prevNextFocusNext');
const hasCourseMain = document.querySelector('.course_main');
const hasVideoFrame = document.querySelector('iframe.ans-attach-online');
if (hasJobIcon || hasNextButton || hasCourseMain || hasVideoFrame) {
currentSite = SITES.CHAOXING_STUDY;
logMessage(`[站点检测] 通过DOM检测到学习页面: ${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;
}
if (currentHost.includes('chinaedu') ||
currentUrl.includes('exam') ||
document.querySelector('.questionItem') ||
document.querySelector('.queStemC') ||
document.title.includes('学起') ||
document.title.includes('考试')) {
currentSite = SITES.XUEQI;
logMessage(`[站点检测] 通过特征识别: ${currentSite.name}`, '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.XUEQI;
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',
'Authorization': `Bearer ${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 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 = `
⚠️ 重要提示
新用户默认0次查询机会,请充值后使用答题功能。
请妥善保存您的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 getXueqiQuestions() {
try {
const questions = [];
const examTypeElement = document.querySelector('.test-part .f18.c_2d4.fb');
const examType = examTypeElement ? examTypeElement.textContent.trim() : '未知题型';
const examInfoElement = document.querySelector('.test-part .c_dan');
const examInfo = examInfoElement ? examInfoElement.textContent.trim() : '';
const questionElements = document.querySelectorAll('.questionItem');
questionElements.forEach((questionEl, index) => {
const questionData = {
type: examType,
questionType: '',
number: index + 1,
stem: '',
options: [],
score: ''
};
if (questionEl.classList.contains('singItem') || questionEl.querySelector('.singItem')) {
questionData.questionType = '单选题';
} else if (questionEl.classList.contains('judge') || questionEl.querySelector('.judge')) {
questionData.questionType = '判断题';
} else if (questionEl.classList.contains('Mutli') || questionEl.querySelector('.Mutli')) {
questionData.questionType = '多选题';
} else {
questionData.questionType = '未知题型';
}
const stemElement = questionEl.querySelector('.queStemC');
if (stemElement) {
const numberEl = stemElement.querySelector('.din.fb.mr10');
if (numberEl) {
questionData.number = numberEl.textContent.trim();
}
const contentEls = stemElement.querySelectorAll('.din');
if (contentEls.length > 1) {
const contentEl = contentEls[1];
if (contentEl) {
const tableSpan = contentEl.querySelector('table span');
if (tableSpan) {
questionData.stem = tableSpan.textContent.trim();
}
else {
const pEl = contentEl.querySelector('p');
if (pEl) {
questionData.stem = pEl.textContent.trim();
} else {
const textContent = contentEl.textContent.trim();
questionData.stem = textContent.replace(/\s+/g, ' ').trim();
}
}
}
}
if (!questionData.stem) {
const allText = stemElement.textContent.trim();
const cleanText = allText.replace(/^\d+\.\s*/, '').replace(/(\d+分)$/, '').trim();
if (cleanText) {
questionData.stem = cleanText;
}
}
const scoreEl = stemElement.querySelector('.f13.c_dan var');
if (scoreEl) {
questionData.score = scoreEl.textContent.trim() + '分';
}
}
if (questionData.questionType === '判断题') {
const judgeButtons = questionEl.querySelectorAll('.JudgeBtn input');
judgeButtons.forEach((btn, idx) => {
const value = btn.value.trim();
questionData.options.push({
label: idx === 0 ? 'T' : 'F',
content: value,
element: btn
});
});
} else if (questionData.questionType === '多选题') {
const optionElements = questionEl.querySelectorAll('dd.clearfix');
optionElements.forEach(optionEl => {
const optionLabel = optionEl.querySelector('.duplexCheck');
const optionContent = optionEl.querySelector('div');
if (optionLabel && optionContent) {
questionData.options.push({
label: optionLabel.textContent.trim(),
content: optionContent.textContent.trim(),
element: optionEl
});
}
});
} else {
const optionElements = questionEl.querySelectorAll('dd.clearfix');
optionElements.forEach(optionEl => {
const optionLabel = optionEl.querySelector('.singleCheck');
const optionContent = optionEl.querySelector('div');
if (optionLabel && optionContent) {
questionData.options.push({
label: optionLabel.textContent.trim(),
content: optionContent.textContent.trim(),
element: optionEl
});
}
});
}
questions.push(questionData);
});
return questions;
} catch (error) {
return [];
}
}
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 {
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',
'.questionItem',
'.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 [];
}
console.log(`✅ [调试] 使用选择器 "${usedSelector}" 找到 ${foundElements.length} 个题目元素`);
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 = '判断题';
}
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');
const results = [];
for (let i = 0; i < questions.length; i++) {
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();
}
}
async function fillAnswers(questions, answers) {
if (!questions || questions.length === 0) {
logMessage('❌ 没有题目需要填写答案', 'error');
return { success: false, message: '没有题目需要填写答案' };
}
if (!answers || answers.length === 0) {
logMessage('❌ 没有获取到答案', 'error');
return { success: false, message: '没有获取到答案' };
}
let successCount = 0;
const results = [];
for (let i = 0; i < Math.min(questions.length, answers.length); i++) {
const question = questions[i];
const answer = answers[i];
try {
logQuestionAnswer(question.stem, answer.answer, question.questionType);
const result = await fillSingleAnswer(question, answer);
results.push(result);
if (result.success) {
successCount++;
logMessage(`✅ 第${i + 1}题填写成功`, 'success');
} else {
logMessage(`❌ 第${i + 1}题填写失败: ${result.message}`, 'error');
}
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
logMessage(`❌ 第${i + 1}题处理异常: ${error.message}`, 'error');
results.push({ success: false, message: error.message });
}
}
const message = `答题完成:成功 ${successCount}/${questions.length} 题`;
logMessage(message, successCount > 0 ? 'success' : 'error');
return {
success: successCount > 0,
message: message,
successCount: successCount,
totalCount: questions.length,
results: results
};
}
function selectXueqiAnswer(questionIndex, answer) {
try {
const questionElements = document.querySelectorAll('.questionItem');
if (questionIndex >= questionElements.length) {
console.log('题目索引超出范围');
return false;
}
const questionEl = questionElements[questionIndex];
if (questionEl.classList.contains('judge') || questionEl.querySelector('.judge')) {
const judgeButtons = questionEl.querySelectorAll('.JudgeBtn input');
for (let btn of judgeButtons) {
const btnValue = btn.value.trim();
if ((answer === 'T' && btnValue === '正确') ||
(answer === 'F' && btnValue === '错误')) {
btn.click();
const judgeBtn = btn.parentElement;
judgeBtn.style.backgroundColor = '#e8f5e8';
setTimeout(() => {
judgeBtn.style.backgroundColor = '';
}, 1000);
return true;
}
}
} else if (questionEl.classList.contains('Mutli') || questionEl.querySelector('.Mutli')) {
const options = questionEl.querySelectorAll('dd.clearfix');
if (!answer || typeof answer !== 'string' || answer.length === 0) {
console.error('多选题答案无效:', answer);
return false;
}
let answersToSelect;
try {
answersToSelect = [...answer];
} catch (err) {
console.error('多选题答案转换失败:', err);
return false;
}
let successCount = 0;
for (let option of options) {
const optionLabel = option.querySelector('.duplexCheck');
if (optionLabel) {
const labelText = optionLabel.textContent.trim();
if (answersToSelect && answersToSelect.includes(labelText)) {
option.click();
option.style.backgroundColor = '#e8f5e8';
setTimeout(() => {
option.style.backgroundColor = '';
}, 1000);
successCount++;
}
}
}
return answersToSelect && successCount === answersToSelect.length;
} else {
const options = questionEl.querySelectorAll('dd.clearfix');
for (let option of options) {
const optionLabel = option.querySelector('.singleCheck');
if (optionLabel && optionLabel.textContent.trim() === answer) {
option.click();
option.style.backgroundColor = '#e8f5e8';
setTimeout(() => {
option.style.backgroundColor = '';
}, 1000);
return true;
}
}
}
return false;
} catch (error) {
console.error('选择答案失败:', error);
return false;
}
}
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 createControlWindow() {
const isPreviewPage = window.location.href.includes('/exam-ans/mooc2/exam/preview');
if (!isPreviewPage) {
return null;
}
if (document.getElementById('exam-auto-control')) {
return document.getElementById('exam-auto-control');
}
const controlWindow = document.createElement('div');
controlWindow.id = 'exam-auto-control';
controlWindow.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 450px;
max-height: 400px;
background: #ffffff;
border: 1px solid #e1e5e9;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
backdrop-filter: blur(10px);
`;
const titleBar = document.createElement('div');
titleBar.style.cssText = `
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 16px;
cursor: move;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
`;
const titleText = document.createElement('span');
titleText.textContent = '🤖 AI答题助手';
const minimizeBtn = document.createElement('button');
minimizeBtn.innerHTML = '−';
minimizeBtn.style.cssText = `
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
line-height: 1;
`;
titleBar.appendChild(titleText);
titleBar.appendChild(minimizeBtn);
const logArea = document.createElement('div');
logArea.id = 'log-display';
logArea.style.cssText = `
height: 320px;
overflow-y: auto;
padding: 16px;
font-size: 13px;
line-height: 1.5;
background: #fafbfc;
`;
let isMinimized = false;
minimizeBtn.addEventListener('click', () => {
isMinimized = !isMinimized;
if (isMinimized) {
logArea.style.display = 'none';
controlWindow.style.height = 'auto';
minimizeBtn.innerHTML = '+';
} else {
logArea.style.display = 'block';
minimizeBtn.innerHTML = '−';
}
});
controlWindow.appendChild(titleBar);
controlWindow.appendChild(logArea);
document.body.appendChild(controlWindow);
makeDraggable(controlWindow, titleBar);
return controlWindow;
}
function makeDraggable(element, handle) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
handle.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
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');
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');
const selectSuccess = await selectAnswer(questionIndex, answer);
if (selectSuccess) {
logMessage(`✅ 第 ${questionIndex + 1} 题答题成功`, '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');
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');
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 {
createControlWindow();
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');
return;
}
if (currentSite && currentSite.autoStudy) {
logMessage('🎓 学习页面检测成功,自动学习功能已启动', 'success');
logMessage('📹 视频自动播放功能已启用', 'info');
logMessage('📝 章节测验自动答题功能已启用', 'info');
logMessage('⏭️ 自动进入下一节功能已启用', 'info');
return;
}
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 createVideoSpeedPanel() {
if (document.getElementById('videoSpeedPanel')) {
return;
}
const speedPanel = document.createElement('div');
speedPanel.id = 'videoSpeedPanel';
speedPanel.innerHTML = `
倍速控制
当前: 1x
0/0 | 等待中
`;
const style = document.createElement('style');
style.textContent = `
#videoSpeedPanel {
position: fixed;
top: 120px;
right: 20px;
width: 130px;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 10000;
font-family: Arial, sans-serif;
display: none;
font-size: 12px;
}
.speed-panel-header {
background: #f5f5f5;
padding: 6px 8px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
font-size: 12px;
}
.speed-panel-close {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
}
.speed-panel-content {
padding: 8px;
}
.speed-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
margin-bottom: 6px;
}
.speed-btn {
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 3px;
padding: 4px;
font-size: 11px;
cursor: pointer;
}
.speed-btn:hover {
background: #e9e9e9;
}
.speed-btn.active {
background: #007cba;
color: white;
border-color: #007cba;
}
.speed-info {
text-align: center;
font-size: 11px;
margin: 4px 0;
color: #666;
}
.task-status {
text-align: center;
font-size: 10px;
color: #666;
margin: 4px 0;
padding-top: 4px;
border-top: 1px solid #eee;
}
.panel-footer {
text-align: center;
font-size: 11px;
color: #007cba;
margin-top: 6px;
padding-top: 4px;
border-top: 1px solid #eee;
cursor: pointer;
}
.panel-footer:hover {
color: #0056b3;
}
`;
document.head.appendChild(style);
document.body.appendChild(speedPanel);
bindVideoSpeedEvents();
}
function bindVideoSpeedEvents() {
const speedPanel = document.getElementById('videoSpeedPanel');
const closeBtn = speedPanel.querySelector('.speed-panel-close');
const speedBtns = speedPanel.querySelectorAll('.speed-btn');
const currentSpeedSpan = document.getElementById('currentSpeed');
const dragHandle = document.getElementById('speedPanelDragHandle');
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
dragHandle.addEventListener('mousedown', (e) => {
isDragging = true;
const rect = speedPanel.getBoundingClientRect();
dragOffset.x = e.clientX - rect.left;
dragOffset.y = e.clientY - rect.top;
dragHandle.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const x = e.clientX - dragOffset.x;
const y = e.clientY - dragOffset.y;
const maxX = window.innerWidth - speedPanel.offsetWidth;
const maxY = window.innerHeight - speedPanel.offsetHeight;
speedPanel.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
speedPanel.style.top = Math.max(0, Math.min(y, maxY)) + 'px';
speedPanel.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
dragHandle.style.cursor = 'move';
}
});
closeBtn.addEventListener('click', () => {
speedPanel.style.display = 'none';
if (window.taskStatusUpdateInterval) {
clearInterval(window.taskStatusUpdateInterval);
window.taskStatusUpdateInterval = null;
}
});
speedBtns.forEach(btn => {
btn.addEventListener('click', () => {
const speed = parseFloat(btn.dataset.speed);
setVideoSpeed(speed);
speedBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentSpeedSpan.textContent = speed + 'x';
});
});
const savedSpeed = getSavedVideoSpeed();
speedBtns.forEach(btn => {
const btnSpeed = parseFloat(btn.dataset.speed);
if (btnSpeed === savedSpeed) {
btn.classList.add('active');
currentSpeedSpan.textContent = `${savedSpeed}x`;
}
});
if (!speedPanel.querySelector('.speed-btn.active')) {
speedBtns[0].classList.add('active');
}
const panelFooter = speedPanel.querySelector('.panel-footer');
if (panelFooter) {
panelFooter.addEventListener('click', () => {
const groupNumber = '923349555';
if (navigator.clipboard) {
navigator.clipboard.writeText(groupNumber).then(() => {
panelFooter.textContent = '群号已复制!';
setTimeout(() => {
panelFooter.textContent = '反馈群: 923349555';
}, 2000);
}).catch(() => {
panelFooter.textContent = '手动复制: 923349555';
setTimeout(() => {
panelFooter.textContent = '反馈群: 923349555';
}, 3000);
});
} else {
panelFooter.textContent = '手动复制: 923349555';
setTimeout(() => {
panelFooter.textContent = '反馈群: 923349555';
}, 3000);
}
});
}
}
function setVideoSpeed(speed) {
try {
localStorage.setItem('videoPlaybackRate', speed.toString());
console.log(`💾 [倍速设置] 已保存倍速: ${speed}x`);
} catch (e) {
console.warn('⚠️ [倍速设置] 保存失败:', e);
}
let videoCount = 0;
function setSpeedInDocument(doc, context = 'main', depth = 0) {
if (depth > 5) return;
try {
const videos = doc.querySelectorAll('video');
videos.forEach((video, index) => {
if (video.readyState >= 1) {
video.playbackRate = speed;
videoCount++;
console.log(`🎬 [倍速设置] ${context} 视频 #${index} 倍速已设置为 ${speed}x`);
}
});
const iframes = doc.querySelectorAll('iframe');
iframes.forEach((iframe, index) => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc) {
const iframeContext = `${context}-iframe-${index}`;
setSpeedInDocument(iframeDoc, iframeContext, depth + 1);
}
} catch (e) {
console.warn(`⚠️ [倍速设置] ${context}-iframe-${index} 访问失败(跨域)`);
}
});
} catch (e) {
console.warn(`⚠️ [倍速设置] ${context} 处理失败:`, e);
}
}
setSpeedInDocument(document, 'main');
console.log(`🎬 [倍速设置] 总共设置了 ${videoCount} 个视频为 ${speed}x 倍速`);
try {
window.dispatchEvent(new CustomEvent('videoSpeedChanged', {
detail: { speed: speed, videoCount: videoCount }
}));
} catch (e) {
}
}
function getSavedVideoSpeed() {
try {
const savedSpeed = localStorage.getItem('videoPlaybackRate');
if (savedSpeed) {
const speed = parseFloat(savedSpeed);
if (speed > 0 && speed <= 16) {
console.log(`📖 [倍速设置] 读取保存的倍速: ${speed}x`);
return speed;
}
}
} catch (e) {
console.warn('⚠️ [倍速设置] 读取失败:', e);
}
console.log(`📖 [倍速设置] 使用默认倍速: 1x`);
return 1;
}
function applySavedSpeedToVideo(video) {
if (!video) return;
const savedSpeed = getSavedVideoSpeed();
const applySpeed = () => {
if (video.readyState >= 1) {
try {
video.playbackRate = savedSpeed;
console.log(`🎬 [倍速恢复] 视频倍速已设置为: ${savedSpeed}x (readyState: ${video.readyState})`);
} catch (e) {
console.warn('⚠️ [倍速恢复] 设置失败:', e);
}
} else {
const onLoadedData = () => {
try {
video.playbackRate = savedSpeed;
console.log(`🎬 [倍速恢复] 视频加载完成后倍速已设置为: ${savedSpeed}x`);
} catch (e) {
console.warn('⚠️ [倍速恢复] 延迟设置失败:', e);
}
video.removeEventListener('loadeddata', onLoadedData);
};
video.addEventListener('loadeddata', onLoadedData);
setTimeout(() => {
video.removeEventListener('loadeddata', onLoadedData);
}, 5000);
}
};
applySpeed();
}
function updateTaskStatus() {
const taskProgressEl = document.getElementById('taskProgress');
const currentTaskEl = document.getElementById('currentTask');
if (!taskProgressEl || !currentTaskEl) return;
const taskStatus = typeof checkTaskCompletion === 'function' ? checkTaskCompletion() : { completed: 0, total: 0, isAllCompleted: false };
taskProgressEl.textContent = `${taskStatus.completed}/${taskStatus.total}`;
let currentTask = '空闲';
if (GLOBAL_STATE.isAnswering) {
currentTask = '答题中';
} else if (GLOBAL_STATE.isChapterTesting) {
currentTask = '章节测试';
} else if (taskStatus.total > 0 && !taskStatus.isAllCompleted) {
const videoIframes = document.querySelectorAll('iframe.ans-attach-online');
const hasVideo = videoIframes.length > 0;
if (hasVideo) {
currentTask = '视频学习';
} else {
currentTask = '文档学习';
}
} else if (taskStatus.isAllCompleted) {
currentTask = '已完成';
}
currentTaskEl.textContent = currentTask;
}
function showVideoSpeedPanel() {
createVideoSpeedPanel();
const speedPanel = document.getElementById('videoSpeedPanel');
speedPanel.style.display = 'block';
updateTaskStatus();
if (!window.taskStatusUpdateInterval) {
window.taskStatusUpdateInterval = setInterval(updateTaskStatus, 2000);
}
}
function startVideoSpeedMonitoring() {
console.log(`[${new Date().toLocaleTimeString()}] [倍速监控] 启动视频倍速监控系统`);
let monitorInterval = setInterval(() => {
const savedSpeed = getSavedVideoSpeed();
if (savedSpeed === 1) return;
let appliedCount = 0;
function checkVideosInDocument(doc, context = 'main', depth = 0) {
if (depth > 5) return;
try {
const videos = doc.querySelectorAll('video');
videos.forEach((video, index) => {
if (video.readyState >= 1 && video.playbackRate !== savedSpeed) {
video.playbackRate = savedSpeed;
appliedCount++;
console.log(`🔄 [倍速监控] ${context} 视频 #${index} 倍速已调整为 ${savedSpeed}x`);
}
});
const iframes = doc.querySelectorAll('iframe');
iframes.forEach((iframe, index) => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc) {
checkVideosInDocument(iframeDoc, `${context}-iframe-${index}`, depth + 1);
}
} catch (e) {
}
});
} catch (e) {
}
}
checkVideosInDocument(document, 'main');
if (appliedCount > 0) {
console.log(`🔄 [倍速监控] 本次检查调整了 ${appliedCount} 个视频的倍速`);
}
}, 5000);
window.videoSpeedMonitorId = monitorInterval;
}
if (!window.videoSpeedMonitorId) {
startVideoSpeedMonitoring();
}
function controlIframeVideo() {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] 开始检测iframe视频`);
const videoIframes = document.querySelectorAll('iframe');
let controlledCount = 0;
videoIframes.forEach((iframe, index) => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (!iframeDoc) {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 无法访问(可能跨域)`);
return;
}
const videos = iframeDoc.querySelectorAll('video');
const playButton = iframeDoc.querySelector('.vjs-big-play-button');
if (videos.length === 0 && !playButton) {
return;
}
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 发现 ${videos.length} 个视频元素`);
videos.forEach((video, videoIndex) => {
// 检查视频是否已经被处理过,避免重复设置
if (!video._iframeVideoProcessed) {
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
// 应用保存的播放速度
applySavedSpeedToVideo(video);
// 标记视频已被处理
video._iframeVideoProcessed = true;
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 初始化完成`);
}
if (!video._iframeControllerAdded) {
video.addEventListener('play', () => {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 开始播放`);
applySavedSpeedToVideo(video);
// 清除等待标记
video._waitingLogged = false;
video._pauseWaitingLogged = false;
});
video.addEventListener('pause', () => {
if (!video.ended && video.readyState >= 2) {
// 禁用旧的视频控制逻辑,改为使用handleVideoFrames系统
const shouldPlay = false; // checkIfVideoShouldPlay(video, `iframe-${index}-video-${videoIndex}`);
if (shouldPlay) {
// 避免频繁恢复播放,添加时间间隔检查
const now = Date.now();
const lastResumeAttempt = video._lastResumeAttempt || 0;
if (now - lastResumeAttempt > 1000) { // 1秒内不重复恢复
video._lastResumeAttempt = now;
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 被暂停,正在恢复播放`);
setTimeout(() => {
video.play().catch(e => {
console.warn(`[${new Date().toLocaleTimeString()}] [iframe视频] 恢复播放失败:`, e);
});
}, 100);
}
} else {
// 只在状态变化时记录日志
if (!video._pauseWaitingLogged) {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 被暂停,等待轮到播放`);
video._pauseWaitingLogged = true;
}
}
}
});
video.addEventListener('loadeddata', () => {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 加载完成`);
applySavedSpeedToVideo(video);
});
video._iframeControllerAdded = true;
}
// 只在视频暂停且准备就绪时才尝试播放,避免重复操作
if (video.paused && !video.ended && video.readyState >= 2) {
// 禁用旧的视频控制逻辑,改为使用handleVideoFrames系统
const shouldPlay = false; // checkIfVideoShouldPlay(video, `iframe-${index}-video-${videoIndex}`);
if (shouldPlay) {
// 检查是否最近已经尝试过播放,避免频繁重试
const now = Date.now();
const lastPlayAttempt = video._lastPlayAttempt || 0;
if (now - lastPlayAttempt > 3000) { // 3秒内不重复尝试播放
video._lastPlayAttempt = now;
if (playButton && playButton.offsetParent !== null) {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 点击播放按钮`);
playButton.click();
} else {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 直接播放视频`);
video.play().then(() => {
applySavedSpeedToVideo(video);
}).catch(e => {
console.warn(`[${new Date().toLocaleTimeString()}] [iframe视频] 播放失败:`, e);
});
}
}
} else {
// 只在状态变化时记录日志,避免重复日志
if (!video._waitingLogged) {
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 视频 #${videoIndex} 等待轮到播放`);
video._waitingLogged = true;
}
}
} else if (!video.paused) {
// 如果视频正在播放,清除等待日志标记
video._waitingLogged = false;
}
controlledCount++;
});
} catch (error) {
console.warn(`[${new Date().toLocaleTimeString()}] [iframe视频] iframe #${index} 处理失败:`, error.message);
}
});
if (controlledCount > 0) {
showVideoSpeedPanel();
console.log(`[${new Date().toLocaleTimeString()}] [iframe视频] 成功控制 ${controlledCount} 个视频元素`);
}
return controlledCount > 0;
}
/*
iframe视频控制功能改进:
1. ✅ 增强iframe检测 - 检测所有iframe,不仅限于特定类名
2. ✅ 递归倍速设置 - 支持多层嵌套iframe的倍速控制
3. ✅ 智能视频监控 - 为每个视频添加事件监听器,自动处理暂停恢复
4. ✅ 倍速持久化 - 保存用户设置并自动应用到新视频
5. ✅ 定期监控系统 - 定期检查并调整新加载视频的倍速
6. ✅ 详细调试日志 - 完整的日志系统便于问题排查
7. ✅ 错误处理机制 - 优雅处理跨域和其他异常情况
新增特性:
- 支持多层嵌套iframe(最多5层)
- 自动事件监听器绑定
- 视频状态智能检测
- 倍速变更事件通知
- 定期监控和自动调整
*/
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;
}
};
const IFRAME_TYPES = {
VIDEO: 'video',
CHAPTER_TEST: 'test',
DOCUMENT: 'document',
DISCUSSION: 'discussion',
UNKNOWN: 'unknown'
};
function findDeepestIframes(doc = document, depth = 0) {
const iframes = doc.querySelectorAll('iframe');
let deepestFrames = [];
for (const iframe of iframes) {
const src = iframe.src || '';
if (src.startsWith('javascript:void') ||
src.includes('UE.instants') ||
src.includes('ueditorInstant')) {
continue;
}
try {
const subDoc = iframe.contentDocument;
if (subDoc) {
if (subDoc.body && subDoc.body.className === 'view') {
continue;
}
const video = subDoc.querySelector('video');
if (video) {
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
if (video.paused && !video.ended) {
const bigPlayBtn = subDoc.querySelector('.vjs-big-play-button');
const playControlBtn = subDoc.querySelector('.vjs-play-control');
if (bigPlayBtn && bigPlayBtn.offsetParent !== null) {
bigPlayBtn.click();
} else if (playControlBtn && playControlBtn.offsetParent !== null) {
playControlBtn.click();
} else {
video.play().catch(() => {});
}
}
}
const childFrames = findDeepestIframes(subDoc, depth + 1);
if (childFrames.length > 0) {
deepestFrames.push(...childFrames);
} else {
const frameType = classifyIframe(src, subDoc);
if (frameType !== IFRAME_TYPES.UNKNOWN) {
deepestFrames.push({
iframe,
doc: subDoc,
src: src,
type: frameType,
accessible: true,
depth: depth
});
}
}
} else {
const frameType = classifyIframe(src, null);
if (frameType !== IFRAME_TYPES.UNKNOWN) {
deepestFrames.push({
iframe,
doc: null,
src: src,
type: frameType,
accessible: false,
depth: depth
});
}
}
} catch (e) {
const frameType = classifyIframe(src, null);
if (frameType !== IFRAME_TYPES.UNKNOWN) {
deepestFrames.push({
iframe,
doc: null,
src: src,
type: frameType,
accessible: false,
depth: depth,
error: e.message
});
}
}
}
return deepestFrames;
}
function classifyIframe(src, doc) {
if (src.startsWith('javascript:void') ||
src.includes('UE.instants') ||
src.includes('ueditorInstant') ||
(doc && doc.body && doc.body.className === 'view')) {
return IFRAME_TYPES.UNKNOWN;
}
if (src.includes('mooc1.chaoxing.com/ananas/modules/video') ||
src.includes('ans-attach-online') ||
src.includes('insertvideo')) {
return IFRAME_TYPES.VIDEO;
}
if (src.includes('mooc1.chaoxing.com/mooc-ans/api/work') ||
src.includes('/mooc-ans/work/doHomeWorkNew/') ||
src.includes('/ananas/modules/work/')) {
return IFRAME_TYPES.CHAPTER_TEST;
}
if (src.includes('pan-yz.chaoxing.com/screen/file') ||
src.includes('/screen/file')) {
return IFRAME_TYPES.DOCUMENT;
}
if (src.includes('mooc1.chaoxing.com/mooc-ans/bbscircle/chapter') ||
src.includes('/bbscircle/chapter') ||
src.includes('insertbbs/index.html') ||
src.includes('/modules/insertbbs/') ||
src.includes('discussion') ||
src.includes('comment')) {
return IFRAME_TYPES.DISCUSSION;
}
if (doc) {
try {
if (doc.querySelector('video') || doc.querySelector('.vjs-big-play-button')) {
console.log('📹 [分类] 通过DOM识别为视频iframe');
return IFRAME_TYPES.VIDEO;
}
if (doc.querySelector('.singleQuesId') ||
doc.querySelector('.CeYan h3') ||
doc.querySelector('.questionLi')) {
console.log('📝 [分类] 通过DOM识别为章节测验iframe');
return IFRAME_TYPES.CHAPTER_TEST;
}
if (doc.querySelector('.ppt-container') ||
doc.querySelector('.document-viewer') ||
doc.querySelector('canvas')) {
console.log('📄 [分类] 通过DOM识别为文档iframe');
return IFRAME_TYPES.DOCUMENT;
}
if (doc.querySelector('.bbs-content') ||
doc.querySelector('.discussion-area')) {
console.log('💬 [分类] 通过DOM识别为讨论iframe');
return IFRAME_TYPES.DISCUSSION;
}
} catch (e) {
console.log(`⚠️ [分类] DOM检查时出错: ${e.message}`);
}
}
console.log('❓ [分类] 未知类型iframe');
return IFRAME_TYPES.UNKNOWN;
}
// === 新的清晰的内容处理系统 ===
// === 基于原版.js优化的内容处理系统 ===
async function processIframeContent() {
// 检查是否有任务正在进行
if (GLOBAL_STATE.isAnswering || GLOBAL_STATE.isChapterTesting) {
console.log('⏸️ [iframe处理] 已有任务在进行中,跳过');
return { processed: false, reason: 'busy' };
}
// 查找最深层iframe
const deepestFrames = findDeepestIframes();
if (deepestFrames.length === 0) {
console.log('ℹ️ [iframe处理] 未找到任何iframe,尝试直接进入下一节');
const nextSectionResult = goToNextSection();
if (nextSectionResult) {
console.log('✅ [iframe处理] 已点击下一节按钮');
return { processed: true, reason: 'no_frames_next_section', action: 'next_section' };
} else {
console.log('❌ [iframe处理] 未找到下一节按钮');
return { processed: false, reason: 'no_frames_no_next' };
}
}
console.log(`🔍 找到 ${deepestFrames.length} 个最深层iframe`);
deepestFrames.forEach(frame => {
console.log(`📋 深度${frame.depth}: ${frame.type} - ${frame.src}`);
});
// 按页面顺序逐个处理iframe内容
console.log('📋 [顺序处理] 按页面顺序逐个处理iframe内容');
for (let i = 0; i < deepestFrames.length; i++) {
const frame = deepestFrames[i];
console.log(`📋 [顺序处理] 处理第${i + 1}个iframe: ${frame.type}`);
switch (frame.type) {
case IFRAME_TYPES.CHAPTER_TEST:
const testResult = await handleChapterTest([frame]);
if (testResult) {
console.log(`✅ [顺序处理] 第${i + 1}个iframe(章节测验)处理完成,本轮结束`);
return { processed: true, type: 'chapter_test' };
}
break;
case IFRAME_TYPES.VIDEO:
const videoResult = handleVideoFrames([frame]);
if (videoResult) {
console.log(`✅ [顺序处理] 第${i + 1}个iframe(视频)处理完成,本轮结束`);
return { processed: true, type: 'video' };
}
break;
case IFRAME_TYPES.DOCUMENT:
const docResult = handleDocumentFrames([frame]);
if (docResult) {
console.log(`✅ [顺序处理] 第${i + 1}个iframe(文档)处理完成,本轮结束`);
return { processed: true, type: 'document' };
}
break;
case IFRAME_TYPES.DISCUSSION:
// 讨论区域直接标记为完成
const isCommentPage = frame.src.includes('insertbbs/index.html') ||
frame.src.includes('/modules/insertbbs/') ||
frame.src.includes('bbscircle/chapter') ||
frame.src.includes('discussion') ||
frame.src.includes('comment');
if (isCommentPage) {
console.log(`💬 [顺序处理] 第${i + 1}个iframe确认为评论页面,标记为完成`);
} else {
console.log(`💬 [顺序处理] 第${i + 1}个iframe为普通讨论区域,标记为完成`);
}
console.log(`✅ [顺序处理] 第${i + 1}个iframe(讨论)处理完成,本轮结束`);
return { processed: true, type: 'discussion' };
default:
console.log(`❓ [顺序处理] 第${i + 1}个iframe类型未知(${frame.type}),跳过`);
continue;
}
}
// 无需处理的内容
console.log('ℹ️ [顺序处理] 无需处理的内容');
return { processed: false, type: 'none' };
}
// === 基于多视频播放逻辑.js的顺序播放系统 ===
// 视频播放状态管理
const VIDEO_CONTROLLER = {
isPlaying: false, // 当前是否有视频在播放
videoQueue: [], // 视频队列
currentVideoInfo: null, // 当前播放的视频信息
lastScanTime: 0 // 上次扫描时间
};
// 查找所有iframe中的视频(递归扫描)
function findAllVideos() {
console.log(`📹 [顺序播放] 开始扫描所有iframe中的视频`);
VIDEO_CONTROLLER.videoQueue = [];
function scanWindow(win, depth = 0, path = 'main') {
try {
// 先快速检查是否有视频元素,没有就直接返回
const videos = win.document.querySelectorAll('video');
const iframes = win.document.querySelectorAll('iframe');
if (videos.length === 0 && iframes.length === 0) {
console.log(`📹 [扫描] ${path} 没有视频和iframe,跳过`);
return;
}
// 应用防切屏设置(只在有视频或iframe时应用)
if (videos.length > 0 || iframes.length > 0) {
injectHooksToDocument(win.document, `video-scan-${path}`);
}
// 处理当前窗口中的视频
if (videos.length > 0) {
console.log(`📹 [扫描] ${path} 发现 ${videos.length} 个视频元素`);
videos.forEach((video, index) => {
const videoInfo = {
video: video,
window: win,
depth: depth,
path: `${path}-video-${index}`,
iframe: depth > 0 ? win.frameElement : null
};
// 检查视频是否在视频容器中(任务点视频)
const container = video.closest('.ans-attach-ct.videoContainer') ||
video.closest('.ans-attach-ct') ||
video.closest('[class*="video"]');
if (container) {
videoInfo.isTaskVideo = true;
videoInfo.container = container;
// 检查是否已完成
const completedIcon = container.querySelector('.ans-job-icon.ans-job-finished');
videoInfo.isCompleted = !!completedIcon;
}
VIDEO_CONTROLLER.videoQueue.push(videoInfo);
console.log(`📹 [扫描] 发现视频: ${videoInfo.path} (深度${depth}) ${videoInfo.isCompleted ? '✅已完成' : '⏳未完成'}`);
});
}
// 递归扫描iframe(只在有iframe时进行)
if (iframes.length > 0) {
console.log(`📹 [扫描] ${path} 发现 ${iframes.length} 个iframe,开始递归扫描`);
iframes.forEach((iframe, index) => {
try {
const childWin = iframe.contentWindow;
if (childWin && childWin.document) {
scanWindow(childWin, depth + 1, `${path}-iframe-${index}`);
}
} catch (e) {
console.log(`📹 [扫描] 无法访问iframe ${path}-iframe-${index},可能是跨域`);
}
});
}
} catch (error) {
console.warn(`📹 [扫描] 扫描窗口失败 ${path}:`, error.message);
}
}
// 开始扫描主窗口
scanWindow(window);
// 如果没有找到任何视频,直接返回
if (VIDEO_CONTROLLER.videoQueue.length === 0) {
console.log(`📹 [扫描完成] 未发现任何视频元素`);
return false;
}
// 过滤掉已完成的视频,按任务点视频优先排序
const uncompletedVideos = VIDEO_CONTROLLER.videoQueue.filter(info => !info.isCompleted);
const taskVideos = uncompletedVideos.filter(info => info.isTaskVideo);
const otherVideos = uncompletedVideos.filter(info => !info.isTaskVideo);
VIDEO_CONTROLLER.videoQueue = [...taskVideos, ...otherVideos];
console.log(`📹 [扫描完成] 总计发现 ${VIDEO_CONTROLLER.videoQueue.length} 个未完成视频 (任务点视频: ${taskVideos.length}, 其他视频: ${otherVideos.length})`);
return VIDEO_CONTROLLER.videoQueue.length > 0;
}
// 播放队列中的下一个视频
function playNextVideo() {
if (VIDEO_CONTROLLER.isPlaying) {
console.log(`📹 [顺序播放] 当前有视频正在播放,等待完成`);
return false;
}
if (VIDEO_CONTROLLER.videoQueue.length === 0) {
console.log(`📹 [顺序播放] 视频队列为空,重新扫描`);
findAllVideos();
if (VIDEO_CONTROLLER.videoQueue.length === 0) {
console.log(`📹 [顺序播放] 没有找到需要播放的视频`);
return false;
}
}
const videoInfo = VIDEO_CONTROLLER.videoQueue.shift();
const { video, path } = videoInfo;
if (!video || video.ended) {
console.log(`📹 [顺序播放] 视频 ${path} 无效或已结束,跳过`);
return playNextVideo(); // 递归播放下一个
}
try {
VIDEO_CONTROLLER.isPlaying = true;
VIDEO_CONTROLLER.currentVideoInfo = videoInfo;
console.log(`▶️ [顺序播放] 开始播放视频: ${path}`);
// 设置视频属性
video.volume = 0.01; // 极低音量
video.autoplay = true;
// 应用倍速设置
applySavedSpeedToVideo(video);
// 添加结束事件监听器(避免重复添加)
if (!video._sequentialPlayListener) {
video.addEventListener('ended', () => {
console.log(`✅ [顺序播放] 视频 ${path} 播放完成`);
VIDEO_CONTROLLER.isPlaying = false;
VIDEO_CONTROLLER.currentVideoInfo = null;
// 延迟播放下一个视频
setTimeout(() => {
playNextVideo();
}, 1000);
});
video.addEventListener('error', () => {
console.log(`❌ [顺序播放] 视频 ${path} 播放出错`);
VIDEO_CONTROLLER.isPlaying = false;
VIDEO_CONTROLLER.currentVideoInfo = null;
playNextVideo();
});
video._sequentialPlayListener = true;
}
// 尝试播放视频
const playPromise = video.play();
if (playPromise) {
playPromise.then(() => {
console.log(`✅ [顺序播放] 视频 ${path} 开始播放成功`);
applySavedSpeedToVideo(video);
showVideoSpeedPanel();
}).catch(error => {
console.warn(`❌ [顺序播放] 视频 ${path} 播放失败:`, error.message);
VIDEO_CONTROLLER.isPlaying = false;
VIDEO_CONTROLLER.currentVideoInfo = null;
playNextVideo();
});
}
return true;
} catch (error) {
console.warn(`❌ [顺序播放] 播放视频 ${path} 时发生错误:`, error.message);
VIDEO_CONTROLLER.isPlaying = false;
VIDEO_CONTROLLER.currentVideoInfo = null;
return playNextVideo();
}
}
// 主要的视频控制函数
function controlIframeVideo() {
const now = Date.now();
// 快速检查页面是否有视频相关元素
const hasVideoElements = document.querySelector('video') ||
document.querySelector('iframe') ||
document.querySelector('.ans-attach-ct.videoContainer');
if (!hasVideoElements) {
console.log(`📹 [视频控制] 页面没有视频相关元素,跳过扫描`);
return false;
}
// 每30秒重新扫描一次视频
if (now - VIDEO_CONTROLLER.lastScanTime > 30000) {
console.log(`📹 [视频控制] 定期重新扫描视频`);
const foundVideos = findAllVideos();
VIDEO_CONTROLLER.lastScanTime = now;
if (!foundVideos) {
console.log(`📹 [视频控制] 重新扫描未发现视频`);
return false;
}
}
// 如果没有视频在播放,尝试播放下一个
if (!VIDEO_CONTROLLER.isPlaying) {
return playNextVideo();
} else if (VIDEO_CONTROLLER.currentVideoInfo) {
// 检查当前播放的视频状态
const { video, path } = VIDEO_CONTROLLER.currentVideoInfo;
if (video.paused && !video.ended) {
console.log(`⏸️ [视频控制] 检测到视频 ${path} 被暂停,尝试恢复播放`);
video.play().catch(e => {
console.warn(`❌ [视频控制] 恢复播放失败:`, e.message);
});
}
}
return VIDEO_CONTROLLER.videoQueue.length > 0 || VIDEO_CONTROLLER.isPlaying;
}
// 删除复杂的视频容器控制函数,使用原版.js的简单方式
// 删除复杂的独立处理函数,在processIframeContent中直接调用原有的处理函数
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.contentWindow;
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('🚀 [章节测验] 开始答题流程');
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}`);
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}`);
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}`);
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}`);
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}`);
} catch (error) {
opt.click();
console.log(` 🎯 已选择多选项: ${label} (备用)`);
}
} else {
opt.click();
console.log(` 🎯 已选择多选项: ${label}`);
}
}
}
} 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}`);
break;
}
}
}
}
} catch (error) {
console.log(` ❌ 答题异常: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('✅ [章节测验] 答题完成,准备提交');
GLOBAL_STATE.isAnswering = false;
setTimeout(() => {
if (iframeWindow.btnBlueSubmit) {
iframeWindow.btnBlueSubmit();
console.log('✅ [自动提交] 测验提交成功');
setTimeout(() => {
monitorSubmitDialog();
}, 500);
}
}, 2000);
return true;
}
return false;
}
function handleVideoFrames(specificFrames = null) {
console.log(`🎯 [统一视频控制] handleVideoFrames系统启动 - ${new Date().toLocaleTimeString()}`);
let videoContainers;
if (specificFrames && specificFrames.length > 0) {
videoContainers = [];
specificFrames.forEach(frame => {
if (frame.iframe && frame.type === IFRAME_TYPES.VIDEO) {
const container = frame.iframe.closest('.ans-attach-ct.videoContainer');
if (container) {
videoContainers.push(container);
}
}
});
console.log(`📹 [视频处理] 处理指定的 ${videoContainers.length} 个视频容器`);
} else {
const wrapContainer = document.querySelector('.wrap');
if (wrapContainer) {
videoContainers = wrapContainer.querySelectorAll('.ans-attach-ct.videoContainer');
} else {
videoContainers = document.querySelectorAll('.ans-attach-ct.videoContainer');
}
console.log(`📹 [视频处理] 找到 ${videoContainers.length} 个视频容器,当前播放: ${GLOBAL_STATE.currentPlayingVideoIndex || '无'} (统一控制系统)`);
}
if (videoContainers.length === 0) {
return false;
}
let processedAny = false;
let playingVideoFound = false;
const videoStates = [];
videoContainers.forEach((container, index) => {
const videoState = {
index: index + 1,
container: container,
isCompleted: false,
isPlaying: false,
needsPlay: false
};
try {
if (container.classList.contains('ans-job-finished')) {
videoState.isCompleted = true;
const videoIframe = container.querySelector('iframe.ans-insertvideo-online');
if (videoIframe) {
try {
const videoDoc = videoIframe.contentDocument;
if (videoDoc) {
const video = videoDoc.querySelector('video');
if (video) {
const alreadyProcessed = video.getAttribute('data-task-completed') === 'true';
if (!alreadyProcessed) {
video.setAttribute('data-task-completed', 'true');
video.taskCompleted = true;
if (!video.paused) {
video.pause();
console.log(`⏸️ [视频${index + 1}] 已暂停播放 (任务点已完成)`);
}
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
}
}
}
} catch (e) {
}
}
} else {
const taskIcon = container.querySelector('.ans-job-icon');
if (taskIcon) {
const ariaLabel = taskIcon.getAttribute('aria-label');
if (ariaLabel && ariaLabel.includes('已完成')) {
videoState.isCompleted = true;
const videoIframe = container.querySelector('iframe.ans-insertvideo-online');
if (videoIframe) {
try {
const videoDoc = videoIframe.contentDocument;
if (videoDoc) {
const video = videoDoc.querySelector('video');
if (video) {
const alreadyProcessed = video.getAttribute('data-task-completed') === 'true';
if (!alreadyProcessed) {
video.setAttribute('data-task-completed', 'true');
video.taskCompleted = true;
if (!video.paused) {
video.pause();
console.log(`⏸️ [视频${index + 1}] 已暂停播放 (任务点已完成)`);
}
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
}
}
}
} catch (e) {
}
}
} else {
const videoIframe = container.querySelector('iframe.ans-insertvideo-online');
if (videoIframe) {
try {
const videoDoc = videoIframe.contentDocument;
if (videoDoc) {
const video = videoDoc.querySelector('video');
if (video) {
if (!video.paused && !video.ended) {
videoState.isPlaying = true;
playingVideoFound = true;
console.log(`▶️ [视频${index + 1}] 正在播放中`);
} else if (video.paused && !video.ended && video.readyState >= 2) {
videoState.needsPlay = true;
console.log(`⏸️ [视频${index + 1}] 已暂停,需要播放`);
} else if (video.readyState < 2) {
console.log(`⏳ [视频${index + 1}] 正在加载中`);
}
}
}
} catch (e) {
console.log(`❌ [视频${index + 1}] 跨域无法访问`);
}
}
}
} else {
console.log(`❓ [视频${index + 1}] 未找到任务点图标,标记为需要跳过`);
videoState.noTaskIcon = true;
}
}
} catch (error) {
console.log(`❌ [视频${index + 1}] 检查失败:`, error.message);
}
videoStates.push(videoState);
});
const videosWithoutTaskIcon = videoStates.filter(state => state.noTaskIcon);
if (videosWithoutTaskIcon.length > 0) {
console.log(`❓ [视频处理] 没有任务点图标,标记为完成,让主循环控制跳转`);
return true;
}
if (playingVideoFound) {
return true;
}
// 实现顺序播放:只播放第一个未完成的视频
const firstUncompletedVideo = videoStates.find(state => state.needsPlay);
if (firstUncompletedVideo) {
// 检查是否已有视频在播放
if (GLOBAL_STATE.currentPlayingVideoIndex !== null &&
GLOBAL_STATE.currentPlayingVideoIndex !== firstUncompletedVideo.index) {
console.log(`⏸️ [视频控制] 视频${GLOBAL_STATE.currentPlayingVideoIndex}正在播放,等待完成`);
return true; // 等待当前视频完成
}
// 暂停所有其他正在播放的视频
videoStates.forEach(state => {
if (state.isPlaying && state.index !== firstUncompletedVideo.index) {
pauseVideo(state.container, state.index);
}
});
console.log(`▶️ [视频处理] 开始播放视频${firstUncompletedVideo.index} (顺序播放)`);
const success = playVideo(firstUncompletedVideo.container, firstUncompletedVideo.index);
if (success) {
GLOBAL_STATE.currentPlayingVideoIndex = firstUncompletedVideo.index;
processedAny = true;
}
}
// 显示视频队列状态
if (videoStates.length > 0) {
const completed = videoStates.filter(s => s.isCompleted).length;
const playing = videoStates.filter(s => s.isPlaying).length;
const needsPlay = videoStates.filter(s => s.needsPlay).length;
console.log(`📊 [视频状态] 总计:${videoStates.length} | 已完成:${completed} | 播放中:${playing} | 待播放:${needsPlay}`);
// 删除调试信息调用,保持简洁
}
return processedAny;
}
function playVideo(container, videoIndex) {
try {
const videoIframe = container.querySelector('iframe.ans-insertvideo-online');
if (!videoIframe) {
console.log(`❌ [视频${videoIndex}] 未找到视频iframe`);
return false;
}
showVideoSpeedPanel();
let videoDoc = null;
try {
videoDoc = videoIframe.contentDocument;
} catch (e) {
console.log(`❌ [视频${videoIndex}] 无法访问iframe内容,跨域限制`);
return false;
}
if (!videoDoc) {
console.log(`❌ [视频${videoIndex}] iframe文档为空`);
return false;
}
const video = videoDoc.querySelector('video');
const playButton = videoDoc.querySelector('.vjs-big-play-button');
if (!video && !playButton) {
console.log(`❌ [视频${videoIndex}] 未找到视频元素`);
return false;
}
if (video) {
// 不设置 muted = true,因为防切屏设置会阻止静音
// video.muted = true; // 注释掉,与防切屏设置保持一致
video.volume = 0.01; // 设置极低音量
video.autoplay = true;
// 添加视频结束事件监听,自动播放下一个视频
if (!video.hasSequentialListener) {
video.addEventListener('ended', () => {
console.log(`✅ [视频${videoIndex}] 播放完成,准备播放下一个视频`);
// 清除当前播放状态
if (GLOBAL_STATE.currentPlayingVideoIndex === videoIndex) {
GLOBAL_STATE.currentPlayingVideoIndex = null;
}
setTimeout(() => {
// 触发下一轮视频检查
handleVideoFrames();
}, 1000);
});
video.hasSequentialListener = true;
}
if (video.paused && !video.ended) {
const playButtons = [
videoDoc.querySelector('.vjs-big-play-button'),
videoDoc.querySelector('.vjs-play-control'),
playButton
].filter(btn => btn && btn.offsetParent !== null);
if (playButtons.length > 0) {
playButtons[0].click();
console.log(`✅ [视频${videoIndex}] 通过点击播放按钮开始播放(非静音,1%音量)`);
console.log(`🔍 [视频${videoIndex}] 使用的按钮选择器:`, playButtons[0].className);
// 确保其他视频被暂停
pauseAllOtherVideos(videoIndex);
setTimeout(() => {
applySavedSpeedToVideo(video);
}, 500);
} else {
video.play().then(() => {
console.log(`✅ [视频${videoIndex}] 直接播放成功(非静音,1%音量)`);
// 确保其他视频被暂停
pauseAllOtherVideos(videoIndex);
applySavedSpeedToVideo(video);
}).catch((error) => {
console.log(`❌ [视频${videoIndex}] 自动播放被阻止,需要用户交互: ${error.message}`);
console.log(`💡 [视频${videoIndex}] 建议:在控制台执行 document.querySelector(".vjs-big-play-button").click()`);
});
}
return true;
}
}
return false;
} catch (error) {
console.log(`❌ [视频${videoIndex}] 播放失败:`, error.message);
return false;
}
}
// 暂停指定视频
function pauseVideo(container, videoIndex) {
try {
const videoIframe = container.querySelector('iframe.ans-insertvideo-online');
if (!videoIframe) {
return false;
}
let videoDoc = null;
try {
videoDoc = videoIframe.contentDocument;
} catch (e) {
return false;
}
if (!videoDoc) {
return false;
}
const video = videoDoc.querySelector('video');
if (video && !video.paused) {
video.pause();
console.log(`⏸️ [视频${videoIndex}] 已暂停播放 (顺序控制)`);
// 清除全局播放状态
if (GLOBAL_STATE.currentPlayingVideoIndex === videoIndex) {
GLOBAL_STATE.currentPlayingVideoIndex = null;
}
return true;
}
return false;
} catch (error) {
console.log(`❌ [视频${videoIndex}] 暂停失败:`, error.message);
return false;
}
}
// 删除旧的复杂视频控制函数
// 删除复杂的视频暂停控制函数
// === 基于多视频播放逻辑.js的顺序播放系统 ===
/*
优化总结:
1. 🎯 核心设计理念:
- 严格的视频顺序播放:一次只播放一个视频
- 使用队列管理:videoQueue 确保播放顺序
- 状态控制:isPlaying 标志防止同时播放
2. 📹 视频处理流程:
- findAllVideos(): 递归扫描所有iframe中的视频
- playNextVideo(): 从队列中取出下一个视频播放
- 视频结束后自动播放下一个
- 任务点视频优先播放
3. 🛡️ 防切屏增强:
- 主页面防切屏设置
- 扫描时自动应用iframe防切屏设置
- 事件监听器拦截
4. 🔄 智能管理:
- 定期重新扫描视频(30秒)
- 自动跳过已完成的视频
- 暂停恢复机制
- 错误处理和重试
5. 📊 解决的问题:
- ✅ 多视频同时播放冲突
- ✅ 视频播放顺序混乱
- ✅ 任务点视频优先级
- ✅ 播放状态管理
*/
function handleDocumentFrames(docFrames) {
if (!docFrames || docFrames.length === 0) {
return false;
}
console.log(`📄 [文档处理] 开始处理 ${docFrames.length} 个文档iframe`);
let processedAny = false;
docFrames.forEach((frame, index) => {
if (!frame.accessible || !frame.doc) {
console.log(`❌ [文档${index + 1}] iframe不可访问,跳过`);
return;
}
try {
const doc = frame.doc;
const iframeWindow = frame.iframe.contentWindow;
const isPPT = frame.src.includes('pan-yz.chaoxing.com/screen/file') ||
frame.src.includes('/screen/file') ||
doc.querySelector('.ppt-container') ||
doc.querySelector('.document-viewer') ||
doc.querySelector('canvas');
if (isPPT) {
console.log(`📄 [PPT${index + 1}] 开始自动滚动`);
startPPTAutoScroll(doc, iframeWindow, index + 1);
processedAny = true;
} else {
processedAny = true;
}
} catch (error) {
console.log(`❌ [文档${index + 1}] 处理失败:`, error.message);
}
});
return processedAny;
}
function startPPTAutoScroll(doc, iframeWindow, docIndex) {
if (!doc || !iframeWindow) {
return;
}
const fileBox = doc.querySelector('.fileBox');
const pageItems = doc.querySelectorAll('.fileBox ul li');
if (!fileBox || pageItems.length === 0) {
console.log(`❌ PPT${docIndex} 未找到PPT页面结构`);
return;
}
console.log(`📄 PPT${docIndex} 找到${pageItems.length}页内容`);
const autoScrollToBottom = () => {
try {
const docHeight = Math.max(
doc.body.scrollHeight,
doc.body.offsetHeight,
doc.documentElement.clientHeight,
doc.documentElement.scrollHeight,
doc.documentElement.offsetHeight
);
console.log(`📏 PPT${docIndex} 文档高度: ${docHeight}px`);
iframeWindow.scrollTo({
top: docHeight,
behavior: 'smooth'
});
setTimeout(() => {
doc.documentElement.scrollTop = docHeight;
doc.body.scrollTop = docHeight;
const scrollEvent = new iframeWindow.Event('scroll', { bubbles: true });
iframeWindow.dispatchEvent(scrollEvent);
console.log(`📜 PPT${docIndex} 滚动到底部完成`);
}, 1000);
setTimeout(() => {
const currentScrollTop = Math.max(
doc.documentElement.scrollTop || 0,
doc.body.scrollTop || 0
);
const windowHeight = iframeWindow.innerHeight || doc.documentElement.clientHeight;
const isAtBottom = (currentScrollTop + windowHeight) >= (docHeight - 10);
console.log(`✅ PPT${docIndex} 滚动检查: ${isAtBottom ? '已到底部' : '未到底部'}`);
if (isAtBottom) {
try {
if (iframeWindow.parent && iframeWindow.parent !== iframeWindow) {
iframeWindow.parent.postMessage({ isFinished: 1 }, '*');
console.log(`📤 PPT${docIndex} 发送完成消息`);
}
} catch (e) {
console.log(`⚠️ PPT${docIndex} 发送完成消息失败:`, e.message);
}
console.log(`🎉 PPT${docIndex} 浏览完成,等待主循环检测任务完成状态`);
}
}, 2000);
} catch (error) {
console.log(`❌ PPT${docIndex} 滚动失败:`, error.message);
}
};
setTimeout(() => {
autoScrollToBottom();
}, 500);
}
function checkTaskCompletion() {
const taskIcons = document.querySelectorAll('.ans-job-icon');
let totalTasks = 0;
let completedTasks = 0;
taskIcons.forEach((icon) => {
totalTasks++;
const ariaLabel = icon.getAttribute('aria-label') || '';
if (ariaLabel.includes('已完成')) {
completedTasks++;
}
});
if (totalTasks === 0) {
const iframes = document.querySelectorAll('iframe');
const hasContent = iframes.length > 0;
if (!hasContent) {
console.log('ℹ️ [任务检查] 页面无任务点图标且无iframe内容,认为已完成');
return {
total: 0,
completed: 0,
isAllCompleted: true,
details: []
};
} else {
console.log('ℹ️ [任务检查] 页面无任务点图标但有iframe内容,检查处理状态');
if (!GLOBAL_STATE.isAnswering && !GLOBAL_STATE.isChapterTesting) {
console.log('ℹ️ [任务检查] 非答题状态,可能内容已完成,认为页面已完成');
return {
total: 1,
completed: 1,
isAllCompleted: true,
details: []
};
}
}
}
const isAllCompleted = totalTasks > 0 && completedTasks === totalTasks;
return {
total: totalTasks,
completed: completedTasks,
isAllCompleted: isAllCompleted,
details: []
};
}
function goToNextSection() {
console.log('🔍 [下一节] 开始查找下一节按钮...');
const isInIframe = window !== window.top;
const targetDoc = isInIframe ? window.top.document : document;
try {
const nextButton = targetDoc.querySelector('#prevNextFocusNext');
console.log('🔍 [下一节] 在', isInIframe ? '主页面' : '当前页面', '查找按钮:', !!nextButton);
if (nextButton) {
console.log('🔍 [下一节] 找到按钮,详情:', {
id: nextButton.id,
className: nextButton.className,
display: nextButton.style.display,
visible: nextButton.offsetParent !== null,
onclick: nextButton.getAttribute('onclick'),
textContent: nextButton.textContent?.trim()
});
const isVisible = nextButton.offsetParent !== null;
const isDisplayed = nextButton.style.display !== 'none';
if (isVisible && isDisplayed) {
console.log('✅ [下一节] 找到可用的下一节按钮,准备点击');
setTimeout(() => {
try {
nextButton.click();
console.log('✅ [下一节] 已点击下一节按钮');
} catch (error) {
console.log('❌ [下一节] 点击失败:', error);
const onclickAttr = nextButton.getAttribute('onclick');
if (onclickAttr) {
console.log('🔄 [下一节] 尝试执行onclick事件:', onclickAttr);
try {
const targetWindow = isInIframe ? window.top : window;
targetWindow.eval(onclickAttr);
console.log('✅ [下一节] onclick事件执行成功');
} catch (evalError) {
console.log('❌ [下一节] onclick事件执行失败:', evalError);
}
}
}
}, 2000);
return true;
} else {
console.log('❌ [下一节] 按钮不可用:', {
visible: isVisible,
displayed: isDisplayed
});
}
} else {
console.log('❌ [下一节] 未找到 #prevNextFocusNext 按钮');
}
} catch (error) {
console.log('❌ [下一节] 访问主页面失败(跨域限制):', error);
if (isInIframe) {
console.log('🔄 [下一节] 尝试通过postMessage通信');
window.top.postMessage({
type: 'CLICK_NEXT_SECTION',
selector: '#prevNextFocusNext'
}, '*');
}
}
return false;
}
function isStudyPage() {
const url = window.location.href;
return url.includes('/knowledge/cards') &&
url.includes('mooc1.chaoxing.com') &&
url.includes('clazzid') &&
url.includes('courseid');
}
if (isStudyPage()) {
console.log(`🎯 [系统启动] 基于多视频播放逻辑.js的顺序播放系统`);
console.log(`📹 [视频系统] controlIframeVideo() - 严格的视频顺序播放,一次只播放一个`);
console.log(`📋 [内容系统] processIframeContent() - 顺序处理测验和文档`);
console.log(`🛡️ [防切屏] 主页面防切屏设置已应用: document.hidden=${document.hidden}, document.visibilityState=${document.visibilityState}`);
console.log(`✅ [顺序播放] 多视频顺序播放系统已启用,避免同时播放冲突`);
console.log(`⚡ [性能优化] 添加快速检查机制,无视频元素时跳过扫描`);
// 初始化视频扫描
setTimeout(() => {
console.log(`🔍 [初始化] 开始首次视频扫描`);
// 快速检查是否有视频相关元素
const hasVideoElements = document.querySelector('video') ||
document.querySelector('iframe') ||
document.querySelector('.ans-attach-ct.videoContainer');
if (!hasVideoElements) {
console.log(`🔍 [初始化] 页面没有视频相关元素,跳过初始化扫描`);
return;
}
const foundVideos = findAllVideos();
if (foundVideos && VIDEO_CONTROLLER.videoQueue.length > 0) {
console.log(`🎬 [初始化] 发现 ${VIDEO_CONTROLLER.videoQueue.length} 个视频,开始顺序播放`);
playNextVideo();
} else {
console.log(`🔍 [初始化] 未发现需要播放的视频`);
}
}, 3000);
let nextSectionTriggered = false;
let lastTaskStatus = null; // 缓存上次的任务状态
let videoControlCount = 0; // 视频控制计数器
window.mainIntervalId = setInterval(async () => {
// 不再调用controlIframeVideo,改为使用handleVideoFrames系统
// if (videoControlCount % 3 === 0) {
// console.log(`🔄 [主循环] 执行视频控制 (第${videoControlCount + 1}次循环)`);
// controlIframeVideo();
// }
videoControlCount++;
const taskStatus = checkTaskCompletion();
if (taskStatus.isAllCompleted && !nextSectionTriggered) {
console.log('✅ [自动跳转] 所有任务已完成,跳转下一节');
nextSectionTriggered = true;
goToNextSection();
return;
}
// 只在任务状态变化或首次运行时处理iframe内容
const taskStatusChanged = !lastTaskStatus ||
lastTaskStatus.isAllCompleted !== taskStatus.isAllCompleted ||
lastTaskStatus.completedCount !== taskStatus.completedCount;
if (!taskStatus.isAllCompleted && !GLOBAL_STATE.isAnswering && !GLOBAL_STATE.isChapterTesting) {
// 学习原版.js:每次循环都执行视频控制
controlIframeVideo();
// 只在状态变化时或每30秒处理一次iframe内容,避免频繁处理
if (taskStatusChanged || videoControlCount % 6 === 0) {
const iframeResult = await processIframeContent();
if (iframeResult.reason === 'no_frames_next_section') {
console.log('🔄 [自动跳转] 无内容需要处理,跳转下一节');
nextSectionTriggered = true;
return;
}
if (iframeResult.processed) {
console.log(`📝 [内容处理] 已处理: ${iframeResult.type},等待所有任务完成`);
}
}
}
// 更新任务状态缓存
lastTaskStatus = { ...taskStatus };
if (!taskStatus.isAllCompleted) {
nextSectionTriggered = false;
}
}, 10000); // 调整为10秒间隔,进一步减少频繁调用
}
if (window === window.top) {
window.addEventListener('message', function (event) {
if (event.data && event.data.type === 'CLICK_NEXT_SECTION') {
console.log('📨 [postMessage] 收到iframe的下一节请求');
const nextButton = document.querySelector(event.data.selector);
if (nextButton && nextButton.offsetParent !== null) {
setTimeout(() => {
nextButton.click();
console.log('✅ [postMessage] 已点击下一节按钮');
}, 1000);
}
}
});
}
})();