// ==UserScript== // @name ChatGPT学习通作业考试助手 // @version 3.2.3 // @description 本脚本【采用ChatGPT,Claude,Gemini等世界顶尖AI生成答案】 【🥇操作简单】ChatGPT学习通助手,安装即可使用;推荐使用作业、考试自动答题【✨版本特性】版本特性:无题库,全采用AI回复答案,原作者:Ne-21 // @match *://*.chaoxing.com/* // @match *://*.edu.cn/* // @match *://*.org.cn/* // @match *://*.ac.cn/* // @match *://*.xueyinonline.com/* // @match *://*.sslibrary.com/* // @match *://*.neauce.com/* // @match *://*.nbdlib.cn/* // @match *://*.hnsyu.net/* // @match *://*.qutjxjy.cn/* // @match *://*.ynny.cn/* // @match *://*.hnvist.cn/* // @match *://*.fjlecb.cn/* // @match *://*.gdhkmooc.com/* // @match *://*.cugbonline.cn/* // @match *://*.zjelib.cn/* // @match *://*.cqrspx.cn/* // @match *://*.zhihui-yun.com/* // @match *://*.cqie.cn/* // @match *://*.ccqmxx.com/* // @match *://*.jxgmxy.com/* // @match *://*.jnzyjsxy.cn/* // @connect 911285.xyz // @connect gptjs.808860.xyz // @connect 127.0.0.1 // @run-at document-end // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_info // @grant GM_getResourceText // @require https://gptjs.808860.xyz/libs/TyprMd5.js // @require https://gptjs.808860.xyz/libs/sweetalert2-11.1.0.all.min.js // @require https://gptjs.808860.xyz/libs/jquery-3.7.1.min.js // @require https://gptjs.808860.xyz/libs/request-guard.js?v=1 // @resource Table https://gptjs.808860.xyz/libs/table.json // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDEiIGhlaWdodD0iNDEiIHZpZXdCb3g9IjAgMCA0MSA0MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2Utd2lkdGg9IjEuNSIgY2xhc3M9ImgtNiB3LTYiIHJvbGU9ImltZyI+PHRpdGxlPkNoYXRHUFQ8L3RpdGxlPjx0ZXh0IHg9Ii05OTk5IiB5PSItOTk5OSI+Q2hhdEdQVDwvdGV4dD48cGF0aCBkPSJNMzcuNTMyNCAxNi44NzA3QzM3Ljk4MDggMTUuNTI0MSAzOC4xMzYzIDE0LjA5NzQgMzcuOTg4NiAxMi42ODU5QzM3Ljg0MDkgMTEuMjc0NCAzNy4zOTM0IDkuOTEwNzYgMzYuNjc2IDguNjg2MjJDMzUuNjEyNiA2LjgzNDA0IDMzLjk4ODIgNS4zNjc2IDMyLjAzNzMgNC40OTg1QzMwLjA4NjQgMy42Mjk0MSAyNy45MDk4IDMuNDAyNTkgMjUuODIxNSAzLjg1MDc4QzI0Ljg3OTYgMi43ODkzIDIzLjcyMTkgMS45NDEyNSAyMi40MjU3IDEuMzYzNDFDMjEuMTI5NSAwLjc4NTU3NSAxOS43MjQ5IDAuNDkxMjY5IDE4LjMwNTggMC41MDAxOTdDMTYuMTcwOCAwLjQ5NTA0NCAxNC4wODkzIDEuMTY4MDMgMTIuMzYxNCAyLjQyMjE0QzEwLjYzMzUgMy42NzYyNCA5LjM0ODUzIDUuNDQ2NjYgOC42OTE3IDcuNDc4MTVDNy4zMDA4NSA3Ljc2Mjg2IDUuOTg2ODYgOC4zNDE0IDQuODM3NyA5LjE3NTA1QzMuNjg4NTQgMTAuMDA4NyAyLjczMDczIDExLjA3ODIgMi4wMjgzOSAxMi4zMTJDMC45NTY0NjQgMTQuMTU5MSAwLjQ5ODkwNSAxNi4yOTg4IDAuNzIxNjk4IDE4LjQyMjhDMC45NDQ0OTIgMjAuNTQ2NyAxLjgzNjEyIDIyLjU0NDkgMy4yNjggMjQuMTI5M0MyLjgxOTY2IDI1LjQ3NTkgMi42NjQxMyAyNi45MDI2IDIuODExODIgMjguMzE0MUMyLjk1OTUxIDI5LjcyNTYgMy40MDcwMSAzMS4wODkyIDQuMTI0MzcgMzIuMzEzOEM1LjE4NzkxIDM0LjE2NTkgNi44MTIzIDM1LjYzMjIgOC43NjMyMSAzNi41MDEzQzEwLjcxNDEgMzcuMzcwNCAxMi44OTA3IDM3LjU5NzMgMTQuOTc4OSAzNy4xNDkyQzE1LjkyMDggMzguMjEwNyAxNy4wNzg2IDM5LjA1ODcgMTguMzc0NyAzOS42MzY2QzE5LjY3MDkgNDAuMjE0NCAyMS4wNzU1IDQwLjUwODcgMjIuNDk0NiA0MC40OTk4QzI0LjYzMDcgNDAuNTA1NCAyNi43MTMzIDM5LjgzMjEgMjguNDQxOCAzOC41NzcyQzMwLjE3MDQgMzcuMzIyMyAzMS40NTU2IDM1LjU1MDYgMzIuMTExOSAzMy41MTc5QzMzLjUwMjcgMzMuMjMzMiAzNC44MTY3IDMyLjY1NDcgMzUuOTY1OSAzMS44MjFDMzcuMTE1IDMwLjk4NzQgMzguMDcyOCAyOS45MTc4IDM4Ljc3NTIgMjguNjg0QzM5Ljg0NTggMjYuODM3MSA0MC4zMDIzIDI0LjY5NzkgNDAuMDc4OSAyMi41NzQ4QzM5Ljg1NTYgMjAuNDUxNyAzOC45NjM5IDE4LjQ1NDQgMzcuNTMyNCAxNi44NzA3Wk0yMi40OTc4IDM3Ljg4NDlDMjAuNzQ0MyAzNy44ODc0IDE5LjA0NTkgMzcuMjczMyAxNy42OTk0IDM2LjE1MDFDMTcuNzYwMSAzNi4xMTcgMTcuODY2NiAzNi4wNTg2IDE3LjkzNiAzNi4wMTYxTDI1LjkwMDQgMzEuNDE1NkMyNi4xMDAzIDMxLjMwMTkgMjYuMjY2MyAzMS4xMzcgMjYuMzgxMyAzMC45Mzc4QzI2LjQ5NjQgMzAuNzM4NiAyNi41NTYzIDMwLjUxMjQgMjYuNTU0OSAzMC4yODI1VjE5LjA1NDJMMjkuOTIxMyAyMC45OThDMjkuOTM4OSAyMS4wMDY4IDI5Ljk1NDEgMjEuMDE5OCAyOS45NjU2IDIxLjAzNTlDMjkuOTc3IDIxLjA1MiAyOS45ODQyIDIxLjA3MDcgMjkuOTg2NyAyMS4wOTAyVjMwLjM4ODlDMjkuOTg0MiAzMi4zNzUgMjkuMTk0NiAzNC4yNzkxIDI3Ljc5MDkgMzUuNjg0MUMyNi4zODcyIDM3LjA4OTIgMjQuNDgzOCAzNy44ODA2IDIyLjQ5NzggMzcuODg0OVpNNi4zOTIyNyAzMS4wMDY0QzUuNTEzOTcgMjkuNDg4OCA1LjE5NzQyIDI3LjcxMDcgNS40OTgwNCAyNS45ODMyQzUuNTU3MTggMjYuMDE4NyA1LjY2MDQ4IDI2LjA4MTggNS43MzQ2MSAyNi4xMjQ0TDEzLjY5OSAzMC43MjQ4QzEzLjg5NzUgMzAuODQwOCAxNC4xMjMzIDMwLjkwMiAxNC4zNTMyIDMwLjkwMkMxNC41ODMgMzAuOTAyIDE0LjgwODggMzAuODQwOCAxNS4wMDczIDMwLjcyNDhMMjQuNzMxIDI1LjExMDNWMjguOTk3OUMyNC43MzIxIDI5LjAxNzcgMjQuNzI4MyAyOS4wMzc2IDI0LjcxOTkgMjkuMDU1NkMyNC43MTE1IDI5LjA3MzYgMjQuNjk4OCAyOS4wODkzIDI0LjY4MjkgMjkuMTAxMkwxNi42MzE3IDMzLjc0OTdDMTQuOTA5NiAzNC43NDE2IDEyLjg2NDMgMzUuMDA5NyAxMC45NDQ3IDM0LjQ5NTRDOS4wMjUwNiAzMy45ODExIDcuMzg3ODUgMzIuNzI2MyA2LjM5MjI3IDMxLjAwNjRaTTQuMjk3MDcgMTMuNjE5NEM1LjE3MTU2IDEyLjA5OTggNi41NTI3OSAxMC45MzY0IDguMTk4ODUgMTAuMzMyN0M4LjE5ODg1IDEwLjQwMTMgOC4xOTQ5MSAxMC41MjI4IDguMTk0OTEgMTAuNjA3MVYxOS44MDhDOC4xOTM1MSAyMC4wMzc4IDguMjUzMzQgMjAuMjYzOCA4LjM2ODIzIDIwLjQ2MjlDOC40ODMxMiAyMC42NjE5IDguNjQ4OTMgMjAuODI2NyA4Ljg0ODYzIDIwLjk0MDRMMTguNTcyMyAyNi41NTQyTDE1LjIwNiAyOC40OTc5QzE1LjE4OTQgMjguNTA4OSAxNS4xNzAzIDI4LjUxNTUgMTUuMTUwNSAyOC41MTczQzE1LjEzMDcgMjguNTE5MSAxNS4xMTA3IDI4LjUxNiAxNS4wOTI0IDI4LjUwODJMNy4wNDA0NiAyMy44NTU3QzUuMzIxMzUgMjIuODYwMSA0LjA2NzE2IDIxLjIyMzUgMy41NTI4OSAxOS4zMDQ2QzMuMDM4NjIgMTcuMzg1OCAzLjMwNjI0IDE1LjM0MTMgNC4yOTcwNyAxMy42MTk0Wk0zMS45NTUgMjAuMDU1NkwyMi4yMzEyIDE0LjQ0MTFMMjUuNTk3NiAxMi40OTgxQzI1LjYxNDIgMTIuNDg3MiAyNS42MzMzIDEyLjQ4MDUgMjUuNjUzMSAxMi40Nzg3QzI1LjY3MjkgMTIuNDc2OSAyNS42OTI4IDEyLjQ4MDEgMjUuNzExMSAxMi40ODc5TDMzLjc2MzEgMTcuMTM2NEMzNC45OTY3IDE3Ljg0OSAzNi4wMDE3IDE4Ljg5ODIgMzYuNjYwNiAyMC4xNjEzQzM3LjMxOTQgMjEuNDI0NCAzNy42MDQ3IDIyLjg0OSAzNy40ODMyIDI0LjI2ODRDMzcuMzYxNyAyNS42ODc4IDM2LjgzODIgMjcuMDQzMiAzNS45NzQzIDI4LjE3NTlDMzUuMTEwMyAyOS4zMDg2IDMzLjk0MTUgMzAuMTcxNyAzMi42MDQ3IDMwLjY2NDFDMzIuNjA0NyAzMC41OTQ3IDMyLjYwNDcgMzAuNDczMyAzMi42MDQ3IDMwLjM4ODlWMjEuMTg4QzMyLjYwNjYgMjAuOTU4NiAzMi41NDc0IDIwLjczMjggMzIuNDMzMiAyMC41MzM4QzMyLjMxOSAyMC4zMzQ4IDMyLjE1NCAyMC4xNjk4IDMxLjk1NSAyMC4wNTU2Wk0zNS4zMDU1IDE1LjAxMjhDMzUuMjQ2NCAxNC45NzY1IDM1LjE0MzEgMTQuOTE0MiAzNS4wNjkgMTQuODcxN0wyNy4xMDQ1IDEwLjI3MTJDMjYuOTA2IDEwLjE1NTQgMjYuNjgwMyAxMC4wOTQzIDI2LjQ1MDQgMTAuMDk0M0MyNi4yMjA2IDEwLjA5NDMgMjUuOTk0OCAxMC4xNTU0IDI1Ljc5NjMgMTAuMjcxMkwxNi4wNzI2IDE1Ljg4NThWMTEuOTk4MkMxNi4wNzE1IDExLjk3ODMgMTYuMDc1MyAxMS45NTg1IDE2LjA4MzcgMTEuOTQwNUMxNi4wOTIxIDExLjkyMjUgMTYuMTA0OCAxMS45MDY4IDE2LjEyMDcgMTEuODk0OUwyNC4xNzE5IDcuMjUwMjVDMjUuNDA1MyA2LjUzOTAzIDI2LjgxNTggNi4xOTM3NiAyOC4yMzgzIDYuMjU0ODJDMjkuNjYwOCA2LjMxNTg5IDMxLjAzNjQgNi43ODA3NyAzMi4yMDQ0IDcuNTk1MDhDMzMuMzcyMyA4LjQwOTM5IDM0LjI4NDIgOS41Mzk0NSAzNC44MzM0IDEwLjg1MzFDMzUuMzgyNiAxMi4xNjY3IDM1LjU0NjQgMTMuNjA5NSAzNS4zMDU1IDE1LjAxMjhaTTE0LjI0MjQgMjEuOTQxOUwxMC44NzUyIDE5Ljk5ODFDMTAuODU3NiAxOS45ODkzIDEwLjg0MjMgMTkuOTc2MyAxMC44MzA5IDE5Ljk2MDJDMTAuODE5NSAxOS45NDQxIDEwLjgxMjIgMTkuOTI1NCAxMC44MDk4IDE5LjkwNThWMTAuNjA3MUMxMC44MTA3IDkuMTgyOTUgMTEuMjE3MyA3Ljc4ODQ4IDExLjk4MTkgNi41ODY5NkMxMi43NDY2IDUuMzg1NDQgMTMuODM3NyA0LjQyNjU5IDE1LjEyNzUgMy44MjI2NEMxNi40MTczIDMuMjE4NjkgMTcuODUyNCAyLjk5NDY0IDE5LjI2NDkgMy4xNzY3QzIwLjY3NzUgMy4zNTg3NiAyMi4wMDg5IDMuOTM5NDEgMjMuMTAzNCA0Ljg1MDY3QzIzLjA0MjcgNC44ODM3OSAyMi45MzcgNC45NDIxNSAyMi44NjY4IDQuOTg0NzNMMTQuOTAyNCA5LjU4NTE3QzE0LjcwMjUgOS42OTg3OCAxNC41MzY2IDkuODYzNTYgMTQuNDIxNSAxMC4wNjI2QzE0LjMwNjUgMTAuMjYxNiAxNC4yNDY2IDEwLjQ4NzcgMTQuMjQ3OSAxMC43MTc1TDE0LjI0MjQgMjEuOTQxOVpNMTYuMDcxIDE3Ljk5OTFMMjAuNDAxOCAxNS40OTc4TDI0LjczMjUgMTcuOTk3NVYyMi45OTg1TDIwLjQwMTggMjUuNDk4M0wxNi4wNzEgMjIuOTk4NVYxNy45OTkxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg+PC9zdmc+ // @homepage https://scriptcat.org/script-show-page/1027 // @license 未经授权请勿二开 // ==/UserScript== var setting = { showBox: 1, task: 0, video: 1, audio: 1, rate: 1, review: 0, work: 1, time: 2500, sub: 1, force: 0, decrypt: 1, redo: 0, examTurn: 0, examTurnTime: 0, goodStudent: 1, alterTitle: 1, autoLogin: 0, phone: '', password: '' } try { document.domain = 'chaoxing.com'; } catch (e) { console.error('[GPTJs] 无法设置 document.domain', e); } try { Object.defineProperty(unsafeWindow, 'onblur', { get: function () { return function () { }; }, set: function () { } }); document.hasFocus = function () { return true; }; Object.defineProperty(document, 'visibilityState', { get: function () { return 'visible'; }, configurable: true }); Object.defineProperty(document, 'hidden', { get: function () { return false; }, configurable: true }); } catch (e) { console.error('[GPTJs] 无法劫持 window.onblur / document.hasFocus', e); } var localStorage = { _getPrefix: function () { try { var host = window.location.hostname; var matches = host.match(/([^.]+\.(?:com|net|org|edu)\.cn|[^.]+\.[^.]+)$/i); return (matches ? matches[0] : host) + ':'; } catch (e) { return ''; } }, getItem: function (key) { var prefixedKey = this._getPrefix() + key; var val = undefined; try { val = GM_getValue(prefixedKey); } catch (e) { } if (val === undefined || val === null) { try { val = window.localStorage.getItem(key); if (val !== null) { GM_setValue(prefixedKey, val); } } catch (e) { } } return (val !== undefined && val !== null) ? val : null; }, setItem: function (key, value) { var prefixedKey = this._getPrefix() + key; try { GM_setValue(prefixedKey, String(value)); } catch (e) { } try { window.localStorage.setItem(key, String(value)); } catch (e) { } }, removeItem: function (key) { var prefixedKey = this._getPrefix() + key; try { GM_deleteValue(prefixedKey); } catch (e) { } try { window.localStorage.removeItem(key); } catch (e) { } } }; var _w = unsafeWindow, _l = location, _d = _w.document, $ = _w.jQuery || top.jQuery, md5 = md5 || window.md5, UE = _w.UE, Swal = Swal || window.Swal; var HOST_CANDIDATES = [ "https://911285.xyz", "https://gptjs.808860.xyz" ]; var _cachedHost = localStorage.getItem('GPTJsSetting.hostSelected'); var _cachedAt = parseInt(localStorage.getItem('GPTJsSetting.hostSelectedAt') || '0'); var _host = _cachedHost || HOST_CANDIDATES[0]; function requestGuardClientId() { var saved = GM_getValue('GPTJsSetting.guardClientId', ''); if (!/^[a-f0-9]{32}$/.test(saved)) { saved = GptRequestGuard.clientId(); GM_setValue('GPTJsSetting.guardClientId', saved); } return saved; } var _guardClientId = requestGuardClientId(); function guardHttp(host, path, method, data, headers) { return new Promise(function (resolve, reject) { GM_xmlhttpRequest({ method: method, url: host + path, headers: Object.assign({ 'X-Client-Id': _guardClientId }, data ? { 'Content-Type': 'application/json' } : {}, headers || {}), data: data ? JSON.stringify(data) : undefined, timeout: 8000, onload: function (xhr) { var body = {}; try { var parsed = JSON.parse(xhr.responseText); if (parsed && typeof parsed === 'object') body = parsed; } catch (_) { } if (xhr.status >= 200 && xhr.status < 300) { resolve({ data: body, response: xhr.responseText }); } else { var error = new Error(body.message || body.msg || '服务器请求失败,请稍后重试。'); error.response = { status: xhr.status, data: body }; reject(error); } }, onerror: function () { reject(new Error('暂时无法连接服务器,请稍后重试。')); }, ontimeout: function () { reject(new Error('服务器响应超时,请稍后重试。')); } }); }); } function requestAuthRaw(host, headers) { var startAt = Date.now(); var learningAccountId = getLearningAccountId(); return guardHttp(host, '/api/v1/auth?uid=' + encodeURIComponent(learningAccountId || '') + '&v=' + encodeURIComponent(GM_info.script.version), 'GET', null, headers) .then(function (result) { return { host: host, ms: Date.now() - startAt, response: result.response, learningAccountId: learningAccountId }; }); } function requestAuth(host, initialError) { var pendingError = initialError; return GptRequestGuard.withCaptcha(function (headers) { if (pendingError) { var error = pendingError; pendingError = null; return Promise.reject(error); } return requestAuthRaw(host, headers); }, { clientId: _guardClientId, refresh: function (captchaId, headers) { return guardHttp(host, '/api/security/captcha/refresh', 'POST', { captchaId: captchaId }, headers); } }); } function rememberHost(result) { _host = result.host; localStorage.setItem('GPTJsSetting.hostSelected', _host); localStorage.setItem('GPTJsSetting.hostSelectedAt', String(Date.now())); return result; } function findFastest() { return new Promise(function (resolve, reject) { var finished = false; var failures = 0; var limitedHost; var firstError; HOST_CANDIDATES.forEach(function (host) { requestAuthRaw(host, { 'X-Client-Id': _guardClientId }).then(function (result) { if (!finished) { finished = true; resolve(rememberHost(result)); } }).catch(function (error) { firstError = firstError || error; if (error.response && error.response.status === 429 && !limitedHost) limitedHost = { host: host, error: error }; failures++; if (failures === HOST_CANDIDATES.length && !finished) { finished = true; if (limitedHost) requestAuth(limitedHost.host, limitedHost.error).then(function (result) { resolve(rememberHost(result)); }).catch(reject); else reject(firstError); } }); }); }); } function initFastestHost() { var twoHours = 2 * 60 * 60 * 1000; if (_cachedAt && Date.now() - _cachedAt < twoHours) { return requestAuthRaw(_host, { 'X-Client-Id': _guardClientId }).catch(function (error) { if (error.response && error.response.status === 429) return requestAuth(_host, error); return findFastest(); }); } return findFastest(); } var _authPromise = initFastestHost(); _authPromise.catch(function () { }); var _mlist, _defaults, _domList, $subBtn, $saveBtn, $frame_c, $okBtn; var _currentQuestionMeta = null; var _ne21NextAiAllowedAt = 0; $('.navshow').find('a:contains(体验新版)')[0] ? $('.navshow').find('a:contains(体验新版)')[0].click() : ''; setting.decrypt ? decryptFont() : ''; if (localStorage.getItem('GPTJsSetting.time') !== null) { var _savedTime = parseFloat(localStorage.getItem('GPTJsSetting.time')); if (isFinite(_savedTime) && _savedTime >= 0) { setting.time = _savedTime * 1000; } } function toggleBoxVisibility() { var $box = $('#ne-21box'); if ($box.length === 0) { try { $box = $('#ne-21box', top.document); } catch (_) {} } if ($box.length === 0) return; if (localStorage.getItem('GPTJsSetting.showBox') === 'hide') { $box.css('display', ''); localStorage.setItem('GPTJsSetting.showBox', 'show'); try { logger('F9 显示面板'); } catch (_) {} } else { $box.css('display', 'none'); localStorage.setItem('GPTJsSetting.showBox', 'hide'); } } $(document).on('keydown.ne21f9', function (e) { if (e.keyCode === 120) toggleBoxVisibility(); }); try { if (top !== window) { $(top.document).on('keydown.ne21f9', function (e) { if (e.keyCode === 120) toggleBoxVisibility(); }); } } catch (_) {} function waitForJQueryElement(selector, timeout) { return new Promise(function (resolve) { var interval = setInterval(function () { if ($(selector).length > 0) { clearInterval(interval); if (timer) clearTimeout(timer); resolve(true); } }, 500); var timer = null; if (timeout && timeout > 0) { timer = setTimeout(function () { clearInterval(interval); resolve(false); }, timeout); } }); } if (window === top) { showBox(); } if (_l.hostname.startsWith('i.mooc.') || _l.hostname.startsWith('i.chaoxing.com') || _l.hostname === 'i.chaoxing.com') { if (window === top) { if (_l.pathname.includes('/space/out_link.shtml')) { logger('检测到外链嵌套页面,脚本将在 iframe 内自动运行考试/作业', 'green'); } else { logger('ChatGPT学习通助手已就绪,进入课程视频、作业或考试页面即可自动运行', 'green'); } } } else if (_l.pathname == '/login') { showBox() if (setting.autoLogin) waitForJQueryElement('#phone', 30000).then(function (found) { if (found) autoLogin(); else logger('登录页等待超时(30s),未找到手机号输入框', 'red'); }); } else if (_l.pathname.includes('/mycourse/studentstudy')) { showBox() $('#ne-21log', window.parent.document).html('初始化完毕!') setupAntiSleep() setupAutoRefresh() setInterval(function () { try { if (typeof _w.checkJob === 'function' && !_w._gptCheckJobHooked) { _w._gptCheckJobHooked = true; _w.checkJob = function () { return false; }; } } catch (_) {} try { if (_w.PCount && typeof _w.PCount.next === 'function' && !_w.PCount._gptHooked) { var _origPCountNext = _w.PCount.next; _w.PCount.next = function () { if (_w._gptJsJumpAllowed) { _w._gptJsJumpAllowed = false; return _origPCountNext.apply(this, arguments); } console.log('[GPTJs] 拦截 PCount.next 自动跳转'); return false; }; _w.PCount._gptHooked = true; } } catch (_) {} }, 1000); var _blankSectionChecks = 0; var _lastBlankSectionDoc = null; setInterval(function () { var cardsDoc = getStudyCardsDocument(_w); if (localStorage.getItem('GPTJsSetting.isPaused') === 'true' || !isEmptyStudyPage(cardsDoc)) { _blankSectionChecks = 0; _lastBlankSectionDoc = null; return; } if (cardsDoc !== _lastBlankSectionDoc) { _blankSectionChecks = 0; _lastBlankSectionDoc = cardsDoc; } _blankSectionChecks++; if (_blankSectionChecks >= 2) { _blankSectionChecks = 0; toNext(true); } }, 5000); } else if (_l.pathname.includes('/knowledge/cards')) { setupAntiSleep() var params = getTaskParams() var parsedParams = null; if (params && params !== '$mArg') { try { parsedParams = $.parseJSON(params); } catch (e) { parsedParams = null; } } if (!parsedParams || !parsedParams['attachments'] || parsedParams['attachments'].length <= 0) { logger('检测到无任务点(无任务参数),自动跳过', 'red') setTimeout(function () { toNext(true) }, 2000) } else { waitForJQueryElement('.wrap .ans-cc .ans-attach-ct', 8000).then(function (found) { if (!found) { var anyExists = document.querySelectorAll('.ans-attach-ct, .ans-job-ct, .chapter-item').length; if (anyExists > 0) { logger('章节任务均已完成,准备跳转', 'green') } else { logger('检测到无任务点(DOM 无任务节点),自动跳过', 'red') } setTimeout(function () { toNext(anyExists === 0) }, 2000) return; } try { if (typeof top.checkJob === 'function') top.checkJob = function () { return false; }; } catch (_) {} var _checkJobGuard = setInterval(function () { try { if (typeof top.checkJob === 'function') top.checkJob = function () { return false; }; } catch (_) {} }, 3000); _defaults = parsedParams['defaults'] var allDomNodes = [] $.each($('.wrap .ans-cc .ans-attach-ct'), function (i, t) { var iframeEl = $(t).find('iframe') var dataStr = '' try { dataStr = iframeEl.attr('data') || '' } catch (_) {} allDomNodes.push({ iframe: iframeEl, data: dataStr }) }) var rawList = parsedParams['attachments'] _mlist = [] _domList = [] var usedDomIndexes = {} for (var ai = 0; ai < rawList.length; ai++) { var att = rawList[ai] var attMid = (att['property'] && att['property']['mid']) || '' var attObjId = (att['property'] && att['property']['objectid']) || '' var matched = false for (var di = 0; di < allDomNodes.length; di++) { if (usedDomIndexes[di]) continue var dStr = allDomNodes[di].data if (dStr && ((attMid && dStr.indexOf(attMid) !== -1) || (attObjId && dStr.indexOf(attObjId) !== -1))) { _mlist.push(att) _domList.push(allDomNodes[di].iframe) usedDomIndexes[di] = true matched = true break } } if (!matched) { var skipType = att['type'] || (att['property'] && att['property']['module']) || 'unknown' var skipName = (att['property'] && att['property']['name']) || '' if (skipType === 'knowledgeGraph') { _mlist.push(att) _domList.push(null) console.log('[GPTJs] 保留 knowledgeGraph 任务点: ' + (skipName || '知识图谱')) } else { console.log('[GPTJs] 跳过无DOM对应的附件: type=' + skipType + (skipName ? ', name=' + skipName : '')) } } } if (_mlist.length === 0 && rawList.length > 0 && allDomNodes.length > 0) { console.log('[GPTJs] 精确匹配未命中,回退到索引对应模式') _mlist = rawList _domList = [] for (var fi = 0; fi < allDomNodes.length; fi++) { _domList.push(allDomNodes[fi].iframe) } } missonStart() }); } } else if (_l.pathname.includes('/exam/test/reVersionTestStartNew')) { showBox() waitForJQueryElement('.mark_table .whiteDiv', 30000).then(function (found) { if (found) missonExam(); else logger('考试页等待超时(30s),未找到题目容器', 'red'); }); } else if (_l.pathname.includes('/mooc2/exam/preview')) { showBox() waitForJQueryElement('.mark_table .questionLi', 30000).then(function (found) { if (found) missonExamPreview(); else logger('整卷预览页等待超时(30s),未找到题目列表', 'red'); }); } else if (_l.pathname.includes('/mooc2/work/dowork')) { showBox() waitForJQueryElement('.mark_table form', 30000).then(function (found) { if (found) missonHomeWork(); else logger('作业页等待超时(30s),未找到作业表单', 'red'); }); } else if (_l.pathname.includes('/work/phone/doHomeWork')) { var _oldal = _w.alert _w.alert = function (msg) { if (msg == '保存成功') { return; } return _oldal(msg) } var _oldcf = _w.confirm _w.confirm = function (msg) { if (msg.includes('确认提交') || msg.includes('未做完')) { return true } return _oldcf(msg) } } else if (_l.pathname.includes('/page/quiz/stu/answerQuestion')) { showBox() waitForJQueryElement('.question-item', 30000).then(function (found) { if (found) missonQuizPractice(); else logger('随堂练习页等待超时(30s),未找到题目容器', 'red'); }); } else if (_l.pathname.includes('/mooc2/exam/exam-list')) { } else if (_l.pathname == '/mycourse/stu') { checkBrowser() } else { if (window === top) { logger('ChatGPT学习通助手已就绪,进入课程视频、作业或考试页面即可自动运行', 'green'); } } function checkBrowser() { var userAgent = navigator.userAgent if (userAgent.indexOf('Chrome') == -1 || GM_info.scriptHandler != 'ScriptCat') { } } function parseUrlParams() { let query = window.location.search.substring(1); let vars = query.split("&"); let _p = {} for (let i = 0; i < vars.length; i++) { let pair = vars[i].split("="); _p[pair[0]] = pair[1] } return _p } function updateLocalStorage(event) { var checkbox = event.target; localStorage.setItem(checkbox.id, checkbox.checked); } function isRedoMode() { var stored = localStorage.getItem('GPTJsSetting.redo'); if (stored !== null) return stored === 'true'; return !!setting.redo; } function isFuzzyMatchEnabled() { return true; } function stringSimilarity(s1, s2) { if (!s1 && !s2) return 1; if (!s1 || !s2) return 0; s1 = s1.toLowerCase().trim(); s2 = s2.toLowerCase().trim(); if (s1 === s2) return 1; var len1 = s1.length, len2 = s2.length; if (len1 === 0 || len2 === 0) return 0; var prev = [], curr = []; for (let j = 0; j <= len2; j++) prev[j] = j; for (let i = 1; i <= len1; i++) { curr[0] = i; for (let j = 1; j <= len2; j++) { if (s1[i - 1] === s2[j - 1]) { curr[j] = prev[j - 1]; } else { curr[j] = 1 + Math.min(prev[j], curr[j - 1], prev[j - 1]); } } var tmp = prev; prev = curr; curr = tmp; } var maxLen = Math.max(len1, len2); return 1 - prev[len2] / maxLen; } function findBestFuzzyMatch(optionTexts, aiAnswer, threshold) { if (!isFuzzyMatchEnabled()) return -1; if (!aiAnswer || !optionTexts || optionTexts.length === 0) return -1; threshold = (threshold !== undefined) ? threshold : 0.5; var bestIndex = -1, bestScore = 0; for (var i = 0; i < optionTexts.length; i++) { var score = stringSimilarity(optionTexts[i], aiAnswer); if (score > bestScore) { bestScore = score; bestIndex = i; } } if (bestScore >= threshold) { logger('相似度匹配: 最佳匹配项[' + bestIndex + '] 相似度=' + (bestScore * 100).toFixed(1) + '%', 'blue'); return bestIndex; } logger('相似度匹配: 所有选项相似度均低于阈值(' + (threshold * 100) + '%),最高=' + (bestScore * 100).toFixed(1) + '%', 'orange'); return -1; } function findFuzzyMatchMultiple(optionTexts, aiAnswer, threshold) { if (!isFuzzyMatchEnabled()) return []; if (!aiAnswer || !optionTexts || optionTexts.length === 0) return []; threshold = (threshold !== undefined) ? threshold : 0.5; var parts = aiAnswer.split('|'); var matched = []; for (var p = 0; p < parts.length; p++) { var part = parts[p].trim(); if (!part) continue; var bestIndex = -1, bestScore = 0; for (var i = 0; i < optionTexts.length; i++) { var score = stringSimilarity(optionTexts[i], part); if (score > bestScore) { bestScore = score; bestIndex = i; } } if (bestScore >= threshold && matched.indexOf(bestIndex) === -1) { matched.push(bestIndex); logger('相似度匹配(多选): "' + part + '" → 选项[' + bestIndex + '] 相似度=' + (bestScore * 100).toFixed(1) + '%', 'blue'); } } return matched; } function normalizeChoiceText(text) { return String(text == null ? '' : text) .replace(/^[A-Z]\s*[\.、.::)]?\s*/i, '') .replace(/\s+/g, '') .trim(); } function getAnswerLetterIndexes(answer, optionCount) { var s = String(answer == null ? '' : answer).toUpperCase(); var result = []; var letterPattern = /(?:^|[^A-Z])([A-Z])(?=$|[^A-Z])/g; var m; while ((m = letterPattern.exec(s)) !== null) { var idx = m[1].charCodeAt(0) - 65; if (idx >= 0 && idx < optionCount && result.indexOf(idx) === -1) result.push(idx); } if (result.length === 0 && /^[A-Z]+$/.test(s.trim())) { for (var i = 0; i < s.trim().length; i++) { var charIdx = s.trim().charCodeAt(i) - 65; if (charIdx >= 0 && charIdx < optionCount && result.indexOf(charIdx) === -1) result.push(charIdx); } } return result; } function splitAiAnswers(answer) { return String(answer == null ? '' : answer) .split(/[|#;;、,,\n]+/) .map(function (s) { return s.trim(); }) .filter(Boolean); } function findVideoQuizMatchIndexes(optionTexts, aiAnswer, isMultiple) { var result = getAnswerLetterIndexes(aiAnswer, optionTexts.length); if (result.length > 0) return isMultiple ? result : [result[0]]; var normalizedOptions = optionTexts.map(normalizeChoiceText); var parts = splitAiAnswers(aiAnswer); if (parts.length === 0 && aiAnswer) parts = [String(aiAnswer).trim()]; for (var p = 0; p < parts.length; p++) { var normalizedAnswer = normalizeChoiceText(parts[p]); if (!normalizedAnswer) continue; for (var i = 0; i < normalizedOptions.length; i++) { if (result.indexOf(i) !== -1) continue; if (normalizedOptions[i] === normalizedAnswer || normalizedOptions[i].indexOf(normalizedAnswer) !== -1 || normalizedAnswer.indexOf(normalizedOptions[i]) !== -1) { result.push(i); break; } } } if (result.length === 0) { result = isMultiple ? findFuzzyMatchMultiple(normalizedOptions, aiAnswer, 0.45) : [findBestFuzzyMatch(normalizedOptions, aiAnswer, 0.45)]; result = result.filter(function (idx) { return idx >= 0; }); } return isMultiple ? result : result.slice(0, 1); } function getVideoQuizText(container, optionNodes) { var clone = container.cloneNode(true); try { Array.prototype.forEach.call(clone.querySelectorAll('.ans-videoquiz-opt, #videoquiz-submit, button, input, label'), function (node) { if (node.parentNode) node.parentNode.removeChild(node); }); } catch (_) {} var text = (clone.innerText || clone.textContent || '').trim(); if (!text) { var title = container.querySelector('.ans-videoquiz-title, .ans-videoquiz-subject, .ans-videoquiz-question, .videoquiz-title, .videoquiz-question'); text = title ? (title.innerText || title.textContent || '').trim() : ''; } optionNodes.forEach(function (label) { var optText = (label.innerText || label.textContent || '').trim(); if (optText) text = text.replace(optText, ''); }); return text.replace(/\s+/g, ' ').trim(); } function handleVideoQuiz(doc, media, taskName) { if (!setting.work || localStorage.getItem('GPTJsSetting.isPaused') === 'true') return false; var container = doc.querySelector('.ans-videoquiz'); var submitBtn = doc.querySelector('#videoquiz-submit'); if (!container || !submitBtn) return false; var quizText = container.innerText || ''; if (quizText.indexOf('恭喜你,答对了!') !== -1 || quizText.indexOf('继续学习') !== -1) { logger('弹题已答对,尝试点击“继续学习”并清理弹窗', 'green'); var btn = Array.prototype.slice.call(container.querySelectorAll('a, button, span, div')).find(function (el) { var txt = (el.innerText || el.textContent || '').trim(); return txt === '继续学习' || txt.indexOf('继续') !== -1; }); if (btn) { try { btn.click(); } catch (_) { } } setTimeout(function () { try { if (container && container.parentNode) container.parentNode.removeChild(container); Array.prototype.forEach.call(doc.querySelectorAll('.x-component-default'), function (node) { node.style.display = 'none'; }); if (media && media.paused && localStorage.getItem('GPTJsSetting.isPaused') !== 'true') media.play(); } catch (_) { } }, 800); return true; } if (container.getAttribute('data-ne21-status') === 'processing') return true; var optionNodes = Array.prototype.slice.call(container.querySelectorAll('.ans-videoquiz-opt label')); if (optionNodes.length === 0) optionNodes = Array.prototype.slice.call(container.querySelectorAll('label')); if (optionNodes.length === 0) return false; container.setAttribute('data-ne21-status', 'processing'); try { media.pause(); } catch (_) {} var optionTexts = optionNodes.map(function (label) { return (label.innerText || label.textContent || '').trim(); }); var inputTypes = optionNodes.map(function (label) { var input = label.querySelector('input'); return input ? String(input.type || '').toLowerCase() : ''; }); var isMultiple = inputTypes.indexOf('checkbox') !== -1; var isJudge = !isMultiple && optionTexts.length === 2 && optionTexts.some(function (t) { return /正确|错误|对|错|是|否|true|false/i.test(t); }); var question = getVideoQuizText(container, optionNodes) || '视频弹题'; var typeName = isMultiple ? '多选题' : (isJudge ? '判断题' : '单选题'); window._failedVideoQuizzes = window._failedVideoQuizzes || {}; if (!window._failedVideoQuizzes[question]) { window._failedVideoQuizzes[question] = { triedIndexes: [], lastTriedIndexes: [] }; } var quizRecord = window._failedVideoQuizzes[question]; if (quizRecord.lastTriedIndexes && quizRecord.lastTriedIndexes.length > 0) { quizRecord.triedIndexes = quizRecord.triedIndexes.concat(quizRecord.lastTriedIndexes); quizRecord.triedIndexes = quizRecord.triedIndexes.filter(function (item, pos, self) { return self.indexOf(item) === pos; }); quizRecord.lastTriedIndexes = []; } if (quizRecord.triedIndexes.length >= optionTexts.length) { quizRecord.triedIndexes = []; } var doSubmit = function (indexes) { quizRecord.lastTriedIndexes = indexes; indexes.forEach(function (idx) { var label = optionNodes[idx]; if (!label) return; label.style.fontWeight = 'bold'; label.style.color = '#dc2626'; if (localStorage.getItem('GPTJsSetting.goodStudent') !== 'true') { setTimeout(function () { label.click(); }, 100); } }); if (localStorage.getItem('GPTJsSetting.goodStudent') === 'true') { logger('视频弹题答案已加粗,当前为好学生模式,请手动选择并提交', 'orange'); container.removeAttribute('data-ne21-status'); return; } setTimeout(function () { submitBtn.click(); logger('视频弹题已提交,准备继续播放', 'green'); setTimeout(function () { try { var quiz = doc.querySelector('#video .ans-videoquiz, .ans-videoquiz'); if (quiz) { var txt = quiz.innerText || ''; if (txt.indexOf('恭喜你,答对了!') !== -1 || txt.indexOf('继续学习') !== -1) { var btn = Array.prototype.slice.call(quiz.querySelectorAll('a, button, span, div')).find(function (el) { var t = (el.innerText || el.textContent || '').trim(); return t === '继续学习' || t.indexOf('继续') !== -1; }); if (btn) { try { btn.click(); } catch (_) { } } setTimeout(function () { if (quiz && quiz.parentNode) quiz.parentNode.removeChild(quiz); Array.prototype.forEach.call(doc.querySelectorAll('.x-component-default'), function (node) { node.style.display = 'none'; }); if (media && media.paused && localStorage.getItem('GPTJsSetting.isPaused') !== 'true') media.play(); }, 800); } else { quiz.removeAttribute('data-ne21-status'); container.removeAttribute('data-ne21-status'); } } else { Array.prototype.forEach.call(doc.querySelectorAll('.x-component-default'), function (node) { node.style.display = 'none'; }); if (media && media.paused && localStorage.getItem('GPTJsSetting.isPaused') !== 'true') media.play(); } } catch (_) {} }, 1500); }, 600); }; if (quizRecord.triedIndexes.length > 0) { var remainingIndexes = []; optionTexts.forEach(function (_, idx) { if (quizRecord.triedIndexes.indexOf(idx) === -1) { remainingIndexes.push(idx); } }); if (remainingIndexes.length === 0) { quizRecord.triedIndexes = []; remainingIndexes = optionTexts.map(function (_, idx) { return idx; }); } var nextIndexes = [remainingIndexes[0]]; logger('视频弹题上一选项错误,排除后尝试新选项: ' + nextIndexes.map(function (i) { return optionTexts[i]; }).join(','), 'orange'); doSubmit(nextIndexes); } else { var prompt = buildPrompt({ type: typeName, question: question, options: optionTexts, answer_format: isMultiple ? "用'|'分割多个答案,或回答选项字母" : (isJudge ? '只回答正确或错误,或回答选项字母' : '只回答一个选项字母或选项内容') }); logger('检测到视频中题目,开始处理:' + (taskName || ''), 'purple'); getAnswer(isMultiple ? 1 : (isJudge ? 3 : 0), prompt).then(function (answer) { var indexes; if (isJudge) { var parsed = parseJudgeAnswer(answer); if (parsed) { indexes = []; optionTexts.forEach(function (text, idx) { var norm = normalizeChoiceText(text); if (parsed === 'true' && /正确|对|是|true|√/i.test(norm)) indexes.push(idx); if (parsed === 'false' && /错误|错|否|false|×/i.test(norm)) indexes.push(idx); }); } } if (!indexes || indexes.length === 0) { indexes = findVideoQuizMatchIndexes(optionTexts, answer, isMultiple); } if (indexes.length === 0) { logger('视频弹题未能匹配到选项,已暂停等待手动处理', 'red'); container.removeAttribute('data-ne21-status'); return; } doSubmit(indexes); }).catch(function () { logger('视频弹题 AI 作答失败,已暂停等待手动处理', 'red'); container.removeAttribute('data-ne21-status'); }); } } function getRate() { var stored = localStorage.getItem('GPTJsSetting.rate'); var n = stored !== null ? parseFloat(stored) : (setting.rate || 1); if (!isFinite(n) || n <= 0) n = 1; if (n > 16) n = 16; return n; } function parseJudgeAnswer(agrs) { if (!agrs) return null; var s = agrs.replace(/[。,.,!!\s]/g, '').toLowerCase(); var trueWords = ['正确', '是', '对', '√', 't', 'true', 'ri', 'right', 'yes']; var falseWords = ['错误', '否', '错', '×', 'f', 'false', 'wr', 'wrong', 'no']; for (let i = 0; i < trueWords.length; i++) { if (s === trueWords[i]) return 'true'; } for (let i = 0; i < falseWords.length; i++) { if (s === falseWords[i]) return 'false'; } for (let i = 0; i < falseWords.length; i++) { if (s.indexOf(falseWords[i]) !== -1) return 'false'; } for (let i = 0; i < trueWords.length; i++) { if (s.indexOf(trueWords[i]) !== -1) return 'true'; } return null; } function findJudgeOptionIndex(optionTexts, isTrue) { var trueWords = ['正确', '是', '对', '√', 'T', 'ri']; var falseWords = ['错误', '否', '错', '×', 'F', 'wr']; var words = isTrue ? trueWords : falseWords; for (var i = 0; i < optionTexts.length; i++) { var t = optionTexts[i]; for (var j = 0; j < words.length; j++) { if (t.indexOf(words[j]) !== -1) return i; } } return -1; } function findAnswerTextareas($container) { if (!$container || $container.length === 0) return $(); var $eles = $container.find('textarea[name^="answerEditor"]'); if ($eles.length > 0) return $eles; $eles = $container.find('.subEditor textarea, .Answer .divText textarea, .stem_answer textarea, .edui-editor textarea'); if ($eles.length > 0) return $eles; return $container.find('textarea'); } function showBox() { if (setting.showBox && top.document.querySelector('#ne-21notice') == undefined) { if (!top.document.getElementById('ne-21style')) { var styleEl = top.document.createElement('style'); styleEl.id = 'ne-21style'; styleEl.textContent = ` /* === Liquid Glass UI (iOS 26 style, neutral light glass) === */ #ne-21box{position:fixed;top:5%;right:16%;width:340px;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;font-size:13px;color:rgba(15,23,42,.86);background:linear-gradient(180deg,rgba(255,255,255,.62) 0%,rgba(241,245,249,.55) 100%);backdrop-filter:blur(22px) saturate(180%) brightness(1.04);-webkit-backdrop-filter:blur(22px) saturate(180%) brightness(1.04);border:1px solid rgba(255,255,255,.65);border-radius:22px;box-shadow:0 0 0 1px rgba(15,23,42,.09),0 24px 48px -12px rgba(15,23,42,.45),0 10px 26px -8px rgba(15,23,42,.3),inset 0 1px 0 rgba(255,255,255,.9),inset 0 -1px 0 rgba(15,23,42,.06);overflow:hidden;transition:opacity .25s ease,transform .25s ease;animation:ne21-in .4s cubic-bezier(.2,.9,.3,1) both;} @keyframes ne21-in{from{opacity:0;transform:translateY(-8px) scale(.98)}to{opacity:1;transform:none}} #ne-21box .ne21-header{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:linear-gradient(180deg,rgba(255,255,255,.72) 0%,rgba(248,250,252,.35) 100%);color:rgba(15,23,42,.92);border-bottom:1px solid rgba(15,23,42,.07);cursor:move;user-select:none;} #ne-21box .ne21-header::after{content:'';position:absolute;left:14px;right:14px;bottom:-1px;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.85),transparent);pointer-events:none;} #ne-21box.ne21-collapsed .ne21-body{display:none;} #ne-21box.ne21-collapsed .ne21-header{border-bottom:none;} #ne-21box.ne21-collapsed .ne21-header::after{display:none;} #ne-21box .ne21-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;letter-spacing:.3px;margin:0;color:inherit;} #ne-21box .ne21-dot{width:9px;height:9px;border-radius:50%;background:radial-gradient(circle at 32% 28%,rgba(255,255,255,.98),rgba(255,255,255,.5) 55%,rgba(15,23,42,.18) 100%);box-shadow:0 0 0 0 rgba(255,255,255,.7),inset 0 1px 1px rgba(255,255,255,.95);animation:ne21-pulse 2s infinite;flex-shrink:0;} @keyframes ne21-pulse{0%{box-shadow:0 0 0 0 rgba(255,255,255,.7),inset 0 1px 1px rgba(255,255,255,.95)}70%{box-shadow:0 0 0 8px rgba(255,255,255,0),inset 0 1px 1px rgba(255,255,255,.95)}100%{box-shadow:0 0 0 0 rgba(255,255,255,0),inset 0 1px 1px rgba(255,255,255,.95)}} #ne-21box #ne-21close, #ne-21box #ne-21pause{margin:0;width:24px;height:24px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:0;line-height:1;color:rgba(15,23,42,.7);cursor:pointer;border:1px solid rgba(255,255,255,.65);border-radius:50%;background:rgba(255,255,255,.55);box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.8),0 1px 2px rgba(15,23,42,.08);transition:background .2s,color .2s,transform .15s;user-select:none;} #ne-21box #ne-21close:hover, #ne-21box #ne-21pause:hover{background:rgba(255,255,255,.78);color:rgba(15,23,42,.92);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.9),0 1px 2px rgba(15,23,42,.1);} #ne-21box #ne-21close:active, #ne-21box #ne-21pause:active{transform:scale(.92);} #ne-21box .ne21-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;} #ne-21box .ne21-kbd{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;font-size:10.5px;font-weight:500;letter-spacing:.2px;color:rgba(15,23,42,.66);background:rgba(255,255,255,.55);border:1px solid rgba(255,255,255,.72);border-radius:8px;box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.85),0 1px 2px rgba(15,23,42,.06);user-select:none;cursor:default;font-family:"SF Mono","Cascadia Code",Consolas,Menlo,monospace;line-height:1;white-space:nowrap;} #ne-21box .ne21-kbd b{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;font-weight:600;color:rgba(15,23,42,.78);} #ne-21box .ne21-body{padding:14px 16px 16px;} #ne-21box #ne-21notice{border-top:none!important;margin:0 0 6px!important;overflow:visible;} #ne-21box .ne21-uid{display:flex;align-items:center;gap:6px;color:rgba(15,23,42,.62);font-size:12px;margin-bottom:10px;padding:8px 12px;background:rgba(255,255,255,.5);border:1px solid rgba(255,255,255,.7);border-radius:12px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75),0 1px 2px rgba(15,23,42,.05);} #ne-21box .ne21-uid b{color:rgba(15,23,42,.92);font-weight:600;} #ne-21box .ne21-uid-missing{flex-direction:column;align-items:flex-start;gap:4px;line-height:1.6;} #ne-21box .ne21-row{display:flex;gap:8px;align-items:center;} #ne-21box .ne21-btn{display:inline-flex;align-items:center;justify-content:center;padding:7px 14px;font-size:12px;font-weight:500;border-radius:14px;cursor:pointer;border:1px solid rgba(255,255,255,.7);transition:transform .15s,box-shadow .2s,background .2s,color .2s;white-space:nowrap;} #ne-21box .ne21-btn-primary{color:rgba(15,23,42,.92);background:rgba(255,255,255,.72);box-shadow:0 0 0 1px rgba(15,23,42,.07),inset 0 1px 0 rgba(255,255,255,.95),inset 0 -6px 12px -6px rgba(15,23,42,.08),0 4px 10px -2px rgba(15,23,42,.22);} #ne-21box .ne21-btn-primary:hover{transform:translateY(-1px);background:rgba(255,255,255,.88);box-shadow:0 0 0 1px rgba(15,23,42,.09),inset 0 1px 0 rgba(255,255,255,1),inset 0 -6px 12px -6px rgba(15,23,42,.1),0 6px 14px -2px rgba(15,23,42,.28);} #ne-21box .ne21-btn-secondary{color:rgba(15,23,42,.78);background:rgba(255,255,255,.45);box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.75),0 1px 2px rgba(15,23,42,.06);} #ne-21box .ne21-btn-secondary:hover{background:rgba(255,255,255,.65);color:rgba(15,23,42,.92);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.85),0 2px 4px rgba(15,23,42,.08);} #ne-21box .ne21-btn:active{transform:translateY(0) scale(.98);} #ne-21box .ne21-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;} #ne-21box #modelSelect{flex:1;min-width:0;padding:7px 10px;font-size:12px;border-radius:14px;border:1px solid rgba(255,255,255,.7);background:rgba(255,255,255,.55);color:rgba(15,23,42,.86);cursor:pointer;outline:none;box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.8);transition:background .2s,box-shadow .2s;} #ne-21box #modelSelect:hover{background:rgba(255,255,255,.7);} #ne-21box #modelSelect:focus{background:rgba(255,255,255,.75);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.85),0 0 0 4px rgba(15,23,42,.1);} #ne-21box .ne21-select{padding:5px 8px;font-size:12px;border-radius:10px;border:1px solid rgba(255,255,255,.65);background:rgba(255,255,255,.5);color:rgba(15,23,42,.86);cursor:pointer;outline:none;min-width:80px;flex-shrink:0;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75);transition:background .2s,box-shadow .2s;} #ne-21box .ne21-select:hover{background:rgba(255,255,255,.7);} #ne-21box .ne21-select:focus{background:rgba(255,255,255,.72);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.82),0 0 0 3px rgba(15,23,42,.06);} #ne-21box #userInfo{margin:10px 0 0;padding:10px 12px;background:rgba(255,255,255,.5);border:1px solid rgba(255,255,255,.7);border-radius:12px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75);font-size:12px;color:rgba(15,23,42,.66);line-height:1.6;overflow:hidden;} #ne-21box #userInfo:empty{display:none;} #ne-21box #userInfo b{color:rgba(15,23,42,.9);font-weight:600;} #ne-21box #moreSettings{padding:4px 14px;background:rgba(255,255,255,.42);border:1px solid rgba(255,255,255,.65);border-radius:14px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.7);margin:10px 0 0;} #ne-21box #moreSettings label{display:flex;flex-direction:row-reverse;align-items:center;justify-content:space-between;margin:0;padding:8px 2px;font-size:12px;color:rgba(15,23,42,.78);cursor:pointer;user-select:none;line-height:1.4;} #ne-21box #moreSettings label + label{border-top:1px dashed rgba(15,23,42,.1);} #ne-21box #moreSettings input[type=checkbox]{appearance:none;-webkit-appearance:none;width:34px;height:20px;border:1px solid rgba(15,23,42,.08);border-radius:20px;cursor:pointer;position:relative;transition:background .25s,box-shadow .25s;background:rgba(15,23,42,.16);box-shadow:inset 0 1px 2px rgba(15,23,42,.12);margin:0 0 0 10px;flex-shrink:0;} #ne-21box #moreSettings input[type=checkbox]::before{content:'';position:absolute;top:1px;left:1px;width:16px;height:16px;border-radius:50%;background:linear-gradient(180deg,rgba(255,255,255,1),rgba(255,255,255,.85));box-shadow:0 1px 3px rgba(15,23,42,.25),inset 0 1px 0 rgba(255,255,255,1);transition:transform .25s cubic-bezier(.2,.9,.3,1);} #ne-21box #moreSettings input[type=checkbox]:hover{background:rgba(15,23,42,.24);} #ne-21box #moreSettings input[type=checkbox]:checked{background:rgba(255,255,255,.78);border-color:rgba(15,23,42,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 -2px 4px rgba(15,23,42,.08),0 0 0 1px rgba(15,23,42,.06);} #ne-21box #moreSettings input[type=checkbox]:checked:hover{background:rgba(255,255,255,.92);} #ne-21box #moreSettings input[type=checkbox]:checked::before{transform:translateX(14px);} #ne-21box #moreSettings p{display:none;} #ne-21box #ne-21thinking{display:none!important;} @keyframes ne21-spin{to{transform:rotate(360deg)}} @keyframes ne21-dot{0%,80%,100%{transform:scale(.5);opacity:.4}40%{transform:scale(1);opacity:1}} #ne-21box #ne-21log .ne21-log-spinner{display:inline-block;width:9px;height:9px;margin-right:5px;border:1.5px solid rgba(15,23,42,.18);border-top-color:rgba(15,23,42,.7);border-radius:50%;vertical-align:-1px;animation:ne21-spin .8s linear infinite;} #ne-21box #ne-21log .ne21-log-dots{display:inline-flex;gap:2px;margin-left:3px;vertical-align:1px;} #ne-21box #ne-21log .ne21-log-dots i{width:3px;height:3px;border-radius:50%;background:currentColor;opacity:.65;animation:ne21-dot 1.2s infinite ease-in-out both;} #ne-21box #ne-21log .ne21-log-dots i:nth-child(2){animation-delay:.16s;} #ne-21box #ne-21log .ne21-log-dots i:nth-child(3){animation-delay:.32s;} #ne-21box #ne-21log{max-height:140px;overflow-y:auto;margin:12px 0 0;padding:10px 12px;background:rgba(15,23,42,.06);border:1px solid rgba(255,255,255,.55);border-radius:14px;box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.6),inset 0 0 16px rgba(15,23,42,.06);font-family:"SF Mono","Cascadia Code",Consolas,Menlo,monospace;font-size:11px;line-height:1.6;color:rgba(15,23,42,.78);} #ne-21box #ne-21log:empty{display:none;} #ne-21box #ne-21log::-webkit-scrollbar{width:5px;} #ne-21box #ne-21log::-webkit-scrollbar-thumb{background:rgba(15,23,42,.2);border-radius:4px;} #ne-21box #ne-21log::-webkit-scrollbar-thumb:hover{background:rgba(15,23,42,.32);} #ne-21box #ne-21log p{margin:0;padding:2px 0;word-break:break-all;} #ne-21box #ne-21log hr{display:none;} #ne-21box #ne-21log .ne21-time{color:rgba(15,23,42,.4);margin-right:6px;} `; top.document.head.appendChild(styleEl); } var box_html = `

