// ==UserScript==
// @name 学习通自动助手
// @version 1.1.0
// @license Apache
// @description 学习通视频/弹题/作业/考试自动答题;支持 DeepSeek / 豆包 / ChatGPT 三种接口,可随时切换
// @match *://*.chaoxing.com/*
// @run-at document-end
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_info
// @grant GM_getResourceText
// @grant GM_openInTab
// @icon http://pan-yz.chaoxing.com/favicon.ico
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://cdn.bootcdn.net/ajax/libs/limonte-sweetalert2/11.1.0/sweetalert2.all.min.js
// @require https://pkpkq.n1t.cn/font-decrypt/TyprMd5.js
// @resource Table https://pkpkq.n1t.cn/font-decrypt/Table.json
// @connect api.deepseek.com
// @connect ark.cn-beijing.volces.com
// @connect api.openai.com
// @tag AI
// ==/UserScript==
/********************************* 常量 ****************************************/
var VIDEO_RATE_MIN = 1;
var VIDEO_RATE_MAX = 8;
var VIDEO_RATE_STEP = 0.25;
function clampVideoRate(v) {
v = parseFloat(v);
if (isNaN(v)) v = VIDEO_RATE_MIN;
v = Math.round(v / VIDEO_RATE_STEP) * VIDEO_RATE_STEP;
v = Math.round(v * 100) / 100;
return Math.min(VIDEO_RATE_MAX, Math.max(VIDEO_RATE_MIN, v));
}
/********************************* 跳转闸门 ****************************************/
var __cxPendingSubmit = false;
var __cxJumpLock = false;
function markPendingSubmit(v) { __cxPendingSubmit = !!v; }
function canJumpNow() { if (__cxJumpLock) return false; if (__cxPendingSubmit) return false; return true; }
/********************************* 全局停止 & 失败计数 ****************************************/
var __cxScriptStopped = false;
var __cxChapterFailCount = 0;
var __cxChapterFailKey = '';
var __cxHardTimeoutTimers = [];
var __cxQuizAbortFlag = false;
function resetChapterFailCount() {
__cxChapterFailCount = 0;
__cxChapterFailKey = (typeof defaults !== 'undefined' && defaults && defaults.knowledgeid) ? String(defaults.knowledgeid) : (location.pathname + location.search);
}
function bumpChapterFail(reason) {
var key = (typeof defaults !== 'undefined' && defaults && defaults.knowledgeid) ? String(defaults.knowledgeid) : (location.pathname + location.search);
if (key !== __cxChapterFailKey) { __cxChapterFailKey = key; __cxChapterFailCount = 0; }
__cxChapterFailCount++;
var limit = parseInt(CONFIG.failLimit) || 3;
var action = CONFIG.failAction || 'stop';
log('⚠️ 本章失败计数: ' + __cxChapterFailCount + '/' + limit + '(' + reason + ')', 'orange');
if (__cxChapterFailCount >= limit) {
if (action === 'jump') { abortQuizAndNextChapter('本章失败达到' + limit + '次,自动跳章'); }
else { stopAllScript('本章失败达到' + limit + '次,等待人工处理'); }
return true;
}
return false;
}
function stopAllScript(reason) {
if (__cxScriptStopped) return;
__cxScriptStopped = true;
log('🛑 脚本已停止执行:' + reason, 'red');
__cxJumpLock = false;
markPendingSubmit(false);
isProcessing = false;
isJumping = false;
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
stopQuizForAllMedia();
stopQuizObserverGuard();
for (var i = 0; i < __cxHardTimeoutTimers.length; i++) { clearTimeout(__cxHardTimeoutTimers[i]); }
__cxHardTimeoutTimers = [];
if (typeof CONFIG !== 'undefined') { CONFIG.handleQuiz = 0; CONFIG.autoSubmit = 0; CONFIG.forceSubmit = 0; }
}
function abortQuizAndNextChapter(reason) {
if (__cxQuizAbortFlag) return;
__cxQuizAbortFlag = true;
log('🚨 ' + reason + ',跳过当前测验/作业并跳转下一章', 'red');
__cxJumpLock = false;
markPendingSubmit(false);
isJumping = false;
isProcessing = false;
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
stopQuizForAllMedia();
stopQuizObserverGuard();
if (taskList && taskList.length > 0) { taskList.splice(0, taskList.length); }
if (domList && domList.length > 0) { domList.splice(0, domList.length); }
setTimeout(function() { __cxQuizAbortFlag = false; goToNext(); }, 1500);
}
/********************************* 播放器解锁层 ****************************************/
var _cxForceRate = null;
var _cxRateValue = 1;
function initPlayerUnlock() {
var W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (W.__cxUnlockReady) return;
W.__cxRateValue = W.__cxRateValue || 1;
var _origSetter = null;
var _origGetter = null;
function hijackProto(win) {
try {
var proto = win.HTMLMediaElement && win.HTMLMediaElement.prototype;
if (!proto || proto.__cxHijacked) return;
var desc = Object.getOwnPropertyDescriptor(proto, 'playbackRate');
if (!desc || !desc.set || !desc.get) return;
_origSetter = desc.set;
_origGetter = desc.get;
Object.defineProperty(proto, 'playbackRate', {
configurable: true, enumerable: true,
get: function() { return _origGetter.call(this); },
set: function(v) {
v = parseFloat(v);
if (isNaN(v) || v <= 0) return;
var target = W.__cxRateValue || 1;
if (this.__cxShouldPlay) {
if (Math.abs(v - 1) < 0.01 && target > 1) { _origSetter.call(this, target); return; }
var legal = Math.round(v * 4) / 4;
if (Math.abs(legal - v) > 0.01) { _origSetter.call(this, target); return; }
}
_origSetter.call(this, v);
}
});
proto.__cxHijacked = true;
} catch(e) {}
}
function hijackAll() {
hijackProto(W);
try {
document.querySelectorAll('iframe').forEach(function(f) {
try { var cw = f.contentWindow; if (cw && cw !== window) hijackProto(cw); } catch(e) {}
});
} catch(e) {}
}
hijackAll();
setInterval(hijackAll, 2000);
W.__cxForceRate = function(media, rate) {
if (!media) return;
rate = parseFloat(rate) || 1;
W.__cxRateValue = rate;
try {
media.__cxShouldPlay = true;
if (_origSetter) _origSetter.call(media, rate);
else media.playbackRate = rate;
} catch(e) {}
};
_cxForceRate = W.__cxForceRate;
document.addEventListener('ratechange', function(e) {
var media = e.target;
if (!media || (media.tagName !== 'VIDEO' && media.tagName !== 'AUDIO')) return;
if (!media.__cxShouldPlay) return;
var target = W.__cxRateValue || 1;
if (Math.abs(media.playbackRate - target) > 0.01) { try { W.__cxForceRate(media, target); } catch(err) {} }
if (media.paused && !media.ended) {
setTimeout(function() {
if (media.paused && !media.ended && media.__cxShouldPlay) {
try { media.play(); } catch(err) {}
try { W.__cxForceRate(media, W.__cxRateValue); } catch(err) {}
}
}, 80);
}
}, true);
document.addEventListener('pause', function(e) {
var media = e.target;
if (!media || (media.tagName !== 'VIDEO' && media.tagName !== 'AUDIO')) return;
if (!media.__cxShouldPlay || media.ended) return;
setTimeout(function() {
if (media.paused && !media.ended && media.__cxShouldPlay) {
try { media.play(); } catch(err) {}
try { W.__cxForceRate(media, W.__cxRateValue); } catch(err) {}
}
}, 150);
}, true);
function rateHack() {
var done = false;
var hack = function() {
var videojs = W.videojs;
var Ext = W.Ext;
if (done || !videojs || !Ext) return;
done = true;
try {
var origin = videojs.getPlugin('seekBarControl');
var dragCount = 0;
videojs.registerPlugin('seekBarControl', videojs.extend(videojs.getPlugin('plugin'), {
constructor: function(ext, data) {
var sendLog = data.sendLog;
data.sendLog = function() {
var args = Array.prototype.slice.call(arguments);
if (args[1] === 'drag') { if (++dragCount > 100) { dragCount = 0; } return; }
return sendLog.apply(data, args);
};
origin.apply(origin.prototype, [ext, data]);
}
}));
} catch(e) {}
try {
Ext.define('ans.VideoJs', {
override: 'ans.VideoJs',
constructor: function(data) {
this.addEvents(['seekstart']);
this.mixins.observable.constructor.call(this, data);
var vjs = videojs(data.videojs, this.params2VideoOpt(data.params), function() {});
var el = Ext.fly(data.videojs);
el.on('contextmenu', function(e) { e.preventDefault(); });
el.on('keydown', function(e) { if ([32, 37, 39, 107].indexOf(e.keyCode) !== -1) e.preventDefault(); });
try { var videoEl = document.getElementById(data.videojs); if (videoEl) W.__cxForceRate(videoEl, W.__cxRateValue); } catch(e) {}
try {
setInterval(function() {
var vv = document.getElementById(data.videojs);
if (vv && vv.__cxShouldPlay && !vv.paused && !vv.ended) {
var want = W.__cxRateValue;
if (Math.abs(vv.playbackRate - want) > 0.01) { try { W.__cxForceRate(vv, want); } catch(e) {} }
}
}, 2000);
} catch(e) {}
}
});
} catch(e) {}
};
hack();
setTimeout(hack, 300);
setTimeout(hack, 1000);
setTimeout(hack, 2500);
setTimeout(hack, 5000);
try { window.addEventListener('load', hack); } catch(e) {}
}
rateHack();
try {
var obs = new MutationObserver(function(muts) {
for (var i = 0; i < muts.length; i++) {
var nodes = muts[i].addedNodes;
for (var j = 0; j < nodes.length; j++) {
var n = nodes[j];
if (n.nodeType !== 1) continue;
var videos = [];
if (n.tagName === 'VIDEO' || n.tagName === 'AUDIO') videos.push(n);
if (n.querySelectorAll) { var inner = n.querySelectorAll('video, audio'); for (var k = 0; k < inner.length; k++) videos.push(inner[k]); }
for (var v = 0; v < videos.length; v++) {
(function(el) { if (el.__cxShouldPlay) return; el.__cxShouldPlay = true; try { W.__cxForceRate(el, W.__cxRateValue); } catch(e) {} })(videos[v]);
}
}
}
});
obs.observe(document.documentElement, { childList: true, subtree: true });
} catch(e) {}
W.__cxUnlockReady = true;
}
/********************************* 反检测层 ****************************************/
(function() {
'use strict';
try { Object.defineProperty(navigator, 'webdriver', { get: function() { return undefined; }, configurable: true }); } catch(e) {}
try { if (!window.chrome) window.chrome = {}; if (!window.chrome.runtime) window.chrome.runtime = {}; } catch(e) {}
try {
Object.defineProperty(document, 'visibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(document, 'hidden', { get: function() { return false; }, configurable: true });
document.hasFocus = function() { return true; };
document.addEventListener('visibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
} catch(e) {}
try {
window.onblur = null; window.onfocus = null;
['blur', 'focus', 'pagehide', 'pageshow'].forEach(function(evt) { window.addEventListener(evt, function(e) { e.stopImmediatePropagation(); }, true); });
} catch(e) {}
var _OrigMO = window.MutationObserver;
if (_OrigMO) {
window.MutationObserver = function(callback) {
var wrappedCallback = function(mutations, observer) {
var delay = 50 + Math.random() * 150;
setTimeout(function() { callback(mutations, observer); }, delay);
};
return new _OrigMO(wrappedCallback);
};
window.MutationObserver.prototype = _OrigMO.prototype;
}
try {
var _origQuery = navigator.permissions && navigator.permissions.query;
if (_origQuery) {
navigator.permissions.query = function(desc) {
if (desc && desc.name === 'notifications') return Promise.resolve({ state: 'prompt', onchange: null });
return _origQuery.call(navigator.permissions, desc);
};
}
} catch(e) {}
})();
/********************************* 配置管理器 ****************************************/
var SettingsManager = {
_defaults: {
showBox: 1, showMaskImg: 1, taskOnly: 0,
handleVideo: 1, handleAudio: 1, videoRate: 1, videoMode: 'simulate', showProgressBar: 1,
reviewMode: 0, handleQuiz: 1, handleVideoQuiz: 1,
answerInterval: 5000, minInterval: 3, maxInterval: 6, useRandomInterval: 1,
autoSubmit: 0, forceSubmit: 0, decryptFont: 1,
autoNextExam: 1, examNextDelay: 1, insertAnswer: 1,
autoLogin: 0, loginPhone: '', loginPassword: '',
answerDelayEnabled: 1, answerDelayMin: 3, answerDelayMax: 10,
jumpDelayEnabled: 1, jumpDelayMin: 10, jumpDelayMax: 30,
redoAnswered: 0, failLimit: 3, failAction: 'stop'
},
get: function(key) {
var gv = GM_getValue('setting_' + key, null);
if (gv !== null && gv !== undefined) { this._defaults[key] = gv; return gv; }
var stored = localStorage.getItem('setting_' + key);
if (stored !== null) {
if (stored === 'true') return true;
if (stored === 'false') return false;
if (!isNaN(parseFloat(stored)) && isFinite(stored)) return parseFloat(stored);
return stored;
}
return this._defaults[key];
},
set: function(key, value) { localStorage.setItem('setting_' + key, value); GM_setValue('setting_' + key, value); this._defaults[key] = value; return value; },
load: function() {
for (var key in this._defaults) { var v = this.get(key); if (v !== undefined) this._defaults[key] = v; }
this._defaults.videoRate = clampVideoRate(this._defaults.videoRate);
return this._defaults;
},
saveVideoMode: function(mode) { this.set('videoMode', mode); GM_setValue('videoMode', mode); },
saveVideoRate: function(rate) { rate = clampVideoRate(rate); this.set('videoRate', rate); GM_setValue('videoRate', rate); },
saveProgressBar: function(show) { this.set('showProgressBar', show ? 1 : 0); GM_setValue('showProgressBar', show ? 1 : 0); }
};
var CONFIG = SettingsManager.load();
CONFIG.videoRate = clampVideoRate(CONFIG.videoRate);
_cxRateValue = CONFIG.videoRate;
function clampQuestionInterval() {
CONFIG.minInterval = Math.max(1, Math.min(60, parseInt(CONFIG.minInterval) || 3));
CONFIG.maxInterval = Math.max(CONFIG.minInterval, Math.min(60, parseInt(CONFIG.maxInterval) || 6));
}
function clampAnswerDelay() {
CONFIG.answerDelayMin = Math.max(3, Math.min(30, parseInt(CONFIG.answerDelayMin) || 3));
CONFIG.answerDelayMax = Math.max(CONFIG.answerDelayMin, Math.min(30, parseInt(CONFIG.answerDelayMax) || 10));
}
function clampJumpDelay() {
CONFIG.jumpDelayMin = Math.max(3, Math.min(60, parseInt(CONFIG.jumpDelayMin) || 10));
CONFIG.jumpDelayMax = Math.max(CONFIG.jumpDelayMin, Math.min(60, parseInt(CONFIG.jumpDelayMax) || 30));
}
clampQuestionInterval(); clampAnswerDelay(); clampJumpDelay();
function updateConfig(key, value) {
var oldValue = CONFIG[key];
CONFIG[key] = SettingsManager.set(key, value);
if (key === 'videoRate') {
CONFIG.videoRate = clampVideoRate(CONFIG.videoRate);
SettingsManager.set('videoRate', CONFIG.videoRate);
_cxRateValue = CONFIG.videoRate;
try { (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).__cxRateValue = CONFIG.videoRate; } catch(e) {}
}
log('⚙️ 配置已更新: ' + key + ' = ' + value + (oldValue !== value ? ' (已生效)' : ''), 'blue');
}
function getRandomIntervalSec() {
if (CONFIG.useRandomInterval) {
var mn = Math.max(1, Math.min(60, parseInt(CONFIG.minInterval) || 3));
var mx = Math.max(mn, Math.min(60, parseInt(CONFIG.maxInterval) || 6));
return mn + Math.random() * (mx - mn);
}
return Math.max(1, Math.round((CONFIG.answerInterval || 5000) / 1000));
}
function getRandomInterval() { return Math.floor(getRandomIntervalSec() * 1000); }
function getRandomAnswerDelaySec() {
if (!CONFIG.answerDelayEnabled) return 0;
var mn = Math.max(3, Math.min(30, parseInt(CONFIG.answerDelayMin) || 3));
var mx = Math.max(mn, Math.min(30, parseInt(CONFIG.answerDelayMax) || 10));
return mn + Math.random() * (mx - mn);
}
function getRandomJumpDelaySec() {
if (!CONFIG.jumpDelayEnabled) return 0;
var mn = Math.max(3, Math.min(60, parseInt(CONFIG.jumpDelayMin) || 10));
var mx = Math.max(mn, Math.min(60, parseInt(CONFIG.jumpDelayMax) || 30));
return mn + Math.random() * (mx - mn);
}
function applyAnswerWithDelay(answer, fillFn, label) {
return new Promise(function(resolve) {
var delay = getRandomAnswerDelaySec();
if (delay <= 0) { try { fillFn(answer); } catch(e) {} resolve(); return; }
log('⏳ ' + (label || '作答') + ' 延迟 ' + delay.toFixed(1) + ' 秒后填写', 'blue');
setTimeout(function() { try { fillFn(answer); } catch(e) {} resolve(); }, delay * 1000);
});
}
/********************************* 多选题匹配(修复:结果数不超过答案数) ****************************************/
function matchMultiChoiceIndices(optsText, answer) {
var ansArr = splitAnswer(answer);
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnsArr = ansArr.map(function(a) { return normalizeForCompare(a); }).filter(function(a) { return a !== ''; });
var hitIdx = [];
// [FIX] 标签型选项特殊处理
if (isTagLikeOptionList(optsText)) {
for (var t = 0; t < ansArr.length; t++) {
var target = normalizeTagOption(ansArr[t]);
for (var oi = 0; oi < optsText.length; oi++) {
if (hitIdx.indexOf(oi) === -1 && normalizeTagOption(optsText[oi]) === target) { hitIdx.push(oi); break; }
}
}
if (hitIdx.length > ansArr.length) hitIdx = hitIdx.slice(0, ansArr.length);
if (hitIdx.length > 0) return hitIdx;
}
normalizedAnsArr.forEach(function(na) {
for (var i = 0; i < normalizedOpts.length; i++) {
if (normalizedOpts[i] === na && hitIdx.indexOf(i) === -1) { hitIdx.push(i); return; }
}
});
normalizedAnsArr.forEach(function(na) {
var already = false;
for (var h = 0; h < hitIdx.length; h++) { if (normalizedOpts[hitIdx[h]] === na) { already = true; break; } }
if (already) return;
var bestIdx = -1, bestDiff = Infinity;
for (var i = 0; i < normalizedOpts.length; i++) {
if (hitIdx.indexOf(i) !== -1) continue;
var opt = normalizedOpts[i];
var diff = -1;
if (opt.indexOf(na) !== -1) diff = opt.length - na.length;
else if (na.indexOf(opt) !== -1) diff = na.length - opt.length;
if (diff >= 0 && diff <= 1 && diff < bestDiff) { bestDiff = diff; bestIdx = i; }
}
if (bestIdx !== -1) hitIdx.push(bestIdx);
});
if (hitIdx.length > normalizedAnsArr.length) hitIdx = hitIdx.slice(0, normalizedAnsArr.length);
return hitIdx;
}
/********************************* 题型映射 ****************************************/
var QuestionTypeMap = {
0: ['单选题', '单项选择题', '单选', '选择题'],
1: ['多选题', '多项选择题', '多选'],
2: ['填空题', '填空'],
3: ['判断题', '是非题', '判断'],
4: ['简答题', '简答', '问答题', '名词解释', '论述题', '论述', '计算题', '计算', '分录题', '资料题', '作图题', '其他', '其它', '阅读理解', '阅读', '阅读题', '理解题', '完形填空', '完形', '综合题']
};
function getQuestionTypeFromText(text) {
if (!text) return undefined;
for (var typeId = 0; typeId <= 4; typeId++) {
var keywords = QuestionTypeMap[typeId];
for (var i = 0; i < keywords.length; i++) { if (text.indexOf(keywords[i]) !== -1) return typeId; }
}
return undefined;
}
/********************************* 全局变量 ****************************************/
var UNSAFE_WIN = unsafeWindow;
var LOCATION = location;
var DOCUMENT = UNSAFE_WIN.document;
var $ = UNSAFE_WIN.jQuery || top.jQuery;
var UE = UNSAFE_WIN.UE;
var API_SERVERS = [
{ url: "https://api.deepseek.com/chat/completions", name: "DeepSeek" }
];
var currentServerIndex = 0;
/* ============================ 多 AI 接口 ============================ */
var AI_PROVIDERS = {
deepseek: {
name: 'DeepSeek', url: 'https://api.deepseek.com/chat/completions',
model: 'deepseek-chat', keyName: 'key_deepseek', consoleUrl: 'https://platform.deepseek.com/usage'
},
doubao: {
name: '豆包 AI', url: 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
model: 'doubao-pro-32k', keyName: 'key_doubao', consoleUrl: 'https://console.volcengine.com/ark'
},
openai: {
name: 'ChatGPT', url: 'https://api.openai.com/v1/chat/completions',
model: 'gpt-4o-mini', keyName: 'key_openai', consoleUrl: 'https://platform.openai.com/usage'
}
};
var PROVIDER_IDS = ['deepseek', 'doubao', 'openai'];
// 兼容旧版:把原来的单个 api_key 迁移到 DeepSeek
(function migrateOldKey() {
try {
var old = String(GM_getValue('api_key', '') || '').trim();
if (old && !String(GM_getValue('key_deepseek', '') || '').trim()) {
GM_setValue('key_deepseek', old);
}
} catch (e) {}
})();
function activeProviderId() {
var p = GM_getValue('ai_provider', 'deepseek');
return AI_PROVIDERS[p] ? p : 'deepseek';
}
function setActiveProvider(id) { if (AI_PROVIDERS[id]) GM_setValue('ai_provider', id); }
function activeProvider() { return AI_PROVIDERS[activeProviderId()]; }
function providerKey(id) { return String(GM_getValue(AI_PROVIDERS[id].keyName, '') || '').trim(); }
function configuredProviders() {
return PROVIDER_IDS.filter(function (id) { return !!providerKey(id); });
}
function getApiKey() {
var id = activeProviderId();
var k = providerKey(id);
if (k) return k;
// 当前接口没配、但只配了另一家 → 自动切过去
var c = configuredProviders();
if (c.length === 1) { setActiveProvider(c[0]); return providerKey(c[0]); }
return '';
}
function hasApiKey() { return configuredProviders().length > 0; }
/********************************* 配额(DeepSeek 版不再限次,保留同名空实现) ****************************************/
var QUOTA_KEYS = { remaining: 'quota_remaining', freeRemaining: 'quota_free_remaining', rechargeBalance: 'quota_recharge_balance', todayFreeUsage: 'quota_today_free_usage' };
function getQuotaInfo() {
return { remaining: hasApiKey() ? 999999 : 0, freeRemaining: 0, rechargeBalance: 0, todayFreeUsage: 0 };
}
var quotaInfo = getQuotaInfo();
function applyQuotaFromApi(newQuotaInfo) {}
function updateQuotaInfo(newQuotaInfo) {}
function saveQuotaInfo() {}
function isNoQuotaMode() {
return !hasApiKey();
}
function hasEnoughQuotaForSubmit() { return hasApiKey(); }
function getSubmitDecision() {
if (!hasApiKey()) { log('⚠️ 没有接入任何 API,停止答题与提交', 'orange'); return { action: 'none', reason: '无密钥,停止操作' }; }
if (CONFIG.autoSubmit) return { action: 'submit', reason: '自动提交' };
if (CONFIG.forceSubmit) return { action: 'submit', reason: '强制提交' };
return { action: 'save', reason: '自动保存' };
}
function updateQuotaDisplay() {
try {
var targetDoc = top.document;
if (!targetDoc.querySelector('#quotaInfo')) targetDoc = document;
var $quotaInfo = $('#quotaInfo', targetDoc);
if ($quotaInfo.length) {
var c = configuredProviders();
if (!c.length) $quotaInfo.html('⚠️ 未接入任何 API,请在上方填入密钥');
else $quotaInfo.html('🤖 ' + activeProvider().name + ':' + dsModel() + ' 已就绪');
}
} catch(e) {}
}
function dsModel() {
var id = activeProviderId();
return GM_getValue('model_' + id, '') || AI_PROVIDERS[id].model;
}
/********************************* 日志 ****************************************/
var logQueue = [];
var logTimer = null;
function getTopLogContainer() {
try {
var topDoc = top.document;
var c = topDoc.getElementById('ne-21log');
if (c) return $(c);
c = document.getElementById('ne-21log');
if (c) return $(c);
return null;
} catch(e) { return null; }
}
function flushLogs() {
if (logQueue.length === 0) return;
var container = getTopLogContainer();
if (container && container.length) {
var logsToShow = logQueue.slice();
logQueue = [];
for (var i = logsToShow.length - 1; i >= 0; i--) container.prepend(logsToShow[i]);
container.scrollTop(0);
}
if (logTimer) { clearTimeout(logTimer); logTimer = null; }
}
function scheduleFlushLogs() { if (logTimer) return; logTimer = setTimeout(flushLogs, 100); }
function log(str, color) {
var time = new Date().toLocaleTimeString();
var logHtml = '
[' + time + '] ' + str + '
';
logQueue.push(logHtml);
scheduleFlushLogs();
console.log('[学习通助手][' + time + ']', str);
}
/********************************* [FIX] 标签型选项支持 ****************************************/
function decodeHtmlEntities(text) {
if (!text) return '';
return String(text)
.replace(/</gi, '<')
.replace(/>/gi, '>')
.replace(/"/gi, '"')
.replace(/'/gi, "'")
.replace(/&/gi, '&')
.replace(/ /gi, ' ');
}
function isTagLikeOption(text) {
if (!text) return false;
var t = decodeHtmlEntities(String(text)).trim();
return /^<\/?[a-zA-Z][a-zA-Z0-9]*\s*\/?>$/.test(t);
}
function isTagLikeOptionList(optsText) {
if (!optsText || optsText.length === 0) return false;
var hit = 0;
for (var i = 0; i < optsText.length; i++) { if (isTagLikeOption(optsText[i])) hit++; }
return hit >= Math.ceil(optsText.length / 2);
}
function normalizeTagOption(text) {
if (!text) return '';
return decodeHtmlEntities(String(text)).replace(/\s+/g, '').toLowerCase();
}
function findSingleChoiceIndex(optsText, answer) {
if (!optsText || optsText.length === 0 || answer == null) return -1;
var ansRaw = decodeHtmlEntities(String(answer)).trim();
if (isTagLikeOptionList(optsText)) {
var ansTag = normalizeTagOption(ansRaw);
for (var i = 0; i < optsText.length; i++) {
if (normalizeTagOption(optsText[i]) === ansTag) return i;
}
var ansBare = normalizeForCompare(ansRaw);
for (var j = 0; j < optsText.length; j++) {
if (normalizeForCompare(optsText[j]) === ansBare) return j;
}
return -1;
}
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnswer = normalizeForCompare(ansRaw);
var idx = normalizedOpts.indexOf(normalizedAnswer);
if (idx !== -1) return idx;
for (var k = 0; k < normalizedOpts.length; k++) {
var o = normalizedOpts[k];
if (!o) continue;
if (o.indexOf(normalizedAnswer) !== -1 && o.length - normalizedAnswer.length <= 1) return k;
if (normalizedAnswer.indexOf(o) !== -1 && normalizedAnswer.length - o.length <= 1) return k;
}
for (var m = 0; m < normalizedOpts.length; m++) {
var o2 = normalizedOpts[m];
if (!o2) continue;
if (o2.indexOf(normalizedAnswer) !== -1 || normalizedAnswer.indexOf(o2) !== -1) return m;
}
return -1;
}
/********************************* 选项清理 ****************************************/
function cleanOptionText(text) {
if (!text) return text;
var rawDecoded = decodeHtmlEntities(String(text)).trim();
var cleaned = text.replace(/<[^>]*>/g, '');
cleaned = cleaned.replace(/^[A-Z][\s]*[.、.))\s]+/, '');
cleaned = cleaned.replace(/^\([A-Z]\)[\s]*/, '');
cleaned = cleaned.replace(/^([A-Z])[\s]*/, '');
cleaned = cleaned.replace(/^[:\s\-_]+/, '');
cleaned = cleaned.trim();
if (cleaned === '' && rawDecoded !== '') return rawDecoded;
return cleaned;
}
function cleanQuizOptionText(text) {
if (!text) return '';
var rawDecoded = decodeHtmlEntities(String(text)).replace(/ /g, ' ').trim();
var cleaned = String(text).replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim();
for (var round = 0; round < 3; round++) {
var before = cleaned;
cleaned = cleaned.replace(/^\s*[A-Za-z]\s*[.、.,,::))]+\s*/, '');
cleaned = cleaned.replace(/^\s*[((]\s*[A-Za-z]\s*[))]\s*/, '');
cleaned = cleaned.replace(/^\s*\d+\s*[.、.,,::))]+\s*/, '');
cleaned = cleaned.replace(/^\s*[((]\s*\d+\s*[))]\s*/, '');
cleaned = cleaned.replace(/^\s*[A-Za-z]\s+/, '');
if (cleaned === before) break;
}
cleaned = cleaned.trim();
if (cleaned === '' && rawDecoded !== '') return rawDecoded;
return cleaned;
}
function tidyString(s) {
if (!s) return null;
var rawDecoded = decodeHtmlEntities(String(s)).trim();
var cleaned = s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').trim().replace(/ /g, '');
cleaned = cleanOptionText(cleaned);
if ((cleaned === '' || cleaned === null) && rawDecoded !== '') return rawDecoded;
return cleaned;
}
function tidyQuestion(s) {
if (!s) return null;
var cleaned = s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/^\d+[.、]/, '').trim();
return cleanOptionText(cleaned);
}
function normalizeForCompare(text) {
if (!text) return '';
return text.toUpperCase().replace(/[^\u4e00-\u9fa5A-Z0-9]/g, '').trim();
}
function splitAnswer(answer) {
if (!answer) return [];
var parts = answer.split(/[#]+/).map(function(a) { return a.trim(); }).filter(function(a) { return a !== ''; });
if (parts.length === 0 && answer.trim() !== '') return [answer.trim()];
return parts;
}
function extractString(str, start, end) {
var res = str.match(new RegExp(start + '(.*?)' + end));
return res ? res[1] : null;
}
/********************************* 悬浮窗 ****************************************/
function showPikaqiu() {
if (!CONFIG.showMaskImg) return;
if (pikaqiuAdded) return;
try {
var targetDoc = top.document;
if (!targetDoc.body) { setTimeout(showPikaqiu, 500); return; }
if (targetDoc.querySelector('#ne-21entry')) return;
// 左上角齿轮入口(在「返回课程」下方,不遮挡),下方写「刷课助手」
var entryHtml = '' +
'' +
'
' +
ic('gear', 20, '#2c7da0') + '
' +
'
刷课助手
' +
'
';
$(targetDoc.body).append(entryHtml);
pikaqiuAdded = true;
var $entry = $('#ne-21entry', targetDoc);
$entry.on('mouseenter', function() { this.style.opacity = '1'; });
$entry.on('mouseleave', function() { this.style.opacity = '.88'; });
$entry.click(function() {
var $box = $('#ne-21box', targetDoc);
if ($box.length) { if ($box.css('display') === 'none') { $box.css('display', 'block'); isBoxHidden = false; } else { $box.css('display', 'none'); isBoxHidden = true; } }
});
} catch(e) {}
}
function bindKeydown() {
$(document).off('keydown.cxpanel').on('keydown.cxpanel', function(e) {
if (e.keyCode == 120) {
var $box = $('#ne-21box');
if ($box.length === 0) return;
if (isBoxHidden) { $box.css('display', 'block'); isBoxHidden = false; log('📌 面板已显示', 'green'); }
else { $box.css('display', 'none'); isBoxHidden = true; log('📌 面板已隐藏,按F9键恢复显示', 'blue'); }
}
});
}
/********************************* DeepSeek 调用 ****************************************/
var DS_HOST = 'https://api.deepseek.com/chat/completions'; // 兼容旧引用,实际用 activeProvider().url
var LETTERS = 'ABCDEFGHIJ';
function dsChat(system, user, retry) {
retry = retry || 0;
return new Promise(function (resolve, reject) {
var key = getApiKey();
if (!key || !key.trim()) { reject('NO_API_KEY'); return; }
var body = JSON.stringify({
model: dsModel(),
messages: [{ role: 'system', content: system }, { role: 'user', content: user }],
temperature: 0,
max_tokens: 1024,
stream: false
});
var done = false;
var timer = setTimeout(function () {
if (done) return; done = true;
if (retry < 1) resolve(dsChat(system, user, retry + 1));
else reject('HARD_TIMEOUT');
}, 70000);
GM_xmlhttpRequest({
method: 'POST', url: activeProvider().url,
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + key.trim() },
data: body, timeout: 65000,
onload: function (xhr) {
if (done) return;
var j = null;
try { j = JSON.parse(xhr.responseText); } catch (e) {}
if (!j) {
done = true; clearTimeout(timer);
if (retry < 1 && xhr.status >= 500) resolve(dsChat(system, user, retry + 1));
else reject('接口返回异常(' + xhr.status + ')');
return;
}
if (j.error) {
done = true; clearTimeout(timer);
var m = j.error.message || j.error.type || '接口错误';
log('❌ DeepSeek: ' + m, 'red');
if (/unauthorized|invalid|api key/i.test(m)) { reject('密钥无效'); return; }
if (retry < 1 && /rate|overload|busy|timeout|server/i.test(m)) resolve(dsChat(system, user, retry + 1));
else reject(m);
return;
}
var c = j.choices && j.choices[0] && j.choices[0].message && j.choices[0].message.content;
done = true; clearTimeout(timer);
if (!c) { resolve(''); return; }
resolve(String(c));
},
onerror: function () {
if (done) return; done = true; clearTimeout(timer);
if (retry < 1) resolve(dsChat(system, user, retry + 1));
else reject('网络请求失败');
},
ontimeout: function () {
if (done) return; done = true; clearTimeout(timer);
if (retry < 1) resolve(dsChat(system, user, retry + 1));
else reject('请求超时');
}
});
});
}
function testApi(key, serverIndex) {
return dsChat('你是回显助手,只按用户要求输出。', '只回复两个字:可用')
.then(function (t) {
log('✅ ' + activeProvider().name + ' 连接正常:' + String(t).slice(0, 20), 'green');
return { code: 1, data: t };
});
}
function refreshQuotaFromServer() { return Promise.resolve(hasApiKey()); }
function checkApiKeyBeforeAction(actionName) {
if (!hasApiKey()) { log('❌ 无法执行' + actionName + ':请先在面板填入 DeepSeek API Key', 'red'); return false; }
return true;
}
function buildDsPrompt(qType, qText, options) {
var opts = options || [];
var lines = [];
for (var i = 0; i < opts.length && i < 10; i++) lines.push(LETTERS.charAt(i) + '. ' + opts[i]);
if (qType === 0) {
return '题目:' + qText + '\n选项:\n' + lines.join('\n') +
'\n\n请判断唯一正确选项,只输出一个字母(A-J),不要输出其他内容。';
}
if (qType === 1) {
return '题目:' + qText + '\n选项:\n' + lines.join('\n') +
'\n\n这是多选题,请选出所有正确选项,只输出字母并按字母序连写(例如 ABD),不要输出其他内容。';
}
if (qType === 2) {
var n = (opts[0] && /^\d+$/.test(String(opts[0]))) ? opts[0] : '若干';
return '填空题:' + qText + '\n\n该题共有 ' + n + ' 个空。请按顺序给出每个空的答案,用 # 分隔,只输出答案本身,不要编号、不要解释。';
}
if (qType === 3) {
return '判断题:' + qText + '\n\n请只输出「对」或「错」,不要输出其他内容。';
}
return '简答题:' + qText + '\n\n请给出准确、简洁的答案,200 字以内,不要写「答案:」前缀,不要解释。';
}
function cleanModelText(s) {
var t = String(s == null ? '' : s).trim();
t = t.replace(/^```[a-zA-Z]*\s*/, '').replace(/```$/, '').trim();
t = t.replace(/^(正确)?答案\s*[::]\s*/, '').replace(/^答\s*[::]\s*/, '').trim();
t = t.replace(/^\*+|\*+$/g, '').trim();
return t;
}
function resolveByLetters(qType, text, options) {
var letters = String(text || '').toUpperCase().replace(/[^A-J]/g, '');
if (!letters || letters.length > 6) return null;
var picked = [];
for (var i = 0; i < letters.length; i++) {
var n = LETTERS.indexOf(letters.charAt(i));
if (n >= 0 && n < options.length && picked.indexOf(n) === -1) picked.push(n);
}
if (!picked.length) return null;
picked.sort(function (a, b) { return a - b; });
if (qType === 0) return options[picked[0]];
return picked.map(function (n) { return options[n]; }).join('#');
}
function matchByText(qType, text, options) {
var t = cleanModelText(text);
if (!t) return null;
var nt = normalizeForCompare(t);
var hits = [];
for (var i = 0; i < options.length; i++) {
var no = normalizeForCompare(options[i]);
if (!no) continue;
if (nt === no) hits.push(i);
else if (nt.indexOf(no) !== -1 || no.indexOf(nt) !== -1) hits.push(i);
}
if (!hits.length) return null;
if (qType === 0) return options[hits[0]];
return hits.map(function (n) { return options[n]; }).join('#');
}
function getAnswer(questionType, questionText, optionsData, retryCount) {
return new Promise(function (resolve, reject) {
if (!hasApiKey()) { log('❌ 请先在面板填入 DeepSeek API Key', 'red'); reject('NO_API_KEY'); return; }
var typeMap = { 0: '单选题', 1: '多选题', 2: '填空题', 3: '判断题', 4: '简答题' };
log('🔍 问 DeepSeek - ' + (typeMap[questionType] || '题目') + ':' + String(questionText).substring(0, 40) + '...', 'blue');
dsChat('你是中国高校网课的答题助手,熟悉各学科。严格遵守输出格式,只给答案,不解释。',
buildDsPrompt(questionType, questionText, optionsData), 0)
.then(function (raw) {
var text = cleanModelText(raw);
if (!text) { log('⚠️ DeepSeek 返回为空', 'orange'); resolve('暂无答案'); return; }
if (questionType === 0 || questionType === 1) {
var byLetter = resolveByLetters(questionType, text, optionsData || []);
if (byLetter) { log('✅ 答案:' + byLetter, 'purple'); resolve(byLetter); return; }
var byText = matchByText(questionType, text, optionsData || []);
if (byText) { log('✅ 答案(文本匹配):' + byText, 'purple'); resolve(byText); return; }
log('⚠️ 选项没匹配上,原始返回:' + text.slice(0, 40), 'orange');
resolve('暂无答案');
return;
}
if (questionType === 3) {
var isTrue = /对|正确|是|√|T|true/i.test(text) && !/错|不正确|否|错误|×|F|false/i.test(text);
log('✅ 判断:' + (isTrue ? '对' : '错'), 'purple');
resolve(isTrue ? '对' : '错');
return;
}
if (questionType === 2) {
var parts = String(text).split(/[#\n]+/).map(function (x) {
return x.replace(/^\s*\d+[.、)]\s*/, '').trim();
}).filter(function (x) { return x; });
if (!parts.length) { resolve('暂无答案'); return; }
log('✅ 填空:' + parts.join(' | '), 'purple');
resolve(parts.join('#'));
return;
}
log('✅ 简答:' + text.slice(0, 40), 'purple');
resolve(text);
})
.catch(function (err) {
log('❌ DeepSeek 取答案失败:' + err, 'red');
reject(err);
});
});
}
function getAnswerWithHardTimeout(questionType, questionText, optionsData) {
return Promise.race([
getAnswer(questionType, questionText, optionsData),
new Promise(function(_, reject) { var t = setTimeout(function() { reject('HARD_TIMEOUT'); }, 75000); __cxHardTimeoutTimers.push(t); })
]);
}
/********************************* 视频弹题(多视频修复版) ****************************************/
var currentVideoQuizObserver = null;
var quizObserverGuardTimer = null;
var quizPollTimer = null;
var __cxVideoDocMap = new WeakMap();
var __cxDocActiveMedia = new WeakMap();
var __cxHandledQuizSet = new WeakSet();
var __cxCurrentMedia = null;
function isVideoQuizPopup(node) {
if (!node || node.nodeType !== 1) return false;
var hasTitle = node.querySelector && (
node.querySelector('.TiMu') ||
node.querySelector('.ans-videoquiz') ||
node.querySelector('.ans-videoquiz-title') ||
node.querySelector('.videoquiz') ||
node.querySelector('.ans-videoquiz-opt')
);
var hasInput = node.querySelector && (
node.querySelector('input[type=radio]') ||
node.querySelector('input[type=checkbox]') ||
node.querySelector('.ans-videoquiz-opt label')
);
if (hasTitle && hasInput) return true;
var txt = node.textContent || '';
if ((txt.indexOf('题') !== -1 || txt.indexOf('选择') !== -1) &&
node.querySelectorAll && node.querySelectorAll('input[type=radio],input[type=checkbox]').length > 0) {
return true;
}
return false;
}
function extractVideoQuiz(popupEl) {
var titleEl = popupEl.querySelector('.TiMu') || popupEl.querySelector('.ans-videoquiz-title') || popupEl.querySelector('.videoquiz-title') || popupEl.querySelector('.ans-videoquiz') || popupEl;
var title = (titleEl.textContent || '').trim();
var options = [];
var inputs = popupEl.querySelectorAll('input[type=radio],input[type=checkbox]');
if (inputs.length) {
inputs.forEach(function(input, idx) {
var label = input.closest('label') || input.parentElement;
var raw = (label.textContent || '').trim();
options.push({ index: idx, text: cleanQuizOptionText(raw), rawText: raw, type: input.type, el: input });
});
} else {
var liList = popupEl.querySelectorAll('.ans-videoquiz-opt li, .ans-videoquiz-opt label');
liList.forEach(function(el, idx) {
var raw = (el.textContent || '').trim();
options.push({ index: idx, text: cleanQuizOptionText(raw), rawText: raw, type: 'click', el: el });
});
}
var qType = 'single';
if (popupEl.querySelector('input[type=checkbox]')) qType = 'multiple';
else if (popupEl.querySelector('.ans-videoquiz-opt .dui, .panduan')) qType = 'judge';
return { title: title, options: options, type: qType, popup: popupEl };
}
function getSecFont() {
try {
var styleEl = document.querySelector("style[type='text/css']");
if (!styleEl) return null;
var m = styleEl.textContent.match(/'(data:application\/font-ttf;.*?)'/);
return m ? m[1] : null;
} catch(e) { return null; }
}
function base64ToUint8Array(base64) {
var data = window.atob(base64);
var buf = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++) buf[i] = data.charCodeAt(i);
return buf;
}
function getTypr() {
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) return unsafeWindow.Typr;
if (typeof window !== 'undefined' && window.Typr) return window.Typr;
if (typeof Typr !== 'undefined') return Typr;
return null;
}
function getMd5() {
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.md5) return unsafeWindow.md5;
if (typeof window !== 'undefined' && window.md5) return window.md5;
if (typeof md5 !== 'undefined') return md5;
return null;
}
var __localTableCache = null;
function getLocalTable() {
if (__localTableCache) return __localTableCache;
try {
var raw = GM_getResourceText('Table');
if (!raw) return null;
__localTableCache = JSON.parse(raw);
return __localTableCache;
} catch(e) { return null; }
}
function localDecryptText(secFontDataUrl, cipherText) {
return new Promise(function(resolve) {
try {
var TyprInstance = getTypr();
var md5Instance = getMd5();
var table = getLocalTable();
if (!TyprInstance || !md5Instance || !TyprInstance.U || !table) { resolve(cipherText); return; }
var m = String(secFontDataUrl).match(/base64,([\w\W]+)/);
if (!m || !m[1]) { resolve(cipherText); return; }
var fontUint8Array = base64ToUint8Array(m[1].split("'")[0]);
var parsedFont = TyprInstance.parse(fontUint8Array);
if (!parsedFont || parsedFont.length === 0) { resolve(cipherText); return; }
var font = parsedFont[0];
var codeMap = {};
var uniqueCodes = new Set();
for (var i = 0; i < cipherText.length; i++) uniqueCodes.add(cipherText.charCodeAt(i));
uniqueCodes.forEach(function(code) {
var glyph = TyprInstance.U.codeToGlyph(font, code);
if (!glyph) return;
var path = TyprInstance.U.glyphToPath(font, glyph);
if (!path) return;
var hash = md5Instance(JSON.stringify(path));
if (!hash) return;
var key = hash.slice(24);
var realChar = table[key];
if (realChar !== undefined && realChar !== null && realChar !== 0) codeMap[code] = realChar;
});
var out = '';
for (var j = 0; j < cipherText.length; j++) {
var c = cipherText.charCodeAt(j);
out += codeMap[c] !== undefined ? String.fromCharCode(codeMap[c]) : cipherText.charAt(j);
}
resolve(out);
} catch(e) { resolve(cipherText); }
});
}
function decryptVideoQuizFont(popupEl) {
return new Promise(function(resolve) {
if (!CONFIG.decryptFont) { resolve(); return; }
var nodes = popupEl.querySelectorAll('.font-cxsecret');
if (!nodes.length) { resolve(); return; }
var secFont = getSecFont();
if (!secFont) { resolve(); return; }
var pending = nodes.length;
nodes.forEach(function(node) {
var cipher = (node.textContent || '').trim();
localDecryptText(secFont, cipher).then(function(plain) {
if (plain && plain !== cipher) node.textContent = plain;
if (--pending === 0) resolve();
}).catch(function() { if (--pending === 0) resolve(); });
});
});
}
/* ---------- 点击与校验工具(弹题专用) ---------- */
function clickOptOnce(opt) {
try {
if (!opt || !opt.el) return;
if (opt.el.click) opt.el.click();
} catch(e) {}
}
function isOptChecked(opt) {
try {
if (!opt || !opt.el) return false;
var el = opt.el;
if (el.type === 'checkbox' || el.type === 'radio') return !!el.checked;
var cls = (el.className || '') + ' ' + ((el.parentElement && el.parentElement.className) || '');
return /checked|selected|active|cur/.test(cls) || el.getAttribute('aria-checked') === 'true';
} catch(e) { return false; }
}
function clickOpt(opt) {
try {
if (opt.el) {
if (opt.el.click) opt.el.click();
var label = opt.el.closest && opt.el.closest('label');
if (label && label.click) label.click();
}
} catch(e) {}
}
function fillVideoQuizAnswer(quiz, answer) {
return new Promise(function(resolve) {
var qType = quiz.type;
var opts = quiz.options;
if (!opts.length) { resolve(false); return; }
if (qType === 'judge') {
var isTrue = /对|正确|是|√|T/i.test(answer);
var targetIdx = isTrue ? 0 : 1;
if (opts[targetIdx]) { clickOptOnce(opts[targetIdx]); resolve(true); return; }
resolve(false); return;
}
if (qType === 'multiple') {
var optsText = opts.map(function(o) { return o.text; });
var hitIdx = matchMultiChoiceIndices(optsText, answer);
if (hitIdx.length === 0) { resolve(false); return; }
log('🎯 多选题目标选项: ' + hitIdx.map(function(i) { return opts[i].text; }).join(' | '), 'blue');
var i = 0;
function step() {
if (i >= hitIdx.length) {
var checkedCount = opts.filter(isOptChecked).length;
log('📊 多选题已选 ' + checkedCount + '/' + hitIdx.length + ' 项', checkedCount === hitIdx.length ? 'green' : 'orange');
resolve(true);
return;
}
var opt = opts[hitIdx[i]];
clickOptOnce(opt);
setTimeout(function() {
if (!isOptChecked(opt)) { clickOptOnce(opt); }
i++;
setTimeout(step, 400);
}, 200);
}
step();
return;
}
var vqOptsText = opts.map(function(o) { return o.text; });
var ansFirst = splitAnswer(answer)[0] || answer;
var singleIdx = findSingleChoiceIndex(vqOptsText, ansFirst);
if (singleIdx !== -1) { clickOptOnce(opts[singleIdx]); resolve(true); return; }
var ansArr = splitAnswer(answer);
var clicked = false;
opts.forEach(function(opt) {
var normOpt = normalizeForCompare(opt.text);
for (var k = 0; k < ansArr.length; k++) {
if (normOpt === normalizeForCompare(ansArr[k])) { clickOptOnce(opt); clicked = true; break; }
}
});
if (clicked) { resolve(true); return; }
opts.forEach(function(opt) {
var normOpt = normalizeForCompare(opt.text);
for (var k = 0; k < ansArr.length; k++) {
var normAns = normalizeForCompare(ansArr[k]);
if (normOpt.indexOf(normAns) !== -1 && normOpt.length - normAns.length <= 1) { clickOptOnce(opt); clicked = true; break; }
if (normAns.indexOf(normOpt) !== -1 && normAns.length - normOpt.length <= 1) { clickOptOnce(opt); clicked = true; break; }
}
});
if (clicked) { resolve(true); return; }
opts.forEach(function(opt) {
var normRaw = normalizeForCompare(opt.rawText);
for (var k = 0; k < ansArr.length; k++) {
if (normRaw === normalizeForCompare(ansArr[k])) { clickOptOnce(opt); clicked = true; break; }
}
});
resolve(clicked);
});
}
function randomPickVideoQuiz(quiz) {
var opts = quiz.options || [];
if (!opts.length) return false;
if (quiz.type === 'multiple') {
var pickCount = 1 + Math.floor(Math.random() * opts.length);
var pool = opts.slice();
var picks = [];
for (var k = 0; k < pickCount && pool.length > 0; k++) {
var pi = Math.floor(Math.random() * pool.length);
picks.push(pool[pi]);
pool.splice(pi, 1);
}
var idx = 0;
(function step() {
if (idx >= picks.length) return;
var cur = picks[idx];
clickOptOnce(cur);
setTimeout(function() {
if (!isOptChecked(cur)) clickOptOnce(cur);
idx++;
setTimeout(step, 400);
}, 200);
})();
log('🎲 多选题随机选择了 ' + picks.length + ' 项', 'orange');
return true;
}
var ri = Math.floor(Math.random() * opts.length);
clickOptOnce(opts[ri]);
log('🎲 随机选择第 ' + (ri + 1) + ' 项: ' + opts[ri].text, 'orange');
return true;
}
function submitVideoQuiz(popupEl, isMultiple) {
var submitBtn2 = popupEl.querySelector('.ans-videoquiz-submit, .submit-btn, .confirm, button[type=submit]');
if (submitBtn2) {
var delay = (isMultiple ? 1500 : 500) + Math.random() * 500;
setTimeout(function() { submitBtn2.click(); log('📤 弹题已提交', 'green'); }, delay);
}
}
function resumeVideoWhenQuizGone(popupEl, video) {
var done = false;
function resume() {
if (done) return;
done = true;
var doc = video && video.ownerDocument;
if (doc && __cxDocActiveMedia.get(doc) && __cxDocActiveMedia.get(doc) !== video) return;
if (video && video.paused && !video.ended) {
video.__cxShouldPlay = true;
video.play().then(function() {
try {
var r = clampVideoRate(CONFIG.videoRate);
if (typeof unsafeWindow.__cxForceRate === 'function') { unsafeWindow.__cxForceRate(video, r); }
else { video.playbackRate = r; }
} catch(e) {}
log('▶️ 视频已恢复播放(倍速 ' + clampVideoRate(CONFIG.videoRate) + 'x)', 'blue');
}).catch(function(){});
}
}
try {
var mo = new MutationObserver(function() { if (!popupEl.isConnected) { mo.disconnect(); resume(); } });
mo.observe(popupEl.parentNode || document.body, { childList: true, subtree: true });
} catch(e) {}
setTimeout(resume, 8000);
var t = setInterval(function() {
if (!popupEl.isConnected || popupEl.style.display === 'none') { clearInterval(t); resume(); }
}, 500);
setTimeout(function() { clearInterval(t); resume(); }, 12000);
}
function handleVideoQuiz(popupEl, video) {
if (__cxScriptStopped) return;
if (!CONFIG.handleVideoQuiz) return;
if (!popupEl) return;
if (__cxHandledQuizSet.has(popupEl)) return;
__cxHandledQuizSet.add(popupEl);
popupEl.dataset.videoQuizHandled = 'true';
log('🎯 检测到视频弹题,开始处理...', 'purple');
if (video && !video.paused) video.pause();
if (isNoQuotaMode()) {
log('⚠️ 没有答题次数,弹题随机作答(不走接口)', 'orange');
var quizTmp = extractVideoQuiz(popupEl);
randomPickVideoQuiz(quizTmp);
setTimeout(function() {
submitVideoQuiz(popupEl, quizTmp.type === 'multiple');
resumeVideoWhenQuizGone(popupEl, video);
}, quizTmp.type === 'multiple' ? 1800 : 800);
return;
}
decryptVideoQuizFont(popupEl).then(function() {
var quiz = extractVideoQuiz(popupEl);
log('📖 弹题题干: ' + quiz.title.substring(0, 60) + ' | 题型: ' + quiz.type, 'blue');
log('🔎 清洗后的弹题选项: ' + JSON.stringify(quiz.options.map(function(o){return o.text;})), 'blue');
var typeId = 0;
if (quiz.type === 'multiple') typeId = 1;
else if (quiz.type === 'judge') typeId = 3;
var optsText = quiz.options.map(function(o) { return o.text; });
var optionsData = (typeId === 3) ? ["对", "错"] : optsText;
getAnswerWithHardTimeout(typeId, quiz.title, optionsData).then(function(answer) {
log('📥 接口返回答案: ' + answer, 'purple');
if (!answer || answer === '暂无答案') {
log('⚠️ 弹题未获取到答案,随机选择一项提交', 'orange');
applyAnswerWithDelay(null, function() {
randomPickVideoQuiz(quiz);
setTimeout(function() {
submitVideoQuiz(popupEl, quiz.type === 'multiple');
resumeVideoWhenQuizGone(popupEl, video);
}, quiz.type === 'multiple' ? 1800 : 800);
}, '视频弹题');
} else {
applyAnswerWithDelay(answer, function(ans) {
fillVideoQuizAnswer(quiz, ans).then(function(filled) {
if (!filled) {
log('⚠️ 弹题答案未匹配到选项,随机选择提交', 'orange');
randomPickVideoQuiz(quiz);
} else {
log('✅ 弹题已作答: ' + ans, 'green');
}
setTimeout(function() {
submitVideoQuiz(popupEl, quiz.type === 'multiple');
resumeVideoWhenQuizGone(popupEl, video);
}, quiz.type === 'multiple' ? 1000 : 600);
});
}, '视频弹题');
}
}).catch(function(err) {
if (__cxScriptStopped) return;
if (err === 'HARD_TIMEOUT' || err === 'QUOTA_EXHAUSTED' || err === '所有接口均失败') {
abortQuizAndNextChapter('弹题接口无返回/不可用');
return;
}
log('⚠️ 弹题获取答案失败: ' + err + ',随机作答', 'orange');
applyAnswerWithDelay(null, function() {
randomPickVideoQuiz(quiz);
setTimeout(function() {
submitVideoQuiz(popupEl, quiz.type === 'multiple');
resumeVideoWhenQuizGone(popupEl, video);
}, quiz.type === 'multiple' ? 1800 : 800);
}, '视频弹题');
});
});
}
function bindQuizForMedia(media) {
if (!media || media.__cxQuizBound) return;
media.__cxQuizBound = true;
var doc = media.ownerDocument;
if (!doc) return;
__cxVideoDocMap.set(media, doc);
var onPlay = function() { __cxDocActiveMedia.set(doc, media); };
media.addEventListener('play', onPlay, true);
media.addEventListener('playing', onPlay, true);
media.addEventListener('ended', function() {
if (__cxDocActiveMedia.get(doc) === media) __cxDocActiveMedia.delete(doc);
}, true);
if (!media.paused) __cxDocActiveMedia.set(doc, media);
}
function quizBelongsToMedia(popupEl, media) {
if (!popupEl || !media) return false;
return popupEl.ownerDocument === media.ownerDocument;
}
function observeVideoQuizForMedia(media) {
if (!CONFIG.handleVideoQuiz) return;
if (!media) return;
bindQuizForMedia(media);
var doc = media.ownerDocument;
if (!doc || !doc.body) return;
if (currentVideoQuizObserver) {
try { currentVideoQuizObserver.disconnect(); } catch(e) {}
currentVideoQuizObserver = null;
}
__cxCurrentMedia = media;
var handle = function(popupEl) {
if (!quizBelongsToMedia(popupEl, media)) return;
handleVideoQuiz(popupEl, media);
};
var existings = doc.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
existings.forEach(function(el) { if (isVideoQuizPopup(el)) handle(el); });
currentVideoQuizObserver = new MutationObserver(function(mutations) {
if (!CONFIG.handleVideoQuiz) return;
var active = __cxDocActiveMedia.get(doc);
if (active && active !== media) return;
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType !== 1) return;
if (isVideoQuizPopup(node)) { handle(node); }
else if (node.querySelectorAll) {
var inner = node.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
inner.forEach(function(el) { if (isVideoQuizPopup(el)) handle(el); });
}
});
});
});
currentVideoQuizObserver.observe(doc.body, { childList: true, subtree: true });
doc.__cxQuizMedia = media;
startQuizPollForMedia(media);
log('👀 视频弹题监听已绑定到当前播放视频', 'blue');
}
function startQuizPollForMedia(media) {
if (quizPollTimer) { clearInterval(quizPollTimer); quizPollTimer = null; }
if (!media) return;
var doc = media.ownerDocument;
quizPollTimer = setInterval(function() {
if (!CONFIG.handleVideoQuiz) return;
if (!doc || !doc.body) return;
var active = __cxDocActiveMedia.get(doc);
if (active && active !== media) return;
try {
var pops = doc.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
pops.forEach(function(el) {
if (isVideoQuizPopup(el) && !__cxHandledQuizSet.has(el)) { handleVideoQuiz(el, media); }
});
} catch(e) {}
}, 1500);
}
function stopQuizForMedia(media) {
if (currentVideoQuizObserver) { try { currentVideoQuizObserver.disconnect(); } catch(e) {} currentVideoQuizObserver = null; }
stopQuizPoll();
var doc = media && media.ownerDocument;
if (doc) {
if (__cxDocActiveMedia.get(doc) === media) __cxDocActiveMedia.delete(doc);
doc.__cxQuizMedia = null;
}
if (__cxCurrentMedia === media) __cxCurrentMedia = null;
}
function stopQuizForAllMedia() {
if (currentVideoQuizObserver) { try { currentVideoQuizObserver.disconnect(); } catch(e) {} currentVideoQuizObserver = null; }
stopQuizPoll();
__cxCurrentMedia = null;
}
function observeVideoQuiz(doc) {
if (!CONFIG.handleVideoQuiz) return;
if (!doc || !doc.body) return;
var media = __cxDocActiveMedia.get(doc) || doc.querySelector('video');
if (!media) return;
observeVideoQuizForMedia(media);
}
function startQuizObserverGuard() {
if (quizObserverGuardTimer) return;
quizObserverGuardTimer = setInterval(function() {
if (!CONFIG.handleVideoQuiz) return;
try {
document.querySelectorAll('iframe').forEach(function(f) {
var d;
try { d = f.contentDocument || (f.contentWindow && f.contentWindow.document); } catch(e) { return; }
if (!d || !d.body) return;
var media = __cxDocActiveMedia.get(d);
if (!media) return;
if (!media.isConnected) { stopQuizForMedia(media); }
});
} catch(e) {}
}, 5000);
}
function stopQuizObserverGuard() { if (quizObserverGuardTimer) { clearInterval(quizObserverGuardTimer); quizObserverGuardTimer = null; } }
function startQuizPoll(doc) {
if (!CONFIG.handleVideoQuiz) return;
if (!doc || !doc.body) return;
var media = __cxDocActiveMedia.get(doc) || doc.querySelector('video');
if (!media) return;
startQuizPollForMedia(media);
}
function stopQuizPoll() { if (quizPollTimer) { clearInterval(quizPollTimer); quizPollTimer = null; } }
/********************************* 填空/简答填充 ****************************************/
function getBlankCount($question) {
var count = 0;
var $inputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]');
if ($inputs.length) {
var uniqueIdentifiers = new Set();
$inputs.each(function() {
var name = $(this).attr('name'); var id = $(this).attr('id');
if (name) uniqueIdentifiers.add(name);
else if (id) uniqueIdentifiers.add(id);
else { var parentHtml = $(this).parent().html() || ''; uniqueIdentifiers.add($(this).index() + '_' + parentHtml.substring(0, 50)); }
});
count = uniqueIdentifiers.size;
if (count > 0) return count;
}
var $editorBlocks = $question.find('[data-editorindex]');
if ($editorBlocks.length) {
var uniqueIndices = new Set();
$editorBlocks.each(function() { var idx = $(this).attr('data-editorindex'); if (idx !== undefined && idx !== null && idx !== '') uniqueIndices.add(idx); });
count = uniqueIndices.size;
if (count > 0) return count;
}
var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea');
if ($pcInputs.length) {
var uniquePCInputs = new Set();
$pcInputs.each(function() {
var id = $(this).attr('id'); var name = $(this).attr('name');
if (id) uniquePCInputs.add(id);
else if (name) uniquePCInputs.add(name);
else uniquePCInputs.add($(this).index());
});
count = uniquePCInputs.size;
if (count > 0) return count;
}
return count === 0 ? 1 : count;
}
function fillBlankAnswer($question, answers, isPhoneMode, contextWindow) {
var answerList = splitAnswer(answers);
log('📝 填空题答案: ' + answerList.join(' | '), 'blue');
if (isPhoneMode) {
var $allInputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]');
if ($allInputs.length) {
var uniqueInputs = []; var seen = new Set();
$allInputs.each(function() {
var name = $(this).attr('name'); var id = $(this).attr('id');
var key = name || id || $(this).index();
if (!seen.has(key)) { seen.add(key); uniqueInputs.push(this); }
});
log('📝 找到 ' + uniqueInputs.length + ' 个填空输入框,准备填写', 'blue');
$(uniqueInputs).each(function(i) {
setTimeout(function() {
var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
$(this).val(answerValue);
$(this).trigger('input').trigger('change');
log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green');
}.bind(this), i * 200);
});
return true;
}
var $editorBlocks = $question.find('[data-editorindex]');
if ($editorBlocks.length) {
var uniqueEditors = []; var seenIndices = new Set();
$editorBlocks.each(function() { var idx = $(this).attr('data-editorindex'); if (idx && !seenIndices.has(idx)) { seenIndices.add(idx); uniqueEditors.push(this); } });
log('📝 找到 ' + uniqueEditors.length + ' 个编辑器,准备填写', 'blue');
uniqueEditors.forEach(function(editor, i) {
var editorIndex = $(editor).attr('data-editorindex');
var itemId = $(editor).attr('data-itemid');
setTimeout(function() {
try {
var ueditor = null;
if (contextWindow && contextWindow.editors && contextWindow.editors[editorIndex]) { ueditor = contextWindow.editors[editorIndex].ueditor; }
if (!ueditor && contextWindow && contextWindow.UE && contextWindow.UE.instants) { var instantKey = 'ueditorInstant' + editorIndex; ueditor = contextWindow.UE.instants[instantKey]; }
if (!ueditor && itemId && contextWindow && contextWindow.UE && contextWindow.UE.getEditor) { ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId); }
if (ueditor) { var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); ueditor.setContent(answerValue); log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green'); }
if (itemId) { var answerValue2 = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); $('#answer' + itemId).val(answerValue2).trigger('change'); }
} catch(e) {}
}, i * 300);
});
return true;
}
}
var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea');
if ($pcInputs.length) {
var uniquePCInputs = []; var seenPC = new Set();
$pcInputs.each(function() {
var id = $(this).attr('id'); var name = $(this).attr('name');
var key = id || name || $(this).index();
if (!seenPC.has(key)) { seenPC.add(key); uniquePCInputs.push(this); }
});
log('📝 找到 ' + uniquePCInputs.length + ' 个填空输入框 (PC),准备填写', 'blue');
$(uniquePCInputs).each(function(i) {
setTimeout(function() {
var $this = $(this);
var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
if (UE && UE.getEditor && UE.getEditor($this.attr('id'))) { UE.getEditor($this.attr('id')).setContent(answerValue); }
else { $this.val(answerValue); $this.trigger('input').trigger('change'); }
log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green');
}.bind(this), i * 200);
});
return true;
}
return false;
}
function fillShortAnswer($question, answer, isPhoneMode, contextWindow) {
log('📝 简答题答案: ' + answer.substring(0, 100) + '...', 'blue');
if (isPhoneMode) {
var $editorBlocks = $question.find('[data-editorindex]');
if ($editorBlocks.length) {
$editorBlocks.each(function(i) {
var editorIndex = $(this).attr('data-editorindex');
var itemId = $(this).attr('data-itemid');
setTimeout(function() {
try {
var ueditor = null;
if (contextWindow && contextWindow.editors && contextWindow.editors[editorIndex]) { ueditor = contextWindow.editors[editorIndex].ueditor; }
if (!ueditor && contextWindow && contextWindow.UE && contextWindow.UE.instants) { var instantKey = 'ueditorInstant' + editorIndex; ueditor = contextWindow.UE.instants[instantKey]; }
if (!ueditor && itemId && contextWindow && contextWindow.UE && contextWindow.UE.getEditor) { ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId); }
if (ueditor) { ueditor.setContent(answer); log('✅ 简答题答案已填写', 'green'); }
if (itemId) { $('#answer' + itemId).val(answer).trigger('change'); }
} catch(e) {}
}, 300);
});
return true;
}
var $textarea = $question.find('textarea[name^="answer"], .answerTxt textarea');
if ($textarea.length) { $textarea.val(answer); $textarea.trigger('input').trigger('change'); return true; }
return false;
}
var $ueTextarea = $question.find('textarea[name^="answerEditor"], .eidtDiv textarea, .divText textarea');
if ($ueTextarea.length) {
var id = $ueTextarea.first().attr('id');
if (id && UE && UE.getEditor && UE.getEditor(id)) { try { UE.getEditor(id).setContent(answer); return true; } catch(e) {} }
$ueTextarea.val(answer);
$ueTextarea.trigger('input').trigger('change');
return true;
}
return false;
}
/********************************* 重做已做题 ****************************************/
function clearSingleQuestionAnswer($question, questionType) {
if (!CONFIG.redoAnswered) return false;
var cleared = false;
try {
if (questionType == 0 || questionType == 1 || questionType == 3) {
$question.find('input[type="radio"], input[type="checkbox"]').each(function() {
if (this.checked) { this.checked = false; $(this).prop('checked', false); $(this).removeAttr('checked'); cleared = true; }
});
$question.find('.check_answer, .cur, .active, .selected, [aria-label="true"]').each(function() {
var $el = $(this);
try {
$el.click(); $el.parent().click(); $el.find('*').click(); $el.parent().find('*').click();
if ($el.hasClass('fl') || $el.hasClass('answer_p') || $el.parent().hasClass('answerBg')) { $el.find('a, span, i, em').each(function() { $(this).click(); }); }
} catch(e) {}
$el.removeClass('check_answer cur active selected checked').attr('aria-label', 'false');
$el.find('*').removeClass('check_answer cur active selected checked').attr('aria-label', 'false');
if ($el.attr('style') && ($el.attr('style').indexOf('background') !== -1 || $el.attr('style').indexOf('color') !== -1)) { $el.attr('style', ''); }
cleared = true;
});
$question.find('.answerBg, .fl.answer_p, .answer_p, .clearfix, .Zy_ulTop li, .answerList li').each(function() {
var $box = $(this);
var isSelected = $box.hasClass('check_answer') || ($box.attr('class') || '').indexOf('cur') !== -1 || $box.find('.check_answer').length > 0 || ($box.find('span').attr('class') || '').indexOf('check_answer') !== -1 || $box.find('i').length > 0;
if (isSelected) {
try { $box.click(); $box.find('span, i, a, em, label').each(function() { $(this).click(); }); } catch(e) {}
$box.removeClass('check_answer cur active selected checked');
$box.find('*').removeClass('check_answer cur active selected checked');
if ($box.attr('style') && ($box.attr('style').indexOf('background') !== -1 || $box.attr('style').indexOf('color') !== -1)) { $box.attr('style', ''); }
cleared = true;
}
});
} else if (questionType == 2 || questionType == 4) {
var $inputs = $question.find('.blankList2 input, input[type="text"][name*="answer"], .stem_answer .Answer .divText .textDIV textarea, .Zy_ulTk .XztiHover1 textarea, .subEditor textarea, textarea[name^="answer"]');
$inputs.each(function() { if ($(this).val()) { $(this).val('').trigger('input').trigger('change'); cleared = true; } });
var $editorBlocks = $question.find('[data-editorindex]');
$editorBlocks.each(function() {
var itemId = $(this).attr('data-itemid');
if (itemId && $('#answer' + itemId).val()) { $('#answer' + itemId).val('').trigger('change'); cleared = true; }
try {
var win = this.ownerDocument.defaultView || unsafeWindow;
var idx = $(this).attr('data-editorindex');
var ue = null;
if (win.editors && win.editors[idx]) ue = win.editors[idx].ueditor;
if (!ue && win.UE && win.UE.instants) ue = win.UE.instants['ueditorInstant' + idx];
if (!ue && itemId && win.UE && win.UE.getEditor) ue = win.UE.getEditor('ananas-editor-answer' + itemId);
if (ue && ue.setContent) { ue.setContent(''); cleared = true; }
} catch(e) {}
});
}
} catch(e) {}
if (cleared) { log('♻️ 已清空本题,准备覆盖重答', 'blue'); }
return cleared;
}
/********************************* 通用等待 ****************************************/
function waitForElement(selector, timeout) {
timeout = timeout || 30000;
return new Promise(function(resolve, reject) {
var start = Date.now();
var timer = setInterval(function() {
if ($(selector).length) { clearInterval(timer); resolve(); }
else if (Date.now() - start > timeout) { clearInterval(timer); reject(); }
}, 500);
});
}
function getElement(parent, selector, timeout) {
timeout = timeout || 10000;
return new Promise(function(resolve) {
var result = parent.querySelector(selector);
if (result) return resolve(result);
var timer;
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
for (var j = 0; j < mutation.addedNodes.length; j++) {
var node = mutation.addedNodes[j];
if (node instanceof Element) {
result = node.matches(selector) ? node : node.querySelector(selector);
if (result) { observer.disconnect(); timer && clearTimeout(timer); return resolve(result); }
}
}
}
});
observer.observe(parent, { childList: true, subtree: true });
timer = setTimeout(function() { observer.disconnect(); resolve(null); }, timeout);
});
}
function getTaskParams() {
try {
var scripts = DOCUMENT.scripts;
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].innerHTML.indexOf('mArg = "";') != -1 && scripts[i].innerHTML.indexOf('==UserScript==') == -1) {
var params = extractString(scripts[i].innerHTML.replace(/\s/g, ""), 'try{mArg=', ';}catch');
return params;
}
}
return null;
} catch(e) { return null; }
}
function isTaskCompleted(task) {
if (!task) return false;
if (task.isPassed === true) return true;
if (task.status === 'completed' || task.status === 'finished') return true;
if (task.finished === true) return true;
return false;
}
/********************************* 跳转 ****************************************/
function goToNext() {
if (__cxScriptStopped) return false;
if (isJumping) return false;
if (!canJumpNow()) {
log('⏸️ 检测到未提交的答题任务,暂停章节跳转,等待提交完成', 'orange');
var waitTimer = setInterval(function() { if (canJumpNow()) { clearInterval(waitTimer); goToNext(); } }, 2000);
setTimeout(function() { clearInterval(waitTimer); if (isJumping) return; __cxJumpLock = false; __cxPendingSubmit = false; goToNext(); }, 60000);
return false;
}
isJumping = true;
log('🔄 检查是否有下一章节...', 'blue');
var nextBtn = null;
try {
nextBtn = top.document.querySelector('#mainid > .prev_next.next:not(.disabled)') || top.document.querySelector('#prevNextFocusNext:not(.disabled)') || top.document.querySelector('.prev_next.next:not(.disabled)') || $('.prev_next.next:not(.disabled)')[0];
if (nextBtn && !nextBtn.disabled && !nextBtn.classList.contains('disabled')) {
var jumpDelay = getRandomJumpDelaySec();
if (jumpDelay > 0) {
log('✅ 找到下一节按钮,' + jumpDelay.toFixed(1) + '秒后跳转', 'green');
setTimeout(function() {
if (!canJumpNow()) { log('⏸️ 跳转前发现新答题任务,取消本次跳转', 'orange'); isJumping = false; return; }
try { nextBtn.click(); log('📖 已点击跳转按钮', 'green'); } catch(e) {}
isJumping = false;
}, jumpDelay * 1000);
} else {
log('✅ 找到下一节按钮,立即跳转', 'green');
try { nextBtn.click(); } catch(e) {}
isJumping = false;
}
return true;
} else { log('📚 课程已全部完成,无更多章节可跳转', 'green'); }
} catch(e) { log('❌ 跳转失败: ' + e.message, 'red'); }
isJumping = false;
return false;
}
function completeCurrentTask() {
if (__cxScriptStopped) return;
if (taskList && taskList.length > 0) {
var completedTask = taskList[0];
var taskName = completedTask.property ? (completedTask.property.name || completedTask.property.title || '未知') : '未知';
log('✅ 完成任务: ' + taskName, 'green');
taskList.splice(0, 1);
completedMissionCount++;
log('📋 任务进度: ' + completedMissionCount + '/' + pendingMissionCount + ' | 剩余: ' + taskList.length, 'blue');
}
if (domList && domList.length > 0) { domList.splice(0, 1); }
isProcessing = false;
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
stopQuizForAllMedia();
stopQuizObserverGuard();
if (taskList && taskList.length > 0) { log('📋 还有 ' + taskList.length + ' 个任务点待处理,继续...', 'green'); setTimeout(function() { startMission(); }, 2000); }
else {
log('✅ 此页面所有任务处理完毕', 'green');
if (canJumpNow()) { goToNext(); }
else { log('⏸️ 答题提交尚未完成,暂不跳转章节', 'orange'); var t = setInterval(function() { if (canJumpNow()) { clearInterval(t); goToNext(); } }, 2000); }
}
}
function switchMission() {
if (__cxScriptStopped) return;
if (taskList.length <= 0) { log('✅ 此页面任务处理完毕', 'green'); isProcessing = false; goToNext(); return; }
isProcessing = false;
setTimeout(startMission, 3000);
}
function startMission() {
if (__cxScriptStopped) return;
if (isProcessing) return;
if (taskList.length <= 0) { log('✅ 此页面任务处理完毕', 'green'); isProcessing = false; goToNext(); return; }
isProcessing = true;
var taskType = taskList[0]['type'];
var dom = domList[0];
var task = taskList[0];
currentProcessingTask = task;
if (taskType == undefined) { taskType = taskList[0]['property']["module"]; }
var handlers = {
'video': function() { processVideo(dom, task); },
'audio': function() { processAudio(dom, task); },
'workid': function() { processQuiz(dom, task); },
'document': function() { processDocument(dom, task); },
'read': function() { processRead(dom, task); },
'insertbook': function() { processBook(dom, task); }
};
if (handlers[taskType]) { handlers[taskType](); }
else if (['insertimage'].indexOf(taskType) !== -1) { log('ℹ️ 发现无需处理任务,跳过。', 'orange'); completeCurrentTask(); }
else { log('⚠️ 暂不支持处理此类型:' + taskType + ',跳过。', 'red'); completeCurrentTask(); }
}
/********************************* 视频/音频/文档/阅读 ****************************************/
function processVideo(dom, obj) {
if (__cxScriptStopped) return;
if (!CONFIG.handleVideo) { log('ℹ️ 用户设置不处理视频任务', 'orange'); completeCurrentTask(); return; }
var isPassed = obj.isPassed;
var name = obj.property.name;
if (!CONFIG.reviewMode && isPassed === true) { log('✅ 视频:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; }
var target = dom.length > 0 ? dom[0] : null;
if (!target) { log('⚠️ 未找到视频iframe,3秒后重试', 'orange'); isProcessing = false; setTimeout(function() { processVideo(dom, obj); }, 3000); return; }
log('🎬 处理视频:' + name, 'purple');
var executed = false;
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
currentVideoInterval = setInterval(function() {
if (__cxScriptStopped) { if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; } return; }
var innerDoc = target.contentDocument || target.contentWindow.document;
if (!innerDoc) return;
var media = innerDoc.querySelector('video') || innerDoc.querySelector('audio');
if (media && !executed) {
executed = true;
log('✅ ' + name + ' 开始播放', 'green');
media.pause();
media.muted = true;
media.__cxShouldPlay = true;
var targetRate = clampVideoRate(CONFIG.videoRate);
try {
if (typeof unsafeWindow.__cxForceRate === 'function') { unsafeWindow.__cxForceRate(media, targetRate); }
else { media.playbackRate = targetRate; }
} catch(e) {}
media.play();
observeVideoQuizForMedia(media);
startQuizObserverGuard();
var resumePlay = function() {
if (media.paused && !media.ended) {
setTimeout(function() {
var popup = innerDoc.querySelector('.ans-videoquiz, .TiMu, .videoquiz');
if (!popup && media.paused && !media.ended) {
media.__cxShouldPlay = true;
media.play();
try {
var r = clampVideoRate(CONFIG.videoRate);
if (typeof unsafeWindow.__cxForceRate === 'function') { unsafeWindow.__cxForceRate(media, r); }
else { media.playbackRate = r; }
} catch(e) {}
log('🔄 ' + name + ' 恢复播放(倍速 ' + clampVideoRate(CONFIG.videoRate) + 'x)', 'blue');
}
}, 3000);
}
};
media.addEventListener('pause', resumePlay);
var onVideoEnd = function() {
log('✅ ' + name + ' 播放完成', 'green');
media.__cxShouldPlay = false;
media.removeEventListener('pause', resumePlay);
media.removeEventListener('ended', onVideoEnd);
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
stopQuizForMedia(media);
stopQuizObserverGuard();
completeCurrentTask();
};
media.addEventListener('ended', onVideoEnd);
if (media.ended) { onVideoEnd(); }
} else if (media && executed) {
if (media.ended) {
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
stopQuizForMedia(media);
stopQuizObserverGuard();
completeCurrentTask();
}
}
}, 1500);
}
function processAudio(dom, obj) {
if (__cxScriptStopped) return;
if (!CONFIG.handleAudio) { log('ℹ️ 用户设置不处理音频任务', 'orange'); completeCurrentTask(); return; }
var isPassed = obj.isPassed;
var name = obj.property.name;
if (!CONFIG.reviewMode && isPassed === true) { log('✅ 音频:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; }
log('🎵 处理音频:' + name + ',等待5秒后完成', 'purple');
setTimeout(function() { if (!__cxScriptStopped) completeCurrentTask(); }, 5000);
}
function processBook(dom, obj) {
if (__cxScriptStopped) return;
var jobId = obj['property']['jobid']; var name = obj['property']['bookname']; var jtoken = obj['jtoken'];
var knowledgeId = defaults['knowledgeid']; var courseId = defaults['courseid']; var clazzId = defaults['clazzId'];
if (isTaskCompleted(obj)) { log('✅ 读书:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; }
$.ajax({
url: LOCATION.protocol + "//" + LOCATION.host + '/ananas/job?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
method: 'GET',
success: function(res) { log('📚 读书:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); },
error: function() { log('❌ 读书:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { processBook(dom, obj); }, 3000); }
});
}
function processDocument(dom, obj) {
if (__cxScriptStopped) return;
var jobId = obj['property']['jobid']; var name = obj['property']['name']; var jtoken = obj['jtoken'];
var knowledgeId = defaults['knowledgeid']; var courseId = defaults['courseid']; var clazzId = defaults['clazzId'];
if (isTaskCompleted(obj)) { log('✅ 文档:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; }
$.ajax({
url: LOCATION.protocol + "//" + LOCATION.host + '/ananas/job/document?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
method: 'GET',
success: function(res) { log('📄 文档:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); },
error: function() { log('❌ 文档:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { processDocument(dom, obj); }, 3000); }
});
}
function processRead(dom, obj) {
if (__cxScriptStopped) return;
var jobId = obj['property']['jobid']; var name = obj['property']['title']; var jtoken = obj['jtoken'];
var knowledgeId = defaults['knowledgeid']; var courseId = defaults['courseid']; var clazzId = defaults['clazzId'];
if (isTaskCompleted(obj)) { log('✅ 阅读:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; }
$.ajax({
url: LOCATION.protocol + '//' + LOCATION.host + '/ananas/job/readv2?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
method: 'GET',
success: function(res) { log('📖 阅读:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); },
error: function() { log('❌ 阅读:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { processRead(dom, obj); }, 3000); }
});
}
/********************************* 测验/作业/考试 ****************************************/
function processQuiz(dom, obj) {
if (__cxScriptStopped) return;
if (!CONFIG.handleQuiz) { log('ℹ️ 用户设置不自动处理测验', 'orange'); completeCurrentTask(); return; }
resetChapterFailCount();
if (isNoQuotaMode()) {
refreshQuotaFromServer().then(function() {
if (__cxScriptStopped) return;
if (isNoQuotaMode()) { abortQuizAndNextChapter('题库配额为0'); return; }
doProcessQuiz(dom, obj);
});
return;
}
doProcessQuiz(dom, obj);
}
function doProcessQuiz(dom, obj) {
if (__cxScriptStopped) return;
var isDo = true;
if (CONFIG.taskOnly && obj['jobid'] == undefined) { isDo = false; }
if (isDo) {
if (obj['jobid'] !== undefined) {
var phoneWeb = LOCATION.protocol + '//' + LOCATION.host + '/work/phone/work?workId=' + obj['jobid'].replace('work-', '') + '&courseId=' + defaults['courseid'] + '&clazzId=' + defaults['clazzId'] + '&knowledgeId=' + defaults['knowledgeid'] + '&jobId=' + obj['jobid'] + '&enc=' + obj['enc'];
log('📝 准备处理测验', 'purple');
setTimeout(function() { if (!__cxScriptStopped) startPhoneQuiz(0, dom, phoneWeb, obj); }, 3000);
} else { setTimeout(function() { if (!__cxScriptStopped) startPCQuiz(0, dom, obj); }, 3000); }
} else { log('ℹ️ 用户设置只处理属于任务点的任务', 'orange'); completeCurrentTask(); }
}
function startPhoneQuiz(index, doms, phoneWeb, taskObj) {
if (__cxScriptStopped) return;
if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; }
getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) {
if (__cxScriptStopped) return;
if (!iframe) { setTimeout(function() { if (!__cxScriptStopped) startPhoneQuiz(index, doms, phoneWeb, taskObj); }, 5000); return; }
var workIframe = $(iframe);
var workStatus = workIframe.contents().find('.newTestCon .newTestTitle .testTit_status').text().trim();
if (!workStatus) { domList.splice(0, 1); isProcessing = false; setTimeout(switchMission, 2000); return; }
if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1 || workStatus.indexOf("未达到及格线") != -1) {
workIframe.attr('src', phoneWeb);
getElement($(doms[index]).contents()[0], 'iframe[src="' + phoneWeb + '"]').then(function() { setTimeout(function() { if (!__cxScriptStopped) doPhoneQuiz(workIframe.contents(), taskObj); }, 3000); });
} else if (workStatus.indexOf('待批阅') != -1) { taskList.splice(0, 1); domList.splice(0, 1); log('⚠️ 测验待批阅,跳过', 'red'); completeCurrentTask(); }
else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) { log('✅ 测验已完成,跳过', 'green'); completeCurrentTask(); }
else { taskList.splice(0, 1); domList.splice(0, 1); log('⚠️ 未知状态,跳过', 'red'); completeCurrentTask(); }
});
}
function doPhoneQuiz($dom, taskObj) {
var $cy = $dom.find('.Wrappadding form');
submitBtn = $cy.find('.zquestions .zsubmit .btn-ok-bottom');
okBtn = $dom.find('#okBtn');
saveBtn = $cy.find('.zquestions .zsubmit .btn-save');
var questionList = $cy.find('.zquestions .Py-mian1');
processPhoneQuestions(0, questionList, taskObj);
}
function processPhoneQuestions(index, questionList, taskObj) {
if (__cxScriptStopped) return;
if (index == questionList.length) {
__cxJumpLock = true; markPendingSubmit(true);
var decision = getSubmitDecision();
if (decision.action === 'submit') {
log('✅ 测验处理完成,准备' + decision.reason + '。', 'green');
setTimeout(function() {
if (submitBtn && submitBtn.length) submitBtn.click();
setTimeout(function() {
if (okBtn && okBtn.length) okBtn.click();
log('✅ 提交成功', 'green');
__cxJumpLock = false; markPendingSubmit(false); completeCurrentTask();
}, 3000);
}, 5000);
} else if (decision.action === 'save') {
log('💾 ' + decision.reason + ',自动保存', 'green');
setTimeout(function() {
if (saveBtn && saveBtn.length) saveBtn.click();
setTimeout(function() { log('✅ 保存成功', 'green'); __cxJumpLock = false; markPendingSubmit(false); completeCurrentTask(); }, 3000);
}, 5000);
} else { log('⏹️ 没有答题次数,不保存不提交,直接结束', 'orange'); __cxJumpLock = false; markPendingSubmit(false); completeCurrentTask(); }
return;
}
var contextWindow = questionList[index] ? (questionList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow;
var $question = $(questionList[index]);
var questionHtml = $question.find('.Py-m1-title').html() || $question.html() || '';
var typeName = '';
var bracketMatch = questionHtml.match(/\[(.*?)\]/);
if (bracketMatch && bracketMatch[1]) { typeName = bracketMatch[1]; }
if (!typeName) { var chineseMatch = questionHtml.match(/(单选题|多选题|填空题|判断题|简答题)/); if (chineseMatch) { typeName = chineseMatch[1]; } }
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
if (questionType === undefined) { questionType = getQuestionTypeFromText(questionHtml); }
if (questionType === undefined) {
if ($question.find('.answerList.singleChoice li').length) questionType = 0;
else if ($question.find('.answerList.multiChoice li').length) questionType = 1;
else if ($question.find('.blankList2 input').length) questionType = 2;
else if ($question.find('.answerList.panduan li').length) questionType = 3;
else if ($question.find('textarea').length) questionType = 4;
}
if (questionType === undefined) {
log('⚠️ 第' + (index + 1) + '题无法识别题型,跳过', 'orange');
if (bumpChapterFail('题型无法识别')) return;
setTimeout(function() { if (!__cxScriptStopped) processPhoneQuestions(index + 1, questionList, taskObj); }, getRandomIntervalSec() * 1000);
return;
}
var cleanedQuestion = tidyQuestion(questionHtml);
cleanedQuestion = cleanedQuestion.replace(/\[.*?\]/, '').trim();
cleanedQuestion = cleanedQuestion.replace(/^\d+[.、]?\s*/, '');
clearSingleQuestionAnswer($question, questionType);
proceedPhoneQuestion(index, questionList, taskObj, $question, questionType, cleanedQuestion, contextWindow);
}
function proceedPhoneQuestion(index, questionList, taskObj, $question, questionType, cleanedQuestion, contextWindow) {
if (__cxScriptStopped) return;
var $opts = []; var optsText = []; var optionsData = [];
var pureQuestion = cleanedQuestion;
if (questionType == 0 || questionType == 1) {
$opts = questionType == 0 ? $question.find('.answerList.singleChoice li') : $question.find('.answerList.multiChoice li');
$opts.each(function() {
var rawText = tidyString($(this).html());
rawText = rawText.replace(/^[A-Z][\s]*[.、.))\s]+/, '').replace(/^\([A-Z]\)[\s]*/, '').replace(/^([A-Z])[\s]*/, '');
optsText.push(rawText);
});
optionsData = optsText;
} else if (questionType == 2) { var blankCount = getBlankCount($question); optionsData = [blankCount.toString()]; }
else if (questionType == 3) { optionsData = ["对", "错"]; }
else if (questionType == 4) { optionsData = ["1"]; }
getAnswerWithHardTimeout(questionType, pureQuestion, optionsData).then(function(answer) {
if (__cxScriptStopped) return;
if (answer == '暂无答案' || !answer) {
log('⚠️ 第' + (index + 1) + '题无法获取答案,跳过', 'red');
if (bumpChapterFail('无答案')) return;
setTimeout(function() { if (!__cxScriptStopped) processPhoneQuestions(index + 1, questionList, taskObj); }, getRandomIntervalSec() * 1000);
return;
}
applyAnswerWithDelay(answer, function(ans) {
if (CONFIG.insertAnswer) { $question.find('.Py-m1-title').html($question.find('.Py-m1-title').html() + '📖 ' + ans + '
'); }
if (questionType == 0) {
var idx = findSingleChoiceIndex(optsText, ans);
if (idx != -1) { $question.find('.answerList.singleChoice li').eq(idx).click(); log('✅ 单选题已选: ' + optsText[idx], 'green'); }
else { log('⚠️ 未找到匹配选项: ' + ans + ' | 选项: ' + JSON.stringify(optsText), 'orange'); }
} else if (questionType == 1) {
var hitIdx = matchMultiChoiceIndices(optsText, ans);
if (hitIdx.length > 0) { hitIdx.forEach(function(i, k) { setTimeout(function() { $($opts[i]).click(); }, 300 * (k + 1)); }); log('✅ 多选题已选 ' + hitIdx.length + ' 项', 'green'); }
else { log('⚠️ 多选题未匹配到任何选项', 'orange'); }
} else if (questionType == 2) { fillBlankAnswer($question, ans, true, contextWindow); }
else if (questionType == 3) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pd = $question.find('.answerList.panduan li');
if (ans && ans.trim() !== '' && (trueKeywords.indexOf(ans) != -1 || trueKeywords.toLowerCase().indexOf(ans.toLowerCase()) != -1)) { $pd.each(function(i, t) { if ($(t).attr('val-param') == 'true') { $(t).click(); log('✅ 判断题自动选择: 正确', 'green'); } }); }
else { $pd.each(function(i, t) { if ($(t).attr('val-param') == 'false') { $(t).click(); log('✅ 判断题自动选择: 错误', 'green'); } }); }
} else if (questionType == 4) { fillShortAnswer($question, ans, true, contextWindow); }
log('✅ 第' + (index + 1) + '题自动答题成功', 'green');
}, '第' + (index + 1) + '题');
setTimeout(function() { if (!__cxScriptStopped) processPhoneQuestions(index + 1, questionList, taskObj); }, (getRandomAnswerDelaySec() + getRandomIntervalSec()) * 1000);
}).catch(function(err) {
if (__cxScriptStopped) return;
log('⚠️ 第' + (index + 1) + '题请求失败: ' + err + ',跳过该题', 'orange');
if (bumpChapterFail('接口失败')) return;
setTimeout(function() { if (!__cxScriptStopped) processPhoneQuestions(index + 1, questionList, taskObj); }, getRandomIntervalSec() * 1000);
});
}
function startPCQuiz(index, doms, taskObj) {
if (__cxScriptStopped) return;
if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; }
getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) {
if (__cxScriptStopped) return;
if (!iframe) { setTimeout(function() { if (!__cxScriptStopped) startPCQuiz(index, doms, taskObj); }, 5000); return; }
var workIframe = $(iframe);
var workStatus = workIframe.contents().find(".newTestCon .newTestTitle .testTit_status").text().trim();
if (!workStatus) { domList.splice(0, 1); isProcessing = false; setTimeout(switchMission, 2000); return; }
if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1) { log('📝 准备处理测验', 'purple'); setTimeout(function() { if (!__cxScriptStopped) doPCQuiz(index, doms, iframe, taskObj); }, 5000); }
else if (workStatus.indexOf('待批阅') != -1) { taskList.splice(0, 1); domList.splice(0, 1); log('⚠️ 测验待批阅,跳过', 'red'); completeCurrentTask(); }
else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) { log('✅ 测验已完成,跳过', 'green'); completeCurrentTask(); }
else { taskList.splice(0, 1); domList.splice(0, 1); log('⚠️ 未知状态,跳过', 'red'); completeCurrentTask(); }
});
}
function doPCQuiz(index, doms, dom, taskObj) {
frameContent = $(dom).contents();
var $cyHtml = frameContent.find('.CeYan');
var questionList = $cyHtml.find('.TiMu');
submitBtn = frameContent.find(".ZY_sub").find(".btnSubmit");
saveBtn = frameContent.find(".ZY_sub").find(".btnSave");
processPCQuestions(index, doms, 0, questionList, taskObj);
}
function processPCQuestions(index, doms, qIndex, questionList, taskObj) {
if (__cxScriptStopped) return;
if (qIndex == questionList.length) {
__cxJumpLock = true; markPendingSubmit(true);
var decision = getSubmitDecision();
if (decision.action === 'submit') {
log('✅ 测验处理完成,准备' + decision.reason + '。', 'green');
setTimeout(function() {
submitBtn.click();
setTimeout(function() {
frameContent.find('#confirmSubWin > div > div > a.bluebtn').click();
log('✅ 提交成功', 'green');
__cxJumpLock = false; markPendingSubmit(false); completeCurrentTask();
}, 3000);
}, 5000);
} else if (decision.action === 'save') {
log('💾 ' + decision.reason + ',自动保存', 'green');
setTimeout(function() {
saveBtn.click();
setTimeout(function() { log('✅ 保存成功', 'green'); __cxJumpLock = false; markPendingSubmit(false); completeCurrentTask(); }, 3000);
}, 5000);
} else { log('⏹️ 没有答题次数,不保存不提交,直接结束', 'orange'); __cxJumpLock = false; markPendingSubmit(false); completeCurrentTask(); }
return;
}
var $question = $(questionList[qIndex]);
var questionHtml = $question.find('.Zy_TItle.clearfix > div').html() || $question.html() || '';
var typeName = '';
var bracketMatch = questionHtml.match(/【(.*?)】/);
if (bracketMatch && bracketMatch[1]) { typeName = bracketMatch[1]; }
if (!typeName) { var chineseMatch = questionHtml.match(/(单选题|多选题|填空题|判断题|简答题)/); if (chineseMatch) { typeName = chineseMatch[1]; } }
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
if (questionType === undefined) { questionType = getQuestionTypeFromText(questionHtml); }
if (questionType === undefined) {
if ($question.find('.Zy_ulTop li').length) { questionType = $question.find('input[type="checkbox"]').length ? 1 : 0; }
else if ($question.find('.Zy_ulTk .XztiHover1').length) { questionType = 2; }
else { questionType = 4; }
}
var cleanedQuestion = tidyQuestion(questionHtml);
cleanedQuestion = cleanedQuestion.replace(/【.*?】/, '').trim();
cleanedQuestion = cleanedQuestion.replace(/^\d+[.、]?\s*/, '');
clearSingleQuestionAnswer($question, questionType);
proceedPCQuestion(index, doms, qIndex, questionList, taskObj, $question, questionType, cleanedQuestion);
}
function proceedPCQuestion(index, doms, qIndex, questionList, taskObj, $question, questionType, cleanedQuestion) {
if (__cxScriptStopped) return;
var $opts = []; var optsText = []; var optionsData = [];
var pureQuestion = cleanedQuestion;
if (questionType == 0 || questionType == 1) {
$opts = $question.find('.Zy_ulTop li a');
$opts.each(function() {
var rawText = tidyString($(this).html());
rawText = rawText.replace(/^[A-Z][\s]*[.、.))\s]+/, '').replace(/^\([A-Z]\)[\s]*/, '').replace(/^([A-Z])[\s]*/, '');
optsText.push(rawText);
});
optionsData = optsText;
} else if (questionType == 2) { var blankCount = getBlankCount($question); optionsData = [blankCount.toString()]; }
else if (questionType == 3) { optionsData = ["对", "错"]; }
else if (questionType == 4) { optionsData = ["1"]; }
getAnswerWithHardTimeout(questionType, pureQuestion, optionsData).then(function(answer) {
if (__cxScriptStopped) return;
if (answer && answer != '暂无答案') {
applyAnswerWithDelay(answer, function(ans) {
if (CONFIG.insertAnswer) { $question.find('.Zy_TItle.clearfix > div').html($question.find('.Zy_TItle.clearfix > div').html() + '📖 ' + ans + '
'); }
if (questionType == 0) {
var idx = findSingleChoiceIndex(optsText, ans);
if (idx != -1) { $($opts[idx]).parent().click(); log('✅ PC单选题已选: ' + optsText[idx], 'green'); }
else { log('⚠️ 未找到匹配选项: ' + ans + ' | 选项: ' + JSON.stringify(optsText), 'orange'); }
} else if (questionType == 1) {
var hitIdx = matchMultiChoiceIndices(optsText, ans);
if (hitIdx.length > 0) { hitIdx.forEach(function(i, k) { setTimeout(function() { $($opts[i]).parent().click(); }, 300 * (k + 1)); }); log('✅ PC多选题已选 ' + hitIdx.length + ' 项', 'green'); }
else { log('⚠️ PC多选题未匹配到任何选项', 'orange'); }
} else if (questionType == 2) { fillBlankAnswer($question, ans, false, null); }
else if (questionType == 3) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pdContainer = $question.find('.Zy_ulTop li');
if ($pdContainer.length === 0) { $pdContainer = $question.find('.stem_answer .answer_p').parent(); }
if (ans && ans.trim() !== '' && (trueKeywords.indexOf(ans) != -1 || trueKeywords.toLowerCase().indexOf(ans.toLowerCase()) != -1)) {
var clicked = false;
$pdContainer.each(function(i, t) { var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param'); if (valAttr == 'true') { $(t).click(); clicked = true; log('✅ PC判断题自动选择: 正确', 'green'); } });
if (!clicked && $pdContainer.length > 0) { $($pdContainer[0]).click(); log('✅ PC判断题自动选择: 正确(默认第一个)', 'green'); }
} else {
var clicked2 = false;
$pdContainer.each(function(i, t) { var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param'); if (valAttr == 'false') { $(t).click(); clicked2 = true; log('✅ PC判断题自动选择: 错误', 'green'); } });
if (!clicked2 && $pdContainer.length > 1) { $($pdContainer[1]).click(); log('✅ PC判断题自动选择: 错误(默认第二个)', 'green'); }
else if (!clicked2 && $pdContainer.length === 1) { $($pdContainer[0]).click(); log('✅ PC判断题自动选择: 错误(唯一选项)', 'green'); }
}
} else if (questionType == 4) { fillShortAnswer($question, ans, false, null); }
log('✅ PC第' + (qIndex + 1) + '题自动答题成功', 'green');
}, 'PC第' + (qIndex + 1) + '题');
} else { log('⚠️ PC第' + (qIndex + 1) + '题无法获取答案', 'orange'); if (bumpChapterFail('无答案')) return; }
setTimeout(function() { if (!__cxScriptStopped) processPCQuestions(index, doms, qIndex + 1, questionList, taskObj); }, (getRandomAnswerDelaySec() + getRandomIntervalSec()) * 1000);
}).catch(function(err) {
if (__cxScriptStopped) return;
log('⚠️ PC第' + (qIndex + 1) + '题请求失败: ' + err + ',跳过该题', 'orange');
if (bumpChapterFail('接口失败')) return;
setTimeout(function() { if (!__cxScriptStopped) processPCQuestions(index, doms, qIndex + 1, questionList, taskObj); }, getRandomIntervalSec() * 1000);
});
}
function processHomework() {
if (__cxScriptStopped) return;
resetChapterFailCount();
if (isNoQuotaMode()) {
refreshQuotaFromServer().then(function() { if (__cxScriptStopped) return; if (isNoQuotaMode()) { abortQuizAndNextChapter('作业页配额为0'); return; } doProcessHomework(); });
return;
}
doProcessHomework();
}
function doProcessHomework() {
if (__cxScriptStopped) return;
if (!checkApiKeyBeforeAction('作业自动答题')) { abortQuizAndNextChapter('作业页缺少API密钥'); return; }
var $homeworkTable = $('.mark_table').find('form');
var questionList = $homeworkTable.find('.questionLi');
doHomework(0, questionList);
}
function doHomework(index, questionList) {
if (__cxScriptStopped) return;
if (index == questionList.length) { __cxJumpLock = false; markPendingSubmit(false); log('✅ 作业题目已全部完成', 'green'); return; }
var $question = $(questionList[index]);
var typeName = $question.attr('typename');
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
var questionFull = $question.find('.mark_name').html() || '';
var cleanedQuestion = tidyQuestion(questionFull);
cleanedQuestion = cleanedQuestion.replace(/^[(].*?[)]/, '').trim();
cleanedQuestion = cleanedQuestion.replace(/^\d+[.、]?\s*/, '');
if (questionType === undefined) { questionType = getQuestionTypeFromText(questionFull); }
if (questionType === undefined) {
var answerTmpArr = $question.find('.stem_answer').find('.answer_p');
if (answerTmpArr && answerTmpArr.length > 0) { questionType = $question.find('input[type="checkbox"]').length ? 1 : 0; }
else if ($question.find('.stem_answer').find('.divText textarea').length) { questionType = 4; }
}
if (questionType === undefined) {
log('⚠️ 作业第' + (index + 1) + '题无法识别题型,跳过', 'orange');
if (bumpChapterFail('题型无法识别')) return;
setTimeout(function() { if (!__cxScriptStopped) doHomework(index + 1, questionList); }, getRandomIntervalSec() * 1000);
return;
}
clearSingleQuestionAnswer($question, questionType);
proceedHomework(index, questionList, $question, questionType, cleanedQuestion);
}
function proceedHomework(index, questionList, $question, questionType, cleanedQuestion) {
if (__cxScriptStopped) return;
var $opts = []; var optsText = []; var optionsData = [];
var pureQuestion = cleanedQuestion;
if (questionType == 0 || questionType == 1) {
$opts = $question.find('.stem_answer .answer_p');
$opts.each(function() {
var rawText = tidyString($(this).html());
rawText = rawText.replace(/^[A-Z][\s]*[.、.))\s]+/, '').replace(/^\([A-Z]\)[\s]*/, '').replace(/^([A-Z])[\s]*/, '');
optsText.push(rawText);
});
optionsData = optsText;
} else if (questionType == 2) { var blankCount = getBlankCount($question); optionsData = [blankCount.toString()]; }
else if (questionType == 3) { optionsData = ["对", "错"]; }
else if (questionType == 4) { optionsData = ["1"]; }
getAnswerWithHardTimeout(questionType, pureQuestion, optionsData).then(function(answer) {
if (__cxScriptStopped) return;
if (answer && answer != '暂无答案') {
applyAnswerWithDelay(answer, function(ans) {
if (CONFIG.insertAnswer) { $question.find('.mark_name').html($question.find('.mark_name').html() + '📖 ' + ans + '
'); }
if (questionType == 0) {
var idx = findSingleChoiceIndex(optsText, ans);
if (idx != -1) {
var $target = $($opts[idx]).parent();
$target.click();
setTimeout(function() {
if (($target.find('span').attr('class') || '').indexOf('check_answer') === -1 && ($target.attr('class') || '').indexOf('check_answer') === -1) { $target.click(); }
}, 100);
log('✅ 作业单选题已选: ' + optsText[idx], 'green');
} else { log('⚠️ 未找到匹配选项: ' + ans + ' | 选项: ' + JSON.stringify(optsText), 'orange'); }
} else if (questionType == 1) {
var hitIdx = matchMultiChoiceIndices(optsText, ans);
if (hitIdx.length > 0) {
hitIdx.forEach(function(i, k) {
setTimeout(function() {
var $target = $($opts[i]).parent();
$target.click();
setTimeout(function() {
if (($target.find('span').attr('class') || '').indexOf('check_answer') === -1 && ($target.attr('class') || '').indexOf('check_answer') === -1) { $target.click(); }
}, 100);
}, 300 * (k + 1));
});
log('✅ 作业多选题已选 ' + hitIdx.length + ' 项', 'green');
} else { log('⚠️ 作业多选题未匹配到任何选项', 'orange'); }
} else if (questionType == 2) { fillBlankAnswer($question, ans, false, null); }
else if (questionType == 3) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pdContainer = $question.find('.stem_answer .answer_p').parent();
if (ans && ans.trim() !== '' && (trueKeywords.indexOf(ans) != -1 || trueKeywords.toLowerCase().indexOf(ans.toLowerCase()) != -1)) { if ($pdContainer.length > 0) { $($pdContainer[0]).click(); log('✅ 作业判断题自动选择: 正确', 'green'); } }
else { if ($pdContainer.length > 1) { $($pdContainer[1]).click(); log('✅ 作业判断题自动选择: 错误', 'green'); } else if ($pdContainer.length === 1) { $($pdContainer[0]).click(); log('✅ 作业判断题自动选择: 错误(唯一选项)', 'green'); } }
} else if (questionType == 4) { fillShortAnswer($question, ans, false, null); }
log('✅ 作业第' + (index + 1) + '题自动答题成功', 'green');
}, '作业第' + (index + 1) + '题');
} else { log('⚠️ 作业第' + (index + 1) + '题无法获取答案,跳过', 'orange'); if (bumpChapterFail('无答案')) return; }
setTimeout(function() { if (!__cxScriptStopped) doHomework(index + 1, questionList); }, (getRandomAnswerDelaySec() + getRandomIntervalSec()) * 1000);
}).catch(function(err) {
if (__cxScriptStopped) return;
log('⚠️ 作业第' + (index + 1) + '题请求失败: ' + err + ',跳过该题继续', 'orange');
if (bumpChapterFail('接口失败')) return;
setTimeout(function() { if (!__cxScriptStopped) doHomework(index + 1, questionList); }, getRandomIntervalSec() * 1000);
});
}
function processExam() {
if (__cxScriptStopped) return;
if (isNoQuotaMode()) {
refreshQuotaFromServer().then(function() { if (isNoQuotaMode()) { stopAllScript('考试页检测到配额为0'); return; } doProcessExam(); });
return;
}
doProcessExam();
}
function doProcessExam() {
if (__cxScriptStopped) return;
if (!checkApiKeyBeforeAction('考试自动答题')) { stopAllScript('考试页缺少API密钥'); return; }
var $examTable = $('.mark_table').find('.whiteDiv');
var questionFull = tidyString($examTable.find('h3.mark_name').html().trim());
var typeName = questionFull.match(/[(](.*?),.*?分[)]|$/)[1];
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
var cleanedQuestion = tidyQuestion(questionFull.replace(/[(].*?分[)]/, '').replace(/^\s*/, ''));
cleanedQuestion = cleanedQuestion.replace(/^\d+[.、]?\s*/, '');
var $answerDom = $examTable.find('#submitTest').find('.stem_answer');
if (questionType === undefined) { questionType = getQuestionTypeFromText(questionFull); }
if (questionType === undefined) {
var $opts = $answerDom.find('.clearfix.answerBg .fl.answer_p');
if ($opts.length) { questionType = $answerDom.find('input[type="checkbox"]').length ? 1 : 0; }
else if ($answerDom.find('.Answer .divText .subEditor textarea').length) { questionType = 4; }
}
if (questionType === undefined) { log('⚠️ 考试题目无法识别题型', 'orange'); stopAllScript('考试题型无法识别'); return; }
clearSingleQuestionAnswer($answerDom, questionType);
proceedExam($examTable, $answerDom, questionType, cleanedQuestion);
}
function proceedExam($examTable, $answerDom, questionType, cleanedQuestion) {
if (__cxScriptStopped) return;
var $opts = []; var optsText = []; var optionsData = [];
var pureQuestion = cleanedQuestion;
if (questionType == 0 || questionType == 1) {
$opts = $answerDom.find('.clearfix.answerBg .fl.answer_p');
$opts.each(function() {
var rawText = tidyString($(this).html());
rawText = rawText.replace(/^[A-Z][\s]*[.、.))\s]+/, '').replace(/^\([A-Z]\)[\s]*/, '').replace(/^([A-Z])[\s]*/, '');
optsText.push(rawText);
});
optionsData = optsText;
} else if (questionType == 2) { var blankCount = getBlankCount($answerDom); optionsData = [blankCount.toString()]; }
else if (questionType == 4) { optionsData = ["1"]; }
else if (questionType == 3) { optionsData = ["对", "错"]; }
getAnswerWithHardTimeout(questionType, pureQuestion, optionsData).then(function(answer) {
if (__cxScriptStopped) return;
if (answer && answer != '暂无答案') {
applyAnswerWithDelay(answer, function(ans) {
if (CONFIG.insertAnswer) { $examTable.find('h3.mark_name').html($examTable.find('h3.mark_name').html() + '📖 ' + ans + ''); }
if (questionType == 0) {
var idx = findSingleChoiceIndex(optsText, ans);
if (idx != -1) { $($opts[idx]).parent().click(); log('✅ 考试单选题已选: ' + optsText[idx], 'green'); }
else { log('⚠️ 未找到匹配选项: ' + ans + ' | 选项: ' + JSON.stringify(optsText), 'orange'); }
} else if (questionType == 1) {
var hitIdx = matchMultiChoiceIndices(optsText, ans);
if (hitIdx.length > 0) { hitIdx.forEach(function(i, k) { setTimeout(function() { $($opts[i]).parent().click(); }, 300 * (k + 1)); }); log('✅ 考试多选题已选 ' + hitIdx.length + ' 项', 'green'); }
else { log('⚠️ 考试多选题未匹配到任何选项', 'orange'); }
} else if (questionType == 2) { fillBlankAnswer($answerDom, ans, false, null); }
else if (questionType == 3) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pdContainer = $answerDom.find('.clearfix.answerBg .fl.answer_p').parent();
if (ans && ans.trim() !== '' && (trueKeywords.indexOf(ans) != -1 || trueKeywords.toLowerCase().indexOf(ans.toLowerCase()) != -1)) { if ($pdContainer.length > 0) { $($pdContainer[0]).click(); log('✅ 考试判断题自动选择: 正确', 'green'); } }
else { if ($pdContainer.length > 1) { $($pdContainer[1]).click(); log('✅ 考试判断题自动选择: 错误', 'green'); } else if ($pdContainer.length === 1) { $($pdContainer[0]).click(); log('✅ 考试判断题自动选择: 错误(唯一选项)', 'green'); } }
} else if (questionType == 4) { fillShortAnswer($answerDom, ans, false, null); }
log('✅ 考试自动答题成功', 'green');
}, '考试');
} else { log('⚠️ 考试无法获取答案', 'orange'); stopAllScript('考试无答案'); return; }
setTimeout(function() { if (!__cxScriptStopped) goToNextExam(); }, (getRandomAnswerDelaySec() + getRandomIntervalSec()) * 1000);
}).catch(function(err) {
if (__cxScriptStopped) return;
if (err === 'HARD_TIMEOUT' || err === 'QUOTA_EXHAUSTED' || err === '所有接口均失败') { stopAllScript('考试页接口无返回/不可用'); return; }
stopAllScript('考试页获取答案失败: ' + err);
});
}
function goToNextExam() {
if (__cxScriptStopped) return;
if (CONFIG.autoNextExam) {
var $nextBtn = $('.mark_table .whiteDiv .nextDiv a.jb_btn');
var delay;
if (CONFIG.useRandomInterval) {
var mn = Math.max(1, Math.min(60, parseInt(CONFIG.minInterval) || 3));
var mx = Math.max(mn, Math.min(60, parseInt(CONFIG.maxInterval) || 6));
delay = mn + Math.random() * (mx - mn);
} else { delay = Math.max(1, Math.round((CONFIG.answerInterval || 5000) / 1000)); }
setTimeout(function() { $nextBtn.click(); }, delay * 1000);
log('⏭️ ' + delay.toFixed(1) + '秒后自动跳转下一题', 'blue');
}
}
/********************************* 页面整体字体解密 ****************************************/
function decryptFont() {
var TyprInstance = getTypr();
var md5Instance = getMd5();
var table = getLocalTable();
if (!TyprInstance || !md5Instance || !TyprInstance.U || !table) { setTimeout(function() { decryptFont(); }, 1000); return; }
var $tip = $('style:contains(font-cxsecret)');
if (!$tip.length) return;
try {
var styleText = $tip.text();
var fontBase64 = null;
var match1 = styleText.match(/base64,([\w\W]+?)'/);
if (match1 && match1[1]) fontBase64 = match1[1];
if (!fontBase64) { var match2 = styleText.match(/url\("data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)"\)/); if (match2 && match2[1]) fontBase64 = match2[1]; }
if (!fontBase64) { var match3 = styleText.match(/url\('data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)'\)/); if (match3 && match3[1]) fontBase64 = match3[1]; }
if (!fontBase64) return;
var fontUint8Array = base64ToUint8Array(fontBase64);
var parsedFont = TyprInstance.parse(fontUint8Array);
if (!parsedFont || parsedFont.length === 0) return;
var font = parsedFont[0];
var $secretElements = $('.font-cxsecret');
if ($secretElements.length === 0) return;
var existingChars = new Set();
$secretElements.each(function() { var html = $(this).html(); if (html) { for (var j = 0; j < html.length; j++) existingChars.add(html.charCodeAt(j)); } });
if (existingChars.size === 0) return;
var match = {};
var charCodes = Array.from(existingChars);
log('🔓 发现 ' + charCodes.length + ' 个加密字符,开始本地解密...', 'blue');
for (var idx = 0; idx < charCodes.length; idx++) {
var i = charCodes[idx];
var glyph = TyprInstance.U.codeToGlyph(font, i);
if (!glyph) continue;
var path = TyprInstance.U.glyphToPath(font, glyph);
if (!path) continue;
var pathStr = JSON.stringify(path);
var hash = md5Instance(pathStr);
if (!hash) continue;
var hashKey = hash.slice(24);
var realChar = table[hashKey];
if (realChar !== undefined && realChar !== null && realChar !== 0) match[i] = realChar;
}
var matchCount = Object.keys(match).length;
if (matchCount === 0) return;
$secretElements.each(function() {
var $el = $(this);
var html = $el.html();
if (!html) return;
for (var code in match) {
var encryptedChar = String.fromCharCode(parseInt(code));
var realChar = String.fromCharCode(match[code]);
var escapedChar = encryptedChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
var regex = new RegExp(escapedChar, 'g');
html = html.replace(regex, realChar);
}
if ($el.html() !== html) { $el.html(html); $el.removeClass('font-cxsecret'); }
});
log('✅ 字体解密完成!已还原 ' + matchCount + ' 个字符', 'green');
} catch(e) { log('❌ 字体解密失败: ' + e.message, 'red'); }
}
/********************************* UI(设置/日志双标签页) ****************************************/
/* ---- Apple 风格线性图标(统一 1.7 描边) ---- */
function ic(name, size, color) {
size = size || 14;
color = color || 'currentColor';
var d = {
gear: '',
notes: '',
video: '',
clock: '',
sliders: '',
up: '',
down: '',
check: '',
x: '',
bolt: ''
};
return '';
}
function cardStyle(bg, border) { return 'margin-bottom:12px;background:' + bg + ';padding:10px 14px;border-radius:12px;border:1px solid ' + border + ';'; }
/* ---- 面板小部件 ---- */
function keyRowHTML(id) {
var p = AI_PROVIDERS[id];
return '' +
'' + p.name + '' +
'' +
'' +
'
';
}
function providerOptionsHTML() {
var html = '';
PROVIDER_IDS.forEach(function (id) {
html += '';
});
return html;
}
function getSettingsPanelHTML() {
var answerSec = Math.round((CONFIG.answerInterval || 5000) / 1000);
return `
${ic('gear', 16, '#2c7da0')} 学习通助手
✕
默认接口
${keyRowHTML('deepseek')}${keyRowHTML('doubao')}${keyRowHTML('openai')}
${ic('bolt', 12)} 未接入 API
${ic('gear', 13)} 设置
${ic('notes', 13)} 日志
`;
}
function bindSettingUI(targetDoc) {
var $box = $('#ne-21box', targetDoc);
var boxEl = $box[0];
if (boxEl) {
var savedLeft = GM_getValue('panel_left', null);
var savedTop = GM_getValue('panel_top', null);
if (savedLeft !== null && savedTop !== null) {
var vw = $(targetDoc).width();
var vh = $(targetDoc).height();
var maxLeft = Math.max(0, vw - boxEl.offsetWidth);
var maxTop = Math.max(0, vh - boxEl.offsetHeight);
savedLeft = Math.max(0, Math.min(maxLeft, savedLeft));
savedTop = Math.max(0, Math.min(maxTop, savedTop));
$box.css({ left: savedLeft + 'px', top: savedTop + 'px', right: 'auto' });
}
var $handle = $('#ne-21drag-handle', targetDoc);
var dragging = false, startX = 0, startY = 0, origLeft = 0, origTop = 0;
$handle.off('mousedown.panelDrag').on('mousedown.panelDrag', function(e) {
if ($(e.target).is('span, button, input, a')) return;
dragging = true;
var rect = boxEl.getBoundingClientRect();
origLeft = rect.left; origTop = rect.top;
startX = e.clientX; startY = e.clientY;
$box.css({ left: origLeft + 'px', top: origTop + 'px', right: 'auto' });
e.preventDefault();
});
$(targetDoc).off('mousemove.panelDrag mouseup.panelDrag')
.on('mousemove.panelDrag', function(e) {
if (!dragging) return;
var dx = e.clientX - startX, dy = e.clientY - startY;
var newLeft = origLeft + dx, newTop = origTop + dy;
var vw = $(targetDoc).width(), vh = $(targetDoc).height();
newLeft = Math.max(0, Math.min(vw - boxEl.offsetWidth, newLeft));
newTop = Math.max(0, Math.min(vh - boxEl.offsetHeight, newTop));
$box.css({ left: newLeft + 'px', top: newTop + 'px' });
})
.on('mouseup.panelDrag', function() {
if (!dragging) return;
dragging = false;
var rect = boxEl.getBoundingClientRect();
GM_setValue('panel_left', Math.round(rect.left));
GM_setValue('panel_top', Math.round(rect.top));
});
}
// ===== 标签切换 =====
var $tabs = $('.ne21-tab', targetDoc);
var $tabSettings = $('#ne-21-tab-settings', targetDoc);
var $tabLogs = $('#ne-21-tab-logs', targetDoc);
function switchTab(tabName) {
$tabs.each(function() {
var $t = $(this);
if ($t.data('tab') === tabName) {
$t.addClass('ne21-tab-active').css({ color: '#2c7da0', borderBottomColor: '#2c7da0' });
} else {
$t.removeClass('ne21-tab-active').css({ color: '#8b9eb0', borderBottomColor: 'transparent' });
}
});
if (tabName === 'settings') {
$tabSettings.show(); $tabLogs.hide();
} else {
$tabSettings.hide(); $tabLogs.show();
flushLogs();
var $logEl = $('#ne-21log', targetDoc);
if ($logEl.length) $logEl.scrollTop(0);
}
GM_setValue('panel_active_tab', tabName);
}
$tabs.off('click.ne21tab').on('click.ne21tab', function() { switchTab($(this).data('tab')); });
var savedTab = GM_getValue('panel_active_tab', 'settings');
if (savedTab !== 'settings' && savedTab !== 'logs') savedTab = 'settings';
switchTab(savedTab);
// ===== 标签切换结束 =====
$('#ne-21close', targetDoc).off('click').on('click', function() { $('#ne-21box', targetDoc).css('display', 'none'); isBoxHidden = true; });
var moreBtn = targetDoc.getElementById('moreSettingsBtn');
var moreSet = targetDoc.getElementById('moreSettings');
var visible = false;
if (moreBtn) {
moreBtn.onclick = function() {
moreSet.style.display = visible ? 'none' : 'block';
moreBtn.innerHTML = visible ? ic('gear', 13) + ' 更多设置' : ic('x', 13) + ' 关闭设置';
visible = !visible;
};
}
/* ---------- AI 接口:三个密钥口,接一个锁其余 ---------- */
function refreshKeyInputs() {
var configured = configuredProviders();
PROVIDER_IDS.forEach(function (id) {
var inp = targetDoc.getElementById('apiKey_' + id);
if (!inp) return;
var has = !!providerKey(id);
var locked = configured.length > 0 && !has;
inp.value = '';
inp.placeholder = has ? '已接入 · 留空不改' : (AI_PROVIDERS[id].name + ' 密钥');
inp.disabled = locked;
inp.style.background = locked ? '#efeff2' : '#fff';
inp.style.color = locked ? '#b0b0b5' : '#1c1c1e';
var btn = targetDoc.getElementById('clearKey_' + id);
if (btn) { btn.disabled = !has; btn.style.opacity = has ? '1' : '0.45'; }
});
if (configured.length === 1) setActiveProvider(configured[0]);
var sel = targetDoc.getElementById('aiProviderSelect');
if (sel) sel.value = activeProviderId();
updateQuotaDisplay();
}
var providerSel = targetDoc.getElementById('aiProviderSelect');
if (providerSel) providerSel.onchange = function() {
setActiveProvider(this.value);
log('默认接口:' + activeProvider().name, 'blue');
refreshKeyInputs();
};
PROVIDER_IDS.forEach(function (id) {
var inp = targetDoc.getElementById('apiKey_' + id);
if (inp) {
inp.addEventListener('change', function() {
var v = (this.value || '').trim();
if (!v) return;
GM_setValue(AI_PROVIDERS[id].keyName, v);
log('🔑 ' + AI_PROVIDERS[id].name + ' 密钥已保存', 'green');
var tr = targetDoc.getElementById('testResult');
if (tr) { tr.textContent = AI_PROVIDERS[id].name + ' 已接入'; setTimeout(function() { if (tr) tr.textContent = ''; }, 3000); }
refreshKeyInputs();
});
inp.addEventListener('keydown', function(e) { if (e.key === 'Enter') this.blur(); });
}
var clr = targetDoc.getElementById('clearKey_' + id);
if (clr) clr.onclick = function(e) {
e.preventDefault();
GM_setValue(AI_PROVIDERS[id].keyName, '');
log('🧹 已清除 ' + AI_PROVIDERS[id].name + ' 密钥,三个接口都可重新输入', 'orange');
refreshKeyInputs();
};
});
var goToTkBtn = targetDoc.getElementById('goToTkBtn');
if (goToTkBtn) { goToTkBtn.onclick = function() { GM_openInTab(activeProvider().consoleUrl, { active: true }); }; }
var refreshQuotaBtn = targetDoc.getElementById('refreshQuotaBtn');
if (refreshQuotaBtn) {
refreshQuotaBtn.onclick = function() {
if (!getApiKey()) { log('❌ 当前接口还没接入密钥', 'red'); return; }
var $testResult = $('#testResult', targetDoc);
refreshQuotaBtn.disabled = true;
refreshQuotaBtn.textContent = '测试中...';
refreshQuotaBtn.style.opacity = '0.6';
if ($testResult.length) $testResult.html('⏳ 正在请求 ' + activeProvider().name + '…').css('color', '#666');
testApi(getApiKey(), 0).then(function() {
refreshQuotaBtn.disabled = false;
refreshQuotaBtn.textContent = '测试';
refreshQuotaBtn.style.opacity = '1';
updateQuotaDisplay();
log('✅ ' + activeProvider().name + ' 可用(模型 ' + dsModel() + ')', 'green');
if ($testResult.length) $testResult.html('✅ 可用(' + activeProvider().name + ' · ' + dsModel() + ')').css('color', 'green');
setTimeout(function() { if ($testResult.length) $testResult.html(''); }, 4000);
}).catch(function(err) {
refreshQuotaBtn.disabled = false;
refreshQuotaBtn.textContent = '测试';
refreshQuotaBtn.style.opacity = '1';
log('❌ ' + activeProvider().name + ' 不可用: ' + err, 'red');
if ($testResult.length) { $testResult.html('❌ ' + err).css('color', 'red'); setTimeout(function() { $testResult.html(''); }, 6000); }
});
};
}
refreshKeyInputs();
var minInput = targetDoc.getElementById('minIntervalInput');
if (minInput) minInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v) || v < 1) v = 1;
if (v > 60) { v = 60; log('⚠️ 最小间隔超过60秒,已截断为60', 'orange'); }
e.target.value = v;
updateConfig('minInterval', v); SettingsManager.set('minInterval', v);
if (v > CONFIG.maxInterval) { CONFIG.maxInterval = v; SettingsManager.set('maxInterval', v); var mx = targetDoc.getElementById('maxIntervalInput'); if (mx) mx.value = v; log('⚠️ 最小值超过最大值,已同步最大值', 'orange'); }
});
var maxInput = targetDoc.getElementById('maxIntervalInput');
if (maxInput) maxInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v) || v < 1) v = 1;
if (v > 60) { v = 60; log('⚠️ 最大间隔超过60秒,已截断为60', 'orange'); }
e.target.value = v;
updateConfig('maxInterval', v); SettingsManager.set('maxInterval', v);
if (v < CONFIG.minInterval) { CONFIG.minInterval = v; SettingsManager.set('minInterval', v); var mn = targetDoc.getElementById('minIntervalInput'); if (mn) mn.value = v; log('⚠️ 最大值小于最小值,已同步最小值', 'orange'); }
});
var useRandomCb = targetDoc.getElementById('setting_useRandomInterval');
if (useRandomCb) useRandomCb.addEventListener('change', function() {
var v = this.checked ? 1 : 0;
updateConfig('useRandomInterval', v); SettingsManager.set('useRandomInterval', v);
log('🎲 题间节奏随机: ' + (v ? '启用' : '禁用'), 'blue');
});
var intervalSecInput = targetDoc.getElementById('answerIntervalSecInput');
if (intervalSecInput) intervalSecInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v) || v < 1) v = 1;
if (v > 60) { v = 60; log('⚠️ 固定间隔超过60秒,已截断为60', 'orange'); }
e.target.value = v;
updateConfig('answerInterval', v * 1000); SettingsManager.set('answerInterval', v * 1000);
log('⏱️ 固定间隔已设为 ' + v + ' 秒', 'blue');
});
var $ansDelayEnabled = $('#setting_answerDelayEnabled', targetDoc);
if ($ansDelayEnabled.length) $ansDelayEnabled.off('change').on('change', function() { updateConfig('answerDelayEnabled', this.checked ? 1 : 0); log('⏳ 作答延迟: ' + (this.checked ? '开' : '关'), 'blue'); });
var ansMinInput = targetDoc.getElementById('answerDelayMinInput');
if (ansMinInput) ansMinInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v)) v = 3;
if (v < 3) { v = 3; log('⚠️ 作答延迟最小 <3 秒,已截断为 3', 'orange'); }
if (v > 30) { v = 30; log('⚠️ 作答延迟最小 >30 秒,已截断为 30', 'orange'); }
e.target.value = v;
updateConfig('answerDelayMin', v); SettingsManager.set('answerDelayMin', v);
if (v > CONFIG.answerDelayMax) { CONFIG.answerDelayMax = v; SettingsManager.set('answerDelayMax', v); var mx = targetDoc.getElementById('answerDelayMaxInput'); if (mx) mx.value = v; log('⚠️ 作答延迟最小值超过最大值,已同步最大值', 'orange'); }
});
var ansMaxInput = targetDoc.getElementById('answerDelayMaxInput');
if (ansMaxInput) ansMaxInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v)) v = 10;
if (v < 3) { v = 3; log('⚠️ 作答延迟最大 <3 秒,已截断为 3', 'orange'); }
if (v > 30) { v = 30; log('⚠️ 作答延迟最大 >30 秒,已截断为 30', 'orange'); }
e.target.value = v;
updateConfig('answerDelayMax', v); SettingsManager.set('answerDelayMax', v);
if (v < CONFIG.answerDelayMin) { CONFIG.answerDelayMin = v; SettingsManager.set('answerDelayMin', v); var mn = targetDoc.getElementById('answerDelayMinInput'); if (mn) mn.value = v; log('⚠️ 作答延迟最大值小于最小值,已同步最小值', 'orange'); }
});
var $jumpDelayEnabled = $('#setting_jumpDelayEnabled', targetDoc);
if ($jumpDelayEnabled.length) $jumpDelayEnabled.off('change').on('change', function() { updateConfig('jumpDelayEnabled', this.checked ? 1 : 0); log('⏭️ 章节跳转延迟: ' + (this.checked ? '开' : '关(立即跳转)'), 'blue'); });
var jumpMinInput = targetDoc.getElementById('jumpDelayMinInput');
if (jumpMinInput) jumpMinInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v)) v = 10;
if (v < 3) { v = 3; log('⚠️ 跳转延迟最小 <3 秒,已截断为 3', 'orange'); }
if (v > 60) { v = 60; log('⚠️ 跳转延迟最小 >60 秒,已截断为 60', 'orange'); }
e.target.value = v;
updateConfig('jumpDelayMin', v); SettingsManager.set('jumpDelayMin', v);
if (v > CONFIG.jumpDelayMax) { CONFIG.jumpDelayMax = v; SettingsManager.set('jumpDelayMax', v); var mx = targetDoc.getElementById('jumpDelayMaxInput'); if (mx) mx.value = v; log('⚠️ 跳转延迟最小值超过最大值,已同步最大值', 'orange'); }
});
var jumpMaxInput = targetDoc.getElementById('jumpDelayMaxInput');
if (jumpMaxInput) jumpMaxInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v)) v = 30;
if (v < 3) { v = 3; log('⚠️ 跳转延迟最大 <3 秒,已截断为 3', 'orange'); }
if (v > 60) { v = 60; log('⚠️ 跳转延迟最大 >60 秒,已截断为 60', 'orange'); }
e.target.value = v;
updateConfig('jumpDelayMax', v); SettingsManager.set('jumpDelayMax', v);
if (v < CONFIG.jumpDelayMin) { CONFIG.jumpDelayMin = v; SettingsManager.set('jumpDelayMin', v); var mn = targetDoc.getElementById('jumpDelayMinInput'); if (mn) mn.value = v; log('⚠️ 跳转延迟最大值小于最小值,已同步最小值', 'orange'); }
});
var rateInput = targetDoc.getElementById('videoRateInput');
var rateMinus = targetDoc.getElementById('videoRateMinus');
var ratePlus = targetDoc.getElementById('videoRatePlus');
function applyVideoRate(newRate, fromUI) {
newRate = clampVideoRate(newRate);
CONFIG.videoRate = newRate;
SettingsManager.saveVideoRate(newRate);
_cxRateValue = newRate;
try { (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).__cxRateValue = newRate; } catch(e) {}
if (rateInput && (!fromUI || fromUI !== 'input')) { rateInput.value = newRate; }
try {
document.querySelectorAll('iframe').forEach(function(f) {
var d = f.contentDocument || (f.contentWindow && f.contentWindow.document);
if (!d) return;
var media = d.querySelector('video') || d.querySelector('audio');
if (media) {
media.__cxShouldPlay = true;
if (typeof unsafeWindow.__cxForceRate === 'function') { unsafeWindow.__cxForceRate(media, newRate); }
else { media.playbackRate = newRate; }
log('📹 视频倍速已实时改为: ' + newRate + 'x', 'green');
}
});
} catch(e) {}
try {
var topMedia = targetDoc.querySelector('video, audio');
if (topMedia) {
topMedia.__cxShouldPlay = true;
if (typeof unsafeWindow.__cxForceRate === 'function') { unsafeWindow.__cxForceRate(topMedia, newRate); }
else { topMedia.playbackRate = newRate; }
}
} catch(e) {}
return newRate;
}
if (rateInput) {
rateInput.addEventListener('change', function() { var v = applyVideoRate(this.value); this.value = v; });
rateInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { var v = applyVideoRate(this.value); this.value = v; this.blur(); } });
}
if (rateMinus) rateMinus.addEventListener('click', function() { var cur = parseFloat(rateInput ? rateInput.value : CONFIG.videoRate) || 1; var v = applyVideoRate(cur - VIDEO_RATE_STEP); if (rateInput) rateInput.value = v; });
if (ratePlus) ratePlus.addEventListener('click', function() { var cur = parseFloat(rateInput ? rateInput.value : CONFIG.videoRate) || 1; var v = applyVideoRate(cur + VIDEO_RATE_STEP); if (rateInput) rateInput.value = v; });
var checkboxes = ['handleVideoQuiz', 'autoSubmit', 'forceSubmit', 'autoNextExam', 'insertAnswer', 'redoAnswered'];
checkboxes.forEach(function(id) {
var $cb = $('#setting_' + id, targetDoc);
if (!$cb.length) return;
$cb.off('change').on('change', function() {
var checked = this.checked;
updateConfig(id, checked);
if (id === 'handleVideoQuiz') {
if (checked) {
try {
document.querySelectorAll('iframe').forEach(function(f) {
var d = f.contentDocument || (f.contentWindow && f.contentWindow.document);
if (d && d.body && d.querySelector('video')) { observeVideoQuiz(d); startQuizPoll(d); startQuizObserverGuard(); }
});
log('👀 视频弹题监听已开启', 'green');
} catch(e) {}
} else {
stopQuizForAllMedia();
stopQuizObserverGuard();
log('👀 视频弹题监听已关闭', 'blue');
}
}
if (id === 'redoAnswered') { log('♻️ 覆盖重答: ' + (checked ? '开启' : '关闭'), 'blue'); }
if (id === 'autoSubmit' && checked && CONFIG.forceSubmit) { CONFIG.forceSubmit = false; SettingsManager.set('forceSubmit', false); $('#setting_forceSubmit', targetDoc).prop('checked', false); log('⚙️ 自动提交与强制提交互斥,已关闭强制提交', 'blue'); }
if (id === 'forceSubmit' && checked && CONFIG.autoSubmit) { CONFIG.autoSubmit = false; SettingsManager.set('autoSubmit', false); $('#setting_autoSubmit', targetDoc).prop('checked', false); log('⚙️ 强制提交与自动提交互斥,已关闭自动提交', 'blue'); }
});
});
var failLimitInput = targetDoc.getElementById('failLimitInput');
if (failLimitInput) failLimitInput.addEventListener('change', function(e) {
var v = parseInt(e.target.value);
if (isNaN(v) || v < 1) v = 1;
if (v > 10) v = 10;
e.target.value = v;
updateConfig('failLimit', v);
});
var failRadios = targetDoc.getElementsByName('setting_failAction');
failRadios.forEach(function(radio) {
radio.addEventListener('change', function() { if (this.checked) { updateConfig('failAction', this.value); } });
});
}
function showBox() {
if (!CONFIG.showBox) return;
try {
var targetDoc = top.document;
if (targetDoc.querySelector('#ne-21box')) { flushLogs(); quotaInfo = getQuotaInfo(); updateQuotaDisplay(); return; }
if (!targetDoc.body) { setTimeout(showBox, 300); return; }
} catch(e) { setTimeout(showBox, 300); return; }
setTimeout(function() {
try {
var targetDoc = top.document;
if (!targetDoc.body) { setTimeout(showBox, 500); return; }
var boxHtml = getSettingsPanelHTML();
$(targetDoc.body).append(boxHtml);
bindSettingUI(targetDoc);
updateQuotaDisplay();
var savedKey = getApiKey();
if (savedKey) {
testApi(savedKey, currentServerIndex).then(function(res) {
log('✅ ' + activeProvider().name + ' 就绪(' + dsModel() + ')', 'green');
updateQuotaDisplay();
}).catch(function(e) {
log('⚠️ ' + activeProvider().name + ' 暂不可用:' + e, 'orange');
});
}
flushLogs();
showPikaqiu();
} catch(e) { console.error('显示悬浮窗失败:', e); }
}, 100);
}
/********************************* 初始化入口 ****************************************/
$(function() {
var savedServerIndex = GM_getValue('current_server_index', 0);
if (savedServerIndex >= 0 && savedServerIndex < API_SERVERS.length) currentServerIndex = savedServerIndex;
CONFIG.videoMode = GM_getValue('videoMode', CONFIG.videoMode);
CONFIG.videoRate = clampVideoRate(GM_getValue('videoRate', CONFIG.videoRate));
CONFIG.showProgressBar = GM_getValue('showProgressBar', CONFIG.showProgressBar);
CONFIG.minInterval = parseInt(GM_getValue('setting_minInterval', 3)) || 3;
CONFIG.maxInterval = parseInt(GM_getValue('setting_maxInterval', 6)) || 6;
CONFIG.useRandomInterval = GM_getValue('setting_useRandomInterval', 1) ? 1 : 0;
if (CONFIG.minInterval < 1) CONFIG.minInterval = 1;
if (CONFIG.maxInterval > 60) CONFIG.maxInterval = 60;
if (CONFIG.minInterval > CONFIG.maxInterval) CONFIG.minInterval = CONFIG.maxInterval;
clampAnswerDelay(); clampJumpDelay();
_cxRateValue = CONFIG.videoRate;
try { (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).__cxRateValue = CONFIG.videoRate; } catch(e) {}
initPlayerUnlock();
bindKeydown();
var settingsMap = { 'autoSubmit': 'autoSubmit', 'forceSubmit': 'forceSubmit', 'autoNextExam': 'autoNextExam', 'insertAnswer': 'insertAnswer', 'handleVideoQuiz': 'handleVideoQuiz', 'redoAnswered': 'redoAnswered' };
for (var key in settingsMap) {
var val = GM_getValue('setting_' + key, null);
if (val === null) val = localStorage.getItem('setting_' + key);
if (val !== null && val !== undefined) { CONFIG[settingsMap[key]] = (val === 'true' || val === true || val === 1 || val === '1'); }
}
$('.navshow').find('a:contains(体验新版)')[0] && $('.navshow').find('a:contains(体验新版)')[0].click();
if (CONFIG.decryptFont) { setTimeout(function() { decryptFont(); }, 1000); setTimeout(function() { if ($('.font-cxsecret').length > 0) decryptFont(); }, 8000); }
if (LOCATION.pathname == '/login' && CONFIG.autoLogin) {
showBox();
waitForElement('#phone').then(function() { $('#phone').val(CONFIG.loginPhone); $('#pwd').val(CONFIG.loginPassword); $('#loginBtn').click(); });
} else if (LOCATION.pathname.includes('/mycourse/studentstudy')) {
showBox();
log('✅ 初始化完毕!', 'green');
log('🎲 题间节奏: ' + (CONFIG.useRandomInterval ? '随机 ' + CONFIG.minInterval + '~' + CONFIG.maxInterval + 's' : '固定 ' + Math.round(CONFIG.answerInterval / 1000) + 's') + ' | ⏳ 作答延迟: ' + (CONFIG.answerDelayEnabled ? CONFIG.answerDelayMin + '~' + CONFIG.answerDelayMax + 's' : '关') + ' | ⏭️ 章节跳转: ' + (CONFIG.jumpDelayEnabled ? CONFIG.jumpDelayMin + '~' + CONFIG.jumpDelayMax + 's' : '关(立即跳转)') + ' | ♻️ 覆盖重答: ' + (CONFIG.redoAnswered ? '开' : '关'), 'blue');
} else if (LOCATION.pathname.includes('/knowledge/cards')) {
showBox();
var params = getTaskParams();
if (!params || params == '$mArg') {
log('⚠️ 无任务点可处理', 'red');
if (!hasTriggeredNoTaskJump) {
hasTriggeredNoTaskJump = true;
log('🔄 检测到无任务点,尝试跳转到下一章节...', 'blue');
setTimeout(function() {
var nextBtn = top.document.querySelector('#mainid > .prev_next.next:not(.disabled)') || top.document.querySelector('#prevNextFocusNext:not(.disabled)') || top.document.querySelector('.prev_next.next:not(.disabled)') || $('.prev_next.next:not(.disabled)')[0];
if (nextBtn && !nextBtn.disabled && !nextBtn.classList.contains('disabled')) { log('✅ 找到下一节按钮,即将跳转', 'green'); nextBtn.click(); }
else { log('📚 已是最后一章,无更多章节', 'green'); }
}, 2000);
}
return;
}
try {
var allTasks = $.parseJSON(params)['attachments'];
if (!allTasks || allTasks.length <= 0) {
log('⚠️ 无任务点可处理', 'red');
if (!hasTriggeredNoTaskJump) {
hasTriggeredNoTaskJump = true;
setTimeout(function() {
var nextBtn = top.document.querySelector('#mainid > .prev_next.next:not(.disabled)') || top.document.querySelector('#prevNextFocusNext:not(.disabled)') || top.document.querySelector('.prev_next.next:not(.disabled)') || $('.prev_next.next:not(.disabled)')[0];
if (nextBtn && !nextBtn.disabled && !nextBtn.classList.contains('disabled')) { nextBtn.click(); }
}, 2000);
}
return;
}
} catch(e) { log('⚠️ 解析任务参数失败', 'red'); return; }
waitForElement('.wrap .ans-cc .ans-attach-ct').then(function() {
if (top.checkJob) top.checkJob = function() { return false; };
var allTasks = $.parseJSON(params)['attachments'];
defaults = $.parseJSON(params)['defaults'];
domList = [];
var pendingTasks = [];
for (var i = 0; i < allTasks.length; i++) {
var task = allTasks[i];
if (!isTaskCompleted(task)) pendingTasks.push(task);
else { var taskName = task.property ? (task.property.name || task.property.title || '未知') : '未知'; log('✅ 任务点 ' + taskName + ' 已完成,跳过', 'green'); }
}
totalMissionCount = allTasks.length;
pendingMissionCount = pendingTasks.length;
completedMissionCount = 0;
if (pendingMissionCount === 0) { log('✅ 所有任务点已完成', 'green'); setTimeout(function() { goToNext(); }, 2000); return; }
log('📋 发现 ' + totalMissionCount + ' 个任务点,其中 ' + pendingMissionCount + ' 个待处理', 'green');
taskList = [];
var newDomList = [];
$('.wrap .ans-cc .ans-attach-ct').each(function(i, t) {
if (i < allTasks.length && !isTaskCompleted(allTasks[i])) { taskList.push(allTasks[i]); newDomList.push($(t).find('iframe')); }
});
domList = newDomList;
if (taskList.length > 0) { log('🚀 开始处理 ' + taskList.length + ' 个待完成任务点', 'green'); startMission(); }
else { log('✅ 所有任务点已完成', 'green'); setTimeout(function() { goToNext(); }, 2000); }
}).catch(function() { log('❌ 等待元素超时', 'red'); if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(function() { goToNext(); }, 2000); } });
} else if (LOCATION.pathname.includes('/exam/test/reVersionTestStartNew')) {
showBox();
waitForElement('.mark_table .whiteDiv').then(function() { processExam(); });
} else if (LOCATION.pathname.includes('/mooc2/work/dowork')) {
showBox();
waitForElement('.mark_table form').then(function() { processHomework(); });
} else if (LOCATION.pathname.includes('/work/phone/doHomeWork')) {
var oldAlert = UNSAFE_WIN.alert;
UNSAFE_WIN.alert = function(msg) { if (msg == '保存成功') return; return oldAlert(msg); };
var oldConfirm = UNSAFE_WIN.confirm;
UNSAFE_WIN.confirm = function(msg) { if (msg.includes('确认提交') || msg.includes('未做完')) return true; return oldConfirm(msg); };
}
});
/********************************* 全局变量声明 ****************************************/
var taskList = null;
var defaults = null;
var domList = null;
var submitBtn = null;
var saveBtn = null;
var frameContent = null;
var okBtn = null;
var isProcessing = false;
var isBoxHidden = false;
var pikaqiuAdded = false;
var pendingMissionCount = 0;
var completedMissionCount = 0;
var isJumping = false;
var currentProcessingTask = null;
var totalMissionCount = 0;
var currentVideoInterval = null;
var hasTriggeredNoTaskJump = false;