// ==UserScript==
// @name 智慧云学全自动刷课(倍速生效版)
// @namespace https://www.zhihuiyunxue.com/
// @version 2.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();
})();