ChatGPT 学习通助手

F9 显隐
AI 思考中
`; $(box_html).appendTo('body'); (function () { var $box = $('#ne-21box'); try { var savedPos = localStorage.getItem('GPTJsSetting.boxPosition'); if (savedPos) { var pos = JSON.parse(savedPos); if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') { var vw = window.innerWidth, vh = window.innerHeight; var w = $box.outerWidth() || 340; var nx = Math.max(40 - w, Math.min(pos.left, vw - 40)); var ny = Math.max(0, Math.min(pos.top, vh - 40)); $box.css({ left: nx + 'px', top: ny + 'px', right: 'auto' }); } } } catch (_) {} if (localStorage.getItem('GPTJsSetting.boxCollapsed') === 'true') { $box.addClass('ne21-collapsed'); $('#ne-21close').html('').attr('aria-label', '展开'); } if (localStorage.getItem('GPTJsSetting.showBox') === 'hide') { $box.css('display', 'none'); } })(); $('#ne-21box').on('mousedown', '#ne-21pause', function (e) { e.stopPropagation(); }).on('click', '#ne-21pause', function (e) { e.stopPropagation(); var isPaused = localStorage.getItem('GPTJsSetting.isPaused') === 'true'; isPaused = !isPaused; localStorage.setItem('GPTJsSetting.isPaused', isPaused); $(this).html(isPaused ? '' : ''); $(this).attr('title', isPaused ? '已暂停,点击继续' : '运行中,点击暂停'); if (isPaused) { try { logger('脚本已暂停运行', 'orange'); } catch (_) { } } else { try { logger('脚本已恢复运行', 'green'); } catch (_) { } } }); (function () { var isPaused = localStorage.getItem('GPTJsSetting.isPaused') === 'true'; var $pauseBtn = $('#ne-21pause'); $pauseBtn.html(isPaused ? '' : ''); $pauseBtn.attr('title', isPaused ? '已暂停,点击继续' : '运行中,点击暂停'); })(); $('#ne-21close').on('mousedown', function (e) { e.stopPropagation(); }).on('click', function (e) { e.stopPropagation(); var collapsed = $('#ne-21box').toggleClass('ne21-collapsed').hasClass('ne21-collapsed'); $(this).html(collapsed ? '' : ''); $(this).attr('aria-label', collapsed ? '展开' : '收起'); try { localStorage.setItem('GPTJsSetting.boxCollapsed', collapsed ? 'true' : 'false'); } catch (_) {} }); (function () { var $box = $('#ne-21box'); var $header = $box.find('.ne21-header'); var dragging = false, startX = 0, startY = 0, startLeft = 0, startTop = 0; $header.on('mousedown', function (e) { if (e.which !== 1) return; if ($(e.target).closest('#ne-21close').length) return; dragging = true; var rect = $box[0].getBoundingClientRect(); startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; $box.css({ left: startLeft + 'px', top: startTop + 'px', right: 'auto' }); $('body').css('user-select', 'none'); e.preventDefault(); }); $(document).on('mousemove.ne21drag', function (e) { if (!dragging) return; var nx = startLeft + (e.clientX - startX); var ny = startTop + (e.clientY - startY); var w = $box.outerWidth(); var vw = window.innerWidth; var vh = window.innerHeight; nx = Math.max(40 - w, Math.min(nx, vw - 40)); ny = Math.max(0, Math.min(ny, vh - 40)); $box.css({ left: nx + 'px', top: ny + 'px' }); }).on('mouseup.ne21drag', function () { if (!dragging) return; dragging = false; $('body').css('user-select', ''); try { var rect = $box[0].getBoundingClientRect(); localStorage.setItem('GPTJsSetting.boxPosition', JSON.stringify({ left: rect.left, top: rect.top })); } catch (_) {} }); })(); (function () { var moreSettings = document.getElementById('moreSettings'); var userInfo = document.getElementById('userInfo'); if (!moreSettings || !userInfo) return; var isSettingsVisible = false; $('#ne-21box').on('click', '#moreSettingsBtn', function () { userInfo.style.display = isSettingsVisible ? 'block' : 'none'; moreSettings.style.display = isSettingsVisible ? 'none' : 'block'; this.textContent = isSettingsVisible ? '设置' : '返回'; isSettingsVisible = !isSettingsVisible; }); if (localStorage.getItem('GPTJsSetting.alterTitle') === null) { localStorage.setItem('GPTJsSetting.alterTitle', 'true'); } if (localStorage.getItem('GPTJsSetting.fuzzyMatch') === null) { localStorage.setItem('GPTJsSetting.fuzzyMatch', 'true'); } if (localStorage.getItem('GPTJsSetting.sub') === null) { localStorage.setItem('GPTJsSetting.sub', 'true'); } ['sub', 'force', 'examTurn', 'goodStudent', 'alterTitle', 'redo', 'fuzzyMatch'].forEach(function (settingId) { var checkbox = document.getElementById('GPTJsSetting.' + settingId); if (!checkbox) return; checkbox.addEventListener('change', updateLocalStorage); checkbox.checked = localStorage.getItem('GPTJsSetting.' + settingId) === 'true'; }); var rateSelect = document.getElementById('GPTJsSetting.rate'); if (rateSelect) { rateSelect.value = localStorage.getItem('GPTJsSetting.rate') || '1'; rateSelect.addEventListener('change', function () { localStorage.setItem('GPTJsSetting.rate', rateSelect.value); }); } var timeInput = document.getElementById('GPTJsSetting.time'); if (timeInput) { var savedTime = localStorage.getItem('GPTJsSetting.time'); timeInput.value = (savedTime !== null && isFinite(parseFloat(savedTime))) ? savedTime : String((setting.time || 2500) / 1000); timeInput.addEventListener('change', function () { var v = parseFloat(timeInput.value); if (!isFinite(v) || v < 0) v = 0; if (v > 60) v = 60; timeInput.value = String(v); localStorage.setItem('GPTJsSetting.time', String(v)); setting.time = v * 1000; }); } })(); } else { $('#ne-21log', window.parent.document).html('') } let _u = [getCk('_uid'), getCk('UID')].find(value => /^\d+$/.test(value || '') && Number(value) > 0 && Number(value) <= 2147483647) || ''; $('#ne-21notice').html(`
${_u ? '学习通账号 UID:' + _u + '' : '请先登录学习通登录后刷新页面,即可查看 UID 并充值。'}
`); var CACHE_KEY_MODELS = 'GPTJsSetting.cachedModels'; var cachedModels = localStorage.getItem(CACHE_KEY_MODELS); if (cachedModels) { $('#modelSelect').html(cachedModels); } var lastSelectedModel = localStorage.getItem('GPTJsSetting.model'); if (lastSelectedModel) { $('#modelSelect').val(lastSelectedModel); } $('#modelSelect').off('change.gptjsModel').on('change.gptjsModel', function () { localStorage.setItem('GPTJsSetting.model', $(this).val()); }); updateAuthPanel(cachedModels); } var LEARNING_ACCOUNT_STORAGE_KEY = 'GPTJsSetting.lastLearningAccount'; function getLearningAccountId() { return [getCk('_uid'), getCk('UID')].find(function (value) { return /^\d+$/.test(value || '') && Number(value) > 0 && Number(value) <= 2147483647; }) || ''; } function openRechargePage(learningAccountId) { window.open(_host + '?uid=' + encodeURIComponent(learningAccountId), '_blank', 'noopener,noreferrer'); } function showLearningAccountChangeNotice(learningAccountId) { var message = '检测到您已更换学习通账号,积分是根据学习通账号同步的。如有疑问,请前往充值页联系售后。'; if (Swal && typeof Swal.fire === 'function') { Swal.fire({ icon: 'info', title: '学习通账号已更换', text: message, confirmButtonText: '前往充值页', showCancelButton: true, cancelButtonText: '知道了', confirmButtonColor: '#0071e3', reverseButtons: true }).then(function (result) { if (result.isConfirmed) openRechargePage(learningAccountId); }); return; } if (window.confirm(message + '\n\n是否前往充值页?')) openRechargePage(learningAccountId); } function checkLearningAccountChange(learningAccountId) { if (!learningAccountId) return; var previousLearningAccountId = String(GM_getValue(LEARNING_ACCOUNT_STORAGE_KEY, '') || ''); if (previousLearningAccountId === learningAccountId) return; GM_setValue(LEARNING_ACCOUNT_STORAGE_KEY, learningAccountId); if (previousLearningAccountId) showLearningAccountChangeNotice(learningAccountId); } function updateAuthPanel(cachedModels) { var CACHE_KEY_MODELS = 'GPTJsSetting.cachedModels'; if (_authPromise) { _authPromise.then(function (res) { var obj = {}; try { obj = $.parseJSON(res.response) || {}; } catch (e) {} var data = obj.data || {}; var notice = data.notice || ''; var score = (data.score !== undefined && data.score !== null) ? data.score : '-'; checkLearningAccountChange(res.learningAccountId); $('#userInfo').html(notice + "积分余额:" + score); if (data.models && data.models !== cachedModels) { localStorage.setItem(CACHE_KEY_MODELS, data.models); var selectedValue = localStorage.getItem('GPTJsSetting.model') || $('#modelSelect').val(); $('#modelSelect').html(data.models); if (selectedValue) { $('#modelSelect').val(selectedValue); } } }).catch(function (error) { var body = GptRequestGuard.errorBody(error); var message = body.message || error.message || '暂时无法获取账号信息,请稍后重试。'; var $retry = $('').css({ marginTop: '8px' }); $('#userInfo').empty().append($('
').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 } 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(doc) { try { var _iframeScripts = (doc || _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 getStudyWindow() { var current = _w; try { while (current) { if (current.location.pathname.includes('/mycourse/studentstudy')) return current; if (current === current.parent) break; current = current.parent; } } catch (_) {} return _w.top; } function getStudyCardsDocument(studyWindow) { try { if (studyWindow.location.pathname.includes('/knowledge/cards')) return studyWindow.document; for (var i = 0; i < studyWindow.frames.length; i++) { var doc = getStudyCardsDocument(studyWindow.frames[i]); if (doc) return doc; } } catch (_) {} return null; } function isEmptyStudyPage(doc) { if (!doc || doc.readyState !== 'complete') return false; var params = getTaskParams(doc); try { var parsed = JSON.parse(params); if (parsed && Array.isArray(parsed.attachments)) { return parsed.attachments.length === 0; } } catch (_) {} return !!doc.querySelector('.ans-cc') && !doc.querySelector('.ans-attach-ct, .ans-job-ct, .chapter-item'); } function scheduleStudyNavigation(studyWindow, navigate) { var doc = studyWindow.document; var cardsDoc = getStudyCardsDocument(studyWindow); var chapterId = doc.querySelector('.posCatalog_active')?.id || ''; var tabSelector = '#prev_tab > li.active, .prev_ul > li.active, #prevTabBox > li.active, span.currents'; var activeTab = doc.querySelector(tabSelector); var pending = studyWindow._gptJsNextPagePending; if (pending && pending.cardsDoc === cardsDoc && pending.chapterId === chapterId && pending.activeTab === activeTab) return; pending = { cardsDoc: cardsDoc, chapterId: chapterId, activeTab: activeTab }; studyWindow._gptJsNextPagePending = pending; function release() { if (studyWindow._gptJsNextPagePending === pending) studyWindow._gptJsNextPagePending = null; } studyWindow.setTimeout(function run() { if (studyWindow.document !== doc || getStudyCardsDocument(studyWindow) !== cardsDoc || (doc.querySelector('.posCatalog_active')?.id || '') !== chapterId || doc.querySelector(tabSelector) !== activeTab) { release(); return; } if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') { studyWindow.setTimeout(run, 1000); return; } try { if (!navigate()) release(); } catch (error) { release(); logger('自动切换失败:' + error.message, 'red'); } }, 2000); } function toNext(noTasks) { var studyWindow = getStudyWindow(); if (_l.pathname.includes('/knowledge/cards') && getStudyCardsDocument(studyWindow) !== _d) return; if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') { setTimeout(function () { toNext(noTasks); }, 3000); return; } var courseList = noTasks === true || setting.review || !setting.work ? Promise.resolve(null) : refreshCourseList().catch(function (error) { logger(error.message + ',使用页面目录继续处理', 'orange'); return studyWindow.document.querySelector('#coursetree'); }); courseList.then((res) => { if (_l.pathname.includes('/knowledge/cards') && getStudyCardsDocument(studyWindow) !== _d) return; function detectSubTabPosition() { try { var selectors = ['#prev_tab > li', '.prev_ul > li', '#prevTabBox > li']; for (var i = 0; i < selectors.length; i++) { var nodes = studyWindow.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 }; } var currents = studyWindow.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: [currents].concat(nextSibs) }; } } } catch (e) {} return null; } function clickNextPageBtn() { $('#ne-21log', window.parent.document).html('') try { studyWindow._gptJsJumpAllowed = true; } catch (_) {} var nextBtn = studyWindow.document.querySelector('#mainid .prev_next.next') if (nextBtn) { nextBtn.click(); return true } var sub = detectSubTabPosition(); if (sub && sub.hasNext && sub.tabs) { sub.tabs[sub.activeIndex + 1].click(); return true; } logger('未找到本课时下一页入口,等待页面加载', 'orange'); return false } function clickNextChapterBtn() { $('#ne-21log', window.parent.document).html('') try { studyWindow._gptJsJumpAllowed = true; } catch (_) {} var nextBtn = studyWindow.document.querySelector('#mainid .prev_next.next') if (nextBtn) { nextBtn.click(); return true } var focusBtn = studyWindow.document.querySelector('#prevNextFocusNext') if (focusBtn) { focusBtn.click(); return true } logger('未找到下一章节入口,本次不切换', 'orange'); return false } var sub = detectSubTabPosition() if (sub && sub.hasNext) { logger('当前课时存在未完成页面(' + (sub.activeIndex + 1) + '/' + sub.total + '),准备切换到下一页', 'blue') scheduleStudyNavigation(studyWindow, clickNextPageBtn) return } if (noTasks === true || setting.review || !setting.work) { logger(noTasks === true ? '当前页面无任务点,准备切换到下一章节' : '本课时已无未完成页面,准备切换到下一章节', 'blue') scheduleStudyNavigation(studyWindow, clickNextChapterBtn) 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 && curid.indexOf('cur') != -1) { _t.push({ 'curid': curid, 'status': status, 'name': name }) } }) let _curChaterId = $('#coursetree', studyWindow.document).find('.posCatalog_active').attr('id') let _curIndex = _t.findIndex((item) => item['curid'] == _curChaterId) if (_curIndex < 0) { logger('课程目录未能定位当前章节,尝试页面下一节入口', 'orange') scheduleStudyNavigation(studyWindow, clickNextChapterBtn) return } for (_curIndex; _curIndex < _t.length - 1; _curIndex++) { if (_t[_curIndex]['status'].indexOf('待完成') != -1) { var subAgain = detectSubTabPosition() if (subAgain && subAgain.hasNext) { logger('兜底检测到本课时仍有未完成页面(' + (subAgain.activeIndex + 1) + '/' + subAgain.total + '),切换到下一页', 'blue') scheduleStudyNavigation(studyWindow, clickNextPageBtn) return } } let t = _t[_curIndex + 1] if (t['status'].indexOf('待完成') != -1) { scheduleStudyNavigation(studyWindow, function () { var clicked = clickNextChapterBtn() showBox() return clicked }) return } else if (t['status'].indexOf('闯关') != -1) { logger('当前为闯关模式,存在未完成任务点,脚本已暂停运行,请手动完成并点击下一章节', 'red') return } else if (t['status'].indexOf('开放') != -1) { logger('章节未开放', 'red') return } else { } } logger('此课程处理完毕', 'green') return }).catch(function (error) { logger('自动切换失败:' + error.message, 'red'); }) } 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() } } } } 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; 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'; logger(`处理${taskLabel}:${name},正在解析`); window._failedVideoQuizzes = {}; let executed = false; let _parseDebugLogged = false; const PARSE_TIMEOUT_MS = 60 * 1000; const parseStartTime = Date.now(); 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; var m = frameDoc.querySelector('video') || frameDoc.querySelector('audio'); if (m) { if (m.tagName.toLowerCase() === 'audio') mediaType = 'audio'; return { media: m, doc: frameDoc }; } 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 (_) {} } 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; } var result = dom && dom.length > 0 ? searchIframesForMedia(dom) : null; if (!result) { try { var allIframes = _d.querySelectorAll('.ans-attach-ct iframe'); if (allIframes.length > 0) { result = searchIframesForMedia(allIframes); } } catch (_) {} } 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; 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) => { 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); const quizIntervalId = mediaType === 'video' ? setInterval(function () { handleVideoQuiz(doc, media, name); }, 1500) : null; 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); let rateRestored = finalRate <= 1; const onTimeUpdate = () => { if (!rateRestored && isFinite(media.duration) && media.duration - media.currentTime < 10) { rateRestored = true; try { delete media.playbackRate; } catch (_) {} hookMediaRate(media, 1); media.removeEventListener('timeupdate', onTimeUpdate); } }; if (!rateRestored) { media.addEventListener('timeupdate', onTimeUpdate); } 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); clearInterval(_stallCheckId); resolve(); }; media.addEventListener("ended", () => { _doResolve('已播放完成'); }); let _lastProgressTime = -1; let _stalledMs = 0; const _STALL_LIMIT_MS = 5 * 60 * 1000; const _stallCheckId = setInterval(() => { if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') { _lastProgressTime = media.currentTime; _stalledMs = 0; return; } if (mediaType === 'video' && doc.querySelector('.ans-videoquiz')) { _lastProgressTime = media.currentTime; _stalledMs = 0; return; } if (Math.abs(media.currentTime - _lastProgressTime) > 0.5) { _lastProgressTime = media.currentTime; _stalledMs = 0; } else { _stalledMs += 30 * 1000; if (_stalledMs >= _STALL_LIMIT_MS) { _doResolve('播放长时间无进度(疑似卡死),自动跳过'); } } }, 30 * 1000); 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); } function hookMediaRate(media, rate) { try { media.playbackRate = rate; } catch (_) {} 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 (__) {} } }); } catch (___) {} } } 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']) || '知识图谱'; if (obj['job'] == undefined) { logger('知识图谱:' + taskName + ',检测已完成,准备执行下一个任务。', 'green') switchMission() return } 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) { if (!packBtn.classList.contains('active')) { packBtn.click(); logger('知识图谱:' + taskName + ',已点击展开按钮', 'green'); } else { logger('知识图谱:' + taskName + ',已是展开状态', 'green'); } clicked = true; break; } } catch (e) { } } } catch (e) { console.log('[GPTJs] 查找知识图谱 iframe 异常:', e); } if (!clicked) { logger('知识图谱:' + taskName + ',未找到展开按钮,可能已完成或不在当前页面,跳过', 'orange'); } 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(() => { startDoPhoneCyWork(0, dom, phoneWeb) }, 3000) } else { setTimeout(() => { startDoCyWork(0, dom) }, 3000) } } 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 } 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; 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') 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') 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') 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: { let tkList = $(TimuList[index]).find('.blankList2 input') let tkEditorBlocks = $(TimuList[index]).find('[data-editorindex]') 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 if (contextWindow.editors && contextWindow.editors[editorIndex]) { ueditor = contextWindow.editors[editorIndex].ueditor } if (!ueditor && contextWindow.UE && contextWindow.UE.instants) { let instantKey = 'ueditorInstant' + editorIndex ueditor = contextWindow.UE.instants[instantKey] } 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') } 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) {} } } } 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) { 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) $(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 }) let jdEditorBlocks = $(TimuList[index]).find('[data-editorindex]') let jdTextareas = $(TimuList[index]).find('textarea[name^="answer"]') let jdIsAnswered = false 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') if (!itemId && jdTextareas.length > 0) { let tid = $(jdTextareas[0]).attr('id') if (tid) itemId = tid.replace('answer', '') } setTimeout(() => { try { let ueditor = null if (contextWindow.editors && contextWindow.editors[editorIndex]) { ueditor = contextWindow.editors[editorIndex].ueditor } if (!ueditor && contextWindow.UE && contextWindow.UE.instants) { let instantKey = 'ueditorInstant' + editorIndex ueditor = contextWindow.UE.instants[instantKey] } 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') } if (jdTextareas.length > 0) { let ta = $(jdTextareas[0]) ta.val(agrs) try { ta[0].dispatchEvent(new Event('change')) ta[0].dispatchEvent(new Event('input')) } catch (e) {} } } catch (e) { logger('简答题填入失败:' + e.message, 'red') if (jdTextareas.length > 0) { $(jdTextareas[0]).val(agrs) logger('简答题通过textarea填入答案', 'blue') } } }, 500) } else { 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) } }) } 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) => { 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') localStorage.setItem('GPTJsSetting.sub', false) setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) break } } function pollForElement(iframeDom, selector, interval, maxAttempts) { interval = interval || 2000; maxAttempts = (typeof maxAttempts === 'number' && maxAttempts > 0) ? maxAttempts : 60; 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) {} 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(); }); } 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; document.addEventListener('visibilitychange', function () { try { if (ac.state === 'suspended') ac.resume(); } catch (_) {} }); } catch (_) {} } 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); } } 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 (_) {} }, 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) { 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 getQuizPracticeType($item) { var typeText = ($item.find('.grey-text').first().text() || '').replace(/\s+/g, '') var className = String($item.attr('class') || '') if (typeText.indexOf('多选') !== -1 || className.indexOf('multiple-choice') !== -1) { return { type: 1, name: '多选题' } } if (typeText.indexOf('判断') !== -1) { return { type: 3, name: '判断题' } } if (typeText.indexOf('填空') !== -1 || $item.find('.option-fill-list').length > 0) { return { type: 2, name: '填空题' } } if (/(简答|问答|论述|名词解释|计算题|材料题|写作|翻译)/.test(typeText) || $item.find('.short-answer-textarea').length > 0) { return { type: 4, name: typeText.replace(/^\[|\]$/g, '') || '简答题' } } if (typeText.indexOf('单选') !== -1 || className.indexOf('single-choice') !== -1) { return { type: 0, name: '单选题' } } return { type: -1, name: typeText || '未知题型' } } function getQuizPracticeQuestionText($item) { var $name = $item.find('.question-name').first().clone() $name.find('.grey-text').remove() var text = ($name.text() || '').replace(/\s+/g, ' ').trim() return tidyQuestion(text) || '随堂练习题' } function getQuizPracticeOptions($item) { return $item.find('.option-list > li').map(function () { var $option = $(this) var $clone = $option.clone() $clone.find('.option-letter').remove() return tidyStr($clone.text()) || '' }).get() } function getQuizPracticeFillInputs($item) { return $item.find('input.ofl-ipt, .option-fill-list input, .option-fill-list textarea').filter(function () { return !this.disabled }) } function getQuizPracticeTextarea($item) { var $textarea = $item.find('.short-answer-textarea textarea').first() if ($textarea.length === 0) $textarea = $item.find('textarea').first() return $textarea } function isQuizPracticeAnswered($item, type) { if (type === 0 || type === 1 || type === 3) { return $item.find('.option-list > li.active, .option-list > li.selected, .option-list > li.checked').length > 0 } if (type === 2) { var $inputs = getQuizPracticeFillInputs($item) var hasFill = false $inputs.each(function () { if ($.trim($(this).val() || '')) hasFill = true }) return hasFill } if (type === 4) { return $.trim(getQuizPracticeTextarea($item).val() || '') !== '' } return false } function setQuizPracticeValue($element, value) { var element = $element.get(0) if (!element) return var text = value == null ? '' : String(value) try { var prototype = element.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype var descriptor = Object.getOwnPropertyDescriptor(prototype, 'value') if (descriptor && descriptor.set) descriptor.set.call(element, text) else element.value = text } catch (_) { element.value = text } try { element.dispatchEvent(new Event('input', { bubbles: true })) element.dispatchEvent(new Event('change', { bubbles: true })) element.dispatchEvent(new Event('blur', { bubbles: true })) } catch (_) {} } function isQuizPracticeGoodStudent() { var stored = localStorage.getItem('GPTJsSetting.goodStudent') return stored === null ? !!setting.goodStudent : stored === 'true' } function setQuizPracticeOptionState($options, selectedIndexes) { var selected = {} selectedIndexes.forEach(function (index) { selected[index] = true }) $options.each(function (index) { var $option = $(this) var shouldSelect = !!selected[index] if (isQuizPracticeGoodStudent()) { if (shouldSelect) $option.find('.option-letter, .option-result').css('font-weight', 'bold') return } var isSelected = $option.hasClass('active') || $option.hasClass('selected') || $option.hasClass('checked') if (shouldSelect !== isSelected) { try { this.click() } catch (_) { $option.trigger('click') } } }) } function appendQuizPracticeAnswer($item, answer) { var alterTitle = localStorage.getItem('GPTJsSetting.alterTitle') var shouldAlter = alterTitle === null ? !!setting.alterTitle : alterTitle === 'true' if (!shouldAlter || !answer || $item.attr('data-ne21-answer-appended') === 'true') return var $name = $item.find('.question-name').first() if ($name.length === 0) return $('

').css('font-weight', 'bold').text('答案:' + answer).appendTo($name) $item.attr('data-ne21-answer-appended', 'true') } function getQuizPracticeSubmitButton() { var candidates = $('button, input[type="button"], input[type="submit"], .van-button, .submit-btn, .submit, [role="button"]').filter(function () { if (this.disabled) return false if (!$(this).is(':visible')) return false var text = ($(this).text() || $(this).val() || '').replace(/\s+/g, '') var className = typeof this.className === 'string' ? this.className : '' if (text === '提交' || (text.indexOf('提交') !== -1 && text.length <= 8)) return true return /(?:^|[-_\s])submit(?:$|[-_\s])/i.test(' ' + className + ' ') }) return candidates.last() } function continueQuizPractice(index, questions, state) { var wait = Number(setting.time) if (!isFinite(wait) || wait < 300) wait = 2500 wait += Math.floor(Math.random() * 350) setTimeout(function () { doQuizPractice(index, questions, state) }, wait) } function finishQuizPractice(state) { if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') { setTimeout(function () { finishQuizPractice(state) }, 3000) return } logger('随堂练习处理完成,共 ' + state.total + ' 题,已作答 ' + state.answered + ' 题,失败 ' + state.failed + ' 题', state.failed ? 'orange' : 'green') if (state.failed > 0) localStorage.setItem('GPTJsSetting.sub', false) var storedSub = localStorage.getItem('GPTJsSetting.sub') var autoSubmit = storedSub === null ? !!setting.sub : storedSub === 'true' var storedForce = localStorage.getItem('GPTJsSetting.force') var forceSubmit = storedForce === null ? !!setting.force : storedForce === 'true' if (!autoSubmit && !forceSubmit) { logger('随堂练习未开启自动提交,已保留当前作答', 'blue') return } if (state.failed > 0 && !forceSubmit) { logger('存在未完成题目,已停止提交;如需强制提交请开启“测验强制提交”', 'orange') return } var $submit = getQuizPracticeSubmitButton() if ($submit.length === 0) { logger('未找到随堂练习提交按钮,请手动提交', 'red') return } try { $submit.get(0).click() logger('已提交随堂练习', 'green') } catch (_) { logger('随堂练习提交按钮点击失败,请手动提交', 'red') } } function doQuizPractice(index, questions, state) { if (localStorage.getItem('GPTJsSetting.isPaused') === 'true') { setTimeout(function () { doQuizPractice(index, questions, state) }, 3000) return } if (index >= questions.length) { setTimeout(function () { finishQuizPractice(state) }, 500) return } var $item = $(questions[index]) if ($item.length === 0) { state.failed++ continueQuizPractice(index + 1, questions, state) return } var typeInfo = getQuizPracticeType($item) _currentQuestionMeta = { index: index, total: questions.length, typeName: typeInfo.name } if (typeInfo.type < 0) { state.failed++ logger('第' + (index + 1) + '题题型未识别,已跳过', 'red') continueQuizPractice(index + 1, questions, state) return } if (!isRedoMode() && isQuizPracticeAnswered($item, typeInfo.type)) { state.answered++ logger('第' + (index + 1) + '题已作答,已跳过', 'blue') continueQuizPractice(index + 1, questions, state) return } var question = getQuizPracticeQuestionText($item) var options = getQuizPracticeOptions($item) var prompt if (typeInfo.type === 0) { if (options.length === 0) { state.failed++ logger('第' + (index + 1) + '题未找到选项,已跳过', 'red') continueQuizPractice(index + 1, questions, state) return } prompt = buildPrompt({ type: '单选题', question: question, options: options, answer_format: "只回答一个选项字母或选项内容" }) } else if (typeInfo.type === 1) { if (options.length === 0) { state.failed++ logger('第' + (index + 1) + '题未找到选项,已跳过', 'red') continueQuizPractice(index + 1, questions, state) return } prompt = buildPrompt({ type: '多选题', question: question, options: options, answer_format: "用'|'分割多个答案,或回答选项字母" }) } else if (typeInfo.type === 3) { if (options.length === 0) { state.failed++ logger('第' + (index + 1) + '题未找到选项,已跳过', 'red') continueQuizPractice(index + 1, questions, state) return } prompt = buildPrompt({ type: '判断题', question: question, options: options, answer_format: "只回答正确或错误,或回答选项字母" }) } else if (typeInfo.type === 2) { prompt = buildPrompt({ type: '填空题', question: question, answer_format: "多个填空内容用'|'分隔" }) } else { prompt = buildPrompt({ type: '简答题', question: question }) } getAnswer(typeInfo.type, prompt).then(function (answer) { appendQuizPracticeAnswer($item, answer) var applied = false if (typeInfo.type === 0 || typeInfo.type === 1 || typeInfo.type === 3) { var indexes = [] if (typeInfo.type === 3) { var judgeAnswer = parseJudgeAnswer(answer) var judgeIndex = judgeAnswer === null ? -1 : findJudgeOptionIndex(options, judgeAnswer === 'true') if (judgeIndex !== -1) indexes = [judgeIndex] } if (indexes.length === 0) { indexes = findVideoQuizMatchIndexes(options, answer, typeInfo.type === 1) } if (typeInfo.type !== 1) indexes = indexes.slice(0, 1) if (indexes.length === 0) { state.failed++ logger('第' + (index + 1) + '题未能匹配到选项,已跳过', 'red') } else { var $options = $item.find('.option-list > li') setQuizPracticeOptionState($options, indexes) applied = true state.answered++ } } else if (typeInfo.type === 2) { var $inputs = getQuizPracticeFillInputs($item) var parts = $inputs.length === 1 ? [String(answer || '').trim()] : splitAiAnswers(answer) if (parts.length === 0 && answer) parts = [String(answer).trim()] if ($inputs.length > 1 && parts.length < $inputs.length) { var whitespaceParts = String(answer || '').split(/\s+/).filter(Boolean) if (whitespaceParts.length >= $inputs.length) parts = whitespaceParts } if ($inputs.length === 0) { state.failed++ logger('第' + (index + 1) + '题未找到填空输入框,已跳过', 'red') } else { $inputs.each(function (inputIndex) { var value = parts[inputIndex] != null ? parts[inputIndex] : (parts.length === 1 ? parts[0] : '') setQuizPracticeValue($(this), value) }) applied = true state.answered++ } } else { var $textarea = getQuizPracticeTextarea($item) if ($textarea.length === 0) { state.failed++ logger('第' + (index + 1) + '题未找到作答输入框,已跳过', 'red') } else { setQuizPracticeValue($textarea, answer) applied = true state.answered++ } } if (applied) logger('第' + (index + 1) + '题作答完成', 'green') continueQuizPractice(index + 1, questions, state) }).catch(function () { state.failed++ localStorage.setItem('GPTJsSetting.sub', false) continueQuizPractice(index + 1, questions, state) }) } function missonQuizPractice() { if (document.documentElement.getAttribute('data-ne21-quiz-practice') === 'running') return var questions = $('.question-item').toArray() if (questions.length === 0) { logger('未找到随堂练习题目', 'red') return } document.documentElement.setAttribute('data-ne21-quiz-practice', 'running') var state = { total: questions.length, answered: 0, failed: 0 } logger('检测到随堂练习,共 ' + questions.length + ' 题,开始处理', 'green') doQuizPractice(0, questions, state) } 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; } 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) {} }, 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; 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') 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() } } } if (check_answer_flag == 0) { getAnswer(_type, _question).then((agrs) => { if (localStorage.getItem('GPTJsSetting.alterTitle') === 'true') { let timuele = $(TiMuList[index]).find('.mark_name') 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 } 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) {} }, 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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: "请根据题目作答" }) let editorTextareas = $(TiMuList[index]).find('.stem_answer textarea[name^="answerEditor"]'); if (editorTextareas && editorTextareas.length > 0) { 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'); 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 (_) {} _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() } }) } 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') 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') 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) { 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') 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') 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 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') 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) {} }) 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) {} }) } _question = buildPrompt({ type: '填空题', question: _question, answer_format: "用'|'分割多个答案" }); 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) { 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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'); 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'); let editorScripts = $('script:contains("UE.getEditor")'); let editorIdMatch = null; if (editorScripts && editorScripts.length > 0) { 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') } } 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) } function getExamPreviewDelay() { let base = (setting && setting.time) ? setting.time : 2500 return base + Math.floor(Math.random() * 1500) } 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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) {} }) 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) {} }, 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', dataType: 'html', timeout: 10000, success: function (res) { resolve(res) }, error: function (xhr, status) { reject(new Error(status === 'timeout' ? '课程目录请求超时(10秒)' : '课程目录请求失败(' + (xhr.status || status) + ')')) } }) }) } 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'); } } 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) { 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') 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; 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') } var _maxRetries = 3; longWaitTimer = setTimeout(() => { if (!requestCompleted) { requestCompleted = true; if (retryCount >= _maxRetries) { updateLogEntry($thinkingLog, '已重试' + _maxRetries + '次仍未响应,请刷新页面重试', 'red') reject({ 'c': 666 }) return; } updateLogEntry($thinkingLog, '请求超过2.5分钟未响应,正在重新发起请求...(第' + (retryCount + 1) + '次重试)', 'orange') getAnswer(_t, _q, retryCount + 1).then(resolve).catch(reject) } }, 150000 + _waitMs); setTimeout(function () { if (requestCompleted) return; 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: 135000, onload: function (xhr) { if (requestCompleted) { logger('收到已超时请求的响应,已忽略', 'gray') return; } requestCompleted = true; clearTimeout(longWaitTimer); if (xhr.status == 200) { let obj = $.parseJSON(xhr.responseText) || {}; if (!obj.data || typeof obj.data.answer !== 'string') { updateLogEntry($thinkingLog, obj.msg || '服务器返回数据异常', 'red') localStorage.setItem('GPTJsSetting.sub', false) reject({ 'c': 0 }) return; } let _answer = obj.data.answer.replace(/。$/, ''); 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') localStorage.setItem('GPTJsSetting.sub', false) reject({ 'c': 0 }) } else { updateLogEntry($thinkingLog, '暂无答案', 'red') 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); if (retryCount >= _maxRetries) { updateLogEntry($thinkingLog, '已重试' + _maxRetries + '次仍超时,请刷新页面重试', 'red') reject({ 'c': 666 }) return; } updateLogEntry($thinkingLog, '请求超时,正在自动重试...(第' + (retryCount + 1) + '次重试)', 'orange') getAnswer(_t, _q, retryCount + 1).then(resolve).catch(reject) } }); }, _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 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') 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 == '暂无答案') { 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() { 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; }