').text(message)).append($retry);
$retry.on('click', function () {
$retry.prop('disabled', true);
_authPromise = requestAuth(_host);
updateAuthPanel(cachedModels);
});
});
}
}
var _ne21LogColorMap = {
red: '#dc2626', green: '#059669', blue: '#2563eb',
yellow: '#ca8a04', orange: '#ea580c', purple: '#7c3aed',
pink: '#db2777', gray: '#64748b', grey: '#64748b'
}
function logger(str, color) {
var _time = new Date().toLocaleTimeString()
var c = _ne21LogColorMap[color] || color || '#334155'
var $p = $('
[' + _time + ']' + str + '
')
$('#ne-21log', window.parent.document).prepend($p)
return $p
}
// 原地更新一条已存在的日志(由 logger 返回的 jQuery
元素)。
// 用于把 "AI 思考中..." 占位行原地替换为答案/错误提示, 避免反复出现/消失。
function updateLogEntry($p, str, color) {
if (!$p || !$p.length) return
var c = _ne21LogColorMap[color] || color || '#334155'
$p.find('.ne21-msg').css('color', c).html(str)
}
function getStr(str, start, end) {
let res = str.match(new RegExp(`${start}(.*?)${end}`))
return res ? res[1] : null
}
function getTaskParams() {
try {
var _iframeScripts = _d.scripts,
_p = null;
for (let i = 0; i < _iframeScripts.length; i++) {
if (_iframeScripts[i].innerHTML.indexOf('mArg = "";') != -1 && _iframeScripts[i].innerHTML.indexOf('==UserScript==') == -1) {
_p = getStr(_iframeScripts[i].innerHTML.replace(/\s/g, ""), 'try{mArg=', ';}catch');
return _p
}
}
return _p
} catch (e) {
return null
}
}
function getCk(name) {
return document.cookie.match(`[;\\s+]?${name}=([^;]*)`)?.pop();
}
function autoLogin() {
logger('用户已设置自动登录', 'green')
if (setting.phone.length <= 0 || setting.password.length <= 0) {
logger('用户未设置登录信息', 'red')
return
}
setTimeout(() => {
$('#phone').val(setting.phone)
$('#pwd').val(setting.password)
$('#loginBtn').click()
}, 3000)
}
function toNext() {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(toNext, 3000);
return;
}
refreshCourseList().then((res) => {
// 检测当前课时是否还有未完成的页面(兼容多种 DOM 结构)
// 返回 { activeIndex, total, hasNext, tabs } 或 null
function detectSubTabPosition() {
try {
// 兼容现代版(.prev_ul)、旧版(#prev_tab)、备用版(#prevTabBox)
var selectors = ['#prev_tab > li', '.prev_ul > li', '#prevTabBox > li'];
for (var i = 0; i < selectors.length; i++) {
var nodes = top.document.querySelectorAll(selectors[i]);
if (!nodes || nodes.length === 0) continue;
var tabs = Array.prototype.slice.call(nodes);
var activeIdx = -1;
for (var j = 0; j < tabs.length; j++) {
if (tabs[j].classList && tabs[j].classList.contains('active')) {
activeIdx = j;
break;
}
}
if (activeIdx === -1) continue;
return {
activeIndex: activeIdx,
total: tabs.length,
hasNext: activeIdx < tabs.length - 1,
tabs: tabs
};
}
// 兼容风格:span.currents ~ span(当前页指示器后还有兄弟节点表示存在下一页)
var currents = top.document.querySelector('span.currents');
if (currents) {
var nextSibs = [];
var sib = currents.nextElementSibling;
while (sib) {
if (sib.tagName === 'SPAN') nextSibs.push(sib);
sib = sib.nextElementSibling;
}
if (nextSibs.length > 0) {
return { activeIndex: 0, total: nextSibs.length + 1, hasNext: true, tabs: null };
}
}
} catch (e) { /* ignore */ }
return null;
}
// 点击章节内 “下一页” 按钮(仅在当前课时尚有页面时使用,避免误跳到下一章节)
function clickNextPageBtn() {
$('#ne-21log', window.parent.document).html('')
try { top._gptJsJumpAllowed = true; } catch (_) { /* empty */ }
var nextBtn = top.document.querySelector('#mainid > .prev_next.next')
if (nextBtn) { nextBtn.click(); return true }
return false
}
// 点击 “下一章节” 按钮:优先尝试章节内的“下一页/下一节”统一按钮,
// 找不到时再退回 #prevNextFocusNext(仅用于章节级跳转)
function clickNextChapterBtn() {
$('#ne-21log', window.parent.document).html('')
try { top._gptJsJumpAllowed = true; } catch (_) { /* empty */ }
var nextBtn = top.document.querySelector('#mainid > .prev_next.next')
if (nextBtn) { nextBtn.click(); return true }
var focusBtn = top.document.querySelector('#prevNextFocusNext')
if (focusBtn) { focusBtn.click(); return true }
return false
}
// 优先级 1:当前课时若仍有未完成的页面,先切换到下一页,避免漏刷
var sub = detectSubTabPosition()
if (sub && sub.hasNext) {
logger('当前课时存在未完成页面(' + (sub.activeIndex + 1) + '/' + sub.total + '),准备切换到下一页', 'blue')
setTimeout(() => {
if (!clickNextPageBtn()) {
logger('未找到本课时下一页按钮,回退到下一章节跳转', 'orange')
clickNextChapterBtn()
}
}, 2000)
return
}
if (setting.review || !setting.work) {
logger('本课时已无未完成页面,准备切换到下一章节', 'blue')
setTimeout(() => { clickNextChapterBtn() }, 2000)
return
}
let _t = []
$.each($(res).find('li'), (_, t) => {
let curid = $(t).find('.posCatalog_select').attr('id'),
status = $(t).find('.prevHoverTips').text(),
name = $(t).find('.posCatalog_name').attr('title');
if (curid.indexOf('cur') != -1) {
_t.push({ 'curid': curid, 'status': status, 'name': name })
}
})
let _curChaterId = $('#coursetree', window.parent.document).find('.posCatalog_active').attr('id')
let _curIndex = _t.findIndex((item) => item['curid'] == _curChaterId)
for (_curIndex; _curIndex < _t.length - 1; _curIndex++) {
// 当前章节仍标记为待完成,但章节内已无更多页面(hasNext=false 已在前面判定过)
// 此分支保留作为兜底:万一 detectSubTabPosition 漏检,仍尝试调用一次
if (_t[_curIndex]['status'].indexOf('待完成') != -1) {
var subAgain = detectSubTabPosition()
if (subAgain && subAgain.hasNext) {
logger('兜底检测到本课时仍有未完成页面(' + (subAgain.activeIndex + 1) + '/' + subAgain.total + '),切换到下一页', 'blue')
setTimeout(() => {
if (!clickNextPageBtn()) clickNextChapterBtn()
}, 2000)
return
}
}
let t = _t[_curIndex + 1]
if (t['status'].indexOf('待完成') != -1) {
setTimeout(() => {
clickNextChapterBtn()
showBox()
}, 2000)
return
} else if (t['status'].indexOf('闯关') != -1) {
logger('当前为闯关模式,存在未完成任务点,脚本已暂停运行,请手动完成并点击下一章节', 'red')
return
} else if (t['status'].indexOf('开放') != -1) {
logger('章节未开放', 'red')
return
} else {
// console.log(t)
}
}
logger('此课程处理完毕', 'green')
return
})
}
function missonStart() {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(missonStart, 3000);
return;
}
if (_mlist.length <= 0) {
logger('此页面任务处理完毕,准备跳转页面', 'green')
return toNext()
}
let _type = _mlist[0]['type'],
_dom = _domList[0],
_task = _mlist[0];
if (_type == undefined) {
_type = _mlist[0]['property']["module"]
}
switch (_type) {
case "video":
if (_mlist[0]['property']['module'] == 'insertvideo') {
logger('开始处理视频', 'purple')
missonVideo(_dom, _task)
break
} else if (_mlist[0]['property']['module'] == 'insertaudio') {
logger('开始处理音频', 'purple')
missonVideo(_dom, _task)
break
} else {
logger('未知类型任务,请联系作者,跳过', 'red')
switchMission()
break
}
case "workid":
logger('开始处理测验', 'purple')
missonWork(_dom, _task)
break
case "document":
logger('开始处理文档', 'purple')
missonDoucument(_dom, _task)
break
case "read":
logger('开始处理阅读', 'purple')
missonRead(_dom, _task)
break
case "insertbook":
logger('开始处理读书', 'purple')
missonBook(_dom, _task)
break
case "knowledgeGraph":
logger('开始处理知识图谱', 'purple')
missonKnowledgeGraph(_task)
break
default: {
// 无需处理或不算任务点的占位类型:图片、答疑、分享、外链题库等
let GarbageTasks = ['insertimage', 'insertanswerquestion', 'insertshare', 'insertquestion', 'insertdiscuss', 'insertsubject']
if (GarbageTasks.indexOf(_type) != -1) {
logger('发现无需处理任务(' + _type + '),跳过。', 'red')
switchMission()
} else {
logger('暂不支持处理此类型:' + _type + ',跳过。', 'red')
switchMission()
}
}
}
}
// 新增的 playMedia 函数,处理自动播放限制
async function playMedia(playFunction) {
const tryPlayMedia = () => {
return new Promise((resolve, reject) => {
try {
const playRes = playFunction();
if (playRes) {
playRes.then(resolve).catch(reject);
} else {
resolve();
}
} catch (err) {
reject(err);
}
});
};
try {
await tryPlayMedia();
return true;
} catch (err) {
console.error(err);
if (String(err).includes("failed because the user didn't interact with the document first")) {
return new Promise((resolve) => {
const content = "播放音视频失败,由于浏览器的用户隐私保护措施,如果要播放带有音量的视频,或者某些无法自动播放音视频的网站,您必须先点击一次页面上的任意位置脚本才能进行音视频的播放,后续无需重新点击。";
if (typeof Swal !== 'undefined' && Swal.fire) {
Swal.fire({
title: "提示",
text: content,
icon: "warning",
confirmButtonText: "确定"
}).then(async () => {
try {
await tryPlayMedia();
resolve(true);
} catch (e) {
console.error(e);
resolve(false);
}
});
} else {
alert(content);
tryPlayMedia().then(() => resolve(true)).catch(() => resolve(false));
}
});
} else if (String(err).includes("The element has no supported sources")) {
logger("当前视频无法播放。", "red");
} else if (String(err).includes("background media was paused to save power") || String(err).includes("interrupted because video-only") || String(err).includes("interrupted by a call to pause")) {
console.log("[GPTJs] 忽略浏览器播放中断:", err);
} else {
logger("播放视频时发生未知错误:" + String(err), "red");
}
return false;
}
}
function missonVideo(dom, obj) {
const { isPassed, otherInfo, property } = obj;
const { _jobid: jobId, name, objectid: objectId, module } = property;
// 同一函数处理视频与音频两种任务,按 module 区分日志/开关
const isAudioTask = module === 'insertaudio';
const taskLabel = isAudioTask ? '音频' : '视频';
if (isAudioTask ? !setting.audio : !setting.video) {
logger(`用户设置不处理${taskLabel}任务,准备开始下一个任务。`, 'red');
return setTimeout(switchMission, 3000);
}
if (!setting.review && isPassed === true) {
logger(`${taskLabel}:${name} 检测已完成,准备处理下一个任务`, 'green');
return switchMission();
}
let mediaType = isAudioTask ? 'audio' : 'video'; // 按任务类型预设,循环里再以 DOM 为准
logger(`处理${taskLabel}:${name},正在解析`);
window._failedVideoQuizzes = {};
let executed = false;
let _parseDebugLogged = false;
// 超时检测:超过 60 秒仍未找到媒体元素则刷新页面
const PARSE_TIMEOUT_MS = 60 * 1000;
const parseStartTime = Date.now();
// 在给定的 iframe 集合(含嵌套 iframe)中搜索 video/audio 元素
// 返回 { media, doc } 或 null
function searchIframesForMedia(iframes) {
for (var fi = 0; fi < iframes.length; fi++) {
var frame = iframes[fi];
var frameDoc;
try {
frameDoc = frame.contentDocument || frame.contentWindow.document;
} catch (_) { continue; }
if (!frameDoc) continue;
// 在当前 iframe 文档中查找
var m = frameDoc.querySelector('video') || frameDoc.querySelector('audio');
if (m) {
if (m.tagName.toLowerCase() === 'audio') mediaType = 'audio';
return { media: m, doc: frameDoc };
}
// 在嵌套 iframe 中查找
try {
var nested = frameDoc.querySelectorAll('iframe');
for (var ni = 0; ni < nested.length; ni++) {
var nDoc;
try { nDoc = nested[ni].contentDocument || nested[ni].contentWindow.document; } catch (_) { continue; }
if (!nDoc) continue;
m = nDoc.querySelector('video') || nDoc.querySelector('audio');
if (m) {
if (m.tagName.toLowerCase() === 'audio') mediaType = 'audio';
return { media: m, doc: nDoc };
}
}
} catch (_) { /* empty */ }
}
return null;
}
const intervalId = setInterval(() => {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') return;
// 超时检测
if (!executed && Date.now() - parseStartTime > PARSE_TIMEOUT_MS) {
clearInterval(intervalId);
logger(`${taskLabel}:${name} 解析超时(超过60秒未加载),即将刷新页面重试`, 'red');
setTimeout(() => { location.reload(); }, 2000);
return;
}
// 优先在传入的 dom 中查找
var result = dom && dom.length > 0 ? searchIframesForMedia(dom) : null;
// fallback:dom 中没找到,搜索页面上所有 .ans-attach-ct 内的 iframe
if (!result) {
try {
var allIframes = _d.querySelectorAll('.ans-attach-ct iframe');
if (allIframes.length > 0) {
result = searchIframesForMedia(allIframes);
}
} catch (_) { /* empty */ }
}
// 诊断日志:首次轮询时输出状态
if (!_parseDebugLogged) {
_parseDebugLogged = true;
var domSrcs = [];
if (dom && dom.length > 0) {
for (var di = 0; di < dom.length; di++) domSrcs.push(dom[di].src || '(无src)');
}
console.log('[GPTJs Debug] dom iframes:', domSrcs, '| 找到媒体:', !!result);
}
if (result && !executed) {
executed = true;
clearInterval(intervalId);
var media = result.media;
var doc = result.doc;
// 计算最终倍速:最高 16×
const userRate = getRate();
const finalRate = userRate;
if (mediaType === 'video' && isPlaybackRateDisabled(doc) && userRate > 1) {
logger(`${name} 超星倍速菜单不可用,已改用脚本强制倍速:${userRate}×`, 'orange');
} else if (userRate > 1) {
logger(`已开启倍速:${userRate}×(高倍速可能被超星判定异常)`, 'orange');
}
logger(`${name} - ${mediaType} 播放成功,开始控制播放(${finalRate}×)`);
const runPlayback = () => {
return new Promise((resolve, reject) => {
// 1. 错误检测定时器
const reloadInterval = setInterval(() => {
const errorDiv = doc.querySelector(".vjs-modal-dialog-content");
if (["视频文件损坏", "网络错误导致视频下载中途失败", "视频因格式不支持", "网络的问题无法加载"].some(
(s) => errorDiv && errorDiv.innerText.includes(s)
)) {
logger("检测到视频/音频加载失败,即将跳过。", "red");
clearInterval(reloadInterval);
if (quizIntervalId) clearInterval(quizIntervalId);
setTimeout(resolve, 3000);
}
}, 3000);
// 2. 视频弹题定时器
const quizIntervalId = mediaType === 'video' ? setInterval(function () {
handleVideoQuiz(doc, media, name);
}, 1500) : null;
// 3. 暂停恢复逻辑 (playFunction)
const playFunction = async () => {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') return;
if (mediaType === 'video' && doc.querySelector('.ans-videoquiz')) return;
if (media.ended === false) {
await new Promise(r => setTimeout(r, 1000));
if (media.paused && media.ended === false) {
media.muted = true;
try {
media.play().then(() => {
hookMediaRate(media, finalRate);
}).catch(async (e) => {
console.log('[GPTJs] 后台恢复播放失败,尝试静音并重新播放:', e);
media.muted = true;
await playMedia(() => media.play());
hookMediaRate(media, finalRate);
});
} catch (err) {
console.error(err);
}
}
}
};
media.addEventListener("pause", playFunction);
// 4. 临近结尾自动恢复 1×(仅当当前为倍速)
let rateRestored = finalRate <= 1;
const onTimeUpdate = () => {
if (!rateRestored && isFinite(media.duration) && media.duration - media.currentTime < 10) {
rateRestored = true;
try { delete media.playbackRate; } catch (_) { /* empty */ }
hookMediaRate(media, 1);
media.removeEventListener('timeupdate', onTimeUpdate);
}
};
if (!rateRestored) {
media.addEventListener('timeupdate', onTimeUpdate);
}
// 5. 播放结束监听
let _playbackResolved = false;
const _doResolve = (reason) => {
if (_playbackResolved) return;
_playbackResolved = true;
logger(`${name} - ${mediaType} ${reason || '已播放完成'}`);
media.removeEventListener("pause", playFunction);
media.removeEventListener('timeupdate', onTimeUpdate);
if (quizIntervalId) clearInterval(quizIntervalId);
clearInterval(reloadInterval);
clearTimeout(_playbackTimeout);
resolve();
};
media.addEventListener("ended", () => { _doResolve('已播放完成'); });
// 6.5 兜底超时:最长等待 10 分钟,防止 ended 事件永远不触发导致卡死
const _playbackTimeout = setTimeout(() => {
_doResolve('播放超时(10分钟),自动跳过');
}, 10 * 60 * 1000);
// 6. 初始播放流程
media.muted = true;
media.currentTime = 0;
setTimeout(() => {
playMedia(() => media.play()).then(() => {
hookMediaRate(media, finalRate);
}).catch(reject);
}, 200);
});
};
runPlayback().then(() => {
setTimeout(switchMission, 1000);
}).catch(err => {
logger(`${name} - 播放异常: ${err},即将切换下一任务`, 'red');
setTimeout(switchMission, 1000);
});
}
}, 2500);
}
// 强锁 playbackRate:用 Object.defineProperty 阻止超星播放器把倍速改回 1
// 失败则降级为 ratechange 监听守护,仍尽力维持倍速
function hookMediaRate(media, rate) {
try { media.playbackRate = rate; } catch (_) { /* empty */ }
try {
Object.defineProperty(media, 'playbackRate', {
configurable: true,
get: function () { return rate; },
set: function () { /* 阻止外部改写 */ }
});
} catch (_) {
try {
media.addEventListener('ratechange', function () {
if (media.playbackRate !== rate) {
try { media.playbackRate = rate; } catch (__) { /* empty */ }
}
});
} catch (___) { /* empty */ }
}
}
// 超星 video.js 播放器若禁用了倍速,倍速菜单会无项 → 视为禁用
function isPlaybackRateDisabled(iframeDocument) {
try {
var items = iframeDocument.querySelectorAll('.vjs-playback-rate .vjs-menu-content .vjs-menu-item');
return items.length === 0;
} catch (_) {
return false;
}
}
function missonBook(dom, obj) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { missonBook(dom, obj) }, 3000);
return;
}
if (setting.task) {
if (obj['jobid'] == undefined) {
logger("当前只处理任务点任务,跳过", 'red')
switchMission()
return
}
}
let jobId = obj['property']['jobid'],
name = obj['property']['bookname'],
jtoken = obj['jtoken'],
knowledgeId = _defaults['knowledgeid'],
courseId = _defaults['courseid'],
clazzId = _defaults['clazzId'];
if (obj['job'] == undefined) {
logger('读书:' + name + '检测已完成,准备执行下一个任务。', 'green')
switchMission()
return
}
$.ajax({
url: _l.protocol + '//' + _l.host + '/ananas/job?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + String(Math.round(new Date())),
method: 'GET',
success: function (res) {
if (res.status) {
logger('读书:' + name + res.msg + ',准备执行下一个任务。', 'green')
} else {
logger('读书:' + name + '处理异常,跳过。', 'red')
}
switchMission()
return
},
})
}
function missonDoucument(dom, obj) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { missonDoucument(dom, obj) }, 3000);
return;
}
if (setting.task) {
if (obj['jobid'] == undefined) {
logger("当前只处理任务点任务,跳过", 'red')
switchMission()
return
}
}
let jobId = obj['property']['jobid'],
name = obj['property']['name'],
jtoken = obj['jtoken'],
knowledgeId = _defaults['knowledgeid'],
courseId = _defaults['courseid'],
clazzId = _defaults['clazzId'];
if (obj['job'] == undefined) {
logger('文档:' + name + '检测已完成,准备执行下一个任务。', 'green')
switchMission()
return
}
$.ajax({
url: _l.protocol + '//' + _l.host + '/ananas/job/document?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + String(Math.round(new Date())),
method: 'GET',
success: function (res) {
if (res.status) {
logger('文档:' + name + res.msg + ',准备执行下一个任务。', 'green')
} else {
logger('文档:' + name + '处理异常,跳过。', 'red')
}
switchMission()
return
},
})
}
function missonRead(dom, obj) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { missonRead(dom, obj) }, 3000);
return;
}
if (setting.task) {
if (obj['jobid'] == undefined) {
logger("当前只处理任务点任务,跳过", 'red')
switchMission()
return
}
}
let jobId = obj['property']['jobid'],
name = obj['property']['title'],
jtoken = obj['jtoken'],
knowledgeId = _defaults['knowledgeid'],
courseId = _defaults['courseid'],
clazzId = _defaults['clazzId'];
if (obj['job'] == undefined) {
logger('阅读:' + name + ',检测已完成,准备执行下一个任务。', 'green')
switchMission()
return
}
$.ajax({
url: _l.protocol + '//' + _l.host + '/ananas/job/readv2?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + String(Math.round(new Date())),
method: 'GET',
success: function (res) {
if (res.status) {
logger('阅读:' + name + res.msg + ',准备执行下一个任务。', 'green')
} else {
logger('阅读:' + name + '处理异常,跳过。', 'red')
}
switchMission()
return
}
})
}
function missonKnowledgeGraph(obj) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(function () { missonKnowledgeGraph(obj) }, 3000);
return;
}
var taskName = (obj['property'] && obj['property']['name']) || '知识图谱';
// 如果已完成(job 字段不存在),直接跳过
if (obj['job'] == undefined) {
logger('知识图谱:' + taskName + ',检测已完成,准备执行下一个任务。', 'green')
switchMission()
return
}
// 在所有 iframe 中查找知识图谱的展开按钮
var clicked = false;
try {
var iframes = document.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
try {
var iframeDoc = iframes[i].contentDocument || iframes[i].contentWindow.document;
if (!iframeDoc) continue;
var packBtn = iframeDoc.querySelector('#graphPackBtn');
if (packBtn) {
// 如果按钮还没展开(没有 active 类),点击它
if (!packBtn.classList.contains('active')) {
packBtn.click();
logger('知识图谱:' + taskName + ',已点击展开按钮', 'green');
} else {
logger('知识图谱:' + taskName + ',已是展开状态', 'green');
}
clicked = true;
break;
}
} catch (e) {
// 跨域 iframe 忽略
}
}
} catch (e) {
console.log('[GPTJs] 查找知识图谱 iframe 异常:', e);
}
if (!clicked) {
logger('知识图谱:' + taskName + ',未找到展开按钮,可能已完成或不在当前页面,跳过', 'orange');
}
// 等待 iframe 内部的 finishJob 完成汇报后再切换下一个任务
setTimeout(function () { switchMission() }, 3000);
}
function missonWork(dom, obj) {
if (!setting.work) {
logger('用户设置不自动处理测验,准备处理下一个任务', 'green')
switchMission()
return
}
let isDo;
if (setting.task) {
logger("当前只处理任务点任务", 'red')
if (obj['jobid'] == undefined ? false : true) {
isDo = true
} else {
isDo = false
}
} else {
logger("当前默认处理所有任务(包括非任务点任务)", 'red')
isDo = true
}
if (isDo) {
if (obj['jobid'] !== undefined) {
var phoneWeb = _l.protocol + '//' + _l.host + '/work/phone/work?workId=' + obj['jobid'].replace('work-', '') + '&courseId=' + _defaults['courseid'] + '&clazzId=' + _defaults['clazzId'] + '&knowledgeId=' + _defaults['knowledgeid'] + '&jobId=' + obj['jobid'] + '&enc=' + obj['enc']
// setTimeout(() => { startDoCyWork(0, dom) }, 3000)
setTimeout(() => { startDoPhoneCyWork(0, dom, phoneWeb) }, 3000)
} else {
setTimeout(() => { startDoCyWork(0, dom) }, 3000)
}
// } else if (!GM_getValue('cando')) {
// logger('存在未完成任务点,脚本已暂停执行,请手动处理后刷新网页。', 'red')
// return
} else {
logger('用户设置只处理属于任务点的任务,准备处理下一个任务', 'green')
switchMission()
return
}
}
function doPhoneWork($dom) {
let $cy = $dom.find('.Wrappadding form')
$subBtn = $cy.find('.zquestions .zsubmit .btn-ok-bottom')
$okBtn = $dom.find('#okBtn')
$saveBtn = $cy.find('.zquestions .zsubmit .btn-save')
let TimuList = $cy.find('.zquestions .Py-mian1')
startDoPhoneTimu(0, TimuList)
}
function startDoPhoneTimu(index, TimuList) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { startDoPhoneTimu(index, TimuList) }, 3000);
return;
}
if (index == TimuList.length) {
if (localStorage.getItem('GPTJsSetting.sub') === 'true') {
logger('测验处理完成,准备自动提交。', 'green')
setTimeout(() => {
$subBtn.click()
setTimeout(() => {
$okBtn.click()
logger('提交成功,准备切换下一个任务。', 'green')
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(() => { switchMission() }, 3000)
}, 3000)
}, 5000)
} else if (localStorage.getItem('GPTJsSetting.force') === 'true') {
logger('测验处理完成,存在无答案题目,由于用户设置了强制提交,准备自动提交。', 'red')
setTimeout(() => {
$subBtn.click()
setTimeout(() => {
$okBtn.click()
logger('提交成功,准备切换下一个任务。', 'green')
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(() => { switchMission() }, 3000)
}, 3000)
}, 5000)
} else {
logger('测验处理完成,存在无答案题目或用户设置不自动提交,自动保存!', 'green')
setTimeout(() => {
$saveBtn.click()
setTimeout(() => {
logger('保存成功,准备切换下一个任务。', 'green')
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(() => { switchMission() }, 3000)
}, 3000)
}, 5000)
}
return
}
// 获取当前题目所属的window对象 (可能是iframe)
let contextWindow = TimuList[index] ? (TimuList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow;
let questionFull = $(TimuList[index]).find('.Py-m1-title').html()
let _question = tidyQuestion(questionFull).replace(/.*?\[.*?题\]\s*\n\s*/, '').trim()
let typeName = questionFull.match(/.*?\[(.*?)]|$/)[1];
let _type = ({
单选题: 0, 单项选择题: 0, 单选: 0, 选择题: 0,
多选题: 1, 多项选择题: 1, 多选: 1,
填空题: 2, 填空: 2,
判断题: 3, 是非题: 3, 判断: 3,
简答题: 4, 简答: 4, 问答题: 4, 名词解释: 4, 论述题: 4, 论述: 4,
计算题: 4, 计算: 4, 分录题: 4, 资料题: 4, 作图题: 4, 其他: 4, 其它: 4, 阅读理解: 4, 阅读: 4, 阅读题: 4, 理解题: 4, 完形填空: 4, 完形: 4, 综合题: 4,
写作题: 5,
翻译题: 6
})[typeName]
let _a = []
let _answerTmpArr
var check_answer_flag = 0;
// 如果题型不在预设类型中,根据DOM结构自动识别题型
if (_type === undefined) {
logger('未知题型: ' + typeName + ',尝试自动识别', 'blue');
// 检查选项列表特征
let singleChoiceList = $(TimuList[index]).find('.answerList.singleChoice li');
let multiChoiceList = $(TimuList[index]).find('.answerList.multiChoice li');
if (singleChoiceList && singleChoiceList.length > 0) {
_type = 0; // 单选题
logger('自动识别为单选题', 'green');
} else if (multiChoiceList && multiChoiceList.length > 0) {
_type = 1; // 多选题
logger('自动识别为多选题', 'green');
} else {
// 检查是否为填空题
let tkList = $(TimuList[index]).find('.blankList2 input');
if (tkList && tkList.length > 0) {
_type = 2; // 填空题
logger('自动识别为填空题', 'green');
} else {
// 判断题等其他情况
let panduanList = $(TimuList[index]).find('.answerList.panduan li');
if (panduanList && panduanList.length > 0) {
_type = 3; // 判断题
logger('自动识别为判断题', 'green');
} else {
// 检查是否为简答题或材料题
let textareaList = $(TimuList[index]).find('textarea');
let editorList = $(TimuList[index]).find('.edui-editor');
if ((textareaList && textareaList.length > 0) || (editorList && editorList.length > 0)) {
_type = 4; // 简答题
logger('自动识别为简答题或材料题', 'green');
}
}
}
}
}
_currentQuestionMeta = { index: index, total: TimuList.length, typeName: typeName }
switch (_type) {
case 0: {
//遍历选项列表
_answerTmpArr = $(TimuList[index]).find('.answerList.singleChoice li')
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers.split('|') })
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if ($(_answerTmpArr[i]).attr('aria-label')) {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30)
} else {
logger(index + 1 + '此题已作答,重做模式下重新作答', 'blue')
// 重做模式:先取消已选选项
$(_answerTmpArr[i]).click()
}
break
}
}
if (check_answer_flag == 0) {
getAnswer(_type, _question).then((agrs) => {
_answerTmpArr = $(TimuList[index]).find('.answerList.singleChoice li')
$.each(_answerTmpArr, (i, t) => {
_a.push(tidyStr($(t).html()).replace(/^[A-Z]\s*\n\s*/, '').trim())
})
let _i = _a.findIndex((item) => item == agrs)
if (_i == -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i == -1) {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
} else {
$(_answerTmpArr[_i]).click()
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
}
}
break
case 1: {
//遍历选项列表
_answerTmpArr = $(TimuList[index]).find('.answerList.multiChoice li')
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '多选题', question: _question, options: mergedAnswers.split('|'), answer_format: "用'|'分割多个答案" })
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if ($(_answerTmpArr[i]).attr('aria-label')) {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30)
} else {
logger(index + 1 + '此题已作答,重做模式下重新作答', 'blue')
// 重做模式:先取消已选选项
$(_answerTmpArr[i]).click()
}
break
}
}
if (check_answer_flag == 0) {
getAnswer(_type, _question).then((agrs) => {
if (agrs == '暂无答案') {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
} else {
_answerTmpArr = $(TimuList[index]).find('.answerList.multiChoice li')
let _multiOptions = []
$.each(_answerTmpArr, (i, t) => {
_multiOptions.push(tidyStr($(t).html()).replace(/^[A-Z]\s*\n\s*/, '').trim())
})
let _matchedAny = false
$.each(_answerTmpArr, (i, t) => {
if (agrs.indexOf(_multiOptions[i]) != -1) {
_matchedAny = true
setTimeout(() => { $(_answerTmpArr[i]).click() }, 300)
}
})
// 如果精确匹配没有命中任何选项,尝试模糊匹配
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs)
for (var fi = 0; fi < fuzzyIndices.length; fi++) {
(function (idx) {
setTimeout(function () { $(_answerTmpArr[idx]).click() }, 300)
})(fuzzyIndices[fi])
}
}
let check = 0
setTimeout(() => {
$.each(_answerTmpArr, (i, t) => {
if (($(_answerTmpArr[i]).attr('class') || '').indexOf('cur') != -1) {
check = 1
}
})
if (check) {
logger('自动答题成功,准备切换下一题', 'green')
} else {
logger('未能正确选择答案,请手动选择,跳过此题', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
}
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}, 1000)
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
}
}
break
case 2: {
// 填空题处理 - 使用全局editors数组 (ExtJS)
let tkList = $(TimuList[index]).find('.blankList2 input')
// 使用属性选择器匹配包含editorIndex的元素
let tkEditorBlocks = $(TimuList[index]).find('[data-editorindex]')
// 检查是否使用UEditor编辑器(手机页面)
if (tkEditorBlocks && tkEditorBlocks.length > 0) {
let firstTextarea = $(TimuList[index]).find('textarea[name^="answer"]')
if (firstTextarea.length > 0 && $(firstTextarea[0]).val() && $(firstTextarea[0]).val().trim() !== '' && !isRedoMode()) {
logger("此题已作答,跳过", "green");
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30);
break
}
_question = buildPrompt({ type: '填空题', question: _question, answer_format: "多个填空用'|'分隔" })
getAnswer(_type, _question).then((agrs) => {
if (agrs == '暂无答案') {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
return
}
let answers = agrs.split('|')
let editorBlocks = $(TimuList[index]).find('[data-editorindex]')
$.each(editorBlocks, (i, block) => {
let editorIndex = $(block).attr('data-editorindex')
let itemId = $(block).attr('data-itemid')
let answerContent = answers[i] || answers[0] || agrs
setTimeout(() => {
try {
let ueditor = null
// 1. 尝试通过 contextWindow.editors 获取
if (contextWindow.editors && contextWindow.editors[editorIndex]) {
ueditor = contextWindow.editors[editorIndex].ueditor
}
// 2. 尝试通过 contextWindow.UE.instants 获取
if (!ueditor && contextWindow.UE && contextWindow.UE.instants) {
let instantKey = 'ueditorInstant' + editorIndex
ueditor = contextWindow.UE.instants[instantKey]
}
// 3. 尝试通过标准ID获取 (ananas-editor-answer + itemId)
if (!ueditor && itemId && contextWindow.UE && contextWindow.UE.getEditor) {
ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId)
}
if (ueditor) {
ueditor.setContent(answerContent)
logger(`填空题第${i + 1}空已填入 (Index: ${editorIndex})`, 'green')
} else {
logger(`填空题第${i + 1}空未找到编辑器实例 (Index: ${editorIndex}, ItemId: ${itemId})`, 'yellow')
}
// 始终尝试更新隐藏的textarea作为兜底
if (itemId) {
let textarea = $('#answer' + itemId)
if (textarea.length > 0) {
textarea.val(answerContent)
try {
if (textarea[0].value === answerContent) {
textarea[0].dispatchEvent(new Event('change'))
textarea[0].dispatchEvent(new Event('input'))
}
} catch (e) { /* empty */ }
}
}
} catch (e) {
logger('填空题填入详情失败:' + e.message, 'red')
}
}, 500 * (i + 1))
})
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time + 300 * editorBlocks.length)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
} else if (tkList && tkList.length > 0) {
// 普通input模式(旧版页面)
if ($(tkList[0]).val() && $(tkList[0]).val().trim() !== '' && !isRedoMode()) {
logger("此题已作答,跳过", "green");
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30);
break
}
getAnswer(_type, _question).then((agrs) => {
if (agrs == '暂无答案') {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
return
}
let answers = agrs.split('|')
let inputList = $(TimuList[index]).find('.blankList2 input')
$.each(inputList, (i, t) => {
setTimeout(() => {
$(t).val(answers[i] || answers[0] || agrs)
// 触发input事件以确保框架能够检测到值的变化
$(t).trigger('input').trigger('change')
}, 200)
})
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
} else {
logger('未找到填空题输入区域,跳过此题', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
break
}
case 3: {
_answerTmpArr = $(TimuList[index]).find('.answerList.panduan li')
$.each(_answerTmpArr, (i, t) => {
_a.push($(t).text().trim())
})
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if ($(_answerTmpArr[i]).attr('aria-label')) {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30)
} else {
logger(index + 1 + '此题已作答,重做模式下重新作答', 'blue')
// 重做模式:先取消已选选项
$(_answerTmpArr[i]).click()
}
break
}
}
if (check_answer_flag == 0) {
_question = buildPrompt({ type: '判断题', question: _question, answer_format: "只回答正确或错误" })
getAnswer(_type, _question).then((agrs) => {
let judgeResult = parseJudgeAnswer(agrs)
if (judgeResult === null) {
logger('答案匹配出错,准备切换下一题', 'red')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
return
}
let _i = findJudgeOptionIndex(_a, judgeResult === 'true')
if (_i === -1) {
logger('未匹配到正确选项,跳过', 'red')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
return
}
setTimeout(() => {
$(_answerTmpArr[_i]).click()
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}, 300)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
}
break
}
case 4: { // 简答题或材料题
_question = buildPrompt({ type: '简答题或材料题', question: _question })
// 查找可能的编辑器区域(通过data-editorindex)
let jdEditorBlocks = $(TimuList[index]).find('[data-editorindex]')
let jdTextareas = $(TimuList[index]).find('textarea[name^="answer"]')
// 检查是否已作答
let jdIsAnswered = false
// 优先处理UEditor编辑器(手机页面)
if (jdEditorBlocks && jdEditorBlocks.length > 0) {
if (jdTextareas.length > 0 && $(jdTextareas[0]).val() && $(jdTextareas[0]).val().trim() !== '' && !isRedoMode()) {
logger(index + 1 + '简答题已作答,准备切换下一题', 'green')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30)
break
}
getAnswer(_type, _question).then((agrs) => {
if (agrs == '暂无答案') {
logger('AI无法匹配答案,请手动完成', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
return
}
// 获取第一个编辑器(简答题通常只有一个)
let firstBlock = jdEditorBlocks.first()
if (firstBlock.length > 0) {
let editorIndex = firstBlock.attr('data-editorindex')
let itemId = firstBlock.attr('data-itemid')
// 如果container上没有itemid,尝试从子元素或关联的textarea找
if (!itemId && jdTextareas.length > 0) {
let tid = $(jdTextareas[0]).attr('id')
if (tid) itemId = tid.replace('answer', '')
}
setTimeout(() => {
try {
let ueditor = null
// 1. 尝试通过 contextWindow.editors 获取
if (contextWindow.editors && contextWindow.editors[editorIndex]) {
ueditor = contextWindow.editors[editorIndex].ueditor
}
// 2. 尝试通过 contextWindow.UE.instants 获取
if (!ueditor && contextWindow.UE && contextWindow.UE.instants) {
let instantKey = 'ueditorInstant' + editorIndex
ueditor = contextWindow.UE.instants[instantKey]
}
// 3. 尝试通过标准ID获取 (ananas-editor-answer + itemId)
if (!ueditor && itemId && contextWindow.UE && contextWindow.UE.getEditor) {
ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId)
}
if (ueditor) {
ueditor.setContent(agrs)
logger(`简答题已填入 (Index: ${editorIndex})`, 'green')
} else {
logger(`简答题未找到编辑器实例 (Index: ${editorIndex}, ItemId: ${itemId})`, 'yellow')
}
// 兜底:更新隐藏textarea
if (jdTextareas.length > 0) {
let ta = $(jdTextareas[0])
ta.val(agrs)
// 触发change/input事件
try {
ta[0].dispatchEvent(new Event('change'))
ta[0].dispatchEvent(new Event('input'))
} catch (e) { /* empty */ }
}
} catch (e) {
logger('简答题填入失败:' + e.message, 'red')
// 尝试直接设置textarea
if (jdTextareas.length > 0) {
$(jdTextareas[0]).val(agrs)
logger('简答题通过textarea填入答案', 'blue')
}
}
}, 500)
} else {
// Fallback direct textarea
if (jdTextareas.length > 0) {
$(jdTextareas[0]).val(agrs)
logger('简答题通过textarea填入答案', 'blue')
}
}
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
}
// 如果没有编辑器,但有textarea,直接使用textarea
else if (jdTextareas && jdTextareas.length > 0) {
// 检查是否已作答
if ($(jdTextareas[0]).val() && $(jdTextareas[0]).val().trim() !== '' && !isRedoMode()) {
logger(index + 1 + '简答题已作答,准备切换下一题', 'green')
jdIsAnswered = true
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30)
} else {
getAnswer(_type, _question).then((agrs) => {
if (agrs == '暂无答案') {
logger('AI无法匹配答案,请手动完成', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
} else {
$(jdTextareas[0]).val(agrs)
$(jdTextareas[0]).trigger('input').trigger('change')
logger('简答题自动答题成功,准备切换下一题', 'green')
}
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}).catch((agrs) => {
if (agrs && agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
}
}
// 如果以上方法都失败
else {
logger('无法找到简答题输入区域,请手动完成', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
break
}
case 5: {
getAnswer(_type, _question).then((agrs) => {
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
logger('此类型题目无法区分单/多选,请手动选择答案', 'red')
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
}
})
break
}
default:
logger('暂不支持处理此类型题目:' + questionFull.match(/.*?\[(.*?)]|$/)[1] + ',跳过!请手动作答。', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time)
break
}
}
// 轮询方式查找iframe内的元素
// 解决getElement使用MutationObserver在iframe文档替换时失效导致脚本卡死的问题
// 第四个参数 maxAttempts:最大轮询次数,超时返回 null(上层 if(!el) 会触发自身重试),避免永久卡死
function pollForElement(iframeDom, selector, interval, maxAttempts) {
interval = interval || 2000;
maxAttempts = (typeof maxAttempts === 'number' && maxAttempts > 0) ? maxAttempts : 60; // 默认 60 次 ≈ 2 分钟
return new Promise(function (resolve) {
var attempts = 0;
var check = function () {
try {
var doc = $(iframeDom).contents()[0];
if (doc) {
var el = doc.querySelector(selector);
if (el) return resolve(el);
}
} catch (e) { /* iframe未就绪或跨域 */ }
attempts++;
if (attempts >= maxAttempts) {
logger('框架等待超时(' + Math.round(attempts * interval / 1000) + 's),上层将重试或跳过', 'red');
return resolve(null);
}
if (attempts % 15 === 0) {
logger('框架仍在加载中,已等待' + (attempts * interval / 1000) + '秒...请耐心等待', 'orange');
}
setTimeout(check, interval);
};
check();
});
}
// 后台标签页防休眠:用 Web Audio 静音振荡器维持音频上下文,缓解浏览器对隐藏标签页的节流。
// 浏览器策略要求自动播放需用户手势;首次失败时会挂监听到 click/keydown 上再试一次。
var _ne21AntiSleepStarted = false;
function setupAntiSleep() {
if (_ne21AntiSleepStarted) return;
var AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return;
function tryStart() {
if (_ne21AntiSleepStarted) return;
try {
var ac = new AC();
var osc = ac.createOscillator();
var gain = ac.createGain();
gain.gain.value = 0; // 完全静音
osc.connect(gain);
gain.connect(ac.destination);
osc.start();
_ne21AntiSleepStarted = true;
// visibilitychange 后某些浏览器会 suspend AudioContext,主动恢复
document.addEventListener('visibilitychange', function () {
try { if (ac.state === 'suspended') ac.resume(); } catch (_) { /* empty */ }
});
} catch (_) { /* ignore */ }
}
tryStart();
if (!_ne21AntiSleepStarted) {
var onUserGesture = function () {
tryStart();
if (_ne21AntiSleepStarted) {
document.removeEventListener('click', onUserGesture, true);
document.removeEventListener('keydown', onUserGesture, true);
document.removeEventListener('touchstart', onUserGesture, true);
}
};
document.addEventListener('click', onUserGesture, true);
document.addEventListener('keydown', onUserGesture, true);
document.addEventListener('touchstart', onUserGesture, true);
}
}
// 周期性自动刷新页面,防止脚本因不可恢复的 iframe 卡死/超星接口响应异常而死循环
// 默认关闭,需用户在 localStorage 写入 GPTJsSetting.autoRefresh=true 启用
// 默认 30 分钟,可通过 GPTJsSetting.autoRefreshMinutes 改写(最小 5 分钟)
function setupAutoRefresh() {
var stored = localStorage.getItem('GPTJsSetting.autoRefresh');
var enabled = stored !== null ? (stored === 'true') : false;
if (!enabled) return;
var minutes = parseInt(localStorage.getItem('GPTJsSetting.autoRefreshMinutes'), 10);
if (!isFinite(minutes) || minutes < 5) minutes = 30;
setTimeout(function () {
logger('已达到自动刷新时间(' + minutes + '分钟),3 秒后刷新页面...', 'orange');
setTimeout(function () { try { window.location.reload(); } catch (_) { /* empty */ } }, 3000);
}, minutes * 60 * 1000);
}
function startDoPhoneCyWork(index, doms, phoneWeb) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { startDoPhoneCyWork(index, doms, phoneWeb) }, 3000);
return;
}
if (index == doms.length) {
logger('此页面全部测验已处理完毕!准备进行下一项任务')
setTimeout(missonStart, 5000)
return
}
logger('等待测验框架加载...', 'purple')
pollForElement(doms[index], 'iframe').then(element => {
let workIframe = element
if (!workIframe) {
setTimeout(() => { startDoPhoneCyWork(index, doms, phoneWeb) }, 5000)
return
}
let workStatus = $(workIframe).contents().find('.newTestCon .newTestTitle .testTit_status').text().trim()
if (!workStatus) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(missonStart, 2000)
return
}
if (isRedoMode() && workStatus.indexOf("已完成") != -1) {
logger('测验:' + (index + 1) + ',重做模式下重新处理已完成测验', 'blue')
$(workIframe).attr('src', phoneWeb)
getElement($(doms[index]).contents()[0], 'iframe[src="' + phoneWeb + '"]', 60000).then((element) => {
if (!element) { logger('测验框架加载超时(60s),跳过此测验', 'red'); setTimeout(() => { startDoPhoneCyWork(index + 1, doms, phoneWeb) }, 3000); return; }
setTimeout(() => { doPhoneWork($(element).contents()) }, 3000)
})
} else if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1 || workStatus.indexOf("重做") != -1 || workStatus.indexOf("未达到") != -1) {
var isRedoStatus = workStatus.indexOf("重做") != -1 || workStatus.indexOf("未达到") != -1
logger('测验:' + (index + 1) + (isRedoStatus ? ',未达到及格线,准备重做...' : ',准备处理此测验...'), 'purple')
$(workIframe).attr('src', phoneWeb)
getElement($(doms[index]).contents()[0], 'iframe[src="' + phoneWeb + '"]', 60000).then((element) => {
if (!element) { logger('测验框架加载超时(60s),跳过此测验', 'red'); setTimeout(() => { startDoPhoneCyWork(index + 1, doms, phoneWeb) }, 3000); return; }
setTimeout(() => { doPhoneWork($(element).contents()) }, 3000)
})
} else if (workStatus.indexOf('待批阅') != -1) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',测验待批阅,跳过', 'red')
setTimeout(() => { startDoPhoneCyWork(index + 1, doms, phoneWeb) }, 5000)
} else if (workStatus.indexOf('已完成') != -1) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',已完成,跳过', 'green')
setTimeout(() => { startDoPhoneCyWork(index + 1, doms, phoneWeb) }, 5000)
} else {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',未知状态[' + workStatus + '],跳过', 'red')
setTimeout(() => { startDoPhoneCyWork(index + 1, doms, phoneWeb) }, 5000)
}
})
}
function startDoCyWork(index, doms) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { startDoCyWork(index, doms) }, 3000);
return;
}
if (index == doms.length) {
logger('此页面全部测验已处理完毕!准备进行下一项任务')
setTimeout(missonStart, 5000)
return
}
logger('等待测验框架加载...', 'purple')
pollForElement(doms[index], 'iframe').then(element => {
let workIframe = element
if (!workIframe) {
setTimeout(() => { startDoCyWork(index, doms) }, 5000)
return
}
let workStatus = $(workIframe).contents().find(".newTestCon .newTestTitle .testTit_status").text().trim()
if (!workStatus) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(missonStart, 2000)
return
}
if (isRedoMode() && workStatus.indexOf("已完成") != -1) {
logger('测验:' + (index + 1) + ',重做模式下重新处理已完成测验', 'blue')
setTimeout(() => { doWork(index, doms, workIframe) }, 5000)
} else if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1 || workStatus.indexOf("重做") != -1 || workStatus.indexOf("未达到") != -1) {
var isRedoStatus = workStatus.indexOf("重做") != -1 || workStatus.indexOf("未达到") != -1
logger('测验:' + (index + 1) + (isRedoStatus ? ',未达到及格线,准备重做...' : ',准备处理此测验...'), 'purple')
setTimeout(() => { doWork(index, doms, workIframe) }, 5000)
} else if (workStatus.indexOf('待批阅') != -1) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',测验待批阅,跳过', 'red')
setTimeout(() => { startDoCyWork(index + 1, doms) }, 5000)
} else if (workStatus.indexOf('已完成') != -1) {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',已完成,跳过', 'green')
setTimeout(() => { startDoCyWork(index + 1, doms) }, 5000)
} else {
_mlist.splice(0, 1)
_domList.splice(0, 1)
logger('测验:' + (index + 1) + ',未知状态[' + workStatus + '],跳过', 'red')
setTimeout(() => { startDoCyWork(index + 1, doms) }, 5000)
}
})
}
function getElement(parent, selector, timeout = 0) {
/**
* Author cxxjackie
* From https://bbs.tampermonkey.net.cn
*/
return new Promise(resolve => {
var result = parent.querySelector(selector);
if (result) return resolve(result);
var timer;
const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
if (mutationObserver) {
const observer = new mutationObserver(mutations => {
for (var mutation of mutations) {
for (var addedNode of mutation.addedNodes) {
if (addedNode instanceof Element) {
result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
if (result) {
observer.disconnect();
timer && clearTimeout(timer);
return resolve(result);
}
}
}
}
});
observer.observe(parent, {
childList: true,
subtree: true
});
if (timeout > 0) {
timer = setTimeout(() => {
observer.disconnect();
return resolve(null);
}, timeout);
}
} else {
const listener = e => {
if (e.target instanceof Element) {
result = e.target.matches(selector) ? e.target : e.target.querySelector(selector);
if (result) {
parent.removeEventListener('DOMNodeInserted', listener, true);
timer && clearTimeout(timer);
return resolve(result);
}
}
};
parent.addEventListener('DOMNodeInserted', listener, true);
if (timeout > 0) {
timer = setTimeout(() => {
parent.removeEventListener('DOMNodeInserted', listener, true);
return resolve(null);
}, timeout);
}
}
});
}
function missonHomeWork() {
logger('开始处理作业', 'green')
let $_homeworktable = $('.mark_table').find('form')
let TimuList = $_homeworktable.find('.questionLi')
doHomeWork(0, TimuList)
}
function doHomeWork(index, TiMuList) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { doHomeWork(index, TiMuList) }, 3000);
return;
}
if (index == TiMuList.length) {
logger('作业题目已全部完成', 'green')
return
}
// 检测是否为阅读理解、完形填空等含有子题的大题
let $subQuestions = $(TiMuList[index]).find('.reading_answer');
if ($subQuestions && $subQuestions.length > 0) {
logger('检测到复合大题(含有 ' + $subQuestions.length + ' 个子题),开始处理子题...', 'blue');
let parentQuestion = tidyQuestion($(TiMuList[index]).find('h3.mark_name').html() || '');
let currentTypeName = $(TiMuList[index]).attr('typename') || '阅读理解';
_currentQuestionMeta = { index: index, total: TiMuList.length, typeName: currentTypeName };
processSubQuestions(0, $subQuestions, parentQuestion, () => setting.time, () => { doHomeWork(index + 1, TiMuList) });
return;
}
// Helper function for handling normal textareas
function handleNormalTextarea(textareaList, jdt, index, TiMuList) {
if (!textareaList || textareaList.length === 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
return;
}
getAnswer(4, jdt).then((agrs) => {
$.each(textareaList, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name');
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
logger('自动答题成功,准备切换下一题', 'green');
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time + 200 * textareaList.length);
}).catch((agrs) => {
if (agrs && agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
}
});
}
let typeName = $(TiMuList[index]).attr('typename');
let _type = ({
单选题: 0, 单项选择题: 0, 单选: 0,
多选题: 1, 多项选择题: 1, 多选: 1,
填空题: 2, 填空: 2,
判断题: 3, 是非题: 3, 判断: 3,
简答题: 4, 简答: 4, 问答题: 4, 名词解释: 4, 论述题: 4, 论述: 4,
计算题: 4, 计算: 4, 分录题: 4, 资料题: 4, 作图题: 4, 其他: 4, 其它: 4, 阅读理解: 4, 阅读: 4, 阅读题: 4, 理解题: 4, 完形填空: 4, 完形: 4, 综合题: 4,
写作题: 5,
翻译题: 6
})[typeName]
_currentQuestionMeta = { index: index, total: TiMuList.length, typeName: typeName }
let _questionFull = $(TiMuList[index]).find('.mark_name').html()
let _question = tidyQuestion(_questionFull).replace(/^[(].*?[)]/, '').trim()
let _a = []
let _answerTmpArr, _textareaList
var check_answer_flag = 0;
// 如果题型不在预设类型中,根据DOM结构自动识别题型
if (_type === undefined) {
logger('未知题型: ' + typeName + ',尝试自动识别', 'blue');
// 检查是否有选择题特征
_answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p')
if (_answerTmpArr && _answerTmpArr.length > 0) {
_type = 0; // 假定为单选题
// 检查是否有多个可选项
let multiChoiceCheck = $(TiMuList[index]).find('.stem_answer input[type="checkbox"]');
if (multiChoiceCheck && multiChoiceCheck.length > 0) {
_type = 1; // 多选题
logger('自动识别为多选题', 'green');
} else {
logger('自动识别为单选题', 'green');
}
}
// 检查是否有文本输入框特征
else {
_textareaList = $(TiMuList[index]).find('.stem_answer').find('.subEditor textarea, .Answer .divText textarea, .Answer .divText .textDIV textarea, textarea[name^="answerEditor"], .edui-editor textarea');
if (_textareaList && _textareaList.length > 0) {
_type = 4; // 简答题
logger('自动识别为简答题', 'green');
}
}
}
switch (_type) {
case 0: {
_answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p')
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers.split('|') })
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if (($(_answerTmpArr[i]).parent().find('span').attr('class') || '').indexOf('check_answer') == -1) {
//没有被选择
} else {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
} else {
logger(index + 1 + '此题已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[i]).parent().click()
}
break
}
}
if (check_answer_flag == 0) {
getAnswer(_type, _question).then((agrs) => {
$.each(_answerTmpArr, (i, t) => {
_a.push(tidyStr($(t).html()))
})
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[index]).find('.mark_name')
// logger("timuele题目标签:"+timuele.html())
timuele.html(timuele.html() + "
" + agrs)
}
let _i = _a.findIndex((item) => item == agrs)
if (_i == -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i == -1) {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
} else {
setTimeout(() => {
let check = $(_answerTmpArr[_i]).parent().find('span').attr('class') || ''
if (check.indexOf('check_answer') == -1) {
$(_answerTmpArr[_i]).parent().click()
}
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}, 300)
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}
})
}
}
break
case 1: {
_answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p')
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '多选题', question: _question, options: mergedAnswers.split('|'), answer_format: "用'|'分割多个答案" })
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if (($(_answerTmpArr[i]).parent().find('span').attr('class') || '').indexOf('check_answer') == -1) {
//没有被选择
} else {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
break
} else {
logger(index + 1 + '此题已作答,重做模式下取消旧答案', 'blue')
$(_answerTmpArr[i]).parent().click()
// 不break,继续取消其他已选选项
}
}
}
if (check_answer_flag == 0) {
getAnswer(_type, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[index]).find('.mark_name')
// logger("timuele题目标签:"+timuele.html())
timuele.html(timuele.html() + "
" + agrs)
}
let _multiOptions = []
$.each(_answerTmpArr, (i, t) => {
_multiOptions.push(tidyStr($(t).html()))
})
let _matchedAny = false
$.each(_answerTmpArr, (i, t) => {
if (agrs.indexOf(_multiOptions[i]) != -1) {
_matchedAny = true
setTimeout(() => {
let check = $(_answerTmpArr[i]).parent().find('span').attr('class') || ''
if (check.indexOf('check_answer_dx') == -1) {
$(_answerTmpArr[i]).parent().click()
}
}, 300)
}
});
// 如果精确匹配没有命中任何选项,尝试模糊匹配
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs)
for (var fi = 0; fi < fuzzyIndices.length; fi++) {
(function (idx) {
setTimeout(function () {
let check = $(_answerTmpArr[idx]).parent().find('span').attr('class') || ''
if (check.indexOf('check_answer_dx') == -1) {
$(_answerTmpArr[idx]).parent().click()
}
}, 300)
})(fuzzyIndices[fi])
}
}
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}
})
}
}
break
case 2: {
_question = buildPrompt({ type: '填空题', question: _question, answer_format: "用'|'分割多个答案" });
_textareaList = findAnswerTextareas($(TiMuList[index]));
if (!_textareaList || _textareaList.length === 0) {
logger('未找到填空题输入区域,跳过此题', 'red');
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
break
}
// 判断题目是否已作答(用 try/catch 防止 UE.getEditor 抛错;id 为空则回退 name)
let _id = $(_textareaList[0]).attr('id') || $(_textareaList[0]).attr('name');
let firstAnswered = false;
try {
if (_id && UE.getEditor(_id) && UE.getEditor(_id).getContent && UE.getEditor(_id).getContent() !== '') firstAnswered = true;
} catch (e) { firstAnswered = false; }
if (firstAnswered && !isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green');
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30);
} else {
getAnswer(_type, _question).then((agrs) => {
let _answerTmpArr = (agrs || '').split('|');
$.each(_textareaList, (i, t) => {
let _currentId = $(t).attr('id') || $(t).attr('name');
let val = _answerTmpArr[i] !== undefined ? _answerTmpArr[i] : (_answerTmpArr[0] || agrs);
setTimeout(() => {
try { UE.getEditor(_currentId).setContent(val) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time + 200 * _textareaList.length);
logger('自动答题成功,准备切换下一题', 'green');
}).catch((agrs) => {
if (agrs && agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
}
});
}
break
}
case 3: {
_answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p')
$.each(_answerTmpArr, (i, t) => {
_a.push($(t).text().trim())
})
//判断题目是否已作答
for (let i = 0; i < _answerTmpArr.length; i++) {
if (($(_answerTmpArr[i]).parent().find('span').attr('class') || '').indexOf('check_answer') == -1) {
//没有被选择
} else {
if (!isRedoMode()) {
logger(index + 1 + '此题已作答,准备切换下一题', 'green')
check_answer_flag = 1;
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
} else {
logger(index + 1 + '此题已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[i]).parent().click()
}
break
}
}
if (check_answer_flag == 0) {
_question = buildPrompt({ type: '判断题', question: _question, answer_format: "只回答正确或错误" })
getAnswer(_type, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $(TiMuList[index]).find('.mark_name')
timuele.html(timuele.html() + "
" + agrs)
}
let judgeResult = parseJudgeAnswer(agrs)
if (judgeResult === null) {
logger('答案匹配出错,准备切换下一题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
return
}
let _i = findJudgeOptionIndex(_a, judgeResult === 'true')
if (_i === -1) {
logger('未匹配到正确选项,跳过', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
return
}
setTimeout(() => {
let check = $(_answerTmpArr[_i]).parent().find('span').attr('class') || ''
if (check.indexOf('check_answer') == -1) {
$(_answerTmpArr[_i]).parent().click()
}
}, 300)
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
}
})
}
break
}
case 4: {
let _answerEle = findAnswerTextareas($(TiMuList[index]))
if (!_answerEle || _answerEle.length === 0) {
logger((index + 1) + ' 未找到文本作答区域,跳过此题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
break
}
let _isAnswered4 = false
$.each(_answerEle, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnswered4 = true } catch (e) { /* ignore */ }
})
if (_isAnswered4 && !isRedoMode()) {
logger((index + 1) + ' 此题已作答,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
break
}
let jdt = buildPrompt({ type: typeName || '简答题', question: _question, answer_format: "用50字简要回答" })
getAnswer(_type, jdt).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $(TiMuList[index]).find('.mark_name')
timuele.html(timuele.html() + "
" + agrs)
}
$.each(_answerEle, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time + 200 * _answerEle.length);
}).catch(() => {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
});
}
break
case 5: {
let _answerEle5 = findAnswerTextareas($(TiMuList[index]))
if (!_answerEle5 || _answerEle5.length === 0) {
logger((index + 1) + ' 未找到写作题文本框,跳过此题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
break
}
// 已作答检测
let _isAnswered5 = false
$.each(_answerEle5, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnswered5 = true } catch (e) { /* ignore */ }
})
if (_isAnswered5 && !isRedoMode()) {
logger((index + 1) + ' 此题已作答,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
break
}
let jdt5 = buildPrompt({ type: typeName || '写作题', question: _question, answer_format: "用英文根据题目进行写作" })
getAnswer(_type, jdt5).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $(TiMuList[index]).find('.mark_name')
timuele.html(timuele.html() + "
" + agrs)
}
$.each(_answerEle5, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time + 200 * _answerEle5.length);
}).catch(() => {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
});
}
break
case 6: {
let _answerEle6 = findAnswerTextareas($(TiMuList[index]))
if (!_answerEle6 || _answerEle6.length === 0) {
logger((index + 1) + ' 未找到翻译题文本框,跳过此题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
break
}
// 已作答检测
let _isAnswered6 = false
$.each(_answerEle6, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnswered6 = true } catch (e) { /* ignore */ }
})
if (_isAnswered6 && !isRedoMode()) {
logger((index + 1) + ' 此题已作答,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, 30)
break
}
let jdt6 = buildPrompt({ type: typeName || '翻译题', question: _question, answer_format: "中文英文互译" })
getAnswer(_type, jdt6).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $(TiMuList[index]).find('.mark_name')
timuele.html(timuele.html() + "
" + agrs)
}
$.each(_answerEle6, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
logger('自动答题成功,准备切换下一题', 'green')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time + 200 * _answerEle6.length);
}).catch(() => {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
});
}
break
default: {
if (_type === undefined) {
logger('无法识别题型:' + typeName + ',跳过此题', 'red')
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time)
} else {
// 尝试获取文本输入区域
_textareaList = $(TiMuList[index]).find('.stem_answer').find('textarea, .subEditor textarea, .divText textarea, .eidtDiv textarea, .divText .edui-editor, textarea[name^="answerEditor"]');
if (_textareaList && _textareaList.length > 0) {
logger('检测到文本输入区域,尝试回答', 'green');
let jdt = buildPrompt({ type: typeName || '未知题型', question: _question, answer_format: "请根据题目作答" })
// 检查是否有富文本编辑器特有的textarea
let editorTextareas = $(TiMuList[index]).find('.stem_answer textarea[name^="answerEditor"]');
if (editorTextareas && editorTextareas.length > 0) {
// 使用富文本编辑器ID
let editorId = $(editorTextareas[0]).attr('id');
if (editorId) {
getAnswer(_type || 4, jdt).then((agrs) => {
setTimeout(() => { UE.getEditor(editorId).setContent(agrs) }, 300);
logger('使用富文本编辑器ID回答成功,准备切换下一题', 'green');
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
}).catch((agrs) => {
if (agrs['c'] == 0) {
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
}
});
} else {
logger('找到富文本编辑器但无法获取ID,改用普通方法', 'yellow');
// 如果没有ID,退回到常规处理
handleNormalTextarea(_textareaList, jdt, index, TiMuList);
}
} else {
// 处理普通文本输入区域
handleNormalTextarea(_textareaList, jdt, index, TiMuList);
}
} else {
logger('无法处理此题型:' + typeName + ',跳过此题', 'red');
setTimeout(() => { doHomeWork(index + 1, TiMuList) }, setting.time);
}
}
}
}
}
function missonExam() {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(missonExam, 3000);
return;
}
let $_examtable = $('.mark_table').find('.whiteDiv')
let _questionFull = tidyStr($_examtable.find('h3.mark_name').html().trim())
let typeName = _questionFull.match(/[(](.*?),.*?分[)]|$/)[1];
let _qType = ({
单选题: 0, 单项选择题: 0, 单选: 0,
多选题: 1, 多项选择题: 1, 多选: 1,
填空题: 2, 填空: 2,
判断题: 3, 是非题: 3, 判断: 3,
简答题: 4, 简答: 4, 问答题: 4, 名词解释: 4, 论述题: 4, 论述: 4,
计算题: 4, 计算: 4, 分录题: 4, 资料题: 4, 作图题: 4, 其他: 4, 其它: 4, 阅读理解: 4, 阅读: 4, 阅读题: 4, 理解题: 4, 完形填空: 4, 完形: 4, 综合题: 4,
写作题: 5,
翻译题: 6
})[typeName]
// 尝试从导航栏获取当前题号
let _examCurIdx = null
try {
let $curLi = $('.mark_table .mark_li_list li.active, .mark_table .mark_li_list li.current')
if ($curLi.length) _examCurIdx = parseInt($curLi.text().trim()) - 1
} catch (_) { /* empty */ }
_currentQuestionMeta = { index: isFinite(_examCurIdx) ? _examCurIdx : null, total: null, typeName: typeName }
let _question = tidyQuestion(_questionFull.replace(/[(].*?分[)]/, '').replace(/^\s*/, ''))
let $_ansdom = $_examtable.find('#submitTest').find('.stem_answer')
// 检测是否为阅读理解、完形填空等含有子题的大题
let $subQuestions = $_ansdom.find('.reading_answer');
if ($subQuestions && $subQuestions.length > 0) {
logger('检测到复合大题(含有 ' + $subQuestions.length + ' 个子题),开始处理子题...', 'blue');
let parentQuestion = tidyQuestion($_examtable.find('h3.mark_name').html() || '');
_currentQuestionMeta = { index: _examCurIdx, total: null, typeName: typeName || '阅读理解' };
processSubQuestions(0, $subQuestions, parentQuestion, () => setting.time, () => { toNextExam() });
return;
}
let _answerTmpArr;
let _a = []
function handleStandardExamTextarea(standardTextareas, _question) {
logger('检测到标准文本输入区域,尝试回答', 'green');
let jdt = buildPrompt({ type: typeName || '未知题型', question: _question, answer_format: "请根据题目作答" })
getAnswer(4, jdt).then((agrs) => {
$.each(standardTextareas, (i, t) => {
let _id = $(t).attr('id')
setTimeout(() => { UE.getEditor(_id).setContent(agrs) }, 300)
})
logger('自动答题成功,准备切换下一题', 'green')
toNextExam()
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam()
}
})
}
// 如果题型不在预设类型中,根据DOM结构自动识别题型
if (_qType === undefined) {
logger('未知题型: ' + typeName + ',尝试自动识别', 'blue');
// 检查是否有选择题特征
_answerTmpArr = $_ansdom.find('.clearfix.answerBg .fl.answer_p');
if (_answerTmpArr && _answerTmpArr.length > 0) {
_qType = 0; // 假定为单选题
// 检查是否有多个可选项
let multiChoiceCheck = $_ansdom.find('.clearfix.answerBg input[type="checkbox"]');
if (multiChoiceCheck && multiChoiceCheck.length > 0) {
_qType = 1; // 多选题
logger('自动识别为多选题', 'green');
} else {
logger('自动识别为单选题', 'green');
}
}
// 检查是否有文本输入框特征
else {
let _textareaList = $_ansdom.find('.Answer .divText .subEditor textarea, .Answer .divText .edui-editor, .Answer .divText textarea, textarea[name^="answerEditor"]');
if (_textareaList && _textareaList.length > 0) {
_qType = 4; // 简答题
logger('自动识别为简答题', 'green');
}
}
}
switch (_qType) {
case 0: {
_answerTmpArr = $_ansdom.find('.clearfix.answerBg .fl.answer_p')
// 已作答前置检查:兼容 check_answer 与 check_answer_dx(indexOf('check_answer') 都能命中)
let _answeredIdxE0 = -1
for (let _ai = 0; _ai < _answerTmpArr.length; _ai++) {
let _cls = $(_answerTmpArr[_ai]).parent().find('span').attr('class') || ''
if (_cls.indexOf('check_answer') !== -1) { _answeredIdxE0 = _ai; break }
}
if (_answeredIdxE0 !== -1 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
if (_answeredIdxE0 !== -1 && isRedoMode()) {
logger('此题已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[_answeredIdxE0]).parent().click()
}
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers.split('|') })
getAnswer(_qType, _question).then((agrs) => {
$.each(_answerTmpArr, (i, t) => {
_a.push(tidyStr($(t).html()))
})
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $_examtable.find('h3.mark_name')
// logger(timuele.html())
timuele.html(timuele.html() + agrs)
}
let _i = _a.findIndex((item) => item == agrs)
if (_i == -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i == -1) {
logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red')
setTimeout(toNextExam, 5000)
} else {
setTimeout(() => {
if (($(_answerTmpArr[_i]).parent().find('span').attr('class') || '').indexOf('check_answer') == -1) {
//好学生模式,ABCD加粗
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[_i]).parent().find('span').css('font-weight', 'bold');
} else {
setTimeout(() => { $(_answerTmpArr[_i]).parent().click() }, 300)
}
logger('自动答题成功,准备切换下一题', 'green')
toNextExam()
} else {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
}
}, 300)
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam()
}
})
}
break
case 1: {
_answerTmpArr = $_ansdom.find('.clearfix.answerBg .fl.answer_p')
// 已作答前置检查(多选用 check_answer_dx)
let _alreadyAnsweredE1 = $_ansdom.find('.clearfix.answerBg span.check_answer_dx, .clearfix.answerBg span.check_answer').length > 0
if (_alreadyAnsweredE1 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
if (_alreadyAnsweredE1 && isRedoMode()) {
logger('此题已作答,重做模式下重新作答', 'blue')
$.each(_answerTmpArr, function (_i2, _t2) {
var _cls2 = $(_t2).parent().find('span').attr('class') || ''
if (_cls2.indexOf('check_answer') !== -1) {
$(_t2).parent().click()
}
})
}
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '多选题', question: _question, options: mergedAnswers.split('|'), answer_format: "用'|'分割多个答案" })
getAnswer(_qType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $_examtable.find('h3.mark_name')
// logger(timuele.html())
timuele.html(timuele.html() + agrs)
}
{
let _multiOptions = []
$.each(_answerTmpArr, (i, t) => {
_multiOptions.push(tidyStr($(t).html()))
})
let _matchedAny = false
$.each(_answerTmpArr, (i, t) => {
if (agrs.indexOf(_multiOptions[i]) != -1) {
_matchedAny = true
//好学生模式,ABCD加粗
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[i]).parent().find('span').css('font-weight', 'bold');
} else {
setTimeout(() => { $(_answerTmpArr[i]).parent().click() }, 300)
}
}
});
// 如果精确匹配没有命中任何选项,尝试模糊匹配
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs)
for (var fi = 0; fi < fuzzyIndices.length; fi++) {
(function (idx) {
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[idx]).parent().find('span').css('font-weight', 'bold');
} else {
setTimeout(function () { $(_answerTmpArr[idx]).parent().click() }, 300)
}
})(fuzzyIndices[fi])
}
}
logger('自动答题成功,准备切换下一题', 'green')
toNextExam()
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam()
}
})
}
break
case 2: {
let _textareaList = $_ansdom.find('.Answer .divText .subEditor textarea')
// 已作答前置检查:任一 textarea 已有内容即视为已作答
let _alreadyAnsweredE2 = false
$.each(_textareaList, function (_i2, _t2) {
let _eid = $(_t2).attr('id')
try {
if (_eid && typeof UE !== 'undefined' && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') {
_alreadyAnsweredE2 = true
}
} catch (_e) { /* ignore */ }
})
if (_alreadyAnsweredE2 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
if (_alreadyAnsweredE2 && isRedoMode()) {
logger('此题已作答,重做模式下重新作答', 'blue')
$.each(_textareaList, function (_i2, _t2) {
let _eid = $(_t2).attr('id')
try { if (_eid && UE.getEditor(_eid)) UE.getEditor(_eid).setContent('') } catch (_e) { /* ignore */ }
})
}
_question = buildPrompt({ type: '填空题', question: _question, answer_format: "用'|'分割多个答案" });
// logger(_textareaList)
getAnswer(_qType, _question).then((agrs) => {
let _answerTmpArr = agrs.split('|')
$.each(_textareaList, (i, t) => {
let _id = $(t).attr('id')
setTimeout(() => { UE.getEditor(_id).setContent(_answerTmpArr[i]) }, 300)
})
logger('自动答题成功,准备切换下一题', 'green')
toNextExam()
}).catch((agrs) => {
if (agrs && agrs['c'] == 0) { toNextExam() }
})
break
}
case 3: {
_answerTmpArr = $_ansdom.find('.clearfix.answerBg .fl.answer_p')
// 已作答前置检查
let _answeredIdxE3 = -1
for (let _ai = 0; _ai < _answerTmpArr.length; _ai++) {
let _cls = $(_answerTmpArr[_ai]).parent().find('span').attr('class') || ''
if (_cls.indexOf('check_answer') !== -1) { _answeredIdxE3 = _ai; break }
}
if (_answeredIdxE3 !== -1 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
if (_answeredIdxE3 !== -1 && isRedoMode()) {
logger('此题已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[_answeredIdxE3]).parent().click()
}
_question = buildPrompt({ type: '判断题', question: _question, answer_format: "只回答正确或错误" });
$.each(_answerTmpArr, (i, t) => {
_a.push($(t).text().trim())
})
getAnswer(_qType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $_examtable.find('h3.mark_name')
timuele.html(timuele.html() + agrs)
}
let judgeResult = parseJudgeAnswer(agrs)
if (judgeResult === null) {
logger('答案匹配出错,准备切换下一题', 'red')
toNextExam()
return
}
let _i = findJudgeOptionIndex(_a, judgeResult === 'true')
if (_i === -1) {
logger('未匹配到正确选项,跳过', 'red')
toNextExam()
return
}
if (($(_answerTmpArr[_i]).parent().find('span').attr('class') || '').indexOf('check_answer') == -1) {
//好学生模式,ABCD加粗
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
setTimeout(() => { $(_answerTmpArr[_i]).parent().find('span').css('font-weight', 'bold'); }, 300)
} else {
$(_answerTmpArr[_i]).parent().click()
}
logger('自动答题成功,准备切换下一题', 'green')
toNextExam()
} else {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
}
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam()
}
})
break
}
case 4: {
let _answerEle = findAnswerTextareas($_ansdom)
if (!_answerEle || _answerEle.length === 0) { toNextExam(); break }
// 已作答检测
let _isAnsweredE4 = false
$.each(_answerEle, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnsweredE4 = true } catch (e) { /* ignore */ }
})
if (_isAnsweredE4 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
let jdt = buildPrompt({ type: typeName || '简答题', question: _question, answer_format: "用50字简要回答" })
getAnswer(_qType, jdt).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $_examtable.find('h3.mark_name')
timuele.html(timuele.html() + agrs)
}
$.each(_answerEle, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
setTimeout(toNextExam, 300 + 200 * _answerEle.length);
}).catch(() => { toNextExam(); });
}
break
case 5: {
let _answerEle = findAnswerTextareas($_ansdom)
if (!_answerEle || _answerEle.length === 0) { toNextExam(); break }
// 已作答检测
let _isAnsweredE5 = false
$.each(_answerEle, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnsweredE5 = true } catch (e) { /* ignore */ }
})
if (_isAnsweredE5 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
let jdt = buildPrompt({ type: typeName || '写作题', question: _question, answer_format: "用英文根据题目进行写作" })
getAnswer(_qType, jdt).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $_examtable.find('h3.mark_name')
timuele.html(timuele.html() + agrs)
}
$.each(_answerEle, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
setTimeout(toNextExam, 300 + 200 * _answerEle.length);
}).catch(() => { toNextExam(); });
}
break
case 6: {
let _answerEle = findAnswerTextareas($_ansdom)
if (!_answerEle || _answerEle.length === 0) { toNextExam(); break }
// 已作答检测
let _isAnsweredE6 = false
$.each(_answerEle, function (i, t) {
let _eid = $(t).attr('id') || $(t).attr('name')
try { if (_eid && UE.getEditor(_eid) && UE.getEditor(_eid).getContent && UE.getEditor(_eid).getContent() !== '') _isAnsweredE6 = true } catch (e) { /* ignore */ }
})
if (_isAnsweredE6 && !isRedoMode()) {
logger('此题已作答,准备切换下一题', 'green')
toNextExam()
break
}
let jdt = buildPrompt({ type: typeName || '翻译题', question: _question, answer_format: "中文英文互译" })
getAnswer(_qType, jdt).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $_examtable.find('h3.mark_name')
timuele.html(timuele.html() + agrs)
}
$.each(_answerEle, (i, t) => {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(() => {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200);
});
setTimeout(toNextExam, 300 + 200 * _answerEle.length);
}).catch(() => { toNextExam(); });
}
break
default: {
if (_qType === undefined) {
logger('无法识别题型:' + typeName + ',跳过此题', 'red')
toNextExam()
} else {
// 尝试获取文本输入区域
// 查找所有可能的文本输入区域
let standardTextareas = $_ansdom.find('.Answer .divText .subEditor textarea');
let richEditors = $_ansdom.find('.Answer .divText .edui-editor');
// 首先检查是否有材料题特有的富文本编辑器textarea
let editorTextareas = $_ansdom.find('textarea[name^="answerEditor"]');
if (editorTextareas && editorTextareas.length > 0) {
logger('检测到材料题富文本编辑器,尝试回答', 'green');
let editorId = $(editorTextareas[0]).attr('id');
if (editorId) {
let jdt = buildPrompt({ type: '材料题', question: _question, answer_format: "请根据材料详细回答" })
getAnswer(4, jdt).then((agrs) => {
setTimeout(() => { UE.getEditor(editorId).setContent(agrs) }, 300);
logger('材料题自动答题成功,准备切换下一题', 'green');
toNextExam();
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam();
}
});
} else {
logger('找到材料题编辑器但无法获取ID,尝试其他方法', 'yellow');
handleStandardExamTextarea(standardTextareas, _question);
}
}
// 处理标准文本区域
else if (standardTextareas && standardTextareas.length > 0) {
handleStandardExamTextarea(standardTextareas, _question);
}
// 处理其他类型的富文本编辑器
else if (richEditors && richEditors.length > 0) {
logger('检测到富文本编辑器,尝试查找编辑器ID', 'green');
// 尝试在页面中查找所有可能的编辑器ID
let editorScripts = $('script:contains("UE.getEditor")');
let editorIdMatch = null;
if (editorScripts && editorScripts.length > 0) {
// 从脚本中提取编辑器ID
let scriptContent = editorScripts.text();
let matches = scriptContent.match(/UE\.getEditor\(['"](.*?)['"]/);
if (matches && matches.length > 1) {
editorIdMatch = matches[1];
logger('从脚本中发现编辑器ID: ' + editorIdMatch, 'green');
}
}
if (editorIdMatch) {
let jdt = buildPrompt({ type: '材料题', question: _question, answer_format: "请根据材料详细回答" })
getAnswer(4, jdt).then((agrs) => {
setTimeout(() => { UE.getEditor(editorIdMatch).setContent(agrs) }, 300);
logger('使用脚本找到的编辑器ID回答成功,准备切换下一题', 'green');
toNextExam();
}).catch((agrs) => {
if (agrs['c'] == 0) {
toNextExam();
}
});
} else {
logger('无法找到有效的编辑器ID,跳过此题', 'red');
toNextExam();
}
}
else {
logger('无法处理此题型:' + typeName + ',跳过此题', 'red');
toNextExam();
}
}
}
}
}
function toNextExam() {
if (localStorage.getItem('GPTJsSetting.examTurn') === 'true') {
let $_examtable = $('.mark_table').find('.whiteDiv')
let $nextbtn = $_examtable.find('.nextDiv a.jb_btn')
setTimeout(() => {
$nextbtn.click()
}, setting.examTurnTime ? 2000 + (Math.floor(Math.random() * 5 + 1) * 1000) : 2000)
} else {
logger('用户设置不自动跳转下一题,请手动点击', 'blue')
}
}
// ============== 整卷预览页面(一页多题)自动答题 ==============
// 适配 /mooc-ans/mooc2/exam/preview 与 /ans/mooc2/exam/preview
// 不自动跳转、不自动交卷;叠加随机抖动
function missonExamPreview() {
logger('进入整卷预览页面,开始处理考试', 'green')
let TiMuList = $('.mark_table').find('.questionLi')
if (!TiMuList || TiMuList.length === 0) {
logger('未解析到题目,请确认页面已渲染', 'red')
return
}
logger('共解析到 ' + TiMuList.length + ' 道题', 'blue')
doExamPreview(0, TiMuList)
}
// 题间间隔:基础 setting.time + 0~1500ms 抖动,模仿真人节奏
function getExamPreviewDelay() {
let base = (setting && setting.time) ? setting.time : 2500
return base + Math.floor(Math.random() * 1500)
}
// 解析 questionLi 题型:返回 { type: 0~6 | undefined, typeName: string }
function getExamPreviewType($timu) {
let typeMap = {
单选题: 0, 单项选择题: 0, 单选: 0,
多选题: 1, 多项选择题: 1, 多选: 1,
填空题: 2, 填空: 2,
判断题: 3, 是非题: 3, 判断: 3,
简答题: 4, 简答: 4, 问答题: 4, 名词解释: 4, 论述题: 4, 论述: 4,
计算题: 4, 计算: 4, 分录题: 4, 资料题: 4, 作图题: 4, 其他: 4, 其它: 4, 阅读理解: 4, 阅读: 4, 阅读题: 4, 理解题: 4, 完形填空: 4, 完形: 4, 综合题: 4,
写作题: 5,
翻译题: 6
}
let typeName = $timu.attr('typename')
if (typeName && typeMap[typeName] !== undefined) {
return { type: typeMap[typeName], typeName: typeName }
}
let prefixText = $timu.find('.colorShallow').text() || $timu.find('.mark_name').text() || ''
let m = prefixText.match(/(单选题|多选题|填空题|判断题|简答题|论述题|写作题|翻译题)/)
if (m && typeMap[m[1]] !== undefined) {
return { type: typeMap[m[1]], typeName: m[1] }
}
let qid = $timu.attr('data') || $timu.find('.questionId').val() || $timu.find('input.questionId').val()
if (qid) {
let typeVal = $('[name="type' + qid + '"]').val()
if (typeVal !== undefined && typeVal !== null && typeVal !== '') {
let n = parseInt(typeVal)
if (!isNaN(n) && n >= 0 && n <= 6) {
return { type: n, typeName: typeName || ('类型' + n) }
}
}
}
let $opts = $timu.find('.answerBg .answer_p')
if ($opts && $opts.length > 0) {
let hasCheckbox = $timu.find('.answerBg input[type="checkbox"]').length > 0
return { type: hasCheckbox ? 1 : 0, typeName: typeName || (hasCheckbox ? '多选题' : '单选题') }
}
let $textareas = $timu.find('textarea[name^="answerEditor"], .subEditor textarea')
if ($textareas && $textareas.length > 0) {
return { type: 4, typeName: typeName || '简答题' }
}
return { type: undefined, typeName: typeName || '未知' }
}
function doExamPreview(index, TiMuList) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { doExamPreview(index, TiMuList) }, 3000);
return;
}
if (index >= TiMuList.length) {
logger('整卷预览答题已完成,请人工核对后手动交卷', 'green')
return
}
// 检测是否为阅读理解、完形填空等含有子题的大题
let $subQuestions = $(TiMuList[index]).find('.reading_answer');
if ($subQuestions && $subQuestions.length > 0) {
logger('检测到复合大题(含有 ' + $subQuestions.length + ' 个子题),开始处理子题...', 'blue');
let parentQuestion = tidyQuestion($(TiMuList[index]).find('h3.mark_name').html() || '');
let currentTypeName = $(TiMuList[index]).attr('typename') || '阅读理解';
_currentQuestionMeta = { index: index, total: TiMuList.length, typeName: currentTypeName };
processSubQuestions(0, $subQuestions, parentQuestion, getExamPreviewDelay, () => { doExamPreview(index + 1, TiMuList) });
return;
}
let $timu = $(TiMuList[index])
let typeInfo = getExamPreviewType($timu)
let _type = typeInfo.type
let typeName = typeInfo.typeName
let _questionFull = $timu.find('.mark_name').html() || ''
let _question = tidyQuestion(_questionFull).replace(/^[(].*?[)]/, '').trim()
let _a = []
let _answerTmpArr, _textareaList
let alreadyAnswered = 0
let prefix = '第' + (index + 1) + '题: '
function nextSoon() {
setTimeout(function () { doExamPreview(index + 1, TiMuList) }, getExamPreviewDelay())
}
function nextFast() {
setTimeout(function () { doExamPreview(index + 1, TiMuList) }, 30)
}
_currentQuestionMeta = { index: index, total: TiMuList.length, typeName: typeName }
if (_type === undefined) {
logger(prefix + '无法识别题型(' + typeName + '),跳过此题', 'red')
return nextSoon()
}
switch (_type) {
case 0: {
_answerTmpArr = $timu.find('.answerBg .answer_p')
if (!_answerTmpArr || _answerTmpArr.length === 0) {
logger(prefix + '未找到选项,跳过', 'red')
return nextSoon()
}
let mergedAnswers = []
_answerTmpArr.each(function () {
mergedAnswers.push($(this).text().replace(/[ABCD]/g, '').trim())
})
let prompt = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers })
for (let i = 0; i < _answerTmpArr.length; i++) {
let cls = $(_answerTmpArr[i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer') !== -1) {
if (!isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
alreadyAnswered = 1
} else {
logger(prefix + '已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[i]).parent().click()
}
break
}
}
if (alreadyAnswered) return nextFast()
getAnswer(_type, prompt).then(function (agrs) {
$.each(_answerTmpArr, function (i, t) { _a.push(tidyStr($(t).html())) })
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $timu.find('.mark_name')
timuele.html(timuele.html() + '
' + agrs)
}
let _i = _a.findIndex(function (item) { return item === agrs })
if (_i === -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i === -1) {
logger(prefix + 'AI无法完美匹配正确答案,请手动选择', 'red')
return nextSoon()
}
setTimeout(function () {
let cls = $(_answerTmpArr[_i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer') === -1) {
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[_i]).parent().find('span').css('font-weight', 'bold')
} else {
$(_answerTmpArr[_i]).parent().click()
}
}
logger(prefix + '自动答题成功', 'green')
nextSoon()
}, 300)
}).catch(function () { nextSoon() })
break
}
case 1: {
_answerTmpArr = $timu.find('.answerBg .answer_p')
if (!_answerTmpArr || _answerTmpArr.length === 0) {
logger(prefix + '未找到选项,跳过', 'red')
return nextSoon()
}
let mergedAnswers = []
_answerTmpArr.each(function () {
mergedAnswers.push($(this).text().replace(/[ABCD]/g, '').trim())
})
let prompt = buildPrompt({ type: '多选题', question: _question, options: mergedAnswers, answer_format: "用'|'分割多个答案" })
for (let i = 0; i < _answerTmpArr.length; i++) {
let cls = $(_answerTmpArr[i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer') !== -1) {
if (!isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
alreadyAnswered = 1
break
} else {
logger(prefix + '已作答,重做模式下取消旧答案', 'blue')
$(_answerTmpArr[i]).parent().click()
}
}
}
if (alreadyAnswered) return nextFast()
getAnswer(_type, prompt).then(function (agrs) {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $timu.find('.mark_name')
timuele.html(timuele.html() + '
' + agrs)
}
let _multiOptions = []
$.each(_answerTmpArr, function (i, t) {
_multiOptions.push(tidyStr($(t).html()))
})
let _matchedAny = false
$.each(_answerTmpArr, function (i, t) {
if (agrs.indexOf(_multiOptions[i]) !== -1) {
_matchedAny = true
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[i]).parent().find('span').css('font-weight', 'bold')
} else {
let cls = $(_answerTmpArr[i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer_dx') === -1) {
setTimeout(function () { $(_answerTmpArr[i]).parent().click() }, 300)
}
}
}
})
// 如果精确匹配没有命中任何选项,尝试模糊匹配
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs)
for (var fi = 0; fi < fuzzyIndices.length; fi++) {
(function (idx) {
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[idx]).parent().find('span').css('font-weight', 'bold')
} else {
let cls = $(_answerTmpArr[idx]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer_dx') === -1) {
setTimeout(function () { $(_answerTmpArr[idx]).parent().click() }, 300)
}
}
})(fuzzyIndices[fi])
}
}
logger(prefix + '自动答题成功', 'green')
nextSoon()
}).catch(function () { nextSoon() })
break
}
case 2: {
_textareaList = $timu.find('textarea[name^="answerEditor"]')
if (!_textareaList || _textareaList.length === 0) {
_textareaList = $timu.find('.subEditor textarea')
}
if (!_textareaList || _textareaList.length === 0) {
logger(prefix + '未找到填空文本框,跳过', 'red')
return nextSoon()
}
let isAnswered = false
$.each(_textareaList, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
try {
if (_id && UE.getEditor(_id) && UE.getEditor(_id).getContent && UE.getEditor(_id).getContent() !== '') {
isAnswered = true
}
} catch (e) { /* ignore */ }
})
if (isAnswered && !isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
return nextFast()
}
let prompt = buildPrompt({ type: '填空题', question: _question, answer_format: "用'|'分割多个答案" })
getAnswer(_type, prompt).then(function (agrs) {
let parts = (agrs || '').split('|')
$.each(_textareaList, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
let val = parts[i] !== undefined ? parts[i] : (parts[parts.length - 1] || '')
setTimeout(function () {
try { UE.getEditor(_id).setContent(val) } catch (e) { /* ignore */ }
}, 300 + i * 200)
})
logger(prefix + '自动答题成功', 'green')
nextSoon()
}).catch(function () { nextSoon() })
break
}
case 3: {
_answerTmpArr = $timu.find('.answerBg .answer_p')
if (!_answerTmpArr || _answerTmpArr.length === 0) {
logger(prefix + '未找到判断选项,跳过', 'red')
return nextSoon()
}
$.each(_answerTmpArr, function (i, t) { _a.push($(t).text().trim()) })
for (let i = 0; i < _answerTmpArr.length; i++) {
let cls = $(_answerTmpArr[i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer') !== -1) {
if (!isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
alreadyAnswered = 1
} else {
logger(prefix + '已作答,重做模式下重新作答', 'blue')
$(_answerTmpArr[i]).parent().click()
}
break
}
}
if (alreadyAnswered) return nextFast()
let prompt = buildPrompt({ type: '判断题', question: _question, answer_format: "只回答正确或错误" })
getAnswer(_type, prompt).then(function (agrs) {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
let timuele = $timu.find('.mark_name')
timuele.html(timuele.html() + '
' + agrs)
}
let judgeResult = parseJudgeAnswer(agrs)
let _i = judgeResult !== null ? findJudgeOptionIndex(_a, judgeResult === 'true') : -1
if (_i === -1) {
logger(prefix + '答案匹配出错,跳过', 'red')
return nextSoon()
}
setTimeout(function () {
let cls = $(_answerTmpArr[_i]).parent().find('span').attr('class') || ''
if (cls.indexOf('check_answer') === -1) {
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$(_answerTmpArr[_i]).parent().find('span').css('font-weight', 'bold')
} else {
$(_answerTmpArr[_i]).parent().click()
}
}
logger(prefix + '自动答题成功', 'green')
nextSoon()
}, 300)
}).catch(function () { nextSoon() })
break
}
case 4: {
let _answerEle = findAnswerTextareas($timu)
if (!_answerEle || _answerEle.length === 0) {
logger(prefix + '未找到答题文本框,跳过', 'red')
return nextSoon()
}
let isAnswered = false
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
try {
if (_id && UE.getEditor(_id) && UE.getEditor(_id).getContent && UE.getEditor(_id).getContent() !== '') {
isAnswered = true
}
} catch (e) { /* ignore */ }
})
if (isAnswered && !isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
return nextFast()
}
let prompt = buildPrompt({ type: typeName || '简答题', question: _question, answer_format: "用50字简要回答" })
getAnswer(_type, prompt).then(function (agrs) {
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(function () {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200)
})
logger(prefix + '自动答题成功', 'green')
nextSoon()
}).catch(function () { nextSoon() })
break
}
case 5: {
let _answerEle = findAnswerTextareas($timu)
if (!_answerEle || _answerEle.length === 0) {
logger(prefix + '未找到答题文本框,跳过', 'red')
return nextSoon()
}
// 已作答检测
let isAnswered5 = false
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
try {
if (_id && UE.getEditor(_id) && UE.getEditor(_id).getContent && UE.getEditor(_id).getContent() !== '') {
isAnswered5 = true
}
} catch (e) { /* ignore */ }
})
if (isAnswered5 && !isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
return nextFast()
}
let prompt = buildPrompt({ type: typeName || '写作题', question: _question, answer_format: "用英文根据题目进行写作" })
getAnswer(_type, prompt).then(function (agrs) {
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(function () {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200)
})
logger(prefix + '自动答题成功', 'green')
nextSoon()
}).catch(function () { nextSoon() })
break
}
case 6: {
let _answerEle = findAnswerTextareas($timu)
if (!_answerEle || _answerEle.length === 0) {
logger(prefix + '未找到答题文本框,跳过', 'red')
return nextSoon()
}
// 已作答检测
let isAnswered6 = false
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
try {
if (_id && UE.getEditor(_id) && UE.getEditor(_id).getContent && UE.getEditor(_id).getContent() !== '') {
isAnswered6 = true
}
} catch (e) { /* ignore */ }
})
if (isAnswered6 && !isRedoMode()) {
logger(prefix + '已作答,跳过', 'green')
return nextFast()
}
let prompt = buildPrompt({ type: typeName || '翻译题', question: _question, answer_format: "中文英文互译" })
getAnswer(_type, prompt).then(function (agrs) {
$.each(_answerEle, function (i, t) {
let _id = $(t).attr('id') || $(t).attr('name')
setTimeout(function () {
try { UE.getEditor(_id).setContent(agrs) } catch (e) { /* ignore */ }
}, 300 + i * 200)
})
logger(prefix + '自动答题成功', 'green')
nextSoon()
}).catch(function () { nextSoon() })
break
}
default: {
logger(prefix + '无法处理此题型: ' + typeName + ',跳过', 'red')
nextSoon()
}
}
}
function refreshCourseList() {
let _p = parseUrlParams()
return new Promise((resolve, reject) => {
$.ajax({
url: _l.protocol + '//' + _l.host + '/mycourse/studentstudycourselist?courseId=' + _p['courseid'] + '&chapterId=' + _p['knowledgeid'] + '&clazzid=' + _p['clazzid'] + '&mooc2=1',
type: 'GET',
dateType: 'html',
success: function (res) {
resolve(res)
}
})
})
}
var _ne21ThinkingCount = 0;
function showThinking() {
_ne21ThinkingCount++;
if (_ne21ThinkingCount === 1) {
$('#ne-21thinking', window.parent.document).addClass('ne21-active');
}
}
function hideThinking() {
if (_ne21ThinkingCount > 0) _ne21ThinkingCount--;
if (_ne21ThinkingCount === 0) {
$('#ne-21thinking', window.parent.document).removeClass('ne21-active');
}
}
// 构造结构化提示词,使 AI 能更精确地理解题目并回答。
// 入参 opts:
// - type: 题型(如 单选题/多选题/判断题/填空题/简答题/写作题/翻译题)
// - question: 题干
// - options: 选项数组(可选)
// - answer_format: 答案格式说明(可选,如 "用'|'分割多个答案"、"只回答正确或错误")
// 返回 { payload, display }:
// - payload: 发送给 AI 的 JSON 字符串(更精确,便于 AI 解析)
// - display: 用于用户日志展示的简洁文本(仅含题干与选项,不含题型/答案格式等元信息)
function buildPrompt(opts) {
opts = opts || {}
let q = opts.question != null ? String(opts.question) : ''
let payloadObj = {}
if (opts.type) payloadObj.type = String(opts.type)
if (opts.answer_format) payloadObj.answer_format = String(opts.answer_format)
payloadObj.question = q
if (Array.isArray(opts.options) && opts.options.length > 0) {
payloadObj.options = opts.options.map(function (s) { return String(s == null ? '' : s).trim() })
}
let payload = JSON.stringify(payloadObj, null, 2)
let display = q
if (payloadObj.options && payloadObj.options.length) {
display += '\n' + payloadObj.options.join(' | ')
}
return { payload: payload, display: display }
}
// 全局复用函数:递归处理复合大题的子小题
function processSubQuestions(subIndex, $subQuestions, parentQuestion, getDelay, onComplete) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { processSubQuestions(subIndex, $subQuestions, parentQuestion, getDelay, onComplete) }, 3000);
return;
}
let delay = typeof getDelay === 'function' ? getDelay() : (setting.time || 2500);
if (subIndex === $subQuestions.length) {
logger('该大题所有子题已处理完毕,准备切换下一题', 'green');
setTimeout(onComplete, delay);
return;
}
let $subQ = $($subQuestions[subIndex]);
let subQuestionText = $subQ.find('.reader_answer_tit').text().replace(/^\(\d+\)\s*/, '').trim();
let fullQuestion = "[大题背景]\n" + parentQuestion + "\n\n[小题]\n" + subQuestionText;
let subTypeVal = parseInt($subQ.attr('qtype'));
let readTypeText = $subQ.find('.read_type').text().trim();
let subType = undefined;
if (!isNaN(subTypeVal)) {
subType = subTypeVal;
} else if (readTypeText.indexOf('单选') !== -1) {
subType = 0;
} else if (readTypeText.indexOf('多选') !== -1) {
subType = 1;
} else if (readTypeText.indexOf('填空') !== -1) {
subType = 2;
} else if (readTypeText.indexOf('判断') !== -1) {
subType = 3;
} else if (readTypeText.indexOf('简答') !== -1 || readTypeText.indexOf('问答') !== -1 || readTypeText.indexOf('论述') !== -1) {
subType = 4;
}
if (subType === undefined) {
logger('子题 ' + (subIndex + 1) + ' 题型未知,跳过', 'red');
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
return;
}
// 查找可选项的通用逻辑
let $options = $subQ.find('.answerBg .answer_p');
if ($options.length === 0) $options = $subQ.find('.stem_answer .answer_p');
// 已作答检测
let hasAnswered = false;
if (subType === 0 || subType === 1 || subType === 3) {
for (let i = 0; i < $options.length; i++) {
let cls = $($options[i]).parent().find('span').attr('class') || '';
if (cls.indexOf('check_answer') !== -1) {
hasAnswered = true;
break;
}
}
} else {
let $subTextareas = $subQ.find('textarea, .subEditor textarea, .divText textarea, textarea[name^="answerEditor"]');
$.each($subTextareas, function (i, t) {
let eid = $(t).attr('id') || $(t).attr('name');
try {
if (eid && UE.getEditor(eid) && UE.getEditor(eid).getContent && UE.getEditor(eid).getContent() !== '') {
hasAnswered = true;
}
} catch (e) {
if ($(t).val() && $(t).val().trim() !== '') {
hasAnswered = true;
}
}
});
}
if (hasAnswered && !isRedoMode()) {
logger('子题 ' + (subIndex + 1) + ' 已作答,跳过', 'green');
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, 30);
return;
}
// 统一好学生模式和普通点击的辅助函数
function clickOption($opt) {
if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') {
$opt.parent().find('span').css('font-weight', 'bold');
} else {
$opt.parent().click();
}
}
// 更新当前题目元信息供日志使用
let currentTypeName = $subQuestions.parent().parent().attr('typename') || '阅读理解';
_currentQuestionMeta = { index: _currentQuestionMeta ? _currentQuestionMeta.index : null, total: _currentQuestionMeta ? _currentQuestionMeta.total : null, typeName: currentTypeName + ' 第' + (subIndex + 1) + '小题' };
if (subType === 0 || subType === 3) {
let optionTexts = [];
$options.each(function () {
optionTexts.push($(this).text().replace(/[ABCD]/g, '').trim());
});
let typeStr = subType === 0 ? '单选题' : '判断题';
let answer_format = subType === 0 ? undefined : '只回答正确或错误';
let prompt = buildPrompt({ type: typeStr, question: fullQuestion, options: subType === 0 ? optionTexts : undefined, answer_format: answer_format });
getAnswer(subType, prompt).then(agrs => {
let _a = [];
$options.each((i, t) => {
_a.push(tidyStr($(t).html()));
});
let _i = -1;
if (subType === 3) {
let judgeResult = parseJudgeAnswer(agrs);
if (judgeResult !== null) {
_i = findJudgeOptionIndex(_a, judgeResult === 'true');
}
} else {
_i = _a.findIndex(item => item == agrs);
if (_i === -1) {
_i = findBestFuzzyMatch(_a, agrs);
}
}
if (_i === -1) {
logger('AI未能匹配到正确选项,请手动选择', 'red');
} else {
clickOption($($options[_i]));
logger('子题 ' + (subIndex + 1) + ' 答题成功', 'green');
}
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
}).catch(() => {
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
});
} else if (subType === 1) {
let optionTexts = [];
$options.each(function () {
optionTexts.push($(this).text().replace(/[ABCD]/g, '').trim());
});
let prompt = buildPrompt({ type: '多选题', question: fullQuestion, options: optionTexts, answer_format: "用'|'分割多个答案" });
getAnswer(1, prompt).then(agrs => {
let _multiOptions = [];
$options.each((i, t) => {
_multiOptions.push(tidyStr($(t).html()));
});
let _matchedAny = false;
$options.each((i, t) => {
if (agrs.indexOf(_multiOptions[i]) != -1) {
_matchedAny = true;
let check = $(t).parent().find('span').attr('class') || '';
if (check.indexOf('check_answer_dx') == -1) {
clickOption($(t));
}
}
});
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs);
fuzzyIndices.forEach(idx => {
let check = $($options[idx]).parent().find('span').attr('class') || '';
if (check.indexOf('check_answer_dx') == -1) {
clickOption($($options[idx]));
}
});
}
logger('子题 ' + (subIndex + 1) + ' 答题成功', 'green');
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
}).catch(() => {
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
});
} else if (subType === 2 || subType === 4) {
let $subTextareas = $subQ.find('textarea, .subEditor textarea, .divText textarea, textarea[name^="answerEditor"]');
if ($subTextareas.length === 0) {
logger('未找到该小题的填空/文本输入框,跳过', 'red');
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
return;
}
let typeStr = subType === 2 ? '填空题' : '简答题';
let answer_format = subType === 2 ? "用'|'分割多个答案" : "用50字简要回答";
let prompt = buildPrompt({ type: typeStr, question: fullQuestion, answer_format: answer_format });
getAnswer(subType, prompt).then(agrs => {
let parts = subType === 2 ? (agrs || '').split('|') : [agrs];
$.each($subTextareas, (i, t) => {
let _currentId = $(t).attr('id') || $(t).attr('name');
let val = parts[i] !== undefined ? parts[i] : (parts[0] || agrs);
setTimeout(() => {
try {
if (_currentId && UE.getEditor(_currentId)) {
UE.getEditor(_currentId).setContent(val);
} else {
$(t).val(val);
}
} catch (e) {
$(t).val(val);
}
}, 300 + i * 200);
});
logger('子题 ' + (subIndex + 1) + ' 答题成功', 'green');
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay + 200 * $subTextareas.length);
}).catch(() => {
setTimeout(() => { processSubQuestions(subIndex + 1, $subQuestions, parentQuestion, getDelay, onComplete) }, delay);
});
}
}
function getAnswer(_t, _q, retryCount = 0) {
// 兼容: _q 既可为字符串(旧调用),也可为 buildPrompt() 返回的 { payload, display } 对象
let _payload, _display
if (_q && typeof _q === 'object' && (_q.payload != null || _q.display != null)) {
_payload = _q.payload != null ? String(_q.payload) : ''
_display = _q.display != null ? String(_q.display) : _payload
} else {
_payload = _q == null ? '' : String(_q)
_display = _payload
}
let _qPrefix = ''
if (_currentQuestionMeta) {
var _m = _currentQuestionMeta
_qPrefix = '第' + ((_m.index != null ? _m.index : -1) + 1)
if (_m.total) _qPrefix += '/' + _m.total
_qPrefix += '题 [' + (_m.typeName || '未知') + '] '
}
logger(_qPrefix + '题目:' + _display, 'pink')
// 在日志中插入一条 "AI 思考中..." 占位行, 拿到响应后原地替换为答案/错误, 避免独立提示框反复出现/消失
let _thinkingHtml = '
AI 思考中
' + (retryCount > 0 ? '(第' + (retryCount + 1) + '次)' : '')
let $thinkingLog = logger(_thinkingHtml, 'gray')
return new Promise((resolve, reject) => {
let _u = getCk('_uid') || getCk('UID')
let requestCompleted = false; // 标记请求是否已完成
let longWaitTimer = null; // 长时间等待定时器
// 按用户设置的时间间隔节流:计算本次需要等待的 ms,并预订下一次可发起时间
let _intervalMs = Math.min(60000, setting.time !== undefined ? setting.time : 2500)
let _nowTs = Date.now()
let _waitMs = Math.max(0, _ne21NextAiAllowedAt - _nowTs)
// 预订下一次最早可发起时刻:当前/解锁时间 + 间隔
_ne21NextAiAllowedAt = Math.max(_nowTs, _ne21NextAiAllowedAt) + _intervalMs
if (_waitMs > 0) {
updateLogEntry($thinkingLog, '搜题间隔限制,等待 ' + Math.round(_waitMs / 1000) + 's 后发起请求...', 'gray')
}
// 设置5分钟的监控定时器(覆盖 throttle 等待时间)
longWaitTimer = setTimeout(() => {
if (!requestCompleted) {
requestCompleted = true; // 标记为已完成,避免处理旧响应
// 原地把 "AI 思考中" 行替换为重试提示, 不再追加新日志
updateLogEntry($thinkingLog, '请求超过5分钟未响应,正在重新发起请求...(第' + (retryCount + 1) + '次重试)', 'orange')
// 重新发起请求(递归调用会创建新的 "思考中" 行)
getAnswer(_t, _q, retryCount + 1).then(resolve).catch(reject)
}
}, 300000 + _waitMs); // 5分钟 = 300000毫秒
setTimeout(function () {
if (requestCompleted) return; // 若已因 5 分钟超时进入重试,则不再发送本次请求
// 请求实际发起前,若节流等待行还在显示,更新为"思考中"
if (_waitMs > 0) {
let _resumeHtml = '
AI 思考中
' + (retryCount > 0 ? '(第' + (retryCount + 1) + '次)' : '')
updateLogEntry($thinkingLog, _resumeHtml, 'gray')
}
GM_xmlhttpRequest({
method: 'POST',
url: _host + '/api/v1/cx?v=' + GM_info['script']['version'],
headers: {
'Content-type': 'application/x-www-form-urlencoded',
'Authorization': ''
},
data: 'question=' + encodeURIComponent(_payload) + '&u=' + _u + '&model=' + (localStorage.getItem('GPTJsSetting.model') || $('#modelSelect').val() || 'GPT-3.5'),
timeout: 120000,
onload: function (xhr) {
// 如果请求已经被标记为完成(超时重试了),则忽略此响应
if (requestCompleted) {
logger('收到已超时请求的响应,已忽略', 'gray')
return;
}
requestCompleted = true;
clearTimeout(longWaitTimer); // 清除监控定时器
// 请求完成 — 不再需要 hideThinking, 后面会通过 updateLogEntry 原地替换日志行
if (xhr.status == 200) {
let obj = $.parseJSON(xhr.responseText) || {};
//正则表达式去掉句子最后一个句号
let _answer = obj.data.answer.replace(/。$/, '');
// _answer = obj.data.answer
// logger(_answer)
if (obj.code == 200 && _answer) {
updateLogEntry($thinkingLog, "答案:" + _answer, 'purple')
resolve(_answer.replace(/^[A-Z]\s*\n\s*/, '').trim())
} else if (obj.msg && _answer != '') {
updateLogEntry($thinkingLog, obj.msg, 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
reject({ 'c': 0 })
} else {
updateLogEntry($thinkingLog, '暂无答案', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
reject({ 'c': 0 })
}
} else if (xhr.status == 403) {
updateLogEntry($thinkingLog, '请求过于频繁,请稍后再试', 'red')
reject({ 'c': 403 })
} else if (xhr.status == 500) {
updateLogEntry($thinkingLog, 'ChatGPT官方服务器压力过大,请先保存答案后刷新重试', 'red')
reject({ 'c': 500 })
} else if (xhr.status == 444) {
updateLogEntry($thinkingLog, 'IP异常,已被拉入服务器黑名单,请过几个小时再试', 'red')
reject({ 'c': 444 })
} else if (xhr.status == 400) {
let obj = $.parseJSON(xhr.responseText) || {};
updateLogEntry($thinkingLog, obj.msg, 'red')
reject({ 'c': 400 })
} else if (xhr.status == 222) {
let obj = $.parseJSON(xhr.responseText) || {};
updateLogEntry($thinkingLog, "账户余额不足!", 'red')
reject({ 'c': 222 })
} else {
updateLogEntry($thinkingLog, '请求异常...,请先保存答案后刷新重试,仍然失败请尝试更换模型或联系售后', 'red')
reject({ 'c': 555 })
}
},
ontimeout: function () {
// 如果请求已经被标记为完成(超时重试了),则忽略此超时
if (requestCompleted) {
return;
}
requestCompleted = true;
clearTimeout(longWaitTimer); // 清除监控定时器
// 请求超时 — 不再调用 hideThinking, 直接原地更新日志行
updateLogEntry($thinkingLog, '请求异常...,请先保存答案后刷新重试,仍然失败请尝试更换模型或联系售后', 'red')
reject({ 'c': 666 })
}
});
}, _waitMs);
})
}
function doWork(index, doms, dom) {
$frame_c = $(dom).contents();
let $CyHtml = $frame_c.find('.CeYan')
let TiMuList = $CyHtml.find('.TiMu')
$subBtn = $frame_c.find(".ZY_sub").find(".btnSubmit");
$saveBtn = $frame_c.find(".ZY_sub").find(".btnSave");
startDoWork(index, doms, 0, TiMuList)
}
function startDoWork(index, doms, c, TiMuList) {
if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') {
setTimeout(() => { startDoWork(index, doms, c, TiMuList) }, 3000);
return;
}
if (c == TiMuList.length) {
if (localStorage.getItem('GPTJsSetting.sub') === 'true') {
logger('测验处理完成,准备自动提交。', 'green')
setTimeout(() => {
$subBtn.click()
setTimeout(() => {
$frame_c.find('#confirmSubWin > div > div > a.bluebtn').click()
logger('提交成功,准备切换下一个任务。', 'green')
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(() => { startDoCyWork(index + 1, doms) }, 3000)
}, 3000)
}, 5000)
} else if (localStorage.getItem('GPTJsSetting.force') === 'true') {
logger('测验处理完成,存在无答案题目,由于用户设置了强制提交,准备自动提交。', 'red')
setTimeout(() => {
$subBtn.click()
setTimeout(() => {
$frame_c.find('#confirmSubWin > div > div > a.bluebtn').click()
logger('提交成功,准备切换下一个任务。', 'green')
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(() => { startDoCyWork(index + 1, doms) }, 3000)
}, 3000)
}, 5000)
} else {
logger('测验处理完成,存在无答案题目或者用户设置不提交。', 'red')
}
return
}
let questionFull = $(TiMuList[c]).find('.Zy_TItle.clearfix > div').html()
questionFull = tidyQuestion(questionFull).replace("/
.*?/", "");
let _question = tidyQuestion(questionFull)
let typeName = questionFull.match(/^【(.*?)】|$/)[1];
let _TimuType = {
单选题: 0, 单项选择题: 0, 单选: 0,
多选题: 1, 多项选择题: 1, 多选: 1,
填空题: 2, 填空: 2,
判断题: 3, 是非题: 3, 判断: 3,
简答题: 4, 简答: 4, 问答题: 4, 名词解释: 4, 论述题: 4, 论述: 4,
计算题: 4, 计算: 4, 分录题: 4, 资料题: 4, 作图题: 4, 其他: 4, 其它: 4, 阅读理解: 4, 阅读: 4, 阅读题: 4, 理解题: 4, 完形填空: 4, 完形: 4, 综合题: 4
}[typeName]
_currentQuestionMeta = { index: c, total: TiMuList.length, typeName: typeName }
let _a = []
let _answerTmpArr
// 如果题型不在预设类型中,根据DOM结构自动识别题型
if (_TimuType === undefined) {
logger('未知题型: ' + typeName + ',尝试自动识别', 'blue');
// 检查是否有选择题特征
let choiceList = $(TiMuList[c]).find('.Zy_ulTop li');
if (choiceList && choiceList.length > 0) {
// 检查是否为判断题
if (choiceList.length === 2 &&
($(choiceList[0]).text().includes('对') || $(choiceList[0]).text().includes('√')) &&
($(choiceList[1]).text().includes('错') || $(choiceList[1]).text().includes('×'))) {
_TimuType = 3; // 判断题
logger('自动识别为判断题', 'green');
}
// 检查是否为选择题
else {
// 默认为单选题,后续可根据页面特征判断是否为多选题
_TimuType = 0;
logger('自动识别为单选题', 'green');
}
}
// 检查是否有填空题特征
else {
let fillBlankList = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1');
if (fillBlankList && fillBlankList.length > 0) {
_TimuType = 2; // 填空题
logger('自动识别为填空题', 'green');
} else {
// 检查是否有富文本编辑器
let editorList = $(TiMuList[c]).find('.edui-editor');
if (editorList && editorList.length > 0) {
_TimuType = 4; // 简答题
logger('检测到富文本编辑器,识别为简答题', 'green');
} else {
// 默认当作简答题处理
_TimuType = 4;
logger('无法准确判断题型,按简答题处理', 'blue');
}
}
}
}
switch (_TimuType) {
case 0: {
_answerTmpArr = $(TiMuList[c]).find('.Zy_ulTop li').find('a')
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers.split('|') })
$.each(_answerTmpArr, (i, t) => {
_a.push(tidyStr($(t).html()))
})
getAnswer(_TimuType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[c]).find('.Zy_TItle.clearfix > div')
timuele.html(timuele.html() + agrs)
}
let _i = _a.findIndex((item) => item == agrs)
if (_i == -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i == -1) {
logger('AI无法完美匹配正确答案,请手动选择,跳过', 'red')
localStorage.setItem('GPTJsSetting.sub', false)
} else {
$(_answerTmpArr[_i]).parent().click();
}
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
})
break
}
case 1: {
_answerTmpArr = $(TiMuList[c]).find('.Zy_ulTop li').find('a')
//遍历选项列表
let mergedAnswers = [];
_answerTmpArr.each(function () {
var answerText = $(this).text().replace(/[ABCD]/g, '').trim();
mergedAnswers.push(answerText);
});
mergedAnswers = mergedAnswers.join("|");
_question = buildPrompt({ type: '多选题', question: _question, options: mergedAnswers.split('|'), answer_format: "用'|'分割多个答案" })
getAnswer(_TimuType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[c]).find('.Zy_TItle.clearfix > div')
timuele.html(timuele.html() + agrs)
}
let _multiOptions = []
$.each(_answerTmpArr, (i, t) => {
_multiOptions.push(tidyStr($(t).html()))
})
let _matchedAny = false
$.each(_answerTmpArr, (i, t) => {
if (agrs.indexOf(_multiOptions[i]) != -1) {
_matchedAny = true
$(_answerTmpArr[i]).parent().click();
_a.push(['A', 'B', 'C', 'D', 'E', 'F', 'G'][i])
}
})
// 如果精确匹配没有命中任何选项,尝试模糊匹配
if (!_matchedAny) {
let fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs)
for (var fi = 0; fi < fuzzyIndices.length; fi++) {
$(_answerTmpArr[fuzzyIndices[fi]]).parent().click();
_a.push(['A', 'B', 'C', 'D', 'E', 'F', 'G'][fuzzyIndices[fi]])
}
}
let id = getStr($(TiMuList[c]).find('.Zy_ulTop li:nth-child(1)').attr('onclick'), 'addcheck(', ');').replace('(', '').replace(')', '')
if (_a.length <= 0) {
logger('AI无法完美匹配正确答案,请手动选择,跳过', 'red')
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
} else {
$(TiMuList[c]).find('.Zy_ulTop').parent().find('#answer' + id).val(_a.join(""))
}
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
})
break
}
case 2: {
_question = buildPrompt({ type: '填空题', question: _question, answer_format: "多个填空用'|'分隔" })
let _textareaList = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1')
getAnswer(_TimuType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[c]).find('.Zy_TItle.clearfix > div')
timuele.html(timuele.html() + agrs)
}
let _answerList = agrs.split("|")
$.each(_textareaList, (i, t) => {
setTimeout(() => {
$(t).find('#ueditor_' + i).contents().find('.view p').html(_answerList[i]);
$(t).find('textarea').html('' + _answerList[i] + '
')
}, 300)
})
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
})
break
}
case 3: {
_answerTmpArr = $(TiMuList[c]).find(".Zy_ulTop li").find("a");
let _true = "正确|是|对|√|T|ri";
$.each(_answerTmpArr, (i, t) => {
_a.push(tidyStr($(t).html()));
});
_question = buildPrompt({ type: '判断题', question: _question, answer_format: "只回答正确或错误" })
getAnswer(_TimuType, _question).then((agrs) => {
if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') {
//修改题目将答案插入
let timuele = $(TiMuList[c]).find('.Zy_TItle.clearfix > div')
timuele.html(timuele.html() + agrs)
}
agrs = _true.indexOf(agrs) != -1 ? "对" : "错";
let _i = _a.findIndex((item) => item == agrs);
if (_i == -1) {
// 精确匹配失败,尝试相似度匹配
_i = findBestFuzzyMatch(_a, agrs)
}
if (_i == -1) {
logger("未匹配到正确答案,跳过", "red");
localStorage.setItem('GPTJsSetting.sub', false)
} else {
$(_answerTmpArr[_i]).parent().click();
}
setTimeout(() => {
startDoWork(index, doms, c + 1, TiMuList);
}, setting.time);
}).catch((agrs) => {
setTimeout(() => {
startDoWork(index, doms, c + 1, TiMuList);
}, setting.time);
});
break;
}
case 4: {
let _textareaLista = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1')
getAnswer(_TimuType, _question).then((agrs) => {
if (agrs == '暂无答案') {
// setting.sub = 0
localStorage.setItem('GPTJsSetting.sub', false)
}
let _answerList = agrs.split("#")
$.each(_textareaLista, (i, t) => {
setTimeout(() => {
$(t).find('#ueditor_' + i).contents().find('.view p').html(_answerList[i]);
$(t).find('textarea').html('' + _answerList[i] + '
')
}, 300)
})
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
}).catch((agrs) => {
setTimeout(() => { startDoWork(index, doms, c + 1, TiMuList) }, setting.time)
})
break
}
}
}
function switchMission() {
_mlist.splice(0, 1)
_domList.splice(0, 1)
setTimeout(missonStart, 5000)
}
function tidyStr(s) {
if (s) {
let str = s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').trim().replace(/ /g, '').replace(new RegExp(" ", ("gm")), '').replace(/^\s+/, '').replace(/\s+$/, '');
return str
} else {
return null
}
}
function tidyQuestion(s) {
if (s) {
let str = s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/^\d+[.、]/, '').trim().replace(/ /g, '').replace('javascript:void(0);', '').replace(new RegExp(" ", ("gm")), '').replace(/^\s+/, '').replace(/\s+$/, '');
return str
} else {
return null
}
}
function decryptFont() {
/**
* Author wyn665817
* From https://greasyfork.org/zh-CN/scripts/445007
*/
var $tip = $('style:contains(font-cxsecret)');
if (!$tip.length) return;
var font = $tip.text().match(/base64,([\w\W]+?)'/)[1];
font = Typr.parse(base64ToUint8Array(font))[0];
var table = JSON.parse(GM_getResourceText('Table'));
var match = {};
for (var i = 19968; i < 40870; i++) {
$tip = Typr.U.codeToGlyph(font, i);
if (!$tip) continue;
$tip = Typr.U.glyphToPath(font, $tip);
$tip = md5(JSON.stringify($tip)).slice(24);
match[i] = table[$tip];
}
$('.font-cxsecret').html(function (index, html) {
$.each(match, function (key, value) {
key = String.fromCharCode(key);
key = new RegExp(key, 'g');
value = String.fromCharCode(value);
html = html.replace(key, value);
});
return html;
}).removeClass('font-cxsecret');
}
function base64ToUint8Array(base64) {
var data = window.atob(base64);
var buffer = new Uint8Array(data.length);
for (var i = 0; i < data.length; ++i) {
buffer[i] = data.charCodeAt(i);
}
return buffer;
}