// ==UserScript==
// @name BZM超星学习通挂机助手
// @version 4.3.7
// @author bzm
// @description 后台任务,不占用宽带,学习通助手,支持视频弹题、视频,作业、考试自动答题,免费高分题库。后台任务版,不占用宽带。后台上报,实时进度条。反检测增强。修复多选题匹配。弹题随机兜底。面板可拖动。
// @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://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js
// @require https://pkpkq.n1t.cn/font-decrypt/TyprMd5.js
// @resource Table https://pkpkq.n1t.cn/font-decrypt/Table.json
// @connect tk.swk.tw
// @connect n1t.cn
// @tag 免费搜题
// @tag 可选付费
// ==/UserScript==
/********************************* 反检测层 ****************************************/
(function() {
'use strict';
try {
Object.defineProperty(document, 'visibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(document, 'hidden', { get: function() { return false; }, configurable: true });
Object.defineProperty(document, 'webkitVisibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(document, 'webkitHidden', { get: function() { return false; }, configurable: true });
document.addEventListener('visibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
document.addEventListener('webkitvisibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
} catch(e) {}
try {
window.requestAnimationFrame = function(cb) { return setTimeout(function() { cb(Date.now()); }, 16); };
window.cancelAnimationFrame = function(id) { clearTimeout(id); };
} catch(e) {}
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 {
document.hasFocus = function() { return true; };
window.onblur = null;
window.onfocus = null;
['blur', 'focus', 'pagehide', 'pageshow'].forEach(function(evt) {
window.addEventListener(evt, function(e) { e.stopImmediatePropagation(); }, true);
});
} catch(e) {}
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) {}
try {
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;
}
} catch(e) {}
try {
var _origAppendChild = Node.prototype.appendChild;
Node.prototype.appendChild = function(node) {
var result = _origAppendChild.call(this, node);
try {
if (node && node.tagName === 'IFRAME') {
setTimeout(function() { patchIframe(node); }, 500);
node.addEventListener('load', function() { patchIframe(node); });
}
} catch(e) {}
return result;
};
function keepAliveIframeWindow(win) {
try {
if (!win || !win.document) return;
Object.defineProperty(win.document, 'visibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(win.document, 'hidden', { get: function() { return false; }, configurable: true });
Object.defineProperty(win.document, 'webkitVisibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(win.document, 'webkitHidden', { get: function() { return false; }, configurable: true });
win.document.hasFocus = function() { return true; };
win.document.addEventListener('visibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
win.document.addEventListener('webkitvisibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
['blur', 'focus', 'pagehide', 'pageshow'].forEach(function(evt) {
win.document.addEventListener(evt, function(e) { e.stopImmediatePropagation(); }, true);
});
if (win.navigator) {
try { Object.defineProperty(win.navigator, 'webdriver', { get: function() { return undefined; }, configurable: true }); } catch(e) {}
}
} catch(e) {}
}
function patchIframe(iframe) {
try {
var d = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
if (!d) return;
keepAliveIframeWindow(iframe.contentWindow);
if (d.__antiDetectPatched) return;
d.__antiDetectPatched = true;
Object.defineProperty(d, 'visibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(d, 'hidden', { get: function() { return false; }, configurable: true });
Object.defineProperty(d, 'webkitVisibilityState', { get: function() { return 'visible'; }, configurable: true });
Object.defineProperty(d, 'webkitHidden', { get: function() { return false; }, configurable: true });
d.hasFocus = function() { return true; };
d.addEventListener('visibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
d.addEventListener('webkitvisibilitychange', function(e) { e.stopImmediatePropagation(); }, true);
['blur', 'focus', 'pagehide', 'pageshow'].forEach(function(evt) {
d.addEventListener(evt, function(e) { e.stopImmediatePropagation(); }, true);
});
try {
if (d.defaultView && d.defaultView.navigator) {
Object.defineProperty(d.defaultView.navigator, 'webdriver', { get: function() { return undefined; }, configurable: true });
}
} catch(e) {}
} catch(e) {}
}
setTimeout(function() { document.querySelectorAll('iframe').forEach(patchIframe); }, 1000);
} catch(e) {}
console.log('[学习通助手] 反检测层已加载');
})();
/********************************* 配置管理器 ****************************************/
var SettingsManager = {
_defaults: {
showBox: 1,
showMaskImg: 1,
taskOnly: 0,
handleVideo: 1,
handleAudio: 1,
videoRate: 1,
reviewMode: 0,
handleQuiz: 1,
handleVideoQuiz: 1,
answerInterval: 5000,
autoSubmit: 0,
forceSubmit: 0,
decryptFont: 1,
autoNextExam: 0,
examNextDelay: 1,
insertAnswer: 1,
autoLogin: 0,
loginPhone: '',
loginPassword: '',
videoMode: 'simulate',
reportBaseInterval: 58,
showProgressBar: 1
},
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;
}
if (this._defaults.videoRate > 8) this._defaults.videoRate = 8;
return this._defaults;
},
saveVideoMode: function(mode) { this.set('videoMode', mode); GM_setValue('videoMode', mode); },
saveVideoRate: function(rate) { if (rate > 8) rate = 8; 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();
if (CONFIG.videoRate > 8) CONFIG.videoRate = 8;
function updateConfig(key, value) {
var oldValue = CONFIG[key];
CONFIG[key] = SettingsManager.set(key, value);
if (key === 'videoRate' && CONFIG.videoRate > 8) { CONFIG.videoRate = 8; SettingsManager.set('videoRate', 8); }
log('⚙️ 配置已更新: ' + key + ' = ' + value + (oldValue !== value ? ' (已生效)' : ''), 'blue');
}
/**************************************************************************************************/
var UNSAFE_WIN = unsafeWindow;
var LOCATION = location;
var DOCUMENT = UNSAFE_WIN.document;
var $ = UNSAFE_WIN.jQuery || top.jQuery;
var UE = UNSAFE_WIN.UE;
var MD5 = window.md5 || $.md5;
var TyprInstance = null;
try {
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) TyprInstance = unsafeWindow.Typr;
else if (typeof window !== 'undefined' && window.Typr) TyprInstance = window.Typr;
else if (typeof Typr !== 'undefined') TyprInstance = Typr;
else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) TyprInstance = unsafeWindow.TyprMd5;
else if (typeof TyprMd5 !== 'undefined') TyprInstance = TyprMd5;
} catch(e) {}
function cleanOptionText(text) {
if (!text) return text;
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\-_]+/, '');
return cleaned.trim();
}
function getCookie(name) { return ('; ' + document.cookie).split('; ' + name + '=').pop().split(';')[0]; }
function formatDuration(seconds) {
if (!seconds || seconds < 0) return '00:00';
var total = Math.max(0, Math.floor(Number(seconds) || 0));
var hours = Math.floor(total / 3600);
var minutes = Math.floor((total % 3600) / 60);
var secs = total % 60;
var pad = function(n) { return String(n).padStart(2, '0'); };
if (hours > 0) return pad(hours) + ':' + pad(minutes) + ':' + pad(secs);
return pad(minutes) + ':' + pad(secs);
}
function tidyString(s) {
if (!s) return null;
var cleaned = s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').trim().replace(/ /g, '');
return cleanOptionText(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 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;
}
var logQueue = [];
var logTimer = null;
function getTopLogContainer() {
try {
var topDoc = top.document;
var container = topDoc.getElementById('ne-21log');
if (container) return $(container);
container = document.getElementById('ne-21log');
if (container) return $(container);
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);
}
var API_SERVERS = [
{ url: "https://tk.n1t.cn/api/search.php", name: "接口1" },
{ url: "https://tk.swk.tw/api/search.php", name: "接口2" }
];
var currentServerIndex = 0;
function getApiKey() { return GM_getValue('api_key', ''); }
function hasApiKey() { var key = getApiKey(); return key && key.trim() !== ''; }
function getQuotaInfo() {
return {
remaining: GM_getValue('quota_remaining', 0),
freeRemaining: GM_getValue('quota_free_remaining', 0),
rechargeBalance: GM_getValue('quota_recharge_balance', 0),
todayFreeUsage: GM_getValue('quota_today_free_usage', 0)
};
}
var quotaInfo = getQuotaInfo();
function saveQuotaInfo() {
GM_setValue('quota_remaining', quotaInfo.remaining);
GM_setValue('quota_free_remaining', quotaInfo.freeRemaining);
GM_setValue('quota_recharge_balance', quotaInfo.rechargeBalance);
GM_setValue('quota_today_free_usage', quotaInfo.todayFreeUsage);
}
function updateQuotaInfo(newQuotaInfo) {
if (newQuotaInfo) {
quotaInfo.remaining = newQuotaInfo.remaining !== undefined ? newQuotaInfo.remaining : quotaInfo.remaining;
quotaInfo.freeRemaining = newQuotaInfo.free_remaining !== undefined ? newQuotaInfo.free_remaining : quotaInfo.freeRemaining;
quotaInfo.rechargeBalance = newQuotaInfo.recharge_balance !== undefined ? newQuotaInfo.recharge_balance : quotaInfo.rechargeBalance;
quotaInfo.todayFreeUsage = newQuotaInfo.today_free_usage !== undefined ? newQuotaInfo.today_free_usage : quotaInfo.todayFreeUsage;
saveQuotaInfo();
updateQuotaDisplay();
}
}
function updateQuotaDisplay() {
try {
var targetDoc = top.document;
if (!targetDoc.querySelector('#quotaInfo')) targetDoc = document;
var $quotaInfo = $('#quotaInfo', targetDoc);
if ($quotaInfo.length) {
var remaining = quotaInfo.remaining;
var hasKey = hasApiKey();
if (remaining > 0) $quotaInfo.html('📊 剩余次数: ' + remaining + ' | 免费: ' + quotaInfo.freeRemaining);
else if (remaining === 0 && hasKey) $quotaInfo.html('⚠️ 次数已用完 | 点击充值');
else if (!hasKey) $quotaInfo.html('⚠️ 请先设置API密钥');
else $quotaInfo.html('📊 剩余次数: 加载中...');
}
} catch(e) {}
}
function checkApiKeyBeforeAction(actionName) {
var apiKey = getApiKey();
if (!apiKey || apiKey.trim() === '') { log('❌ 无法执行' + actionName + ':请先在设置中配置API密钥', 'red'); return false; }
quotaInfo = getQuotaInfo();
if (quotaInfo.remaining <= 0 && quotaInfo.freeRemaining <= 0) { log('❌ 无法执行' + actionName + ':API次数已用完,请充值', 'red'); return false; }
return true;
}
function hasEnoughQuotaForSubmit() {
quotaInfo = getQuotaInfo();
return hasApiKey() && (quotaInfo.remaining > 0 || quotaInfo.freeRemaining > 0);
}
function getSubmitDecision() {
var hasQuota = hasEnoughQuotaForSubmit();
if (CONFIG.autoSubmit && hasQuota) return { action: 'submit', reason: '自动提交' };
else if (CONFIG.forceSubmit && hasQuota) return { action: 'submit', reason: '强制提交' };
else if (CONFIG.autoSubmit && !hasQuota) return { action: 'save', reason: '次数不足,改为保存' };
else if (CONFIG.forceSubmit && !hasQuota) return { action: 'save', reason: '次数不足,改为保存' };
else return { action: 'save', reason: '自动保存' };
}
function testApi(key, serverIndex) {
return new Promise(function(resolve, reject) {
var apiUrl = API_SERVERS[serverIndex || 0].url;
var formData = 'question=' + encodeURIComponent("test") + '&key=' + encodeURIComponent(key) + '&type=' + encodeURIComponent("单选题") + '&options=' + encodeURIComponent(JSON.stringify(["选项A", "选项B"]));
var timeoutId = setTimeout(function() { reject('请求超时'); }, 10000);
GM_xmlhttpRequest({
method: 'POST', url: apiUrl,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formData, timeout: 10000,
onload: function(xhr) {
clearTimeout(timeoutId);
try {
var res = JSON.parse(xhr.responseText);
if (res.code == 1 || res.code == 400) {
if (res.quota_info) updateQuotaInfo(res.quota_info);
resolve(res);
} else reject(res.msg || '密钥无效');
} catch(e) { reject('解析响应失败'); }
},
onerror: function() { clearTimeout(timeoutId); reject('网络请求失败'); },
ontimeout: function() { clearTimeout(timeoutId); reject('请求超时'); }
});
});
}
function generateEncLocal(classId, uid, jobId, objectId, playTime, duration) {
var clipTime = '0_' + duration;
var str = '[' + classId + '][' + uid + '][' + jobId + '][' + objectId + ']'
+ '[' + (playTime * 1000) + ']'
+ '[d_yHJ!$pdA~5]'
+ '[' + (duration * 1000) + ']'
+ '[' + clipTime + ']';
return MD5(str);
}
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) {
return new Promise(function(resolve) {
var iframe = parent.querySelector(selector);
if (iframe) resolve(iframe);
var observer = new MutationObserver(function(mutations) {
var iframe = parent.querySelector(selector);
if (iframe) { observer.disconnect(); resolve(iframe); }
});
observer.observe(parent, { childList: true, subtree: true });
setTimeout(function() { observer.disconnect(); resolve(null); }, 10000);
});
}
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 key = ($(this).attr('name') || '') + '_' + ($(this).attr('id') || '') + '_' + ($(this).attr('placeholder') || '') + '_' + $(this).index();
uniqueIdentifiers.add(key);
});
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() {
uniquePCInputs.add(($(this).attr('id') || '') + '_' + ($(this).attr('name') || '') + '_' + $(this).index());
});
count = uniquePCInputs.size;
if (count > 0) return count;
}
var $blankInputs = $question.find('input[type="text"], textarea').filter(function() {
var className = $(this).attr('class') || '';
var id = $(this).attr('id') || '';
return className.toLowerCase().indexOf('blank') !== -1 || className.toLowerCase().indexOf('fill') !== -1 || id.toLowerCase().indexOf('blank') !== -1 || id.toLowerCase().indexOf('fill') !== -1;
});
if ($blankInputs.length) {
var uniqueBlanks = new Set();
$blankInputs.each(function() {
uniqueBlanks.add(($(this).attr('name') || '') + '_' + ($(this).attr('id') || '') + '_' + $(this).index());
});
count = uniqueBlanks.size;
if (count > 0) return count;
}
var questionText = $question.text() || '';
var pattern1 = questionText.match(/第(\d+)空/g);
if (pattern1 && pattern1.length > 0) {
var maxBlank = 0;
pattern1.forEach(function(match) {
var num = parseInt(match.match(/\d+/)[0]);
if (num > maxBlank) maxBlank = num;
});
if (maxBlank > 0) return maxBlank;
}
var pattern2 = questionText.match(/[((](\d+)[))]/g);
if (pattern2 && pattern2.length > 0) {
var maxBlank = 0;
pattern2.forEach(function(match) {
var num = parseInt(match.match(/\d+/)[0]);
if (num > maxBlank) maxBlank = num;
});
if (maxBlank > 0 && maxBlank <= 20) return maxBlank;
}
return 1;
}
function fillBlankAnswer($question, answers, isPhoneMode, contextWindow) {
var answerList = splitAnswer(answers);
log('📝 填空题答案: ' + answerList.join(' | '), 'blue');
var blankCount = getBlankCount($question);
while (answerList.length < blankCount) answerList.push(answerList[answerList.length - 1] || answerList[0] || '');
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 key = ($(this).attr('name') || '') + '_' + ($(this).attr('id') || '') + '_' + ($(this).attr('placeholder') || '') + '_' + $(this).index();
if (!seen.has(key)) { seen.add(key); uniqueInputs.push(this); }
});
$(uniqueInputs).each(function(i) {
setTimeout(function() {
var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
$(this).val(answerValue);
$(this).trigger('input').trigger('change');
setTimeout(function() { $(this).trigger('blur'); }.bind(this), 50);
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); }
});
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) {
ueditor = contextWindow.UE.instants['ueditorInstant' + editorIndex];
}
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 answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
$('#answer' + itemId).val(answerValue).trigger('change');
}
} catch(e) { log('⚠️ 填写编辑器第' + (i+1) + '空失败: ' + e.message, 'orange'); }
}, i * 300);
});
return true;
}
return false;
}
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 key = ($(this).attr('id') || '') + '_' + ($(this).attr('name') || '') + '_' + $(this).index();
if (!seenPC.has(key)) { seenPC.add(key); uniquePCInputs.push(this); }
});
$(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'))) {
try { UE.getEditor($this.attr('id')).setContent(answerValue); }
catch(e) { $this.val(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) {
ueditor = contextWindow.UE.instants['ueditorInstant' + editorIndex];
}
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;
}
// ========== 视频弹题处理(仅正常播放模式使用) ==========
var currentVideoQuizObserver = null;
var quizObserverGuardTimer = null;
var quizPollTimer = 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;
options.push({ index: idx, text: (label.textContent || '').trim(), type: input.type, el: input });
});
} else {
var liList = popupEl.querySelectorAll('.ans-videoquiz-opt li, .ans-videoquiz-opt label');
liList.forEach(function(el, idx) {
options.push({ index: idx, text: (el.textContent || '').trim(), 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 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) {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://pkpkq.n1t.cn/font-decrypt/decrypt.php',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ secFont: secFont, dstText: (node.textContent || '').trim() }),
onload: function(res) {
try {
var data = JSON.parse(res.responseText);
if (data && data.srcText) node.textContent = data.srcText;
} catch(e) {}
if (--pending === 0) resolve();
},
onerror: function() { if (--pending === 0) resolve(); },
ontimeout: function() { if (--pending === 0) resolve(); }
});
});
});
}
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) {
var qType = quiz.type;
var opts = quiz.options;
if (!opts.length) return false;
if (qType === 'judge') {
var isTrue = /对|正确|是|√|T/i.test(answer);
var targetIdx = isTrue ? 0 : 1;
if (opts[targetIdx]) { clickOpt(opts[targetIdx]); return true; }
}
var ansArr = splitAnswer(answer);
var clicked = false;
opts.forEach(function(opt) {
var normOpt = normalizeForCompare(opt.text);
for (var i = 0; i < ansArr.length; i++) {
var normAns = normalizeForCompare(ansArr[i]);
if (normOpt === normAns || normOpt.indexOf(normAns) !== -1) {
clickOpt(opt);
clicked = true;
break;
}
}
});
return 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();
for (var k = 0; k < pickCount && pool.length > 0; k++) {
var pi = Math.floor(Math.random() * pool.length);
clickOpt(pool[pi]);
pool.splice(pi, 1);
}
log('🎲 多选题随机选择了 ' + pickCount + ' 项', 'orange');
return true;
}
var ri = Math.floor(Math.random() * opts.length);
clickOpt(opts[ri]);
log('🎲 随机选择第 ' + (ri + 1) + ' 项: ' + opts[ri].text, 'orange');
return true;
}
function submitVideoQuiz(popupEl) {
var submitBtn2 = popupEl.querySelector('.ans-videoquiz-submit, .submit-btn, .confirm, button[type=submit]');
if (submitBtn2) {
setTimeout(function() { submitBtn2.click(); log('📤 弹题已提交', 'green'); }, 500 + Math.random() * 500);
}
}
function resumeVideoWhenQuizGone(popupEl, video) {
var done = false;
function resume() {
if (done) return;
done = true;
if (video && video.paused) {
video.play().then(function() { log('▶️ 视频已恢复播放', '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) {
if (!CONFIG.handleVideoQuiz) return;
if (CONFIG.videoMode !== 'normal') return;
if (!popupEl || popupEl.dataset.videoQuizHandled === 'true') return;
popupEl.dataset.videoQuizHandled = 'true';
log('🎯 检测到视频弹题,开始处理...', 'purple');
var doc = popupEl.ownerDocument || document;
var video = doc.querySelector('video');
if (video && !video.paused) video.pause();
decryptVideoQuizFont(popupEl).then(function() {
var quiz = extractVideoQuiz(popupEl);
log('📖 弹题题干: ' + quiz.title.substring(0, 60) + ' | 题型: ' + quiz.type, '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;
getAnswer(typeId, quiz.title, optionsData).then(function(answer) {
if (!answer || answer === '暂无答案') {
log('⚠️ 弹题未获取到答案,随机选择一项提交', 'orange');
randomPickVideoQuiz(quiz);
} else {
var filled = fillVideoQuizAnswer(quiz, answer);
if (!filled) {
log('⚠️ 弹题答案未匹配到选项,随机选择提交', 'orange');
randomPickVideoQuiz(quiz);
} else {
log('✅ 弹题已作答: ' + answer, 'green');
}
}
submitVideoQuiz(popupEl);
resumeVideoWhenQuizGone(popupEl, video);
}).catch(function(err) {
log('⚠️ 弹题获取答案失败: ' + err + ',随机作答', 'orange');
randomPickVideoQuiz(quiz);
submitVideoQuiz(popupEl);
resumeVideoWhenQuizGone(popupEl, video);
});
});
}
function observeVideoQuiz(doc) {
if (!CONFIG.handleVideoQuiz) return;
if (CONFIG.videoMode !== 'normal') return;
if (currentVideoQuizObserver) { try { currentVideoQuizObserver.disconnect(); } catch(e) {} currentVideoQuizObserver = null; }
if (!doc || !doc.body) return;
var existings = doc.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
existings.forEach(function(el) { if (isVideoQuizPopup(el)) handleVideoQuiz(el); });
currentVideoQuizObserver = new MutationObserver(function(mutations) {
if (!CONFIG.handleVideoQuiz) return;
if (CONFIG.videoMode !== 'normal') return;
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType !== 1) return;
if (isVideoQuizPopup(node)) {
handleVideoQuiz(node);
} else if (node.querySelectorAll) {
var inner = node.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
inner.forEach(function(el) { if (isVideoQuizPopup(el)) handleVideoQuiz(el); });
}
});
});
});
currentVideoQuizObserver.observe(doc.body, { childList: true, subtree: true });
doc.__quizObserverAlive = true;
var oldDisconnect = currentVideoQuizObserver.disconnect.bind(currentVideoQuizObserver);
currentVideoQuizObserver.disconnect = function() {
try { doc.__quizObserverAlive = false; } catch(e) {}
oldDisconnect();
};
log('👀 视频弹题监听已启动', 'blue');
}
function startQuizObserverGuard() {
if (quizObserverGuardTimer) return;
quizObserverGuardTimer = setInterval(function() {
if (!CONFIG.handleVideoQuiz) return;
if (CONFIG.videoMode !== 'normal') 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;
if (!d.querySelector('video')) return;
if (d.__quizObserverAlive) return;
observeVideoQuiz(d);
log('🔄 弹题监听断线重连', 'blue');
});
} catch(e) {}
}, 3000);
}
function stopQuizObserverGuard() {
if (quizObserverGuardTimer) { clearInterval(quizObserverGuardTimer); quizObserverGuardTimer = null; }
}
function startQuizPoll(doc) {
if (quizPollTimer) clearInterval(quizPollTimer);
quizPollTimer = setInterval(function() {
if (!CONFIG.handleVideoQuiz) return;
if (CONFIG.videoMode !== 'normal') return;
try {
var pops = doc.querySelectorAll('.ans-videoquiz, .TiMu, .videoquiz');
pops.forEach(function(el) {
if (isVideoQuizPopup(el) && el.dataset.videoQuizHandled !== 'true') {
handleVideoQuiz(el);
}
});
} catch(e) {}
}, 1500);
}
function stopQuizPoll() {
if (quizPollTimer) { clearInterval(quizPollTimer); quizPollTimer = null; }
}
// ========== 纯后台视频上报 Worker ==========
var reportWorker = null;
var activeVideoJob = null;
function getActualReportInterval() {
var baseInterval = CONFIG.reportBaseInterval || 58;
var safeRate = Math.min(CONFIG.videoRate, 1.1);
var actualInterval = Math.floor(baseInterval / safeRate);
return Math.max(10, actualInterval);
}
function createReportWorker(videoInfo, taskObj) {
var name = videoInfo.name;
var duration = videoInfo.duration;
var dtoken = videoInfo.dtoken;
var objectId = videoInfo.objectId;
var jobId = taskObj.jobid;
var rt = videoInfo.rt;
var otherInfo = videoInfo.otherInfo;
var reportUrl = defaults.reportUrl;
var classId = defaults.clazzId;
var uid = getCookie('_uid') || getCookie('UID');
var startTime = videoInfo.playedTime || 0;
var rate = Math.min(CONFIG.videoRate, 1.1);
var reportInterval = getActualReportInterval();
log('📡 上报配置: 基础间隔=' + CONFIG.reportBaseInterval + 's, 安全倍速=' + rate + 'x, 实际上报间隔=' + reportInterval + 's', 'blue');
var workerCode = `
var timer = null;
var currentTime = ${startTime};
var duration = ${duration};
var rate = ${rate};
var reportInterval = ${reportInterval};
var lastReportTime = ${startTime};
var isRunning = true;
var startTime = Date.now();
function updateLoop() {
if (!isRunning) return;
var now = Date.now();
var elapsed = (now - startTime) / 1000;
var newTime = Math.min(elapsed * rate, duration);
if (newTime > currentTime) {
currentTime = newTime;
var timeSinceLastReport = currentTime - lastReportTime;
var shouldReport = timeSinceLastReport >= reportInterval || currentTime >= duration;
if (shouldReport) {
self.postMessage({ type: 'doReport', playTime: Math.ceil(currentTime), isComplete: currentTime >= duration });
lastReportTime = currentTime;
}
self.postMessage({ type: 'progress', currentTime: currentTime, duration: duration });
}
if (currentTime >= duration) {
isRunning = false;
if (timer) clearTimeout(timer);
setTimeout(function() { self.postMessage({ type: 'completed' }); }, 3000);
} else {
timer = setTimeout(updateLoop, 200);
}
}
updateLoop();
self.onmessage = function(e) {
var data = e.data;
switch(data.type) {
case 'updateRate':
rate = Math.min(data.rate, 1.1);
var baseInterval = ${CONFIG.reportBaseInterval};
reportInterval = Math.max(10, Math.floor(baseInterval / rate));
self.postMessage({ type: 'rateUpdated', rate: rate, interval: reportInterval });
break;
case 'stop':
isRunning = false;
if (timer) clearTimeout(timer);
self.postMessage({ type: 'stopped' });
break;
}
};
`;
var blob = new Blob([workerCode], { type: 'application/javascript' });
var worker = new Worker(URL.createObjectURL(blob));
worker.videoContext = {
name: name, duration: duration, dtoken: dtoken, objectId: objectId,
jobId: jobId, rt: rt, otherInfo: otherInfo, reportUrl: reportUrl,
classId: classId, uid: uid
};
worker.onmessage = function(e) {
var data = e.data;
switch(data.type) {
case 'progress':
if (CONFIG.showProgressBar) updateProgressBar(data.currentTime, data.duration);
break;
case 'rateUpdated':
log('📡 上报间隔已更新: 倍速=' + data.rate + 'x, 上报间隔=' + data.interval + 's', 'blue');
break;
case 'doReport':
var ctx = worker.videoContext;
var enc = generateEncLocal(ctx.classId, ctx.uid, ctx.jobId, ctx.objectId, data.playTime, ctx.duration);
var reportsUrl = ctx.reportUrl + '/' + ctx.dtoken
+ '?clazzId=' + ctx.classId
+ '&playingTime=' + data.playTime
+ '&duration=' + ctx.duration
+ '&clipTime=0_' + ctx.duration
+ '&objectId=' + ctx.objectId
+ '&otherInfo=' + ctx.otherInfo
+ '&jobid=' + ctx.jobId
+ '&userid=' + ctx.uid
+ '&isdrag=0'
+ '&view=pc'
+ '&enc=' + enc
+ '&rt=' + ctx.rt
+ '&dtype=Video'
+ '&_t=' + Date.now();
log('📡 上报: ' + formatDuration(data.playTime) + '/' + formatDuration(ctx.duration) + ' (间隔:' + getActualReportInterval() + 's)', 'blue');
GM_xmlhttpRequest({
method: "GET",
url: reportsUrl,
headers: {
'Host': LOCATION.host,
'Referer': LOCATION.protocol + '//' + LOCATION.host + '/ananas/modules/video/index.html'
},
onload: function(res) {
try {
var result = JSON.parse(res.responseText);
if (result.isPassed || result.status === true || result.msg === 'success') {
if (!worker.isCompleted) {
worker.isCompleted = true;
log('✅ 视频任务已完成: ' + ctx.name, 'green');
if (worker.completeCallback) worker.completeCallback();
}
}
} catch(e) {
if (res.responseText.indexOf('success') !== -1 && !worker.isCompleted) {
worker.isCompleted = true;
log('✅ 视频任务已完成: ' + ctx.name, 'green');
if (worker.completeCallback) worker.completeCallback();
}
}
},
onerror: function() { log('⚠️ 上报失败,将继续尝试', 'orange'); }
});
break;
case 'completed':
log('⏹️ 播放完成,发送收尾确认心跳...', 'green');
var ctx = worker.videoContext;
var finalEnc = generateEncLocal(ctx.classId, ctx.uid, ctx.jobId, ctx.objectId, ctx.duration, ctx.duration);
var finalUrl = ctx.reportUrl + '/' + ctx.dtoken
+ '?clazzId=' + ctx.classId
+ '&playingTime=' + ctx.duration
+ '&duration=' + ctx.duration
+ '&clipTime=0_' + ctx.duration
+ '&objectId=' + ctx.objectId
+ '&otherInfo=' + ctx.otherInfo
+ '&jobid=' + ctx.jobId
+ '&userid=' + ctx.uid
+ '&isdrag=0'
+ '&view=pc'
+ '&enc=' + finalEnc
+ '&rt=' + ctx.rt
+ '&dtype=Video'
+ '&_t=' + Date.now();
GM_xmlhttpRequest({
method: "GET",
url: finalUrl,
headers: {
'Host': LOCATION.host,
'Referer': LOCATION.protocol + '//' + LOCATION.host + '/ananas/modules/video/index.html'
},
onload: function() {
log('✅ 收尾确认已发送', 'green');
setTimeout(function() { if (worker.completeCallback) worker.completeCallback(); }, 2000);
},
onerror: function() {
setTimeout(function() { if (worker.completeCallback) worker.completeCallback(); }, 2000);
}
});
break;
case 'stopped':
log('📡 Worker已停止', 'blue');
break;
}
};
return worker;
}
var currentVideoInterval = null;
var currentProgressBar = null;
var currentVideoWorker = null;
var currentVideoTaskId = null;
var videoTaskCounter = 0;
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 hasTriggeredNoTaskJump = false;
var isVideoTaskActive = false;
var currentVideoName = null;
function forceCleanupAll() {
if (currentVideoWorker) {
try {
currentVideoWorker.postMessage({ type: 'stop' });
setTimeout(function() { try { if (currentVideoWorker) currentVideoWorker.terminate(); } catch(e) {} }, 100);
currentVideoWorker = null;
} catch(e) {}
}
if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; }
if (reportWorker) { try { reportWorker.terminate(); } catch(e) {} reportWorker = null; }
if (currentVideoQuizObserver) { try { currentVideoQuizObserver.disconnect(); } catch(e) {} currentVideoQuizObserver = null; }
stopQuizObserverGuard();
stopQuizPoll();
removeProgressBar();
isVideoTaskActive = false;
currentVideoName = null;
activeVideoJob = null;
currentVideoTaskId = null;
}
function removeProgressBar() {
if (currentProgressBar) { try { $(currentProgressBar).remove(); currentProgressBar = null; } catch(e) {} }
try {
var targetDoc = top.document;
var existingBar = targetDoc.querySelector('#video-progress-bar');
if (existingBar) $(existingBar).remove();
} catch(e) {}
}
function createProgressBar(videoName, duration) {
removeProgressBar();
currentVideoName = videoName;
var taskId = ++videoTaskCounter;
currentVideoTaskId = taskId;
var targetDoc = top.document;
var actualInterval = getActualReportInterval();
var barHtml = `
🎬 ${videoName}
0%
00:00
剩余: --:--
${formatDuration(duration)}
● 后台上报中
⚡ ${CONFIG.videoRate.toFixed(2)}x (间隔:${actualInterval}s)
`;
try { $(targetDoc.body).append(barHtml); currentProgressBar = targetDoc.querySelector('#video-progress-bar'); }
catch(e) { $(document.body).append(barHtml); currentProgressBar = document.querySelector('#video-progress-bar'); }
}
function updateProgressBar(playTime, duration) {
if (!currentProgressBar) return;
var taskId = currentProgressBar.getAttribute('data-task-id');
if (taskId && currentVideoTaskId && parseInt(taskId) !== currentVideoTaskId) return;
var percent = Math.min(100, Math.round((playTime / duration) * 100));
var remaining = Math.max(0, duration - playTime);
var fillBar = currentProgressBar.querySelector('#progress-bar-fill');
var percentSpan = currentProgressBar.querySelector('#progress-percent');
var currentSpan = currentProgressBar.querySelector('#progress-current');
var remainingSpan = currentProgressBar.querySelector('#progress-remaining');
if (fillBar) fillBar.style.width = percent + '%';
if (percentSpan) percentSpan.textContent = percent + '%';
if (currentSpan) currentSpan.textContent = formatDuration(playTime);
if (remainingSpan) remainingSpan.textContent = '剩余: ' + formatDuration(remaining);
}
function updateProgressBarRateDisplay() {
if (!currentProgressBar) return;
var actualInterval = getActualReportInterval();
var rateSpan = currentProgressBar.querySelector('#progress-rate');
if (rateSpan) rateSpan.innerHTML = '⚡ ' + CONFIG.videoRate.toFixed(2) + 'x (间隔:' + actualInterval + 's)';
}
function completeCurrentTask() {
forceCleanupAll();
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 (taskList && taskList.length > 0) {
log('📋 还有 ' + taskList.length + ' 个任务点待处理,继续...', 'green');
setTimeout(function() { startMission(); }, 1000);
} else {
log('✅ 此页面所有任务处理完毕', 'green');
goToNext();
}
}
function goToNext() {
if (isJumping) return;
isJumping = true;
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')) {
log('✅ 找到下一节按钮,3秒后跳转', 'green');
setTimeout(function() { nextBtn.click(); isJumping = false; }, 3000);
return true;
} else log('📚 课程已全部完成,无更多章节可跳转', 'green');
} catch(e) { log('❌ 跳转失败: ' + e.message, 'red'); }
isJumping = false;
return false;
}
function getVideoStatus(item, callback) {
var objectId = item.property.objectid;
var statusUrl = LOCATION.protocol + '//' + LOCATION.host + '/ananas/status/' + objectId + '?k=' + (getCookie('fid') || '') + '&flag=normal&_dc=' + Date.now();
GM_xmlhttpRequest({
method: "GET",
url: statusUrl,
headers: {
'Host': LOCATION.host,
'Referer': LOCATION.protocol + '//' + LOCATION.host + '/ananas/modules/video/index.html'
},
onload: function(res) {
try {
var videoInfo = JSON.parse(res.responseText);
callback({
success: true,
duration: videoInfo.duration,
dtoken: videoInfo.dtoken,
objectId: objectId,
rt: item.property.rt || '0.9',
otherInfo: item.otherInfo || '',
jobid: item.jobid,
playedTime: videoInfo.playingTime || 0,
isPassed: videoInfo.isPassed || false,
name: item.property.name
});
} catch(e) { callback({ success: false, error: e.message }); }
},
onerror: function(err) { callback({ success: false, error: err.error }); }
});
}
function simulateVideoReport(videoInfo, taskObj) {
var name = videoInfo.name || taskObj.property.name;
var duration = videoInfo.duration;
var isAlreadyPassed = videoInfo.isPassed || false;
if (isAlreadyPassed && !CONFIG.reviewMode) { log('✅ 视频已完成: ' + name + ',跳过', 'green'); completeCurrentTask(); return; }
var actualInterval = getActualReportInterval();
log('🎬 模拟上报模式: ' + name + ',总时长: ' + formatDuration(duration), 'purple');
log('📡 上报间隔: ' + actualInterval + '秒 (安全倍速 ' + Math.min(CONFIG.videoRate, 1.1).toFixed(2) + 'x)', 'blue');
if (CONFIG.showProgressBar) { createProgressBar(name, duration); updateProgressBar(videoInfo.playedTime || 0, duration); }
isVideoTaskActive = true;
currentVideoName = name;
currentVideoWorker = createReportWorker(videoInfo, taskObj);
reportWorker = currentVideoWorker;
var workerTaskId = videoTaskCounter;
currentVideoWorker.completeCallback = function() {
if (workerTaskId === currentVideoTaskId && isVideoTaskActive) { forceCleanupAll(); completeCurrentTask(); }
};
}
function normalVideoPlay(dom, obj) {
var name = obj.property.name;
var target = dom.length > 0 ? dom[0] : null;
if (!target) {
log('⚠️ 未找到视频iframe,3秒后重试', 'orange');
isProcessing = false;
setTimeout(function() { normalVideoPlay(dom, obj); }, 3000);
return;
}
log('🎬 正常播放模式处理视频:' + name, 'purple');
isVideoTaskActive = true;
currentVideoName = name;
var executed = false;
var doc = target.contentDocument || target.contentWindow.document;
if (currentVideoInterval) clearInterval(currentVideoInterval);
currentVideoInterval = setInterval(function() {
var media = doc.querySelector('video') || doc.querySelector('audio');
if (media && !executed) {
executed = true;
log('✅ ' + name + ' 开始播放', 'green');
media.muted = true;
var playRate = Math.min(CONFIG.videoRate, 8);
media.playbackRate = playRate > 1 ? playRate : 1;
media.play();
if (doc && doc.body && !currentVideoQuizObserver) {
observeVideoQuiz(doc);
startQuizObserverGuard();
startQuizPoll(doc);
}
var resumePlay = function() { if (media.paused && !media.ended) media.play(); };
media.addEventListener('pause', resumePlay);
var onVideoEnd = function() {
log('✅ ' + name + ' 播放完成', 'green');
media.removeEventListener('pause', resumePlay);
media.removeEventListener('ended', onVideoEnd);
clearInterval(currentVideoInterval);
currentVideoInterval = null;
forceCleanupAll();
completeCurrentTask();
};
media.addEventListener('ended', onVideoEnd);
if (media.ended) onVideoEnd();
}
}, 1500);
}
function processVideo(dom, obj) {
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; }
if (CONFIG.videoMode === 'simulate') {
log('🎬 模拟上报模式处理视频: ' + name, 'purple');
getVideoStatus(obj, function(result) {
if (result.success) simulateVideoReport(result, obj);
else {
log('❌ 获取视频信息失败: ' + result.error + ',降级到正常播放', 'red');
normalVideoPlay(dom, obj);
}
});
return;
}
normalVideoPlay(dom, obj);
}
function processAudio(dom, obj) {
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() { completeCurrentTask(); }, 5000);
}
function processBook(dom, obj) {
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) {
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) {
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 startMission() {
if (isProcessing) { log('⚠️ 已有任务在处理中,跳过', 'orange'); return; }
if (!taskList || taskList.length <= 0) { log('✅ 此页面任务处理完毕', 'green'); isProcessing = false; goToNext(); return; }
forceCleanupAll();
isProcessing = true;
var taskType = taskList[0].type;
var dom = domList[0];
var task = taskList[0];
if (taskType == undefined) taskType = taskList[0].property.module;
var taskName = task.property ? (task.property.name || task.property.title || '未知') : '未知';
log('🔄 开始处理任务: ' + taskName + ' (类型: ' + taskType + ')', 'blue');
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 getAnswer(questionType, questionText, optionsData, retryCount) {
retryCount = retryCount || 0;
var startServerIndex = retryCount;
return new Promise(function(resolve, reject) {
var apiKey = getApiKey();
if (!apiKey || apiKey.trim() === '') { log('❌ 请先设置API密钥才能使用自动答题功能', 'red'); reject('NO_API_KEY'); return; }
quotaInfo = getQuotaInfo();
if (quotaInfo.remaining <= 0 && quotaInfo.freeRemaining <= 0) { log('❌ API次数已用完,无法获取答案', 'red'); reject('QUOTA_EXHAUSTED'); return; }
var typeMap = {0:'单选题',1:'多选题',2:'填空题',3:'判断题',4:'简答题'};
var typeText = typeMap[questionType] || '单选题';
function tryServer(index) {
if (index >= API_SERVERS.length) { log('❌ 所有接口均无法获取答案', 'red'); reject('所有接口均失败'); return; }
var apiUrl = API_SERVERS[index].url;
var formData = 'question=' + encodeURIComponent(questionText) + '&key=' + encodeURIComponent(apiKey) + '&type=' + encodeURIComponent(typeText);
if (optionsData && optionsData.length) formData += '&options=' + encodeURIComponent(JSON.stringify(optionsData));
var timeoutId = setTimeout(function() { tryServer(index + 1); }, 15000);
GM_xmlhttpRequest({
method: 'POST', url: apiUrl,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formData, timeout: 15000,
onload: function(xhr) {
clearTimeout(timeoutId);
try {
var res = JSON.parse(xhr.responseText);
if (res.code == 1 && res.data) {
if (res.quota_info) updateQuotaInfo(res.quota_info);
currentServerIndex = index;
GM_setValue('current_server_index', index);
log('✅ 答案: ' + res.data.substring(0, 100) + '...', 'purple');
resolve(res.data);
} else if (res.code == 400) { log('❌ ' + (res.msg || 'API密钥无效'), 'red'); reject('API_KEY_INVALID'); }
else tryServer(index + 1);
} catch(e) { tryServer(index + 1); }
},
onerror: function() { clearTimeout(timeoutId); tryServer(index + 1); },
ontimeout: function() { clearTimeout(timeoutId); tryServer(index + 1); }
});
}
tryServer(startServerIndex);
});
}
function processQuiz(dom, obj) {
if (!CONFIG.handleQuiz) { log('ℹ️ 用户设置不自动处理测验', 'orange'); completeCurrentTask(); return; }
if (!checkApiKeyBeforeAction('自动答题')) { completeCurrentTask(); 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() { startPhoneQuiz(0, [dom], phoneWeb, obj); }, 3000);
} else setTimeout(function() { startPCQuiz(0, [dom], obj); }, 3000);
} else { log('ℹ️ 用户设置只处理属于任务点的任务', 'orange'); completeCurrentTask(); }
}
function startPhoneQuiz(index, doms, phoneWeb, taskObj) {
if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; }
getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) {
if (!iframe) { setTimeout(function() { 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() { 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 isQuestionAnswered($question, questionType) {
if (questionType == 0 || questionType == 1) {
var $opts = questionType == 0 ? $question.find('.answerList.singleChoice li') : $question.find('.answerList.multiChoice li');
for (var i = 0; i < $opts.length; i++) if ($($opts[i]).attr('aria-label')) return true;
} else if (questionType == 2) {
var $inputs = $question.find('.blankList2 input');
if ($inputs.length && $inputs.first().val() && $inputs.first().val().trim() !== '') return true;
var $editorBlocks = $question.find('[data-editorindex]');
if ($editorBlocks.length) {
var hasContent = false;
$editorBlocks.each(function() {
var itemId = $(this).attr('data-itemid');
if (itemId && $('#answer' + itemId).val() && $('#answer' + itemId).val().trim() !== '') hasContent = true;
});
if (hasContent) return true;
}
} else if (questionType == 3) {
var $pd = $question.find('.answerList.panduan li');
for (var i = 0; i < $pd.length; i++) if ($($pd[i]).attr('aria-label')) return true;
} else if (questionType == 4) {
var $ta = $question.find('textarea[name^="answer"]');
if ($ta.length && $ta.first().val() && $ta.first().val().trim() !== '') return true;
}
return false;
}
function getQuestionType($question, typeName) {
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
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;
}
return questionType;
}
function handlePhoneJudge($question, answer) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pd = $question.find('.answerList.panduan li');
if (answer && answer.trim() !== '' && (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.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'); } });
}
}
function processPhoneQuestions(index, questionList, taskObj) {
if (index == questionList.length) {
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');
completeCurrentTask();
}, 3000);
}, 5000);
} else {
log('💾 ' + decision.reason + ',自动保存', 'green');
setTimeout(function() {
if (saveBtn && saveBtn.length) saveBtn.click();
log('✅ 保存成功', 'green');
completeCurrentTask();
}, 5000);
}
return;
}
var contextWindow = questionList[index] ? (questionList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow;
var $question = $(questionList[index]);
var questionFull = $question.find('.Py-m1-title').html();
var cleanedQuestion = tidyQuestion(questionFull).replace(/.*?\[.*?题\]\s*\n\s*/, '').trim();
var typeName = questionFull.match(/.*?\[(.*?)]|$/)[1];
var questionType = getQuestionType($question, typeName);
if (isQuestionAnswered($question, questionType)) {
log('📌 第' + (index + 1) + '题已作答,跳过', 'green');
setTimeout(function() { processPhoneQuestions(index + 1, questionList, taskObj); }, 30);
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]+/, '');
rawText = rawText.replace(/^\([A-Z]\)[\s]*/, '');
rawText = rawText.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"];
getAnswer(questionType, pureQuestion, optionsData).then(function(answer) {
if (answer == '暂无答案' || !answer) {
log('⚠️ 无法匹配正确答案,跳过此题', 'red');
setTimeout(function() { processPhoneQuestions(index + 1, questionList, taskObj); }, CONFIG.answerInterval);
return;
}
if (CONFIG.insertAnswer) $question.find('.Py-m1-title').html($question.find('.Py-m1-title').html() + '📖 ' + answer + '
');
if (questionType == 0) {
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnswer = normalizeForCompare(answer);
var idx = normalizedOpts.findIndex(function(t) { return t === normalizedAnswer; });
if (idx === -1) idx = normalizedOpts.findIndex(function(t) { return normalizedAnswer.indexOf(t) !== -1 || t.indexOf(normalizedAnswer) !== -1; });
if (idx != -1) { $question.find('.answerList.singleChoice li').eq(idx).click(); log('✅ 单选题已选: ' + optsText[idx], 'green'); }
else log('⚠️ 未找到匹配选项: ' + answer, 'orange');
} else if (questionType == 1) {
var ansArr = splitAnswer(answer);
var matched = false;
$opts.each(function(i, t) {
var normalizedOpt = normalizeForCompare(optsText[i]);
for (var j = 0; j < ansArr.length; j++) {
var normalizedAns = normalizeForCompare(ansArr[j]);
if (normalizedOpt === normalizedAns || normalizedOpt.indexOf(normalizedAns) !== -1) {
setTimeout(function() { $(t).click(); }, 300 * (i + 1));
matched = true;
break;
}
}
});
if (matched) log('✅ 多选题已选完', 'green');
else log('⚠️ 多选题未匹配到任何选项,答案: ' + answer, 'orange');
} else if (questionType == 2) fillBlankAnswer($question, answer, true, contextWindow);
else if (questionType == 3) handlePhoneJudge($question, answer);
else if (questionType == 4) fillShortAnswer($question, answer, true, contextWindow);
log('✅ 第' + (index + 1) + '题自动答题成功', 'green');
setTimeout(function() { processPhoneQuestions(index + 1, questionList, taskObj); }, CONFIG.answerInterval);
}).catch(function() {
log('⚠️ 第' + (index + 1) + '题获取答案失败,跳过', 'orange');
setTimeout(function() { processPhoneQuestions(index + 1, questionList, taskObj); }, CONFIG.answerInterval);
});
}
function startPCQuiz(index, doms, taskObj) {
if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; }
getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) {
if (!iframe) { setTimeout(function() { 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() { 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(0, questionList);
}
function handlePCJudge($question, answer) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pdContainer = $question.find('.Zy_ulTop li');
if ($pdContainer.length === 0) $pdContainer = $question.find('.stem_answer .answer_p').parent();
if (answer && answer.trim() !== '' && (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.toLowerCase()) != -1)) {
var clicked = false;
$pdContainer.each(function(i, t) {
var $opt = $(t).find('a, .answer_p');
if ($opt.length === 0) $opt = $(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 clicked = false;
$pdContainer.each(function(i, t) {
var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param');
if (valAttr == 'false') { $(t).click(); clicked = true; log('✅ PC端判断题选择: 错误', 'green'); }
});
if (!clicked && $pdContainer.length > 1) { $($pdContainer[1]).click(); log('✅ PC端判断题选择: 错误(默认)', 'green'); }
else if (!clicked && $pdContainer.length === 1) { $($pdContainer[0]).click(); log('✅ PC端判断题选择: 错误(唯一)', 'green'); }
}
}
function processPCQuestions(index, questionList) {
if (index == questionList.length) {
var decision = getSubmitDecision();
if (decision.action === 'submit') {
log('✅ 测验处理完成,准备' + decision.reason + '。', 'green');
setTimeout(function() {
if (submitBtn && submitBtn.length) submitBtn.click();
setTimeout(function() {
if (frameContent && frameContent.find('#confirmSubWin > div > div > a.bluebtn').length) frameContent.find('#confirmSubWin > div > div > a.bluebtn').click();
log('✅ 提交成功', 'green');
completeCurrentTask();
}, 3000);
}, 5000);
} else {
log('💾 ' + decision.reason + ',自动保存', 'green');
setTimeout(function() {
if (saveBtn && saveBtn.length) saveBtn.click();
log('✅ 保存成功', 'green');
completeCurrentTask();
}, 5000);
}
return;
}
var $question = $(questionList[index]);
var questionFull = $question.find('.Zy_TItle.clearfix > div').html();
var cleanedQuestion = tidyQuestion(questionFull);
var typeName = questionFull.match(/^【(.*?)】|$/)[1];
var questionType = getQuestionType($question, typeName);
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]+/, '');
rawText = rawText.replace(/^\([A-Z]\)[\s]*/, '');
rawText = rawText.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"];
getAnswer(questionType, pureQuestion, optionsData).then(function(answer) {
if (answer == '暂无答案' || !answer) {
log('⚠️ PC第' + (index + 1) + '题无法获取答案,跳过', 'orange');
setTimeout(function() { processPCQuestions(index + 1, questionList); }, CONFIG.answerInterval);
return;
}
if (CONFIG.insertAnswer) $question.find('.Zy_TItle.clearfix > div').html($question.find('.Zy_TItle.clearfix > div').html() + '📖 ' + answer + '
');
if (questionType == 0) {
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnswer = normalizeForCompare(answer);
var idx = normalizedOpts.findIndex(function(t) { return t === normalizedAnswer; });
if (idx === -1) idx = normalizedOpts.findIndex(function(t) { return normalizedAnswer.indexOf(t) !== -1 || t.indexOf(normalizedAnswer) !== -1; });
if (idx != -1) { $($opts[idx]).parent().click(); log('✅ PC单选题已选: ' + optsText[idx], 'green'); }
else log('⚠️ 未找到匹配选项: ' + answer, 'orange');
} else if (questionType == 1) {
var ansArr = splitAnswer(answer);
var matched = false;
$opts.each(function(i, t) {
var normalizedOpt = normalizeForCompare(optsText[i]);
for (var j = 0; j < ansArr.length; j++) {
var normalizedAns = normalizeForCompare(ansArr[j]);
if (normalizedOpt === normalizedAns || normalizedOpt.indexOf(normalizedAns) !== -1) {
setTimeout(function() { $($opts[i]).parent().click(); }, 300 * (i + 1));
matched = true;
break;
}
}
});
if (matched) log('✅ PC多选题已选完', 'green');
else log('⚠️ PC多选题未匹配到任何选项,答案: ' + answer, 'orange');
} else if (questionType == 2) fillBlankAnswer($question, answer, false, null);
else if (questionType == 3) handlePCJudge($question, answer);
else if (questionType == 4) fillShortAnswer($question, answer, false, null);
log('✅ PC第' + (index + 1) + '题自动答题成功', 'green');
setTimeout(function() { processPCQuestions(index + 1, questionList); }, CONFIG.answerInterval);
}).catch(function() {
log('⚠️ PC第' + (index + 1) + '题获取答案失败,跳过', 'orange');
setTimeout(function() { processPCQuestions(index + 1, questionList); }, CONFIG.answerInterval);
});
}
function switchMission() { isProcessing = false; startMission(); }
function processHomework() {
log('📝 开始处理作业', 'green');
if (!checkApiKeyBeforeAction('作业自动答题')) return;
var $homeworkTable = $('.mark_table').find('form');
var questionList = $homeworkTable.find('.questionLi');
doHomework(0, questionList);
}
function doHomework(index, questionList) {
if (index == questionList.length) { log('✅ 作业题目已全部完成', 'green'); return; }
var typeName = $(questionList[index]).attr('typename');
var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 };
var questionType = typeMap[typeName];
var questionFull = $(questionList[index]).find('.mark_name').html();
var cleanedQuestion = tidyQuestion(questionFull).replace(/^[(].*?[)]/, '').trim();
if (questionType === undefined) {
var answerTmpArr = $(questionList[index]).find('.stem_answer').find('.answer_p');
if (answerTmpArr && answerTmpArr.length > 0) questionType = $(questionList[index]).find('input[type="checkbox"]').length ? 1 : 0;
else if ($(questionList[index]).find('.stem_answer').find('.divText textarea').length) questionType = 4;
}
var $opts = [];
var optsText = [];
var optionsData = [];
var pureQuestion = cleanedQuestion;
if (questionType == 0 || questionType == 1) {
$opts = $(questionList[index]).find('.stem_answer .answer_p');
$opts.each(function() {
var rawText = tidyString($(this).html());
rawText = rawText.replace(/^[A-Z][\s]*[.、.))\s]+/, '');
rawText = rawText.replace(/^\([A-Z]\)[\s]*/, '');
rawText = rawText.replace(/^([A-Z])[\s]*/, '');
optsText.push(rawText);
});
optionsData = optsText;
} else if (questionType == 2) { var blankCount = getBlankCount($(questionList[index])); optionsData = [blankCount.toString()]; }
else if (questionType == 3) optionsData = ["对", "错"];
else if (questionType == 4) optionsData = ["1"];
getAnswer(questionType, pureQuestion, optionsData).then(function(answer) {
if (answer == '暂无答案' || !answer) {
log('⚠️ 作业第' + (index + 1) + '题无法获取答案,跳过', 'orange');
setTimeout(function() { doHomework(index + 1, questionList); }, CONFIG.answerInterval);
return;
}
if (CONFIG.insertAnswer) $(questionList[index]).find('.mark_name').html($(questionList[index]).find('.mark_name').html() + '📖 ' + answer + '
');
if (questionType == 0) {
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnswer = normalizeForCompare(answer);
var idx = normalizedOpts.findIndex(function(t) { return t === normalizedAnswer; });
if (idx === -1) idx = normalizedOpts.findIndex(function(t) { return normalizedAnswer.indexOf(t) !== -1 || t.indexOf(normalizedAnswer) !== -1; });
if (idx != -1) { $($opts[idx]).parent().click(); log('✅ 作业单选题已选: ' + optsText[idx], 'green'); }
} else if (questionType == 1) {
var ansArr = splitAnswer(answer);
var matched = false;
$opts.each(function(i, t) {
var normalizedOpt = normalizeForCompare(optsText[i]);
for (var j = 0; j < ansArr.length; j++) {
var normalizedAns = normalizeForCompare(ansArr[j]);
if (normalizedOpt === normalizedAns || normalizedOpt.indexOf(normalizedAns) !== -1) {
setTimeout(function() { $($opts[i]).parent().click(); }, 300 * (i + 1));
matched = true;
break;
}
}
});
if (matched) log('✅ 作业多选题已选完', 'green');
else log('⚠️ 作业多选题未匹配到任何选项,答案: ' + answer, 'orange');
} else if (questionType == 2) fillBlankAnswer($(questionList[index]), answer, false, null);
else if (questionType == 3) handlePCJudge($(questionList[index]), answer);
else if (questionType == 4) fillShortAnswer($(questionList[index]), answer, false, null);
log('✅ 作业第' + (index + 1) + '题自动答题成功', 'green');
setTimeout(function() { doHomework(index + 1, questionList); }, CONFIG.answerInterval);
}).catch(function() {
log('⚠️ 作业第' + (index + 1) + '题获取答案失败,跳过', 'orange');
setTimeout(function() { doHomework(index + 1, questionList); }, CONFIG.answerInterval);
});
}
function processExam() {
if (!checkApiKeyBeforeAction('考试自动答题')) 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*/, ''));
var $answerDom = $examTable.find('#submitTest').find('.stem_answer');
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;
}
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]+/, '');
rawText = rawText.replace(/^\([A-Z]\)[\s]*/, '');
rawText = rawText.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 = ["对", "错"];
getAnswer(questionType, pureQuestion, optionsData).then(function(answer) {
if (answer == '暂无答案' || !answer) {
log('⚠️ 考试此题无法获取答案,跳过', 'orange');
setTimeout(function() { goToNextExam(); }, CONFIG.answerInterval);
return;
}
if (CONFIG.insertAnswer) $examTable.find('h3.mark_name').html($examTable.find('h3.mark_name').html() + '📖 ' + answer + '');
if (questionType == 0) {
var normalizedOpts = optsText.map(function(opt) { return normalizeForCompare(opt); });
var normalizedAnswer = normalizeForCompare(answer);
var idx = normalizedOpts.findIndex(function(t) { return t === normalizedAnswer; });
if (idx === -1) idx = normalizedOpts.findIndex(function(t) { return normalizedAnswer.indexOf(t) !== -1 || t.indexOf(normalizedAnswer) !== -1; });
if (idx != -1) { $($opts[idx]).parent().click(); log('✅ 考试单选题已选: ' + optsText[idx], 'green'); }
} else if (questionType == 1) {
var ansArr = splitAnswer(answer);
var matched = false;
$opts.each(function(i, t) {
var normalizedOpt = normalizeForCompare(optsText[i]);
for (var j = 0; j < ansArr.length; j++) {
var normalizedAns = normalizeForCompare(ansArr[j]);
if (normalizedOpt === normalizedAns || normalizedOpt.indexOf(normalizedAns) !== -1) {
setTimeout(function() { $($opts[i]).parent().click(); }, 300 * (i + 1));
matched = true;
break;
}
}
});
if (matched) log('✅ 考试多选题已选完', 'green');
else log('⚠️ 考试多选题未匹配到任何选项,答案: ' + answer, 'orange');
} else if (questionType == 2) fillBlankAnswer($answerDom, answer, false, null);
else if (questionType == 3) handleExamJudge($answerDom, answer);
else if (questionType == 4) fillShortAnswer($answerDom, answer, false, null);
log('✅ 考试自动答题成功', 'green');
setTimeout(function() { goToNextExam(); }, CONFIG.answerInterval);
}).catch(function() {
log('⚠️ 考试此题获取答案失败,跳过', 'orange');
setTimeout(function() { goToNextExam(); }, CONFIG.answerInterval);
});
}
function handleExamJudge($container, answer) {
var trueKeywords = '正确|是|对|√|T|ri';
var $pdContainer = $container.find('.clearfix.answerBg .fl.answer_p').parent();
if (answer && answer.trim() !== '' && (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.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'); }
}
}
function goToNextExam() {
if (CONFIG.autoNextExam) {
var $nextBtn = $('.mark_table .whiteDiv .nextDiv a.jb_btn');
var delay = CONFIG.examNextDelay ? 2000 + (Math.floor(Math.random() * 5 + 1) * 1000) : 2000;
setTimeout(function() { $nextBtn.click(); }, delay);
log('⏭️ ' + (delay/1000) + '秒后自动跳转下一题', 'blue');
}
}
// ========== 字体解密 ==========
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 decryptFont() {
if (CONFIG.decryptFont !== 1) return;
try {
if (!TyprInstance || !TyprInstance.U || !TyprInstance.parse) {
try {
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) TyprInstance = unsafeWindow.Typr;
else if (typeof window !== 'undefined' && window.Typr) TyprInstance = window.Typr;
else if (typeof Typr !== 'undefined') TyprInstance = Typr;
else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) TyprInstance = unsafeWindow.TyprMd5;
else if (typeof TyprMd5 !== 'undefined') TyprInstance = TyprMd5;
} catch(e) {}
if (!TyprInstance || !TyprInstance.U) { setTimeout(decryptFont, 1000); return; }
}
var $tip = $('style:contains(font-cxsecret)');
if (!$tip.length) return;
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 tableJson = GM_getResourceText('Table');
if (!tableJson) return;
var table;
try { table = JSON.parse(tableJson); } catch(e) { return; }
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++) {
var charCode = html.charCodeAt(j);
if (charCode >= 19968 && charCode <= 40869) existingChars.add(charCode);
}
}
});
if (existingChars.size === 0) return;
var match = {};
var charCodes = Array.from(existingChars);
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 = MD5(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 ==========
function showPikaqiu() {
if (!CONFIG.showMaskImg) return;
if (pikaqiuAdded) return;
try {
var targetDoc = top.document;
if (targetDoc.querySelector('#pikaqiu-img')) return;
var imgHtml = '
';
$(targetDoc.body).append(imgHtml);
pikaqiuAdded = true;
$('#pikaqiu-img', targetDoc).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 getSettingsPanelHTML() {
var actualInterval = getActualReportInterval();
return ``;
}
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;
var dy = e.clientY - startY;
var newLeft = origLeft + dx;
var newTop = origTop + dy;
var vw = $(targetDoc).width();
var 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));
});
}
$('#ne-21close', targetDoc).off('click').on('click', function() { $('#ne-21box', targetDoc).hide(); isBoxHidden = true; });
var $moreBtn = $('#moreSettingsBtn', targetDoc);
var $moreSet = $('#moreSettings', targetDoc);
var visible = false;
$moreBtn.off('click').on('click', function() {
visible = !visible;
$moreSet.css('display', visible ? 'block' : 'none');
$moreBtn.text(visible ? '✖️ 收起设置' : '⚙️ 更多设置');
$moreBtn.css('background', visible ? '#eef2f6' : '#f0f4f9');
});
$('input[name="videoMode"]', targetDoc).off('change').on('change', function() {
var newMode = this.value;
var changed = CONFIG.videoMode !== newMode;
updateConfig('videoMode', newMode);
SettingsManager.saveVideoMode(CONFIG.videoMode);
log('🎬 视频模式切换为: ' + (newMode === 'simulate' ? '模拟上报' : '正常播放'), 'green');
if (changed && isVideoTaskActive && currentVideoName) {
log('🔄 检测到正在处理视频,1秒后按新模式重跑当前任务', 'orange');
forceCleanupAll();
isProcessing = false;
setTimeout(function() { startMission(); }, 1000);
}
});
var $rateSlider = $('#videoRateSlider', targetDoc);
var $rateValue = $('#rateValue', targetDoc);
$rateSlider.off('input change').on('input', function() { $rateValue.text(parseFloat(this.value).toFixed(2)); })
.on('change', function() {
var newRate = parseFloat(this.value);
if (newRate > 8) newRate = 8;
updateConfig('videoRate', newRate);
SettingsManager.saveVideoRate(CONFIG.videoRate);
if (currentVideoWorker && isVideoTaskActive) currentVideoWorker.postMessage({ type: 'updateRate', rate: CONFIG.videoRate });
var actualInterval = getActualReportInterval();
log('📹 视频倍速: ' + CONFIG.videoRate.toFixed(2) + 'x, 上报间隔: ' + actualInterval + '秒', 'green');
updateProgressBarRateDisplay();
});
$('#showProgressBarCheck', targetDoc).off('change').on('change', function() {
var show = this.checked ? 1 : 0;
updateConfig('showProgressBar', show);
SettingsManager.saveProgressBar(show);
if (show === 0) {
removeProgressBar();
log('📊 进度条已隐藏', 'blue');
} else if (isVideoTaskActive && currentVideoName) {
var dur = currentVideoWorker && currentVideoWorker.videoContext ? currentVideoWorker.videoContext.duration : 0;
if (dur > 0) createProgressBar(currentVideoName, dur);
log('📊 进度条已显示', 'green');
}
});
$('#answerInterval', targetDoc).off('change').on('change', function(e) {
var newTime = parseInt(e.target.value);
if (newTime >= 1000 && newTime <= 10000) updateConfig('answerInterval', newTime);
else e.target.value = CONFIG.answerInterval;
});
var checkboxes = ['autoSubmit', 'forceSubmit', 'autoNextExam', 'insertAnswer', 'reviewMode', 'handleVideoQuiz'];
checkboxes.forEach(function(id) {
var $cb = $('#setting_' + id, targetDoc);
if ($cb.length) $cb.off('change').on('change', function() {
updateConfig(id, this.checked);
if (id === 'handleVideoQuiz') {
if (this.checked) {
if (CONFIG.videoMode === 'normal') {
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 {
log('👀 弹窗答题已开启(模拟上报模式无弹题,切到正常播放时生效)', 'blue');
}
} else {
if (currentVideoQuizObserver) {
try { currentVideoQuizObserver.disconnect(); } catch(e) {}
currentVideoQuizObserver = null;
}
stopQuizPoll();
stopQuizObserverGuard();
log('👀 视频弹题监听已关闭', 'blue');
}
}
});
});
$('#goToTkBtn', targetDoc).off('click').on('click', function() { GM_openInTab('https://tk.n1t.cn', { active: true }); });
$('#saveApiKeyBtn', targetDoc).off('click').on('click', function() {
var key = $('#apiKeyInput', targetDoc).val();
var $testResult = $('#testResult', targetDoc);
if (key && key.trim()) {
$testResult.html('⏳ 测试中...').css('color', '#7d9bb5');
testApi(key.trim(), 0).then(function(res) {
GM_setValue('api_key', key.trim());
log('✅ API密钥已保存并验证有效', 'green');
if (res.quota_info) {
updateQuotaInfo(res.quota_info);
$testResult.html('✅ 密钥有效!剩余次数: ' + (res.quota_info.remaining || 0) + ' | 免费: ' + (res.quota_info.free_remaining || 0)).css('color', '#2c7da0');
} else $testResult.html('✅ 密钥有效').css('color', '#2c7da0');
$('#apiKeyInput', targetDoc).val('');
$('#apiKeyInput', targetDoc).attr('placeholder', '已设置');
updateQuotaDisplay();
setTimeout(function() { $testResult.html(''); }, 5000);
}).catch(function(err) {
$testResult.html('❌ 密钥无效: ' + err).css('color', '#e07c5c');
log('❌ API密钥无效: ' + err, 'red');
setTimeout(function() { $testResult.html(''); }, 5000);
});
} else {
var existingKey = getApiKey();
if (existingKey) {
testApi(existingKey, 0).then(function(res) {
if (res.quota_info) updateQuotaInfo(res.quota_info);
$testResult.html('✅ 密钥有效').css('color', '#2c7da0');
updateQuotaDisplay();
setTimeout(function() { $testResult.html(''); }, 5000);
}).catch(function(err) {
GM_setValue('api_key', '');
quotaInfo = { remaining: 0, freeRemaining: 0, rechargeBalance: 0, todayFreeUsage: 0 };
saveQuotaInfo();
updateQuotaDisplay();
$testResult.html('❌ 密钥已失效').css('color', '#e07c5c');
setTimeout(function() { $testResult.html(''); }, 5000);
});
} else {
$testResult.html('❌ 请输入API密钥').css('color', '#e07c5c');
setTimeout(function() { $testResult.html(''); }, 3000);
}
}
});
}
function showBox() {
if (!CONFIG.showBox) return;
try {
var targetDoc = top.document;
if (targetDoc.querySelector('#ne-21box')) { flushLogs(); updateQuotaDisplay(); return; }
} catch(e) {}
setTimeout(function() {
try {
var targetDoc = top.document;
var boxHtml = getSettingsPanelHTML();
$(targetDoc.body).append(boxHtml);
bindSettingUI(targetDoc);
var actualInterval = getActualReportInterval();
$('#ne-21notice', targetDoc).html('✅ 脚本已加载(后台版) | 模式: ' + (CONFIG.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + CONFIG.videoRate.toFixed(2) + 'x | 上报间隔: ' + actualInterval + 's
');
var savedKey = getApiKey();
if (savedKey) {
$('#apiKeyInput', targetDoc).attr('placeholder', '已设置');
testApi(savedKey, 0).then(function(res) { if (res.quota_info) updateQuotaInfo(res.quota_info); log('✅ API密钥有效', 'green'); })
.catch(function(err) {
GM_setValue('api_key', '');
quotaInfo = { remaining: 0, freeRemaining: 0, rechargeBalance: 0, todayFreeUsage: 0 };
saveQuotaInfo();
updateQuotaDisplay();
log('⚠️ API密钥已失效', 'orange');
});
} else updateQuotaDisplay();
flushLogs();
showPikaqiu();
} catch(e) { console.error('显示悬浮窗失败:', e); }
}, 100);
}
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) {
return extractString(scripts[i].innerHTML.replace(/\s/g, ""), 'try{mArg=', ';}catch');
}
}
return null;
} catch(e) { return null; }
}
function reinitTaskList() {
if (isProcessing) { setTimeout(reinitTaskList, 3000); return; }
forceCleanupAll();
var params = getTaskParams();
if (!params || params == '$mArg') return;
try {
var allTasks = $.parseJSON(params).attachments;
if (!allTasks || allTasks.length <= 0) return;
defaults = $.parseJSON(params).defaults;
var pendingTasks = [];
for (var i = 0; i < allTasks.length; i++) if (!isTaskCompleted(allTasks[i])) pendingTasks.push(allTasks[i]);
pendingMissionCount = pendingTasks.length;
if (pendingMissionCount === 0) return;
log('📋 重新加载任务列表,发现 ' + pendingMissionCount + ' 个待处理任务', 'green');
taskList = [];
domList = [];
$('.wrap .ans-cc .ans-attach-ct').each(function(i, t) {
if (i < allTasks.length && !isTaskCompleted(allTasks[i])) { taskList.push(allTasks[i]); domList.push($(t).find('iframe')); }
});
if (taskList.length > 0 && !isProcessing) { log('🚀 继续处理剩余任务', 'green'); startMission(); }
} catch(e) { log('❌ 重新初始化失败: ' + e.message, 'red'); }
}
$(function() {
if (CONFIG.videoRate > 8) CONFIG.videoRate = 8;
var savedServerIndex = GM_getValue('current_server_index', 0);
if (savedServerIndex >= 0 && savedServerIndex < API_SERVERS.length) currentServerIndex = savedServerIndex;
CONFIG.videoMode = GM_getValue('videoMode', 'simulate');
CONFIG.videoRate = GM_getValue('videoRate', 1);
if (CONFIG.videoRate > 8) CONFIG.videoRate = 8;
CONFIG.showProgressBar = GM_getValue('showProgressBar', 1);
UNSAFE_WIN.confirm = function() { return true; };
if (CONFIG.decryptFont) {
setTimeout(decryptFont, 2000);
setTimeout(function() { if ($('.font-cxsecret').length > 0) decryptFont(); }, 5000);
setTimeout(function() { if ($('.font-cxsecret').length > 0) decryptFont(); }, 10000);
}
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();
var actualInterval = getActualReportInterval();
log('✅ 初始化完毕!视频模式: ' + (CONFIG.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + CONFIG.videoRate.toFixed(2) + 'x | 上报间隔: ' + actualInterval + '秒', 'green');
} else if (LOCATION.pathname.includes('/knowledge/cards')) {
showBox();
var params = getTaskParams();
if (!params || params == '$mArg') {
log('⚠️ 无任务点可处理', 'red');
if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(function() { goToNext(); }, 2000); }
return;
}
try {
var allTasks = $.parseJSON(params).attachments;
if (!allTasks || allTasks.length <= 0) {
log('⚠️ 无任务点可处理', 'red');
if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(function() { goToNext(); }, 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;
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 || '任务点' + (i+1)) : '任务点' + (i+1); log('✅ 任务点 ' + taskName + ' 已完成,跳过', 'green'); }
}
pendingMissionCount = pendingTasks.length;
completedMissionCount = 0;
if (pendingMissionCount === 0) {
log('✅ 所有任务点已完成', 'green');
setTimeout(function() { goToNext(); }, 2000);
return;
}
log('📋 发现 ' + allTasks.length + ' 个任务点,其中 ' + pendingMissionCount + ' 个待处理', 'green');
taskList = [];
domList = [];
$('.wrap .ans-cc .ans-attach-ct').each(function(i, t) {
if (i < allTasks.length && !isTaskCompleted(allTasks[i])) { taskList.push(allTasks[i]); domList.push($(t).find('iframe')); }
});
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 lastChapterUrl = LOCATION.href;
setInterval(function() {
if (LOCATION.href !== lastChapterUrl) {
lastChapterUrl = LOCATION.href;
forceCleanupAll();
setTimeout(function() { if (LOCATION.pathname.includes('/knowledge/cards')) reinitTaskList(); }, 3000);
}
}, 2000);
window.addEventListener('beforeunload', function() { forceCleanupAll(); });
$(document).keydown(function(e) {
if (e.keyCode == 120) {
var $box = $('#ne-21box');
if ($box.length === 0) return;
if (isBoxHidden) { $box.show(); isBoxHidden = false; log('📌 面板已显示', 'green'); }
else { $box.hide(); isBoxHidden = true; log('📌 面板已隐藏,按F9恢复', 'blue'); }
}
});