// ==UserScript== // @name 长江雨课堂自测连续刷题(修复误跳过) // @name:zh-CN 长江雨课堂自测连续刷题(修复误跳过) // @namespace https://github.com/RTX9090 // @version 5.1 // @description Automatically solve self-test questions on Changjiang Yuketang, expand sidebar nodes, skip AI-generated questions, and auto‑advance to the next self‑test. // @description:zh-CN 自动解答长江雨课堂自测选择题,自动展开侧边栏所有折叠节点,遇到AI出题即跳过当前自测,完成所有自测后自动停止。全程无人值守,连续刷题。 // @author RTX9090 // @original-author aspen138 // @license MIT // @match *://changjiang.yuketang.cn/* // @grant GM_addStyle // @grant GM_notification // ==/UserScript== (function() { 'use strict'; const OPTIONS = ['A', 'B', 'C', 'D', 'E']; const SUBMIT_BTN_SELECTOR = '.submit-btn'; const NEXT_BTN_SELECTOR = '.to-next-btn'; const CHECK_DELAY = 1000; const NEXT_DELAY = 1500; let isRunning = false; let shouldStop = false; // ---------- 辅助函数 ---------- function getOptionLabel(value) { const radio = document.querySelector(`input[type="radio"][value="${value}"]`); return radio ? radio.closest('label[role="radio"]') : null; } function isOptionCorrect(value) { const label = getOptionLabel(value); return label && label.classList.contains('success'); } function isOptionWrong(value) { const label = getOptionLabel(value); return label && label.classList.contains('danger'); } function isCurrentQuestionSolved() { for (let opt of OPTIONS) if (isOptionCorrect(opt)) return true; return false; } function selectOption(value) { const label = getOptionLabel(value); if (!label) return false; label.click(); const radio = label.querySelector('input[type="radio"]'); if (radio) radio.dispatchEvent(new Event('change', { bubbles: true })); return true; } function clickSubmit() { const btn = document.querySelector(SUBMIT_BTN_SELECTOR); if (!btn) return false; btn.click(); return true; } function clickNext() { const btn = document.querySelector(NEXT_BTN_SELECTOR); if (!btn) return false; btn.click(); return true; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // ---------- AI 出题检测 ---------- function isAiGenerated() { if (document.querySelector('.problem-common__title.is-ai')) return true; const aiIcon = document.querySelector('.item-ai .ai-icon'); if (aiIcon && aiIcon.textContent.includes('AI出题')) return true; return false; } // ---------- 判断当前自测是否已完成(带等待确认) ---------- async function isCurrentSelfTestFinished() { // 如果已出现 AI 出题标记,直接视为完成 if (isAiGenerated()) { console.log('[脚本] 检测到 AI 出题标记'); return true; } // 等待题目区域加载选项 A(最多 8 秒) const startTime = Date.now(); while (Date.now() - startTime < 8000) { const radioA = document.querySelector('input[type="radio"][value="A"]'); if (radioA && !radioA.disabled) { // 题目存在且可用,未完成 return false; } // 如果不在自测页面(无高亮叶子节点),跳出等待 if (!document.querySelector('.leaf-item.is-active .leaf-item-tag')) break; await sleep(500); } // 超时或不在自测页,认为已完成 console.log('[脚本] 长时间未发现可用选项A,认为当前自测已完成'); return true; } // ---------- 展开侧边栏所有折叠节点 ---------- function expandAllNavNodes() { const collapsedIcons = document.querySelectorAll('.nav-item-node .expand-icon:not(.is-expanded)'); collapsedIcons.forEach(icon => { const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); icon.dispatchEvent(clickEvent); }); return sleep(300); } // ---------- 获取所有“自测”叶子节点 ---------- function getAllSelfTestItems() { const tags = document.querySelectorAll('.leaf-item-tag'); const items = []; tags.forEach(tag => { if (tag.textContent.trim() === '自测') { const leaf = tag.closest('.leaf-item'); if (leaf) items.push(leaf); } }); return items; } // ---------- 点击下一个自测知识点 ---------- async function goToNextSelfTest() { await expandAllNavNodes(); const allItems = getAllSelfTestItems(); if (allItems.length === 0) { console.log('[脚本] 未找到任何自测节点'); return false; } const currentIdx = allItems.findIndex(item => item.classList.contains('is-active')); if (currentIdx === -1) { console.log('[脚本] 未找到当前高亮自测,从第一个开始'); } const nextIdx = currentIdx === -1 ? 0 : currentIdx + 1; if (nextIdx >= allItems.length) { console.log('[脚本] 已是最后一个自测'); GM_notification({ text: '所有自测已完成!', timeout: 3000 }); return false; } const nextItem = allItems[nextIdx]; const title = nextItem.querySelector('.leaf-item-title')?.textContent.trim() || '未知'; console.log(`[脚本] 点击下一个自测:${title}`); nextItem.click(); // 等待题目加载(最多15秒) console.log('[脚本] 等待新题目加载...'); const startTime = Date.now(); while (Date.now() - startTime < 15000) { if (shouldStop) return false; const radioA = document.querySelector('input[type="radio"][value="A"]'); if (radioA && !radioA.disabled) return true; await sleep(500); } console.log('[脚本] 等待题目加载超时'); return false; } // ---------- 核心刷题逻辑 ---------- async function solveCurrentQuestion() { if (shouldStop) return false; if (isCurrentQuestionSolved()) { console.log('[脚本] 当前题目已正确,尝试下一题'); return true; } for (let i = 0; i < OPTIONS.length; i++) { if (shouldStop) return false; const opt = OPTIONS[i]; console.log(`[脚本] 尝试选项 ${opt} ...`); if (isOptionWrong(opt)) continue; if (!selectOption(opt)) continue; await sleep(300); if (!clickSubmit()) return false; await sleep(CHECK_DELAY); if (isCurrentQuestionSolved()) { console.log(`[脚本] ✅ 正确答案是 ${opt}`); return true; } else { console.log(`[脚本] ❌ 选项 ${opt} 错误,尝试下一个`); } } return false; } async function runAll() { console.log('[脚本] 开始连续刷题...'); let round = 0; while (true) { if (shouldStop) break; // 先展开所有节点 await expandAllNavNodes(); // 检查当前自测是否已完成(改用异步等待版,防止误判) if (await isCurrentSelfTestFinished()) { console.log('[脚本] 当前自测已完成,切换下一个'); const success = await goToNextSelfTest(); if (!success) break; round = 0; continue; } round++; console.log(`[脚本] ===== 第 ${round} 题 =====`); const solved = await solveCurrentQuestion(); if (!solved) { console.log('[脚本] 解答失败,尝试切换知识点'); const success = await goToNextSelfTest(); if (!success) break; round = 0; continue; } if (!clickNext()) { console.log('[脚本] 当前自测已无下一题,切换知识点'); const success = await goToNextSelfTest(); if (!success) break; round = 0; continue; } // 等待下一题加载 await sleep(NEXT_DELAY); let attempts = 0; while (!document.querySelector('input[type="radio"][value="A"]') && attempts < 20) { if (shouldStop) break; await sleep(500); attempts++; } } console.log('[脚本] 刷题结束'); } // ---------- UI 面板 ---------- function updatePanelButtons() { const btnStart = document.getElementById('btn-start'); const btnStop = document.getElementById('btn-stop'); const statusText = document.getElementById('status-text'); if (!btnStart || !btnStop || !statusText) return; if (isRunning) { btnStart.disabled = true; btnStop.disabled = false; statusText.textContent = '运行中...'; } else { btnStart.disabled = false; btnStop.disabled = true; statusText.textContent = '就绪'; } } function createPanel() { const panel = document.createElement('div'); panel.id = 'auto-solve-panel'; panel.innerHTML = `