// ==UserScript== // @name 智慧课程自动刷课助手 // @namespace https://ai.sunmooc.cn/ // @version 1.0 // @description 自动播放视频、视频倍速、自动回答弹窗题目、智能下一节 // @author AI Assistant // @icon https://cdn-icons-png.flaticon.com/512/3208/3208756.png // @match *://ai.sunmooc.cn/* // @match *://*.sunmooc.cn/* // @match *://*.sunmooc.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ======================== 配置 ======================== const CONFIG = { AUTO_PLAY: true, // 自动播放视频 DEFAULT_SPEED: 1.0, // 默认倍速 (0.5~3.0) MUTE: false, // 是否静音 AUTO_NEXT: true, // 自动下一节 AUTO_ANSWER_POPUP: true, // 自动回答视频中的弹窗题目 SHOW_PANEL: true, // 显示控制面板 FORCE_SPEED_INTERVAL: 500, // 强制倍速检查间隔(ms) DEBUG: true, }; let currentSpeed = CONFIG.DEFAULT_SPEED; const log = (...args) => CONFIG.DEBUG && console.log('[刷课助手]', ...args); // 退出全屏 const exitFullscreen = () => { const exit = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen; exit && exit.call(document); }; // ======================== 自动答题(弹窗题目) ======================== class PopupAnswerer { constructor() { this.answeredPopups = new WeakSet(); this.init(); } init() { if (!CONFIG.AUTO_ANSWER_POPUP) return; log('自动答题模块已启动'); const observer = new MutationObserver(() => this.scanAndAnswer()); observer.observe(document.body, { childList: true, subtree: true }); this.scanAndAnswer(); } scanAndAnswer() { // 常见弹窗容器选择器 const popupSelectors = [ '.modal', '.dialog', '.popup', '.video-question', '.el-dialog', '.mask', '.question-popup' ]; for (let sel of popupSelectors) { const popups = document.querySelectorAll(sel); for (let popup of popups) { if (this.answeredPopups.has(popup)) continue; if (popup.style.display !== 'none' && popup.offsetParent !== null) { this.answerPopup(popup); this.answeredPopups.add(popup); } } } } answerPopup(popup) { log('检测到弹窗,尝试自动答题'); // 1. 优先查找题目选项(单选框/复选框) const radios = popup.querySelectorAll('input[type="radio"], input[type="checkbox"]'); if (radios.length) { // 随机选择一个选项(模拟真实点击) const randomIndex = Math.floor(Math.random() * radios.length); radios[randomIndex].click(); log(`已随机选择第 ${randomIndex+1} 个选项`); // 延迟后点击提交/确定按钮 setTimeout(() => { const submitBtn = popup.querySelector('.submit, .confirm, .ok, .next, .btn-primary'); if (submitBtn) { submitBtn.click(); log('已点击提交按钮'); } else { // 如果没有提交按钮,尝试关闭弹窗 const closeBtn = popup.querySelector('.close, .cancel, .skip'); closeBtn && closeBtn.click(); } }, 300); return; } // 2. 处理判断题(只有“正确/错误”按钮) const trueFalse = popup.querySelectorAll('.true, .false, .yes, .no, .correct, .wrong'); if (trueFalse.length) { trueFalse[0].click(); // 默认点第一个(通常为“正确”) log('已点击判断题第一个按钮'); setTimeout(() => { const submit = popup.querySelector('.submit, .confirm'); submit && submit.click(); }, 300); return; } // 3. 兜底:直接关闭弹窗 const closeBtn = popup.querySelector('.close, .cancel, .skip'); if (closeBtn) { closeBtn.click(); log('未找到选项,直接关闭弹窗'); } } } // ======================== 刷课核心 ======================== class AutoCourse { constructor() { this.processed = new WeakSet(); this.intervals = new WeakMap(); this.init(); } init() { if (!CONFIG.AUTO_PLAY) return; log(`启动,目标倍速: ${currentSpeed}`); this.scan(); const observer = new MutationObserver(() => this.scan()); observer.observe(document.body, { childList: true, subtree: true }); } scan() { const videos = document.querySelectorAll('video'); for (let v of videos) { if (this.processed.has(v)) continue; this.processed.add(v); this.bind(v); } } bind(video) { log('绑定视频', video); this.setSpeed(video); // 监听事件 video.addEventListener('ratechange', () => this.setSpeed(video)); video.addEventListener('loadstart', () => this.setSpeed(video)); // 强制间隔检查 if (CONFIG.FORCE_SPEED_INTERVAL > 0) { const id = setInterval(() => { if (!video.ended && video.readyState > 0 && Math.abs(video.playbackRate - currentSpeed) > 0.05) { this.setSpeed(video); log('定时器重置倍速'); } }, CONFIG.FORCE_SPEED_INTERVAL); this.intervals.set(video, id); video.addEventListener('destroyed', () => clearInterval(this.intervals.get(video))); } // 自动播放 video.play().catch(e => log('自动播放失败', e)); // 结束自动下一节 video.addEventListener('ended', () => this.onEnded(video)); // 防暂停 video.addEventListener('pause', () => { if (!video.ended && video.paused && video.currentTime > 0) { log('视频暂停,恢复播放'); video.play().catch(e => {}); } }); } setSpeed(video) { if (Math.abs(video.playbackRate - currentSpeed) > 0.05) { video.playbackRate = currentSpeed; log(`设置倍速 ${currentSpeed}x`); } } updateAllSpeeds() { document.querySelectorAll('video').forEach(v => this.setSpeed(v)); } onEnded(video) { log('视频播放完成'); if (!CONFIG.AUTO_NEXT) return; const tryNext = (retry = 0) => { // 选择器匹配 const selectors = ['.next-btn', '.next-button', '.next', '.video-next', '.continue-btn', '.continue', '.complete-btn', '.finish-btn', '.chapter-next']; for (let sel of selectors) { const btn = document.querySelector(sel); if (btn && btn.offsetParent !== null) { btn.click(); log(`点击下一节: ${sel}`); return; } } // 文字匹配 const all = document.querySelectorAll('button, a, [role="button"], .btn, span, div'); for (let el of all) { const txt = (el.innerText || '').trim().toLowerCase(); if (txt.includes('下一节') || txt.includes('下一步') || txt.includes('继续') || txt === 'next' || txt === 'continue') { el.click(); log(`文字匹配点击: ${txt}`); return; } } if (retry < 5) { setTimeout(() => tryNext(retry+1), 1000); } else { log('未找到下一节按钮'); } }; setTimeout(() => tryNext(), 1500); } } // ======================== 控制面板 ======================== class ControlPanel { constructor(course) { this.course = course; this.create(); } create() { if (!CONFIG.SHOW_PANEL) return; const div = document.createElement('div'); div.id = 'speed-panel'; div.innerHTML = `
🎬 刷课助手
🔊 倍速: ${currentSpeed.toFixed(1)}x
✨ 自动答题 | 自动下一节
`; document.body.appendChild(div); document.getElementById('closePanel')?.addEventListener('click', () => div.remove()); document.getElementById('exitFullscreen')?.addEventListener('click', () => exitFullscreen()); const slider = document.getElementById('speedSlider'); const span = document.getElementById('speedVal'); slider?.addEventListener('input', (e) => { const val = parseFloat(e.target.value); span.innerText = val.toFixed(1); currentSpeed = val; this.course.updateAllSpeeds(); }); document.querySelectorAll('.speedPreset').forEach(btn => { btn.addEventListener('click', () => { const rate = parseFloat(btn.dataset.rate); slider.value = rate; span.innerText = rate.toFixed(1); currentSpeed = rate; this.course.updateAllSpeeds(); }); }); } } // 启动 const course = new AutoCourse(); new PopupAnswerer(); if (CONFIG.SHOW_PANEL) new ControlPanel(course); log('✅ 脚本已启动'); })();