// ==UserScript== // @name 智慧树掌握度答题-AI自动答题脚本 (Zhihuishu AI Auto-Answering) // @namespace http://tampermonkey.net/ // @version 1.2.0 // @description 半自动完成智慧树掌握度练习。新增支持免费模式(GLM-4.5-Flash)及自定义多种AI服务商(DeepSeek/Zhipu/OpenAI/Gemini)。 // @author Coren // @match https://studywisdomh5.zhihuishu.com/study* // @match https://studywisdomh5.zhihuishu.com/exam* // @match https://studywisdomh5.zhihuishu.com/pointOfMastery* // @connect api.coren.xin // @connect open.bigmodel.cn // @connect api.deepseek.com // @connect api.openai.com // @connect generativelanguage.googleapis.com // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @license CC BY-NC-SA 4.0 // @license https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh // ==/UserScript== (function() { 'use strict'; // --- 1. UI 和样式 --- GM_addStyle(` #ai-panel { position: fixed; top: 100px; right: 20px; width: 300px; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; transition: transform 0.3s ease-in-out; transform: translateX(110%); } #ai-panel.show { transform: translateX(0); } #panel-toggle { position: fixed; top: 100px; right: 20px; width: 40px; height: 40px; background-color: #0d6efd; color: white; border: none; border-radius: 50%; cursor: pointer; z-index: 10000; display: flex; justify-content: center; align-items: center; font-size: 20px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } #panel-header { padding: 15px; background-color: #0d6efd; color: white; border-top-left-radius: 8px; border-top-right-radius: 8px; font-size: 18px; font-weight: 500; } #panel-content { padding: 20px; display: flex; flex-direction: column; gap: 15px; } .input-group { display: flex; flex-direction: column; } .input-group label { margin-bottom: 5px; color: #333; font-weight: 500; } .input-group input, .input-group select { padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } #start-button { padding: 10px 15px; background-color: #198754; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } #start-button:hover { background-color: #157347; } #status-log { margin-top: 15px; padding: 10px; background-color: #f8f9fa; border-radius: 4px; height: 100px; overflow-y: auto; font-size: 12px; color: #555; border: 1px solid #e0e0e0; } .custom-settings.hidden, .model-name-group.hidden { display: none; } `); const panelHTML = `
${JSON.stringify(responseData, null, 2)}`);
resolve(null);
}
} else {
log(`API 响应格式不正确或内容为空。`);
log(`原始响应: ${JSON.stringify(responseData, null, 2)}`);
resolve(null);
}
} else {
log(`API 请求失败: ${response.status} ${response.statusText}`);
log(`原始响应: ${response.responseText}`);
resolve(null);
}
},
onerror: (error) => { log(`API 调用出错: ${error.statusText || '网络错误'}`); resolve(null); },
ontimeout: () => { log(`API 请求超时 (15秒)。请检查网络或服务商状态。`); resolve(null); }
});
});
}
// --- 6. 页面处理逻辑 (基本无变化) ---
async function processTestPage() {
log("进入答题页面,开始处理...");
try { await waitForQuestionChange("1", 10000); }
catch(e) { log(`错误: ${e.message}`); toggleAutoMode(false); return; }
const questionItems = document.querySelectorAll('.answer-card .list .item');
log(`共 ${questionItems.length} 道题。`);
for (const questionEl of questionItems) {
if (!autoMode) { log("自动答题已停止。"); return; }
if (questionEl.classList.contains('violet')) {
log(`第 ${questionEl.textContent.trim()} 题已完成,跳过。`);
continue;
}
const questionNumber = questionEl.textContent.trim();
log(`开始处理第 ${questionNumber} 题...`);
if (!questionEl.classList.contains('active')) { reliableClick(questionEl); }
try {
await waitForQuestionChange(questionNumber);
await new Promise(r => setTimeout(r, 300));
const questionContainer = document.querySelector('.exam-item:not([style*="display: none"]) .question-item');
const questionType = questionContainer.querySelector('.quest-type')?.innerText.trim() || '单选题';
const questionTitle = questionContainer.querySelector('.quest-title .option-name')?.innerText.trim();
const optionsElements = Array.from(questionContainer.querySelectorAll('.el-radio, .el-checkbox'));
const optionsText = optionsElements.map(el => el.querySelector('.preStyle')?.innerText.trim());
if (!questionTitle || optionsElements.length === 0) { log("错误: 无法解析题目或选项,跳过此题。"); continue; }
log(`题目 (${questionType}): ${questionTitle}`);
const aiAnswer = await getAIAnswer(questionTitle, optionsText, questionType);
if (aiAnswer) {
log(`尝试选择AI答案: ${aiAnswer}`);
for (let char of aiAnswer) {
const optionIndex = char.charCodeAt(0) - 65;
if (optionIndex >= 0 && optionIndex < optionsElements.length) {
const labelElement = optionsElements[optionIndex];
const inputElement = labelElement.querySelector('.el-radio__original, .el-checkbox__original');
if (inputElement) { reliableClick(inputElement); }
else { log(`错误:找不到选项 ${char} 的内部input元素。`); }
await new Promise(r => setTimeout(r, 200));
}
}
}
if (modeSelect.value === 'free') {
log("免费模式,等待2秒以避免API速率限制...");
await new Promise(r => setTimeout(r, 2000));
}
} catch (err) { log(`处理第 ${questionNumber} 题时出错: ${err.message}, 跳过此题。`); continue; }
}
if (autoMode) {
log("所有题目回答完毕,准备提交...");
await new Promise(r => setTimeout(r, 1000));
const submitButton = document.querySelector('.submit');
reliableClick(submitButton);
log("已提交答案。");
}
}
function waitForQuestionChange(qNum, timeout = 7000) {
log(`正在等待第 ${qNum} 题加载...`);
return new Promise((resolve, reject) => {
const intervalTime = 200;
let elapsedTime = 0;
const interval = setInterval(() => {
elapsedTime += intervalTime;
if (elapsedTime >= timeout) { clearInterval(interval); reject(new Error(`等待第 ${qNum} 题超时`)); return; }
const answerCardItem = Array.from(document.querySelectorAll('.answer-card .list .item')).find(item => item.textContent.trim() === qNum);
if (!answerCardItem || !answerCardItem.classList.contains('active')) return;
const visibleExamItem = Array.from(document.querySelectorAll('.exam-item')).find(el => el.offsetHeight > 0);
const questionContainer = visibleExamItem?.querySelector('.question-item');
const titleElement = questionContainer?.querySelector('.quest-title .option-index');
if (titleElement && titleElement.textContent.trim().startsWith(qNum)) {
log(`第 ${qNum} 题加载成功!`);
clearInterval(interval);
resolve();
}
}, intervalTime);
});
}
async function processResultsPage() {
if (!autoMode) return;
log("进入结算页面,准备返回...");
await new Promise(r => setTimeout(r, 3000));
const backButton = document.querySelector('.backup-icon');
if (backButton) { reliableClick(backButton); log("已返回主页面,准备开始下一轮。"); }
else { log("错误: 未找到返回按钮。"); }
}
async function findAndClickGrayItem() {
log("在主页面,寻找灰色项目...");
let grayItem;
await new Promise(resolve => {
const startTime = Date.now();
const interval = setInterval(() => {
grayItem = document.querySelector('.item-box.gray');
if (grayItem) { clearInterval(interval); resolve(); }
if (Date.now() - startTime > 10000) { clearInterval(interval); log("超时:10秒内未找到灰色项目。"); resolve(); }
}, 500);
});
if (grayItem) {
log("已找到灰色项目,准备点击...");
grayItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
await new Promise(r => setTimeout(r, 1000));
const hoverEvent = new MouseEvent('mouseover', { bubbles: true, cancelable: true, view: unsafeWindow });
grayItem.dispatchEvent(hoverEvent);
await new Promise(r => setTimeout(r, 1000));
const improveButtons = document.querySelectorAll('.custom-content div, .el-popper div');
const improveButton = Array.from(improveButtons).find(el => el.textContent.includes('提升掌握度'));
if (improveButton) { log("找到'提升掌握度'按钮,点击进入..."); reliableClick(improveButton); }
else { log("未找到'提升掌握度'按钮,10秒后重试..."); setTimeout(mainLoop, 10000); }
} else {
log("恭喜!未找到灰色项目,任务全部完成。");
toggleAutoMode(false);
}
}
function mainLoop() {
if (!autoMode) return;
const currentUrl = window.location.href;
if (currentUrl.includes('/study/mastery')) { findAndClickGrayItem(); }
else if (currentUrl.includes('/exam')) { processTestPage(); }
else if (currentUrl.includes('/pointOfMastery')) { processResultsPage(); }
}
function toggleAutoMode(start) {
autoMode = start;
if (autoMode) {
startButton.textContent = '停止自动答题';
startButton.style.backgroundColor = '#dc3545';
log('自动答题已开始!');
mainLoop();
} else {
startButton.textContent = '开始自动答题';
startButton.style.backgroundColor = '#198754';
log('自动答题已停止。');
}
}
// --- 7. 启动脚本和监听器 ---
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
log(`URL 变动: ${url}`);
if(autoMode) setTimeout(mainLoop, 2000);
}
}).observe(document, { subtree: true, childList: true });
window.addEventListener('load', () => {
log("AI答题脚本已加载。请在右侧面板选择模式并开始。");
}, false);
})();