// ==UserScript== // @name 知到智慧树全自动刷问答平时分助手 // @version 1.0.1 // @namespace http://fenda.github.io/ // @description 全自动刷智慧树问答,自动提交,自动刷新,自动跳转,自动关闭,自动通知 // @author fenda // @match https://qah5.zhihuishu.com/qa.html // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant GM_registerMenuCommand // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; const defaultAPIKey = "sk-zt3dcYmk8D4FECtgDa7427F7E71444B589D6FbD6CcC44b30"; const defaultAPIUrl = "https://api.gpt.ge/v1/chat/completions"; const defaultModel = "gpt-3.5-turbo-0125"; const intervalTime = GM_getValue("intervalTime", 2000); const initialCountdownTime = GM_getValue("countdownTime", 20); let requestLock = false; let noAnswerButtonCount = 0; let countdownInterval; const inputEvent = new Event("input", { bubbles: true, cancelable: true }); function debounce(fn, delay) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, arguments), delay); }; } function initMutationObserver() { const debouncedUpdate = debounce(() => { console.log("DOM 变化检测到,更新问题列表..."); addQuestionStatus(); checkHomeRegex(); }, 500); const observer = new MutationObserver(debouncedUpdate); const config = { attributes: true, childList: true, subtree: true }; observer.observe(document.body, config); console.log("MutationObserver 已启动,正在监听 DOM 变化..."); } function displayCountdown(countdown) { let participatoryDiv = document.querySelector('.MyParticipatory-div'); let countdownElement = document.querySelector('.new-neumorphic-card'); const linkClicked = GM_getValue("linkClicked", false); if (participatoryDiv) { if (!countdownElement) { countdownElement = document.createElement('div'); countdownElement.className = 'new-neumorphic-card'; countdownElement.style = ` padding: 15px; border-radius: 15px; background: linear-gradient(145deg, #ffffff, #e6e6e6); box-shadow: 6px 6px 12px #aaaaaa, -6px -6px 12px #ffffff; display: inline-block; max-width: 300px; `; participatoryDiv.parentElement.appendChild(countdownElement); countdownElement.innerHTML = `
`; document.getElementById("apiKeyInput").value = GM_getValue("API_KEY", defaultAPIKey); document.getElementById("apiUrlInput").value = GM_getValue("API_URL", defaultAPIUrl); document.getElementById("countdownTimeInput").value = GM_getValue("countdownTime", initialCountdownTime); document.getElementById("modelInput").value = GM_getValue("model", defaultModel); document.getElementById("saveButton").addEventListener('click', function() { GM_setValue("API_KEY", document.getElementById("apiKeyInput").value); GM_setValue("API_URL", document.getElementById("apiUrlInput").value); GM_setValue("countdownTime", parseInt(document.getElementById("countdownTimeInput").value, 10)); GM_setValue("model", document.getElementById("modelInput").value); alert('设置已保存'); }); if (!linkClicked) { document.getElementById("purchaseLink").addEventListener('click', function(event) { event.preventDefault(); // Prevent actual navigation GM_setValue("linkClicked", true); window.open("https://api.v3.cm/register?aff=25L7", "_blank"); }); } // Tooltip functionality const infoIcon = document.getElementById("infoIcon"); infoIcon.addEventListener("mouseenter", function() { const tooltip = document.createElement('div'); tooltip.id = 'tooltip'; tooltip.style = ` position: absolute; top: 25px; left: 0; background: #333; color: #fff; padding: 8px; border-radius: 5px; font-size: 12px; white-space: pre-wrap; width: 200px; height: auto; z-index: 1000; box-sizing: border-box; overflow: hidden; `; tooltip.innerText = `1.由于答题太快会导致被禁言,故倒计时不要太低。\n2.作者提供的API仅有大概14000题的配额,请在试用脚本有效后立刻点击链接购买API。(1美元配额大概可答7000题)\n3.此API用完作者不会去补充,请为后面想使用的人考虑!\n4.被屏蔽的问题会显示下划线,未回答问题显示红色,回答过显示黑色。\n5.重要信息;考虑到一些原因,只自动回答页面中的问题,也就是说回答完本页问题后,需要手动加载下一页,或者你可以直接提前加载下一页(一页50题目)。`; infoIcon.appendChild(tooltip); }); infoIcon.addEventListener("mouseleave", function() { const tooltip = document.getElementById('tooltip'); if (tooltip) tooltip.remove(); }); } else { countdownElement.querySelector('.Participatorytitle-div div span').innerText = `${countdown} 秒`; } } } function startCountdown(restartCount = null) { if (countdownInterval) { clearInterval(countdownInterval); } let countdown = restartCount !== null ? restartCount : GM_getValue("countdownTime", initialCountdownTime); displayCountdown(countdown); countdownInterval = setInterval(() => { countdown--; displayCountdown(countdown); if (countdown <= 0) { clearInterval(countdownInterval); countdownInterval = null; console.log(`${GM_getValue("countdownTime", initialCountdownTime)}秒已过,点击动作触发。`); scrollToUnansweredQuestions(); startCountdown(); } }, 1000); } function checkHomeRegex() { const homePattern = /^https:\/\/qah5\.zhihuishu\.com\/qa\.html#\/web\/home/; const currentURL = window.location.href; if (homePattern.test(currentURL)) { console.log("正在获取问题列表..."); const items = document.querySelectorAll(".question-item .question-content span"); const courseNameElement = document.querySelector(".course-name"); if (courseNameElement && items.length) { const courseName = courseNameElement.innerText.trim(); let questionsData = GM_getValue("questionsData", {}); if (!questionsData[courseName]) { questionsData[courseName] = {}; } items.forEach(item => { const questionTitle = item.getAttribute("title").trim(); if (!questionsData[courseName][questionTitle]) { questionsData[courseName][questionTitle] = "未回答"; } }); GM_setValue("questionsData", questionsData); console.log("问题列表已更新:", questionsData); } } } function markQuestionAsAnswered(questionText) { const courseNameElement = document.querySelector(".course-name"); if (courseNameElement) { const courseName = courseNameElement.innerText.trim(); let questionsData = GM_getValue("questionsData", {}); if (questionsData[courseName] && questionsData[courseName][questionText] === "未回答") { questionsData[courseName][questionText] = "已回答"; GM_setValue("questionsData", questionsData); console.log(`问题“${questionText}”状态已更新为“已回答”。`); } } } function waitForAnswerButton() { console.log("正在检测‘我来回答’按钮..."); const answerButton = document.querySelector("div.my-answer-btn.ZHIHUISHU_QZMD.tool-show span"); if (answerButton && answerButton.textContent.trim() === "我来回答") { console.log("找到‘我来回答’按钮,点击..."); answerButton.click(); noAnswerButtonCount = 0; setTimeout(() => { const inputElement = document.querySelector("textarea"); if (inputElement) { console.log("文本框已出现,准备回答问题..."); startChecking(); } else { console.log("文本框未出现,继续检测‘我来回答’按钮..."); waitForAnswerButton(); } }, intervalTime); } else { checkHomeRegex(); console.log("未找到‘我来回答’按钮或按钮状态不符合条件,稍后重试..."); noAnswerButtonCount++; const currentURL = window.location.href; const pattern = /^https:\/\/qah5\.zhihuishu\.com\/qa\.html#\/web\/questionDetail\/\d+/; if (noAnswerButtonCount >= 3) { if (pattern.test(currentURL)) { const questionElement = document.querySelector(".question-content span"); const questionText = questionElement ? questionElement.innerText.trim() : ''; if (questionText) { markQuestionAsAnswered(questionText); } console.log("当前页面符合特定模式,关闭网页..."); window.close(); } else { console.log("当前页面不符合关闭条件,继续检测..."); noAnswerButtonCount = 0; } } else { setTimeout(waitForAnswerButton, intervalTime); } } } function addQuestionStatus() { const questionItems = document.querySelectorAll('.question-item'); questionItems.forEach(item => { const spanElement = item.querySelector('.question-content span'); const questionText = spanElement ? spanElement.title.trim() : ''; if (questionText) { const courseNameElement = document.querySelector(".course-name"); if (courseNameElement) { const courseName = courseNameElement.innerText.trim(); let status = "未获取"; const questionsData = GM_getValue("questionsData", {}); if (questionsData[courseName] && questionsData[courseName][questionText]) { status = questionsData[courseName][questionText]; } const invalidPatterns = [ /什么是.+/, /.+是什么/, /.+是什么意思/, /.+的含义/, /指什么/, /是不是/, /对不对/, /好不好/, /好吗/, /近吗/, /远吗/, /对吗/, /可以吗/, /可以.+吗/, /是否可以/, /有没有/, /能不能/, /应该.+吗/, /是否应该/, /必须.+吗/, /是否必须/, /需要.+吗/, /是否需要/, /一定.+吗/, /是否一定/, /属于.+吗/, /是否属于/, /是否合理/, /是否相同/, /是否不同/, /有意义吗/, /意义大吗/, /喜欢.+吗/, /如何翻译.+/ ]; const isInvalidQuestion = invalidPatterns.some(pattern => pattern.test(questionText)); if (isInvalidQuestion) { spanElement.style.textDecoration = 'line-through'; status = "已回答"; questionsData[courseName] = questionsData[courseName] || {}; questionsData[courseName][questionText] = status; GM_setValue("questionsData", questionsData); } else if (status === "未回答") { spanElement.style.color = 'red'; } else if (status === "已回答") { spanElement.style.color = 'green'; } } } }); } function scrollToUnansweredQuestions() { console.log("滚动到未回答的问题"); const questionItems = document.querySelectorAll('.question-item'); const unansweredQuestions = Array.from(questionItems).filter(item => { const spanElement = item.querySelector('.question-content span'); const questionText = spanElement ? spanElement.title.trim() : ''; const courseNameElement = document.querySelector(".course-name"); const courseName = courseNameElement ? courseNameElement.innerText.trim() : ''; const status = GM_getValue("questionsData", {})[courseName]?.[questionText]; return status === "未回答"; }); if (unansweredQuestions.length > 0) { const firstUnansweredQuestionSpan = unansweredQuestions[0].querySelector('.question-content span'); if (firstUnansweredQuestionSpan) { firstUnansweredQuestionSpan.click(); console.log(`已点击第一个未回答的问题: ${firstUnansweredQuestionSpan.title}`); } else { console.log("未找到未回答问题的链接元素"); } } else { console.log("目前无未回答问题."); } } function startChecking() { setInterval(() => { console.log("检查问题和输入框是否存在..."); const questionElement = document.querySelector(".question-content span"); if (!questionElement) { console.log("未找到问题元素。"); return; } const questionText = questionElement.innerText.trim(); if (!questionText) { console.log("问题内容为空。"); return; } const inputElement = document.querySelector("textarea"); if (!inputElement) { console.log("未找到输入框元素。"); return; } addQuestionStatus(); const answerButton = document.querySelector("div.my-answer-btn.ZHIHUISHU_QZMD.tool-show span"); const modalExists = document.querySelector(".yidun_modal"); if (modalExists) { console.log("检测到二维码,需要手动验证。10秒后关闭窗口。"); GM_notification("检测到二维码,请手动验证。10秒后关闭窗口。", "安全验证"); setTimeout(() => { window.close(); }, 10000); return; } if (!answerButton || answerButton.textContent.trim() !== "我来回答") { const currentURL = window.location.href; const pattern = /^https:\/\/qah5\.zhihuishu\.com\/qa\.html#\/web\/questionDetail/; if (pattern.test(currentURL)) { console.log("未找到'我来回答'按钮且当前页面符合特定模式,关闭当前窗口。"); markQuestionAsAnswered(questionText); window.close(); } else { console.log("未找到'我来回答'按钮且当前页面不符合特定模式,停止回答逻辑。"); } return; } if (inputElement.value.trim() !== "") { console.log("输入框中已有数据,检查是否需要点击立即发布..."); startPublishChecking(); return; } console.log("找到问题并准备回答:", questionText); if (!requestLock) { requestLock = true; answerQuestion(questionText, inputElement); } else { console.log("请求进行中,跳过此次回答逻辑。"); } }, intervalTime); } function startPublishChecking() { console.log("检查是否需要点击立即发布按钮..."); const publishButton = document.evaluate( "/html/body/div[2]/div/div[5]/div/div/div[2]/div[1]/div[2]/div", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; const inputElement = document.querySelector('textarea'); if ( inputElement && inputElement.value.trim() !== "" && publishButton && publishButton.textContent.trim() === "立即发布" ) { console.log("找到立即发布按钮,自动点击..."); publishButton.click(); } } function answerQuestion(questionText, inputElement) { GM_xmlhttpRequest({ method: "POST", url: GM_getValue("API_URL", defaultAPIUrl), headers: { "Content-Type": "application/json", "Authorization": `Bearer ${GM_getValue("API_KEY", defaultAPIKey)}` }, data: JSON.stringify({ model: "gpt-3.5-turbo-0125", messages: [ { role: "user", content: `智慧树问题回答,你比如我提问:音乐剧与民歌在曲风、表现形式和情感表达上有何不同?如何在声乐演唱中体现这些差异?你只需要给出简介30字以内或者左右的回答即可。只需要输出回答,不需要其他,也不需要markdown格式,回答中严禁出现下面三个词连在一起:“学习通”。问题如下:${questionText}` } ], max_tokens: 1688, temperature: 0.5, stream: false }), onload: function (response) { requestLock = false; if (response.status === 200) { const jsonResponse = JSON.parse(response.responseText); const answer = jsonResponse.choices[0].message.content.trim(); console.log("获取到的回答:", answer); inputElement.value = answer; inputElement.dispatchEvent(inputEvent); } else { console.error("API 请求失败,状态码:", response.status); } }, onerror: function () { requestLock = false; console.error("API 请求出错。"); } }); } window.onload = () => { console.log("启动智慧树自动回答器..."); waitForAnswerButton(); initMutationObserver(); startCountdown(); }; })();