// ==UserScript== // @name 智慧树网课助手(纯智慧树版) // @version 1.0.0 // @author bzm // @description 智慧树自动刷课,视频心跳、弹题、作业、考试全自动,接自建题库 // @match *://*.zhihuishu.com/* // @match *://*.studyh5.zhihuishu.com/* // @match *://*.xue.zhihuishu.com/* // @run-at document-end // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @grant GM_openInTab // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // @connect studyh5.zhihuishu.com // @connect xue.zhihuishu.com // @connect newapi.zhihuishu.com // @connect tk.n1t.cn // @connect tk.swk.tw // ==/UserScript== /********************************* 配置管理器 ****************************************/ var SettingsManager = { _defaults: { showBox: 1, handleVideo: 1, videoRate: 1, reviewMode: 0, handleQuiz: 1, answerInterval: 5000, autoSubmit: 0, forceSubmit: 0, insertAnswer: 1, videoMode: 'simulate', reportBaseInterval: 58, showProgressBar: 1 }, get: function(key) { var stored = localStorage.getItem('zhs_setting_' + key); if (stored !== null) { if (stored === 'true') return true; if (stored === 'false') return false; if (!isNaN(parseFloat(stored)) && isFinite(stored)) return parseFloat(stored); return stored; } return this._defaults[key]; }, set: function(key, value) { localStorage.setItem('zhs_setting_' + key, value); this._defaults[key] = value; return value; }, load: function() { for (var key in this._defaults) { if (localStorage.getItem('zhs_setting_' + key) !== null) { var stored = localStorage.getItem('zhs_setting_' + key); if (stored === 'true') this._defaults[key] = true; else if (stored === 'false') this._defaults[key] = false; else if (!isNaN(parseFloat(stored)) && isFinite(stored)) this._defaults[key] = parseFloat(stored); else this._defaults[key] = stored; } } if (this._defaults.videoRate > 8) this._defaults.videoRate = 8; return this._defaults; } }; var CONFIG = SettingsManager.load(); if (CONFIG.videoRate > 8) CONFIG.videoRate = 8; function updateConfig(key, value) { CONFIG[key] = SettingsManager.set(key, value); if (key === 'videoRate' && CONFIG.videoRate > 8) { CONFIG.videoRate = 8; SettingsManager.set('videoRate', 8); } log('⚙️ 配置已更新: ' + key + ' = ' + value, 'blue'); } var UNSAFE_WIN = unsafeWindow; var LOCATION = location; var $ = UNSAFE_WIN.jQuery || top.jQuery; // ========== 通用工具 ========== function getCookie(name) { return ('; ' + document.cookie).split('; ' + name + '=').pop().split(';')[0]; } function formatDuration(seconds) { if (!seconds || seconds < 0) return '00:00'; var total = Math.max(0, Math.floor(Number(seconds) || 0)); var hours = Math.floor(total / 3600); var minutes = Math.floor((total % 3600) / 60); var secs = total % 60; var pad = function(n) { return String(n).padStart(2, '0'); }; if (hours > 0) return pad(hours) + ':' + pad(minutes) + ':' + pad(secs); return pad(minutes) + ':' + pad(secs); } function normalizeForCompare(text) { if (!text) return ''; return text.toUpperCase().replace(/[^\u4e00-\u9fa5A-Z0-9]/g, '').trim(); } function splitAnswer(answer) { if (!answer) return []; var parts = answer.split(/[#]+/).map(function(a) { return a.trim(); }).filter(function(a) { return a !== ''; }); if (parts.length === 0 && answer.trim() !== '') return [answer.trim()]; return parts; } function tidyString(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, '').replace(/^【.*?】\s*/, '').replace(/ /g, '').trim(); } function tidyQuestion(s) { if (!s) return null; return s.replace(/<(?!img).*?>/g, '').replace(/^【.*?】\s*/, '').replace(/^\d+[.、]/, '').trim(); } // ========== 日志系统 ========== var logQueue = []; var logTimer = null; function getTopLogContainer() { try { var topDoc = top.document; var container = topDoc.getElementById('zhs-log'); if (container) return $(container); container = document.getElementById('zhs-log'); if (container) return $(container); return null; } catch(e) { return null; } } function flushLogs() { if (logQueue.length === 0) return; var container = getTopLogContainer(); if (container && container.length) { var logsToShow = logQueue.slice(); logQueue = []; for (var i = logsToShow.length - 1; i >= 0; i--) { container.prepend(logsToShow[i]); } container.scrollTop(0); } if (logTimer) { clearTimeout(logTimer); logTimer = null; } } function scheduleFlushLogs() { if (logTimer) return; logTimer = setTimeout(flushLogs, 100); } function log(str, color) { var time = new Date().toLocaleTimeString(); var logHtml = '
[' + time + '] ' + str + '
'; logQueue.push(logHtml); scheduleFlushLogs(); console.log('[智慧树助手][' + time + ']', str); } // ========== 题库配置 ========== var API_SERVERS = [ { url: "https://tk.n1t.cn/api/search.php", name: "接口1" }, { url: "https://tk.swk.tw/api/search.php", name: "接口2" } ]; var currentServerIndex = 0; function getApiKey() { return GM_getValue('api_key', ''); } function hasApiKey() { var key = getApiKey(); return key && key.trim() !== ''; } function getQuotaInfo() { return { remaining: GM_getValue('quota_remaining', 0), freeRemaining: GM_getValue('quota_free_remaining', 0) }; } var quotaInfo = getQuotaInfo(); function saveQuotaInfo() { GM_setValue('quota_remaining', quotaInfo.remaining); GM_setValue('quota_free_remaining', quotaInfo.freeRemaining); } function updateQuotaInfo(info) { if (info) { quotaInfo.remaining = info.remaining !== undefined ? info.remaining : quotaInfo.remaining; quotaInfo.freeRemaining = info.free_remaining !== undefined ? info.free_remaining : quotaInfo.freeRemaining; saveQuotaInfo(); updateQuotaDisplay(); } } function updateQuotaDisplay() { try { var targetDoc = top.document; if (!targetDoc.querySelector('#quotaInfo')) targetDoc = document; var $quotaInfo = $('#quotaInfo', targetDoc); if ($quotaInfo.length) { if (quotaInfo.remaining > 0) $quotaInfo.html('📊 剩余次数: ' + quotaInfo.remaining + ' | 免费: ' + quotaInfo.freeRemaining); else if (hasApiKey()) $quotaInfo.html('⚠️ 次数已用完'); else $quotaInfo.html('⚠️ 请先设置API密钥'); } } catch(e) {} } function checkApiKeyBeforeAction(actionName) { if (!hasApiKey()) { log('❌ 无法执行' + actionName + ':请先配置API密钥', 'red'); return false; } quotaInfo = getQuotaInfo(); if (quotaInfo.remaining <= 0 && quotaInfo.freeRemaining <= 0) { log('❌ 无法执行' + actionName + ':API次数已用完', 'red'); return false; } return true; } function testApi(key, serverIndex) { return new Promise(function(resolve, reject) { var apiUrl = API_SERVERS[serverIndex || 0].url; var formData = 'question=' + encodeURIComponent("test") + '&key=' + encodeURIComponent(key) + '&type=' + encodeURIComponent("单选题"); var timeoutId = setTimeout(function() { reject('请求超时'); }, 10000); GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, data: formData, timeout: 10000, onload: function(xhr) { clearTimeout(timeoutId); try { var res = JSON.parse(xhr.responseText); if (res.code == 1 || res.code == 400) { if (res.quota_info) updateQuotaInfo(res.quota_info); resolve(res); } else reject(res.msg || '密钥无效'); } catch(e) { reject('解析失败'); } }, onerror: function() { clearTimeout(timeoutId); reject('网络失败'); }, ontimeout: function() { clearTimeout(timeoutId); reject('超时'); } }); }); } // ========== 题库调用(核心,通用) ========== function getAnswer(questionType, questionText, optionsData, retryCount) { retryCount = retryCount || 0; var startServerIndex = retryCount; return new Promise(function(resolve, reject) { var apiKey = getApiKey(); if (!apiKey || apiKey.trim() === '') { log('❌ 请先设置API密钥', 'red'); reject('NO_API_KEY'); return; } quotaInfo = getQuotaInfo(); if (quotaInfo.remaining <= 0 && quotaInfo.freeRemaining <= 0) { log('❌ API次数已用完', 'red'); reject('QUOTA_EXHAUSTED'); return; } var typeMap = {0:'单选题',1:'多选题',2:'填空题',3:'判断题',4:'简答题'}; var typeText = typeMap[questionType] || '单选题'; function tryServer(index) { if (index >= API_SERVERS.length) { log('❌ 所有接口均失败', 'red'); reject('所有接口均失败'); return; } var apiUrl = API_SERVERS[index].url; var formData = 'question=' + encodeURIComponent(questionText) + '&key=' + encodeURIComponent(apiKey) + '&type=' + encodeURIComponent(typeText); if (optionsData && optionsData.length) formData += '&options=' + encodeURIComponent(JSON.stringify(optionsData)); var timeoutId = setTimeout(function() { tryServer(index + 1); }, 15000); GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, data: formData, timeout: 15000, onload: function(xhr) { clearTimeout(timeoutId); try { var res = JSON.parse(xhr.responseText); if (res.code == 1 && res.data) { if (res.quota_info) updateQuotaInfo(res.quota_info); currentServerIndex = index; GM_setValue('current_server_index', index); log('✅ 答案: ' + res.data.substring(0, 100), 'purple'); resolve(res.data); } else if (res.code == 400) { log('❌ ' + (res.msg || 'API密钥无效'), 'red'); reject('API_KEY_INVALID'); } else tryServer(index + 1); } catch(e) { tryServer(index + 1); } }, onerror: function() { clearTimeout(timeoutId); tryServer(index + 1); }, ontimeout: function() { clearTimeout(timeoutId); tryServer(index + 1); } }); } tryServer(startServerIndex); }); } // ========== 智慧树适配层 ========== var ZhsAdapter = { // 课程 ID 提取(多路兜底) getRecruitAndCourseId: function() { // 1. URL 参数 var m = LOCATION.search.match(/recruitAndCourseId=(\d+)/); if (m) return m[1]; // 2. URL 路径 var pm = LOCATION.pathname.match(/\/course\/(\d+)/); if (pm) return pm[1]; // 3. 全局变量 try { if (UNSAFE_WIN.recruitAndCourseId) return UNSAFE_WIN.recruitAndCourseId; if (UNSAFE_WIN.courseId) return UNSAFE_WIN.courseId; if (UNSAFE_WIN.__INITIAL_STATE__ && UNSAFE_WIN.__INITIAL_STATE__.recruitAndCourseId) return UNSAFE_WIN.__INITIAL_STATE__.recruitAndCourseId; } catch(e) {} // 4. DOM data 属性 var $el = $('[data-recruit-and-course-id]'); if ($el.length) return $el.attr('data-recruit-and-course-id'); // 5. localStorage try { var ls = localStorage.getItem('recruitAndCourseId'); if (ls) return ls; } catch(e) {} return null; }, // 获取视频元数据 getVideoInfo: function(recruitAndCourseId, lessonId, videoId, callback) { var url = 'https://studyh5.zhihuishu.com/videoStudy/queryVideoStudyInfo' + '?recruitAndCourseId=' + recruitAndCourseId + '&lessonId=' + lessonId + '&videoId=' + videoId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { var data = JSON.parse(res.responseText); if (data.code === 0 && data.data) callback({ success: true, data: data.data }); else callback({ success: false, error: data.message || '返回码异常' }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function(err) { callback({ success: false, error: err.error || '网络错误' }); } }); }, // 视频心跳上报 reportVideoTime: function(params, callback) { var url = 'https://studyh5.zhihuishu.com/videoStudy/saveVideoStudyTime'; var body = 'recruitAndCourseId=' + params.recruitAndCourseId + '&lessonId=' + params.lessonId + '&videoId=' + params.videoId + '&smallLessonId=' + (params.smallLessonId || '') + '&studyTotalTime=' + params.studyTotalTime + '&watchState=' + params.watchState; GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOCATION.href }, data: body, onload: function(res) { try { var data = JSON.parse(res.responseText); callback({ success: data.code === 0, data: data }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function(err) { callback({ success: false, error: err.error }); } }); }, // 查询弹题 queryPopupQuestion: function(recruitAndCourseId, lessonId, videoId, callback) { var url = 'https://studyh5.zhihuishu.com/videoStudy/queryPopupQuestion' + '?recruitAndCourseId=' + recruitAndCourseId + '&lessonId=' + lessonId + '&videoId=' + videoId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { callback({ success: true, data: JSON.parse(res.responseText) }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function() { callback({ success: false }); } }); }, // 提交弹题答案 submitPopupAnswer: function(recruitAndCourseId, questionId, answer, callback) { var url = 'https://studyh5.zhihuishu.com/videoStudy/savePopupQuestion'; var body = 'recruitAndCourseId=' + recruitAndCourseId + '&questionId=' + questionId + '&answer=' + encodeURIComponent(answer); GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOCATION.href }, data: body, onload: function(res) { try { callback({ success: true, data: JSON.parse(res.responseText) }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function() { callback({ success: false }); } }); }, // 提交作业 submitHomework: function(recruitAndCourseId, homeworkId, answers, callback) { var url = 'https://studyh5.zhihuishu.com/homework/saveHomeworkAnswer'; var body = 'recruitAndCourseId=' + recruitAndCourseId + '&homeworkId=' + homeworkId + '&answerJson=' + encodeURIComponent(JSON.stringify(answers)); GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOCATION.href }, data: body, onload: function(res) { try { callback({ success: true, data: JSON.parse(res.responseText) }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function() { callback({ success: false }); } }); }, // 提交考试单题 submitExamAnswer: function(examId, questionId, answer, callback) { var url = 'https://studyh5.zhihuishu.com/exam/saveExamAnswer'; var body = 'examId=' + examId + '&questionId=' + questionId + '&answer=' + encodeURIComponent(answer); GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOCATION.href }, data: body, onload: function(res) { try { callback({ success: true, data: JSON.parse(res.responseText) }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function() { callback({ success: false }); } }); }, // 交卷 submitExam: function(examId, recruitAndCourseId, callback) { var url = 'https://studyh5.zhihuishu.com/exam/submitExam'; var body = 'examId=' + examId + '&recruitAndCourseId=' + recruitAndCourseId; GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOCATION.href }, data: body, onload: function(res) { try { callback({ success: true, data: JSON.parse(res.responseText) }); } catch(e) { callback({ success: false, error: e.message }); } }, onerror: function() { callback({ success: false }); } }); } }; // 智慧树题型映射到通用题型编号 function mapZhsQuestionType(type) { var map = { 1: 0, 2: 1, 3: 3, 4: 2, 5: 4 }; return map[type] !== undefined ? map[type] : 0; } // ========== 进度条 ========== var currentProgressBar = null; var currentVideoTaskId = null; var videoTaskCounter = 0; function removeProgressBar() { if (currentProgressBar) { try { $(currentProgressBar).remove(); currentProgressBar = null; } catch(e) {} } try { var targetDoc = top.document; var existingBar = targetDoc.querySelector('#zhs-progress-bar'); if (existingBar) $(existingBar).remove(); } catch(e) {} } function createProgressBar(videoName, duration) { removeProgressBar(); var taskId = ++videoTaskCounter; currentVideoTaskId = taskId; var targetDoc = top.document; var actualInterval = getActualReportInterval(); var barHtml = '
' + '
' + '🎬 ' + videoName + '' + '0%
' + '
' + '
' + '
' + '00:00' + '剩余: --:--' + '' + formatDuration(duration) + '
' + '
' + '● 智慧树上报中' + '⚡ ' + CONFIG.videoRate.toFixed(2) + 'x (间隔:' + actualInterval + 's)' + '
'; try { $(targetDoc.body).append(barHtml); currentProgressBar = targetDoc.querySelector('#zhs-progress-bar'); } catch(e) { $(document.body).append(barHtml); currentProgressBar = document.querySelector('#zhs-progress-bar'); } } function updateProgressBar(playTime, duration) { if (!currentProgressBar) return; var taskId = currentProgressBar.getAttribute('data-task-id'); if (taskId && currentVideoTaskId && parseInt(taskId) !== currentVideoTaskId) return; var percent = Math.min(100, Math.round((playTime / duration) * 100)); var remaining = Math.max(0, duration - playTime); var fillBar = currentProgressBar.querySelector('#zhs-progress-fill'); var percentSpan = currentProgressBar.querySelector('#zhs-progress-percent'); var currentSpan = currentProgressBar.querySelector('#zhs-progress-current'); var remainingSpan = currentProgressBar.querySelector('#zhs-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 getActualReportInterval() { var baseInterval = CONFIG.reportBaseInterval || 58; var safeRate = Math.min(CONFIG.videoRate, 1.1); return Math.max(10, Math.floor(baseInterval / safeRate)); } // ========== 后台保活 ========== (function keepVisible() { try { Object.defineProperty(document, 'visibilityState', { get: function() { return 'visible'; }, configurable: true }); Object.defineProperty(document, 'hidden', { get: function() { return false; }, configurable: true }); document.addEventListener('visibilitychange', function(e) { e.stopImmediatePropagation(); }, true); } catch(e) {} })(); // ========== 智慧树视频处理 ========== var zhsVideoTimer = null; function processZhsVideo(recruitAndCourseId, lessonId, videoId) { if (!CONFIG.handleVideo) { log('ℹ️ 视频处理已关闭', 'orange'); return; } log('🎬 获取视频信息: lessonId=' + lessonId + ', videoId=' + videoId, 'purple'); ZhsAdapter.getVideoInfo(recruitAndCourseId, lessonId, videoId, function(result) { if (!result.success) { log('❌ 获取视频信息失败: ' + result.error, 'red'); return; } var info = result.data; var duration = info.duration || 0; var studyTotalTime = info.studyTotalTime || 0; var watchState = info.watchState || 0; if (duration <= 0) { log('⚠️ 视频时长为0,跳过', 'orange'); return; } if (watchState === 2 && !CONFIG.reviewMode) { log('✅ 视频已完成: ' + videoId, 'green'); return; } log('🎬 开始智慧树视频: 总时长 ' + formatDuration(duration) + ',已学 ' + formatDuration(studyTotalTime), 'purple'); var safeRate = Math.min(CONFIG.videoRate, 1.1); var reportInterval = getActualReportInterval(); var currentTime = studyTotalTime; var lastReportTime = studyTotalTime; if (CONFIG.showProgressBar) { createProgressBar('智慧树视频 ' + videoId, duration); updateProgressBar(currentTime, duration); } if (zhsVideoTimer) clearInterval(zhsVideoTimer); zhsVideoTimer = setInterval(function() { // 按安全倍速推进 var delta = Math.ceil(reportInterval * safeRate); currentTime = Math.min(currentTime + delta, duration); var params = { recruitAndCourseId: recruitAndCourseId, lessonId: lessonId, videoId: videoId, smallLessonId: info.smallLessonId || '', studyTotalTime: currentTime, watchState: currentTime >= duration ? 2 : 1 }; ZhsAdapter.reportVideoTime(params, function(res) { if (res.success) { log('📡 智慧树上报: ' + formatDuration(currentTime) + '/' + formatDuration(duration), 'blue'); updateProgressBar(currentTime, duration); lastReportTime = currentTime; if (currentTime >= duration) { clearInterval(zhsVideoTimer); zhsVideoTimer = null; log('✅ 智慧树视频完成: ' + videoId, 'green'); setTimeout(removeProgressBar, 3000); } } else { log('⚠️ 上报失败,回退进度重试', 'orange'); currentTime = lastReportTime; } }); // 弹题检测 ZhsAdapter.queryPopupQuestion(recruitAndCourseId, lessonId, videoId, function(popupRes) { if (popupRes.success && popupRes.data && popupRes.data.data && popupRes.data.data.length > 0) { popupRes.data.data.forEach(function(q) { solveZhsPopupQuestion(recruitAndCourseId, q); }); } }); }, reportInterval * 1000); }); } // ========== 智慧树弹题处理 ========== function solveZhsPopupQuestion(recruitAndCourseId, question) { if (!CONFIG.handleQuiz) return; var questionText = tidyQuestion(question.questionContent || question.title || question.questionName || ''); var questionType = mapZhsQuestionType(question.questionType); var options = (question.optionList || question.options || []).map(function(o) { return tidyString(o.content || o.optionContent || o.text || ''); }); log('📝 智慧树弹题: ' + questionText.substring(0, 40), 'purple'); getAnswer(questionType, questionText, options).then(function(answer) { if (!answer || answer === '暂无答案') { log('⚠️ 弹题无答案,用第一项兜底', 'orange'); answer = options[0] || 'A'; } // 多选题答案拆分 var submitAnswer = answer; if (questionType === 1) { submitAnswer = splitAnswer(answer).join(','); } ZhsAdapter.submitPopupAnswer(recruitAndCourseId, question.questionId, submitAnswer, function(res) { if (res.success) log('✅ 弹题提交成功: ' + submitAnswer, 'green'); else log('❌ 弹题提交失败', 'red'); }); }).catch(function() { var fallback = options[0] || 'A'; ZhsAdapter.submitPopupAnswer(recruitAndCourseId, question.questionId, fallback, function(res) { log('⚠️ 弹题兜底提交: ' + fallback, 'orange'); }); }); } // ========== 智慧树作业处理 ========== function processZhsHomework(recruitAndCourseId) { if (!checkApiKeyBeforeAction('智慧树作业')) return; var url = 'https://studyh5.zhihuishu.com/homework/queryHomeworkList?recruitAndCourseId=' + recruitAndCourseId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { var data = JSON.parse(res.responseText); if (data.code !== 0 || !data.data) { log('❌ 获取作业列表失败', 'red'); return; } var list = (data.data.homeworkList || data.data || []).filter(function(hw) { return !hw.submitted && hw.status !== 2; }); if (list.length === 0) { log('✅ 所有作业已完成', 'green'); return; } log('📋 发现 ' + list.length + ' 个待做作业', 'green'); doZhsHomeworkList(0, list, recruitAndCourseId); } catch(e) { log('❌ 解析作业列表失败: ' + e.message, 'red'); } }, onerror: function() { log('❌ 作业列表请求失败', 'red'); } }); } function doZhsHomeworkList(index, list, recruitAndCourseId) { if (index >= list.length) { log('✅ 所有作业处理完毕', 'green'); return; } var hw = list[index]; log('📝 处理作业 ' + (index + 1) + '/' + list.length + ': ' + (hw.homeworkName || hw.title || hw.name), 'purple'); var url = 'https://studyh5.zhihuishu.com/homework/queryHomeworkDetail' + '?homeworkId=' + hw.homeworkId + '&recruitAndCourseId=' + recruitAndCourseId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { var data = JSON.parse(res.responseText); if (data.code !== 0 || !data.data) { log('❌ 获取作业详情失败,跳过', 'red'); setTimeout(function() { doZhsHomeworkList(index + 1, list, recruitAndCourseId); }, 2000); return; } var questions = data.data.questionList || data.data.questions || []; if (questions.length === 0) { log('⚠️ 作业无题目,跳过', 'orange'); setTimeout(function() { doZhsHomeworkList(index + 1, list, recruitAndCourseId); }, 2000); return; } doZhsHomeworkQuestions(0, questions, [], function(answers) { ZhsAdapter.submitHomework(recruitAndCourseId, hw.homeworkId, answers, function(submitRes) { if (submitRes.success) log('✅ 作业提交成功: ' + (hw.homeworkName || hw.title), 'green'); else log('❌ 作业提交失败', 'red'); setTimeout(function() { doZhsHomeworkList(index + 1, list, recruitAndCourseId); }, 3000); }); }); } catch(e) { log('❌ 解析作业详情失败: ' + e.message, 'red'); setTimeout(function() { doZhsHomeworkList(index + 1, list, recruitAndCourseId); }, 2000); } }, onerror: function() { log('❌ 作业详情请求失败,跳过', 'red'); setTimeout(function() { doZhsHomeworkList(index + 1, list, recruitAndCourseId); }, 2000); } }); } function doZhsHomeworkQuestions(index, questions, answers, callback) { if (index >= questions.length) { callback(answers); return; } var q = questions[index]; var questionText = tidyQuestion(q.questionContent || q.title || q.questionName || ''); var questionType = mapZhsQuestionType(q.questionType); var options = (q.optionList || q.options || []).map(function(o) { return tidyString(o.content || o.optionContent || o.text || ''); }); log('✍️ 作业第' + (index + 1) + '/' + questions.length + '题: ' + questionText.substring(0, 40), 'blue'); getAnswer(questionType, questionText, options).then(function(answer) { var finalAnswer = answer; if (!finalAnswer || finalAnswer === '暂无答案') finalAnswer = options[0] || 'A'; if (questionType === 1) finalAnswer = splitAnswer(finalAnswer).join(','); answers.push({ questionId: q.questionId, answer: finalAnswer }); log('✅ 第' + (index + 1) + '题答案: ' + finalAnswer, 'green'); setTimeout(function() { doZhsHomeworkQuestions(index + 1, questions, answers, callback); }, CONFIG.answerInterval); }).catch(function() { var fallback = options[0] || 'A'; answers.push({ questionId: q.questionId, answer: fallback }); log('⚠️ 第' + (index + 1) + '题兜底: ' + fallback, 'orange'); setTimeout(function() { doZhsHomeworkQuestions(index + 1, questions, answers, callback); }, CONFIG.answerInterval); }); } // ========== 智慧树考试处理 ========== function processZhsExam(recruitAndCourseId) { if (!checkApiKeyBeforeAction('智慧树考试')) return; var url = 'https://studyh5.zhihuishu.com/exam/queryExamList?recruitAndCourseId=' + recruitAndCourseId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { var data = JSON.parse(res.responseText); if (data.code !== 0 || !data.data) { log('❌ 获取考试列表失败', 'red'); return; } var list = (data.data.examList || data.data || []).filter(function(ex) { return !ex.submitted && ex.status !== 2; }); if (list.length === 0) { log('✅ 所有考试已完成', 'green'); return; } log('📋 发现 ' + list.length + ' 场考试', 'green'); doZhsExamList(0, list, recruitAndCourseId); } catch(e) { log('❌ 解析考试列表失败: ' + e.message, 'red'); } }, onerror: function() { log('❌ 考试列表请求失败', 'red'); } }); } function doZhsExamList(index, list, recruitAndCourseId) { if (index >= list.length) { log('✅ 所有考试处理完毕', 'green'); return; } var exam = list[index]; log('📝 处理考试 ' + (index + 1) + '/' + list.length + ': ' + exam.examName, 'purple'); var url = 'https://studyh5.zhihuishu.com/exam/startExam' + '?examId=' + exam.examId + '&recruitAndCourseId=' + recruitAndCourseId + '&_t=' + Date.now(); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Referer': LOCATION.href }, onload: function(res) { try { var data = JSON.parse(res.responseText); if (data.code !== 0 || !data.data) { log('❌ 开始考试失败,跳过', 'red'); setTimeout(function() { doZhsExamList(index + 1, list, recruitAndCourseId); }, 2000); return; } var questions = data.data.questionList || data.data.questions || []; if (questions.length === 0) { log('⚠️ 考试无题目,跳过', 'orange'); setTimeout(function() { doZhsExamList(index + 1, list, recruitAndCourseId); }, 2000); return; } doZhsExamQuestions(0, questions, exam.examId, recruitAndCourseId, function() { ZhsAdapter.submitExam(exam.examId, recruitAndCourseId, function(submitRes) { if (submitRes.success) log('✅ 考试交卷成功: ' + exam.examName, 'green'); else log('❌ 交卷失败', 'red'); setTimeout(function() { doZhsExamList(index + 1, list, recruitAndCourseId); }, 3000); }); }); } catch(e) { log('❌ 解析考试数据失败: ' + e.message, 'red'); setTimeout(function() { doZhsExamList(index + 1, list, recruitAndCourseId); }, 2000); } }, onerror: function() { log('❌ 开始考试请求失败,跳过', 'red'); setTimeout(function() { doZhsExamList(index + 1, list, recruitAndCourseId); }, 2000); } }); } function doZhsExamQuestions(index, questions, examId, recruitAndCourseId, doneCallback) { if (index >= questions.length) { doneCallback(); return; } var q = questions[index]; var questionText = tidyQuestion(q.questionContent || q.title || q.questionName || ''); var questionType = mapZhsQuestionType(q.questionType); var options = (q.optionList || q.options || []).map(function(o) { return tidyString(o.content || o.optionContent || o.text || ''); }); log('✍️ 考试第' + (index + 1) + '/' + questions.length + '题', 'blue'); getAnswer(questionType, questionText, options).then(function(answer) { var finalAnswer = answer; if (!finalAnswer || finalAnswer === '暂无答案') finalAnswer = options[0] || 'A'; if (questionType === 1) finalAnswer = splitAnswer(finalAnswer).join(','); ZhsAdapter.submitExamAnswer(examId, q.questionId, finalAnswer, function(res) { if (res.success) log('✅ 第' + (index + 1) + '题提交: ' + finalAnswer, 'green'); else log('⚠️ 第' + (index + 1) + '题提交失败', 'orange'); setTimeout(function() { doZhsExamQuestions(index + 1, questions, examId, recruitAndCourseId, doneCallback); }, Math.max(CONFIG.answerInterval, 3000)); }); }).catch(function() { var fallback = options[0] || 'A'; ZhsAdapter.submitExamAnswer(examId, q.questionId, fallback, function() { setTimeout(function() { doZhsExamQuestions(index + 1, questions, examId, recruitAndCourseId, doneCallback); }, Math.max(CONFIG.answerInterval, 3000)); }); }); } // ========== UI ========== function getSettingsPanelHTML() { var actualInterval = getActualReportInterval(); return '
' + '
' + '

