// ==UserScript== // @name ENAEA自动刷课助手 // @namespace https://greasyfork.org/ // @version 1.0 // @description 中国教育干部网络学院(enaea.edu.cn)自动刷课工具 - 自动连续刷课、多页检测、倍速播放、自动静音、智能跳转 // @author Liontooth // @match https://study.enaea.edu.cn/* // @match https://*.ttcdw.cn/* // @match https://*.ertcloud.net/* // @grant none // @run-at document-start // @license MIT // @homepage https://github.com/chnlion/enaea-auto-study // @supportURL https://github.com/chnlion/enaea-auto-study/issues // @downloadURL none // ==/UserScript== (function() { 'use strict'; console.log('🚀 ENAEA自动刷课助手已启动'); // ==================== 配置项 ==================== let TARGET_SPEED = parseInt(localStorage.getItem('enaea_target_speed')) || 4; let AUTO_MUTE = localStorage.getItem('enaea_auto_mute') !== 'false'; let AUTO_JUMP = true; // 播放页自动跳转到未完成课程 let AUTO_SELECT_COURSE = true; // 列表页自动选择未完成课程 let AUTO_CONTINUOUS = localStorage.getItem('enaea_auto_continuous') !== 'false'; // 自动连续刷课 let CHECK_INTERVAL = parseInt(localStorage.getItem('enaea_check_interval')) || 15; // 检测间隔(秒) let MAX_CONTINUOUS_COUNT = 50; // 最大连续刷课次数 let processedVideos = new WeakSet(); let checkTimer = null; let lastCheckTime = 0; // ==================== 核心功能:劫持播放速度 ==================== function hijackPlaybackRate() { const originalDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate'); Object.defineProperty(HTMLMediaElement.prototype, 'playbackRate', { get: function() { return originalDescriptor.get.call(this); }, set: function(value) { originalDescriptor.set.call(this, TARGET_SPEED); console.log(`🎯 拦截并强制设置播放速度为${TARGET_SPEED}倍速`); }, configurable: true }); } function setVideoSpeed(video) { if (!video || processedVideos.has(video)) return false; try { video.playbackRate = TARGET_SPEED; processedVideos.add(video); console.log(`✅ 视频播放速度已设置为${TARGET_SPEED}倍速`); if (AUTO_MUTE) { video.muted = true; video.volume = 0; console.log('🔇 视频已静音'); } return true; } catch (e) { console.error('❌ 设置视频速度失败:', e); return false; } } function setAllVideos() { const videos = document.querySelectorAll('video'); let count = 0; videos.forEach(video => { if (setVideoSpeed(video)) { count++; } }); if (count > 0) { console.log(`🎬 找到并设置了 ${count} 个视频`); } } // ==================== MutationObserver监控新增视频 ==================== function startObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.tagName === 'VIDEO') { setVideoSpeed(node); } else if (node.querySelectorAll) { const videos = node.querySelectorAll('video'); videos.forEach(video => setVideoSpeed(video)); } }); }); }); observer.observe(document.body || document.documentElement, { childList: true, subtree: true }); console.log('👀 视频监控器已启动'); } // ==================== 播放页:自动识别并跳转到未完成课程 ==================== function findAndJumpToUnfinishedCourse() { console.log('🔍 正在查找未完成的课程...'); console.log('📍 当前URL:', window.location.href); console.log('📍 document.readyState:', document.readyState); console.log('📍 document.body存在:', !!document.body); let allCourses = []; if (!document.body) { console.log('⚠️ 页面body还未加载,延迟1秒后重试...'); setTimeout(findAndJumpToUnfinishedCourse, 1000); return false; } let courseContents = document.querySelectorAll('.cvtb-MCK-course-content, .cvtb-NCK-course-content'); console.log(`📌 方法1:找到 ${courseContents.length} 个课程元素`); if (courseContents.length === 0) { courseContents = document.querySelectorAll('[class*="course-content"]'); console.log(`📌 方法2:找到 ${courseContents.length} 个包含 course-content 的元素`); } if (courseContents.length === 0) { courseContents = document.querySelectorAll('li'); console.log(`📌 方法3:找到 ${courseContents.length} 个 li 元素`); } courseContents.forEach((item, index) => { try { const progressElement = item.querySelector('.cvtb-MCK-CsCt-studyProgress, .cvtb-NCK-CsCt-studyProgress'); const titleElement = item.querySelector('.cvtb-MCK-CsCt-title, .cvtb-NCK-CsCt-title, [class*="title"]'); if (!progressElement || !titleElement) { return; } const progressText = progressElement.textContent.trim(); const progressMatch = progressText.match(/(\d+)%/); const progress = progressMatch ? parseInt(progressMatch[1]) : 0; const title = titleElement.textContent.trim() || `课程${index + 1}`; const linkElement = item.querySelector('a, [onclick], .cvtb-MCK-CsCt-title, .cvtb-NCK-CsCt-title') || item; if (!linkElement) return; allCourses.push({ element: item, title: title, progress: progress, link: linkElement, index: allCourses.length + 1 }); } catch (e) { console.error(`❌ 解析课程 ${index + 1} 时出错:`, e); } }); if (allCourses.length === 0) { console.log('⚠️ 未找到任何课程'); return false; } console.log(`📚 共找到 ${allCourses.length} 门课程`); const unfinishedCourse = allCourses.find(course => course.progress < 100); if (!unfinishedCourse) { console.log('🎉 当前课程所有视频都已完成!'); return false; } console.log(`✅ 找到未完成视频: "${unfinishedCourse.title}" (${unfinishedCourse.progress}%)`); unfinishedCourse.element.style.outline = '3px solid rgb(74, 222, 128)'; unfinishedCourse.element.style.outlineOffset = '2px'; unfinishedCourse.element.style.transition = 'all 0.3s ease'; setTimeout(() => { console.log(`🚀 正在跳转到: ${unfinishedCourse.title}`); try { unfinishedCourse.link.click(); } catch (e) { console.error('❌ 点击失败:', e); } }, 500); return true; } function autoJumpOnLoadInVideoPage() { if (!AUTO_JUMP) { console.log('⏸️ 自动跳转功能已关闭'); return; } if (window.self !== window.top) { console.log('⏸️ 当前在iframe中,跳过自动跳转'); return; } const url = window.location.href; if (!url.includes('study.enaea.edu.cn')) { console.log('⏸️ 当前不在study.enaea.edu.cn域名,跳过自动跳转'); return; } console.log('⏳ 将在3秒、5秒、8秒后尝试自动查找未完成课程...'); let jumpSuccess = false; function tryAutoFind(attemptNum) { if (jumpSuccess) { console.log(`⏭️ 第${attemptNum}次尝试取消(已成功跳转)`); return; } console.log(`🤖 第${attemptNum}次自动执行"查找未完成课程"功能...`); const courseElements = document.querySelectorAll('.cvtb-MCK-course-content, .cvtb-NCK-course-content'); console.log(` 预检查:找到 ${courseElements.length} 个课程元素`); if (courseElements.length > 0) { console.log('✅ 找到课程列表,准备分析并跳转到未完成课程...'); } const result = findAndJumpToUnfinishedCourse(); if (result === true) { jumpSuccess = true; console.log('🎉 自动跳转成功,后续尝试已取消'); } } setTimeout(() => { console.log('⏰ 第1次尝试(页面加载后3秒)'); tryAutoFind(1); }, 3000); setTimeout(() => { console.log('⏰ 第2次尝试(页面加载后5秒)'); tryAutoFind(2); }, 5000); setTimeout(() => { console.log('⏰ 第3次尝试(页面加载后8秒)'); tryAutoFind(3); }, 8000); } // ==================== 播放页:定时检测所有视频进度 ==================== function checkAllVideosCompleted() { // 只在课程播放页执行 const url = window.location.href; if (!url.includes('viewerforccvideo.do') && !url.includes('viewerforicourse.do')) { return; } if (!AUTO_CONTINUOUS) { return; } // 避免频繁检测 const now = Date.now(); if (now - lastCheckTime < CHECK_INTERVAL * 1000 - 1000) { return; } lastCheckTime = now; console.log('═══════════════════════════════════════'); console.log(`🔍 检测课程完成状态 (间隔${CHECK_INTERVAL}秒)`); const courseContents = document.querySelectorAll('.cvtb-MCK-course-content, .cvtb-NCK-course-content'); if (courseContents.length === 0) { console.log('⚠️ 未找到课程列表元素'); return; } console.log(`📊 找到 ${courseContents.length} 个课程视频`); let allCompleted = true; let completedCount = 0; let courseDetails = []; courseContents.forEach((item, index) => { try { const progressElement = item.querySelector('.cvtb-MCK-CsCt-studyProgress, .cvtb-NCK-CsCt-studyProgress'); const titleElement = item.querySelector('.cvtb-MCK-CsCt-title, .cvtb-NCK-CsCt-title, [class*="title"]'); if (!progressElement) return; const progressText = progressElement.textContent.trim(); const progressMatch = progressText.match(/(\d+)%/); const progress = progressMatch ? parseInt(progressMatch[1]) : 0; const title = titleElement ? titleElement.textContent.trim() : `视频${index + 1}`; courseDetails.push({ title, progress }); if (progress === 100) { completedCount++; } else { allCompleted = false; } } catch (e) { console.error(`❌ 解析视频 ${index + 1} 时出错:`, e); } }); console.log(`📈 完成进度: ${completedCount}/${courseContents.length}`); courseDetails.forEach((detail, idx) => { const status = detail.progress === 100 ? '✅' : '⏳'; console.log(` ${status} ${idx + 1}. ${detail.title}: ${detail.progress}%`); }); if (allCompleted && courseContents.length > 0) { console.log('🎉🎉🎉 当前课程所有视频已完成!'); console.log('📨 准备发送完成信号到列表页...'); // 发送完成信号 const signal = { timestamp: Date.now(), courseUrl: window.location.href, totalVideos: courseContents.length }; localStorage.setItem('enaea_course_completed_signal', JSON.stringify(signal)); // 增加连续刷课计数 let count = parseInt(localStorage.getItem('enaea_continuous_count') || '0'); count++; localStorage.setItem('enaea_continuous_count', count.toString()); console.log(`✅ 完成信号已发送!这是第 ${count} 门连续完成的课程`); // 停止检测定时器 if (checkTimer) { clearInterval(checkTimer); checkTimer = null; console.log('⏸️ 已停止课程完成检测定时器'); } } else { console.log(`⏳ 课程尚未完成,将在 ${CHECK_INTERVAL} 秒后再次检测`); } } function startCourseCompletionCheck() { const url = window.location.href; if (!url.includes('viewerforccvideo.do') && !url.includes('viewerforicourse.do')) { return; } if (!AUTO_CONTINUOUS) { console.log('⏸️ 自动连续刷课功能已关闭'); return; } console.log(`🔄 启动课程完成检测 (间隔: ${CHECK_INTERVAL}秒)`); // 清除旧的定时器 if (checkTimer) { clearInterval(checkTimer); } // 启动新的定时器 checkTimer = setInterval(checkAllVideosCompleted, CHECK_INTERVAL * 1000); // 立即执行一次(延迟10秒,等页面加载) setTimeout(checkAllVideosCompleted, 10000); } // ==================== 列表页:自动选择未完成课程 ==================== function findAndClickUnfinishedCourseInList() { console.log('═══════════════════════════════════════'); console.log('📋 正在课程列表页面查找未完成的课程...'); let url = ''; try { url = window.location.href; console.log('📍 当前URL:', url); } catch (e) { console.log('⚠️ 无法获取URL'); } if (!document.body) { console.log('⚠️ 页面body还未加载,延迟1秒后重试...'); setTimeout(findAndClickUnfinishedCourseInList, 1000); return false; } const table = document.querySelector('#J_myOptionRecords'); console.log('🔍 查找表格 #J_myOptionRecords:', table ? '找到' : '未找到'); if (!table) { console.log('⚠️ 未找到课程列表表格,页面可能还在加载'); return false; } const allRows = document.querySelectorAll('#J_myOptionRecords tbody tr'); console.log(`📊 找到 ${allRows.length} 行数据`); let allCourses = []; allRows.forEach((row, index) => { try { const categoryTitle = row.querySelector('td[colspan="6"]'); if (categoryTitle) { console.log(`📂 分类: ${categoryTitle.textContent.trim()}`); return; } const progressElement = row.querySelector('.progressvalue'); if (!progressElement) { return; } const progressText = progressElement.textContent.trim(); const progressMatch = progressText.match(/(\d+)%/); const progress = progressMatch ? parseInt(progressMatch[1]) : 0; const titleElement = row.querySelector('.course-title'); const title = titleElement ? titleElement.getAttribute('title') || titleElement.textContent.trim() : `课程${index}`; const learnButton = row.querySelector('a.golearn'); if (!learnButton) { return; } allCourses.push({ row: row, title: title, progress: progress, button: learnButton, index: allCourses.length + 1 }); console.log(`✅ 课程 ${allCourses.length}: "${title}" - 进度: ${progress}%`); } catch (e) { console.error(`❌ 解析行 ${index + 1} 时出错:`, e); } }); if (allCourses.length === 0) { console.log('⚠️ 未找到任何课程'); return false; } console.log(`\n📚 共找到 ${allCourses.length} 门课程`); const unfinishedCourse = allCourses.find(course => course.progress < 100); if (!unfinishedCourse) { // 当前页全部完成,检查是否有下一页 console.log('✅ 当前页所有课程已完成'); // 获取分页信息 const nextBtn = document.querySelector('#J_myOptionRecords_next'); const isNextDisabled = nextBtn && nextBtn.classList.contains('paginate_button_disabled'); if (nextBtn && !isNextDisabled) { // 有下一页,获取当前页码信息 const activePageBtn = document.querySelector('.paginate_active'); const currentPage = activePageBtn ? activePageBtn.textContent.trim() : '?'; console.log(`📄 当前第 ${currentPage} 页已完成,准备翻到下一页...`); // 点击下一页按钮 nextBtn.click(); console.log('⏳ 等待页面加载(2秒)...'); // 等待页面加载后继续检测 setTimeout(() => { console.log('🔄 页面加载完成,继续检测下一页...'); findAndClickUnfinishedCourseInList(); }, 2000); return true; // 返回true表示正在处理 } else { // 没有下一页了,真的全部完成 console.log('🎉🎉🎉 太棒了!所有页面的课程都已完成 100%!'); console.log('🏆 学习任务全部完成!'); // 清除连续刷课计数 localStorage.removeItem('enaea_continuous_count'); // 弹窗提示 alert('🎉 恭喜!所有课程已完成!\n\n所有页面的课程已全部学习完毕。'); return false; } } console.log(`\n✅ 找到未完成课程: "${unfinishedCourse.title}" (${unfinishedCourse.progress}%)`); console.log(`🎯 这是第 ${unfinishedCourse.index} 门课程,即将打开...`); unfinishedCourse.row.style.backgroundColor = 'rgba(74, 222, 128, 0.2)'; unfinishedCourse.row.style.transition = 'all 0.3s ease'; setTimeout(() => { console.log(`🚀 正在打开课程: ${unfinishedCourse.title}`); try { unfinishedCourse.button.click(); return true; } catch (e) { console.error('❌ 点击失败:', e); const vurl = unfinishedCourse.button.getAttribute('data-vurl'); if (vurl) { console.log('🔄 尝试直接跳转到:', vurl); window.location.href = vurl; return true; } } return false; }, 1000); return true; } function autoSelectCourseInList() { if (!AUTO_SELECT_COURSE) { console.log('⏸️ 列表页自动选课功能已关闭'); return; } let isInIframe = false; try { isInIframe = (window.self !== window.top); } catch (e) { isInIframe = true; } if (isInIframe) { console.log('⏸️ 当前在iframe中,跳过列表页自动选课'); return; } let url = ''; try { url = window.location.href; } catch (e) { console.log('⚠️ 无法获取当前URL'); return; } console.log('🔍 列表页自动选课检测:'); console.log(' 当前URL:', url); if (url.includes('viewerforccvideo.do') || url.includes('viewerforicourse.do')) { console.log('⏸️ 当前在视频播放页面,跳过列表页自动选课'); return; } if (!url.includes('study.enaea.edu.cn')) { console.log('⏸️ 当前不在study.enaea.edu.cn域名,跳过列表页自动选课'); return; } console.log('✅ 页面检测通过,将在3秒、5秒、8秒后尝试在列表页自动选择未完成课程...'); let selectSuccess = false; function tryAutoSelect(attemptNum) { if (selectSuccess) { console.log(`⏭️ 第${attemptNum}次尝试取消(已成功选择)`); return; } console.log(`🤖 第${attemptNum}次自动执行"列表页选课"功能...`); const table = document.querySelector('#J_myOptionRecords'); if (!table) { console.log('⚠️ 未找到课程列表表格'); return; } console.log('✅ 找到课程列表表格,准备分析...'); const result = findAndClickUnfinishedCourseInList(); if (result === true) { selectSuccess = true; console.log('🎉 列表页自动选课成功,后续尝试已取消'); } } setTimeout(() => { console.log('⏰ 第1次尝试(页面加载后3秒)'); tryAutoSelect(1); }, 3000); setTimeout(() => { console.log('⏰ 第2次尝试(页面加载后5秒)'); tryAutoSelect(2); }, 5000); setTimeout(() => { console.log('⏰ 第3次尝试(页面加载后8秒)'); tryAutoSelect(3); }, 8000); } // ==================== 列表页:监听课程完成信号 ==================== function setupStorageListener() { const url = window.location.href; // 只在列表页监听 if (url.includes('viewerforccvideo.do') || url.includes('viewerforicourse.do')) { return; } if (!AUTO_CONTINUOUS) { console.log('⏸️ 自动连续刷课功能已关闭,不监听完成信号'); return; } console.log('👂 开始监听课程完成信号...'); // 监听 storage 事件 window.addEventListener('storage', (e) => { if (e.key === 'enaea_course_completed_signal') { console.log('═══════════════════════════════════════'); console.log('📨 收到课程完成信号!'); try { const signal = JSON.parse(e.newValue); console.log('📊 信号详情:', signal); handleCourseCompleted(); } catch (err) { console.error('❌ 解析信号失败:', err); } } }); // 兜底:定时检查信号(每5秒) setInterval(() => { const signal = localStorage.getItem('enaea_course_completed_signal'); if (signal) { try { const data = JSON.parse(signal); // 检查信号是否是最近10秒内的(避免处理旧信号) if (Date.now() - data.timestamp < 10000) { console.log('📨 定时检查到课程完成信号'); handleCourseCompleted(); } } catch (e) { // 忽略解析错误 } } }, 5000); } function handleCourseCompleted() { if (!AUTO_CONTINUOUS) { console.log('⏸️ 自动连续刷课功能已关闭'); return; } // 检查连续刷课次数 let count = parseInt(localStorage.getItem('enaea_continuous_count') || '0'); console.log(`📊 当前连续刷课计数: ${count}`); if (count >= MAX_CONTINUOUS_COUNT) { console.log(`⚠️ 已连续刷课 ${count} 门课程,达到上限 ${MAX_CONTINUOUS_COUNT}`); alert(`已连续自动刷课 ${count} 门课程!\n\n为了安全,自动刷课已暂停。\n请检查学习进度,如需继续请手动开启。`); AUTO_CONTINUOUS = false; localStorage.setItem('enaea_auto_continuous', 'false'); // 更新控制面板 const checkbox = document.getElementById('auto-continuous'); if (checkbox) checkbox.checked = false; return; } console.log('⏳ 等待2秒后刷新列表页...'); console.log('💡 刷新后将自动选择下一门未完成的课程'); // 清除完成信号(避免重复处理) localStorage.removeItem('enaea_course_completed_signal'); // 延迟2秒后刷新页面 setTimeout(() => { console.log('🔄 正在刷新页面...'); location.reload(); }, 2000); } // ==================== 自动点击继续学习 ==================== function autoClickContinue() { setInterval(() => { const continueBtn = document.querySelector('.el-dialog__footer button.el-button--primary'); if (continueBtn && continueBtn.textContent.includes('继续')) { console.log('🔄 检测到"继续学习"按钮,自动点击'); continueBtn.click(); } }, 2000); } // ==================== 监听事件 ==================== function setupEventListeners() { document.addEventListener('play', function(e) { if (e.target.tagName === 'VIDEO') { setVideoSpeed(e.target); } }, true); document.addEventListener('loadedmetadata', function(e) { if (e.target.tagName === 'VIDEO') { setVideoSpeed(e.target); } }, true); } function checkIframes() { const iframes = document.querySelectorAll('iframe'); if (iframes.length === 0) return; iframes.forEach((iframe, index) => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; if (iframeDoc) { const videos = iframeDoc.querySelectorAll('video'); videos.forEach(video => setVideoSpeed(video)); } } catch (e) { // 静默处理跨域错误 } }); } // ==================== 浮动控制面板 ==================== function createPanel() { // 判断当前页面类型 const url = window.location.href; const isVideoPage = url.includes('viewerforccvideo.do') || url.includes('viewerforicourse.do'); const pageType = isVideoPage ? '播放页' : '列表页'; const panel = document.createElement('div'); panel.id = 'enaea-control-panel'; // 根据页面类型生成不同的面板内容 let panelContent = ''; if (isVideoPage) { // ========== 播放页面板 ========== panelContent = `