// ==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 优化版 │
* └─────────────────────────────────────────────────────────────────────────┘
*/