// ==UserScript== // @name 超星学习通助手 (合并版) // @version 1.2.1 // @description 自定义AI + 本地AI + 题库 的合并版本 // @namespace https://github.com/chaoxing-assistant // @homepage https://scriptcat.org/zh-CN/script-show-page/6637 // @supportURL https://scriptcat.org/zh-CN/script-show-page/6637 // @match *://*.chaoxing.com/* // @match *://*.edu.cn/* // @connect 127.0.0.1 // @connect * // @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://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // @require https://gptjs.808860.xyz/libs/TyprMd5.js // @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js // @resource Table https://gptjs.808860.xyz/libs/table.json // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0Ij4KICA8cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InRyYW5zcGFyZW50Ii8+CiAgPHJlY3QgeD0iMTAiIHk9IjYiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNCIgZmlsbD0iI2M4OTYxZSIgcng9IjMiLz4KICA8cmVjdCB4PSI0MiIgeT0iNiIgd2lkdGg9IjEyIiBoZWlnaHQ9IjE0IiBmaWxsPSIjYzg5NjFlIiByeD0iMyIvPgogIDxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjQwIiBoZWlnaHQ9IjM2IiBmaWxsPSIjZjVkNTNhIiByeD0iOCIvPgogIDxyZWN0IHg9IjE4IiB5PSIyNCIgd2lkdGg9IjI4IiBoZWlnaHQ9IjE4IiBmaWxsPSIjZmZmZmZmIiByeD0iNSIvPgogIDxyZWN0IHg9IjIyIiB5PSIxNCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjgiIGZpbGw9IiNmZWY5YzMiIHJ4PSI0Ii8+CiAgPGNpcmNsZSBjeD0iMjIiIGN5PSIyMiIgcj0iMy41IiBmaWxsPSIjMWExYTFhIi8+CiAgPGNpcmNsZSBjeD0iNDIiIGN5PSIyMiIgcj0iMy41IiBmaWxsPSIjMWExYTFhIi8+CiAgPGNpcmNsZSBjeD0iMjMiIGN5PSIyMSIgcj0iMS4zIiBmaWxsPSIjZmZmZmZmIi8+CiAgPGNpcmNsZSBjeD0iNDMiIGN5PSIyMSIgcj0iMS4zIiBmaWxsPSIjZmZmZmZmIi8+CiAgPHJlY3QgeD0iMTgiIHk9IjE3IiB3aWR0aD0iOSIgaGVpZ2h0PSIyIiBmaWxsPSIjYzg5NjFlIiByeD0iMSIvPgogIDxyZWN0IHg9IjM3IiB5PSIxNyIgd2lkdGg9IjkiIGhlaWdodD0iMiIgZmlsbD0iI2M4OTYxZSIgcng9IjEiLz4KICA8ZWxsaXBzZSBjeD0iMzIiIGN5PSIzMCIgcng9IjQuNSIgcnk9IjMiIGZpbGw9IiMxYTFhMWEiLz4KICA8cGF0aCBkPSJNMjggMzQgUTMyIDM5IDM2IDM0IiBzdHJva2U9IiMxYTFhMWEiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CiAgPGVsbGlwc2UgY3g9IjMyIiBjeT0iMzciIHJ4PSIzIiByeT0iMyIgZmlsbD0iI2ZmNmI2YiIvPgogIDxjaXJjbGUgY3g9IjE2IiBjeT0iMjgiIHI9IjMiIGZpbGw9IiNmYmJmMjQiIG9wYWNpdHk9IjAuNSIvPgogIDxjaXJjbGUgY3g9IjQ4IiBjeT0iMjgiIHI9IjMiIGZpbGw9IiNmYmJmMjQiIG9wYWNpdHk9IjAuNSIvPgo8L3N2Zz4= // ==/UserScript== (function() { 'use strict'; var SCRIPT_VERSION = '1.2.1'; /* ========================= 设置 ========================= */ var setting = { showBox: 1, task: 0, video: 1, audio: 1, rate: 1, review: 0, work: 1, time: 2000, sub: 0, force: 0, examTurn: 0, examTurnTime: 0, goodStudent: 0, alterTitle: 1, apiUrl: 'http://127.0.0.1:11434/api/generate', apiModel: 'qwen3.5:0.8b', apiKey: '' }; var _w = unsafeWindow, _l = location, _d = _w.document; var $ = _w.jQuery || top.jQuery; var UE = _w.UE; var md5 = md5 || window.md5; var _mlist, _defaults, _domList, $subBtn, $saveBtn, $frame_c, $okBtn; var _currentQuestionMeta = null; var _nextAiAllowedAt = 0; /* ========================= 存储工具(统一键名) ========================= */ // 修复:gmGet/gmSet 同时检查 cx.* 和 GPTJsSetting.* 两套键名, // 确保合并版(cx.*)与原版UI面板(GPTJsSetting.*)的读写一致 function gmGet(key) { try { var v = GM_getValue(key); if (v !== undefined && v !== null) return String(v); } catch(e) {} try { var v = localStorage.getItem('cx.' + key); if (v !== null) return v; } catch(e) {} try { var v = localStorage.getItem('GPTJsSetting.' + key); if (v !== null) return v; } catch(e) {} return null; } function gmSet(key, val) { try { GM_setValue(key, val); } catch(e) {} try { localStorage.setItem('cx.' + key, val); } catch(e) {} try { localStorage.setItem('GPTJsSetting.' + key, val); } catch(e) {} } function gmRemove(key) { try { GM_deleteValue(key); } catch(e) {} try { localStorage.removeItem('cx.' + key); } catch(e) {} try { localStorage.removeItem('GPTJsSetting.' + key); } catch(e) {} } function markAnswered(qIdx) { try { var list = JSON.parse(localStorage.getItem('cx.done') || '[]'); if (list.indexOf(qIdx) < 0) { list.push(qIdx); localStorage.setItem('cx.done', JSON.stringify(list)); } } catch(e) {} } function isAnswered(qIdx) { try { return JSON.parse(localStorage.getItem('cx.done') || '[]').indexOf(qIdx) >= 0; } catch(e) { return false; } } if (gmGet('time') !== null) { var _t = parseFloat(gmGet('time')); if (isFinite(_t) && _t >= 0) setting.time = _t * 1000; } /* ========================= 工具函数 ========================= */ function getCk(name) { var m = document.cookie.match(new RegExp('[;\\s+]?' + name + '=([^;]*)')); return m ? m.pop() : null; } function getStr(str, start, end) { var r = str.match(new RegExp(start + '(.*?)' + end)); return r ? r[1] : null; } function tidyStr(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, '').replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/ /g, '').trim(); } function tidyQuestion(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, '').replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/^\d+[.、]/, '').replace(/ /g, '').trim(); } function parseUrlParams() { var q = window.location.search.substring(1), vars = q.split('&'), p = {}; for (var i = 0; i < vars.length; i++) { var pair = vars[i].split('='); p[pair[0]] = pair[1]; } return p; } function waitForJQueryElement(selector) { return new Promise(function (resolve) { var iv = setInterval(function () { if ($(selector).length > 0) { clearInterval(iv); resolve(); } }, 500); }); } function updateLocalStorage(event) { var checkbox = event.target; localStorage.setItem(checkbox.id, checkbox.checked); } function isFuzzyMatchEnabled() { var stored = localStorage.getItem('GPTJsSetting.fuzzyMatch'); if (stored !== null) return stored === 'true'; return true; } function checkBrowser() { var userAgent = navigator.userAgent; if (userAgent.indexOf('Chrome') == -1 || GM_info.scriptHandler != 'ScriptCat') { // 非推荐环境,但不弹出警告 } } function isPaused() { return gmGet('isPaused') === 'true'; } function isRedoMode() { var stored = localStorage.getItem('GPTJsSetting.redo'); if (stored !== null) return stored === 'true'; return !!setting.redo; } function getTaskParams() { try { var scripts = _d.scripts; for (var i = 0; i < scripts.length; i++) { var html = scripts[i].innerHTML; if (html.indexOf('mArg = "";') !== -1 && html.indexOf('==UserScript==') === -1) { return getStr(html.replace(/\s/g, ''), 'try{mArg=', ';}catch'); } } } catch (_) {} return null; } /* ========================= 日志 ========================= */ var _colorMap = { red: '#dc2626', green: '#059669', blue: '#2563eb', yellow: '#ca8a04', orange: '#ea580c', purple: '#7c3aed', pink: '#db2777', gray: '#64748b' }; function logger(str, color) { var t = new Date().toLocaleTimeString(); var c = _colorMap[color] || color || '#334155'; var $logContainer = $('#sa-log'); if (!$logContainer.length) $logContainer = $('#ne-21log', window.parent.document); if (!$logContainer.length) $logContainer = $('#sa-log', window.parent.document); if (!$logContainer.length) return; var p = $('

[' + t + ']' + str + '

