您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ahgbjy自动学习脚本V1.0.1优化版:支持自动选课、自动学习、防休眠、智能课程切换等功能
// ==UserScript== // @name ahgbjy自动学习 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description ahgbjy自动学习脚本V1.0.1优化版:支持自动选课、自动学习、防休眠、智能课程切换等功能 // @author Moker32 // @license GPL-3.0-or-later // @match https://www.ahgbjy.gov.cn/* // @icon https://www.ahgbjy.gov.cn/commons/img/index/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @noframes // @run-at document-start // ==/UserScript== /** * ┌─────────────────────────────────────────────────────────────────────────┐ * │ ahgbjy自动学习 V1.0.1 │ * │ Released: 2025-06-13 │ * │ Updated: 2025-06-27 │ * └─────────────────────────────────────────────────────────────────────────┘ * * ✨ 核心特性 * ├─ 🎯 智能选课:优先选择"学习中"状态课程,支持自动翻页 * ├─ 📚 自动学习:完整章节学习流程,精确时间计算 * ├─ 😴 防休眠:Wake Lock API + 多重备用机制 * ├─ 🔄 课程切换:智能切换下一门课程,支持必修/选修 * ├─ 🎨 简洁UI:实时状态显示,精确倒计时 * └─ 🛡️ 高稳定:统一错误处理,自动重试机制 * * 🏗️ 架构设计 * ├─ VideoAutoplayBlocker → 视频播放控制 * ├─ WakeLockManager → 防休眠系统 * ├─ BackgroundMonitor → 后台保活监控 * ├─ Utils → 统一工具函数 * ├─ UI → 用户界面管理 * ├─ CourseHandler → 课程处理引擎 * └─ Router → 页面路由控制 * * 💡 V1.0.1 优化亮点 * • 手动操作适应性:支持混合使用模式 * • 登录页面保护:智能暂停避免干扰 * • 时间计算优化:100%完整时长 + 1分钟安全余量 * • 存储优化:直接使用数组存储,提升性能 * • 备用机制优化:智能检测页面变化,避免重复操作 * • 定时器简化:移除复杂定时器替换,依赖Wake Lock保活 */ (function() { 'use strict'; // ════════════════════════════════════════════════════════════════════════ // 🎥 视频控制模块 // ════════════════════════════════════════════════════════════════════════ const VideoAutoplayBlocker = { init: () => { Utils.safeExecute(() => { console.log('视频自动播放阻止器启动'); VideoAutoplayBlocker.blockAutoplay(); VideoAutoplayBlocker.blockVideoPopups(); }, '视频阻止器初始化失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 阻止视频自动播放 │ // └─────────────────────────────────────────────────────────────────┘ blockAutoplay: () => { Utils.safeExecute(() => { // 设置现有视频 document.addEventListener('DOMContentLoaded', () => { const videos = document.querySelectorAll('video'); videos.forEach(video => { video.autoplay = false; video.muted = true; }); }); // 监控新增视频 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { if (node.tagName === 'VIDEO') { node.autoplay = false; node.muted = true; } if (node.querySelectorAll) { node.querySelectorAll('video').forEach(video => { video.autoplay = false; video.muted = true; }); } } }); }); }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } console.log('视频自动播放已阻止'); }, '阻止视频自动播放失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 阻止视频弹窗 │ // └─────────────────────────────────────────────────────────────────┘ blockVideoPopups: () => { Utils.safeExecute(() => { const popupSelectors = [ '.video-popup', '.video-ad', '.video-overlay', '.player-popup', '.media-popup', '.video-dialog' ]; const hidePopups = () => { popupSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { if (element) { element.style.display = 'none !important'; } }); }); }; hidePopups(); setInterval(hidePopups, 5000); console.log('视频弹窗阻止器已启动'); }, '视频弹窗阻止设置失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 🛠️ 防休眠系统 // ════════════════════════════════════════════════════════════════════════ const WakeLockManager = { wakeLock: null, fallbackInterval: null, init: () => { Utils.safeExecute(() => { WakeLockManager.requestWakeLock(); WakeLockManager.setupFallbackKeepAwake(); WakeLockManager.handleVisibilityChange(); console.log('防休眠系统已启动'); }, '防休眠初始化失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ Wake Lock API │ // └─────────────────────────────────────────────────────────────────┘ requestWakeLock: async () => { try { if ('wakeLock' in navigator) { WakeLockManager.wakeLock = await navigator.wakeLock.request('screen'); console.log('Wake Lock已激活,系统保持唤醒状态'); WakeLockManager.wakeLock.addEventListener('release', () => { console.log('Wake Lock已释放'); }); } else { console.log('浏览器不支持Wake Lock API,使用备用方案'); } } catch (error) { console.log('Wake Lock请求失败,使用备用方案'); } }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 备用防休眠机制 │ // └─────────────────────────────────────────────────────────────────┘ setupFallbackKeepAwake: () => { Utils.safeExecute(() => { // 定期活动保持系统唤醒 WakeLockManager.fallbackInterval = setInterval(() => { // 轻微的DOM活动 document.title = document.title; // 偶尔发送心跳请求 if (Math.random() < 0.1) { fetch(window.location.href, { method: 'HEAD' }).catch(() => {}); } }, 30000); console.log('备用防休眠机制已启动'); }, '备用防休眠设置失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 页面可见性处理 │ // └─────────────────────────────────────────────────────────────────┘ handleVisibilityChange: () => { document.addEventListener('visibilitychange', async () => { if (!document.hidden && !WakeLockManager.wakeLock) { await WakeLockManager.requestWakeLock(); } }); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 系统清理 │ // └─────────────────────────────────────────────────────────────────┘ cleanup: () => { Utils.safeExecute(() => { if (WakeLockManager.wakeLock) { WakeLockManager.wakeLock.release(); WakeLockManager.wakeLock = null; } if (WakeLockManager.fallbackInterval) { clearInterval(WakeLockManager.fallbackInterval); WakeLockManager.fallbackInterval = null; } console.log('防休眠系统已清理'); }, '防休眠清理失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 📱 后台监控系统 // ════════════════════════════════════════════════════════════════════════ const BackgroundMonitor = { isVisible: !document.hidden, backgroundTime: 0, pendingActions: new Map(), keepAliveWorker: null, init: () => { Utils.safeExecute(() => { // 页面可见性监控 document.addEventListener('visibilitychange', BackgroundMonitor.handleVisibilityChange); // 简化的定时器替换 BackgroundMonitor.replaceTimers(); // Web Worker保活 BackgroundMonitor.createKeepAliveWorker(); // 添加页面状态强制检查 BackgroundMonitor.forceCheckPageState(); console.log('双重后台监控系统已启动'); }, '后台监控初始化失败'); }, handleVisibilityChange: () => { Utils.safeExecute(() => { BackgroundMonitor.isVisible = !document.hidden; const status = BackgroundMonitor.isVisible ? '前台' : '后台'; console.log(`页面状态切换: ${status}`); UI.updateBackgroundStatus(!BackgroundMonitor.isVisible); if (!BackgroundMonitor.isVisible) { BackgroundMonitor.backgroundTime = Date.now(); console.log('页面进入后台'); } else { console.log('页面恢复前台,检查待执行动作'); BackgroundMonitor.processPendingActions(); } }, '可见性变化处理失败'); }, // 简化的Web Worker保活(单一版本) createKeepAliveWorker: () => { Utils.safeExecute(() => { const workerScript = ` let interval = null; let isActive = true; const startKeepAlive = () => { interval = setInterval(() => { if (isActive) { postMessage({type: 'tick', timestamp: Date.now()}); } }, 1000); }; startKeepAlive(); self.onmessage = function(e) { if (e.data === 'stop') { isActive = false; if (interval) clearInterval(interval); } }; `; const blob = new Blob([workerScript], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.onmessage = (e) => { if (e.data.type === 'tick') { BackgroundMonitor.checkPendingActions(); } }; BackgroundMonitor.keepAliveWorker = worker; console.log('Web Worker保活已启动'); }, 'Web Worker创建失败'); }, // 简化的定时器替换(优化版本) replaceTimers: () => { Utils.safeExecute(() => { // 由于已有 Wake Lock API 和 Web Worker 保活机制,简化定时器替换逻辑 console.log('定时器管理已简化,依赖Wake Lock和Web Worker保活'); }, '定时器替换失败'); }, // 简化的后台动作调度 scheduleBackgroundAction: (actionId, callback, delay = 0) => { const action = { id: actionId, callback: callback, scheduledTime: Date.now() + delay, executed: false }; BackgroundMonitor.pendingActions.set(actionId, action); console.log(`注册后台动作: ${actionId}, 延迟: ${delay}ms`); // 延迟执行 setTimeout(() => { if (!action.executed) { Utils.safeExecute(callback, `动作执行失败: ${actionId}`); action.executed = true; BackgroundMonitor.pendingActions.delete(actionId); } }, delay); return actionId; }, // 检查待执行动作 checkPendingActions: () => { Utils.safeExecute(() => { const now = Date.now(); for (const [actionId, action] of BackgroundMonitor.pendingActions) { if (!action.executed && now >= action.scheduledTime) { console.log(`执行待处理动作: ${actionId}`); Utils.safeExecute(action.callback, `执行动作失败: ${actionId}`); action.executed = true; BackgroundMonitor.pendingActions.delete(actionId); } } }, '检查待执行动作失败'); }, // 处理页面恢复时的待执行动作 processPendingActions: () => { Utils.safeExecute(() => { for (const [actionId, action] of BackgroundMonitor.pendingActions) { if (!action.executed) { console.log(`页面恢复,立即执行动作: ${actionId}`); Utils.safeExecute(action.callback, `恢复执行动作失败: ${actionId}`); action.executed = true; BackgroundMonitor.pendingActions.delete(actionId); } } }, '处理恢复动作失败'); }, // 页面状态强制检查机制 forceCheckPageState: () => { Utils.safeExecute(() => { // 定期检查页面状态,即使在后台也能响应 setInterval(() => { const currentUrl = window.location.href; const lastUrl = sessionStorage.getItem('lastUrl') || ''; // 如果是登录页面,跳过所有检查 if (currentUrl.includes('/pc/login.do')) { return; } // 如果URL发生变化,说明页面已经跳转 if (currentUrl !== lastUrl) { console.log(`检测到页面变化: ${lastUrl} -> ${currentUrl}`); sessionStorage.setItem('lastUrl', currentUrl); // 延迟执行以确保页面完全加载 setTimeout(() => { Router.handleCurrentPage(); }, 2000); } // 检查是否需要强制刷新状态 const lastActiveTime = sessionStorage.getItem('lastActiveTime'); if (lastActiveTime) { const elapsed = Date.now() - parseInt(lastActiveTime); // 如果超过5分钟没有活动,且在课程详情页,强制检查 if (elapsed > 300000 && currentUrl.includes('coursedetail.do')) { console.log('长时间无活动,强制检查课程详情页状态'); sessionStorage.setItem('lastActiveTime', Date.now().toString()); Router.handleCourseDetailPage(); } } }, 30000); // 每30秒检查一次 console.log('页面状态强制检查已启动'); }, '页面状态检查设置失败'); }, cleanup: () => { Utils.safeExecute(() => { // 清理简化后的资源 BackgroundMonitor.pendingActions.clear(); if (BackgroundMonitor.keepAliveWorker) { BackgroundMonitor.keepAliveWorker.postMessage('stop'); BackgroundMonitor.keepAliveWorker = null; } console.log('后台监控已清理'); }, '后台监控清理失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 🔧 统一工具模块 // ════════════════════════════════════════════════════════════════════════ const Utils = { // ┌─────────────────────────────────────────────────────────────────┐ // │ 统一错误处理 │ // └─────────────────────────────────────────────────────────────────┘ safeExecute: (func, errorMsg = '操作失败') => { try { return func(); } catch (error) { console.error(`${errorMsg}: ${error.message}`); return null; } }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 智能重试机制 │ // └─────────────────────────────────────────────────────────────────┘ retry: (func, maxRetries = 3, delay = 1000, errorMsg = '重试失败') => { let attempts = 0; const attempt = () => { try { const result = func(); if (result !== false && result !== null && result !== undefined) { return result; } } catch (error) { console.error(`尝试 ${attempts + 1} 失败: ${error.message}`); } attempts++; if (attempts < maxRetries) { setTimeout(attempt, delay); } else { console.error(`${errorMsg}: 已达最大重试次数`); } }; attempt(); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ DOM 选择器 │ // └─────────────────────────────────────────────────────────────────┘ $: (selector, context = document) => { return Utils.safeExecute(() => context.querySelector(selector), `查询失败: ${selector}`); }, $$: (selector, context = document) => { return Utils.safeExecute(() => Array.from(context.querySelectorAll(selector)), `查询失败: ${selector}`) || []; }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 元素等待器 │ // └─────────────────────────────────────────────────────────────────┘ waitForElement: (selector, timeout = 10000) => { return new Promise((resolve) => { Utils.safeExecute(() => { const elements = Utils.$$(selector); if (elements.length > 0) { resolve(elements); return; } const startTime = performance.now(); const checkElements = (currentTime) => { const elements = Utils.$$(selector); if (elements.length > 0 || currentTime - startTime > timeout) { resolve(elements); } else { requestAnimationFrame(checkElements); } }; requestAnimationFrame(checkElements); }, '等待元素失败'); }); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 页面导航器 │ // └─────────────────────────────────────────────────────────────────┘ navigateTo: (url, reason = '页面跳转') => { Utils.safeExecute(() => { console.log(`${reason}: ${url}`); sessionStorage.setItem('returning', 'true'); window.location.href = url; // 单一备用机制 setTimeout(() => { if (window.location.href !== url) { console.log('备用导航触发'); window.location.assign(url); } }, 2000); }, `导航失败: ${url}`); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 时间处理工具 │ // └─────────────────────────────────────────────────────────────────┘ extractMinutes: text => { if (!text) return 30; const match = text.match(/(\d+)/); return match ? parseInt(match[1]) : 30; }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 安全防护设置 │ // └─────────────────────────────────────────────────────────────────┘ setupProtection: () => { Utils.safeExecute(() => { // 基础弹窗阻止 unsafeWindow.alert = () => console.log('警告窗口被屏蔽'); unsafeWindow.confirm = () => true; unsafeWindow.prompt = () => ''; // 防止WebDriver检测 if (window.navigator) { Object.defineProperty(navigator, 'webdriver', { get: () => false }); } console.log('基础防护设置已启用'); }, '防护设置失败'); }, // ═══════════════════════════════════════════════════════════════════ // 💾 存储管理 // ═══════════════════════════════════════════════════════════════════ storage: { get: (key, defaultValue = '') => { return Utils.safeExecute(() => GM_getValue(key, defaultValue), `存储读取错误: ${key}`, defaultValue); }, set: (key, value) => { Utils.safeExecute(() => GM_setValue(key, value), `存储写入错误: ${key}`); }, getVisited: () => { return Utils.safeExecute(() => { return GM_getValue('visitedCourses', []); }, '获取访问记录错误', []); }, addVisited: courseId => { Utils.safeExecute(() => { const visited = Utils.storage.getVisited(); if (!visited.includes(courseId)) { visited.push(courseId); GM_setValue('visitedCourses', visited); } }, `添加访问记录错误: ${courseId}`); }, clearVisited: () => { Utils.safeExecute(() => GM_setValue('visitedCourses', []), '清除访问记录错误'); } }, // ═══════════════════════════════════════════════════════════════════ // 🔗 URL处理 // ═══════════════════════════════════════════════════════════════════ url: { extractCourseId: url => { const match = url.match(/courseid=([0-9A-F-]{36})/i) || url.match(/courseid=(\d+)/); return match ? match[1] : null; }, extractChapterId: url => { const match = url.match(/chapterid=([0-9A-F-]{36})/i) || url.match(/chapterid=(\d+)/); return match ? match[1] : null; }, getParam: name => { const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); const results = regex.exec(window.location.href); return results && results[2] ? decodeURIComponent(results[2].replace(/\+/g, ' ')) : null; } } }; // ════════════════════════════════════════════════════════════════════════ // 🎨 用户界面模块 // ════════════════════════════════════════════════════════════════════════ const UI = { panel: null, stats: { startTime: Date.now(), coursesCompleted: 0, backgroundTime: 0 }, init: () => { Utils.safeExecute(() => { UI.createPanel(); UI.updateStatus('脚本已启动', 'info'); console.log('用户界面已初始化'); }, '用户界面初始化失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 创建控制面板 │ // └─────────────────────────────────────────────────────────────────┘ createPanel: () => { Utils.safeExecute(() => { const panel = document.createElement('div'); panel.id = 'study-assistant-panel'; panel.innerHTML = ` <div style="position: fixed; top: 10px; right: 10px; width: 300px; background: #fff; border: 1px solid #ddd; border-radius: 5px; padding: 15px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 10000; font-family: Arial, sans-serif; font-size: 12px;"> <div style="font-weight: bold; margin-bottom: 10px; color: #333;">安徽干部教育助手 V1.0.1</div> <div id="status-display" style="padding: 8px; background: #f5f5f5; border-radius: 3px; margin-bottom: 10px; min-height: 20px;"></div> <div id="background-status" style="padding: 5px; background: #e8f5e8; border-radius: 3px; font-size: 11px; text-align: center;">前台运行中</div> </div> `; document.body.appendChild(panel); UI.panel = panel; }, 'UI面板创建失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 状态更新器 │ // └─────────────────────────────────────────────────────────────────┘ updateStatus: (message, type = 'info') => { Utils.safeExecute(() => { const statusEl = document.getElementById('status-display'); if (statusEl) { const colors = { info: '#2196F3', success: '#4CAF50', warning: '#FF9800', error: '#F44336' }; statusEl.style.color = colors[type] || colors.info; statusEl.textContent = message; } }, '状态更新失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 后台状态指示器 │ // └─────────────────────────────────────────────────────────────────┘ updateBackgroundStatus: (isBackground) => { Utils.safeExecute(() => { const bgEl = document.getElementById('background-status'); if (bgEl) { if (isBackground) { bgEl.textContent = '后台运行中'; bgEl.style.background = '#fff3cd'; UI.stats.backgroundTime = Date.now(); } else { bgEl.textContent = '前台运行中'; bgEl.style.background = '#e8f5e8'; } } }, '后台状态更新失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 📚 课程处理引擎 // ════════════════════════════════════════════════════════════════════════ const CourseHandler = { currentCourse: null, isProcessing: false, init: () => { Utils.safeExecute(() => { console.log('课程处理器已初始化'); }, '课程处理器初始化失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 智能课程打开器 │ // └─────────────────────────────────────────────────────────────────┘ openCourse: (courseElement) => { if (!courseElement || CourseHandler.isProcessing) return; Utils.safeExecute(() => { CourseHandler.isProcessing = true; const courseTitle = courseElement.textContent?.trim() || '未知课程'; console.log(`准备打开课程: ${courseTitle}`); UI.updateStatus(`正在打开课程: ${courseTitle}`, 'info'); // 智能打开机制:直接导航 + 智能备用 const link = courseElement.querySelector('a') || courseElement.closest('a'); if (link && link.href) { console.log(`直接导航到课程: ${link.href}`); Utils.navigateTo(link.href, '打开课程'); } else { console.log('使用点击方式打开课程'); courseElement.click(); // 智能备用:检查页面是否发生变化,如果没有则再次点击 const currentUrl = window.location.href; setTimeout(() => { if (window.location.href === currentUrl && courseElement) { console.log('页面未跳转,执行备用点击'); courseElement.click(); } }, 2000); } CourseHandler.isProcessing = false; }, '打开课程失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 学习时间管理器 │ // └─────────────────────────────────────────────────────────────────┘ startStudyTime: (requiredMinutes, completeButton) => { Utils.safeExecute(() => { console.log(`开始学习计时: ${requiredMinutes}分钟`); UI.updateStatus(`学习中: ${requiredMinutes}分钟`, 'info'); const waitTime = requiredMinutes * 60 * 1000; const studyStartTime = Date.now(); // 显示倒计时(每秒更新) const updateDisplay = () => { const elapsed = Date.now() - studyStartTime; const remaining = Math.max(0, waitTime - elapsed); const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); if (remaining > 0) { UI.updateStatus(`学习中,剩余: ${minutes}:${seconds.toString().padStart(2, '0')}`, 'info'); } else { UI.updateStatus('学习完成,准备切换...', 'success'); clearInterval(displayInterval); } }; // 立即显示一次 updateDisplay(); // 每秒更新一次倒计时 const displayInterval = setInterval(updateDisplay, 1000); // 时间到后完成 setTimeout(() => { clearInterval(displayInterval); UI.updateStatus('学习完成,准备切换...', 'success'); if (completeButton && typeof completeButton.click === 'function') { console.log('点击完成按钮'); const currentUrl = window.location.href; completeButton.click(); // 学习完成后,等待页面跳转或寻找下一章节 setTimeout(() => { CourseHandler.handleStudyComplete(); }, 3000); // 智能备用:检查页面是否发生变化,如果没有则再次点击 setTimeout(() => { if (window.location.href === currentUrl && completeButton && typeof completeButton.click === 'function') { console.log('页面未跳转,执行备用完成点击'); completeButton.click(); } }, 2000); } }, waitTime); }, '学习时间处理失败'); }, // ┌─────────────────────────────────────────────────────────────────┐ // │ 学习完成处理器 │ // └─────────────────────────────────────────────────────────────────┘ handleStudyComplete: () => { Utils.safeExecute(() => { console.log('章节学习完成,寻找下一步'); // 等待可能的页面跳转 setTimeout(() => { // 检查是否回到了课程详情页 if (window.location.pathname.includes('coursedetail.do')) { console.log('返回课程详情页,寻找下一章节'); Router.handleCourseDetailPage(); } // 检查是否还在学习页面 else if (window.location.pathname.includes('playvideo.do') || window.location.pathname.includes('playscorm.do')) { console.log('仍在学习页面,寻找下一章节按钮'); // 寻找下一章节按钮 const nextChapterButton = Utils.$('a[href*="playvideo"], a[href*="playscorm"], .next-chapter, .next-lesson'); if (nextChapterButton && nextChapterButton.href && nextChapterButton.href !== window.location.href) { console.log('找到下一章节,准备跳转'); UI.updateStatus('发现下一章节,准备继续学习', 'info'); setTimeout(() => nextChapterButton.click(), 2000); } else { // 没有下一章节,尝试返回课程列表 console.log('没有更多章节,准备返回课程列表'); Utils.navigateTo('/courseList.do', '返回课程列表'); } } // 检查是否回到了课程列表页 else if (window.location.pathname.includes('courselist.do')) { console.log('返回课程列表页,寻找下一门课程'); Router.handleCourseListPage(); } // 其他情况,尝试返回课程列表 else { console.log('未知页面,尝试返回课程列表'); Utils.navigateTo('https://www.ahgbjy.gov.cn/pc/course/courselist.do?categoryid=&year=&coutype=0&mostNInput=0&mostHInput=0&mostTJInput=&keyword=', '返回课程列表'); } }, 2000); }, '学习完成处理失败'); }, // ───────────────────────────────────────────────────────────────────── // 🎯 课程选择算法 // ───────────────────────────────────────────────────────────────────── selectCourse: (courseElements, visitedCourses) => { // 🥇 优先级1:选择"学习中"的课程 for (const el of courseElements) { const status = el.lastChild?.innerHTML?.trim() || ''; if (status === "学习中") { console.log('✨ 找到学习中的课程'); return el; } } // 🥈 优先级2:选择未完成且未访问的课程 for (const el of courseElements) { const status = el.lastChild?.innerHTML?.trim() || ''; if (status !== "已完成") { const courseId = CourseHandler.extractCourseId(el); if (!visitedCourses.includes(courseId)) { Utils.storage.addVisited(courseId); console.log(`🎯 选择未完成课程: ${courseId}`); return el; } } } return null; }, // ───────────────────────────────────────────────────────────────────── // 📄 分页处理 // ───────────────────────────────────────────────────────────────────── handlePagination: async () => { try { const pagination = Utils.$('.pagination'); if (!pagination) { console.error('未找到分页元素'); return false; } const pageLinks = pagination.querySelectorAll('a[href]'); console.log(`找到 ${pageLinks.length} 个分页链接`); // 查找">"按钮 for (const link of pageLinks) { const linkText = link.textContent.trim(); if (linkText === '>') { const href = link.getAttribute('href'); if (href) { const fullUrl = href.startsWith('/') ? `https://www.ahgbjy.gov.cn${href}` : href; console.log(`找到下一页按钮,跳转到: ${fullUrl}`); UI.updateStatus('跳转到下一页'); window.location.href = fullUrl; return true; } } } console.error('未找到下一页按钮'); return false; } catch (error) { console.error(`分页处理错误: ${error.message}`); return false; } }, // ───────────────────────────────────────────────────────────────────── // 🔄 课程类型切换 // ───────────────────────────────────────────────────────────────────── switchCourseType: () => { try { const currentType = Utils.url.getParam('coutype') || '0'; console.log(`当前课程类型: ${currentType === '0' ? '必修' : '选修'}`); // 检查是否两种类型都已完成 const requiredCompleted = Utils.storage.get('requiredCoursesCompleted', 'false'); const electiveCompleted = Utils.storage.get('electiveCoursesCompleted', 'false'); if (requiredCompleted === 'true' && electiveCompleted === 'true') { console.log('🎉 所有课程均已完成!'); UI.updateStatus('🎉 所有课程已完成!', 'success'); alert('🎉 恭喜!所有必修和选修课程均已完成!'); return; } // 根据当前类型切换到另一种类型 if (currentType === '0') { // 当前是必修课程,标记必修课程完成,切换到选修课程 console.log('🎉 必修课程已完成,切换到选修课程'); Utils.storage.set('requiredCoursesCompleted', 'true'); UI.updateStatus('必修课程完成!切换到选修课程', 'success'); const electiveUrl = 'https://www.ahgbjy.gov.cn/pc/course/courselist.do?categoryid=&year=&coutype=1&mostNInput=0&mostHInput=0&mostTJInput=&keyword='; setTimeout(() => { window.location.replace(electiveUrl); }, 2000); } else { // 当前是选修课程,标记选修课程完成,切换到必修课程 console.log('🎉 选修课程已完成,切换到必修课程'); Utils.storage.set('electiveCoursesCompleted', 'true'); UI.updateStatus('选修课程完成!切换到必修课程', 'success'); const requiredUrl = 'https://www.ahgbjy.gov.cn/pc/course/courselist.do?categoryid=&year=&coutype=0&mostNInput=0&mostHInput=0&mostTJInput=&keyword='; setTimeout(() => { window.location.replace(requiredUrl); }, 2000); } } catch (error) { console.error(`课程类型切换错误: ${error.message}`); } }, // 提取课程ID extractCourseId: (courseElement) => { try { const href = courseElement.getAttribute('href') || courseElement.querySelector('a')?.getAttribute('href') || ''; return Utils.url.extractCourseId(href) || 'unknown'; } catch (error) { console.error(`提取课程ID错误: ${error.message}`); return 'unknown'; } }, // ───────────────────────────────────────────────────────────────────── // 🔍 章节处理算法 // ───────────────────────────────────────────────────────────────────── findAndClickIncompleteChapter: () => { Utils.safeExecute(() => { console.log('查找未完成章节'); const playButtons = Utils.$$('.playBtn[data-chapterid]'); for (let i = 0; i < playButtons.length; i++) { const button = playButtons[i]; const row = button.closest('tr'); if (!row) continue; const progressCells = row.querySelectorAll('td.col-md-2'); if (progressCells.length >= 2) { const progressText = progressCells[1].textContent; const match = progressText.match(/(\d+)%/); const progress = match ? parseInt(match[1]) : 0; if (progress < 100) { console.log(`找到未完成章节(进度:${progress}%),准备点击`); UI.updateStatus(`进入章节${i + 1}(进度:${progress}%)`, 'info'); const currentUrl = window.location.href; button.click(); // 智能备用:检查页面是否发生变化,如果没有则再次点击 setTimeout(() => { if (window.location.href === currentUrl) { const currentButton = Utils.$$('.playBtn[data-chapterid]')[i]; if (currentButton) { console.log('页面未跳转,执行备用章节点击'); currentButton.click(); } } }, 2000); return; } } } console.log('所有章节已完成,返回课程列表'); UI.updateStatus('课程已完成,返回列表', 'success'); setTimeout(() => CourseHandler.returnToCourseList(), 1000); }, '查找未完成章节失败'); }, // ───────────────────────────────────────────────────────────────────── // 📊 章节信息提取 // ───────────────────────────────────────────────────────────────────── extractChapterInfo: (courseId) => { Utils.safeExecute(() => { const playButtons = Utils.$$('.playBtn[data-chapterid]'); console.log(`找到 ${playButtons.length} 个章节`); playButtons.forEach((button, index) => { Utils.safeExecute(() => { const chapterId = button.getAttribute('data-chapterid'); if (!chapterId) return; const row = button.closest('tr'); if (!row) return; const colMd2Cells = row.querySelectorAll('td.col-md-2'); let totalMinutes = 30; let learnedPercent = 0; // 提取时长 if (colMd2Cells.length >= 1) { const timeText = colMd2Cells[0].textContent; if (timeText.includes('分钟')) { totalMinutes = Utils.extractMinutes(timeText); console.log(`章节${index + 1}时长: ${totalMinutes}分钟`); } } // 提取进度 if (colMd2Cells.length >= 2) { const progressText = colMd2Cells[1].textContent; const match = progressText.match(/(\d+)%/); if (match) { learnedPercent = parseInt(match[1]); console.log(`章节${index + 1}进度: ${learnedPercent}%`); } } // 计算剩余时间 const remainingPercent = Math.max(0, 100 - learnedPercent); const remainingMinutes = Math.ceil((totalMinutes * remainingPercent) / 100); // 保存时长信息 const key = `duration_${courseId}_${chapterId}`; Utils.storage.set(key, remainingMinutes.toString()); }, `章节${index + 1}信息提取错误`); }); }, '章节信息处理错误'); }, // 检查课程完成状态 checkCourseCompletion: () => { return Utils.safeExecute(() => { const colMd2Elements = document.getElementsByClassName('col-md-2'); if (colMd2Elements.length > 0) { const lastElement = colMd2Elements[colMd2Elements.length - 1]; const spans = lastElement.getElementsByTagName('span'); return spans.length > 0 && spans[0].innerHTML === '100'; } return false; }, '课程完成状态检查错误', false); }, // 返回课程列表 returnToCourseList: () => { Utils.safeExecute(() => { const coursetype = sessionStorage.getItem('lastCoutype') || '0'; const backUrl = `https://www.ahgbjy.gov.cn/pc/course/courselist.do?categoryid=&year=&coutype=${coursetype}&mostNInput=0&mostHInput=0&mostTJInput=&keyword=`; sessionStorage.setItem('returning', 'true'); Utils.navigateTo(backUrl, '返回课程列表'); }, '返回课程列表失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 🛣️ 路由管理系统 // ════════════════════════════════════════════════════════════════════════ const Router = { routes: { '/': () => Router.handleHomePage(), '/courseList.do': () => Router.handleCourseListPage(), '/coursedetail.do': () => Router.handleCourseDetailPage(), '/playvideo.do': () => Router.handleVideoPage(), '/playscorm.do': () => Router.handleScormPage() }, init: () => { Utils.safeExecute(() => { Router.handleCurrentPage(); console.log('路由管理器已初始化'); }, '路由管理器初始化失败'); }, handleCurrentPage: () => { Utils.safeExecute(() => { const path = window.location.pathname; const search = window.location.search; const url = window.location.href; console.log(`当前页面: ${path}${search}`); // 检查是否为登录页面,如果是则不执行任何操作 if (url.includes('/pc/login.do')) { console.log('检测到登录页面,脚本暂停工作'); UI.updateStatus('登录页面 - 脚本已暂停', 'info'); return; } // 保存课程类型参数 if (url.includes('courselist.do') && /[?&]coutype=\d/.test(url)) { const match = url.match(/coutype=(\d+)/); if (match) { sessionStorage.setItem('lastCoutype', match[1]); } } // 检查返回状态 if (sessionStorage.getItem('returning') === 'true' && url.includes('courselist.do')) { console.log('检测到从课程页面返回'); sessionStorage.removeItem('returning'); setTimeout(() => Router.handleCourseListPage(), 2000); return; } if (url.includes('courselist.do')) { console.log('当前页面: 课程列表'); setTimeout(() => Router.handleCourseListPage(), 1000); } else if (url.includes('coursedetail.do')) { console.log('当前页面: 课程详情'); setTimeout(() => Router.handleCourseDetailPage(), 1000); } else if (url.includes('playvideo.do') || url.includes('playscorm.do')) { console.log('当前页面: 学习页面'); setTimeout(() => Router.handleVideoPage(), 1000); } else { console.log('当前页面: 首页或其他'); Router.handleHomePage(); } }, '页面处理失败'); }, // ───────────────────────────────────────────────────────────────────── // 🏠 主页处理 // ───────────────────────────────────────────────────────────────────── handleHomePage: () => { Utils.safeExecute(() => { UI.updateStatus('首页已加载,请手动进入课程列表', 'info'); console.log('首页已加载,脚本不会自动跳转到课程列表'); }, '首页处理失败'); }, // ───────────────────────────────────────────────────────────────────── // 📚 课程列表页处理 // ───────────────────────────────────────────────────────────────────── handleCourseListPage: async () => { Utils.safeExecute(async () => { console.log('开始处理课程列表页面'); const currentType = Utils.url.getParam('coutype') || '0'; const typeName = currentType === '0' ? '必修' : '选修'; UI.updateStatus(`正在分析${typeName}课程列表...`, 'info'); // 等待页面加载 await Utils.waitForElement('.coursespan', 5000); const courseElements = Utils.$$('.coursespan'); if (courseElements.length === 0) { console.error('未找到课程元素'); UI.updateStatus('未找到课程元素', 'error'); return; } console.log(`找到 ${courseElements.length} 个${typeName}课程`); UI.updateStatus(`正在分析 ${courseElements.length} 个${typeName}课程`, 'info'); // 统计课程状态 const stats = { completed: 0, learning: 0, uncompleted: 0 }; const visitedCourses = Utils.storage.getVisited(); courseElements.forEach(el => { const status = el.lastChild?.innerHTML?.trim() || ''; if (status === "已完成") stats.completed++; else if (status === "学习中") stats.learning++; else stats.uncompleted++; }); console.log(`${typeName}课程状态 - 已完成: ${stats.completed}, 学习中: ${stats.learning}, 未完成: ${stats.uncompleted}`); // 如果当前页所有课程已完成,尝试翻页 if (stats.completed === courseElements.length) { console.log(`当前页所有${typeName}课程已完成`); UI.updateStatus(`当前页${typeName}课程已完成,准备翻页`, 'success'); Utils.storage.clearVisited(); setTimeout(async () => { if (await CourseHandler.handlePagination()) { return; } console.log(`所有${typeName}课程页面已完成,切换课程类型`); CourseHandler.switchCourseType(); }, 2000); return; } // 选择课程学习 const selectedCourse = CourseHandler.selectCourse(courseElements, visitedCourses); if (selectedCourse) { CourseHandler.openCourse(selectedCourse); } else { console.log('所有课程都已访问,重置记录'); Utils.storage.clearVisited(); setTimeout(() => Router.handleCourseListPage(), 2000); } }, '课程列表页处理失败'); }, // ───────────────────────────────────────────────────────────────────── // 📖 课程详情页处理 // ───────────────────────────────────────────────────────────────────── handleCourseDetailPage: async () => { Utils.safeExecute(async () => { console.log('处理课程详情页'); UI.updateStatus('分析课程详情...', 'info'); // 添加页面焦点监听器 const handlePageFocus = () => { console.log('课程详情页获得焦点,重新检查状态'); setTimeout(() => { Router.handleCourseDetailPage(); }, 1000); }; // 只添加一次焦点监听器 if (!window.focusListenerAdded) { window.addEventListener('focus', handlePageFocus); window.focusListenerAdded = true; } const courseId = Utils.url.extractCourseId(window.location.href); if (!courseId) { console.error('无法获取课程ID'); return; } console.log(`课程ID: ${courseId}`); // 等待页面加载 await Utils.waitForElement('.playBtn[data-chapterid]', 3000); // 提取章节信息 CourseHandler.extractChapterInfo(courseId); // 检查课程完成状态 if (CourseHandler.checkCourseCompletion()) { console.log('课程已完成,返回列表'); UI.updateStatus('课程已完成,返回列表', 'success'); UI.stats.coursesCompleted++; setTimeout(() => CourseHandler.returnToCourseList(), 1000); return; } // 检查是否从学习页面返回或手动关闭 const fromLearning = sessionStorage.getItem('fromLearningPage'); const lastActiveTime = sessionStorage.getItem('lastActiveTime'); const currentTime = Date.now(); // 如果超过一定时间没有活动,强制刷新状态 if (lastActiveTime && (currentTime - parseInt(lastActiveTime)) > 60000) { console.log('检测到长时间无活动,强制刷新状态'); sessionStorage.removeItem('lastActiveTime'); sessionStorage.removeItem('fromLearningPage'); } if (fromLearning === 'true') { console.log('从学习页面返回,继续下一章节'); sessionStorage.removeItem('fromLearningPage'); sessionStorage.setItem('lastActiveTime', currentTime.toString()); setTimeout(() => CourseHandler.findAndClickIncompleteChapter(), 1000); return; } // 首次进入,查找并开始学习 const startButton = Utils.$('.btn.btn-default.startlearn'); if (startButton) { console.log(`找到开始学习按钮: ${startButton.textContent.trim()}`); UI.updateStatus('准备开始学习...', 'info'); sessionStorage.setItem('lastActiveTime', currentTime.toString()); startButton.click(); } else { sessionStorage.setItem('lastActiveTime', currentTime.toString()); CourseHandler.findAndClickIncompleteChapter(); } }, '课程详情页处理失败'); }, // ───────────────────────────────────────────────────────────────────── // 🎬 学习页面处理 // ───────────────────────────────────────────────────────────────────── handleVideoPage: async () => { Utils.safeExecute(async () => { console.log('处理学习页面'); UI.updateStatus('正在学习课程...', 'info'); const courseId = Utils.url.extractCourseId(window.location.href); const chapterId = Utils.url.extractChapterId(window.location.href); console.log(`学习页面 - 课程ID: ${courseId}, 章节ID: ${chapterId}`); // 等待完成按钮出现 await Utils.waitForElement('.btn.btn-default:nth-child(2)', 5000); const completeButton = Utils.$('.btn.btn-default:nth-child(2)'); if (!completeButton) { console.error('未找到完成按钮'); return; } console.log(`找到完成按钮: ${completeButton.textContent.trim()}`); // 获取学习时长 let duration = 30; // 默认30分钟 if (courseId && chapterId) { const storedDuration = Utils.storage.get(`duration_${courseId}_${chapterId}`); if (storedDuration) { duration = Math.max(parseInt(storedDuration), 1); } } // 计算等待时间(100%剩余时长 + 1分钟安全余量,最少30秒) const waitTime = Math.max((duration + 1) * 60 * 1000, 30000); const waitMinutes = Math.round(waitTime / 60000); console.log(`等待 ${waitMinutes} 分钟后完成学习`); // 标记学习状态 sessionStorage.setItem('fromLearningPage', 'true'); // 使用带倒计时的学习时间处理 CourseHandler.startStudyTime(waitMinutes, completeButton); }, '学习页处理失败'); }, handleScormPage: () => { // SCORM页面使用相同的处理逻辑 Router.handleVideoPage(); } }; // ════════════════════════════════════════════════════════════════════════ // 🚀 主应用程序 // ════════════════════════════════════════════════════════════════════════ const App = { init: () => { Utils.safeExecute(() => { console.log('安徽干部在线教育自动学习 V1.0.1 启动(优化版:自动选课 + 智能学习 + 防休眠)'); // 初始化各模块 VideoAutoplayBlocker.init(); BackgroundMonitor.init(); Utils.setupProtection(); // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', App.start); } else { App.start(); } }, '应用初始化失败'); }, start: () => { Utils.safeExecute(() => { if (!document.body) { setTimeout(App.start, 100); return; } console.log('页面加载完成,启动主程序'); // 初始化防休眠系统 WakeLockManager.init(); // 记录初始URL和活动时间 sessionStorage.setItem('lastUrl', window.location.href); sessionStorage.setItem('lastActiveTime', Date.now().toString()); // 初始化UI和路由 UI.init(); CourseHandler.init(); Router.init(); console.log('所有模块启动完成'); }, '应用启动失败'); } }; // ════════════════════════════════════════════════════════════════════════ // 🧹 系统清理与启动 // ════════════════════════════════════════════════════════════════════════ // 页面卸载时清理资源 window.addEventListener('beforeunload', () => { Utils.safeExecute(() => { WakeLockManager.cleanup(); BackgroundMonitor.cleanup(); console.log('✅ 应用已安全清理'); }, '应用清理失败'); }); // 🚀 启动应用程序 App.init(); })(); /** * ┌─────────────────────────────────────────────────────────────────────────┐ * │ ✨ 脚本运行完毕 ✨ │ * │ │ * │ 感谢使用安徽干部在线教育自动学习脚本! │ * │ 如有问题请联系开发者:Moker32 │ * │ │ * │ 🎯 功能特性:自动选课 + 智能学习 + 防休眠 │ * │ 💫 技术栈:ES6+ + WebAPI + Tampermonkey │ * │ 🌟 版本:V1.0.1 优化版 │ * └─────────────────────────────────────────────────────────────────────────┘ */