// ==UserScript== // @name 智慧云学全自动刷课 // @namespace https://www.zhihuiyunxue.com/ // @version 1.0 // @description 全自动刷课,倍速稳定生效,不静音,自动切课 // @author 整合优化版 // @match *://*.zhihuiyunxue.com/* // @grant GM_addStyle // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // ====================== 全局状态 ====================== let state = { currentIndex: 1, playbackRate: 3, hasPlaySuccess: false, playRetryCount: 0, videoInitLock: false, isJumping: false, isRunning: false, lastVideoSrc: '', panelInited: false, monitorInterval: null, speedGuardInterval: null }; // ====================== 工具函数 ====================== const $ = (selector, context = document) => { try { return context.querySelector(selector); } catch (e) { return null; } }; const $$ = (selector, context = document) => { try { return Array.from(context.querySelectorAll(selector)); } catch (e) { return []; } }; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const formatTime = (s) => `${Math.floor(s/60)}:${Math.floor(s%60).toString().padStart(2,'0')}`; // ====================== 日志模块 ====================== let logList, logContent; function initLog() { logList = $('#edu-log-list'); logContent = $('#edu-panel-content'); } function log(text, type = 'info') { console.log(`[智慧云学刷课] ${text}`); if (!logList) return; try { const colors = { debug: '#6b7280', info: '#1f2937', success: '#059669', warn: '#d97706', error: '#dc2626' }; const item = document.createElement('div'); item.style.cssText = `color: ${colors[type] || colors.info}; word-break: break-all; margin: 2px 0; font-size: 12px;`; item.innerHTML = `[${new Date().toLocaleTimeString()}] ${text}`; logList.appendChild(item); logContent.scrollTop = logContent.scrollHeight; } catch (e) { console.error('[智慧云学刷课] 日志输出失败', e); } } // ====================== 悬浮窗模块 ====================== async function waitForBody() { while (!document.body) await sleep(100); return document.body; } async function initPanel() { if (state.panelInited && $('#edu-auto-panel')) return; try { const body = await waitForBody(); const oldPanel = $('#edu-auto-panel'); if (oldPanel) oldPanel.remove(); const panel = document.createElement('div'); panel.id = 'edu-auto-panel'; panel.innerHTML = `
智慧云学自动刷课
视频播放倍速: 当前: 3x
💡 倍速守护每秒执行,稳定不易被重置
💡 不静音,正常播放声音
`; body.appendChild(panel.firstElementChild); initLog(); bindPanelEvents(); state.panelInited = true; log('✅ 脚本启动成功', 'success'); log(`📌 默认倍速:${state.playbackRate}倍,不静音`, 'info'); } catch (e) { console.error('[智慧云学刷课] 悬浮窗创建失败', e); } } function bindPanelEvents() { try { const panel = $('#edu-panel-main'); const header = $('#edu-panel-header'); const minBtn = $('#edu-panel-min'); const speedSelect = $('#edu-select-speed'); const startBtn = $('#edu-btn-start'); const stopBtn = $('#edu-btn-stop'); const resetBtn = $('#edu-btn-reset'); const currentSpeedText = $('#current-speed'); const hideElements = [ $('#edu-panel-speed'), $('#edu-panel-tips'), $('#edu-panel-buttons'), $('#edu-panel-content') ]; // 拖动逻辑 let isDragging = false, offsetX = 0, offsetY = 0; header.addEventListener('mousedown', (e) => { e.preventDefault(); isDragging = true; const rect = panel.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); const newLeft = Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - panel.offsetWidth)); const newTop = Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - panel.offsetHeight)); panel.style.left = `${newLeft}px`; panel.style.top = `${newTop}px`; panel.style.right = 'auto'; }); document.addEventListener('mouseup', () => isDragging = false); // 最小化逻辑 let isMin = false; minBtn.addEventListener('click', () => { isMin = !isMin; hideElements.forEach(ele => ele && (ele.style.display = isMin ? 'none' : 'block')); minBtn.innerText = isMin ? '□' : '—'; }); // 倍速切换 speedSelect.value = state.playbackRate; speedSelect.addEventListener('change', () => { const newSpeed = parseFloat(speedSelect.value); if (newSpeed < 0.1 || newSpeed > 10) { log('❌ 倍速范围:0.1~10倍', 'error'); speedSelect.value = state.playbackRate; return; } state.playbackRate = newSpeed; currentSpeedText.innerText = `当前: ${newSpeed}x`; log(`✅ 已设置倍速为:${newSpeed}倍`, 'success'); const video = getVideo(); if (video) { video.playbackRate = newSpeed; log('▶️ 当前视频倍速已更新', 'success'); } }); // 开始刷课 startBtn.addEventListener('click', () => { state.isRunning = true; startBtn.disabled = true; stopBtn.disabled = false; log('▶️ 开始刷课', 'success'); initVideoControl(); }); // 停止刷课 stopBtn.addEventListener('click', () => { state.isRunning = false; startBtn.disabled = false; stopBtn.disabled = true; log('⏹ 已停止刷课', 'info'); if (state.monitorInterval) clearInterval(state.monitorInterval); if (state.speedGuardInterval) clearInterval(state.speedGuardInterval); }); // 重置状态 resetBtn.addEventListener('click', () => { state.hasPlaySuccess = false; state.playRetryCount = 0; state.videoInitLock = false; state.isJumping = false; log('✅ 已重置所有状态', 'success'); }); } catch (e) { console.error('[智慧云学刷课] 事件绑定失败', e); } } // ====================== 倍速守护(简单直接,不拦截) ====================== function startSpeedGuard(video) { if (state.speedGuardInterval) clearInterval(state.speedGuardInterval); state.speedGuardInterval = setInterval(() => { if (!state.isRunning || !video || video.ended) { clearInterval(state.speedGuardInterval); return; } if (video.playbackRate !== state.playbackRate) { video.playbackRate = state.playbackRate; log(`🔄 倍速被改,已恢复为${state.playbackRate}x`, 'debug'); } }, 1000); // 每秒检查一次 } // 获取视频元素 function getVideo() { return document.querySelector('video'); } // ====================== 课程扫描与识别 ====================== function getCourseList() { try { const courseListContainer = document.querySelector('.el-tree'); if (!courseListContainer) { log('❌ 未找到课程列表容器', 'error'); return []; } const nodes = $$('.el-tree-node', courseListContainer); const videoNodes = nodes.filter(node => node.querySelector('svg') || node.querySelector('.icon-play')); if (videoNodes.length === 0) { log('❌ 未找到视频课程节点', 'error'); return []; } log(`✅ 扫描到${videoNodes.length}节视频课程`, 'success'); return videoNodes; } catch (e) { log(`❌ 扫描课程失败: ${e.message}`, 'error'); return []; } } async function autoDetectCurrentIndex(courseList) { try { log('🔍 自动识别当前课程...', 'info'); const currentNode = courseList.find(node => node.classList.contains('is-current') || node.classList.contains('is-checked') || node.style.backgroundColor !== '' ); if (currentNode) { const index = courseList.indexOf(currentNode); state.currentIndex = index + 1; log(`🎯 识别成功:第${state.currentIndex}节`, 'success'); return index; } log(`⚠️ 自动识别失败,从第1节开始`, 'warn'); return 0; } catch (e) { log(`❌ 识别失败: ${e.message}`, 'error'); return 0; } } // ====================== 自动跳转下一课 ====================== async function jumpToNext() { if (state.isJumping || !state.isRunning) return; state.isJumping = true; log('==================== 跳转下一课 ====================', 'debug'); try { const courseList = getCourseList(); if (courseList.length === 0) throw new Error('课程列表为空'); const currentIndex = await autoDetectCurrentIndex(courseList); if (currentIndex >= courseList.length - 1) { log('🏁 全部课程已播放完毕!', 'success'); alert('恭喜!所有课程视频已播放完成'); $('#edu-btn-stop').click(); return; } const nextNode = courseList[currentIndex + 1]; const clickTarget = $('.el-tree-node__content', nextNode); if (!clickTarget) throw new Error('未找到下一课的点击区域'); log(`✅ 下一课:第${currentIndex+2}节`, 'success'); clickTarget.scrollIntoView({ block: 'center' }); await sleep(300); clickTarget.click(); log('✅ 已点击下一课', 'success'); let checkCount = 0; const checkTimer = setInterval(() => { checkCount++; const videoChanged = getVideo()?.src !== state.lastVideoSrc; if (videoChanged || checkCount >= 15) { clearInterval(checkTimer); if (videoChanged) { state.currentIndex = currentIndex + 2; log('🎉 跳转成功!', 'success'); state.hasPlaySuccess = false; state.playRetryCount = 0; state.videoInitLock = false; } else { log('⚠️ 跳转超时,尝试重试', 'warn'); clickTarget.click(); } state.isJumping = false; } }, 200); } catch (err) { log(`❌ 跳转失败:${err.message}`, 'error'); state.isJumping = false; } } // ====================== 视频播放控制(不静音) ====================== async function tryPlayVideo(video) { if (state.hasPlaySuccess || !state.isRunning) return true; if (state.playRetryCount >= 15) { log('❌ 播放失败,请手动点击播放按钮', 'error'); return false; } try { state.playRetryCount++; log(`🔄 第${state.playRetryCount}次尝试播放`, 'info'); // 直接设置倍速,不静音 video.playbackRate = state.playbackRate; startSpeedGuard(video); // 启动倍速守护 const playPromise = video.play(); if (playPromise) { await playPromise; state.hasPlaySuccess = true; log('▶️ 自动播放成功!', 'success'); log(`🔊 有声音 | 🔒 倍速${state.playbackRate}x`, 'success'); return true; } } catch (err) { // 自动播放失败,尝试点击页面上的播放按钮 const playBtns = [ '.play-btn', 'button[aria-label="播放"]', '.vjs-big-play-button' ]; for (const selector of playBtns) { const btn = $(selector); if (btn) { btn.click(); video.playbackRate = state.playbackRate; state.hasPlaySuccess = true; log('▶️ 点击播放按钮成功', 'success'); return true; } } log(`⚠️ 播放失败:${err.message}`, 'warn'); } return false; } function initVideoControl() { if (!state.isRunning) return; const video = getVideo(); if (!video) { log('⏳ 未找到视频,等待加载...', 'warn'); setTimeout(initVideoControl, 1000); return; } if (state.videoInitLock && video.src === state.lastVideoSrc) return; state.videoInitLock = true; state.hasPlaySuccess = false; state.playRetryCount = 0; state.lastVideoSrc = video.src; log('🎬 检测到新视频,开始加载...', 'info'); // 视频加载完成后播放 video.addEventListener('loadedmetadata', async () => { log('✅ 视频加载完成', 'success'); await tryPlayVideo(video); }, { once: true }); // 如果已经加载足够数据,立即尝试播放 if (video.readyState >= 2) { tryPlayVideo(video); } // 循环重试播放 const retryTimer = setInterval(async () => { if (state.hasPlaySuccess || state.playRetryCount >= 15 || !state.isRunning) { clearInterval(retryTimer); return; } if (!document.hidden) await tryPlayVideo(video); }, 1000); // 防暂停守护 video.addEventListener('pause', async () => { if (video.ended || !state.hasPlaySuccess || !state.isRunning) return; log('⚠️ 视频被暂停,自动恢复', 'warn'); await video.play().catch(() => {}); }); // 进度日志 const progressTimer = setInterval(() => { if (video.ended || !state.isRunning) { clearInterval(progressTimer); return; } const current = Math.floor(video.currentTime); const total = Math.floor(video.duration); if (isNaN(total)) return; if (current % 60 === 0 && current > 0) { const percent = ((current / total) * 100).toFixed(1); log(`📊 进度:${formatTime(current)}/${formatTime(total)} (${percent}%)`, 'info'); } }, 1000); // 播放结束自动跳转 video.addEventListener('ended', async () => { clearInterval(progressTimer); clearInterval(retryTimer); if (state.speedGuardInterval) clearInterval(state.speedGuardInterval); log('🎉 当前视频播放完毕', 'success'); await sleep(1000); await jumpToNext(); }, { once: true }); // 页面切回自动恢复 document.addEventListener('visibilitychange', async () => { if (!document.hidden && state.isRunning && state.hasPlaySuccess && video.paused && !video.ended) { await video.play().catch(() => {}); log('🔄 页面切回,恢复播放', 'info'); } }); // 全局监控:视频源变化时重新初始化 state.monitorInterval = setInterval(() => { if (!state.isRunning) { clearInterval(state.monitorInterval); return; } const newVideo = getVideo(); if (newVideo && newVideo.src !== state.lastVideoSrc) { log('🔄 检测到课程切换,重新初始化', 'info'); state.videoInitLock = false; initVideoControl(); } }, 2000); } // ====================== 初始化 ====================== async function initScript() { if (state.panelInited) return; await initPanel(); } document.addEventListener('DOMContentLoaded', initScript); window.addEventListener('load', initScript); setTimeout(initScript, 1000); initScript(); })();