// ==UserScript== // @name 知到智慧树全自动刷问答平时分助手 // @version 1.0.4 // @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 createQuestionManagementModal() { const modal = document.createElement('div'); modal.className = 'question-management-modal'; modal.style = ` position: fixed; top: 50px; left: 5%; width: 90%; height: 80%; background: linear-gradient(145deg, #ffffff, #e6e6e6); box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.1); border-radius: 15px; z-index: 1000; padding: 20px; overflow: auto; `; const questionsData = GM_getValue("questionsData", {}); const lastViewedCourseIndex = GM_getValue("lastViewedCourseIndex", 0); const coursetabs = document.createElement('div'); coursetabs.style = ` display: flex; justify-content: center; margin-bottom: 15px; border-bottom: 1px solid #e0e0e0; `; const contentArea = document.createElement('div'); Object.keys(questionsData).forEach((courseName, index) => { const courseTab = document.createElement('div'); courseTab.textContent = courseName; courseTab.style = ` padding: 10px 15px; cursor: pointer; background: ${index === lastViewedCourseIndex ? 'linear-gradient(145deg, #4c88d6, #3b5998)' : 'transparent'}; color: ${index === lastViewedCourseIndex ? 'white' : 'black'}; border-radius: 5px 5px 0 0; margin-right: 5px; `; const courseTable = document.createElement('div'); courseTable.style = ` display: ${index === lastViewedCourseIndex ? 'block' : 'none'}; width: 100%; `; const table = document.createElement('table'); table.style = ` width: 100%; border-collapse: collapse; `; table.innerHTML = ` 题目 状态 操作 ${Object.entries(questionsData[courseName]).map(([question, status]) => ` ${question} ${status} `).join('')} `; table.querySelector('.filter-input').addEventListener('input', function() { const filterValue = this.value.toLowerCase(); table.querySelectorAll('tbody tr').forEach(row => { row.style.display = row.getAttribute('data-question').toLowerCase().includes(filterValue) ? '' : 'none'; }); }); table.addEventListener('click', function(event) { const target = event.target; const courseName = target.dataset.course; const question = target.dataset.question; const questionsData = GM_getValue("questionsData", {}); if (target.classList.contains('set-answered')) { questionsData[courseName][question] = '已回答'; GM_setValue("questionsData", questionsData); refreshQuestionManagementModal(); } if (target.classList.contains('set-unanswered')) { questionsData[courseName][question] = '未回答'; GM_setValue("questionsData", questionsData); refreshQuestionManagementModal(); } }); courseTable.appendChild(table); courseTab.addEventListener('click', () => { Array.from(coursetabs.children).forEach((tab, idx) => { tab.style.background = 'transparent'; tab.style.color = 'black'; contentArea.children[idx].style.display = 'none'; }); courseTab.style.background = 'linear-gradient(145deg, #4c88d6, #3b5998)'; courseTab.style.color = 'white'; courseTable.style.display = 'block'; GM_setValue("lastViewedCourseIndex", index); }); courseTab.setAttribute('data-course', courseName); coursetabs.appendChild(courseTab); contentArea.appendChild(courseTable); }); const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style = ` position: absolute; top: 10px; right: 10px; padding: 5px 10px; background: red; color: white; border: none; border-radius: 5px; `; closeButton.addEventListener('click', () => modal.remove()); modal.appendChild(coursetabs); modal.appendChild(contentArea); modal.appendChild(closeButton); document.body.appendChild(modal); } function refreshQuestionManagementModal() { const scrollTop = document.querySelector('.question-management-modal').scrollTop; const existingModal = document.querySelector('.question-management-modal'); if (existingModal) { existingModal.remove(); } createQuestionManagementModal(); document.querySelector('.question-management-modal').scrollTop = scrollTop; } 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 = `
倒计时 ℹ️ ${countdown} 秒
${!linkClicked ? `注册购买API(非作者运营勿找,点击后刷新不再显示)` : ''}
当前版本:1.0.4(最新:加载中...
`; const manageButton = document.createElement('button'); manageButton.textContent = '管理答题列表'; manageButton.style = ` margin-right: 10px; padding: 10px 20px; border: none; border-radius: 10px; background: linear-gradient(145deg, #4c88d6, #3b5998); box-shadow: 3px 3px 6px #2f4686, -3px -3px 6px #507bcf; color: white; font-weight: bold; cursor: pointer; `; manageButton.addEventListener('click', createQuestionManagementModal); const saveButtonContainer = countdownElement.querySelector('div[style*="text-align: right"]'); saveButtonContainer.insertBefore(manageButton, saveButtonContainer.firstChild); document.getElementById("apiKeyInput").value = GM_getValue("API_KEY", defaultAPIKey); document.getElementById("apiUrlInput").value = GM_getValue("API_URL", defaultAPIUrl); document.getElementById("countdownMinInput").value = GM_getValue("countdownMin", 20); document.getElementById("countdownMaxInput").value = GM_getValue("countdownMax", 30); 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("countdownMin", parseInt(document.getElementById("countdownMinInput").value)); GM_setValue("countdownMax", parseInt(document.getElementById("countdownMaxInput").value)); GM_setValue("model", document.getElementById("modelInput").value); alert('设置已保存'); }); checkForLatestVersion(); if (!linkClicked) { document.getElementById("purchaseLink").addEventListener('click', function(event) { event.preventDefault(); GM_setValue("linkClicked", true); window.open("https://api.v3.cm/register?aff=25L7", "_blank"); }); } 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仅供测试,没有多少量,请勿大量使用。请在试用脚本有效后立刻点击链接购买API。\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 checkForLatestVersion() { const lastChecked = GM_getValue("lastVersionCheck", 0); const currentTime = new Date().getTime(); const oneHourMillis = 60 * 60 * 1000; let retryAttempts = 0; const maxRetries = 5; const retryDelay = 1000; function requestVersion() { GM_xmlhttpRequest({ method: "GET", url: "https://update.greasyfork.icu/scripts/519662/%E7%9F%A5%E5%88%B0%E6%99%BA%E6%85%A7%E6%A0%91%E5%85%A8%E8%87%AA%E5%8A%A8%E5%88%B7%E9%97%AE%E7%AD%94%E5%B9%B3%E6%97%B6%E5%88%86%E5%8A%A9%E6%89%8B.meta.js", onload: function(response) { const match = response.responseText.match(/@version\s+([\d.]+)/); if (match) { const latestVersion = match[1]; GM_setValue("lastVersionCheck", currentTime); GM_setValue("lastCheckedVersion", latestVersion); document.getElementById("latestVersion").innerText = latestVersion; } else { if (retryAttempts < maxRetries) { retryAttempts++; setTimeout(requestVersion, retryDelay); } else { document.getElementById("latestVersion").innerText = "获取失败"; } } } }); } if (currentTime - lastChecked > oneHourMillis) { requestVersion(); } else { const lastCheckedVersion = GM_getValue("lastCheckedVersion", "未检测"); document.getElementById("latestVersion").innerText = lastCheckedVersion; } } function startCountdown(restartCount = null) { if (countdownInterval) { clearInterval(countdownInterval); } const minTime = GM_getValue("countdownMin", 20); const maxTime = GM_getValue("countdownMax", 30); let countdown = restartCount !== null ? restartCount : Math.floor(Math.random() * ((maxTime + 1) - minTime)) + minTime; GM_setValue("countdownTime", countdown); 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(); }; })();