'); $logContainer.prepend(p); return p; } function updateLogEntry(p, str, color) { if (!p || !p.length) return; var c = _colorMap[color] || color || '#334155'; p.find('.sa-msg').css('color', c).html(str); } /* ========================= F9 切换 ========================= */ function toggleBox() { var box = $('#sa-box'); if (!box.length) try { box = $('#ne-21box', top.document); } catch (_) {} if (!box.length) return; if (gmGet('showBoxHide') === 'hide') { box.css('display', ''); gmSet('showBoxHide', 'show'); try { logger('F9 显示面板'); } catch (_) {} } else { box.css('display', 'none'); gmSet('showBoxHide', 'hide'); } } $(document).on('keydown.saf9', function (e) { if (e.keyCode === 120) toggleBox(); }); try { if (top !== window) $(top.document).on('keydown.saf9', function (e) { if (e.keyCode === 120) toggleBox(); }); } catch (_) {} /* ========================= API 调用 ========================= */ function getApiConfig() { return { url: gmGet('apiUrl') || setting.apiUrl, model: gmGet('apiModel') || setting.apiModel, key: gmGet('apiKey') || setting.apiKey || '' }; } function callApi(payload) { return new Promise(function (resolve, reject) { var cfg = getApiConfig(); var isOllama = /\/api\/(generate|chat)/.test(cfg.url); var isOpenAI = /\/(v1\/)?chat\/completions/.test(cfg.url); var headers = { 'Content-Type': 'application/json' }; if (cfg.key) headers['Authorization'] = 'Bearer ' + cfg.key; var body; if (isOllama) { body = JSON.stringify({ model: cfg.model, prompt: payload, stream: false }); } else if (isOpenAI) { body = JSON.stringify({ model: cfg.model, messages: [{ role: 'system', content: '你是一个学习助手,直接根据题目返回答案。单选题只返回选项文字。多选题用|分割。判断题只返回正确或错误。填空题用|分割。简答题50字左右。' }, { role: 'user', content: payload }], stream: false }); } else { body = JSON.stringify({ model: cfg.model, messages: [{ role: 'user', content: payload }], stream: false }); } GM_xmlhttpRequest({ method: 'POST', url: cfg.url, headers: headers, data: body, timeout: 120000, onload: function (xhr) { if (xhr.status == 200) { var txt = xhr.responseText; try { var obj = JSON.parse(txt); var answer = ''; if (isOllama) answer = obj.response || obj.message || obj.text || ''; else if (isOpenAI) answer = (obj.choices && obj.choices[0] && obj.choices[0].message && obj.choices[0].message.content) || ''; else answer = obj.answer || obj.response || obj.text || txt; if (typeof answer !== 'string') answer = String(answer); answer = answer.replace(/^[A-Z]\s*\n\s*/, '').trim(); if (answer) { resolve(answer); return; } } catch (e) { var raw = txt.trim(); if (raw) { resolve(raw); return; } } reject({ c: 0, msg: '返回空 | ' + cfg.url }); } else { reject({ c: xhr.status, msg: 'HTTP ' + xhr.status + ' | ' + cfg.url }); } }, onerror: function () { reject({ c: -1, msg: '网络错误 | ' + cfg.url }); }, ontimeout: function () { reject({ c: -2, msg: '超时 | ' + cfg.url }); } }); }); } /* ========================= 相似度匹配 ========================= */ 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 (var j = 0; j <= len2; j++) prev[j] = j; for (var i = 1; i <= len1; i++) { curr[0] = i; for (var 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; } return 1 - prev[len2] / Math.max(len1, len2); } function findBestFuzzyMatch(options, answer, threshold) { if (!answer || !options || !options.length) return -1; threshold = threshold !== undefined ? threshold : 0.5; var bestIdx = -1, bestScore = 0; for (var i = 0; i < options.length; i++) { var score = stringSimilarity(options[i], answer); if (score > bestScore) { bestScore = score; bestIdx = i; } } return bestScore >= threshold ? bestIdx : -1; } function findFuzzyMatchMultiple(options, answer, threshold) { if (!answer || !options || !options.length) return []; threshold = threshold !== undefined ? threshold : 0.5; var parts = answer.split('|'), matched = []; for (var p = 0; p < parts.length; p++) { var part = parts[p].trim(); if (!part) continue; var bestIdx = -1, bestScore = 0; for (var i = 0; i < options.length; i++) { var score = stringSimilarity(options[i], part); if (score > bestScore) { bestScore = score; bestIdx = i; } } if (bestScore >= threshold && matched.indexOf(bestIdx) === -1) matched.push(bestIdx); } return matched; } /* ========================= 判断题解析 ========================= */ function parseJudgeAnswer(agrs) { if (!agrs) return null; var s = agrs.replace(/[。,.,!!\s]/g, '').toLowerCase(); var tw = ['正确','是','对','√','t','true','ri','right','yes']; var fw = ['错误','否','错','×','f','false','wr','wrong','no']; for (var i = 0; i < tw.length; i++) { if (s === tw[i]) return 'true'; } for (var i = 0; i < fw.length; i++) { if (s === fw[i]) return 'false'; } for (var i = 0; i < fw.length; i++) { if (s.indexOf(fw[i]) !== -1) return 'false'; } for (var i = 0; i < tw.length; i++) { if (s.indexOf(tw[i]) !== -1) return 'true'; } return null; } function findJudgeOptionIndex(options, isTrue) { var words = isTrue ? ['正确','是','对','√','T','ri'] : ['错误','否','错','×','F','wr']; for (var i = 0; i < options.length; i++) { for (var j = 0; j < words.length; j++) { if (options[i].indexOf(words[j]) !== -1) return i; } } return -1; } function findAnswerTextareas(container) { if (!container || !container.length) 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'); } var _typeNames = {0:'单选题',1:'多选题',2:'填空题',3:'判断题',4:'简答题',5:'写作题',6:'翻译题'}; var _typeMap = {单选题: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}; /* ========================= Prompt 构建 ========================= */ function buildPrompt(opts) { opts = opts || {}; var q = opts.question != null ? String(opts.question) : ''; var obj = {}; if (opts.type) obj.type = String(opts.type); if (opts.answer_format) obj.answer_format = String(opts.answer_format); obj.question = q; if (Array.isArray(opts.options) && opts.options.length > 0) { obj.options = opts.options.map(function (s) { return String(s == null ? '' : s).trim(); }); } var payload = JSON.stringify(obj, null, 2); var display = q; if (obj.options && obj.options.length) display += '\n' + obj.options.join(' | '); return { payload: payload, display: display }; } /* ========================= 本地题库 ========================= */ function getBank() { try { return JSON.parse(gmGet('questionBank') || '{}'); } catch (e) { return {}; } } function saveBank(bank) { try { gmSet('questionBank', JSON.stringify(bank)); } catch (e) {} } function extractQuestionText(payloadStr) { try { var obj = JSON.parse(payloadStr); return { text: obj.question || '', options: obj.options || [] }; } catch (e) { return { text: '', options: [] }; } } /* ========================= 获取答案 ========================= */ function getAnswer(type, question, retryCount) { retryCount = retryCount || 0; var payload, display; if (question && typeof question === 'object' && question.payload != null) { payload = String(question.payload); display = question.display != null ? String(question.display) : payload; } else { payload = question == null ? '' : String(question); display = payload; } var useBank = gmGet('useBank') !== 'false'; if (useBank) { var qi = extractQuestionText(payload); var qText = qi.text || display || ''; if (qText) { var bankKey = (typeof md5 === 'function') ? md5(qText) : qText; var bank = getBank(); var entry = bank[bankKey]; if (entry && entry.answer) { var match = true; if (entry.options && entry.options.length > 0 && qi.options && qi.options.length > 0 && JSON.stringify(entry.options) !== JSON.stringify(qi.options)) match = false; if (match) { if (_currentQuestionMeta && entry.type) _currentQuestionMeta.typeName = entry.type; var pref = ''; if (_currentQuestionMeta) { pref = '第' + ((_currentQuestionMeta.index != null ? _currentQuestionMeta.index : -1) + 1); if (_currentQuestionMeta.total) pref += '/' + _currentQuestionMeta.total; pref += '题 [' + (_currentQuestionMeta.typeName || '未知') + '] '; } logger(pref + '本地题库命中: ' + entry.answer, 'green'); return Promise.resolve(entry.answer); } } } } var prefix = ''; if (_currentQuestionMeta) { prefix = '第' + ((_currentQuestionMeta.index != null ? _currentQuestionMeta.index : -1) + 1); if (_currentQuestionMeta.total) prefix += '/' + _currentQuestionMeta.total; prefix += '题 [' + (_currentQuestionMeta.typeName || '未知') + '] '; } logger(prefix + '题目:' + display, 'pink'); var thinkHtml = 'AI 思考中' + (retryCount > 0 ? ' (第' + (retryCount+1) + '次)' : ''); var thinkLog = logger(thinkHtml, 'gray'); return new Promise(function (resolve, reject) { var requestCompleted = false, longWaitTimer = null; var interval = Math.min(60000, setting.time !== undefined ? setting.time : 2500); var now = Date.now(); var wait = Math.max(0, _nextAiAllowedAt - now); _nextAiAllowedAt = Math.max(now, _nextAiAllowedAt) + interval; if (wait > 0) updateLogEntry(thinkLog, '等待 ' + Math.round(wait/1000) + 's 后发起请求...', 'gray'); longWaitTimer = setTimeout(function () { if (!requestCompleted) { requestCompleted = true; updateLogEntry(thinkLog, '超时重试... (第' + (retryCount+1) + '次)', 'orange'); getAnswer(type, question, retryCount + 1).then(resolve).catch(reject); } }, 300000 + wait); setTimeout(function () { if (requestCompleted) return; if (wait > 0) updateLogEntry(thinkLog, 'AI 思考中... (第' + (retryCount+1) + '次)', 'gray'); callApi(payload).then(function (answer) { if (requestCompleted) return; requestCompleted = true; clearTimeout(longWaitTimer); if (useBank) { var qi2 = extractQuestionText(payload); var qText2 = qi2.text || display || ''; if (qText2) { var c = answer.replace(/^[A-Z]\s*\n\s*/, '').trim(); var tName = _typeNames[type] || (_currentQuestionMeta ? _currentQuestionMeta.typeName : '') || ''; if (!tName && qi2.options) { if (qi2.options.length === 2) tName = '判断题'; else if (qi2.options.length > 2) tName = '多选题'; else tName = '单选题'; } var k = (typeof md5 === 'function') ? md5(qText2) : qText2; var b = getBank(); if (!b[k]) { b[k] = {text:qText2,options:qi2.options,answer:c,type:tName,savedAt:Date.now()}; saveBank(b); } } } updateLogEntry(thinkLog, '答案: ' + answer, 'purple'); resolve(answer.replace(/^[A-Z]\s*\n\s*/, '').trim()); }).catch(function (err) { if (requestCompleted) return; requestCompleted = true; clearTimeout(longWaitTimer); var msg = (err && err.msg) ? err.msg : 'API 调用失败'; updateLogEntry(thinkLog, msg, 'red'); gmSet('sub', 'false'); reject({ c: err ? err.c || 0 : 0 }); }); }, wait); }); } /* ========================= 防休眠 ========================= */ // Web Audio 静音振荡器(缓解浏览器对隐藏标签页的节流) 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 refreshCourseList() { var p = parseUrlParams(); return new Promise(function (resolve) { $.ajax({ url: _l.protocol + '//' + _l.host + '/mycourse/studentstudycourselist?courseId=' + p['courseid'] + '&chapterId=' + p['knowledgeid'] + '&clazzid=' + p['clazzid'] + '&mooc2=1', type: 'GET', dataType: 'html', success: function (res) { resolve(res); } }); }); } function clickNextPageBtn() { var btn = top.document.querySelector('#mainid > .prev_next.next'); if (btn) { btn.click(); return true; } return false; } function clickNextChapterBtn() { var btn = top.document.querySelector('#mainid > .prev_next.next'); if (btn) { btn.click(); return true; } var fb = top.document.querySelector('#prevNextFocusNext'); if (fb) { fb.click(); return true; } return false; } function detectSubTabPosition() { try { var selectors = ['#prev_tab > li', '.prev_ul > li', '#prevTabBox > li']; for (var i = 0; i < selectors.length; i++) { var nodes = top.document.querySelectorAll(selectors[i]); if (!nodes || nodes.length === 0) continue; var tabs = Array.prototype.slice.call(nodes); var activeIdx = -1; for (var j = 0; j < tabs.length; j++) { if (tabs[j].classList && tabs[j].classList.contains('active')) { activeIdx = j; break; } } if (activeIdx === -1) continue; return { activeIndex: activeIdx, total: tabs.length, hasNext: activeIdx < tabs.length - 1 }; } var currents = top.document.querySelector('span.currents'); if (currents) { var sib = currents.nextElementSibling; while (sib) { if (sib.tagName === 'SPAN') return { activeIndex: 0, total: 2, hasNext: true }; sib = sib.nextElementSibling; } } } catch (_) {} return null; } function toNext() { if (isPaused()) { setTimeout(toNext, 3000); return; } refreshCourseList().then(function (res) { var sub = detectSubTabPosition(); if (sub && sub.hasNext) { logger('本课时还有下一页 (' + (sub.activeIndex+1) + '/' + sub.total + ')', 'blue'); setTimeout(function () { if (!clickNextPageBtn()) clickNextChapterBtn(); }, 5000); return; } if (setting.review || !setting.work) { logger('切换到下一章节', 'blue'); setTimeout(function () { clickNextChapterBtn(); }, 5000); return; } var list = []; $(res).find('li').each(function () { var curid = $(this).find('.posCatalog_select').attr('id'), status = $(this).find('.prevHoverTips').text(), name = $(this).find('.posCatalog_name').attr('title'); if (curid && curid.indexOf('cur') !== -1) list.push({curid:curid,status:status,name:name}); }); var curId = $('#coursetree', window.parent.document).find('.posCatalog_active').attr('id'); var idx = list.findIndex(function (item) { return item.curid == curId; }); for (idx; idx < list.length - 1; idx++) { if (list[idx].status.indexOf('待完成') !== -1) { var sub2 = detectSubTabPosition(); if (sub2 && sub2.hasNext) { setTimeout(function () { if (!clickNextPageBtn()) clickNextChapterBtn(); }, 5000); return; } } var next = list[idx + 1]; if (next.status.indexOf('待完成') !== -1) { setTimeout(function () { clickNextChapterBtn(); }, 5000); return; } else if (next.status.indexOf('闯关') !== -1) { logger('闯关模式,请手动完成', 'red'); return; } else if (next.status.indexOf('开放') !== -1) { logger('章节未开放', 'red'); return; } } logger('此课程处理完毕', 'green'); }); } /* ========================= 任务调度 ========================= */ function switchMission() { _mlist.splice(0, 1); _domList.splice(0, 1); setTimeout(missonStart, 5000); } function missonStart() { if (isPaused()) { setTimeout(missonStart, 3000); return; } if (_mlist.length <= 0) { logger('所有任务处理完毕', 'green'); return toNext(); } var type = _mlist[0]['type'], dom = _domList[0], task = _mlist[0]; if (type == undefined) type = _mlist[0]['property']['module']; switch (type) { case 'video': { var mod = _mlist[0]['property']['module']; if (mod === 'insertvideo' || mod === 'insertaudio') { logger('处理' + (mod === 'insertaudio' ? '音频' : '视频'), 'purple'); missonVideo(dom, task); } 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; default: { var g = ['insertimage','insertanswerquestion','insertshare','insertquestion','insertdiscuss','insertsubject']; if (g.indexOf(type) !== -1) logger('跳过' + type, 'red'); else logger('不支持:' + type + ',跳过', 'red'); switchMission(); } } } /* ========================= 倍速 ========================= */ function getRate() { var stored = gmGet('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 isPlaybackRateDisabled(doc) { try { return doc.querySelectorAll('.vjs-playback-rate .vjs-menu-content .vjs-menu-item').length === 0; } catch (_) { return false; } } 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 missonVideo(dom, obj) { var prop = obj.property, name = prop.name, isAudio = prop.module === 'insertaudio', label = isAudio ? '音频' : '视频'; if (isAudio ? !setting.audio : !setting.video) { logger('不处理' + label + '任务', 'red'); return setTimeout(switchMission, 3000); } if (!setting.review && obj.isPassed === true && !isRedoMode()) { logger(label + '已完成', 'green'); return switchMission(); } var target = dom.length > 0 ? dom[0] : null; if (!target) { logger('未找到iframe,3秒重试', 'orange'); return setTimeout(function () { missonVideo(dom, obj); }, 3000); } logger('处理' + label + ':' + name); var executed = false, doc = target.contentDocument || target.contentWindow.document, iv = setInterval(function () { if (isPaused()) return; var media = doc.querySelector('video'); if (!media) { media = doc.querySelector('audio'); } if (media && !executed) { executed = true; clearInterval(iv); var userRate = getRate(), rateDisabled = !isAudio && isPlaybackRateDisabled(doc), finalRate = rateDisabled ? 1 : userRate; logger(name + ' 播放 (' + finalRate + 'x)', 'green'); media.pause(); media.muted = true; hookMediaRate(media, finalRate); media.play(); var resume = function () { if (isPaused()) return; if (media.paused) media.play(); }; media.addEventListener('pause', resume); var restored = finalRate <= 1; if (!restored) { var onUpdate = function () { if (!restored && isFinite(media.duration) && media.duration - media.currentTime < 10) { restored = true; try { delete media.playbackRate; } catch (_) {} hookMediaRate(media, 1); media.removeEventListener('timeupdate', onUpdate); } }; media.addEventListener('timeupdate', onUpdate); } media.addEventListener('ended', function () { logger(name + ' 播放完成', 'green'); media.removeEventListener('pause', resume); setTimeout(switchMission, 1000); }); } }, 2500); } /* ========================= 文档/阅读/读书 ========================= */ function missonBook(dom, obj) { if (isPaused()) { setTimeout(function () { missonBook(dom, obj); }, 3000); return; } if (obj.job === undefined && !isRedoMode()) { logger('读书已完成', 'green'); return switchMission(); } $.ajax({ url: _l.protocol + '//' + _l.host + '/ananas/job?jobid=' + obj.property.jobid + '&knowledgeid=' + _defaults.knowledgeid + '&courseid=' + _defaults.courseid + '&clazzid=' + _defaults.clazzId + '&jtoken=' + obj.jtoken + '&_dc=' + Date.now(), method: 'GET', success: function (res) { logger('读书: ' + (res.status ? res.msg : '异常'), res.status ? 'green' : 'red'); switchMission(); } }); } function missonDoucument(dom, obj) { if (isPaused()) { setTimeout(function () { missonDoucument(dom, obj); }, 3000); return; } if (obj.job === undefined && !isRedoMode()) { logger('文档已完成', 'green'); return switchMission(); } $.ajax({ url: _l.protocol + '//' + _l.host + '/ananas/job/document?jobid=' + obj.property.jobid + '&knowledgeid=' + _defaults.knowledgeid + '&courseid=' + _defaults.courseid + '&clazzid=' + _defaults.clazzId + '&jtoken=' + obj.jtoken + '&_dc=' + Date.now(), method: 'GET', success: function (res) { logger('文档: ' + (res.status ? res.msg : '异常'), res.status ? 'green' : 'red'); switchMission(); } }); } function missonRead(dom, obj) { if (isPaused()) { setTimeout(function () { missonRead(dom, obj); }, 3000); return; } if (obj.job === undefined && !isRedoMode()) { logger('阅读已完成', 'green'); return switchMission(); } $.ajax({ url: _l.protocol + '//' + _l.host + '/ananas/job/readv2?jobid=' + obj.property.jobid + '&knowledgeid=' + _defaults.knowledgeid + '&courseid=' + _defaults.courseid + '&clazzid=' + _defaults.clazzId + '&jtoken=' + obj.jtoken + '&_dc=' + Date.now(), method: 'GET', success: function (res) { logger('阅读: ' + (res.status ? res.msg : '异常'), res.status ? 'green' : 'red'); switchMission(); } }); } /* ========================= 测验处理(手机版) ========================= */ function missonWork(dom, obj) { if (!setting.work) { logger('用户设置不自动处理测验,准备处理下一个任务', 'green') switchMission() return } var isDo = false; if (setting.task) { logger("当前只处理任务点任务", 'red') isDo = obj.jobid != undefined } else { logger("当前默认处理所有任务(包括非任务点任务)", 'red') isDo = true } if (!isDo) { logger('用户设置只处理属于任务点的任务,准备处理下一个任务', 'green') switchMission() return } 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(function () { startDoPhoneCyWork(0, dom, phoneWeb); }, 300); } else { setTimeout(function () { startDoCyWork(0, dom); }, 300); } } /* ========================= getElement(修复:原来缺失的函数) ========================= */ // 轮询在 iframe 子文档中查找匹配选择器的元素 function getElement(containerDoc, selector, maxAttempts) { maxAttempts = maxAttempts || 60; return new Promise(function (resolve) { var retries = 0; var check = function () { try { var el = containerDoc.querySelector(selector); if (el) return resolve(el); } catch (e) {} retries++; if (retries >= maxAttempts) return resolve(null); setTimeout(check, 1000); }; check(); }); } /* ========================= 轮询查找 iframe 元素 ========================= */ 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(); }); } /* ========================= startDoPhoneCyWork ========================= */ function startDoPhoneCyWork(index, doms, phoneWeb) { if (isPaused()) { setTimeout(function () { startDoPhoneCyWork(index, doms, phoneWeb); }, 3000); return; } logger('正在加载测验...', 'purple'); // 直接导航 iframe,从 iframe 真实 DOM 检查状态(比 XHR 可靠) var iframeEl = doms.length ? doms[0] : doms; if (!iframeEl) { logger('没有 iframe,跳过测验', 'red'); _mlist.splice(0,1); _domList.splice(0,1); return setTimeout(missonStart, 2000); } $(iframeEl).attr('src', phoneWeb); waitForIframeContent(iframeEl, function (loadedDoc) { if (!loadedDoc) { logger('iframe 加载失败,跳过测验', 'red'); _mlist.splice(0,1); _domList.splice(0,1); return setTimeout(missonStart, 3000); } // 主检测:从 iframe 内标准状态元素获取 var workStatus = $(loadedDoc).find('.testTit_status').text().trim() || $(loadedDoc).find('.newTestTitle').text().trim() || ''; var isCompleted = workStatus.indexOf('已完成') !== -1; if (!isCompleted && !workStatus) { // 保底检测:完成后页面可能无状态元素,但会显示特殊标记 isCompleted = $(loadedDoc).find('.prevHoverTips:contains(已完成)').length > 0 || $(loadedDoc).find('.posCatalog_select:contains(已完成)').length > 0 || $(loadedDoc).find('[class*="finish"],[class*="complete"]').length > 0 || $(loadedDoc).find('.catalogueIcon:contains(完成)').length > 0; } logger('测验状态: ' + (workStatus || (isCompleted ? '已完成' : '待做')), 'blue'); if (isCompleted) { logger('测验已完成,跳过', 'green'); _mlist.splice(0,1); _domList.splice(0,1); return setTimeout(missonStart, 3000); } if (workStatus.indexOf('待批阅') !== -1) { logger('测验待批阅,跳过', 'red'); _mlist.splice(0,1); _domList.splice(0,1); return setTimeout(missonStart, 5000); } logger('开始答题', 'green'); doPhoneWorkIframe(iframeEl); }); } // 等 iframe 内容加载完成 function waitForIframeContent(iframeEl, callback) { var retries = 0; var check = function () { try { var doc = iframeEl.contentDocument || iframeEl.contentWindow.document; if (doc && doc.body && doc.body.innerHTML && doc.body.innerHTML.length > 100) { callback(doc); return; } } catch (e) {} retries++; if (retries > 120) { logger('iframe 加载超时,尝试继续', 'orange'); callback(null); return; } if (retries % 15 === 0) { logger('等待 iframe 加载中,已等' + (retries) + '秒...', 'orange'); } setTimeout(check, 1000); }; setTimeout(check, 1000); } /* ========================= doPhoneWork ========================= */ function doPhoneWorkIframe(iframeEl) { try { var doc = iframeEl.contentDocument || iframeEl.contentWindow.document; var $doc = $(doc); var form = $doc.find('.Wrappadding form'); if (!form.length) form = $doc.find('form'); $subBtn = form.find('.zquestions .zsubmit .btn-ok-bottom'); $okBtn = $doc.find('#okBtn'); $saveBtn = form.find('.zquestions .zsubmit .btn-save'); var timuList = form.find('.zquestions .Py-mian1, .zquestions .TiMu, .CeYan .TiMu'); if (timuList.length === 0) { timuList = $doc.find('.Py-mian1, .TiMu, .Zy_TItle, .questionLi, .mark_table .whiteDiv .questionLi, [class*="TiMu"], [class*="question"]'); } // 检测:如果找不到题目或提交按钮,说明可能已提交/页面结构异常 if (timuList.length === 0 && $subBtn.length === 0) { logger('未找到题目,测验可能已提交通过,跳过', 'orange'); _mlist.splice(0,1); _domList.splice(0,1); return setTimeout(missonStart, 3000); } logger('找到 ' + timuList.length + ' 道题', 'blue'); startDoPhoneTimu(0, timuList); } catch (e) { logger('iframe 内容不可访问: ' + e.message, 'red'); _mlist.splice(0,1); _domList.splice(0,1); setTimeout(missonStart, 3000); } } /* ========================= startDoPhoneTimu ========================= */ function startDoPhoneTimu(c, timuList) { var index = c; var TimuList = timuList; if (isPaused()) { setTimeout(function () { startDoPhoneTimu(c, timuList); }, 3000); return; } if (c == timuList.length) { var doSubmit = gmGet('sub') === 'true' || gmGet('force') === 'true'; if (doSubmit) { logger('自动提交...', 'green'); setTimeout(function () { $subBtn.click(); setTimeout(function () { $okBtn.click(); _mlist.splice(0,1); _domList.splice(0,1); setTimeout(missonStart, 3000); }, 3000); }, 5000); } else { logger('测验处理完成,自动保存!', 'green'); setTimeout(function () { $saveBtn.click(); _mlist.splice(0,1); _domList.splice(0,1); setTimeout(missonStart, 3000); }, 5000); } return; } // 获取当前题目所属的window对象 (可能是iframe) let contextWindow = TimuList[index] ? (TimuList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow; let questionFull = $(TimuList[index]).find('.Py-m1-title').html() let _question = tidyQuestion(questionFull).replace(/.*?\[.*?题\]\s*\n\s*/, '').trim() let typeName = questionFull.match(/.*?\[(.*?)]|$/)[1]; let _type = _typeMap[typeName]; let _a = [] let _answerTmpArr var check_answer_flag = 0; // 如果题型不在预设类型中,根据DOM结构自动识别题型 if (_type === undefined) { logger('未知题型: ' + typeName + ',尝试自动识别', 'blue'); let singleChoiceList = $(TimuList[index]).find('.answerList.singleChoice li'); let multiChoiceList = $(TimuList[index]).find('.answerList.multiChoice li'); if (singleChoiceList && singleChoiceList.length > 0) { _type = 0; logger('自动识别为单选题', 'green'); } else if (multiChoiceList && multiChoiceList.length > 0) { _type = 1; logger('自动识别为多选题', 'green'); } else { let tkList = $(TimuList[index]).find('.blankList2 input'); if (tkList && tkList.length > 0) { _type = 2; logger('自动识别为填空题', 'green'); } else { let panduanList = $(TimuList[index]).find('.answerList.panduan li'); if (panduanList && panduanList.length > 0) { _type = 3; logger('自动识别为判断题', 'green'); } else { let textareaList = $(TimuList[index]).find('textarea'); let editorList = $(TimuList[index]).find('.edui-editor'); if ((textareaList && textareaList.length > 0) || (editorList && editorList.length > 0)) { _type = 4; logger('自动识别为简答题或材料题', 'green'); } } } } } _currentQuestionMeta = { index: index, total: TimuList.length, typeName: typeName } switch (_type) { case 0: { // 单选题 _answerTmpArr = $(TimuList[index]).find('.answerList.singleChoice li') let mergedAnswers = []; _answerTmpArr.each(function () { var answerText = $(this).text().replace(/[ABCD]/g, '').trim(); mergedAnswers.push(answerText); }); mergedAnswers = mergedAnswers.join("|"); _question = buildPrompt({ type: '单选题', question: _question, options: mergedAnswers.split('|') }) // 判断是否已作答(aria-label 兜底) for (let i = 0; i < _answerTmpArr.length; i++) { if ($(_answerTmpArr[i]).attr('aria-label')) { logger(index + 1 + '此题已作答,准备切换下一题', 'green') markAnswered(index); check_answer_flag = 1; setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30) 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) } // 兜底:AI 有时会把单选题答案输出为多选格式(e.g. "能谈|爱谈"),拆开逐项匹配 if (_i == -1 && agrs.indexOf('|') !== -1) { var parts = agrs.split('|'); for (var pi = 0; pi < parts.length; pi++) { var p = parts[pi].trim(); if (!p) continue; var matchIdx = _a.findIndex(function (item) { return item == p; }); if (matchIdx === -1) matchIdx = findBestFuzzyMatch(_a, p); if (matchIdx !== -1) { _i = matchIdx; break; } } } if (_i == -1) { logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red') gmSet('sub', 'false') setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) } else { $(_answerTmpArr[_i]).click() markAnswered(index); logger('自动答题成功,准备切换下一题', 'green') setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) } }).catch((agrs) => { if (agrs && 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')) { logger(index + 1 + '此题已作答,准备切换下一题', 'green') markAnswered(index); check_answer_flag = 1; setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30) break } } if (check_answer_flag == 0) { getAnswer(_type, _question).then((agrs) => { if (agrs == '暂无答案') { logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red') gmSet('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 // 原版方式:点父元素 + 写隐藏 input // 多选操作:点 li + 操作内部 checkbox + 点子元素 function _checkOpt(el) { if (!el || !el.length) return; var raw = el[0] || el; // 找内部 checkbox var cb = $(raw).find('input[type="checkbox"]')[0]; if (cb) { cb.checked = true; try { cb.dispatchEvent(new Event('change', {bubbles:true})); } catch(e) {} try { cb.dispatchEvent(new Event('input', {bubbles:true})); } catch(e) {} try { cb.click(); } catch(e) {} } // 点击 li + 父 + 子 try { raw.click(); } catch(e) {} try { $(raw).parent().click(); } catch(e) {} try { $(raw).trigger('mousedown'); } catch(e) {} $(raw).find('a,span,label').each(function(){ try { this.click(); } catch(e){} }); } $.each(_answerTmpArr, function(i) { if (agrs.indexOf(_multiOptions[i]) != -1) { _matchedAny = true; setTimeout(function() { _checkOpt($(_answerTmpArr[i])); }, 300 * i); } }); if (!_matchedAny) { var fuzzyIndices = findFuzzyMatchMultiple(_multiOptions, agrs); for (var fi = 0; fi < fuzzyIndices.length; fi++) { (function(idx) { setTimeout(function() { _checkOpt($(_answerTmpArr[idx])); }, 300 * fi); })(fuzzyIndices[fi]); } } setTimeout(function() { markAnswered(index); logger('自动答题成功,准备切换下一题', 'green'); setTimeout(function() { startDoPhoneTimu(index + 1, TimuList); }, setting.time); }, 1500); } }).catch((agrs) => { if (agrs && 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"); markAnswered(index); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30); break } _question = buildPrompt({ type: '填空题', question: _question, answer_format: "多个填空用'|'分隔" }) getAnswer(_type, _question).then((agrs) => { if (agrs == '暂无答案') { logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red') gmSet('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) ueditor = contextWindow.UE.instants['ueditorInstant' + editorIndex] 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})`, '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)) }) markAnswered(index); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time + 300 * editorBlocks.length) }).catch((agrs) => { if (agrs && 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"); markAnswered(index); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30); break } getAnswer(_type, _question).then((agrs) => { if (agrs == '暂无答案') { logger('AI未能完美匹配正确答案,请尝试更换更高级模型或手动选择,跳过此题', 'red'); gmSet('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 && agrs['c'] == 0) { setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) } }) } else { logger('未找到填空题输入区域,跳过此题', 'red') gmSet('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')) { logger(index + 1 + '此题已作答,准备切换下一题', 'green'); markAnswered(index); check_answer_flag = 1; setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30) 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 && 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"]') if (jdEditorBlocks && jdEditorBlocks.length > 0) { if (jdTextareas.length > 0 && $(jdTextareas[0]).val() && $(jdTextareas[0]).val().trim() !== '' && !isRedoMode()) { logger(index + 1 + '简答题已作答,准备切换下一题', 'green'); markAnswered(index); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30); break } getAnswer(_type, _question).then((agrs) => { if (agrs == '暂无答案') { logger('AI无法匹配答案,请手动完成', 'red'); gmSet('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) ueditor = contextWindow.UE.instants['ueditorInstant' + editorIndex] if (!ueditor && itemId && contextWindow.UE && contextWindow.UE.getEditor) ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId) if (ueditor) { ueditor.setContent(agrs); logger(`简答题已填入`, 'green') } else { logger(`简答题未找到编辑器实例`, '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 && 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'); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, 30) } else { getAnswer(_type, _question).then((agrs) => { if (agrs == '暂无答案') { logger('AI无法匹配答案,请手动完成', 'red'); gmSet('sub', 'false') } else { $(jdTextareas[0]).val(agrs); $(jdTextareas[0]).trigger('input').trigger('change'); markAnswered(index); 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'); gmSet('sub', 'false'); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) } break } case 5: { getAnswer(_type, _question).then((agrs) => { gmSet('sub', 'false'); logger('此类型题目无法区分单/多选,请手动选择答案', 'red'); setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) }).catch((agrs) => { if (agrs && agrs['c'] == 0) { setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) } }) break } default: logger('暂不支持处理此类型题目:' + typeName + ',跳过!请手动作答。', 'red') gmSet('sub', 'false') setTimeout(() => { startDoPhoneTimu(index + 1, TimuList) }, setting.time) break } } /* ========================= PC版测验处理(startDoCyWork / doWork / startDoWork) ========================= */ function startDoCyWork(index, doms) { if (isPaused()) { setTimeout(function () { startDoCyWork(index, doms); }, 3000); return; } if (index == doms.length) { logger('全部测验处理完毕', 'green'); return setTimeout(switchMission, 5000); } var rawIframe = doms[index]; if (!rawIframe) { return setTimeout(function () { startDoCyWork(index, doms); }, 5000); } function waitForContent(retry) { retry = retry || 0; if (retry > 40) { _domList.splice(0,1); logger('加载超时跳过', 'red'); return setTimeout(switchMission, 2000); } try { var doc = rawIframe.contentDocument || rawIframe.contentWindow.document; if (doc && doc.body && doc.body.innerHTML && doc.body.innerHTML.length > 100) { processFrame(rawIframe); } else { setTimeout(function () { waitForContent(retry + 1); }, 500); } } catch (e) { setTimeout(function () { waitForContent(retry + 1); }, 500); } } function processFrame(iframe) { var doc = iframe.contentDocument || iframe.contentWindow.document; var workStatus = $(doc).find('.testTit_status, .newTestTitle').first().text().trim() || '待做'; logger('测验状态: ' + workStatus, 'blue'); var nested = $(doc).find('iframe').first(); if (nested.length && nested[0].contentDocument) { var ndoc = nested[0].contentDocument || nested[0].contentWindow.document; if (ndoc && ndoc.body) { doc = ndoc; workStatus = $(doc).find('.testTit_status, .newTestTitle').first().text().trim() || workStatus; } } if (workStatus.indexOf('已完成') !== -1) { if (isRedoMode()) { logger('已完成,重做模式继续', 'blue'); setTimeout(function () { doWork(index, doms, iframe); }, 300); } else { logger('已完成跳过', 'green'); _mlist.splice(0,1); _domList.splice(0,1); setTimeout(function () { startDoCyWork(index+1, doms); }, 2000); } return; } if (workStatus.indexOf('待做') !== -1 || workStatus.indexOf('待完成') !== -1 || workStatus.indexOf('重做') !== -1 || workStatus.indexOf('未达到') !== -1) { setTimeout(function () { doWork(index, doms, iframe); }, 5000); } else if (workStatus.indexOf('待批阅') !== -1) { _mlist.splice(0,1); _domList.splice(0,1); setTimeout(function () { startDoCyWork(index+1, doms); }, 5000); } else { _mlist.splice(0,1); _domList.splice(0,1); logger('未知状态[' + workStatus + ']跳过', 'red'); setTimeout(function () { startDoCyWork(index+1, doms); }, 2000); } } waitForContent(0); } function doWork(index, doms, dom) { var doc; try { doc = dom.contentDocument || dom.contentWindow.document; } catch (e) { doc = null; } if (!doc || !doc.body) { _mlist.splice(0,1); _domList.splice(0,1); logger('iframe不可访问', 'red'); return setTimeout(function () { startDoCyWork(index+1, doms); }, 2000); } var nestedFrames = $(doc).find('iframe'); for (var ni = 0; ni < nestedFrames.length; ni++) { var ndoc; try { ndoc = nestedFrames[ni].contentDocument || nestedFrames[ni].contentWindow.document; } catch (e) { continue; } if (!ndoc || !ndoc.body || ndoc.body.innerHTML.length < 100) continue; doc = ndoc; break; } $frame_c = $(doc); $subBtn = $frame_c.find('.ZY_sub .btnSubmit, .submitBtn, .btn-ok-bottom'); $saveBtn = $frame_c.find('.ZY_sub .btnSave'); var allQ = $frame_c.find('.zquestions .Py-mian1, .Py-mian1, .CeYan .TiMu, .TiMu, .questionLi'); var realQ = []; allQ.each(function () { if ($(this).find('.Py-m1-title, .Zy_TItle, .mark_name').length > 0) realQ.push(this); }); logger('找到 ' + realQ.length + ' 道题', 'blue'); if (realQ.length > 0) { startDoWork(index, doms, 0, $(realQ)); } else { _mlist.splice(0,1); _domList.splice(0,1); logger('找不到有效题目跳过', 'red'); setTimeout(function () { startDoCyWork(index+1, doms); }, 2000); } } function startDoWork(index, doms, c, TiMuList) { if (isPaused()) { setTimeout(function () { startDoWork(index, doms, c, TiMuList); }, 3000); return; } if (c == TiMuList.length) { if (gmGet('sub') === 'true' || gmGet('force') === 'true') { logger('自动提交...', 'green'); setTimeout(function () { $subBtn.click(); setTimeout(function () { $frame_c.find('#confirmSubWin > div > div > a.bluebtn').click(); _mlist.splice(0,1); _domList.splice(0,1); setTimeout(function () { startDoCyWork(index+1, doms); }, 3000); }, 3000); }, 5000); } return; } var qFull = $(TiMuList[c]).find('.Zy_TItle.clearfix > div').html() || $(TiMuList[c]).html() || ''; if (!qFull || qFull.trim() === '' || qFull.trim() === ' ') { logger('第' + (c+1) + '题空跳过', 'orange'); return setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, 30); } var question = tidyQuestion(qFull); var typeName = (question.match(/^【(.*?)】/) || [,''])[1]; var type = _typeMap[typeName]; _currentQuestionMeta = { index: c, total: TiMuList.length, typeName: typeName }; if (type === undefined) { var choiceList = $(TiMuList[c]).find('.Zy_ulTop li'), fillBlank = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1'), editor = $(TiMuList[c]).find('.edui-editor'); if (choiceList.length) { var hasCb = $(TiMuList[c]).find('input[type="checkbox"]').length > 0; type = (choiceList.length === 2 && $(choiceList[0]).text().match(/[对√]/)) ? 3 : (hasCb ? 1 : 0); } else if (fillBlank.length) type = 2; else type = 4; } var answerArr, opts = []; switch (type) { case 0: answerArr = $(TiMuList[c]).find('.Zy_ulTop li a'); var _already = false; answerArr.each(function () { var $opt = $(this); if ($opt.attr('aria-label') || $opt.hasClass('cur') || $opt.parent().find('.check_answer').length > 0 || $opt.closest('li').find('.check_answer').length > 0 || ($opt.parent().attr('class')||'').indexOf('check') >= 0) _already = true; }); if (_already && !isRedoMode()) { markAnswered(c); logger('第' + (c+1) + '题已作答,跳过', 'green'); return setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, 30); } var merged = []; answerArr.each(function () { merged.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'单选题', question:question, options:merged }); answerArr.each(function (i, t) { opts.push(tidyStr($(t).html())); }); getAnswer(type, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') $(TiMuList[c]).find('.Zy_TItle.clearfix > div').append('