🎓 智慧树网课助手

' + '✕
' + '
' + '
' + '
' + '
' + '' + '' + '' + '
' + '
📊 剩余次数: 加载中...
' + '
' + '
' + '
' + '🎬 视频倍速' + '' + CONFIG.videoRate.toFixed(2) + 'x
' + '' + '
' + '' + '⚡ 上报间隔 ' + actualInterval + 's
' + '' + '' + '
' + '
'; } function bindSettingUI(targetDoc) { $('#zhs-close', targetDoc).off('click').on('click', function() { $('#zhs-box', targetDoc).hide(); }); var $moreBtn = $('#moreSettingsBtn', targetDoc); var $moreSet = $('#moreSettings', targetDoc); var visible = false; $moreBtn.off('click').on('click', function() { visible = !visible; $moreSet.css('display', visible ? 'block' : 'none'); $moreBtn.text(visible ? '✖️ 收起设置' : '⚙️ 更多设置'); }); $('#videoRateSlider', targetDoc).off('input change').on('input', function() { $('#rateValue', targetDoc).text(parseFloat(this.value).toFixed(2)); }).on('change', function() { var r = parseFloat(this.value); if (r > 8) r = 8; updateConfig('videoRate', r); }); $('#showProgressBarCheck', targetDoc).off('change').on('change', function() { updateConfig('showProgressBar', this.checked ? 1 : 0); }); $('#answerInterval', targetDoc).off('change').on('change', function(e) { var v = parseInt(e.target.value); if (v >= 1000 && v <= 30000) updateConfig('answerInterval', v); else e.target.value = CONFIG.answerInterval; }); ['handleVideo', 'handleQuiz', 'reviewMode', 'autoSubmit'].forEach(function(id) { var $cb = $('#setting_' + id, targetDoc); if ($cb.length) $cb.off('change').on('change', function() { updateConfig(id, this.checked); }); }); $('#goToTkBtn', targetDoc).off('click').on('click', function() { GM_openInTab('https://tk.n1t.cn', { active: true }); }); $('#saveApiKeyBtn', targetDoc).off('click').on('click', function() { var key = $('#apiKeyInput', targetDoc).val(); var $tr = $('#testResult', targetDoc); if (key && key.trim()) { $tr.html('⏳ 测试中...').css('color', '#7d9bb5'); testApi(key.trim(), 0).then(function(res) { GM_setValue('api_key', key.trim()); if (res.quota_info) updateQuotaInfo(res.quota_info); $tr.html('✅ 密钥有效').css('color', '#2c7da0'); $('#apiKeyInput', targetDoc).val('').attr('placeholder', '已设置'); updateQuotaDisplay(); log('✅ API密钥已保存', 'green'); setTimeout(function() { $tr.html(''); }, 5000); }).catch(function(err) { $tr.html('❌ 密钥无效: ' + err).css('color', '#e07c5c'); setTimeout(function() { $tr.html(''); }, 5000); }); } else { var existing = getApiKey(); if (existing) { testApi(existing, 0).then(function(res) { if (res.quota_info) updateQuotaInfo(res.quota_info); $tr.html('✅ 密钥有效').css('color', '#2c7da0'); updateQuotaDisplay(); setTimeout(function() { $tr.html(''); }, 5000); }).catch(function() { GM_setValue('api_key', ''); $tr.html('❌ 密钥失效').css('color', '#e07c5c'); updateQuotaDisplay(); setTimeout(function() { $tr.html(''); }, 5000); }); } else { $tr.html('❌ 请输入密钥').css('color', '#e07c5c'); setTimeout(function() { $tr.html(''); }, 3000); } } }); } function showBox() { if (!CONFIG.showBox) return; try { var targetDoc = top.document; if (targetDoc.querySelector('#zhs-box')) { flushLogs(); updateQuotaDisplay(); return; } } catch(e) {} setTimeout(function() { try { var targetDoc = top.document; $(targetDoc.body).append(getSettingsPanelHTML()); bindSettingUI(targetDoc); $('#zhs-notice', targetDoc).html('
✅ 智慧树助手已加载 | 倍速: ' + CONFIG.videoRate.toFixed(2) + 'x | 上报间隔: ' + getActualReportInterval() + 's
'); var savedKey = getApiKey(); if (savedKey) { $('#apiKeyInput', targetDoc).attr('placeholder', '已设置'); testApi(savedKey, 0).then(function(res) { if (res.quota_info) updateQuotaInfo(res.quota_info); log('✅ API密钥有效', 'green'); }) .catch(function() { GM_setValue('api_key', ''); updateQuotaDisplay(); log('⚠️ API密钥失效', 'orange'); }); } else updateQuotaDisplay(); flushLogs(); } catch(e) { console.error('显示悬浮窗失败:', e); } }, 100); } // ========== 页面类型判断与入口 ========== function getVideoParamsFromUrl() { var params = new URLSearchParams(LOCATION.search); var lessonId = params.get('lessonId') || params.get('lessonid'); var videoId = params.get('videoId') || params.get('videoid'); if (!lessonId || !videoId) { // 从 DOM 提取 var $el = $('[data-lesson-id][data-video-id]'); if ($el.length) { lessonId = $el.attr('data-lesson-id'); videoId = $el.attr('data-video-id'); } } return { lessonId: lessonId, videoId: videoId }; } function detectPageType() { var path = LOCATION.pathname.toLowerCase(); if (path.indexOf('videostudy') !== -1 || path.indexOf('/video') !== -1 || LOCATION.search.indexOf('videoId=') !== -1) return 'video'; if (path.indexOf('homework') !== -1) return 'homework'; if (path.indexOf('exam') !== -1) return 'exam'; return 'unknown'; } $(function() { if (CONFIG.videoRate > 8) CONFIG.videoRate = 8; var savedServerIndex = GM_getValue('current_server_index', 0); if (savedServerIndex >= 0 && savedServerIndex < API_SERVERS.length) currentServerIndex = savedServerIndex; showBox(); log('✅ 智慧树助手初始化', 'green'); var recruitAndCourseId = ZhsAdapter.getRecruitAndCourseId(); if (!recruitAndCourseId) { log('⚠️ 未获取到课程ID,请确认在课程页面内', 'orange'); // 延迟重试一次 setTimeout(function() { var retryId = ZhsAdapter.getRecruitAndCourseId(); if (retryId) { log('✅ 重试获取到课程ID: ' + retryId, 'green'); runZhsTask(retryId); } }, 3000); return; } log('🎓 课程ID: ' + recruitAndCourseId, 'green'); runZhsTask(recruitAndCourseId); }); function runZhsTask(recruitAndCourseId) { var pageType = detectPageType(); log('📄 页面类型: ' + pageType, 'blue'); if (pageType === 'video') { var vp = getVideoParamsFromUrl(); if (vp.lessonId && vp.videoId) { processZhsVideo(recruitAndCourseId, vp.lessonId, vp.videoId); } else { log('⚠️ 视频参数缺失,3秒后重试', 'orange'); setTimeout(function() { var retryVp = getVideoParamsFromUrl(); if (retryVp.lessonId && retryVp.videoId) { processZhsVideo(recruitAndCourseId, retryVp.lessonId, retryVp.videoId); } }, 3000); } } else if (pageType === 'homework') { processZhsHomework(recruitAndCourseId); } else if (pageType === 'exam') { processZhsExam(recruitAndCourseId); } else { log('ℹ️ 当前页面无自动化任务', 'blue'); } } // URL 变化监听(智慧树是 SPA) var lastUrl = LOCATION.href; var lastPageType = detectPageType(); setInterval(function() { if (LOCATION.href !== lastUrl) { lastUrl = LOCATION.href; var newType = detectPageType(); if (newType !== lastPageType) { lastPageType = newType; log('🔄 页面切换: ' + newType, 'blue'); var cid = ZhsAdapter.getRecruitAndCourseId(); if (cid) runZhsTask(cid); } } }, 2000); window.addEventListener('beforeunload', function() { if (zhsVideoTimer) { clearInterval(zhsVideoTimer); zhsVideoTimer = null; } removeProgressBar(); });