// ==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 ''
+ '
'
+ '
🎓 智慧树网课助手
'
+ '✕'
+ '
'
+ '
'
+ '
'
+ '
'
+ ''
+ ''
+ ''
+ '
'
+ '
📊 剩余次数: 加载中...
'
+ '
'
+ '
'
+ '
'
+ '
'
+ '
'
+ '
';
}
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();
});