' + agrs); var idx = opts.indexOf(agrs); if (idx === -1) idx = findBestFuzzyMatch(opts, agrs); if (idx !== -1) { $(answerArr[idx]).parent().click(); markAnswered(c); } setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }); break; case 1: answerArr = $(TiMuList[c]).find('.Zy_ulTop li a'); var _already = false; answerArr.each(function () { var $opt = $(this); if ($opt.attr('aria-label') || $opt.hasClass('cur') || $opt.parent().find('.check_answer').length > 0 || $opt.closest('li').find('.check_answer').length > 0 || ($opt.parent().attr('class')||'').indexOf('check') >= 0) _already = true; }); if (_already && !isRedoMode()) { logger('第' + (c+1) + '题已作答,跳过', 'green'); return setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, 30); } var merged2 = []; answerArr.each(function () { merged2.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'多选题', question:question, options:merged2, answer_format:"用'|'分割多个答案" }); getAnswer(type, question).then(function (agrs) { opts = []; answerArr.each(function (i, t) { opts.push(tidyStr($(t).html())); }); var clicked = []; answerArr.each(function (i, t) { if (agrs.indexOf(opts[i]) !== -1) { $(answerArr[i]).parent().click(); clicked.push(['A','B','C','D','E','F','G'][i]); } }); if (clicked.length === 0) { var fuzzy = findFuzzyMatchMultiple(opts, agrs); for (var fi = 0; fi < fuzzy.length; fi++) { $(answerArr[fuzzy[fi]]).parent().click(); clicked.push(['A','B','C','D','E','F','G'][fuzzy[fi]]); } } if (clicked.length > 0) { var firstLi = $(TiMuList[c]).find('.Zy_ulTop li:first'), onclickAttr = firstLi.attr('onclick') || '', idMatch = onclickAttr.match(/addcheck\((\d+)/); if (idMatch) $(TiMuList[c]).find('#answer' + idMatch[1]).val(clicked.join('')); } setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }).catch(function () { setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }); break; case 2: question = buildPrompt({ type:'填空题', question:question, answer_format:"用'|'分割多个答案" }); var taList = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1'); getAnswer(type, question).then(function (agrs) { var as = agrs.split('|'); taList.each(function (i, t) { setTimeout(function () { $(t).find('textarea').html('

