// ==UserScript== // @name 智慧树刷课助手 // @namespace https://github.com/CXRunfree/Autovisor // @version 1.1.0 // @description 智慧树网课自动播放、自动切换下一集、跳过弹窗和弹题、自动静音、倍速播放、检测暂停续播、刷习惯分、支持智慧共享课和翻转课、考试自动答题 // @author 叶屿 // @license GPL3 // @antifeature payment 题库答题功能需要激活码(付费),视频播放等基础功能完全免费 // @match *://passport.zhihuishu.com/* // @match *://studyh5.zhihuishu.com/* // @match *://studyvideoh5.zhihuishu.com/* // @match *://hike.zhihuishu.com/* // @match *://www.zhihuishu.com/* // @match *://studyservice-api.zhihuishu.com/* // @match *://examh5.zhihuishu.com/* // @match *://onlineexamh5new.zhihuishu.com/* // @match *://studentexambaseh5.zhihuishu.com/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @connect qsy.iano.cn // @connect lyck6.cn // @connect * // @run-at document-idle // ==/UserScript== (function () { 'use strict'; /* * ========================================== * 免责声明 / Disclaimer * ========================================== * * 本脚本仅供个人学习和技术研究使用,请勿用于任何违反 * 学校规章制度、考试纪律或法律法规的行为。 * * 使用本脚本即表示您同意以下条款: * 1. 本脚本不鼓励、不支持任何形式的考试作弊行为 * 2. 使用者应自行承担使用本脚本产生的一切后果 * 3. 作者不对因使用本脚本而导致的任何直接或间接 * 损失(包括但不限于学业处分、账号封禁等)承担责任 * 4. 本脚本不保证题库答案的准确性和完整性 * 5. 请遵守《中华人民共和国教育法》及相关法律法规 * 6. 请遵守所在学校的学术诚信规范 * * 如您不同意以上条款,请立即停止使用并删除本脚本。 * * This script is for personal learning and technical * research purposes only. The author assumes no * responsibility for any consequences. * ========================================== */ // ===================== 配置区 ===================== const DEFAULT_CONFIG = { playbackSpeed: 1.5, // 播放倍速 (0.5 ~ 1.8) soundOff: true, // 是否静音 autoNext: true, // 自动切换下一集 autoAnswer: true, // 自动跳过弹题 autoPlay: true, // 自动播放 & 暂停检测 habitScore: true, // 刷习惯分(模拟鼠标移动) autoExam: false, // 考试自动答题(默认关闭,手动点击搜题) examAutoFill: false, // 考试自动填入答案(默认关闭,需手动确认) showPanel: true, // 显示悬浮面板 }; // 从存储读取或使用默认配置 function loadConfig() { const saved = GM_getValue('autovisor_config', null); if (saved) { return Object.assign({}, DEFAULT_CONFIG, saved); } return Object.assign({}, DEFAULT_CONFIG); } function saveConfig(cfg) { GM_setValue('autovisor_config', cfg); } let config = loadConfig(); // ===================== 日志系统 ===================== const LOG_PREFIX = '[智慧树助手]'; const log = { info: (msg) => console.log(`%c${LOG_PREFIX} ${msg}`, 'color:#409EFF;font-weight:bold;'), warn: (msg) => console.warn(`%c${LOG_PREFIX} ${msg}`, 'color:#E6A23C;font-weight:bold;'), error: (msg) => console.error(`%c${LOG_PREFIX} ${msg}`, 'color:#F56C6C;font-weight:bold;'), success: (msg) => console.log(`%c${LOG_PREFIX} ${msg}`, 'color:#67C23A;font-weight:bold;'), }; // ===================== 工具函数 ===================== function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function $(selector) { return document.querySelector(selector); } function $$(selector) { return document.querySelectorAll(selector); } function safeClick(selector) { const el = typeof selector === 'string' ? $(selector) : selector; if (el) { el.click(); return true; } return false; } function safeRemove(selector) { const el = $(selector); if (el) { el.remove(); return true; } return false; } // 判断页面类型 function isNewVersion() { return location.href.includes('fusioncourseh5'); } function isHikeClass() { return location.href.includes('hike.zhihuishu.com'); } function isStudyPage() { return location.href.includes('studyh5.zhihuishu.com') || location.href.includes('studyvideoh5.zhihuishu.com') || location.href.includes('hike.zhihuishu.com'); } function isExamPage() { const url = location.href; // 只匹配实际做题页面,不匹配作业/考试列表页 return url.includes('examh5.zhihuishu.com') || url.includes('onlineexamh5new.zhihuishu.com') || url.includes('studentexambaseh5.zhihuishu.com') || url.includes('/stuExamWeb.html') || url.includes('/webExamList/dohomework/') || url.includes('/webExamList/doexamination/') || // 通用: 页面内存在题目容器(延迟检测) (url.includes('zhihuishu.com') && !!document.querySelector('.examPaper_subject, .questionBox, .ques-detail')); } // ===================== 反检测模块 ===================== // 温和策略:只拦截弹窗DOM,不修改原型链,不拦截XHR function hookAntiCheat() { const KEYWORDS = ['检测到异常脚本', '关闭插件', '异常行为']; // 1. MutationObserver 监听弹窗插入 const target = document.body || document.documentElement; if (!target) return; const observer = new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.nodeType !== 1) continue; const text = (node.textContent || '').slice(0, 200); if (KEYWORDS.some(kw => text.includes(kw))) { node.style.display = 'none'; node.style.visibility = 'hidden'; node.style.opacity = '0'; node.style.pointerEvents = 'none'; try { node.remove(); } catch {} log.info('[反检测] 已拦截异常脚本弹窗'); } } } }); observer.observe(target, { childList: true, subtree: true }); // 2. 定时扫描关闭已有弹窗(只针对特定关键词的弹窗) setInterval(() => { document.querySelectorAll('.el-dialog, .el-message-box, [class*="modal"]').forEach(el => { const t = (el.textContent || '').slice(0, 300); if (KEYWORDS.some(kw => t.includes(kw))) { el.style.display = 'none'; try { el.remove(); } catch {} // 同时移除对应遮罩 const overlay = el.previousElementSibling; if (overlay && (overlay.classList.contains('el-overlay') || overlay.classList.contains('v-modal'))) { overlay.style.display = 'none'; try { overlay.remove(); } catch {} } log.info('[反检测] 已清除异常检测弹窗'); } }); }, 3000); log.info('[反检测] 反检测模块已激活(温和模式)'); } // ===================== 核心功能模块 ===================== // --- 获取视频元素 --- function getVideo() { return $('video'); } // --- 页面优化:关闭弹窗、移除多余元素 --- async function optimizePage() { await sleep(2000); // 关闭学习时长弹窗 safeClick('.iconfont.iconguanbi'); if (!isNewVersion() && !isHikeClass()) { // 自动夜间模式 (18:00 ~ 7:00) const hour = new Date().getHours(); if (hour >= 18 || hour < 7) { const darkBtn = $('.Patternbtn-div'); if (darkBtn) darkBtn.click(); } // 移除无关元素 safeRemove('.exploreTip'); safeRemove('.ai-helper-Index2'); safeRemove('.aiMsg.once'); } log.info('页面优化完成'); } // --- 覆盖视频pause方法,防止被网页暂停 --- function overridePause() { const video = getVideo(); if (video && !video._pauseOverridden) { const origPause = video.pause.bind(video); video.pause = () => { // 只允许用户主动暂停(如点击暂停按钮),忽略脚本检测触发的暂停 }; video._pauseOverridden = true; log.info('已覆盖视频 pause 方法'); } } // --- 静音控制 --- function setMute() { if (!config.soundOff) return; const video = getVideo(); if (video && video.volume !== 0) { video.volume = 0; const volumeBox = $('.volumeBox'); if (volumeBox) volumeBox.classList.add('volumeNone'); log.info('已静音'); } } // --- 倍速控制 --- function setSpeed() { const video = getVideo(); if (!video) return; const speed = Math.max(0.5, Math.min(config.playbackSpeed, 1.8)); if (video.playbackRate !== speed) { video.playbackRate = speed; const speedLabel = $('.speedBox span'); if (speedLabel) speedLabel.innerText = `X ${speed}`; log.info(`已设置倍速: ${speed}x`); } } // --- 检测暂停并续播 --- async function checkAndPlay() { if (!config.autoPlay) return; const video = getVideo(); if (video && video.paused) { try { await video.play(); log.info('检测到视频暂停,已恢复播放'); } catch (e) { // 自动播放可能被浏览器策略阻止 log.warn('自动播放被阻止,请手动点击播放'); } } } // --- 跳过弹题 --- async function skipQuestions() { if (!config.autoAnswer) return; if (isHikeClass()) return; // 翻转课不支持自动答题 try { const quesContainer = $('.el-scrollbar__view'); if (!quesContainer) return; const questions = quesContainer.querySelectorAll('.number'); if (questions.length === 0) return; log.info(`检测到 ${questions.length} 道弹题`); for (const ques of questions) { ques.click(); await sleep(300); // 如果没有已选答案,随机选前两个 if (!$('.answer')) { const choices = $$('.topic-item'); for (let i = 0; i < Math.min(2, choices.length); i++) { choices[i].click(); await sleep(150); } } } // 按 ESC 关闭弹题对话框 await sleep(200); const dialog = $('.el-dialog'); if (dialog) { document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 27 })); log.success('弹题已跳过'); } } catch (e) { log.warn('跳过弹题时出错: ' + e.message); } } // --- 关闭其他弹窗 --- function closePopups() { // 新版共享课弹窗 if (isNewVersion()) { const dialog = $('.el-dialog'); if (dialog) { document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 27 })); } } else if (!isHikeClass()) { // 旧版弹窗 safeClick('.el-message-box__headerbtn'); } // 通用弹窗关闭 safeClick('.iconfont.iconguanbi'); } // --- 检测人机验证 --- function checkVerify() { const verifyModal = $('.yidun_modal__title'); if (verifyModal) { log.warn('⚠️ 检测到安全验证,请手动完成验证!'); updatePanelStatus('⚠️ 需要手动验证!'); return true; } return false; } // --- 习惯分:模拟鼠标在视频区域移动 --- function simulateMouseMove() { if (!config.habitScore) return; const videoArea = $('.videoArea') || getVideo(); if (!videoArea) return; const rect = videoArea.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return; const x = rect.left + rect.width / 2 + (Math.random() - 0.5) * 20; const y = rect.top + rect.height / 2 + (Math.random() - 0.5) * 20; videoArea.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: x, clientY: y, })); } // --- 获取当前学习进度 --- function getCourseProgress() { if (isHikeClass()) { const active = $('.file-item.active'); if (!active) return '0%'; const rate = active.querySelector('.rate'); if (rate) return rate.textContent || '0%'; const finish = active.querySelector('.icon-finish'); return finish ? '100%' : '0%'; } else { const curPlay = $('.current_play'); if (!curPlay) return '0%'; const progress = curPlay.querySelector('.progress-num'); if (progress) return progress.textContent || '0%'; if (isNewVersion()) { const pn = curPlay.querySelector('.progress-num'); if (pn && pn.textContent === '100%') return '100%'; } else { const finish = curPlay.querySelector('.time_icofinish'); if (finish) return '100%'; } return '0%'; } } // --- 获取未完成的课程列表 --- function getUnfinishedLessons() { if (isHikeClass()) { const all = $$('.file-item'); const unfinished = []; all.forEach(item => { const done = item.querySelector('.icon-finish'); if (!done) unfinished.push(item); }); return unfinished.length > 0 ? unfinished : Array.from(all); } else { const all = $$('.clearfix.video'); const unfinished = []; all.forEach(item => { if (isNewVersion()) { const pn = item.querySelector('.progress-num'); if (!pn || pn.textContent !== '100%') unfinished.push(item); } else { const done = item.querySelector('.time_icofinish'); if (!done) unfinished.push(item); } }); return unfinished.length > 0 ? unfinished : Array.from(all); } } // --- 切换到下一集 --- async function switchToNext() { if (!config.autoNext) return; const progress = getCourseProgress(); if (progress !== '100%') return; log.success('当前课程已完成,正在切换下一集...'); updatePanelStatus('🔄 正在切换下一集...'); const lessons = getUnfinishedLessons(); if (lessons.length === 0) { log.success('🎉 所有课程已学习完毕!'); updatePanelStatus('🎉 全部完成!'); stopAllTasks(); return; } // 找到当前课程的下一个 if (isHikeClass()) { const active = $('.file-item.active'); const allItems = Array.from($$('.file-item')); const curIdx = allItems.indexOf(active); const nextIdx = curIdx + 1; if (nextIdx < allItems.length) { allItems[nextIdx].click(); log.info('已切换到下一集'); await sleep(2000); afterLessonSwitch(); } else { log.success('🎉 所有课程已学习完毕!'); updatePanelStatus('🎉 全部完成!'); stopAllTasks(); } } else { const curPlay = $('.current_play'); const allItems = Array.from($$('.clearfix.video')); const curIdx = allItems.indexOf(curPlay); const nextIdx = curIdx + 1; if (nextIdx < allItems.length) { allItems[nextIdx].click(); log.info('已切换到下一集'); await sleep(2000); afterLessonSwitch(); } else { log.success('🎉 所有课程已学习完毕!'); updatePanelStatus('🎉 全部完成!'); stopAllTasks(); } } } // 切换课程后重新初始化 function afterLessonSwitch() { overridePause(); setMute(); setSpeed(); const title = getLessonTitle(); log.info(`正在学习: ${title}`); updatePanelStatus(`▶️ ${title}`); } // 获取当前课程名称 function getLessonTitle() { if (isHikeClass()) { const span = $('.file-item.active span[title]'); return span ? span.getAttribute('title') : '未知课程'; } else { const el = $('#lessonOrder'); return el ? el.getAttribute('title') : '未知课程'; } } // ===================== 定时任务管理 ===================== let timers = []; function addTimer(fn, interval) { const id = setInterval(fn, interval); timers.push(id); return id; } function stopAllTasks() { timers.forEach(id => clearInterval(id)); timers = []; log.info('所有任务已停止'); } // ===================== 悬浮控制面板 ===================== let panelStatusEl = null; let panelProgressEl = null; function createPanel() { if (!config.showPanel) return; if ($('#autovisor-panel')) return; const panel = document.createElement('div'); panel.id = 'autovisor-panel'; panel.innerHTML = `
⚡ 智慧树刷课助手
倍速
${config.playbackSpeed}x
静音
自动播放
自动下一集
跳过弹题
习惯分
进度 --
🚀 脚本运行中...
`; document.body.appendChild(panel); panelStatusEl = $('#av-status'); panelProgressEl = $('#av-progress'); // 收起/展开 $('#av-collapse').addEventListener('click', () => { panel.classList.toggle('collapsed'); $('#av-collapse').textContent = panel.classList.contains('collapsed') ? '▶' : '▼'; }); // 倍速控制 $('#av-speed-down').addEventListener('click', () => { config.playbackSpeed = Math.max(0.5, +(config.playbackSpeed - 0.25).toFixed(2)); saveConfig(config); setSpeed(); $('#av-speed-val').textContent = config.playbackSpeed + 'x'; }); $('#av-speed-up').addEventListener('click', () => { config.playbackSpeed = Math.min(1.8, +(config.playbackSpeed + 0.25).toFixed(2)); saveConfig(config); setSpeed(); $('#av-speed-val').textContent = config.playbackSpeed + 'x'; }); // 静音切换 $('#av-mute-btn').addEventListener('click', () => { config.soundOff = !config.soundOff; saveConfig(config); const video = getVideo(); if (video) { video.volume = config.soundOff ? 0 : 0.5; const volumeBox = $('.volumeBox'); if (volumeBox) { if (config.soundOff) volumeBox.classList.add('volumeNone'); else volumeBox.classList.remove('volumeNone'); } } $('#av-mute-btn').textContent = config.soundOff ? '🔇 已静音' : '🔊 有声音'; }); // 开关切换 function toggleBtn(key, btnId, label) { $(btnId).addEventListener('click', () => { config[key] = !config[key]; saveConfig(config); $(btnId).textContent = config[key] ? '✅ 开启' : '❌ 关闭'; }); } toggleBtn('autoPlay', '#av-autoplay-btn'); toggleBtn('autoNext', '#av-autonext-btn'); toggleBtn('autoAnswer', '#av-autoanswer-btn'); toggleBtn('habitScore', '#av-habit-btn'); // 拖拽 makeDraggable(panel, $('#av-drag-handle')); } function makeDraggable(el, handle) { let offsetX = 0, offsetY = 0, isDragging = false; handle.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - el.getBoundingClientRect().left; offsetY = e.clientY - el.getBoundingClientRect().top; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; el.style.left = (e.clientX - offsetX) + 'px'; el.style.top = (e.clientY - offsetY) + 'px'; el.style.right = 'auto'; }); document.addEventListener('mouseup', () => { isDragging = false; }); } function updatePanelStatus(text) { if (panelStatusEl) panelStatusEl.textContent = text; } function updatePanelProgress(text) { if (panelProgressEl) panelProgressEl.textContent = text; } // ===================== 考试答题模块 ===================== // 对接 qsy.iano.cn 中转API + lyck6.cn 免费题库 // 借鉴万能脚本的选择器与答案匹配逻辑 // --- 设备ID --- function getDeviceId() { let id = GM_getValue('av_device_id', ''); if (!id) { id = 'av_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); GM_setValue('av_device_id', id); } return id; } // --- 文本格式化 (借鉴万能脚本 formatString) --- function formatString(src) { src = String(src); // HTML 实体解码 if (!src.includes('img') && !src.includes('iframe')) { const tmp = document.createElement('div'); tmp.innerHTML = src; src = tmp.textContent || tmp.innerText || ''; } // 全角转半角 src = src.replace(/[\uff01-\uff5e]/g, s => String.fromCharCode(s.charCodeAt(0) - 65248)); return src.replace(/\s+/g, ' ').replace(/[""]/g, '"').replace(/['']/g, "'") .replace(/。/g, '.').replace(/[,.?:!;]$/, '').trim(); } // 判断题匹配 (借鉴万能脚本) function isTrue(str) { return Boolean(String(str).match(/(^|,)(正确|是|对|√|T|ri|true|A)(,|$)/)); } function isFalse(str) { return Boolean(String(str).match(/(^|,)(错误|否|错|×|F|不是|wr|false|B)(,|$)/)); } function isPlainAnswer(answer) { if (!answer || answer.length > 8 || !/[A-Z]/.test(answer)) return false; let min = 0; for (let i = 0; i < answer.length; i++) { if (answer.charCodeAt(i) < min) return false; min = answer.charCodeAt(i); } return true; } // 答案文本相似度 (简化版) function answerSimilar(answer, options) { return options.map(opt => { const o = opt.replace(/\s/g, ''); const a = answer.replace(/\s/g, ''); if (o === a) return 100; if (o.includes(a) || a.includes(o)) return 85; let match = 0; const shorter = a.length < o.length ? a : o; const longer = a.length < o.length ? o : a; for (let i = 0; i < shorter.length; i++) { if (longer.includes(shorter[i])) match++; } return Math.round(match / longer.length * 100); }); } // --- 题型检测 (万能脚本 getQuestionType) --- function getQuestionType(typeText) { const t = String(typeText).trim(); if (/单选|单项选择|radio/i.test(t)) return 0; if (/多选|多项选择|checkbox|不定项/i.test(t)) return 1; if (/填空/i.test(t)) return 2; if (/判断|是非|对错/i.test(t)) return 3; if (/简答|问答|主观/i.test(t)) return 4; if (/名词解释/i.test(t)) return 5; if (/论述/i.test(t)) return 6; if (/计算/i.test(t)) return 7; if (/阅读理解|完型填空|听力/i.test(t)) return 8; return 4; // 默认 } const TYPE_NAMES = { 0: '单选', 1: '多选', 2: '填空', 3: '判断', 4: '简答', 5: '名词解释', 6: '论述', 7: '计算', 8: '其他' }; // --- 题目提取 (借鉴万能脚本的智慧树选择器) --- function extractExamQuestions() { const questions = []; // 按优先级尝试不同页面版本的选择器 const layouts = [ { // 智慧树作业/考试 (examPaper) root: '.examPaper_subject', question: '.subject_describe div, .smallStem_describe p', options: '.subject_node .nodeLab .node_detail', $options: '.subject_node .nodeLab .node_detail', type: '.subject_type span:first-child', checked: '.onChecked', }, { // 智慧树学分课作业 root: '.questionBox', question: '.questionContent', options: '.optionUl label .el-radio__label, .optionUl label .el-checkbox__label', $options: '.optionUl label', type: '.questionTit', checked: '.is-checked', }, { // 智慧树学分课考试 root: '.ques-detail', question: '.questionName .centent-pre', options: '.radio-view li .preStyle, .checkbox-views label .preStyle', $options: '.radio-view li, .checkbox-views label', type: '.letterSortNum', checked: '.is-checked', }, { // 通用回退 root: 'div[class*="subject"], div[class*="question"], div[class*="exam"]', question: '.subject_describe, .question-title, .ques-title, .topic-title, .stem, h3', options: '.subject_node, .option-item, .topic-item, label', $options: '.subject_node, .option-item, .topic-item, label', type: '.subject_type, .questionTit, .letterSortNum', checked: '.onChecked, .is-checked', } ]; let activeLayout = null; let containers = []; for (const layout of layouts) { containers = $$(layout.root); if (containers.length > 0) { activeLayout = layout; break; } } if (!activeLayout || containers.length === 0) { log.warn('[考试] 未检测到题目容器'); return []; } log.info(`[考试] 使用选择器: ${activeLayout.root}, 检测到 ${containers.length} 题`); // 跳过题型/分值标题的正则 const SKIP_HEADER = /^[\d\s\.\))、]*[【\[((]*[单多判填简论计][选断空答述算]|^\d+[\s\.、)\)]*\(?\d+分\)?$|^第?\d+题|^【.*题】/; containers.forEach((container, index) => { // 题目文本 — 跳过题型/分值标题,找到真正的问题 let questionText = ''; const qEls = container.querySelectorAll(activeLayout.question); for (const qEl of qEls) { const t = qEl.textContent.trim(); // 跳过:太短、题型标记如"1.【单选题】(2分)"、纯选项字母 if (t.length <= 5) continue; if (SKIP_HEADER.test(t) && t.length < 30) continue; if (/^[A-G][\..\s、]/.test(t)) continue; questionText = formatString(t); break; } // 备用:直接在container里找长文本段落 if (!questionText) { const ps = container.querySelectorAll('p, span, div'); for (const p of ps) { const t = p.textContent.trim(); if (t.length <= 8) continue; if (SKIP_HEADER.test(t) && t.length < 30) continue; if (/^[A-G][\..\s、]/.test(t)) continue; questionText = formatString(t); break; } } // 清理残留的题号和分值 if (questionText) { questionText = questionText .replace(/^[\d\s\.\))、]+/, '') .replace(/\(\s*\d+分\s*\)/g, '') .replace(/^[【\[((]*[单多判填简论计][选断空答述算][题】\]))]*\s*/g, '') .trim(); } if (!questionText || questionText.length < 4) return; // 题型 const typeEl = container.querySelector(activeLayout.type); const typeCode = typeEl ? getQuestionType(typeEl.textContent) : 4; // 选项文本 & 可点击元素 const optTexts = []; const $optEls = []; const optTextEls = container.querySelectorAll(activeLayout.options); const optClickEls = container.querySelectorAll(activeLayout.$options); const useClickEls = optClickEls.length >= optTextEls.length ? optClickEls : optTextEls; for (let i = 0; i < useClickEls.length; i++) { const textSrc = optTextEls[i] || useClickEls[i]; const raw = formatString(textSrc.textContent).replace(/^[A-Za-z][.\s、.]+/, '').trim(); optTexts.push(raw); $optEls.push(useClickEls[i]); } // 已选检测 const isChecked = (el) => { if (!activeLayout.checked) return false; return activeLayout.checked.split(',').some(sel => el.matches(sel.trim()) || el.querySelector(sel.trim())); }; questions.push({ index, el: container, text: questionText, type: typeCode, options: optTexts, optionEls: $optEls, isChecked, }); }); return questions; } // --- API调用 --- function gmRequest(opts) { return new Promise(resolve => { const timer = setTimeout(() => resolve(null), opts.timeout || 12000); try { GM_xmlhttpRequest({ ...opts, onload: (res) => { clearTimeout(timer); resolve(res); }, onerror: () => { clearTimeout(timer); resolve(null); }, ontimeout: () => { clearTimeout(timer); resolve(null); }, }); } catch { clearTimeout(timer); resolve(null); } }); } // 通用答案数组解析 function parseAnswersFromResult(resultObj, options) { if (!resultObj) return []; const raw = resultObj.answers || resultObj.answer || []; const success = resultObj.success !== false; let answers = []; if (typeof raw === 'string' && raw.trim()) { // 单个字符串答案 answers = [raw.trim()]; } else if (Array.isArray(raw) && raw.length > 0) { if (Array.isArray(raw[0])) { // 嵌套数组 [[答案1, 答案2]] answers = raw[0].map(a => String(a)); } else if (typeof raw[0] === 'number') { // 索引数组 [0, 2] => 对应选项 answers = raw.map(idx => options[idx] || String.fromCharCode(65 + idx)); } else { answers = raw.map(a => String(a)); } } return answers.filter(a => a && a.trim()); } // 你的中转API (qsy.iano.cn) async function askPaidAPI(question, options, type) { const deviceId = getDeviceId(); log.info(`[考试] 发送题目: "${question.slice(0, 40)}..." 选项:${options.length}个`); const res = await gmRequest({ method: 'POST', url: 'https://qsy.iano.cn/index.php?s=/api/question_bank/answer', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ device_id: deviceId, question: question, options: options, type: type, location: '智慧树' }), timeout: 12000, }); if (!res) { log.warn('[考试] 付费API无响应'); return null; } if (res.status !== 200) { log.warn(`[考试] 付费API HTTP ${res.status}`); return null; } try { const json = JSON.parse(res.responseText); log.info(`[考试] 付费API响应: code=${json.code}, msg=${json.msg || ''}`); if (json.code === 1 && json.data) { const result = json.data.result || json.data; const remaining = json.data.remaining_credits || 0; const answers = parseAnswersFromResult(result, options); if (answers.length > 0) { return { answers, remaining, source: '付费题库' }; } log.info('[考试] 付费API返回结果但无有效答案'); } if (json.code === 0) { log.warn(`[考试] 付费API: ${json.msg || '查询失败'}`); } } catch (e) { log.warn('[考试] 付费API解析错误: ' + e.message); } return null; } // 免费题库 (lyck6.cn, 与万能脚本同源) async function askFreeAPI(question, options, type) { const res = await gmRequest({ method: 'POST', url: 'http://lyck6.cn/scriptService/api/autoFreeAnswer', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ question: question, options: options, type: type, location: location.href }), timeout: 15000, }); if (!res) { log.warn('[考试] 免费API无响应'); return null; } if (res.status !== 200) { log.warn(`[考试] 免费API HTTP ${res.status}`); return null; } try { const json = JSON.parse(res.responseText); const result = json.result || json.data || json; const answers = parseAnswersFromResult(result, options); if (answers.length > 0) { return { answers, remaining: -1, source: '免费题库' }; } } catch (e) { log.warn('[考试] 免费API解析错误: ' + e.message); } return null; } // --- 当前题库模式: 'free' 或 'paid' --- let examMode = GM_getValue('av_exam_mode', 'free'); function setExamMode(mode) { examMode = mode; GM_setValue('av_exam_mode', mode); } // --- 免费验证码相关 --- function getVerifyState() { return GM_getValue('av_verify_state', { validUntil: 0 }); } function setVerifyState(state) { GM_setValue('av_verify_state', state); } function isVerifyValid() { const state = getVerifyState(); return state.validUntil && state.validUntil > Date.now() / 1000; } async function verifyCode(code) { const res = await gmRequest({ method: 'POST', url: 'https://qsy.iano.cn/index.php?s=/api/code/verify', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `code=${encodeURIComponent(code)}`, timeout: 10000, }); if (!res || res.status !== 200) return { ok: false, msg: '网络错误' }; try { const json = JSON.parse(res.responseText); if (json.code === 1 && json.data) { const validUntil = json.data.valid_until || (Date.now() / 1000 + 86400); const validStr = json.data.valid_until_str || new Date(validUntil * 1000).toLocaleString(); setVerifyState({ validUntil }); return { ok: true, msg: `验证成功,有效期至 ${validStr}` }; } return { ok: false, msg: json.msg || '验证码无效或已过期' }; } catch { return { ok: false, msg: '解析错误' }; } } // 综合搜索: 根据模式选择题库 async function searchAnswer(question, options, type) { const cleanQ = formatString(question); if (!cleanQ || cleanQ.length < 4) return null; const cleanOpts = (options || []).map(o => formatString(o)); if (examMode === 'paid') { // 付费模式 log.info('[考试] 正在查询付费题库...'); const paidResult = await askPaidAPI(cleanQ, cleanOpts, type); if (paidResult && paidResult.answers.length > 0) { log.success(`[考试] 付费题库找到答案 (余${paidResult.remaining}积分)`); updateCreditsDisplay(paidResult.remaining); return paidResult; } } else { // 免费模式 if (!isVerifyValid()) { log.warn('[考试] 免费验证码未验证或已过期,请先验证'); return null; } log.info('[考试] 正在查询免费题库...'); const freeResult = await askFreeAPI(cleanQ, cleanOpts, type); if (freeResult && freeResult.answers.length > 0) { log.success('[考试] 免费题库找到答案'); return freeResult; } } return null; } // --- 智能答案匹配 & 填入 (借鉴万能脚本) --- function matchAndFill(q, result) { if (!result || !result.answers || result.answers.length === 0) return false; const answers = result.answers; let filled = false; if (q.type === 3) { // 判断题 const ansJoined = answers.join(','); for (let i = 0; i < q.optionEls.length; i++) { const shouldClick = (isTrue(ansJoined) && isFalse(q.options[i])) ? false : (isTrue(ansJoined) && !isFalse(q.options[i])) ? true : (isFalse(ansJoined) && isTrue(q.options[i])) ? false : (isFalse(ansJoined) && !isTrue(q.options[i])) ? true : false; // 更简单的逻辑: 如果答案为"对",点包含"对/正确/√"的选项 const optText = q.options[i] || ''; let match = false; if (isTrue(ansJoined)) match = isTrue(optText) || /对|正确|√|是|true/i.test(optText); else if (isFalse(ansJoined)) match = isFalse(optText) || /错|错误|×|否|false/i.test(optText); if (match && !q.isChecked(q.optionEls[i])) { q.optionEls[i].click(); filled = true; } } } else if (q.type === 2 || q.type === 4 || q.type === 5 || q.type === 6 || q.type === 7) { // 填空题/简答/论述等: 填入 textarea/input const inputs = q.el.querySelectorAll('input[type="text"], textarea'); const editorNames = q.el.querySelectorAll('textarea[name]'); if (editorNames.length > 0) { // 尝试 UEditor editorNames.forEach((ta, i) => { const val = answers[i] || answers[0] || ''; try { if (typeof UE !== 'undefined' && UE.getEditor) { UE.getEditor(ta.getAttribute('name')).setContent(val); filled = true; } } catch { } }); } if (!filled && inputs.length > 0) { inputs.forEach((inp, i) => { const val = answers[i] || answers[0] || ''; inp.value = val.trim(); inp.dispatchEvent(new Event('input', { bubbles: true })); inp.dispatchEvent(new Event('change', { bubbles: true })); }); filled = inputs.length > 0; } } else { // 单选(0)/多选(1)/其他选择题 const beautifulOptions = q.options.map(o => formatString(o).toLowerCase().replace(/\s/g, '')); let targetIndices = new Set(); for (const ans of answers) { // 1. 字母匹配 (A/B/C...) if (ans.length === 1 && isPlainAnswer(ans.toUpperCase())) { targetIndices.add(ans.toUpperCase().charCodeAt(0) - 65); continue; } // 2. 精确文本匹配 const val = formatString(ans).toLowerCase().replace(/\s/g, ''); const exactIdx = beautifulOptions.indexOf(val); if (exactIdx >= 0) { targetIndices.add(exactIdx); continue; } // 3. 模糊匹配 if (val.length >= 5) { const ratings = answerSimilar(val, beautifulOptions); const maxScore = Math.max(...ratings); if (maxScore > 65) { targetIndices.add(ratings.indexOf(maxScore)); } } } for (const idx of targetIndices) { if (idx >= 0 && idx < q.optionEls.length && !q.isChecked(q.optionEls[idx])) { q.optionEls[idx].click(); filled = true; } } } return filled; } // --- 积分管理 --- function updateCreditsDisplay(credits) { const el = $('#ae-credits'); if (el) el.textContent = credits >= 0 ? credits : '--'; } async function queryCredits() { const res = await gmRequest({ method: 'GET', url: `https://qsy.iano.cn/index.php?s=/api/question_bank/credits&device_id=${encodeURIComponent(getDeviceId())}`, timeout: 8000, }); if (!res || res.status !== 200) return 0; try { const json = JSON.parse(res.responseText); if (json.code === 1 && json.data) return json.data.remaining_credits || 0; } catch { } return 0; } async function activateCode(code) { const res = await gmRequest({ method: 'POST', url: 'https://qsy.iano.cn/index.php?s=/api/question_bank/activate', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ code, device_id: getDeviceId() }), timeout: 10000, }); if (!res || res.status !== 200) return { ok: false, msg: '网络错误' }; try { const json = JSON.parse(res.responseText); return { ok: json.code === 1, msg: json.msg || '', data: json.data }; } catch { return { ok: false, msg: '解析错误' }; } } // --- 考试面板 UI --- function createExamPanel() { if ($('#av-exam-panel')) return; const panel = document.createElement('div'); panel.id = 'av-exam-panel'; panel.innerHTML = `
📝 考试助手
扫码获取验证码
📱 扫码观看广告获取验证码
验证后免费使用24小时
0 已找到 0 未找到 0 总题数
⚠️ 仅供学习交流,请勿用于违规行为
使用者自行承担一切后果
`; document.body.appendChild(panel); // 收起/展开 $('#ae-collapse').addEventListener('click', () => { panel.classList.toggle('collapsed'); $('#ae-collapse').textContent = panel.classList.contains('collapsed') ? '▶' : '▼'; }); makeDraggable(panel, $('#ae-drag-handle')); // --- 模式切换 --- function switchModeUI(mode) { const freeTab = $('#ae-mode-free'); const paidTab = $('#ae-mode-paid'); const freeArea = $('#ae-area-free'); const paidArea = $('#ae-area-paid'); if (mode === 'paid') { freeTab.style.background = 'transparent'; freeTab.style.color = '#94a3b8'; paidTab.style.background = 'rgba(96,165,250,0.15)'; paidTab.style.color = '#60a5fa'; freeArea.style.display = 'none'; paidArea.style.display = ''; setExamMode('paid'); } else { paidTab.style.background = 'transparent'; paidTab.style.color = '#94a3b8'; freeTab.style.background = 'rgba(52,211,153,0.15)'; freeTab.style.color = '#34d399'; paidArea.style.display = 'none'; freeArea.style.display = ''; setExamMode('free'); } } $('#ae-mode-free').addEventListener('click', () => switchModeUI('free')); $('#ae-mode-paid').addEventListener('click', () => switchModeUI('paid')); // 恢复上次模式 switchModeUI(examMode); // --- 免费验证码 --- // 恢复验证状态 if (isVerifyValid()) { const state = getVerifyState(); const expStr = new Date(state.validUntil * 1000).toLocaleString(); $('#ae-verify-msg').textContent = `✅ 已验证,有效期至 ${expStr}`; $('#ae-verify-msg').style.color = '#34d399'; $('#ae-verify-code').disabled = true; $('#ae-verify-btn').disabled = true; } $('#ae-verify-btn').addEventListener('click', async () => { const code = ($('#ae-verify-code').value || '').trim(); if (!code || code.length !== 4) { $('#ae-verify-msg').textContent = '请输入4位验证码'; $('#ae-verify-msg').style.color = '#f87171'; return; } $('#ae-verify-btn').disabled = true; $('#ae-verify-btn').textContent = '验证中...'; const result = await verifyCode(code); $('#ae-verify-msg').textContent = result.ok ? `✅ ${result.msg}` : `❌ ${result.msg}`; $('#ae-verify-msg').style.color = result.ok ? '#34d399' : '#f87171'; if (result.ok) { $('#ae-verify-code').disabled = true; } else { $('#ae-verify-btn').disabled = false; } $('#ae-verify-btn').textContent = '验证'; }); // 回车验证 $('#ae-verify-code').addEventListener('keypress', (e) => { if (e.key === 'Enter') $('#ae-verify-btn').click(); }); // --- 付费激活码 --- $('#ae-activate-btn').addEventListener('click', async () => { const code = ($('#ae-code-input').value || '').trim(); if (!code) return; $('#ae-activate-msg').textContent = '激活中...'; const result = await activateCode(code); $('#ae-activate-msg').textContent = result.msg; $('#ae-activate-msg').style.color = result.ok ? '#34d399' : '#f87171'; if (result.ok) { const c = await queryCredits(); updateCreditsDisplay(c); } }); // --- 搜题/填入/停止 --- $('#ae-search-all').addEventListener('click', () => searchAllQuestions()); $('#ae-fill-all').addEventListener('click', () => fillAllAnswers()); $('#ae-stop').addEventListener('click', () => { isSearching = false; log.info('[考试] 已停止搜题'); }); // 付费模式查询积分 if (examMode === 'paid') { queryCredits().then(c => updateCreditsDisplay(c)); } } // --- 搜题与填入逻辑 --- let examResults = new Map(); let isSearching = false; async function searchAllQuestions() { if (isSearching) { log.warn('[考试] 正在搜题中...'); return; } isSearching = true; const questions = extractExamQuestions(); const listEl = $('#ae-question-list'); if (!listEl) { isSearching = false; return; } listEl.innerHTML = ''; examResults.clear(); $('#ae-total').textContent = questions.length; $('#ae-found').textContent = '0'; $('#ae-notfound').textContent = '0'; log.info(`[考试] 共 ${questions.length} 道题目,开始搜题...`); let found = 0, notFound = 0; for (let i = 0; i < questions.length; i++) { if (!isSearching) break; const q = questions[i]; const itemDiv = document.createElement('div'); itemDiv.className = 'ae-q-item searching'; itemDiv.id = `ae-q-${i}`; itemDiv.innerHTML = `
第${i + 1}题 [${TYPE_NAMES[q.type] || '未知'}]
${q.text}
☕ 搜索中...
`; listEl.appendChild(itemDiv); const result = await searchAnswer(q.text, q.options, q.type); const answerDiv = itemDiv.querySelector('.ae-q-answer'); const sourceDiv = itemDiv.querySelector('.ae-q-source'); if (result && result.answers.length > 0) { answerDiv.textContent = '✅ ' + result.answers.join(' | '); sourceDiv.textContent = '来源: ' + result.source; itemDiv.className = 'ae-q-item found'; found++; examResults.set(i, { question: q, result }); } else { answerDiv.textContent = '❌ 未找到答案'; answerDiv.className = 'ae-q-answer empty'; itemDiv.className = 'ae-q-item not-found'; notFound++; examResults.set(i, { question: q, result: null }); } $('#ae-found').textContent = found; $('#ae-notfound').textContent = notFound; await sleep(600); } // 绑定单题按钮 listEl.querySelectorAll('.ae-q-btn').forEach(btn => { btn.addEventListener('click', async (e) => { const action = e.target.dataset.action; const idx = parseInt(e.target.dataset.idx); if (action === 'retry') { const q = questions[idx]; const itemDiv = $(`#ae-q-${idx}`); if (!itemDiv) return; itemDiv.className = 'ae-q-item searching'; itemDiv.querySelector('.ae-q-answer').textContent = '☕ 重新搜索中...'; const result = await searchAnswer(q.text, q.options, q.type); const ad = itemDiv.querySelector('.ae-q-answer'); const sd = itemDiv.querySelector('.ae-q-source'); if (result && result.answers.length > 0) { ad.textContent = '✅ ' + result.answers.join(' | '); sd.textContent = '来源: ' + result.source; itemDiv.className = 'ae-q-item found'; examResults.set(idx, { question: q, result }); } else { ad.textContent = '❌ 未找到答案'; ad.className = 'ae-q-answer empty'; itemDiv.className = 'ae-q-item not-found'; } } else if (action === 'fill') { const data = examResults.get(idx); if (data && data.result) { const ok = matchAndFill(data.question, data.result); log.info(`[考试] 第${idx + 1}题 填入${ok ? '成功' : '失败'}`); } } }); }); isSearching = false; log.success(`[考试] 搜题完成! 找到:${found} 未找到:${notFound}`); if (config.examAutoFill && found > 0) { await sleep(1000); fillAllAnswers(); } } async function fillAllAnswers() { let filled = 0, failed = 0; for (const [idx, data] of examResults) { if (data.result) { const ok = matchAndFill(data.question, data.result); if (ok) filled++; else failed++; await sleep(400); } } log.success(`[考试] 填入完成! 成功:${filled} 失败:${failed}`); } // --- 考试页入口 --- async function examMain() { log.info('考试助手启动!'); await sleep(3000); createExamPanel(); if (config.autoExam) { await sleep(2000); searchAllQuestions(); } } // ===================== 主流程 ===================== async function main() { // 首先激活反检测(必须在所有操作之前) try { hookAntiCheat(); } catch (e) { log.warn('反检测模块初始化异常: ' + e.message); } // 考试页走考试流程 if (isExamPage()) { await examMain(); return; } if (!isStudyPage()) { log.info('当前页面非学习页,脚本待命中'); createPanel(); updatePanelStatus('⏳ 等待进入学习/考试页面...'); return; } log.info('智慧树刷课助手启动!'); log.info(`页面类型: ${isHikeClass() ? '翻转课' : isNewVersion() ? '智慧共享课(新版)' : '共享课'}`); // 等待页面加载 await sleep(3000); // 创建面板 createPanel(); // 页面优化 await optimizePage(); // 等待视频元素 await waitForVideo(); // 初始化视频设置 overridePause(); setMute(); setSpeed(); const title = getLessonTitle(); log.info(`正在学习: ${title}`); updatePanelStatus(`▶️ ${title}`); // 启动定时任务 // 1. 检测暂停并续播 (每3秒) addTimer(checkAndPlay, 3000); // 2. 视频设置维持(倍速、静音) (每5秒) addTimer(() => { overridePause(); setMute(); setSpeed(); }, 5000); // 3. 弹题检测 (每3秒) addTimer(skipQuestions, 3000); // 4. 弹窗关闭 (每4秒) addTimer(closePopups, 4000); // 5. 人机验证检测 (每5秒) addTimer(checkVerify, 5000); // 6. 习惯分模拟鼠标 (每8秒) addTimer(simulateMouseMove, 8000); // 7. 进度监控 & 自动切换 (每3秒) addTimer(() => { const progress = getCourseProgress(); updatePanelProgress(progress); if (progress === '100%') { switchToNext(); } }, 3000); log.success('所有任务模块已启动'); } async function waitForVideo() { let retries = 0; while (!getVideo() && retries < 30) { await sleep(1000); retries++; } if (getVideo()) { log.info('视频元素已就绪'); } else { log.warn('未找到视频元素,部分功能可能不可用'); } } // ===================== 油猴菜单命令 ===================== GM_registerMenuCommand('⚡ 重新启动脚本', () => { stopAllTasks(); const panel = $('#autovisor-panel'); if (panel) panel.remove(); main(); }); GM_registerMenuCommand('⏹ 停止脚本', () => { stopAllTasks(); updatePanelStatus('⏹ 脚本已停止'); log.info('脚本已手动停止'); }); GM_registerMenuCommand('🔄 重置配置', () => { config = Object.assign({}, DEFAULT_CONFIG); saveConfig(config); log.info('配置已重置为默认值'); location.reload(); }); // ===================== 启动 ===================== main(); })();