// ==UserScript==
// @name 国家2026年人工智能刷课脚本
// @namespace http://tampermonkey.net/
// @version 3.4
// @description 修复视频完成后无法自动下一个+完美展开+连续播放+防504
// @author ark
// @match https://higher.smartedu.cn/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ==================== 样式定义 ====================
GM_addStyle(`
#auto-study-panel {
position: fixed;
top: 20px;
right: 20px;
width: 340px;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #ffffff;
overflow: hidden;
transition: all 0.3s ease;
user-select: none;
border: 1px solid rgba(255, 255, 255, 0.1);
}
#auto-study-panel.minimized {
width: 60px;
height: 60px;
border-radius: 50%;
}
#auto-study-panel.minimized .panel-content {
display: none;
}
#auto-study-panel.minimized .panel-header {
height: 60px;
padding: 0;
justify-content: center;
}
#auto-study-panel.minimized .panel-title {
display: none;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
cursor: move;
}
.panel-title {
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.panel-title::before {
content: '';
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.panel-controls {
display: flex;
gap: 8px;
}
.panel-btn {
width: 28px;
height: 28px;
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
font-size: 14px;
}
.panel-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.panel-btn.minimize-btn {
font-size: 18px;
}
.panel-content {
padding: 20px;
}
.status-section {
margin-bottom: 20px;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
}
.status-label {
color: #94a3b8;
}
.status-value {
font-weight: 500;
color: #ffffff;
}
.status-value.running {
color: #10b981;
}
.status-value.paused {
color: #f59e0b;
}
.status-value.stopped {
color: #ef4444;
}
.progress-bar {
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
margin-top: 8px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #10b981, #06b6d4);
border-radius: 4px;
transition: width 0.3s ease;
}
.settings-section {
margin-bottom: 20px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.settings-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
color: #94a3b8;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.setting-label {
font-size: 13px;
color: #cbd5e1;
}
.setting-input {
width: 70px;
padding: 6px 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
background: rgba(255, 255, 255, 0.05);
color: #ffffff;
font-size: 13px;
text-align: center;
}
.setting-input:focus {
outline: none;
border-color: #10b981;
}
.action-buttons {
display: flex;
gap: 10px;
margin-bottom: 16px;
}
.action-btn {
flex: 1;
padding: 12px 0;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn.expand-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
color: #ffffff;
}
.action-btn.expand-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
}
.action-btn.start-btn {
background: linear-gradient(135deg, #10b981 0%, #06b6d4 100%);
color: #0a0a0a;
}
.action-btn.start-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
.action-btn.pause-btn {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: #0a0a0a;
}
.action-btn.pause-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
}
.action-btn.stop-btn {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: #ffffff;
}
.action-btn.stop-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
.log-section {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.log-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 10px;
color: #94a3b8;
display: flex;
justify-content: space-between;
align-items: center;
}
.clear-log-btn {
font-size: 12px;
color: #94a3b8;
background: none;
border: none;
cursor: pointer;
}
.clear-log-btn:hover {
color: #ffffff;
}
.log-container {
height: 140px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
padding: 10px;
font-size: 12px;
font-family: 'Consolas', monospace;
color: #e2e8f0;
}
.log-container::-webkit-scrollbar {
width: 4px;
}
.log-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.log-item {
margin-bottom: 4px;
line-height: 1.4;
}
.log-item.info {
color: #06b6d4;
}
.log-item.success {
color: #10b981;
}
.log-item.warning {
color: #f59e0b;
}
.log-item.error {
color: #ef4444;
}
`);
// ==================== 全局变量 ====================
let isRunning = false;
let isPaused = false;
let currentIndex = 0;
let totalVideos = 0;
let videoItems = [];
let pauseResolve = null;
let stopRequested = false;
let isAllExpanded = false;
// 默认配置(已针对反检测优化)
const config = {
scrollDelayMin: 1200, // 滚动后最小等待时间(ms)
scrollDelayMax: 1800, // 滚动后最大等待时间(ms)
loadDelayMin: 4000, // 点击后视频最小加载时间(ms)
loadDelayMax: 6000, // 点击后视频最大加载时间(ms)
closeDelayMin: 3000, // 关闭后最小等待时间(ms)
closeDelayMax: 4000, // 关闭后最大等待时间(ms)
checkInterval: 3000, // 视频进度检查间隔(ms)
maxTimeout: 600, // 单个视频最大超时次数(3秒/次 = 30分钟)
progressWaitTime: 30000, // 视频播完后等待系统更新进度的时间(ms)
expandDelayMin: 400, // 展开章节最小间隔(ms)
expandDelayMax: 800 // 展开章节最大间隔(ms)
};
// ==================== UI创建 ====================
function createPanel() {
const panel = document.createElement('div');
panel.id = 'auto-study-panel';
panel.innerHTML = `
`;
document.body.appendChild(panel);
bindEvents();
makeDraggable(panel);
log('info', '✅ 终极挂课脚本 V3.3 已加载完成');
log('info', '💡 已修复视频完成后无法自动下一个的Bug');
}
// ==================== 事件绑定 ====================
function bindEvents() {
document.getElementById('expand-btn').addEventListener('click', expandAllChapters);
document.getElementById('start-btn').addEventListener('click', startAutoStudy);
document.getElementById('pause-btn').addEventListener('click', togglePause);
document.getElementById('stop-btn').addEventListener('click', stopAutoStudy);
document.getElementById('minimize-btn').addEventListener('click', toggleMinimize);
document.getElementById('clear-log-btn').addEventListener('click', clearLog);
// 配置输入框事件
document.getElementById('scroll-delay').addEventListener('change', (e) => {
const value = parseInt(e.target.value) || 1500;
config.scrollDelayMin = value - 300;
config.scrollDelayMax = value + 300;
});
document.getElementById('load-delay').addEventListener('change', (e) => {
const value = parseInt(e.target.value) || 5000;
config.loadDelayMin = value - 1000;
config.loadDelayMax = value + 1000;
});
document.getElementById('close-delay').addEventListener('change', (e) => {
const value = parseInt(e.target.value) || 3500;
config.closeDelayMin = value - 500;
config.closeDelayMax = value + 500;
});
document.getElementById('progress-wait').addEventListener('change', (e) => {
config.progressWaitTime = parseInt(e.target.value) || 30000;
});
}
// ==================== 拖拽功能 ====================
function makeDraggable(element) {
const header = document.getElementById('panel-header');
let isDragging = false;
let offsetX, offsetY;
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.panel-controls')) return;
isDragging = true;
offsetX = e.clientX - element.offsetLeft;
offsetY = e.clientY - element.offsetTop;
element.style.zIndex = '999999';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
element.style.left = `${x}px`;
element.style.top = `${y}px`;
element.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
}
// ==================== 核心功能:自动展开所有章节 ====================
async function expandAllChapters() {
log('info', '🔍 正在扫描所有章节...');
try {
// 第一步:展开所有大章节(li.list-item)
const mainChapters = document.querySelectorAll('li.list-item');
log('info', `📚 找到 ${mainChapters.length} 个大章节`);
for (let i = 0; i < mainChapters.length; i++) {
if (stopRequested) break;
const chapter = mainChapters[i];
const leftItem = chapter.querySelector('.left-item');
const icon = leftItem?.querySelector('.icon');
// 通过icon的is-expanded类判断是否已经展开
if (leftItem && icon && !icon.classList.contains('is-expanded')) {
log('info', `📖 展开大章节 ${i + 1}/${mainChapters.length}`);
safeClick(leftItem);
await randomSleep(config.expandDelayMin, config.expandDelayMax);
}
}
// 第二步:展开所有小节(ant-collapse-item)
const subChapters = document.querySelectorAll('.ant-collapse-item');
log('info', `📑 找到 ${subChapters.length} 个小节`);
for (let i = 0; i < subChapters.length; i++) {
if (stopRequested) break;
const chapter = subChapters[i];
const header = chapter.querySelector('.ant-collapse-header');
if (header && header.getAttribute('aria-expanded') !== 'true') {
log('info', `📖 展开小节 ${i + 1}/${subChapters.length}`);
safeClick(header);
await randomSleep(config.expandDelayMin, config.expandDelayMax);
}
}
// 等待所有内容渲染完成
await sleep(1500);
// 第三步:收集所有可见的视频项
videoItems = Array.from(document.querySelectorAll('.child-item')).filter(item => {
return item.offsetParent !== null;
});
totalVideos = videoItems.length;
if (totalVideos === 0) {
log('warning', '⚠️ 未找到任何可见的视频项');
} else {
log('success', `🎉 所有章节已成功展开,共找到 ${totalVideos} 个可见视频`);
}
isAllExpanded = true;
updateUI();
} catch (error) {
log('error', `❌ 展开章节时出错: ${error.message}`);
console.error(error);
}
}
// ==================== 核心功能:自动学习 ====================
async function startAutoStudy() {
if (isRunning) return;
try {
// 只有在未展开过的情况下才自动展开
if (!isAllExpanded) {
log('info', '🔄 检测到未展开章节,正在自动展开...');
await expandAllChapters();
}
if (totalVideos === 0) {
log('error', '❌ 没有找到任何视频,请先点击"展开全部"按钮!');
return;
}
isRunning = true;
isPaused = false;
stopRequested = false;
currentIndex = 0;
updateUI();
log('info', `🚀 开始自动学习,共 ${totalVideos} 个视频`);
for (let i = 0; i < totalVideos; i++) {
if (stopRequested) break;
currentIndex = i + 1;
updateUI();
const currentItem = videoItems[i];
// 检查进度,已完成则跳过
const processElement = currentItem.querySelector('.process');
const processText = processElement ? processElement.innerText.trim() : '';
if (processText.includes('100%') || processText.includes('已完成')) {
log('info', `⏭️ 第 ${currentIndex} 个视频进度 [${processText}],已跳过`);
continue;
}
// 等待暂停
if (isPaused) {
log('info', '⏸️ 已暂停,等待继续...');
await new Promise(resolve => { pauseResolve = resolve; });
if (stopRequested) break;
log('info', '▶️ 继续学习');
}
// 滚动到当前视频
log('info', `▶️ 正在打开第 ${currentIndex} 个视频`);
currentItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
await randomSleep(config.scrollDelayMin, config.scrollDelayMax);
// 安全点击打开视频
safeClick(currentItem);
await randomSleep(config.loadDelayMin, config.loadDelayMax);
// 验证视频弹窗是否真的打开了
const videoEl = document.querySelector('video');
if (!videoEl) {
log('warning', `⚠️ 第 ${currentIndex} 个视频弹窗未打开,重试一次...`);
safeClick(currentItem);
await randomSleep(config.loadDelayMin, config.loadDelayMax);
}
// 尝试点击播放按钮
const playBtn = document.querySelector('.video-play');
if (playBtn) {
log('info', '🎬 点击播放按钮');
safeClick(playBtn);
await sleep(1000);
}
// 等待视频播放完成
log('info', '⏳ 正在监测视频播放进度...');
await waitForProcessToEnd(currentItem, currentIndex);
// 关闭弹窗
log('info', '🔙 正在关闭视频弹窗...');
forceCloseModal();
await randomSleep(config.closeDelayMin, config.closeDelayMax);
}
if (stopRequested) {
log('info', '⏹️ 用户手动停止了学习');
} else {
log('success', '🎉 恭喜!所有视频已全部学习完成!');
}
} catch (error) {
log('error', `❌ 学习过程中出错: ${error.message}`);
console.error(error);
}
isRunning = false;
isPaused = false;
updateUI();
}
// ==================== 控制功能 ====================
function togglePause() {
if (!isRunning) return;
isPaused = !isPaused;
updateUI();
if (!isPaused && pauseResolve) {
pauseResolve();
pauseResolve = null;
}
}
function stopAutoStudy() {
if (!isRunning) return;
stopRequested = true;
if (isPaused && pauseResolve) {
pauseResolve();
}
}
function toggleMinimize() {
const panel = document.getElementById('auto-study-panel');
panel.classList.toggle('minimized');
}
// ==================== 辅助函数 ====================
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 随机延时(反检测核心)
function randomSleep(min, max) {
const ms = Math.floor(Math.random() * (max - min + 1)) + min;
return sleep(ms);
}
// 安全点击函数
function safeClick(element) {
if (!element) return;
try {
// 方法1:原生click()方法(最稳定)
element.click();
// 方法2:备用事件触发
setTimeout(() => {
const event = document.createEvent('MouseEvents');
event.initMouseEvent(
'click', true, true, window, 0,
0, 0, 0, 0, false, false, false, false, 0, null
);
element.dispatchEvent(event);
}, 50);
} catch (error) {
log('warning', `⚠️ 点击时出现小问题,已自动处理`);
console.error(error);
}
}
// 强制关闭弹窗(多重保障)
function forceCloseModal() {
try {
// 1. 首选:antd 关闭图标
const closeIcon = document.querySelector('.anticon-close');
if (closeIcon && closeIcon.offsetParent !== null) {
log('info', '🎯 点击关闭图标');
safeClick(closeIcon);
return;
}
// 2. 备选:视频弹窗关闭按钮
const closeBtn = document.querySelector('.video-modal-close');
if (closeBtn && closeBtn.offsetParent !== null) {
log('info', '🎯 点击关闭按钮');
safeClick(closeBtn);
return;
}
// 3. 备选:遮罩层点击
const mask = document.querySelector('.video-modal-mask');
if (mask) {
log('info', '🎯 点击遮罩层');
safeClick(mask);
return;
}
// 4. 终极:ESC 键
log('info', '🎯 按下 ESC 键');
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', bubbles: true }));
} catch (error) {
log('warning', `⚠️ 关闭弹窗时出现小问题`);
console.error(error);
}
}
// ✅ 完全重写:监听进度与防卡死逻辑(修复核心Bug)
function waitForProcessToEnd(itemNode, index) {
return new Promise((resolve) => {
let timeoutCount = 0;
let mainInterval = null;
let progressTimeout = null;
let hasVideoEnded = false;
// 清理所有定时器的函数
function cleanupAndResolve() {
if (mainInterval) clearInterval(mainInterval);
if (progressTimeout) clearTimeout(progressTimeout);
resolve();
}
// 主检查循环
mainInterval = setInterval(() => {
if (stopRequested) {
cleanupAndResolve();
return;
}
if (isPaused) return;
timeoutCount++;
try {
// 【条件1】系统进度达到100%(最可靠)
const processNode = itemNode.querySelector('.process');
if (processNode) {
const processText = processNode.innerText.trim();
if (processText.includes('100%') || processText.includes('已完成')) {
log('success', `✅ 第 ${index} 个视频系统进度已更新为 ${processText}`);
cleanupAndResolve();
return;
}
}
// 【条件2】视频本体播放完毕
const videoEl = document.querySelector('video');
if (videoEl && !hasVideoEnded) {
videoEl.muted = true; // 强制静音
// 自动恢复意外暂停
if (videoEl.paused && videoEl.currentTime < videoEl.duration) {
videoEl.play().catch(() => {});
}
// 视频播放完成
if (videoEl.ended || (videoEl.duration > 0 && videoEl.duration - videoEl.currentTime <= 1)) {
hasVideoEnded = true;
log('warning', `⚠️ 第 ${index} 个视频已播完,等待系统更新进度(最多${config.progressWaitTime/1000}秒)...`);
// 开始等待系统更新进度
progressTimeout = setTimeout(() => {
log('warning', `⏰ 等待进度更新超时,强制进入下一个视频`);
cleanupAndResolve();
}, config.progressWaitTime);
}
}
// 【条件3】全局超时保护(防504假死)
if (timeoutCount > config.maxTimeout) {
log('warning', `🚨 第 ${index} 个视频监测超时(可能服务器504),强行跳过`);
cleanupAndResolve();
}
} catch (error) {
log('warning', `⚠️ 检查进度时出现小问题`);
console.error(error);
}
}, config.checkInterval);
});
}
// 日志功能
function log(type, message) {
try {
const container = document.getElementById('log-container');
const item = document.createElement('div');
item.className = `log-item ${type}`;
item.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
container.appendChild(item);
container.scrollTop = container.scrollHeight;
} catch (error) {
console.log(message);
}
}
function clearLog() {
document.getElementById('log-container').innerHTML = '';
}
// 更新UI状态
function updateUI() {
try {
const statusText = document.getElementById('status-text');
const progressText = document.getElementById('progress-text');
const progressFill = document.getElementById('progress-fill');
const expandBtn = document.getElementById('expand-btn');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const stopBtn = document.getElementById('stop-btn');
// 更新状态
if (isRunning) {
if (isPaused) {
statusText.textContent = '已暂停';
statusText.className = 'status-value paused';
pauseBtn.textContent = '继续';
} else {
statusText.textContent = '学习中';
statusText.className = 'status-value running';
pauseBtn.textContent = '暂停';
}
} else {
statusText.textContent = '已停止';
statusText.className = 'status-value stopped';
}
// 更新进度
progressText.textContent = `${currentIndex} / ${totalVideos}`;
const progress = totalVideos > 0 ? (currentIndex / totalVideos) * 100 : 0;
progressFill.style.width = `${progress}%`;
// 更新按钮状态
expandBtn.disabled = isRunning;
startBtn.disabled = isRunning;
pauseBtn.disabled = !isRunning;
stopBtn.disabled = !isRunning;
} catch (error) {
console.error(error);
}
}
// ==================== 初始化 ====================
if (document.readyState === 'complete' || document.readyState === 'interactive') {
createPanel();
} else {
window.addEventListener('DOMContentLoaded', createPanel);
}
})();