' + (as[i]||as[0]) + '

'); }, 300); }); setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }); break; case 3: answerArr = $(TiMuList[c]).find('.Zy_ulTop li a'); answerArr.each(function (i, t) { opts.push(tidyStr($(t).html())); }); question = buildPrompt({ type:'判断题', question:question, answer_format:"只回答正确或错误" }); getAnswer(type, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') $(TiMuList[c]).find('.Zy_TItle.clearfix > div').append('

' + agrs); agrs = '正确|是|对|√|T|ri'.indexOf(agrs) !== -1 ? '对' : '错'; var idx = opts.indexOf(agrs); if (idx === -1) idx = findBestFuzzyMatch(opts, agrs); if (idx !== -1) $(answerArr[idx]).parent().click(); markAnswered(c); setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }); break; case 4: case 5: case 6: var xContainer = $(TiMuList[c]).find('.Zy_ulTk .XztiHover1'); if (!xContainer.length) xContainer = $(TiMuList[c]); var ueIframe = xContainer.find('iframe[id^="ueditor_"]'); if (!ueIframe.length) ueIframe = $(TiMuList[c]).find('iframe'); if (ueIframe.length) { var types = {4:'简答题',5:'写作题',6:'翻译题'}, fmts = {4:'用50字简要回答',5:'用英文根据题目进行写作',6:'中文英文互译'}; var p = buildPrompt({ type:types[type]||'简答题', question:question, answer_format:fmts[type]||'简要回答' }); getAnswer(type, p).then(function (agrs) { ueIframe.each(function (fi, frame) { setTimeout(function () { try { var fDoc = frame.contentDocument || frame.contentWindow.document; if (fDoc && fDoc.body) fDoc.body.innerHTML = '

' + agrs.replace(/\n/g,'
') + '

'; } catch (ef) {} }, 200 + fi*300); }); var ta = xContainer.find('textarea'); if (!ta.length) ta = $(TiMuList[c]).find('textarea'); ta.each(function (ti, t) { var tid = $(t).attr('id')||$(t).attr('name'); if (tid) { try { UE.getEditor(tid).setContent(agrs); } catch (e) {} } $(t).val(agrs); $(t).html('

' + agrs + '

'); }); logger('已填入答案', 'green'); setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time + 400); }).catch(function () { setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); }); } else { logger('未找到答题框', 'orange'); setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); } break; default: setTimeout(function () { startDoWork(index, doms, c+1, TiMuList); }, setting.time); } } /* ========================= 考试处理 ========================= */ function missonExam() { logger('处理考试', 'green'); var list = $('.mark_table .whiteDiv .questionLi, .mark_table .questionLi'); if (list.length === 0) list = $('.questionLi'); doExam(0, list); } function toNextExam() { if (gmGet('examTurn') === 'true') { var delay = Math.floor(Math.random() * 4000) + 3000; setTimeout(function () { var n = $('.nextBtn:visible:first'); if (n.length) n.click(); }, delay); } } function doExam(index, TiMuList) { if (isPaused()) { setTimeout(function () { doExam(index, TiMuList); }, 3000); return; } if (index >= TiMuList.length) { logger('考试处理完毕', 'green'); if (gmGet('sub') === 'true' || gmGet('force') === 'true') { var s = $('.examSubmitBtn:visible:first'); if (s.length) setTimeout(function () { s.click(); }, 3000); } return; } var timu = $(TiMuList[index]), typeName = timu.attr('typename') || ''; var qType = _typeMap[typeName]; if (qType === undefined) { var ansP = timu.find('.clearfix.answerBg .fl.answer_p'), textareas = timu.find('textarea[name^="answerEditor"], .subEditor textarea'); if (ansP.length) { qType = timu.find('.clearfix.answerBg input[type="checkbox"]').length > 0 ? 1 : 0; } else if (textareas.length) qType = 4; else { logger('未知题型:' + typeName, 'red'); toNextExam(); return; } } var qFull = timu.find('.mark_name').html() || '', question = tidyQuestion(qFull).replace(/^[(].*?[)]/, '').trim(); _currentQuestionMeta = { index: index, total: TiMuList.length, typeName: typeName }; var ansdom = timu.find('.stem_answer'); switch (qType) { case 0: { var ansArr = ansdom.find('.clearfix.answerBg .fl.answer_p'); var merged = []; ansArr.each(function () { merged.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'单选题', question:question, options:merged }); var answered = false; ansArr.each(function () { if (($(this).parent().find('span').attr('class')||'').indexOf('check_answer') !== -1) answered = true; }); if (answered && !isRedoMode()) { toNextExam(); break; } getAnswer(qType, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') timu.find('.mark_name').append('

' + agrs); var opts = []; ansArr.each(function (i, t) { opts.push(tidyStr($(t).html())); }); var idx = opts.indexOf(agrs); if (idx === -1) idx = findBestFuzzyMatch(opts, agrs); if (idx !== -1) { if (gmGet('goodStudent') === 'true') $(ansArr[idx]).parent().find('span').css('font-weight','bold'); else $(ansArr[idx]).parent().click(); } toNextExam(); }); break; } case 1: { var ansArr2 = ansdom.find('.clearfix.answerBg .fl.answer_p'); var merged2 = []; ansArr2.each(function () { merged2.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'多选题', question:question, options:merged2, answer_format:"用'|'分割多个答案" }); getAnswer(qType, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') timu.find('.mark_name').append('

' + agrs); var opts = []; ansArr2.each(function (i, t) { opts.push(tidyStr($(t).html())); }); ansArr2.each(function (i, t) { if (agrs.indexOf(opts[i]) !== -1) { if (gmGet('goodStudent') === 'true') $(ansArr2[i]).parent().find('span').css('font-weight','bold'); else $(ansArr2[i]).parent().click(); } }); var fuzzy = findFuzzyMatchMultiple(opts, agrs); for (var fi = 0; fi < fuzzy.length; fi++) { (function(idx) { if (gmGet('goodStudent') === 'true') $(ansArr2[idx]).parent().find('span').css('font-weight','bold'); else $(ansArr2[idx]).parent().click(); })(fuzzy[fi]); } toNextExam(); }); break; } case 2: { var taList = ansdom.find('.Answer .divText .subEditor textarea'); question = buildPrompt({ type:'填空题', question:question, answer_format:"用'|'分割多个答案" }); getAnswer(qType, question).then(function (agrs) { var as = agrs.split('|'); taList.each(function (i, t) { var id = $(t).attr('id'); setTimeout(function () { UE.getEditor(id).setContent(as[i]); }, 300); }); toNextExam(); }); break; } case 3: { var ansArr3 = ansdom.find('.clearfix.answerBg .fl.answer_p'); question = buildPrompt({ type:'判断题', question:question, answer_format:"只回答正确或错误" }); getAnswer(qType, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') timu.find('.mark_name').append('

' + agrs); var opts = []; ansArr3.each(function (i, t) { opts.push($(t).text().trim()); }); var judge = parseJudgeAnswer(agrs); if (judge !== null) { var idx = findJudgeOptionIndex(opts, judge === 'true'); if (idx !== -1) { if (gmGet('goodStudent') === 'true') $(ansArr3[idx]).parent().find('span').css('font-weight','bold'); else $(ansArr3[idx]).parent().click(); } } toNextExam(); }); break; } case 4: case 5: case 6: { var taList = findAnswerTextareas(ansdom); if (!taList.length) { toNextExam(); break; } var types = {4:'简答题',5:'写作题',6:'翻译题'}, fmts = {4:'用50字简要回答',5:'用英文根据题目进行写作',6:'中文英文互译'}; var p = buildPrompt({ type:types[qType]||typeName, question:question, answer_format:fmts[qType]||'简要回答' }); getAnswer(qType, p).then(function (agrs) { taList.each(function (i, t) { var id = $(t).attr('id')||$(t).attr('name'); setTimeout(function () { try { UE.getEditor(id).setContent(agrs); } catch (e) { $(t).val(agrs); } }, 300 + i*200); }); setTimeout(toNextExam, 300 + 200*taList.length); }); break; } default: toNextExam(); } } /* ========================= 考试预览 ========================= */ function missonExamPreview() { logger('处理考试预览', 'green'); var list = $('.mark_table .questionLi'); logger('共 ' + list.length + ' 题', 'blue'); doExamPreview(0, list); } function getExamPreviewType(timu) { var typeName = timu.attr('typename'); if (typeName && _typeMap[typeName] !== undefined) return { type: _typeMap[typeName], typeName: typeName }; var text = timu.find('.colorShallow').text() || timu.find('.mark_name').text() || ''; var m = text.match(/(单选题|多选题|填空题|判断题|简答题|论述题|写作题|翻译题)/); if (m && _typeMap[m[1]] !== undefined) return { type: _typeMap[m[1]], typeName: m[1] }; var opts = timu.find('.answerBg .answer_p'); if (opts.length) return { type: timu.find('.answerBg input[type="checkbox"]').length > 0 ? 1 : 0, typeName: typeName || (opts.length > 2 ? '多选题' : '单选题') }; var tas = timu.find('textarea[name^="answerEditor"], .subEditor textarea'); if (tas.length) return { type: 4, typeName: typeName || '简答题' }; return { type: undefined, typeName: typeName || '未知' }; } function doExamPreview(index, TiMuList) { if (isPaused()) { setTimeout(function () { doExamPreview(index, TiMuList); }, 3000); return; } if (index >= TiMuList.length) { logger('预览答题完成', 'green'); return; } var timu = $(TiMuList[index]), info = getExamPreviewType(timu), type = info.type, typeName = info.typeName; var qFull = timu.find('.mark_name').html() || '', question = tidyQuestion(qFull).replace(/^[(].*?[)]/, '').trim(); _currentQuestionMeta = { index: index, total: TiMuList.length, typeName: typeName }; function nextSoon() { setTimeout(function () { doExamPreview(index+1, TiMuList); }, (setting.time||2500) + Math.floor(Math.random()*1500)); } if (type === undefined) { logger('第' + (index+1) + '题无法识别', 'red'); return nextSoon(); } switch (type) { case 0: { var ans = timu.find('.answerBg .answer_p'); if (!ans.length) return nextSoon(); var merged = []; ans.each(function () { merged.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'单选题', question:question, options:merged }); getAnswer(type, question).then(function (agrs) { var opts = []; ans.each(function (i, t) { opts.push(tidyStr($(t).html())); }); var idx = opts.indexOf(agrs); if (idx === -1) idx = findBestFuzzyMatch(opts, agrs); if (idx !== -1) { if (gmGet('goodStudent') === 'true') $(ans[idx]).parent().find('span').css('font-weight','bold'); else $(ans[idx]).parent().click(); } nextSoon(); }); break; } case 1: { var ans2 = timu.find('.answerBg .answer_p'); if (!ans2.length) return nextSoon(); var merged2 = []; ans2.each(function () { merged2.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'多选题', question:question, options:merged2, answer_format:"用'|'分割多个答案" }); getAnswer(type, question).then(function (agrs) { var opts2 = []; ans2.each(function (i, t) { opts2.push(tidyStr($(t).html())); }); ans2.each(function (i, t) { if (agrs.indexOf(opts2[i]) !== -1) $(this).click(); }); var fuzzy = findFuzzyMatchMultiple(opts2, agrs); for (var fi = 0; fi < fuzzy.length; fi++) $(ans2[fuzzy[fi]]).click(); nextSoon(); }); break; } case 2: { var ta = findAnswerTextareas(timu); if (!ta.length) return nextSoon(); question = buildPrompt({ type:'填空题', question:question, answer_format:"用'|'分割多个答案" }); getAnswer(type, question).then(function (agrs) { var as = agrs.split('|'); ta.each(function (i, t) { var id = $(t).attr('id'); setTimeout(function () { try { UE.getEditor(id).setContent(as[i]); } catch (e) {} }, 300); }); nextSoon(); }); break; } case 3: { var ans3 = timu.find('.answerBg .answer_p'); if (!ans3.length) return nextSoon(); question = buildPrompt({ type:'判断题', question:question, answer_format:"只回答正确或错误" }); getAnswer(type, question).then(function (agrs) { var opts3 = []; ans3.each(function (i, t) { opts3.push($(t).text().trim()); }); var judge = parseJudgeAnswer(agrs); if (judge !== null) { var idx = findJudgeOptionIndex(opts3, judge === 'true'); if (idx !== -1) { if (gmGet('goodStudent') === 'true') $(ans3[idx]).parent().find('span').css('font-weight','bold'); else $(ans3[idx]).parent().click(); } } nextSoon(); }); break; } case 4: case 5: case 6: { var ta2 = findAnswerTextareas(timu); if (!ta2.length) return nextSoon(); var types = {4:'简答题',5:'写作题',6:'翻译题'}, fmts = {4:'用50字简要回答',5:'用英文根据题目进行写作',6:'中文英文互译'}; var p = buildPrompt({ type:types[type]||typeName, question:question, answer_format:fmts[type]||'简要回答' }); getAnswer(type, p).then(function (agrs) { ta2.each(function (i, t) { var id = $(t).attr('id')||$(t).attr('name'); setTimeout(function () { try { UE.getEditor(id).setContent(agrs); } catch (e) { $(t).val(agrs); } }, 300 + i*200); }); nextSoon(); }); break; } default: nextSoon(); } } /* ========================= 作业处理 ========================= */ function missonHomeWork() { logger('处理作业', 'green'); var form = $('.mark_table form'); var list = form.find('.questionLi'); doHomeWork(0, list); } function doHomeWork(index, TiMuList) { if (isPaused()) { setTimeout(function () { doHomeWork(index, TiMuList); }, 3000); return; } if (index >= TiMuList.length) { logger('作业全部完成', 'green'); return; } var typeName = $(TiMuList[index]).attr('typename') || $(TiMuList[index]).find('.colorShallow').text() || ''; var m = typeName.match(/(单选题|多选题|填空题|判断题|简答题|写作题|翻译题)/); if (m) typeName = m[1]; var type = _typeMap[typeName]; var qFull = $(TiMuList[index]).find('.mark_name').html() || '', question = tidyQuestion(qFull).replace(/^[(].*?[)]/, '').trim(); _currentQuestionMeta = { index: index, total: TiMuList.length, typeName: typeName }; if (type === undefined) { var opts = $(TiMuList[index]).find('.stem_answer .answer_p'), ta = $(TiMuList[index]).find('.stem_answer textarea'); if (opts.length > 0) type = $(TiMuList[index]).find('.stem_answer input[type="checkbox"]').length > 0 ? 1 : 0; else if (ta.length > 0) type = 4; else { logger('未知题型:' + typeName, 'red'); return setTimeout(function () { doHomeWork(index+1, TiMuList); }, setting.time); } } function nextQ() { setTimeout(function () { doHomeWork(index+1, TiMuList); }, setting.time); } switch (type) { case 0: { var ans = $(TiMuList[index]).find('.stem_answer .answer_p'); if (!ans.length) return nextQ(); var merged = []; ans.each(function () { merged.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'单选题', question:question, options:merged }); getAnswer(type, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') $(TiMuList[index]).find('.mark_name').append('

' + agrs); var opts = []; ans.each(function (i, t) { opts.push(tidyStr($(t).html())); }); var idx = opts.indexOf(agrs); if (idx === -1) idx = findBestFuzzyMatch(opts, agrs); if (idx !== -1) { var cls = $(ans[idx]).parent().find('span').attr('class')||''; if (cls.indexOf('check_answer') === -1) $(ans[idx]).parent().click(); } nextQ(); }).catch(function () { nextQ(); }); break; } case 1: { var ans = $(TiMuList[index]).find('.stem_answer .answer_p'); if (!ans.length) return nextQ(); var merged = []; ans.each(function () { merged.push($(this).text().replace(/[ABCD]/g,'').trim()); }); question = buildPrompt({ type:'多选题', question:question, options:merged, answer_format:"用'|'分割多个答案" }); getAnswer(type, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') $(TiMuList[index]).find('.mark_name').append('

' + agrs); var opts = []; ans.each(function (i, t) { opts.push(tidyStr($(t).html())); }); ans.each(function (i, t) { if (agrs.indexOf(opts[i]) !== -1) { var cls = $(ans[i]).parent().find('span').attr('class')||''; if (cls.indexOf('check_answer_dx') === -1) $(ans[i]).parent().click(); } }); var fuzzy = findFuzzyMatchMultiple(opts, agrs); for (var fi = 0; fi < fuzzy.length; fi++) { (function(idx) { var cls = $(ans[idx]).parent().find('span').attr('class')||''; if (cls.indexOf('check_answer_dx') === -1) $(ans[idx]).parent().click(); })(fuzzy[fi]); } nextQ(); }).catch(function () { nextQ(); }); break; } case 2: { question = buildPrompt({ type:'填空题', question:question, answer_format:"用'|'分割多个答案" }); var textareas = findAnswerTextareas($(TiMuList[index])); if (!textareas.length) { logger('未找到填空区域', 'red'); return nextQ(); } getAnswer(type, question).then(function (agrs) { var parts = agrs.split('|'); textareas.each(function (i, t) { setTimeout(function () { try { UE.getEditor($(t).attr('id')||$(t).attr('name')).setContent(parts[i]||parts[0]||agrs); } catch (e) {} }, 300 + i*200); }); setTimeout(function () { doHomeWork(index+1, TiMuList); }, setting.time + 200*textareas.length); }); break; } case 3: { var ans = $(TiMuList[index]).find('.stem_answer .answer_p'); if (!ans.length) return nextQ(); question = buildPrompt({ type:'判断题', question:question, answer_format:"只回答正确或错误" }); getAnswer(type, question).then(function (agrs) { if (gmGet('alterTitle') !== 'false') $(TiMuList[index]).find('.mark_name').append('

' + agrs); var opts = []; ans.each(function (i, t) { opts.push($(t).text().trim()); }); var judge = parseJudgeAnswer(agrs); if (judge !== null) { var idx = findJudgeOptionIndex(opts, judge === 'true'); if (idx !== -1) $(ans[idx]).parent().click(); } nextQ(); }); break; } case 4: case 5: case 6: { var textareas = findAnswerTextareas($(TiMuList[index])); if (!textareas.length) { logger('未找到答题区域', 'red'); return nextQ(); } var types = {4:'简答题',5:'写作题',6:'翻译题'}, fmts = {4:'用50字简要回答',5:'用英文根据题目进行写作',6:'中文英文互译'}; var p = buildPrompt({ type:types[type]||typeName, question:question, answer_format:fmts[type]||'简要回答' }); getAnswer(type, p).then(function (agrs) { textareas.each(function (i, t) { setTimeout(function () { try { UE.getEditor($(t).attr('id')||$(t).attr('name')).setContent(agrs); } catch (e) {} }, 300 + i*200); }); setTimeout(function () { doHomeWork(index+1, TiMuList); }, setting.time + 200*textareas.length); }); break; } default: nextQ(); } } /* ========================= 字体解密 ========================= */ function base64ToUint8Array(base64) { var data = window.atob(base64); var buffer = new Uint8Array(data.length); for (var i = 0; i < data.length; ++i) buffer[i] = data.charCodeAt(i); return buffer; } function decryptFont() { var $tip = $('style:contains(font-cxsecret)'); if (!$tip.length) return; try { var fontBase64 = $tip.text().match(/base64,([\w\W]+?)'/)[1]; if (typeof Typr === 'undefined') return; var font = Typr.parse(base64ToUint8Array(fontBase64))[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'); } catch (e) {} } /* ========================= UI 浮窗 ========================= */ function loadPresets() { try { return JSON.parse(gmGet('presets') || '[]'); } catch(e) { return []; } } function savePresets(arr) { try { gmSet('presets', JSON.stringify(arr)); } catch(e) {} } function buildPresetOptions() { var presets = loadPresets(); var html = ''; for (var i = 0; i < presets.length; i++) { html += ''; } return html; } function applyPreset(idx) { var presets = loadPresets(); if (!presets[idx]) return; var p = presets[idx]; $('#sa-set-provider').val(p.provider || 'ollama'); $('#sa-set-url').val(p.url || ''); $('#sa-set-model').val(p.model || ''); $('#sa-set-key').val(p.key || ''); logger('已加载预设: ' + p.name, 'green'); } /* ========================= 版本更新检查 ========================= */ function showBox() { if (!setting.showBox) return; if (top.document.querySelector('#sa-box') !== null) return; if (!top.document.getElementById('sa-style')) { var style = top.document.createElement('style'); style.id = 'sa-style'; style.textContent = '#sa-box{position:fixed;top:5%;right:5%;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:#1e293b;background:rgba(255,255,255,.92);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,.7);border-radius:20px;box-shadow:0 20px 48px -12px rgba(0,0,0,.3);overflow:hidden;}#sa-box .sa-header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(255,255,255,.6);border-bottom:1px solid rgba(0,0,0,.06);cursor:move;user-select:none;}#sa-box .sa-title{font-weight:600;font-size:14px;display:flex;align-items:center;gap:8px;}#sa-box .sa-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;}#sa-box .sa-actions{display:flex;gap:6px;}#sa-box .sa-btn{border-radius:50%;border:1px solid rgba(0,0,0,.1);background:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:#475569;width:24px;height:24px;font-size:14px;padding:0;}#sa-box .sa-btn:hover{background:#f1f5f9;}#sa-box .sa-body{padding:12px 14px;}#sa-box .sa-info{font-size:12px;color:#64748b;margin-bottom:8px;padding:6px 10px;background:rgba(255,255,255,.5);border-radius:10px;}#sa-box .sa-info b{color:#1e293b;}#sa-box .sa-settings{background:rgba(255,255,255,.85);border-radius:12px;padding:10px 12px;margin:8px 0;font-size:12px;border:1px solid rgba(0,0,0,.06);}#sa-box .sa-settings label{display:flex;flex-direction:column;margin-bottom:6px;font-size:11px;color:#475569;}#sa-box .sa-settings input[type=text],#sa-box .sa-settings input[type=password],#sa-box .sa-settings select{margin-top:3px;padding:4px 8px;border-radius:8px;border:1px solid #ddd;font-size:12px;}#sa-box .sa-settings input[type=checkbox]{accent-color:#2563eb;margin:0;}#sa-box .sa-settings .sa-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;}#sa-box .sa-settings .sa-grid label{flex-direction:row;align-items:center;gap:6px;cursor:pointer;display:flex;flex-direction:row;}#sa-box .sa-settings .sa-save-btn,#sa-box .sa-settings .sa-test-btn{width:100%;padding:6px;border-radius:10px;border:none;cursor:pointer;font-size:12px;margin-top:4px;}#sa-box .sa-settings .sa-save-btn{background:#2563eb;color:#fff;}#sa-box .sa-settings .sa-test-btn{background:#22c55e;color:#fff;margin-left:4px;flex:1;}#sa-box #sa-log{max-height:140px;overflow-y:auto;padding:8px 10px;background:rgba(0,0,0,.04);border-radius:12px;font-family:monospace;font-size:11px;line-height:1.6;color:#475569;}#sa-box #sa-log:empty{display:none;}#sa-box #sa-log p{margin:0;padding:2px 0;word-break:break-all;}#sa-box #sa-log .sa-time{color:#94a3b8;margin-right:6px;}#sa-box .sa-spinner{display:inline-block;width:8px;height:8px;margin-right:4px;border:1.5px solid #cbd5e1;border-top-color:#475569;border-radius:50%;animation:sa-spin .8s linear infinite;vertical-align:-1px;}@keyframes sa-spin{to{transform:rotate(360deg)}}'; top.document.head.appendChild(style); } var uid = getCk('_uid') || getCk('UID') || '-'; var curModel = (gmGet('apiModel') || setting.apiModel || '未设置'); var boxHtml = '
学习通助手 v' + SCRIPT_VERSION + '
学习通账号 UID:' + uid + '
🤖 ' + curModel + '
'; $(boxHtml).appendTo('body'); // 检查更新按钮事件(必须在面板创建时就绑定,不能等打开设置) $('#sa-update-btn').off('click').on('click', function () { logger('正在检查更新...', 'blue'); GM_xmlhttpRequest({ method: 'GET', url: 'https://scriptcat.org/api/v2/scripts/6637', timeout: 10000, onload: function (xhr) { if (xhr.status == 200) { try { var data = JSON.parse(xhr.responseText); var latest = data.data && data.data.script && data.data.script.version; if (!latest) { logger('未获取到版本信息', 'orange'); return; } var cur = SCRIPT_VERSION.split('.').map(Number); var lat = latest.split('.').map(Number); var newer = false; for (var i = 0; i < Math.max(cur.length, lat.length); i++) { var cv = cur[i]||0, lv = lat[i]||0; if (lv > cv) { newer = true; break; } if (lv < cv) break; } if (newer) { logger('🎉 发现新版本 v' + latest + '!请到脚本管理器更新', 'green'); } else { logger('✓ 当前 v' + SCRIPT_VERSION + ' 已是最新版', 'green'); } } catch (e) { logger('解析失败: ' + e.message, 'red'); } } else { logger('检查更新失败: HTTP ' + xhr.status, 'red'); } }, onerror: function () { logger('检查更新失败: 网络错误', 'red'); }, ontimeout: function () { logger('检查更新超时', 'red'); } }); }); try { var sp = gmGet('boxPos'); if (sp) { var pp = JSON.parse(sp); $('#sa-box').css({left:pp.left+'px',top:pp.top+'px',right:'auto'}); } } catch (_) {} (function syncPause() { var paused = isPaused(); $('#sa-pause-btn').text(paused ? '▶' : '⏸').attr('title', paused ? '已暂停' : '运行中'); })(); $('#sa-pause-btn').click(function () { var p = !isPaused(); gmSet('isPaused', p); $(this).text(p ? '▶' : '⏸').attr('title', p ? '已暂停' : '运行中'); logger(p ? '已暂停' : '已恢复', 'orange'); }); $('#sa-close-btn').click(function () { var collapsed = $('#sa-box').hasClass('sa-collapsed'); if (collapsed) { $('#sa-box').removeClass('sa-collapsed').find('.sa-body').show(); $(this).text('−'); } else { $('#sa-box').addClass('sa-collapsed').find('.sa-body').hide(); $(this).text('+'); } }); // 重做/题目列表按钮 var settingsVisible = false; function buildSettingsPanel() { var cp = 'custom'; var curUrl = gmGet('apiUrl') || ''; var curModel = gmGet('apiModel') || ''; var curKey = gmGet('apiKey') || ''; var presetOpts = buildPresetOptions(); var panelHtml = '' + '
' + '' + '' + '' + '' + '
' + '
' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' + '' + '' + '' + '' + '
' + '' + '' + '
' + '' + '' + '' + '
'; $('#sa-settings-panel').html(panelHtml); $('#sa-preset-select').off('change').on('change', function() { var idx = $(this).val(); if (idx !== '') applyPreset(parseInt(idx)); }); $('#sa-preset-del').off('click').on('click', function() { var idx = $('#sa-preset-select').val(); if (idx === '') { logger('请先选择一个预设', 'orange'); return; } var presets = loadPresets(); presets.splice(parseInt(idx), 1); savePresets(presets); $('#sa-preset-select').val('').html(buildPresetOptions()); logger('预设已删除', 'orange'); }); $('#sa-save-preset-btn').off('click').on('click', function() { var name = prompt('预设名称:'); if (!name || name.trim() === '') return; var presets = loadPresets(); presets.push({ name: name.trim(), provider: $('#sa-set-provider').val(), url: $('#sa-set-url').val(), model: $('#sa-set-model').val(), key: $('#sa-set-key').val() }); savePresets(presets); $('#sa-preset-select').html(buildPresetOptions()).val(String(presets.length - 1)); logger('预设已保存: ' + name, 'green'); }); $('.sa-test-btn').off('click').on('click', function() { var url = $('#sa-set-url').val(); var model = $('#sa-set-model').val(); var key = $('#sa-set-key').val(); if (!url) { logger('请先填写 API URL', 'red'); return; } logger('测试连接: ' + url + ' [' + model + ']', 'blue'); var isOllama = /\/api\/(generate|chat)/.test(url); var body = isOllama ? JSON.stringify({model:model, prompt:'ping', stream:false}) : JSON.stringify({model:model, messages:[{role:'user', content:'ping'}], stream:false}); var headers = { 'Content-Type': 'application/json' }; if (key) headers['Authorization'] = 'Bearer ' + key; GM_xmlhttpRequest({ method:'POST', url:url, headers:headers, data:body, timeout:10000, onload: function(xhr) { if (xhr.status === 200) { logger('连接成功! (' + xhr.status + ')', 'green'); try { var r = JSON.parse(xhr.responseText); logger('模型: ' + (r.model || 'OK'), 'green'); } catch(e) {} } else { logger('HTTP ' + xhr.status + ' - 检查URL/模型名', 'red'); } }, onerror: function() { logger('无法连接 - 服务未运行或地址不对', 'red'); }, ontimeout: function() { logger('超时 - 检查网络和防火墙', 'red'); } }); }); $('#sa-bank-btn').off('click').on('click', function() { var bankData = JSON.parse(gmGet('questionBank') || '{}'); var keys = Object.keys(bankData); var $list = $('#sa-bank-list'); if (keys.length === 0) { $list.html('
暂无题目
').toggle(); return; } if ($list.is(':visible')) { $list.hide(); return; } var types = {}; for (var bk in bankData) { var t = bankData[bk].type || '未知'; types[t] = (types[t]||0) + 1; } var h = '
' + keys.length + '
'; var first = true; for (var t in types) { if (!first) h += ' | '; h += '' + t + ': ' + types[t] + '题'; first = false; } h += '
'; $list.html(h).show(); logger('题库共 ' + keys.length + ' 题', 'green'); }); $('#sa-clear-bank-btn').off('click').on('click', function() { if (confirm('确认清空本地题库?')) { gmSet('questionBank', '{}'); $('#sa-bank-list').hide(); logger('题库已清空', 'orange'); } }); // 导出 BAK $('#sa-export-bank-btn').off('click').on('click', function() { var bankData = getBank(); var keys = Object.keys(bankData); if (keys.length === 0) { logger('题库为空,无可导出', 'orange'); return; } var bak = { exportTime: new Date().toLocaleString(), version: SCRIPT_VERSION, total: keys.length, data: bankData }; var json = JSON.stringify(bak, null, 2); var blob = new Blob([json], { type: 'application/json;charset=utf-8' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'chaoxing-bank-' + new Date().toISOString().slice(0,10) + '.bak'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); logger('已导出 ' + keys.length + ' 题到 .bak 文件', 'green'); }); // 导入(兼容多种格式) $('#sa-import-bank-btn').off('click').on('click', function() { var input = document.createElement('input'); input.type = 'file'; input.accept = '.bak,.json,.txt'; input.onchange = function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(ev) { try { var raw = ev.target.result; var obj = null; try { obj = JSON.parse(raw); } catch (e1) { // LZ-String 解压尝试 if (typeof LZString !== 'undefined') { try { var d = LZString.decompressFromEncodedURIComponent(raw) || LZString.decompress(raw) || LZString.decompressFromBase64(raw); if (d) { obj = JSON.parse(d); logger('检测到 LZ 压缩格式,已解压', 'blue'); } } catch (e2) {} } } if (!obj) { logger('无法解析文件格式', 'red'); return; } var imported = null; // 格式1: 标准 bak 格式 { exportTime, version, data: { ... } } if (obj.data && typeof obj.data === 'object' && !Array.isArray(obj.data)) { imported = obj.data; } // 格式2: 标准 bak 格式 { exportTime, version, data: [...] } else if (obj.data && Array.isArray(obj.data)) { imported = {}; for (var di = 0; di < obj.data.length; di++) { var item = obj.data[di]; var key = item.key || (md5 ? md5(item.text || item.question || '') : (item.text || item.question || '')); if (key) imported[key] = {text: item.text || item.question, options: item.options, answer: item.answer, type: item.type, savedAt: Date.now()}; } } // 格式3: 裸题库 { md5hash: { text, options, answer, type } } else if (obj[Object.keys(obj)[0]] && obj[Object.keys(obj)[0]].answer !== undefined) { imported = obj; } // 格式4: 含 questionBank 字段(某些打包格式) else if (obj.questionBank) { var inner = obj.questionBank; if (typeof inner === 'string') inner = JSON.parse(inner); imported = inner; } // 格式5: 数组 [{ question, answer, options }] else if (Array.isArray(obj)) { imported = {}; for (var ai = 0; ai < obj.length; ai++) { var it = obj[ai]; var k = it.key || (md5 ? md5(it.text || it.question || '') : (it.text || it.question || '')); if (k) imported[k] = {text: it.text || it.question, options: it.options, answer: it.answer, type: it.type, savedAt: Date.now()}; } } if (!imported || Object.keys(imported).length === 0) { logger('无法解析文件格式', 'red'); return; } // 合并到现有题库 var current = getBank(); var before = Object.keys(current).length; for (var k in imported) { if (imported[k] && imported[k].answer) { current[k] = imported[k]; } } var added = Object.keys(current).length - before; saveBank(current); logger('导入完成:新增 ' + added + ' 题,共 ' + Object.keys(current).length + ' 题', 'green'); } catch (er) { logger('导入失败: ' + er.message, 'red'); } }; reader.readAsText(file); }; input.click(); }); // 版本更新检查 $('#sa-set-provider').val(cp).change(function () { var v = $(this).val(); var u = {ollama:'http://127.0.0.1:11434/api/generate',custom:''}; var mm = {ollama:'qwen3.5:0.8b',custom:gmGet('apiModel')||''}; var kk = {ollama:'',custom:gmGet('apiKey')||''}; $('#sa-set-url').val(u[v]||''); $('#sa-set-model').val(mm[v]||''); $('#sa-set-key').val(kk[v]||''); }); $('.sa-save-btn').off('click').on('click', function () { gmSet('apiUrl', $('#sa-set-url').val()); gmSet('apiModel', $('#sa-set-model').val()); gmSet('apiKey', $('#sa-set-key').val()); gmSet('goodStudent', $('#sa-set-good').is(':checked')?'true':'false'); gmSet('sub', $('#sa-set-sub').is(':checked')?'true':'false'); gmSet('examTurn', $('#sa-set-turn').is(':checked')?'true':'false'); gmSet('useBank', $('#sa-set-bank').is(':checked')?'true':'false'); gmSet('force', $('#sa-set-force').is(':checked')?'true':'false'); gmSet('fuzzyMatch', $('#sa-set-fuzzy').is(':checked')?'true':'false'); var tv = parseFloat($('#sa-set-time').val()); if (isFinite(tv) && tv >= 0) { gmSet('time', String(tv)); setting.time = tv * 1000; } logger('设置已保存', 'green'); }); } $('#sa-settings-btn').click(function () { settingsVisible = !settingsVisible; if (settingsVisible) { buildSettingsPanel(); $('#sa-settings-panel').show(); $(this).text('⚙ 返回'); } else { $('#sa-settings-panel').hide(); $(this).text('⚙ 设置'); } }); (function () { var box = $('#sa-box'), header = box.find('.sa-header'); var drag = false, sx, sy, sl, st; header.on('mousedown', function (e) { if (e.which !== 1 || $(e.target).closest('.sa-btn').length) return; drag = true; var r = box[0].getBoundingClientRect(); sx = e.clientX; sy = e.clientY; sl = r.left; st = r.top; box.css({left:sl+'px',top:st+'px',right:'auto'}); $('body').css('user-select','none'); e.preventDefault(); }); $(document).on('mousemove.sadrag', function (e) { if (!drag) return; var nx = Math.max(-300, Math.min(sl + (e.clientX - sx), window.innerWidth - 60)); var ny = Math.max(0, Math.min(st + (e.clientY - sy), window.innerHeight - 60)); box.css({left:nx+'px',top:ny+'px'}); }).on('mouseup.sadrag', function () { if (!drag) return; drag = false; $('body').css('user-select',''); try { var r = box[0].getBoundingClientRect(); gmSet('boxPos', JSON.stringify({left:r.left,top:r.top})); } catch (_) {} }); })(); } /* ========================= 路由 ========================= */ (function() { if (_l.hostname === 'i.mooc.chaoxing.com' || _l.hostname === 'i.chaoxing.com') { return; } if (_l.pathname === '/login') { showBox(); if (setting.autoLogin) waitForJQueryElement('#phone').then(function () { logger('用户已设置自动登录', 'green'); if (setting.phone.length <= 0 || setting.password.length <= 0) { logger('用户未设置登录信息', 'red'); return; } setTimeout(function () { $('#phone').val(setting.phone); $('#pwd').val(setting.password); $('#loginBtn').click(); }, 3000); }); } else if (_l.pathname.indexOf('/mycourse/studentstudy') !== -1) { showBox(); $('#sa-log', window.parent.document).html('初始化完毕!'); setupAntiSleep(); setupAutoRefresh(); // 启动时检查更新 setTimeout(checkForUpdates, 3000); $('.navshow').find('a:contains(体验新版)')[0] && $('.navshow').find('a:contains(体验新版)')[0].click(); } else if (_l.pathname.indexOf('/knowledge/cards') !== -1) { showBox(); setupAntiSleep(); $('.navshow').find('a:contains(体验新版)')[0] && $('.navshow').find('a:contains(体验新版)')[0].click(); try { decryptFont(); } catch (_) {} if (gmGet('time') !== null) { var st = parseFloat(gmGet('time')); if (isFinite(st) && st >= 0) setting.time = st * 1000; } var params = getTaskParams(); var parsedParams = null; if (params && params !== '$mArg') { try { parsedParams = $.parseJSON(params); } catch (e) {} } if (!parsedParams || !parsedParams.attachments || parsedParams.attachments.length <= 0) { logger('无任务点', 'red'); toNext(); } else { waitForJQueryElement('.wrap .ans-cc .ans-attach-ct').then(function () { if (top.checkJob) top.checkJob = function () { return false; }; _domList = []; _mlist = parsedParams.attachments; _defaults = parsedParams.defaults; $('.wrap .ans-cc .ans-attach-ct').each(function () { _domList.push($(this).find('iframe')); }); missonStart(); }); } } else if (_l.pathname.indexOf('/exam/test/reVersionTestStartNew') !== -1) { showBox(); waitForJQueryElement('.mark_table .whiteDiv, .questionLi').then(missonExam); } else if (_l.pathname.indexOf('/mooc2/exam/preview') !== -1) { showBox(); waitForJQueryElement('.mark_table .questionLi').then(missonExamPreview); } else if (_l.pathname.indexOf('/mooc2/work/dowork') !== -1) { showBox(); waitForJQueryElement('.mark_table form').then(missonHomeWork); } else if (_l.pathname == '/mycourse/stu') { checkBrowser(); } else if (_l.pathname.indexOf('/work/phone/doHomeWork') !== -1) { var _oldal = _w.alert; _w.alert = function (msg) { if (msg === '保存成功') return; return _oldal(msg); }; var _oldcf = _w.confirm; _w.confirm = function (msg) { if (msg.indexOf('确认提交') !== -1 || msg.indexOf('未做完') !== -1) return true; return _oldcf(msg); }; } })(); })();