// ==UserScript== // @name 超星学习通助手-后台挂机-免费高分题库 // @version 3.0.3 // @authon bzm // @description 后台任务-不占用宽带-学习通助手,支持视频,作业、考试自动答题,免费高分题库。后台任务版,不占用宽带。后台上报和伪实时进度条。 // @match *://*.chaoxing.com/* // @run-at document-end // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @grant GM_getResourceText // @grant GM_openInTab // @icon http://pan-yz.chaoxing.com/favicon.ico // @original-script https://scriptcat.org/zh-CN/script-show-page/6033 // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // @require https://cdn.bootcdn.net/ajax/libs/limonte-sweetalert2/11.1.0/sweetalert2.all.min.js // @require https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js // @require https://pkpkq.n1t.cn/font-decrypt/TyprMd5.js // @resource Table https://pkpkq.n1t.cn/font-decrypt/Table.json // @connect tk.swk.tw // @connect pkpkq.n1t.cn // @connect soti.ucuc.net // @connect n1t.cn // @tag 免费搜题 // @tag 可选付费 // ==/UserScript== /*********************************自定义配置区******************************************************** */ var setting = { showBox: 1, // 显示脚本浮窗 maskImg: 1, // 显示皮卡丘 task: 0, // 只处理任务点任务 video: 1, // 处理视频 audio: 1, // 处理音频 rate: 1, // 视频/音频倍速 review: 0, // 复习模式 work: 1, // 测验自动处理 time: 5000, // 答题时间间隔(ms) sub: 0, // 测验自动提交 force: 0, // 测验强制提交 decrypt: 1, // 字体解密 examTurn: 0, // 考试自动跳转 examTurnTime: 1, // 考试自动跳转随机间隔 goodStudent: 0, // 好学生模式 alterTitle: 1, // 答案插入题目 autoLogin: 0, // 自动登录 phone: '', // 登录手机号 password: '', // 登录密码 // ========== 视频模拟配置 ========== videoMode: 'simulate', // 'simulate'=模拟上报, 'normal'=正常播放 reportInterval: 60, // 上报间隔(秒) showProgressBar: 1 // 显示可视化进度条 } /**************************************************************************************************/ var _w = unsafeWindow, _l = location, _d = _w.document, $ = _w.jQuery || top.jQuery, UE = _w.UE, md5 = window.md5 || $.md5; // 获取Typr对象(兼容多种导出方式) var TyprInstance = null; try { if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) { TyprInstance = unsafeWindow.Typr; } else if (typeof window !== 'undefined' && window.Typr) { TyprInstance = window.Typr; } else if (typeof Typr !== 'undefined') { TyprInstance = Typr; } else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) { TyprInstance = unsafeWindow.TyprMd5; } else if (typeof TyprMd5 !== 'undefined') { TyprInstance = TyprMd5; } } catch(e) {} // ========== 通用工具函数 ========== function getCookie(name) { return ('; ' + document.cookie).split('; ' + name + '=').pop().split(';')[0]; } function formatDuration(seconds) { if (!seconds || seconds < 0) return '00:00'; var total = Math.max(0, Math.floor(Number(seconds) || 0)); var h = Math.floor(total / 3600); var m = Math.floor((total % 3600) / 60); var s = total % 60; var pad = function(n) { return String(n).padStart(2, '0'); }; if (h > 0) { return pad(h) + ':' + pad(m) + ':' + pad(s); } return pad(m) + ':' + pad(s); } function tidyStr(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').trim().replace(/ /g, ''); } function tidyQuestion(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/^\d+[.、]/, '').trim(); } function splitAnswer(answer) { if (!answer) return []; var parts = answer.split(/[#]+/).map(a => a.trim()).filter(a => a !== ''); if (parts.length === 0 && answer.trim() !== '') { return [answer.trim()]; } return parts; } function getStr(str, start, end) { let res = str.match(new RegExp(start + '(.*?)' + end)); return res ? res[1] : null; } function isTaskCompleted(task) { if (!task) return false; if (task.isPassed === true) return true; if (task.status === 'completed' || task.status === 'finished') return true; if (task.finished === true) return true; return false; } // ========== 日志系统 ========== var logQueue = []; var logTimer = null; function getTopLogContainer() { try { var topDoc = top.document; var container = topDoc.getElementById('ne-21log'); if (container) return $(container); container = document.getElementById('ne-21log'); if (container) return $(container); return null; } catch(e) { return null; } } function flushLogs() { if (logQueue.length === 0) return; var container = getTopLogContainer(); if (container && container.length) { var logsToShow = logQueue.slice(); logQueue = []; for (var i = logsToShow.length - 1; i >= 0; i--) { container.prepend(logsToShow[i]); } container.scrollTop(0); } if (logTimer) { clearTimeout(logTimer); logTimer = null; } } function scheduleFlushLogs() { if (logTimer) return; logTimer = setTimeout(flushLogs, 100); } function log(str, color) { var time = new Date().toLocaleTimeString(); var logHtml = '
[' + time + '] ' + str + '
'; logQueue.push(logHtml); scheduleFlushLogs(); console.log('[学习通助手][' + time + ']', str); } // ========== API配置 ========== var API_SERVERS = [ { url: "https://soti.ucuc.net/api/search.php", name: "主接口" }, { url: "https://n1t.cn/api/search.php", name: "备用接口1" }, { url: "https://tk.swk.tw/api/search.php", name: "备用接口2" } ]; var CURRENT_SERVER_INDEX = 0; function getCurrentApiUrl() { return API_SERVERS[CURRENT_SERVER_INDEX].url; } function getApiKey() { var key = GM_getValue('api_key', ''); return key; } function hasApiKey() { var key = getApiKey(); return key && key.trim() !== ''; } function getQuotaInfo() { return { remaining: GM_getValue('quota_remaining', 0), free_remaining: GM_getValue('quota_free_remaining', 0), recharge_balance: GM_getValue('quota_recharge_balance', 0), today_free_usage: GM_getValue('quota_today_free_usage', 0) }; } var quotaInfo = getQuotaInfo(); function saveQuotaInfo() { GM_setValue('quota_remaining', quotaInfo.remaining); GM_setValue('quota_free_remaining', quotaInfo.free_remaining); GM_setValue('quota_recharge_balance', quotaInfo.recharge_balance); GM_setValue('quota_today_free_usage', quotaInfo.today_free_usage); } function updateQuotaInfo(newQuotaInfo) { if (newQuotaInfo) { quotaInfo.remaining = newQuotaInfo.remaining !== undefined ? newQuotaInfo.remaining : quotaInfo.remaining; quotaInfo.free_remaining = newQuotaInfo.free_remaining !== undefined ? newQuotaInfo.free_remaining : quotaInfo.free_remaining; quotaInfo.recharge_balance = newQuotaInfo.recharge_balance !== undefined ? newQuotaInfo.recharge_balance : quotaInfo.recharge_balance; quotaInfo.today_free_usage = newQuotaInfo.today_free_usage !== undefined ? newQuotaInfo.today_free_usage : quotaInfo.today_free_usage; saveQuotaInfo(); updateQuotaDisplay(); } } function updateQuotaDisplay() { try { var targetDoc = top.document; if (!targetDoc.querySelector('#quotaInfo')) targetDoc = document; var $quotaInfo = $('#quotaInfo', targetDoc); if ($quotaInfo.length) { var remaining = quotaInfo.remaining; var hasKey = hasApiKey(); if (remaining > 0) { $quotaInfo.html('📊 剩余次数: ' + remaining + ' | 免费: ' + quotaInfo.free_remaining); } else if (remaining === 0 && hasKey) { $quotaInfo.html('⚠️ 次数已用完 | 点击充值'); } else if (!hasKey) { $quotaInfo.html('⚠️ 请先设置API密钥'); } else { $quotaInfo.html('📊 剩余次数: 加载中...'); } } } catch(e) {} } function checkApiKeyBeforeAction(actionName) { var apiKey = getApiKey(); if (!apiKey || apiKey.trim() === '') { log('❌ 无法执行' + actionName + ':请先在设置中配置API密钥', 'red'); return false; } quotaInfo = getQuotaInfo(); if (quotaInfo.remaining <= 0 && quotaInfo.free_remaining <= 0) { log('❌ 无法执行' + actionName + ':API次数已用完,请充值', 'red'); return false; } return true; } // ========== 接口测试函数 ========== function testApi(key, serverIndex) { return new Promise((resolve, reject) => { var testQuestion = "test"; var testType = "单选题"; var testOptions = ["选项A", "选项B"]; var apiUrl = API_SERVERS[serverIndex || 0].url; var formData = 'question=' + encodeURIComponent(testQuestion) + '&key=' + encodeURIComponent(key) + '&type=' + encodeURIComponent(testType) + '&options=' + encodeURIComponent(JSON.stringify(testOptions)); var timeoutId = setTimeout(() => { reject('请求超时'); }, 10000); GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, data: formData, timeout: 10000, onload: function(xhr) { clearTimeout(timeoutId); try { var res = JSON.parse(xhr.responseText); if (res.code == 1 || res.code == 400) { if (res.quota_info) { updateQuotaInfo(res.quota_info); } resolve(res); } else { reject(res.msg || '密钥无效'); } } catch(e) { reject('解析响应失败'); } }, onerror: function() { clearTimeout(timeoutId); reject('网络请求失败'); }, ontimeout: function() { clearTimeout(timeoutId); reject('请求超时'); } }); }); } // ========== 本地enc计算 ========== function generateEncLocal(classId, uid, jobId, objectId, playTime, duration) { var str = '[' + classId + '][' + uid + '][' + jobId + '][' + objectId + '][' + (playTime * 1000) + '][d_yHJ!$pdA~5][' + (duration * 1000) + '][0_' + duration + ']'; return md5(str); } // ========== 等待元素 ========== function waitForElement(selector, timeout = 30000) { return new Promise((resolve, reject) => { var start = Date.now(); var timer = setInterval(() => { if ($(selector).length) { clearInterval(timer); resolve(); } else if (Date.now() - start > timeout) { clearInterval(timer); reject(); } }, 500); }); } function getElement(parent, selector) { return new Promise((resolve) => { var iframe = parent.querySelector(selector); if (iframe) resolve(iframe); var observer = new MutationObserver((mutations) => { var iframe = parent.querySelector(selector); if (iframe) { observer.disconnect(); resolve(iframe); } }); observer.observe(parent, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, 10000); }); } // ========== 获取填空数量(修复版)========== function getBlankInputCount($question) { var count = 0; // 方法1: 查找手机端填空输入框 .blankList2 input var $inputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]'); if ($inputs.length) { // 收集唯一标识符 var uniqueIdentifiers = new Set(); $inputs.each(function() { var name = $(this).attr('name'); var id = $(this).attr('id'); var placeholder = $(this).attr('placeholder'); // 使用多种属性组合作为唯一标识 var key = (name || '') + '_' + (id || '') + '_' + (placeholder || '') + '_' + $(this).index(); uniqueIdentifiers.add(key); }); count = uniqueIdentifiers.size; if (count > 0) { log('📝 方法1检测到填空数量: ' + count, 'blue'); return count; } } // 方法2: 查找 data-editorindex 属性(手机端编辑器) var $editorBlocks = $question.find('[data-editorindex]'); if ($editorBlocks.length) { var uniqueIndices = new Set(); $editorBlocks.each(function() { var idx = $(this).attr('data-editorindex'); if (idx !== undefined && idx !== null && idx !== '') { uniqueIndices.add(idx); } }); count = uniqueIndices.size; if (count > 0) { log('📝 方法2检测到填空数量: ' + count, 'blue'); return count; } } // 方法3: 查找PC端填空输入框 .Zy_ulTk .XztiHover1 textarea var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea'); if ($pcInputs.length) { var uniquePCInputs = new Set(); $pcInputs.each(function() { var id = $(this).attr('id'); var name = $(this).attr('name'); var key = (id || '') + '_' + (name || '') + '_' + $(this).index(); uniquePCInputs.add(key); }); count = uniquePCInputs.size; if (count > 0) { log('📝 方法3检测到填空数量: ' + count, 'blue'); return count; } } // 方法4: 查找包含 "blank" 或 "fill" 的输入框 var $blankInputs = $question.find('input[type="text"], textarea').filter(function() { var className = $(this).attr('class') || ''; var id = $(this).attr('id') || ''; return className.toLowerCase().indexOf('blank') !== -1 || className.toLowerCase().indexOf('fill') !== -1 || id.toLowerCase().indexOf('blank') !== -1 || id.toLowerCase().indexOf('fill') !== -1; }); if ($blankInputs.length) { var uniqueBlanks = new Set(); $blankInputs.each(function() { var name = $(this).attr('name'); var id = $(this).attr('id'); var key = (name || '') + '_' + (id || '') + '_' + $(this).index(); uniqueBlanks.add(key); }); count = uniqueBlanks.size; if (count > 0) { log('📝 方法4检测到填空数量: ' + count, 'blue'); return count; } } // 方法5: 从题目文本中解析填空数量(如 "第1空"、"第2空" 或 "(1)"、"(2)") var questionText = $question.text() || ''; var pattern1 = questionText.match(/第(\d+)空/g); if (pattern1 && pattern1.length > 0) { var maxBlank = 0; pattern1.forEach(function(match) { var num = parseInt(match.match(/\d+/)[0]); if (num > maxBlank) maxBlank = num; }); if (maxBlank > 0) { log('📝 方法5从"第X空"检测到填空数量: ' + maxBlank, 'blue'); return maxBlank; } } var pattern2 = questionText.match(/[((](\d+)[))]/g); if (pattern2 && pattern2.length > 0) { var maxBlank = 0; pattern2.forEach(function(match) { var num = parseInt(match.match(/\d+/)[0]); if (num > maxBlank) maxBlank = num; }); if (maxBlank > 0 && maxBlank <= 20) { log('📝 方法5从括号数字检测到填空数量: ' + maxBlank, 'blue'); return maxBlank; } } // 方法6: 查找填空题特有的DOM结构 var $fillItems = $question.find('.fillItem, .fill-item, .blank-item, .blankItem'); if ($fillItems.length) { var uniqueItems = new Set(); $fillItems.each(function() { var idx = $(this).attr('data-index') || $(this).attr('data-id') || $(this).index(); uniqueItems.add(idx); }); count = uniqueItems.size; if (count > 0) { log('📝 方法6检测到填空数量: ' + count, 'blue'); return count; } } // 默认返回1(至少有一个填空) log('📝 使用默认填空数量: 1', 'blue'); return 1; } // ========== 填空题填充(修复版)========== function fillBlankAnswer($question, answers, isPhoneMode, contextWindow) { var answerList = splitAnswer(answers); log('📝 填空题答案: ' + answerList.join(' | '), 'blue'); // 获取实际填空数量 var blankCount = getBlankInputCount($question); log('📝 检测到填空数量: ' + blankCount, 'blue'); // 如果答案数量不足,重复最后一个答案或使用第一个答案填充 while (answerList.length < blankCount) { answerList.push(answerList[answerList.length - 1] || answerList[0] || ''); } if (isPhoneMode) { // 手机端:查找 .blankList2 input var $allInputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]'); if ($allInputs.length) { // 去重获取唯一输入框 var uniqueInputs = []; var seen = new Set(); $allInputs.each(function() { var name = $(this).attr('name'); var id = $(this).attr('id'); var placeholder = $(this).attr('placeholder'); var key = (name || '') + '_' + (id || '') + '_' + (placeholder || '') + '_' + $(this).index(); if (!seen.has(key)) { seen.add(key); uniqueInputs.push(this); } }); log('📝 手机端找到 ' + uniqueInputs.length + ' 个填空输入框', 'blue'); $(uniqueInputs).each(function(i) { setTimeout(() => { var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); $(this).val(answerValue); $(this).trigger('input').trigger('change'); // 触发change事件后再次触发blur以确保保存 setTimeout(() => { $(this).trigger('blur'); }, 50); log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green'); }, i * 200); }); return true; } // 手机端:查找带 data-editorindex 的编辑器 var $editorBlocks = $question.find('[data-editorindex]'); if ($editorBlocks.length) { var uniqueEditors = []; var seenIndices = new Set(); $editorBlocks.each(function() { var idx = $(this).attr('data-editorindex'); if (idx && !seenIndices.has(idx)) { seenIndices.add(idx); uniqueEditors.push(this); } }); log('📝 手机端找到 ' + uniqueEditors.length + ' 个编辑器', 'blue'); uniqueEditors.forEach(function(editor, i) { var editorIndex = $(editor).attr('data-editorindex'); var itemId = $(editor).attr('data-itemid'); setTimeout(() => { try { var ueditor = null; if (contextWindow && contextWindow.editors && contextWindow.editors[editorIndex]) { ueditor = contextWindow.editors[editorIndex].ueditor; } if (!ueditor && contextWindow && contextWindow.UE && contextWindow.UE.instants) { var instantKey = 'ueditorInstant' + editorIndex; ueditor = contextWindow.UE.instants[instantKey]; } if (!ueditor && itemId && contextWindow && contextWindow.UE && contextWindow.UE.getEditor) { ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId); } if (ueditor) { var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); ueditor.setContent(answerValue); log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green'); } if (itemId) { var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); $('#answer' + itemId).val(answerValue).trigger('change'); } } catch(e) { log('⚠️ 填写编辑器第' + (i+1) + '空失败: ' + e.message, 'orange'); } }, i * 300); }); return true; } return false; } // PC端处理 var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea'); if ($pcInputs.length) { var uniquePCInputs = []; var seenPC = new Set(); $pcInputs.each(function() { var id = $(this).attr('id'); var name = $(this).attr('name'); var key = (id || '') + '_' + (name || '') + '_' + $(this).index(); if (!seenPC.has(key)) { seenPC.add(key); uniquePCInputs.push(this); } }); log('📝 PC端找到 ' + uniquePCInputs.length + ' 个填空输入框', 'blue'); $(uniquePCInputs).each(function(i) { setTimeout(() => { var $this = $(this); var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || ''); if (UE && UE.getEditor && UE.getEditor($this.attr('id'))) { try { UE.getEditor($this.attr('id')).setContent(answerValue); } catch(e) { $this.val(answerValue); } } else { $this.val(answerValue); $this.trigger('input').trigger('change'); } log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green'); }, i * 200); }); return true; } log('⚠️ 未找到填空输入框', 'orange'); return false; } // ========== 简答题填充 ========== function fillShortAnswer($question, answer, isPhoneMode, contextWindow) { log('📝 简答题答案: ' + answer.substring(0, 100) + '...', 'blue'); if (isPhoneMode) { var $textarea = $question.find('textarea[name^="answer"], .answerTxt textarea'); if ($textarea.length) { $textarea.val(answer); $textarea.trigger('input').trigger('change'); return true; } return false; } var $ueTextarea = $question.find('textarea[name^="answerEditor"], .eidtDiv textarea, .divText textarea'); if ($ueTextarea.length) { var id = $ueTextarea.first().attr('id'); if (id && UE && UE.getEditor && UE.getEditor(id)) { try { UE.getEditor(id).setContent(answer); return true; } catch(e) {} } $ueTextarea.val(answer); $ueTextarea.trigger('input').trigger('change'); return true; } return false; } // ========== Web Worker 后台上报(修复版)========== var reportWorker = null; var activeVideoJob = null; // 创建Web Worker用于后台计时(包含实际上报逻辑) function createReportWorker(videoInfo, taskObj) { var name = videoInfo.name; var duration = videoInfo.duration; var dtoken = videoInfo.dtoken; var objectId = videoInfo.objectId; var jobId = taskObj.jobid; var rt = videoInfo.rt; var otherInfo = videoInfo.otherInfo; var reportUrl = _defaults.reportUrl; var classId = _defaults.clazzId; var uid = getCookie('_uid') || getCookie('UID'); var startTime = videoInfo.playedTime || 0; var rate = setting.rate; var reportInterval = setting.reportInterval; var workerCode = ` var timer = null; var currentTime = ${startTime}; var duration = ${duration}; var rate = ${rate}; var reportInterval = ${reportInterval}; var lastReportTime = ${startTime}; var isRunning = true; var startTime = Date.now() - (${startTime} * 1000 / ${rate}); var reportCount = 0; // 上报函数(在Worker内部执行GM_xmlhttpRequest需要特殊处理) // 改为通过postMessage让主线程执行上报 function sendReport(playTime, isComplete) { self.postMessage({ type: 'doReport', playTime: playTime, isComplete: isComplete }); } // 计时循环 function updateLoop() { if (!isRunning) return; var now = Date.now(); var elapsed = (now - startTime) / 1000; var newTime = Math.min(elapsed * rate, duration); if (newTime > currentTime) { currentTime = newTime; var timeSinceLastReport = currentTime - lastReportTime; var shouldReport = timeSinceLastReport >= reportInterval || currentTime >= duration; if (shouldReport) { sendReport(Math.ceil(currentTime), currentTime >= duration); lastReportTime = currentTime; } self.postMessage({ type: 'progress', currentTime: currentTime, duration: duration }); } if (currentTime >= duration) { isRunning = false; if (timer) clearInterval(timer); self.postMessage({ type: 'completed' }); } else { timer = setTimeout(updateLoop, 200); } } updateLoop(); self.onmessage = function(e) { var data = e.data; switch(data.type) { case 'updateRate': rate = data.rate; break; case 'stop': isRunning = false; if (timer) clearTimeout(timer); self.postMessage({ type: 'stopped' }); break; } }; `; var blob = new Blob([workerCode], { type: 'application/javascript' }); var worker = new Worker(URL.createObjectURL(blob)); // 存储视频信息供上报使用 worker.videoContext = { name: name, duration: duration, dtoken: dtoken, objectId: objectId, jobId: jobId, rt: rt, otherInfo: otherInfo, reportUrl: reportUrl, classId: classId, uid: uid }; worker.onmessage = function(e) { var data = e.data; switch(data.type) { case 'progress': if (setting.showProgressBar) { updateProgressBar(data.currentTime, data.duration); } break; case 'doReport': // 主线程执行实际上报 var ctx = worker.videoContext; var enc = generateEncLocal(ctx.classId, ctx.uid, ctx.jobId, ctx.objectId, data.playTime, ctx.duration); var isdrag = data.isComplete ? '4' : '0'; var reportsUrl = ctx.reportUrl + '/' + ctx.dtoken + '?clazzId=' + ctx.classId + '&playingTime=' + data.playTime + '&duration=' + ctx.duration + '&clipTime=0_' + ctx.duration + '&objectId=' + ctx.objectId + '&otherInfo=' + ctx.otherInfo + '&jobid=' + ctx.jobId + '&userid=' + ctx.uid + '&isdrag=' + isdrag + '&view=pc' + '&enc=' + enc + '&rt=' + ctx.rt + '&dtype=Video' + '&_t=' + Date.now(); log('📡 上报: ' + formatDuration(data.playTime) + '/' + formatDuration(ctx.duration), 'blue'); GM_xmlhttpRequest({ method: "GET", url: reportsUrl, headers: { 'Host': _l.host, 'Referer': _l.protocol + '//' + _l.host + '/ananas/modules/video/index.html' }, onload: function(res) { try { var result = JSON.parse(res.responseText); if (result.isPassed && !worker.isCompleted) { worker.isCompleted = true; log('✅ 视频任务已完成: ' + ctx.name, 'green'); if (worker.completeCallback) worker.completeCallback(); } } catch(e) {} }, onerror: function() { log('⚠️ 上报失败,将继续尝试', 'orange'); } }); break; case 'completed': log('⏹️ 视频播放完成,等待服务器确认...', 'green'); setTimeout(function() { if (worker.completeCallback) worker.completeCallback(); }, 5000); break; case 'stopped': log('📡 Worker已停止', 'blue'); break; } }; return worker; } // ========== 视频处理相关变量 ========== var currentVideoInterval = null; var currentProgressBar = null; var currentVideoWorker = null; // 单独存储Worker引用 var currentVideoTaskId = null; // 当前视频任务ID var videoTaskCounter = 0; // 视频任务计数器 var _mlist, _defaults, _domList, $subBtn, $saveBtn, $frame_c, $okBtn; var isProcessing = false; var isBoxHidden = false; var pikaqiuAdded = false; var pendingMissionCount = 0; var completedMissionCount = 0; var isJumping = false; var hasTriggeredNoTaskJump = false; var isVideoTaskActive = false; var currentVideoName = null; // ========== 强制清理所有残留资源(每次新任务前调用)========== function forceCleanupAll() { var taskId = videoTaskCounter; log('🧹 开始清理残留资源...', 'orange'); // 停止Worker - 使用独立引用 if (currentVideoWorker) { try { currentVideoWorker.postMessage({ type: 'stop' }); setTimeout(function() { try { if (currentVideoWorker) currentVideoWorker.terminate(); } catch(e) {} }, 100); currentVideoWorker = null; log('✅ 已终止视频上报Worker', 'green'); } catch(e) { log('⚠️ 终止Worker失败: ' + e.message, 'orange'); } } // 清除定时器 if (currentVideoInterval) { clearInterval(currentVideoInterval); currentVideoInterval = null; log('✅ 已清除视频定时器', 'green'); } // 清除reportWorker引用(兼容旧代码) if (reportWorker) { try { reportWorker.terminate(); } catch(e) {} reportWorker = null; } // 立即移除进度条 removeProgressBar(); // 重置所有视频相关状态 isVideoTaskActive = false; currentVideoName = null; activeVideoJob = null; currentVideoTaskId = null; log('🧹 残留资源清理完成', 'green'); } // 单独移除进度条函数 function removeProgressBar() { if (currentProgressBar) { try { $(currentProgressBar).remove(); currentProgressBar = null; log('✅ 已移除视频进度悬浮窗', 'green'); } catch(e) { log('⚠️ 移除进度条失败: ' + e.message, 'orange'); } } // 额外检查:确保DOM中没有残留的进度条 try { var targetDoc = top.document; var existingBar = targetDoc.querySelector('#video-progress-bar'); if (existingBar) { $(existingBar).remove(); log('✅ 已移除残留的进度悬浮窗', 'green'); } } catch(e) {} } // ========== 进度条管理(修复后台更新问题)========== function createProgressBar(videoName, duration) { // 先彻底移除旧的 removeProgressBar(); currentVideoName = videoName; var taskId = ++videoTaskCounter; currentVideoTaskId = taskId; var targetDoc = top.document; var barHtml = `
🎬 ${videoName} 0%
00:00 剩余: --:-- ${formatDuration(duration)}
● 后台上报中 ⚡ ${setting.rate}x
`; try { $(targetDoc.body).append(barHtml); currentProgressBar = targetDoc.querySelector('#video-progress-bar'); } catch(e) { $(document.body).append(barHtml); currentProgressBar = document.querySelector('#video-progress-bar'); } // 确保进度条在后台时也能更新(使用requestAnimationFrame但不依赖页面激活状态) log('📊 进度条已创建,后台模式持续更新', 'blue'); } function updateProgressBar(playTime, duration) { if (!currentProgressBar) return; // 检查是否是当前任务 var taskId = currentProgressBar.getAttribute('data-task-id'); if (taskId && currentVideoTaskId && parseInt(taskId) !== currentVideoTaskId) { return; } var percent = Math.min(100, Math.round((playTime / duration) * 100)); var remaining = Math.max(0, duration - playTime); var fillBar = currentProgressBar.querySelector('#progress-bar-fill'); var percentSpan = currentProgressBar.querySelector('#progress-percent'); var currentSpan = currentProgressBar.querySelector('#progress-current'); var remainingSpan = currentProgressBar.querySelector('#progress-remaining'); if (fillBar) fillBar.style.width = percent + '%'; if (percentSpan) percentSpan.textContent = percent + '%'; if (currentSpan) currentSpan.textContent = formatDuration(playTime); if (remainingSpan) remainingSpan.textContent = '剩余: ' + formatDuration(remaining); } // ========== 完成任务并继续 ========== function completeCurrentTask() { // 清理残留 forceCleanupAll(); if (_mlist && _mlist.length > 0) { var completedTask = _mlist[0]; var taskName = completedTask.property ? (completedTask.property.name || completedTask.property.title || '未知') : '未知'; log('✅ 完成任务: ' + taskName, 'green'); _mlist.splice(0, 1); completedMissionCount++; log('📋 任务进度: ' + completedMissionCount + '/' + pendingMissionCount + ' | 剩余: ' + _mlist.length, 'blue'); } if (_domList && _domList.length > 0) { _domList.splice(0, 1); } isProcessing = false; if (_mlist && _mlist.length > 0) { log('📋 还有 ' + _mlist.length + ' 个任务点待处理,继续...', 'green'); setTimeout(function() { missionStart(); }, 1000); } else { log('✅ 此页面所有任务处理完毕', 'green'); toNext(); } } function toNext() { if (isJumping) return; isJumping = true; log('🔄 检查是否有下一章节...', 'blue'); var nextBtn = null; try { nextBtn = top.document.querySelector('#mainid > .prev_next.next:not(.disabled)') || top.document.querySelector('#prevNextFocusNext:not(.disabled)') || top.document.querySelector('.prev_next.next:not(.disabled)') || $('.prev_next.next:not(.disabled)')[0]; if (nextBtn && !nextBtn.disabled && !nextBtn.classList.contains('disabled')) { log('✅ 找到下一节按钮,3秒后跳转', 'green'); setTimeout(() => { nextBtn.click(); log('📖 已点击跳转按钮', 'green'); isJumping = false; }, 3000); return true; } else { log('📚 课程已全部完成,无更多章节可跳转', 'green'); } } catch(e) { log('❌ 跳转失败: ' + e.message, 'red'); } isJumping = false; return false; } // ========== 获取视频状态 ========== function getVideoStatus(item, callback) { var objectId = item.property.objectid; var statusUrl = _l.protocol + '//' + _l.host + '/ananas/status/' + objectId + '?k=' + (getCookie('fid') || '') + '&flag=normal&_dc=' + Date.now(); GM_xmlhttpRequest({ method: "GET", url: statusUrl, headers: { 'Host': _l.host, 'Referer': _l.protocol + '//' + _l.host + '/ananas/modules/video/index.html' }, onload: function(res) { try { var videoInfo = JSON.parse(res.responseText); callback({ success: true, duration: videoInfo.duration, dtoken: videoInfo.dtoken, objectId: objectId, rt: item.property.rt || '0.9', otherInfo: item.otherInfo || '', jobid: item.jobid, playedTime: videoInfo.playingTime || 0, isPassed: videoInfo.isPassed || false, name: item.property.name }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function(err) { callback({ success: false, error: err.error }); } }); } // ========== 模拟视频上报(修复版)========== function simulateVideoReport(videoInfo, taskObj) { var name = videoInfo.name || taskObj.property.name; var duration = videoInfo.duration; var isAlreadyPassed = videoInfo.isPassed || false; if (isAlreadyPassed && !setting.review) { log('✅ 视频已完成: ' + name + ',跳过', 'green'); completeCurrentTask(); return; } log('🎬 模拟上报模式: ' + name + ',总时长: ' + formatDuration(duration), 'purple'); log('📡 使用Web Worker后台计时,最小化/切换标签页不影响上报', 'blue'); if (setting.showProgressBar) { createProgressBar(name, duration); updateProgressBar(videoInfo.playedTime || 0, duration); } isVideoTaskActive = true; currentVideoName = name; // 创建新的Worker(包含实际上报逻辑) currentVideoWorker = createReportWorker(videoInfo, taskObj); reportWorker = currentVideoWorker; // 兼容 var workerTaskId = videoTaskCounter; // 设置完成回调 currentVideoWorker.completeCallback = function() { if (workerTaskId === currentVideoTaskId && isVideoTaskActive) { forceCleanupAll(); completeCurrentTask(); } }; } function normalVideoPlay(dom, obj) { var name = obj.property.name; var target = dom.length > 0 ? dom[0] : null; if (!target) { log('⚠️ 未找到视频iframe,3秒后重试', 'orange'); isProcessing = false; setTimeout(function() { normalVideoPlay(dom, obj); }, 3000); return; } log('🎬 正常播放模式处理视频:' + name, 'purple'); isVideoTaskActive = true; currentVideoName = name; var executed = false; var doc = target.contentDocument || target.contentWindow.document; if (currentVideoInterval) { clearInterval(currentVideoInterval); } currentVideoInterval = setInterval(function() { var media = doc.querySelector('video') || doc.querySelector('audio'); if (media && !executed) { executed = true; log('✅ ' + name + ' 开始播放', 'green'); media.pause(); media.muted = true; media.playbackRate = setting.rate > 1 ? Math.min(setting.rate, 16) : 1; media.play(); var resumePlay = function() { if (media.paused && !media.ended) { media.play(); } }; media.addEventListener('pause', resumePlay); var onVideoEnd = function() { log('✅ ' + name + ' 播放完成', 'green'); media.removeEventListener('pause', resumePlay); media.removeEventListener('ended', onVideoEnd); clearInterval(currentVideoInterval); currentVideoInterval = null; forceCleanupAll(); completeCurrentTask(); }; media.addEventListener('ended', onVideoEnd); if (media.ended) { onVideoEnd(); } } }, 1500); } function missionVideo(dom, obj) { if (!setting.video) { log('ℹ️ 用户设置不处理视频任务', 'orange'); completeCurrentTask(); return; } var isPassed = obj.isPassed; var name = obj.property.name; if (!setting.review && isPassed === true) { log('✅ 视频:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; } if (setting.videoMode === 'simulate') { log('🎬 模拟上报模式处理视频: ' + name, 'purple'); getVideoStatus(obj, function(result) { if (result.success) { simulateVideoReport(result, obj); } else { log('❌ 获取视频信息失败: ' + result.error + ',降级到正常播放', 'red'); normalVideoPlay(dom, obj); } }); return; } normalVideoPlay(dom, obj); } function missionAudio(dom, obj) { if (!setting.audio) { log('ℹ️ 用户设置不处理音频任务', 'orange'); completeCurrentTask(); return; } var isPassed = obj.isPassed; var name = obj.property.name; if (!setting.review && isPassed === true) { log('✅ 音频:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; } log('🎵 处理音频:' + name + ',等待5秒后完成', 'purple'); setTimeout(function() { completeCurrentTask(); }, 5000); } function missionBook(dom, obj) { var jobId = obj.property.jobid; var name = obj.property.bookname; var jtoken = obj.jtoken; var knowledgeId = _defaults.knowledgeid; var courseId = _defaults.courseid; var clazzId = _defaults.clazzId; if (isTaskCompleted(obj)) { log('✅ 读书:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; } $.ajax({ url: _l.protocol + "//" + _l.host + '/ananas/job?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(), method: 'GET', success: function(res) { log('📚 读书:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); }, error: function() { log('❌ 读书:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { missionBook(dom, obj); }, 3000); } }); } function missionDoucument(dom, obj) { var jobId = obj.property.jobid; var name = obj.property.name; var jtoken = obj.jtoken; var knowledgeId = _defaults.knowledgeid; var courseId = _defaults.courseid; var clazzId = _defaults.clazzId; if (isTaskCompleted(obj)) { log('✅ 文档:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; } $.ajax({ url: _l.protocol + "//" + _l.host + '/ananas/job/document?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(), method: 'GET', success: function(res) { log('📄 文档:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); }, error: function() { log('❌ 文档:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { missionDoucument(dom, obj); }, 3000); } }); } function missionRead(dom, obj) { var jobId = obj.property.jobid; var name = obj.property.title; var jtoken = obj.jtoken; var knowledgeId = _defaults.knowledgeid; var courseId = _defaults.courseid; var clazzId = _defaults.clazzId; if (isTaskCompleted(obj)) { log('✅ 阅读:' + name + ' 已完成,跳过', 'green'); completeCurrentTask(); return; } $.ajax({ url: _l.protocol + '//' + _l.host + '/ananas/job/readv2?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(), method: 'GET', success: function(res) { log('📖 阅读:' + name + (res.msg || '完成'), 'green'); completeCurrentTask(); }, error: function() { log('❌ 阅读:' + name + ' 处理失败,3秒后重试', 'red'); isProcessing = false; setTimeout(function() { missionRead(dom, obj); }, 3000); } }); } // ========== 任务调度(每次开始前强制清理残留)========== function missionStart() { if (isProcessing) { log('⚠️ 已有任务在处理中,跳过', 'orange'); return; } if (!_mlist || _mlist.length <= 0) { log('✅ 此页面任务处理完毕', 'green'); isProcessing = false; toNext(); return; } // 关键:每次开始新任务前,强制清理所有残留资源 forceCleanupAll(); isProcessing = true; var _type = _mlist[0].type; var _dom = _domList[0]; var _task = _mlist[0]; if (_type == undefined) { _type = _mlist[0].property.module; } var taskName = _task.property ? (_task.property.name || _task.property.title || '未知') : '未知'; log('🔄 开始处理任务: ' + taskName + ' (类型: ' + _type + ')', 'blue'); var handlers = { 'video': () => missionVideo(_dom, _task), 'audio': () => missionAudio(_dom, _task), 'workid': () => missionWork(_dom, _task), 'document': () => missionDoucument(_dom, _task), 'read': () => missionRead(_dom, _task), 'insertbook': () => missionBook(_dom, _task) }; if (handlers[_type]) { handlers[_type](); } else if (['insertimage'].includes(_type)) { log('ℹ️ 发现无需处理任务,跳过', 'orange'); completeCurrentTask(); } else { log('⚠️ 暂不支持处理此类型:' + _type + ',跳过', 'red'); completeCurrentTask(); } } // ========== API答题 ========== function getAnswer(type, questionText, optionsData, retryCount) { retryCount = retryCount || 0; var startServerIndex = retryCount; return new Promise((resolve, reject) => { var apiKey = getApiKey(); if (!apiKey || apiKey.trim() === '') { log('❌ 请先设置API密钥才能使用自动答题功能', 'red'); reject('NO_API_KEY'); return; } quotaInfo = getQuotaInfo(); if (quotaInfo.remaining <= 0 && quotaInfo.free_remaining <= 0) { log('❌ API次数已用完,无法获取答案', 'red'); reject('QUOTA_EXHAUSTED'); return; } var typeMap = {0:'单选题',1:'多选题',2:'填空题',3:'判断题',4:'简答题'}; var typeText = typeMap[type] || '单选题'; function tryServer(index) { if (index >= API_SERVERS.length) { log('❌ 所有接口均无法获取答案', 'red'); reject('所有接口均失败'); return; } var apiUrl = API_SERVERS[index].url; var formData = 'question=' + encodeURIComponent(questionText) + '&key=' + encodeURIComponent(apiKey) + '&type=' + encodeURIComponent(typeText); if (optionsData && optionsData.length) { formData += '&options=' + encodeURIComponent(JSON.stringify(optionsData)); } if (index === startServerIndex) { log('🔍 请求答案 - 题型:' + typeText + ' 题目:' + questionText.substring(0, 40) + '...', 'blue'); } var timeoutId = setTimeout(() => { tryServer(index + 1); }, 15000); GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, data: formData, timeout: 15000, onload: function(xhr) { clearTimeout(timeoutId); try { var res = JSON.parse(xhr.responseText); if (res.code == 1 && res.data) { if (res.quota_info) { updateQuotaInfo(res.quota_info); } CURRENT_SERVER_INDEX = index; GM_setValue('current_server_index', index); log('✅ 答案: ' + res.data.substring(0, 100) + '...', 'purple'); resolve(res.data); } else if (res.code == 400) { log('❌ ' + (res.msg || 'API密钥无效'), 'red'); reject('API_KEY_INVALID'); } else { tryServer(index + 1); } } catch(e) { tryServer(index + 1); } }, onerror: function() { clearTimeout(timeoutId); tryServer(index + 1); }, ontimeout: function() { clearTimeout(timeoutId); tryServer(index + 1); } }); } tryServer(startServerIndex); }); } // ========== 测验处理 ========== function missionWork(dom, obj) { if (!setting.work) { log('ℹ️ 用户设置不自动处理测验', 'orange'); completeCurrentTask(); return; } if (!checkApiKeyBeforeAction('自动答题')) { completeCurrentTask(); return; } var isDo = true; if (setting.task && obj.jobid == undefined) { isDo = false; } if (isDo) { if (obj.jobid !== undefined) { var phoneWeb = _l.protocol + '//' + _l.host + '/work/phone/work?workId=' + obj.jobid.replace('work-', '') + '&courseId=' + _defaults.courseid + '&clazzId=' + _defaults.clazzId + '&knowledgeId=' + _defaults.knowledgeid + '&jobId=' + obj.jobid + '&enc=' + obj.enc; log('📝 准备处理测验', 'purple'); setTimeout(function() { startDoPhoneCyWork(0, [dom], phoneWeb, obj); }, 3000); } else { setTimeout(function() { startDoCyWork(0, [dom], obj); }, 3000); } } else { log('ℹ️ 用户设置只处理属于任务点的任务', 'orange'); completeCurrentTask(); } } // ========== 手机端测验处理 ========== function startDoPhoneCyWork(index, doms, phoneWeb, taskObj) { if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; } getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) { if (!iframe) { setTimeout(function() { startDoPhoneCyWork(index, doms, phoneWeb, taskObj); }, 5000); return; } var workIframe = $(iframe); var workStatus = workIframe.contents().find('.newTestCon .newTestTitle .testTit_status').text().trim(); if (!workStatus) { _domList.splice(0, 1); isProcessing = false; setTimeout(switchMission, 2000); return; } if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1 || workStatus.indexOf("未达到及格线") != -1) { workIframe.attr('src', phoneWeb); getElement($(doms[index]).contents()[0], 'iframe[src="' + phoneWeb + '"]').then(function() { setTimeout(function() { doPhoneWork(workIframe.contents(), taskObj); }, 3000); }); } else if (workStatus.indexOf('待批阅') != -1) { _mlist.splice(0, 1); _domList.splice(0, 1); log('⚠️ 测验待批阅,跳过', 'red'); completeCurrentTask(); } else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) { log('✅ 测验已完成,跳过', 'green'); completeCurrentTask(); } else { _mlist.splice(0, 1); _domList.splice(0, 1); log('⚠️ 未知状态,跳过', 'red'); completeCurrentTask(); } }); } function doPhoneWork($dom, taskObj) { var $cy = $dom.find('.Wrappadding form'); $subBtn = $cy.find('.zquestions .zsubmit .btn-ok-bottom'); $okBtn = $dom.find('#okBtn'); $saveBtn = $cy.find('.zquestions .zsubmit .btn-save'); var TimuList = $cy.find('.zquestions .Py-mian1'); startDoPhoneTimu(0, TimuList, taskObj); } function isQuestionAnswered($timu, _type) { if (_type == 0 || _type == 1) { var $opts = _type == 0 ? $timu.find('.answerList.singleChoice li') : $timu.find('.answerList.multiChoice li'); for (var i = 0; i < $opts.length; i++) { if ($($opts[i]).attr('aria-label')) return true; } } else if (_type == 2) { var $inputs = $timu.find('.blankList2 input'); if ($inputs.length && $inputs.first().val() && $inputs.first().val().trim() !== '') return true; // 检查编辑器填空 var $editorBlocks = $timu.find('[data-editorindex]'); if ($editorBlocks.length) { var hasContent = false; $editorBlocks.each(function() { var itemId = $(this).attr('data-itemid'); if (itemId && $('#answer' + itemId).val() && $('#answer' + itemId).val().trim() !== '') { hasContent = true; } }); if (hasContent) return true; } } else if (_type == 3) { var $pd = $timu.find('.answerList.panduan li'); for (var i = 0; i < $pd.length; i++) { if ($($pd[i]).attr('aria-label')) return true; } } else if (_type == 4) { var $ta = $timu.find('textarea[name^="answer"]'); if ($ta.length && $ta.first().val() && $ta.first().val().trim() !== '') return true; } return false; } function getQuestionType($timu, typeName) { var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 }; var _type = typeMap[typeName]; if (_type === undefined) { if ($timu.find('.answerList.singleChoice li').length) _type = 0; else if ($timu.find('.answerList.multiChoice li').length) _type = 1; else if ($timu.find('.blankList2 input').length) _type = 2; else if ($timu.find('.answerList.panduan li').length) _type = 3; else if ($timu.find('textarea').length) _type = 4; } return _type; } function handlePhoneJudge($timu, answer) { var trueKeywords = '正确|是|对|√|T|ri'; var $pd = $timu.find('.answerList.panduan li'); if (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.toLowerCase()) != -1) { $pd.each(function(i, t) { if ($(t).attr('val-param') == 'true') { $(t).click(); log('✅ 手机端判断题选择: 正确 (接口返回: ' + answer + ')', 'green'); } }); } else { $pd.each(function(i, t) { if ($(t).attr('val-param') == 'false') { $(t).click(); log('✅ 手机端判断题选择: 错误 (接口返回: ' + answer + ')', 'green'); } }); } } function startDoPhoneTimu(index, TimuList, taskObj) { if (index == TimuList.length) { finishQuiz(); return; } var contextWindow = TimuList[index] ? (TimuList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow; var $timu = $(TimuList[index]); var questionFull = $timu.find('.Py-m1-title').html(); var _question = tidyQuestion(questionFull).replace(/.*?\[.*?题\]\s*\n\s*/, '').trim(); var typeName = questionFull.match(/.*?\[(.*?)]|$/)[1]; var _type = getQuestionType($timu, typeName); if (isQuestionAnswered($timu, _type)) { log('📌 第' + (index + 1) + '题已作答,跳过', 'green'); setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), 30); return; } var $opts = []; var optsText = []; var optionsData = []; var pureQuestion = _question; if (_type == 0 || _type == 1) { $opts = _type == 0 ? $timu.find('.answerList.singleChoice li') : $timu.find('.answerList.multiChoice li'); $opts.each(function() { optsText.push(tidyStr($(this).html()).replace(/^[A-Z]\s*\n\s*/, '').trim()); }); optionsData = optsText; } else if (_type == 2) { var blankCount = getBlankInputCount($timu); optionsData = [blankCount.toString()]; log('📝 填空题传入空数量: ' + blankCount, 'blue'); } else if (_type == 3) { optionsData = ["对", "错"]; } else if (_type == 4) { optionsData = ["1"]; } getAnswer(_type, pureQuestion, optionsData).then((agrs) => { if (agrs == '暂无答案' || !agrs) { log('⚠️ 无法匹配正确答案,跳过此题', 'red'); setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time); return; } if (setting.alterTitle) { $timu.find('.Py-m1-title').html($timu.find('.Py-m1-title').html() + '

📖 ' + agrs + '

'); } if (_type == 0) { var idx = optsText.findIndex(t => t == agrs); if (idx != -1) $timu.find('.answerList.singleChoice li').eq(idx).click(); else log('⚠️ 未找到匹配选项: ' + agrs, 'orange'); } else if (_type == 1) { var ansArr = splitAnswer(agrs); $opts.each((i, t) => { if (ansArr.includes(optsText[i])) { setTimeout(() => $(t).click(), 300); } }); } else if (_type == 2) { fillBlankAnswer($timu, agrs, true, contextWindow); } else if (_type == 3) { handlePhoneJudge($timu, agrs); } else if (_type == 4) { fillShortAnswer($timu, agrs, true, contextWindow); } log('✅ 第' + (index + 1) + '题自动答题成功', 'green'); setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time); }).catch(() => { log('⚠️ 第' + (index + 1) + '题获取答案失败,跳过', 'orange'); setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time); }); } // ========== PC端测验处理 ========== function startDoCyWork(index, doms, taskObj) { if (index == doms.length) { log('✅ 此页面全部测验已处理完毕', 'green'); completeCurrentTask(); return; } getElement($(doms[index]).contents()[0], 'iframe').then(function(iframe) { if (!iframe) { setTimeout(function() { startDoCyWork(index, doms, taskObj); }, 5000); return; } var workIframe = $(iframe); var workStatus = workIframe.contents().find(".newTestCon .newTestTitle .testTit_status").text().trim(); if (!workStatus) { _domList.splice(0, 1); isProcessing = false; setTimeout(switchMission, 2000); return; } if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1) { log('📝 准备处理测验', 'purple'); setTimeout(function() { doWork(index, doms, iframe, taskObj); }, 5000); } else if (workStatus.indexOf('待批阅') != -1) { _mlist.splice(0, 1); _domList.splice(0, 1); log('⚠️ 测验待批阅,跳过', 'red'); completeCurrentTask(); } else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) { log('✅ 测验已完成,跳过', 'green'); completeCurrentTask(); } else { _mlist.splice(0, 1); _domList.splice(0, 1); log('⚠️ 未知状态,跳过', 'red'); completeCurrentTask(); } }); } function doWork(index, doms, dom, taskObj) { $frame_c = $(dom).contents(); var $CyHtml = $frame_c.find('.CeYan'); var TiMuList = $CyHtml.find('.TiMu'); $subBtn = $frame_c.find(".ZY_sub").find(".btnSubmit"); $saveBtn = $frame_c.find(".ZY_sub").find(".btnSave"); startDoWork(0, TiMuList); } function handlePCJudge($timu, answer) { var trueKeywords = '正确|是|对|√|T|ri'; var $pdContainer = $timu.find('.Zy_ulTop li'); if ($pdContainer.length === 0) { $pdContainer = $timu.find('.stem_answer .answer_p').parent(); } if (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.toLowerCase()) != -1) { var clicked = false; $pdContainer.each(function(i, t) { var $opt = $(t).find('a, .answer_p'); if ($opt.length === 0) $opt = $(t); var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param'); if (valAttr == 'true') { $(t).click(); clicked = true; log('✅ PC端判断题选择: 正确 (接口返回: ' + answer + ')', 'green'); } }); if (!clicked && $pdContainer.length > 0) { $($pdContainer[0]).click(); log('✅ PC端判断题选择: 正确(默认第一个) (接口返回: ' + answer + ')', 'green'); } } else { var clicked = false; $pdContainer.each(function(i, t) { var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param'); if (valAttr == 'false') { $(t).click(); clicked = true; log('✅ PC端判断题选择: 错误 (接口返回: ' + answer + ')', 'green'); } }); if (!clicked && $pdContainer.length > 1) { $($pdContainer[1]).click(); log('✅ PC端判断题选择: 错误(默认第二个) (接口返回: ' + answer + ')', 'green'); } else if (!clicked && $pdContainer.length === 1) { $($pdContainer[0]).click(); log('✅ PC端判断题选择: 错误(唯一选项) (接口返回: ' + answer + ')', 'green'); } } } function finishQuiz() { if (setting.sub) { log('✅ 测验处理完成,准备自动提交', 'green'); setTimeout(() => { if ($subBtn && $subBtn.length) $subBtn.click(); setTimeout(() => { if ($okBtn && $okBtn.length) $okBtn.click(); log('✅ 提交成功', 'green'); completeCurrentTask(); }, 3000); }, 5000); } else if (setting.force) { log('⚠️ 存在无答案题目,强制提交', 'red'); setTimeout(() => { if ($subBtn && $subBtn.length) $subBtn.click(); setTimeout(() => { if ($okBtn && $okBtn.length) $okBtn.click(); log('✅ 提交成功', 'green'); completeCurrentTask(); }, 3000); }, 5000); } else { log('💾 测验处理完成,自动保存', 'green'); setTimeout(() => { if ($saveBtn && $saveBtn.length) $saveBtn.click(); log('✅ 保存成功', 'green'); completeCurrentTask(); }, 5000); } } function startDoWork(c, TiMuList) { if (c == TiMuList.length) { finishQuiz(); return; } var $timu = $(TiMuList[c]); var questionFull = $timu.find('.Zy_TItle.clearfix > div').html(); var _question = tidyQuestion(questionFull); var typeName = questionFull.match(/^【(.*?)】|$/)[1]; var _TimuType = getQuestionType($timu, typeName); var $opts = []; var optsText = []; var optionsData = []; var pureQuestion = _question; if (_TimuType == 0 || _TimuType == 1) { $opts = $timu.find('.Zy_ulTop li a'); $opts.each(function() { optsText.push(tidyStr($(this).html())); }); optionsData = optsText; } else if (_TimuType == 2) { var blankCount = getBlankInputCount($timu); optionsData = [blankCount.toString()]; log('📝 PC端填空题传入空数量: ' + blankCount, 'blue'); } else if (_TimuType == 3) { optionsData = ["对", "错"]; } else if (_TimuType == 4) { optionsData = ["1"]; } getAnswer(_TimuType, pureQuestion, optionsData).then((agrs) => { if (agrs == '暂无答案' || !agrs) { log('⚠️ PC第' + (c + 1) + '题无法获取答案,跳过', 'orange'); setTimeout(() => startDoWork(c + 1, TiMuList), setting.time); return; } if (setting.alterTitle) { $timu.find('.Zy_TItle.clearfix > div').html($timu.find('.Zy_TItle.clearfix > div').html() + '

📖 ' + agrs + '

'); } if (_TimuType == 0) { var idx = optsText.findIndex(t => t == agrs); if (idx != -1) $($opts[idx]).parent().click(); } else if (_TimuType == 1) { var ansArr = splitAnswer(agrs); $opts.each((i, t) => { if (ansArr.includes(optsText[i])) $($opts[i]).parent().click(); }); } else if (_TimuType == 2) { fillBlankAnswer($timu, agrs, false, null); } else if (_TimuType == 3) { handlePCJudge($timu, agrs); } else if (_TimuType == 4) { fillShortAnswer($timu, agrs, false, null); } log('✅ PC第' + (c + 1) + '题自动答题成功', 'green'); setTimeout(() => startDoWork(c + 1, TiMuList), setting.time); }).catch(() => { log('⚠️ PC第' + (c + 1) + '题获取答案失败,跳过', 'orange'); setTimeout(() => startDoWork(c + 1, TiMuList), setting.time); }); } function switchMission() { isProcessing = false; missionStart(); } // ========== 作业处理 ========== function missonHomeWork() { log('📝 开始处理作业', 'green'); if (!checkApiKeyBeforeAction('作业自动答题')) return; var $_homeworktable = $('.mark_table').find('form'); var TimuList = $_homeworktable.find('.questionLi'); doHomeWork(0, TimuList); } function doHomeWork(index, TiMuList) { if (index == TiMuList.length) { log('✅ 作业题目已全部完成', 'green'); return; } var typeName = $(TiMuList[index]).attr('typename'); var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 }; var _type = typeMap[typeName]; var _questionFull = $(TiMuList[index]).find('.mark_name').html(); var _question = tidyQuestion(_questionFull).replace(/^[(].*?[)]/, '').trim(); if (_type === undefined) { var _answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p'); if (_answerTmpArr && _answerTmpArr.length > 0) { _type = $(TiMuList[index]).find('input[type="checkbox"]').length ? 1 : 0; } else if ($(TiMuList[index]).find('.stem_answer').find('.divText textarea').length) { _type = 4; } } var checkAnswered = function() { if (_type == 0 || _type == 1) { var $opts = $(TiMuList[index]).find('.stem_answer .answer_p'); for (var i = 0; i < $opts.length; i++) { if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) { return true; } } } else if (_type == 2) { var $inputs = $(TiMuList[index]).find('.stem_answer .Answer .divText .textDIV textarea'); if ($inputs.length && $inputs.val() && $inputs.val().trim() !== '') return true; } else if (_type == 3) { var $opts = $(TiMuList[index]).find('.stem_answer .answer_p'); for (var i = 0; i < $opts.length; i++) { if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) { return true; } } } else if (_type == 4) { var $ta = $(TiMuList[index]).find('.stem_answer .eidtDiv textarea'); if ($ta.length && $ta.val() && $ta.val().trim() !== '') return true; } return false; }; if (checkAnswered()) { log('📌 第' + (index + 1) + '题已作答,跳过', 'green'); setTimeout(() => doHomeWork(index + 1, TiMuList), 30); return; } var $opts = []; var optsText = []; var optionsData = []; var pureQuestion = _question; if (_type == 0 || _type == 1) { $opts = $(TiMuList[index]).find('.stem_answer .answer_p'); $opts.each(function() { optsText.push(tidyStr($(this).html())); }); optionsData = optsText; } else if (_type == 2) { var blankCount = getBlankInputCount($(TiMuList[index])); optionsData = [blankCount.toString()]; log('📝 作业填空题传入空数量: ' + blankCount, 'blue'); } else if (_type == 3) { optionsData = ["对", "错"]; log('📝 作业判断题传入固定选项: ["对","错"]', 'blue'); } else if (_type == 4) { optionsData = ["1"]; log('📝 作业简答题传入固定参数: ["1"]', 'blue'); } getAnswer(_type, pureQuestion, optionsData).then((agrs) => { if (agrs == '暂无答案' || !agrs) { log('⚠️ 作业第' + (index + 1) + '题无法获取答案,跳过', 'orange'); setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time); return; } if (setting.alterTitle) { $(TiMuList[index]).find('.mark_name').html($(TiMuList[index]).find('.mark_name').html() + '

📖 ' + agrs + '

'); } if (_type == 0) { var idx = optsText.findIndex(t => t == agrs); if (idx != -1) $($opts[idx]).parent().click(); } else if (_type == 1) { var ansArr = splitAnswer(agrs); $opts.each((i, t) => { if (ansArr.includes(optsText[i])) { setTimeout(() => $($opts[i]).parent().click(), 300); } }); } else if (_type == 2) { fillBlankAnswer($(TiMuList[index]), agrs, false, null); } else if (_type == 3) { handlePCJudge($(TiMuList[index]), agrs); } else if (_type == 4) { fillShortAnswer($(TiMuList[index]), agrs, false, null); } log('✅ 作业第' + (index + 1) + '题自动答题成功', 'green'); setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time); }).catch(() => { log('⚠️ 作业第' + (index + 1) + '题获取答案失败,跳过', 'orange'); setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time); }); } // ========== 考试处理 ========== function missonExam() { if (!checkApiKeyBeforeAction('考试自动答题')) return; var $_examtable = $('.mark_table').find('.whiteDiv'); var _questionFull = tidyStr($_examtable.find('h3.mark_name').html().trim()); var typeName = _questionFull.match(/[(](.*?),.*?分[)]|$/)[1]; var typeMap = { '单选题': 0, '多选题': 1, '填空题': 2, '判断题': 3, '简答题': 4 }; var _qType = typeMap[typeName]; var _question = tidyQuestion(_questionFull.replace(/[(].*?分[)]/, '').replace(/^\s*/, '')); var $_ansdom = $_examtable.find('#submitTest').find('.stem_answer'); if (_qType === undefined) { var $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p'); if ($opts.length) { _qType = $_ansdom.find('input[type="checkbox"]').length ? 1 : 0; } else if ($_ansdom.find('.Answer .divText .subEditor textarea').length) { _qType = 4; } } var checkAnswered = function() { if (_qType == 0 || _qType == 1) { var $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p'); for (var i = 0; i < $opts.length; i++) { if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) { return true; } } } else if (_qType == 2) { var $inputs = $_ansdom.find('.Answer .divText .subEditor textarea'); if ($inputs.length && $inputs.val() && $inputs.val().trim() !== '') return true; } else if (_qType == 4) { var $ta = $_ansdom.find('.subEditor textarea'); if ($ta.length && $ta.val() && $ta.val().trim() !== '') return true; } return false; }; if (checkAnswered()) { log('📌 此题已作答,跳过', 'green'); toNextExam(); return; } var $opts = []; var optsText = []; var optionsData = []; var pureQuestion = _question; if (_qType == 0 || _qType == 1) { $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p'); $opts.each(function() { optsText.push(tidyStr($(this).html())); }); optionsData = optsText; } else if (_qType == 2) { var blankCount = getBlankInputCount($_ansdom); optionsData = [blankCount.toString()]; log('📝 考试填空题传入空数量: ' + blankCount, 'blue'); } else if (_qType == 4) { optionsData = ["1"]; log('📝 考试简答题传入固定参数: ["1"]', 'blue'); } else if (_qType == 3) { optionsData = ["对", "错"]; log('📝 考试判断题传入固定选项: ["对","错"]', 'blue'); } getAnswer(_qType, pureQuestion, optionsData).then((agrs) => { if (agrs == '暂无答案' || !agrs) { log('⚠️ 考试此题无法获取答案,跳过', 'orange'); setTimeout(() => toNextExam(), setting.time); return; } if (setting.alterTitle) { $_examtable.find('h3.mark_name').html($_examtable.find('h3.mark_name').html() + '📖 ' + agrs + ''); } if (_qType == 0) { var idx = optsText.findIndex(t => t == agrs); if (idx != -1) { var $target = $($opts[idx]).parent(); if (setting.goodStudent) { $target.find('span').css('font-weight', 'bold'); log('📝 好学生模式:答案已加粗,未自动选择', 'blue'); } else { $target.click(); } } } else if (_qType == 1) { var ansArr = splitAnswer(agrs); $opts.each((i, t) => { if (ansArr.includes(optsText[i])) { if (setting.goodStudent) { $($opts[i]).parent().find('span').css('font-weight', 'bold'); } else { setTimeout(() => $($opts[i]).parent().click(), 300); } } }); } else if (_qType == 2) { fillBlankAnswer($_ansdom, agrs, false, null); } else if (_qType == 3) { handleExamJudge($_ansdom, agrs, setting.goodStudent); } else if (_qType == 4) { fillShortAnswer($_ansdom, agrs, false, null); } log('✅ 考试自动答题成功', 'green'); setTimeout(() => toNextExam(), setting.time); }).catch(() => { log('⚠️ 考试此题获取答案失败,跳过', 'orange'); setTimeout(() => toNextExam(), setting.time); }); } function handleExamJudge($container, answer, goodStudent) { var trueKeywords = '正确|是|对|√|T|ri'; var $pdContainer = $container.find('.clearfix.answerBg .fl.answer_p').parent(); if (trueKeywords.indexOf(answer) != -1 || trueKeywords.toLowerCase().indexOf(answer.toLowerCase()) != -1) { if ($pdContainer.length > 0) { if (goodStudent) { $pdContainer.find('span').css('font-weight', 'bold'); log('📝 好学生模式:答案已加粗,未自动选择', 'blue'); } else { $($pdContainer[0]).click(); } log('✅ 考试判断题选择: 正确 (接口返回: ' + answer + ')', 'green'); } } else { if ($pdContainer.length > 1) { if (goodStudent) { $pdContainer.find('span').css('font-weight', 'bold'); log('📝 好学生模式:答案已加粗,未自动选择', 'blue'); } else { $($pdContainer[1]).click(); } log('✅ 考试判断题选择: 错误 (接口返回: ' + answer + ')', 'green'); } else if ($pdContainer.length === 1) { if (goodStudent) { $pdContainer.find('span').css('font-weight', 'bold'); } else { $($pdContainer[0]).click(); } log('✅ 考试判断题选择: 错误(唯一选项) (接口返回: ' + answer + ')', 'green'); } } } function toNextExam() { if (setting.examTurn) { var $nextbtn = $('.mark_table .whiteDiv .nextDiv a.jb_btn'); var delay = setting.examTurnTime ? 2000 + (Math.floor(Math.random() * 5 + 1) * 1000) : 2000; setTimeout(() => $nextbtn.click(), delay); log('⏭️ ' + (delay/1000) + '秒后自动跳转下一题', 'blue'); } } // ========== 字体解密 ========== function base64ToUint8Array(base64) { var data = window.atob(base64); var buf = new Uint8Array(data.length); for (var i = 0; i < data.length; i++) buf[i] = data.charCodeAt(i); return buf; } function decryptFont() { if (setting.decrypt !== 1) return; try { if (!TyprInstance || !TyprInstance.U || !TyprInstance.parse) { try { if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) { TyprInstance = unsafeWindow.Typr; } else if (typeof window !== 'undefined' && window.Typr) { TyprInstance = window.Typr; } else if (typeof Typr !== 'undefined') { TyprInstance = Typr; } else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) { TyprInstance = unsafeWindow.TyprMd5; } else if (typeof TyprMd5 !== 'undefined') { TyprInstance = TyprMd5; } } catch(e) {} if (!TyprInstance || !TyprInstance.U) { log('⏳ 等待字体解密库加载...', 'blue'); setTimeout(decryptFont, 1000); return; } } var $tip = $('style:contains(font-cxsecret)'); if (!$tip.length) { return; } var styleText = $tip.text(); var fontBase64 = null; var match1 = styleText.match(/base64,([\w\W]+?)'/); if (match1 && match1[1]) fontBase64 = match1[1]; if (!fontBase64) { var match2 = styleText.match(/url\("data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)"\)/); if (match2 && match2[1]) fontBase64 = match2[1]; } if (!fontBase64) { var match3 = styleText.match(/url\('data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)'\)/); if (match3 && match3[1]) fontBase64 = match3[1]; } if (!fontBase64) return; var fontUint8Array = base64ToUint8Array(fontBase64); var parsedFont = TyprInstance.parse(fontUint8Array); if (!parsedFont || parsedFont.length === 0) return; var font = parsedFont[0]; var tableJson = GM_getResourceText('Table'); if (!tableJson) return; var table; try { table = JSON.parse(tableJson); } catch(e) { return; } var match = {}; var matchCount = 0; for (var i = 19968; i < 40870; i++) { var glyph = TyprInstance.U.codeToGlyph(font, i); if (!glyph) continue; var path = TyprInstance.U.glyphToPath(font, glyph); if (!path) continue; var pathStr = JSON.stringify(path); var hash = md5(pathStr); if (!hash) continue; var hashKey = hash.slice(24); var realChar = table[hashKey]; if (realChar !== undefined && realChar !== null && realChar !== 0) { match[i] = realChar; matchCount++; } } if (matchCount === 0) return; var $elements = $('.font-cxsecret'); if ($elements.length === 0) return; $elements.each(function() { var $el = $(this); var html = $el.html(); if (!html) return; for (var code in match) { var encryptedChar = String.fromCharCode(parseInt(code)); var realChar = String.fromCharCode(match[code]); var escapedChar = encryptedChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); var regex = new RegExp(escapedChar, 'g'); html = html.replace(regex, realChar); } if ($el.html() !== html) { $el.html(html); $el.removeClass('font-cxsecret'); } }); log('✅ 字体解密完成!已还原 ' + matchCount + ' 个字符', 'green'); } catch(e) { log('❌ 字体解密失败: ' + e.message, 'red'); } } // ========== 皮卡丘 ========== function showPikaqiu() { if (!setting.maskImg) return; if (pikaqiuAdded) return; try { var targetDoc = top.document; if (targetDoc.querySelector('#pikaqiu-img')) return; var imgHtml = ''; $(targetDoc.body).append(imgHtml); pikaqiuAdded = true; $('#pikaqiu-img', targetDoc).click(function() { var $box = $('#ne-21box', targetDoc); if ($box.length) { if ($box.css('display') === 'none') { $box.css('display', 'block'); isBoxHidden = false; } else { $box.css('display', 'none'); isBoxHidden = true; } } }); } catch(e) {} } // ========== 悬浮窗 ========== function showBox() { if (!setting.showBox) return; try { var targetDoc = top.document; if (targetDoc.querySelector('#ne-21box')) { flushLogs(); quotaInfo = getQuotaInfo(); updateQuotaDisplay(); return; } } catch(e) {} var boxHtml = '
' + '
' + '

📚 学习通助手 v3

' + '[F9隐藏]' + '
' +v '
' + '
' + '
' + '
🔑 API配置
' + '
' + '' + '' + '' + '
' + '
📊 剩余次数: 加载中...
' + '
' + '
' + '
' + '
🎬 视频处理模式
' + '
' + '' + '' + '
' + '
模拟上报:使用Web Worker后台计时,最小化/切换标签页不影响上报
' + '
' + '
' + '
⚙️ 视频参数
' + '
' + '📹 视频倍速: ' + setting.rate.toFixed(2) + 'x' + '' + '
' + '
' + '' + '
' + '
' + '' + '' + '
' + '
' + '
'; try { $(top.document.body).append(boxHtml); } catch(e) { $(document.body).append(boxHtml); } setTimeout(function() { try { var targetDoc = top.document; if (!targetDoc.querySelector('#ne-21close')) { targetDoc = document; } $('#ne-21close', targetDoc).click(function() { $('#ne-21box', targetDoc).hide(); isBoxHidden = true; }); var moreBtn = targetDoc.getElementById('moreSettingsBtn'); var moreSet = targetDoc.getElementById('moreSettings'); var visible = false; if (moreBtn) { moreBtn.onclick = function() { moreSet.style.display = visible ? 'none' : 'block'; moreBtn.textContent = visible ? '⚙️ 更多设置' : '✖️ 关闭设置'; visible = !visible; }; } $('input[name="videoMode"]', targetDoc).change(function() { setting.videoMode = this.value; GM_setValue('videoMode', setting.videoMode); log('🎬 视频模式切换为: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放'), 'green'); }); $('#videoRateSlider', targetDoc).on('input', function() { $('#rateValue', targetDoc).text(parseFloat(this.value).toFixed(2)); }).on('change', function() { var newRate = parseFloat(this.value); setting.rate = newRate; GM_setValue('videoRate', newRate); if (currentVideoWorker && isVideoTaskActive) { currentVideoWorker.postMessage({ type: 'updateRate', rate: newRate }); } log('📹 视频倍速设置为: ' + newRate.toFixed(2) + 'x', 'green'); }); $('#showProgressBarCheck', targetDoc).change(function() { setting.showProgressBar = this.checked ? 1 : 0; GM_setValue('showProgressBar', setting.showProgressBar); }); $('#goToTkBtn', targetDoc).click(function() { GM_openInTab('https://n1t.cn', { active: true }); }); $('#saveApiKeyBtn', targetDoc).click(function() { var key = $('#apiKeyInput', targetDoc).val(); if (key && key.trim()) { var $testResult = $('#testResult', targetDoc); $testResult.html('⏳ 测试中...').css('color', '#666'); testApi(key.trim(), 0).then(function(res) { GM_setValue('api_key', key.trim()); log('✅ API密钥已保存并验证有效', 'green'); if (res.quota_info) { updateQuotaInfo(res.quota_info); var remaining = res.quota_info.remaining || 0; var freeRemaining = res.quota_info.free_remaining || 0; $testResult.html('✅ 密钥有效!剩余次数: ' + remaining + ' | 免费: ' + freeRemaining).css('color', 'green'); } else { $testResult.html('✅ 密钥有效').css('color', 'green'); } $('#apiKeyInput', targetDoc).val(''); $('#apiKeyInput', targetDoc).attr('placeholder', '已设置'); updateQuotaDisplay(); setTimeout(function() { $testResult.html(''); }, 5000); }).catch(function(err) { $testResult.html('❌ 密钥无效: ' + err).css('color', 'red'); log('❌ API密钥无效: ' + err, 'red'); setTimeout(function() { $testResult.html(''); }, 5000); }); } else { var existingKey = getApiKey(); if (existingKey) { var $testResult = $('#testResult', targetDoc); $testResult.html('⏳ 测试现有密钥...').css('color', '#666'); testApi(existingKey, 0).then(function(res) { if (res.quota_info) { updateQuotaInfo(res.quota_info); var remaining = res.quota_info.remaining || 0; var freeRemaining = res.quota_info.free_remaining || 0; $testResult.html('✅ 密钥有效!剩余次数: ' + remaining + ' | 免费: ' + freeRemaining).css('color', 'green'); } else { $testResult.html('✅ 密钥有效').css('color', 'green'); } updateQuotaDisplay(); setTimeout(function() { $testResult.html(''); }, 5000); }).catch(function(err) { GM_setValue('api_key', ''); quotaInfo = { remaining: 0, free_remaining: 0, recharge_balance: 0, today_free_usage: 0 }; saveQuotaInfo(); updateQuotaDisplay(); $testResult.html('❌ 密钥已失效,请重新输入').css('color', 'red'); log('⚠️ API密钥已失效,请重新设置', 'orange'); setTimeout(function() { $testResult.html(''); }, 5000); }); } else { log('❌ 请输入API密钥', 'red'); $('#testResult', targetDoc).html('❌ 请输入API密钥').css('color', 'red'); setTimeout(function() { $('#testResult', targetDoc).html(''); }, 3000); } } }); var timeIntervalInput = targetDoc.getElementById('timeInterval'); if (timeIntervalInput) { timeIntervalInput.addEventListener('change', function(e) { var newTime = parseInt(e.target.value); if (newTime >= 1000 && newTime <= 10000) { setting.time = newTime; localStorage.setItem('GPTJsSetting.time', newTime); log('⚙️ 答题间隔: ' + newTime + 'ms', 'blue'); } else { e.target.value = setting.time; } }); } var settingsList = ['sub', 'force', 'examTurn', 'goodStudent', 'alterTitle', 'review']; settingsList.forEach(function(id) { var cb = targetDoc.getElementById('GPTJsSetting.' + id); if (cb) { var newCb = cb.cloneNode(true); cb.parentNode.replaceChild(newCb, cb); var savedValue = localStorage.getItem('GPTJsSetting.' + id); newCb.checked = savedValue !== null ? savedValue === 'true' : setting[id]; newCb.addEventListener('change', function(e) { var checked = e.target.checked; var settingKey = e.target.id.replace('GPTJsSetting.', ''); setting[settingKey] = checked; localStorage.setItem(e.target.id, checked); log('⚙️ ' + settingKey + ' = ' + checked, 'blue'); }); } }); var savedTime = localStorage.getItem('GPTJsSetting.time'); if (savedTime) { setting.time = parseInt(savedTime); if (timeIntervalInput) timeIntervalInput.value = setting.time; } var savedKey = getApiKey(); if (savedKey) { $('#apiKeyInput', targetDoc).attr('placeholder', '已设置'); testApi(savedKey, 0).then(function(res) { if (res.quota_info) { updateQuotaInfo(res.quota_info); } log('✅ API密钥有效', 'green'); }).catch(function(err) { GM_setValue('api_key', ''); quotaInfo = { remaining: 0, free_remaining: 0, recharge_balance: 0, today_free_usage: 0 }; saveQuotaInfo(); updateQuotaDisplay(); log('⚠️ API密钥已失效,请重新设置', 'orange'); }); } else { updateQuotaDisplay(); } $('#ne-21notice', targetDoc).html('
💡 脚本已加载 v3.1.0 | F9隐藏/显示 | 视频模式: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + setting.rate.toFixed(2) + 'x | 答题间隔: ' + (setting.time/1000) + '秒 | 每次新任务自动清理残留 | 填空题空数检测已修复
'); flushLogs(); showPikaqiu(); } catch(e) { console.error('绑定事件失败:', e); } }, 100); } // ========== 键盘快捷键 ========== $(document).keydown(function(e) { if (e.keyCode == 120) { var $box = $('#ne-21box'); if ($box.length === 0) return; if (isBoxHidden) { $box.show(); isBoxHidden = false; log('📌 面板已显示', 'green'); } else { $box.hide(); isBoxHidden = true; log('📌 面板已隐藏,按F9恢复', 'blue'); } } }); // ========== 获取任务参数 ========== function getTaskParams() { try { var scripts = _d.scripts; for (let i = 0; i < scripts.length; i++) { if (scripts[i].innerHTML.indexOf('mArg = "";') != -1 && scripts[i].innerHTML.indexOf('==UserScript==') == -1) { return getStr(scripts[i].innerHTML.replace(/\s/g, ""), 'try{mArg=', ';}catch'); } } return null; } catch(e) { return null; } } // ========== 重新初始化任务列表 ========== function reinitTaskList() { if (isProcessing) { log('⚠️ 正在处理任务,稍后重新初始化', 'orange'); setTimeout(reinitTaskList, 3000); return; } forceCleanupAll(); var params = getTaskParams(); if (!params || params == '$mArg') { log('⚠️ 无法获取任务参数', 'red'); return; } try { var allTasks = $.parseJSON(params).attachments; if (!allTasks || allTasks.length <= 0) { log('⚠️ 无任务点', 'red'); return; } _defaults = $.parseJSON(params).defaults; var pendingTasks = []; for (var i = 0; i < allTasks.length; i++) { var task = allTasks[i]; if (!isTaskCompleted(task)) { pendingTasks.push(task); } } pendingMissionCount = pendingTasks.length; if (pendingMissionCount === 0) { log('✅ 所有任务点已完成', 'green'); return; } log('📋 重新加载任务列表,发现 ' + pendingMissionCount + ' 个待处理任务', 'green'); _mlist = []; _domList = []; $('.wrap .ans-cc .ans-attach-ct').each((i, t) => { if (i < allTasks.length && !isTaskCompleted(allTasks[i])) { _mlist.push(allTasks[i]); _domList.push($(t).find('iframe')); } }); if (_mlist.length > 0 && !isProcessing) { log('🚀 继续处理剩余任务', 'green'); missionStart(); } } catch(e) { log('❌ 重新初始化任务列表失败: ' + e.message, 'red'); } } // ========== 页面初始化 ========== $(function() { var savedServerIndex = GM_getValue('current_server_index', 0); if (savedServerIndex >= 0 && savedServerIndex < API_SERVERS.length) { CURRENT_SERVER_INDEX = savedServerIndex; } setting.videoMode = GM_getValue('videoMode', 'simulate'); setting.rate = GM_getValue('videoRate', 1); setting.showProgressBar = GM_getValue('showProgressBar', 1); var settingsMap = { 'sub': 'sub', 'force': 'force', 'examTurn': 'examTurn', 'goodStudent': 'goodStudent', 'alterTitle': 'alterTitle', 'review': 'review', 'time': 'time' }; for (var key in settingsMap) { var val = localStorage.getItem('GPTJsSetting.' + key); if (val !== null) { if (key === 'time') { setting[settingsMap[key]] = parseInt(val); } else { setting[settingsMap[key]] = val === 'true'; } } } _w.confirm = function() { return true; }; if (setting.decrypt) { setTimeout(decryptFont, 2000); setTimeout(function() { if ($('.font-cxsecret').length > 0) { decryptFont(); } }, 5000); setTimeout(function() { if ($('.font-cxsecret').length > 0) { decryptFont(); } }, 10000); } if (_l.pathname == '/login' && setting.autoLogin) { showBox(); waitForElement('#phone').then(() => { $('#phone').val(setting.phone); $('#pwd').val(setting.password); $('#loginBtn').click(); }); } else if (_l.pathname.includes('/mycourse/studentstudy')) { showBox(); log('✅ 初始化完毕!视频模式: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + setting.rate.toFixed(2) + 'x', 'green'); } else if (_l.pathname.includes('/knowledge/cards')) { showBox(); var params = getTaskParams(); if (!params || params == '$mArg') { log('⚠️ 无任务点可处理', 'red'); if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(() => { toNext(); }, 2000); } return; } try { var allTasks = $.parseJSON(params).attachments; if (!allTasks || allTasks.length <= 0) { log('⚠️ 无任务点可处理', 'red'); if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(() => { toNext(); }, 2000); } return; } } catch(e) { log('⚠️ 解析任务参数失败', 'red'); return; } waitForElement('.wrap .ans-cc .ans-attach-ct').then(() => { if (top.checkJob) top.checkJob = function() { return false; }; var allTasks = $.parseJSON(params).attachments; _defaults = $.parseJSON(params).defaults; var pendingTasks = []; for (var i = 0; i < allTasks.length; i++) { var task = allTasks[i]; if (!isTaskCompleted(task)) { pendingTasks.push(task); } else { var taskName = task.property?.name || task.property?.title || '任务点' + (i+1); log('✅ 任务点 ' + taskName + ' 已完成,跳过', 'green'); } } pendingMissionCount = pendingTasks.length; completedMissionCount = 0; if (pendingMissionCount === 0) { log('✅ 所有任务点已完成', 'green'); setTimeout(() => { toNext(); }, 2000); return; } log('📋 发现 ' + allTasks.length + ' 个任务点,其中 ' + pendingMissionCount + ' 个待处理', 'green'); _mlist = []; _domList = []; $('.wrap .ans-cc .ans-attach-ct').each((i, t) => { if (i < allTasks.length && !isTaskCompleted(allTasks[i])) { _mlist.push(allTasks[i]); _domList.push($(t).find('iframe')); } }); if (_mlist.length > 0) { log('🚀 开始处理 ' + _mlist.length + ' 个待完成任务', 'green'); missionStart(); } else { log('✅ 所有任务点已完成', 'green'); setTimeout(() => { toNext(); }, 2000); } }).catch(() => { log('❌ 等待元素超时', 'red'); if (!hasTriggeredNoTaskJump) { hasTriggeredNoTaskJump = true; setTimeout(() => { toNext(); }, 2000); } }); } else if (_l.pathname.includes('/exam/test/reVersionTestStartNew')) { showBox(); waitForElement('.mark_table .whiteDiv').then(() => missonExam()); } else if (_l.pathname.includes('/mooc2/work/dowork')) { showBox(); waitForElement('.mark_table form').then(() => missonHomeWork()); } else if (_l.pathname.includes('/work/phone/doHomeWork')) { var _oldal = _w.alert; _w.alert = function(msg) { if (msg == '保存成功') return; return _oldal(msg); }; var _oldcf = _w.confirm; _w.confirm = function(msg) { if (msg.includes('确认提交') || msg.includes('未做完')) return true; return _oldcf(msg); }; } }); // 监听URL变化 var lastChapterUrl = _l.href; setInterval(function() { if (_l.href !== lastChapterUrl) { lastChapterUrl = _l.href; forceCleanupAll(); setTimeout(function() { if (_l.pathname.includes('/knowledge/cards')) { reinitTaskList(); } }, 3000); } }, 2000); window.addEventListener('beforeunload', function() { forceCleanupAll(); });