// ==UserScript==
// @name 地理信息安全在线培训自动化学习(活跃度增强版)
// @namespace http://tampermonkey.net/
// @version 5.0
// @description 增强用户活跃度模拟,确保持续计算学时,平滑滚动和定期交互
// @author YourName
// @match https://gistraining.webmap.cn/*
// @grant GM_addStyle
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
console.log("[培训助手] 脚本开始执行");
// 样式省略...与之前相同
GM_addStyle(`
#control-panel {
position: fixed !important;
bottom: 20px !important;
right: 20px !important;
width: 340px !important;
background: linear-gradient(135deg, #1e88e5, #1565c0) !important;
border-radius: 12px !important;
box-shadow: 0 4px 20px rgba(0,0,0,0.3) !important;
color: white !important;
font-family: 'Segoe UI', Arial, sans-serif !important;
z-index: 999999 !important;
padding: 15px !important;
transition: all 0.3s ease !important;
}
/* 其他样式省略... */
`);
// 脚本状态管理
const scriptState = {
isRunning: false,
scrollInterval: null, // 大幅度滚动间隔
microScrollInterval: null, // 微小滚动间隔(保持活跃)
clickInterval: null, // 点击间隔
moveInterval: null, // 鼠标移动间隔
activityInterval: null, // 活跃度检查间隔
idleTimeout: null, // 检测页面空闲的超时
nextContentInterval: null, // 下一个内容切换间隔
lastActivityTime: Date.now(),// 最后活动时间
currentContentIndex: -1, // 当前内容索引
courseList: [], // 课程列表
currentPage: 1, // 当前页码
totalPages: 1, // 总页数
completedCourses: new Set(), // 已完成课程集合
courseProgress: { // 课程进度
total: 0, // 总课程数
completed: 0, // 已完成数
current: null // 当前课程
},
activityStats: { // 活跃度统计
scrolls: 0, // 滚动次数
clicks: 0, // 点击次数
keystrokes: 0, // 按键次数
mousemoves: 0 // 鼠标移动次数
},
scrollPosition: 0, // 当前滚动位置
scrollDirection: 1, // 1向下,-1向上
bottomReachedCount: 0, // 到达底部次数
waitingForNextCourse: false, // 是否等待下一课
debugInfo: "", // 调试信息
currentContentId: null, // 当前内容ID
scrollingContainer: null, // 滚动容器
startTime: null, // 开始时间
config: {
majorScrollInterval: 30000, // 主要滚动间隔(毫秒)
microScrollInterval: 10000, // 微小滚动间隔(毫秒)
clickInterval: 15000, // 点击间隔(毫秒)
moveInterval: 5000, // 鼠标移动间隔(毫秒)
activityInterval: 20000, // 活跃度触发间隔(毫秒)
idleThreshold: 60000, // 空闲检测阈值(毫秒)
nextContentDelay: 5000, // 切换下一课延迟(毫秒)
scrollSpeed: 2000, // 滚动速度(毫秒)
scrollStep: 80, // 每次滚动步长(像素)
microScrollStep: 10, // 微小滚动步长(像素)
pauseAtBottom: 8000, // 底部暂停(毫秒)
pauseAtTop: 3000, // 顶部暂停(毫秒)
minReadTime: 180000, // 最短阅读时间(毫秒)
scrollTimeThreshold: 2, // 底部达到次数阈值
activityTypes: ['scroll', 'click', 'mousemove', 'keypress', 'studytime'],
autoNextContent: true, // 自动切换下一课
debug: true, // 调试模式
studyTimeInterval: 30000 // 每30秒触发一次学习时间更新
}
};
// 调试日志
function log(message) {
if (scriptState.config.debug) {
console.log(`[培训助手] ${new Date().toLocaleTimeString()} - ${message}`);
scriptState.debugInfo = message + "\n" + scriptState.debugInfo.substring(0, 300);
updateStatus(message);
}
}
// 记录活动统计
function recordActivity(type) {
scriptState.lastActivityTime = Date.now();
switch(type) {
case 'scroll':
scriptState.activityStats.scrolls++;
break;
case 'click':
scriptState.activityStats.clicks++;
break;
case 'keypress':
scriptState.activityStats.keystrokes++;
break;
case 'mousemove':
scriptState.activityStats.mousemoves++;
break;
}
// 更新界面中的活跃度数据
updateActivityStats();
// 清除之前的空闲检测
if (scriptState.idleTimeout) {
clearTimeout(scriptState.idleTimeout);
}
// 如果太久没活动,设置一个新的空闲检测
scriptState.idleTimeout = setTimeout(() => {
if (scriptState.isRunning) {
log("检测到可能的空闲,触发额外活动");
performRandomActivity();
}
}, scriptState.config.idleThreshold);
}
// 执行随机活动
function performRandomActivity() {
if (!scriptState.isRunning) return;
const activities = [
() => simulateScrolling(true), // 强制滚动
() => simulateClick(), // 模拟点击
() => simulateMouseMovement(), // 鼠标移动
() => simulateKeyPress(), // 键盘按键
() => triggerStudyTime() // 触发学习时间
];
// 随机选择1-3个活动执行
const count = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < count; i++) {
const activity = activities[Math.floor(Math.random() * activities.length)];
activity();
}
}
// 触发学习时间更新
function triggerStudyTime() {
try {
// 尝试触发页面自带的jstime函数
if (typeof window.jstime === 'function') {
window.jstime();
log("触发页面jstime函数");
}
// 使用submitAjax更新学习时间
if (typeof window.submitAjax === 'function') {
const studyTime = Math.floor(Math.random() * 30) + 10; // 10-40秒
window.submitAjax({'url': `index.php?course-app-course-ajax-updatestudytime&studytime=${studyTime}`});
log(`通过AJAX更新学习时间: ${studyTime}秒`);
}
// 重置计时器
if (typeof window.timeUserFun === 'function') {
window.timeUserFun(2);
log("重置页面计时器");
}
recordActivity('studytime');
return true;
} catch (e) {
log(`触发学习时间失败: ${e.message}`);
return false;
}
}
// 随机延迟
function randomDelay(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 平滑滚动
function smoothScroll(element, target, duration) {
if (!element) return;
const start = element.scrollTop;
const change = target - start;
const startTime = performance.now();
function animateScroll(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用缓动函数让滚动更自然
const easeProgress = 0.5 - 0.5 * Math.cos(progress * Math.PI);
element.scrollTop = start + change * easeProgress;
// 更新滚动进度条
updateScrollProgressBar(element);
if (progress < 1) {
requestAnimationFrame(animateScroll);
} else {
// 滚动完成时记录活动
recordActivity('scroll');
}
}
requestAnimationFrame(animateScroll);
}
// 更新滚动进度条
function updateScrollProgressBar(element) {
if (!element) return;
const scrollBar = document.querySelector('.scroll-progress-bar');
if (scrollBar) {
const maxScroll = element.scrollHeight - element.clientHeight;
if (maxScroll > 0) {
const progress = (element.scrollTop / maxScroll) * 100;
scrollBar.style.width = `${progress}%`;
}
}
}
// 获取滚动容器
function getScrollContainer() {
// 尝试找到滚动容器
const mainContainers = [
document.querySelector('.box.itembox[style*="overflow-y: scroll"]'),
document.querySelector('.box.itembox[style*="overflow"]'),
document.querySelector('div[style*="overflow-y: scroll"]'),
document.querySelector('div[style*="overflow: auto"]'),
document.querySelector('.box.itembox:not(.collapsed)')
];
// 找到第一个有效的容器
return mainContainers.find(container => container && container.scrollHeight > container.clientHeight);
}
// 检查是否已滚动到底部
function isScrolledToBottom(element) {
if (!element) return false;
return element.scrollHeight - element.scrollTop <= element.clientHeight + 100;
}
// 检查是否已滚动到顶部
function isScrolledToTop(element) {
if (!element) return true;
return element.scrollTop <= 50;
}
// 模拟微小滚动 - 保持活跃度
function simulateMicroScroll() {
if (!scriptState.isRunning) return;
const scrollContainer = scriptState.scrollingContainer || getScrollContainer();
if (!scrollContainer) return;
// 当前滚动位置
const currentScroll = scrollContainer.scrollTop;
const maxScroll = scrollContainer.scrollHeight - scrollContainer.clientHeight;
if (maxScroll <= 0) return;
// 产生一个微小的随机滚动偏移
const offset = (Math.random() > 0.5 ? 1 : -1) * Math.floor(Math.random() * scriptState.config.microScrollStep);
// 确保不会滚出边界
let targetScroll = currentScroll + offset;
if (targetScroll < 0) targetScroll = 0;
if (targetScroll > maxScroll) targetScroll = maxScroll;
// 执行微小滚动
smoothScroll(scrollContainer, targetScroll, 500);
// 模拟用户还在阅读
recordActivity('scroll');
}
// 模拟用户滚动 - 大幅度滚动
function simulateScrolling(force = false) {
if (!scriptState.isRunning) return;
// 查找滚动容器
const scrollContainer = scriptState.scrollingContainer || getScrollContainer();
if (!scrollContainer) {
log("未找到可滚动容器,标记为已完成");
scriptState.bottomReachedCount = scriptState.config.scrollTimeThreshold;
checkCourseCompletion();
return;
}
scriptState.scrollingContainer = scrollContainer;
// 只在第一次或强制滚动时输出日志
if (force || scriptState.activityStats.scrolls === 0) {
log("开始模拟滚动行为");
}
const maxScroll = scrollContainer.scrollHeight - scrollContainer.clientHeight;
if (maxScroll <= 0) {
log("内容不足,无需滚动,直接标记为完成");
scriptState.bottomReachedCount = scriptState.config.scrollTimeThreshold;
checkCourseCompletion();
return;
}
// 获取当前滚动位置
const currentScroll = scrollContainer.scrollTop;
// 根据方向计算下一个滚动目标
let targetScroll;
if (scriptState.scrollDirection === 1) { // 向下滚动
targetScroll = Math.min(currentScroll + randomDelay(100, 300), maxScroll);
// 检查是否到达底部
if (targetScroll >= maxScroll - 100) {
targetScroll = maxScroll;
// 模拟用户滚动到底部后的行为
smoothScroll(scrollContainer, targetScroll, scriptState.config.scrollSpeed);
scriptState.bottomReachedCount++;
log(`到达底部第 ${scriptState.bottomReachedCount} 次,暂停 ${scriptState.config.pauseAtBottom/1000} 秒`);
// 在底部停留一段时间,然后改变方向
setTimeout(() => {
if (scriptState.isRunning) {
scriptState.scrollDirection = -1; // 改变方向
log("开始向上滚动");
// 检查是否已经满足完成条件
if (scriptState.bottomReachedCount >= scriptState.config.scrollTimeThreshold) {
// 检查是否阅读时间足够
const elapsed = Date.now() - (scriptState.startTime || 0);
if (elapsed >= scriptState.config.minReadTime) {
log(`已满足阅读条件,底部次数: ${scriptState.bottomReachedCount},时间: ${Math.floor(elapsed/1000)}秒`);
checkCourseCompletion();
} else {
log(`已到达底部足够次数,但阅读时间不足,还需 ${Math.floor((scriptState.config.minReadTime - elapsed)/1000)} 秒`);
}
}
}
}, scriptState.config.pauseAtBottom);
return;
}
} else { // 向上滚动
targetScroll = Math.max(currentScroll - randomDelay(100, 300), 0);
// 检查是否到达顶部
if (targetScroll <= 100) {
targetScroll = 0;
// 模拟用户滚动到顶部后的行为
smoothScroll(scrollContainer, targetScroll, scriptState.config.scrollSpeed);
// 在顶部停留一段时间,然后改变方向
setTimeout(() => {
if (scriptState.isRunning) {
scriptState.scrollDirection = 1; // 改变方向
log("开始向下滚动");
}
}, scriptState.config.pauseAtTop);
return;
}
}
// 执行滚动
smoothScroll(scrollContainer, targetScroll, scriptState.config.scrollSpeed);
// 模拟用户行为,记录活动
recordActivity('scroll');
}
// 模拟鼠标移动 - 兼容版
function simulateMouseMovement() {
if (!scriptState.isRunning) return;
const scrollContainer = scriptState.scrollingContainer || getScrollContainer();
if (!scrollContainer) return;
try {
const rect = scrollContainer.getBoundingClientRect();
const randomX = Math.round(rect.left + Math.random() * rect.width);
const randomY = Math.round(rect.top + Math.random() * rect.height);
// 方法1: 使用旧的事件API
try {
const evt = document.createEvent('MouseEvents');
evt.initMouseEvent('mousemove', true, true, window, 0,
0, 0, randomX, randomY, false, false, false, false, 0, null);
scrollContainer.dispatchEvent(evt);
recordActivity('mousemove');
return;
} catch (e) {
// 如果旧API失败,尝试新API
}
// 方法2: 使用新的MouseEvent API (不带view参数)
try {
const moveEvent = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
clientX: randomX,
clientY: randomY
});
scrollContainer.dispatchEvent(moveEvent);
recordActivity('mousemove');
} catch (e) {
// 忽略错误
}
} catch (e) {
// 忽略错误
}
}
// 模拟用户点击 - 兼容版
function simulateClick() {
if (!scriptState.isRunning) return;
try {
const scrollContainer = scriptState.scrollingContainer || getScrollContainer();
let randomX, randomY;
// 如果找到了滚动容器,优先在其中点击
if (scrollContainer) {
const rect = scrollContainer.getBoundingClientRect();
randomX = Math.round(rect.left + Math.random() * rect.width * 0.8);
randomY = Math.round(rect.top + Math.random() * rect.height * 0.8);
} else {
// 否则在页面随机位置点击
randomX = Math.round(Math.random() * window.innerWidth * 0.7);
randomY = Math.round(Math.random() * window.innerHeight * 0.7);
}
// 获取目标元素
const element = document.elementFromPoint(randomX, randomY);
if (!element) return;
// 尝试不同的点击方法
// 方法1: 最简单的方法
try {
element.click();
recordActivity('click');
log(`点击元素 ${element.tagName}`);
return;
} catch (e) {
// 尝试下一种方法
}
// 方法2: 使用旧的事件API
try {
const evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window, 1,
0, 0, randomX, randomY, false, false, false, false, 0, null);
element.dispatchEvent(evt);
recordActivity('click');
return;
} catch (e) {
// 尝试下一种方法
}
// 方法3: 使用新的MouseEvent API (不带view参数)
try {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: randomX,
clientY: randomY
});
element.dispatchEvent(clickEvent);
recordActivity('click');
} catch (err) {
log(`点击模拟失败: ${err.message}`);
}
} catch (error) {
log(`点击模拟完全失败: ${error.message}`);
}
}
// 模拟键盘按键
function simulateKeyPress() {
if (!scriptState.isRunning) return;
// 安全的键码列表
const safeKeys = [
{ key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
{ key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
{ key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
{ key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
{ key: 'PageDown', code: 'PageDown', keyCode: 34 },
{ key: 'PageUp', code: 'PageUp', keyCode: 33 },
{ key: 'Home', code: 'Home', keyCode: 36 },
{ key: 'End', code: 'End', keyCode: 35 },
{ key: 'Space', code: 'Space', keyCode: 32 }
];
// 随机选择一个键
const randomKey = safeKeys[Math.floor(Math.random() * safeKeys.length)];
try {
// 方法1: 使用旧的事件API
try {
const evt = document.createEvent('KeyboardEvent');
// 尝试不同的初始化方法
if (typeof evt.initKeyboardEvent !== 'undefined') {
evt.initKeyboardEvent('keydown', true, true, window,
randomKey.key, 0, '', false, '');
} else if (typeof evt.initKeyEvent !== 'undefined') {
evt.initKeyEvent('keydown', true, true, window,
false, false, false, false,
randomKey.keyCode, 0);
}
document.dispatchEvent(evt);
recordActivity('keypress');
return;
} catch (e) {
// 尝试下一种方法
}
// 方法2: 使用新的KeyboardEvent API
try {
const keyEvent = new KeyboardEvent('keydown', {
key: randomKey.key,
code: randomKey.code,
keyCode: randomKey.keyCode,
bubbles: true,
cancelable: true
});
document.dispatchEvent(keyEvent);
recordActivity('keypress');
} catch (err) {
// 忽略错误
}
} catch (error) {
// 忽略错误
}
}
// 维持活跃状态 - 定期执行的活跃度保持函数
function maintainActivity() {
if (!scriptState.isRunning) return;
// 检查距离上次活动的时间
const timeSinceLastActivity = Date.now() - scriptState.lastActivityTime;
// 如果超过阈值,触发随机活动
if (timeSinceLastActivity > scriptState.config.idleThreshold / 2) {
log("检测到活跃度下降,触发随机活动");
performRandomActivity();
} else {
// 即使活跃,也随机执行一些小动作
if (Math.random() > 0.7) {
const activities = [
() => simulateMouseMovement(),
() => simulateMicroScroll(),
() => triggerStudyTime()
];
const activity = activities[Math.floor(Math.random() * activities.length)];
activity();
}
}
}
// 检查当前课程是否完成
function checkCourseCompletion() {
if (!scriptState.isRunning || scriptState.waitingForNextCourse) return;
// 检查滚动到底部次数和总阅读时间
const elapsed = Date.now() - (scriptState.startTime || 0);
const hasReachedBottom = scriptState.bottomReachedCount >= scriptState.config.scrollTimeThreshold;
const hasReadLongEnough = elapsed >= scriptState.config.minReadTime;
log(`检查课程完成情况: 底部次数=${scriptState.bottomReachedCount}/${scriptState.config.scrollTimeThreshold}, 阅读时间=${Math.floor(elapsed/1000)}秒/${scriptState.config.minReadTime/1000}秒`);
// 只有当两个条件都满足时,才认为课程完成
if (hasReachedBottom && hasReadLongEnough) {
const currentUrl = window.location.href;
const contentId = extractContentId(currentUrl);
if (contentId && !scriptState.completedCourses.has(contentId)) {
log(`课程ID ${contentId} 已完成,准备切换下一课`);
scriptState.completedCourses.add(contentId);
scriptState.courseProgress.completed++;
// 强制发送学习时间
try {
if (typeof window.submitAjax === 'function') {
window.submitAjax({'url': "index.php?course-app-course-ajax-updatestudytime&studytime=50"});
log("提交最终学习时间记录");
}
} catch(e) {
// 忽略错误
}
if (scriptState.config.autoNextContent) {
scriptState.waitingForNextCourse = true;
log(`等待 ${scriptState.config.nextContentDelay/1000} 秒后切换到下一课...`);
setTimeout(() => {
goToNextContent();
scriptState.waitingForNextCourse = false;
scriptState.bottomReachedCount = 0;
}, scriptState.config.nextContentDelay);
}
} else {
// 重新获取课程列表并尝试查找当前课程
refreshCourseStatus();
}
updateUI();
}
}
// 从URL中提取contentid
function extractContentId(url) {
const match = url.match(/contentid=(\d+)/);
return match ? match[1] : null;
}
// 刷新课程状态
function refreshCourseStatus() {
log("刷新课程状态");
// 从URL获取当前课程ID
const currentUrl = window.location.href;
const contentId = extractContentId(currentUrl);
if (contentId) {
scriptState.currentContentId = contentId;
log(`当前课程ID: ${contentId}`);
}
// 刷新课程列表
scriptState.courseList = getContentList();
if (scriptState.courseList.length > 0) {
log(`找到 ${scriptState.courseList.length} 个课程`);
// 找到当前课程索引
if (contentId) {
for (let i = 0; i < scriptState.courseList.length; i++) {
const href = scriptState.courseList[i].getAttribute('href');
if (href && href.includes(`contentid=${contentId}`)) {
scriptState.currentContentIndex = i;
log(`当前课程索引: ${i}`);
break;
}
}
}
// 如果没找到对应索引,尝试其他方法定位
if (scriptState.currentContentIndex === -1) {
scriptState.currentContentIndex = findCurrentCourseIndex();
}
scriptState.courseProgress.total = scriptState.courseList.length;
updateUI();
// 检查是否可以切换到下一课
const nextIndex = scriptState.currentContentIndex + 1;
if (nextIndex < scriptState.courseList.length) {
log(`可以切换到下一课 (${nextIndex + 1}/${scriptState.courseList.length})`);
} else {
// 检查是否有下一页
checkAndNavigateToNextPage();
}
} else {
log("未找到任何课程,将尝试从分页导航");
tryFindPagination();
}
}
// 尝试查找分页导航
function tryFindPagination() {
const pagination = document.querySelector('.pagination');
if (!pagination) {
log("未找到分页导航");
return false;
}
const pageLinks = pagination.querySelectorAll('a');
if (pageLinks.length === 0) {
log("分页导航中没有链接");
return false;
}
log(`找到分页链接 ${pageLinks.length} 个`);
// 查找当前页码和下一页
let currentPage = 1;
let nextPageLink = null;
for (const link of pageLinks) {
if (link.classList.contains('current')) {
const pageNum = parseInt(link.textContent);
if (!isNaN(pageNum)) {
currentPage = pageNum;
}
}
// 寻找下一页链接
if (link.textContent.includes('下一页') ||
(parseInt(link.textContent) === currentPage + 1)) {
nextPageLink = link;
}
}
scriptState.currentPage = currentPage;
log(`当前页码: ${currentPage}`);
if (nextPageLink) {
log(`找到下一页链接: ${nextPageLink.getAttribute('href')}`);
return nextPageLink;
}
return false;
}
// 检查并导航到下一页
function checkAndNavigateToNextPage() {
const nextPageLink = tryFindPagination();
if (nextPageLink) {
log("当前页所有课程已完成,将跳转到下一页");
scriptState.waitingForNextCourse = true;
setTimeout(() => {
const href = nextPageLink.getAttribute('href');
if (href) {
log(`跳转到下一页: ${href}`);
window.location.href = href;
}
}, scriptState.config.nextContentDelay);
return true;
}
log("已完成所有课程,或无下一页");
return false;
}
// 查找当前课程索引
function findCurrentCourseIndex() {
// 方法1:查找带有特殊样式或类的元素
for (let i = 0; i < scriptState.courseList.length; i++) {
const course = scriptState.courseList[i];
if (course.classList.contains('active') ||
course.classList.contains('current') ||
course.style.fontWeight === 'bold' ||
course.style.color === 'rgb(255, 0, 0)') {
return i;
}
}
// 方法2:从页面标题匹配
const pageTitle = document.querySelector('h4.title');
if (pageTitle) {
const title = pageTitle.textContent.trim();
for (let i = 0; i < scriptState.courseList.length; i++) {
if (scriptState.courseList[i].textContent.includes(title)) {
return i;
}
}
}
// 方法3:从主内容匹配
const mainContent = document.querySelector('.box.itembox[style*="overflow"]');
if (mainContent) {
const content = mainContent.textContent.trim().substring(0, 50);
for (let i = 0; i < scriptState.courseList.length; i++) {
const courseTitle = scriptState.courseList[i].textContent.trim();
if (content.includes(courseTitle) || courseTitle.includes(content.substring(0, 10))) {
return i;
}
}
}
// 如果无法确定,返回0(第一个)
return 0;
}
// 获取右侧内容列表
function getContentList() {
log("开始查找课程列表");
// 首先尝试获取右侧栏所有课程
const rightColumn = document.querySelector('.col-xs-5.pull-right');
if (rightColumn) {
log("找到右侧栏");
// 尝试查找带有课程链接的容器
const courseContainers = rightColumn.querySelectorAll('.box.itembox');
const allLinks = [];
courseContainers.forEach(container => {
// 找到容器中的所有链接
const links = container.querySelectorAll('a[href*="contentid"]');
links.forEach(link => allLinks.push(link));
});
if (allLinks.length > 0) {
log(`在右侧栏找到 ${allLinks.length} 个课程链接`);
return filterCourseLinks(allLinks);
}
// 备选:获取右侧栏中所有链接
const allRightLinks = rightColumn.querySelectorAll('a');
if (allRightLinks.length > 0) {
log(`在右侧栏找到 ${allRightLinks.length} 个链接`);
return filterCourseLinks(Array.from(allRightLinks));
}
}
// 尝试找到课程盒子
const courseBoxes = document.querySelectorAll('.box.itembox');
for (const box of courseBoxes) {
// 检查是否包含标题
const title = box.querySelector('h4.title');
if (title) {
const links = box.querySelectorAll('a');
if (links.length > 0) {
log(`从课程盒子找到 ${links.length} 个链接`);
return filterCourseLinks(Array.from(links));
}
}
}
// 尝试直接查找所有课程链接
const contentLinks = document.querySelectorAll('a[href*="contentid"]');
if (contentLinks.length > 0) {
log(`直接查找contentid链接,找到 ${contentLinks.length} 个`);
return filterCourseLinks(Array.from(contentLinks));
}
// 备选:查找所有课程相关链接
const courseLinks = document.querySelectorAll('a[href*="course-app-course"]');
if (courseLinks.length > 0) {
log(`查找course-app-course链接,找到 ${courseLinks.length} 个`);
return filterCourseLinks(Array.from(courseLinks));
}
log("未找到任何课程链接");
return [];
}
// 过滤有效的课程链接
function filterCourseLinks(links) {
if (!links || links.length === 0) return [];
// 收集所有可能的课程链接
const validLinks = [];
const seenHrefs = new Set();
for (const link of links) {
if (!link || !link.getAttribute) continue;
const href = link.getAttribute('href') || '';
const text = link.textContent.trim();
// 排除明显不是课程的链接
if (text.includes('首页') ||
text.includes('下一页') ||
text.includes('上一页') ||
text.includes('共') ||
text === '' ||
text.match(/^\d+$/)) {
continue;
}
// 检查是否是课程链接
if ((href.includes('contentid=') || href.includes('course-app-course')) && !seenHrefs.has(href)) {
seenHrefs.add(href);
validLinks.push(link);
}
}
return validLinks;
}
// 切换到下一个内容
function goToNextContent() {
if (!scriptState.isRunning) return;
log("准备切换到下一课");
// 重新获取课程列表和当前状态
refreshCourseStatus();
if (scriptState.courseList.length === 0) {
log("没有找到课程列表,尝试重新加载页面");
setTimeout(() => {
window.location.reload();
}, 2000);
return;
}
// 确定下一个课程索引
const nextIndex = scriptState.currentContentIndex + 1;
if (nextIndex >= scriptState.courseList.length) {
log("已达到当前页最后一个课程,检查是否有下一页");
if (!checkAndNavigateToNextPage()) {
log("没有下一页或已是最后一页,停止自动化");
stopScript();
}
return;
}
const nextCourse = scriptState.courseList[nextIndex];
if (!nextCourse) {
log("无法获取下一课程信息");
return;
}
log(`切换到下一个内容 ${nextIndex+1}/${scriptState.courseList.length}: ${nextCourse.textContent.trim()}`);
// 准备课程切换
scriptState.currentContentIndex = nextIndex;
scriptState.courseProgress.current = nextCourse.textContent.trim();
scriptState.bottomReachedCount = 0;
scriptState.scrollDirection = 1; // 重置滚动方向
scriptState.scrollCount = 0; // 重置滚动计数
// 尝试切换课程
const href = nextCourse.getAttribute('href');
if (!href) {
log("错误:下一课程链接无效");
return;
}
// 强制使用href方式导航(更可靠)
log(`使用href导航: ${href}`);
window.location.href = href;
updateUI();
}
// 强制直接跳转到指定课程
function jumpToCourse(index) {
if (index < 0 || index >= scriptState.courseList.length) {
log(`无效的课程索引: ${index}`);
return;
}
const course = scriptState.courseList[index];
if (!course) {
log("无法获取课程信息");
return;
}
log(`直接跳转到课程 ${index+1}/${scriptState.courseList.length}: ${course.textContent.trim()}`);
const href = course.getAttribute('href');
if (href) {
window.location.href = href;
} else {
log("错误:课程链接无效");
}
}
// 手动切换到下一课
function manualGoToNextContent() {
log("手动切换下一课");
scriptState.bottomReachedCount = scriptState.config.scrollTimeThreshold;
checkCourseCompletion();
}
// 手动触发学习时间记录
function manualTriggerStudyTime() {
log("手动触发学习时间记录");
triggerStudyTime();
}
// 手动滚动到底部
function manualScrollToBottom() {
log("手动滚动到底部");
const scrollContainer = scriptState.scrollingContainer || getScrollContainer();
if (scrollContainer) {
const maxScroll = scrollContainer.scrollHeight - scrollContainer.clientHeight;
smoothScroll(scrollContainer, maxScroll, 1000);
}
}
// 启动脚本
function startScript() {
if (scriptState.isRunning) {
log("脚本已在运行中");
return;
}
scriptState.isRunning = true;
scriptState.bottomReachedCount = 0;
scriptState.scrollDirection = 1; // 向下滚动
scriptState.scrollCount = 0;
scriptState.startTime = Date.now();
// 重置活跃度统计
scriptState.activityStats = {
scrolls: 0,
clicks: 0,
keystrokes: 0,
mousemoves: 0
};
// 初始化课程状态
refreshCourseStatus();
updateUI();
log("启动培训助手脚本");
// 启动主要滚动模拟
scriptState.scrollInterval = setInterval(() => {
simulateScrolling();
}, scriptState.config.majorScrollInterval);
// 启动微小滚动(更频繁,模拟用户读书)
scriptState.microScrollInterval = setInterval(() => {
simulateMicroScroll();
}, scriptState.config.microScrollInterval);
// 启动点击模拟
scriptState.clickInterval = setInterval(() => {
simulateClick();
}, scriptState.config.clickInterval);
// 启动鼠标移动模拟
scriptState.moveInterval = setInterval(() => {
simulateMouseMovement();
}, scriptState.config.moveInterval);
// 启动活跃度维持
scriptState.activityInterval = setInterval(() => {
maintainActivity();
}, scriptState.config.activityInterval);
// 定期更新学习时间
scriptState.studyTimeInterval = setInterval(() => {
triggerStudyTime();
}, scriptState.config.studyTimeInterval);
// 立即执行一次活动,启动滚动
simulateScrolling();
log("脚本已启动,自动化学习进行中,保持页面活跃状态");
}
// 停止脚本
function stopScript() {
if (!scriptState.isRunning) {
log("脚本已停止");
return;
}
scriptState.isRunning = false;
scriptState.waitingForNextCourse = false;
updateUI();
// 清除所有定时器
if (scriptState.scrollInterval) {
clearInterval(scriptState.scrollInterval);
scriptState.scrollInterval = null;
}
if (scriptState.microScrollInterval) {
clearInterval(scriptState.microScrollInterval);
scriptState.microScrollInterval = null;
}
if (scriptState.clickInterval) {
clearInterval(scriptState.clickInterval);
scriptState.clickInterval = null;
}
if (scriptState.moveInterval) {
clearInterval(scriptState.moveInterval);
scriptState.moveInterval = null;
}
if (scriptState.activityInterval) {
clearInterval(scriptState.activityInterval);
scriptState.activityInterval = null;
}
if (scriptState.studyTimeInterval) {
clearInterval(scriptState.studyTimeInterval);
scriptState.studyTimeInterval = null;
}
if (scriptState.idleTimeout) {
clearTimeout(scriptState.idleTimeout);
scriptState.idleTimeout = null;
}
log("脚本已停止");
}
// 更新活跃度统计
function updateActivityStats() {
const activityStats = document.getElementById('activity-stats');
if (activityStats) {
const elapsed = Date.now() - (scriptState.startTime || Date.now());
const minutes = Math.floor(elapsed / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
activityStats.innerHTML = `
<div>运行时间: ${minutes}分${seconds}秒</div>
<div>滚动次数: ${scriptState.activityStats.scrolls}</div>
<div>点击次数: ${scriptState.activityStats.clicks}</div>
<div>按键次数: ${scriptState.activityStats.keystrokes}</div>
<div>鼠标移动: ${scriptState.activityStats.mousemoves}</div>
`;
}
}
// 更新UI状态
function updateUI() {
const statusIndicator = document.querySelector('.status-indicator');
const statusText = document.querySelector('.status-text');
const progressInfo = document.querySelector('.progress-info');
const courseListEl = document.querySelector('.course-list');
const debugInfo = document.querySelector('.debug-info');
if (statusIndicator) {
if (scriptState.isRunning) {
statusIndicator.classList.add('active');
statusText.textContent = scriptState.waitingForNextCourse ?
'即将切换下一课...' :
'运行中 - 自动化学习进行中';
} else {
statusIndicator.classList.remove('active');
statusText.textContent = '已停止 - 点击启动按钮开始';
}
}
if (progressInfo) {
let progressText = `进度: ${scriptState.currentContentIndex + 1}/${scriptState.courseProgress.total} 课程`;
if (scriptState.courseProgress.completed > 0) {
progressText += ` (已完成: ${scriptState.courseProgress.completed})`;
}
if (scriptState.courseProgress.current) {
progressText += `\n当前: ${scriptState.courseProgress.current}`;
}
if (scriptState.currentPage > 1 || document.querySelector('.pagination')) {
progressText += `\n页面: ${scriptState.currentPage}`;
}
// 添加学习时间信息
if (scriptState.startTime) {
const elapsed = Date.now() - scriptState.startTime;
progressText += `\n学习时间: ${Math.floor(elapsed/60000)}分${Math.floor((elapsed%60000)/1000)}秒`;
}
progressText += `\n底部计数: ${scriptState.bottomReachedCount}/${scriptState.config.scrollTimeThreshold}`;
progressInfo.textContent = progressText;
}
// 更新课程列表
if (courseListEl) {
courseListEl.innerHTML = '';
scriptState.courseList.forEach((course, index) => {
const item = document.createElement('div');
item.className = 'course-item';
if (index === scriptState.currentContentIndex) {
item.classList.add('active');
}
const href = course.getAttribute('href') || '';
const contentId = extractContentId(href);
const isCompleted = contentId && scriptState.completedCourses.has(contentId);
item.textContent = `${index + 1}. ${course.textContent.trim().substring(0, 20)} ${isCompleted ? '✓' : ''}`;
// 添加点击课程的功能
item.addEventListener('click', () => jumpToCourse(index));
courseListEl.appendChild(item);
});
}
// 更新活跃度统计
updateActivityStats();
// 更新调试信息
if (debugInfo) {
debugInfo.textContent = scriptState.debugInfo;
}
}
// 更新状态文本
function updateStatus(message) {
const statusText = document.querySelector('.status-text');
if (statusText) {
statusText.textContent = message;
}
}
// 创建控制面板
function createControlPanel() {
console.log("[培训助手] 开始创建控制面板");
// 检查是否已存在控制面板
if (document.getElementById('control-panel')) {
console.log("[培训助手] 控制面板已存在");
return;
}
const panel = document.createElement('div');
panel.id = 'control-panel';
panel.innerHTML = `
<div class="panel-header">
<div class="panel-title">
<span class="panel-icon">🎓</span>
<span>培训助手 5.0</span>
</div>
<button class="toggle-btn" id="toggle-panel">−</button>
</div>
<div class="panel-content">
<div class="control-group">
<label class="control-label">滚动速度</label>
<input type="range" class="control-input" id="scroll-speed"
min="1000" max="5000" step="500" value="${scriptState.config.scrollSpeed}">
</div>
<div class="control-group">
<label class="control-label">最短阅读时间 (分钟)</label>
<input type="number" class="control-input" id="min-read-time"
value="${scriptState.config.minReadTime/60000}" min="1" max="30">
</div>
<div class="checkbox-group">
<input type="checkbox" class="checkbox-input" id="auto-next-content"
${scriptState.config.autoNextContent ? 'checked' : ''}>
<label class="control-label" for="auto-next-content">自动切换下一课</label>
</div>
<div class="btn-group">
<button class="panel-btn start-btn" id="start-btn">启动</button>
<button class="panel-btn stop-btn" id="stop-btn">停止</button>
</div>
<div class="progress-info" id="progress-info">进度: 0/0</div>
<div class="scroll-progress">
<div class="scroll-progress-bar"></div>
</div>
<div id="activity-stats" style="font-size:12px; margin:5px 0; background:rgba(0,0,0,0.1); padding:5px; border-radius:4px;">
活跃度数据载入中...
</div>
<div class="course-list" id="course-list"></div>
<div style="display:flex; gap:5px; margin-top:5px;">
<button class="debug-btn" id="debug-next">切换下一课</button>
<button class="debug-btn" id="debug-time">触发学时</button>
</div>
<div style="display:flex; gap:5px; margin-top:5px;">
<button class="debug-btn" id="debug-scroll">滚动到底部</button>
<button class="debug-btn" id="debug-refresh">刷新课程</button>
</div>
<div class="status-text">
<span class="status-indicator"></span>
已停止 - 点击启动按钮开始
</div>
<div class="debug-info" style="font-size:10px; margin-top:5px; color:#eee; max-height:60px; overflow-y:auto;"></div>
</div>
`;
// 尝试添加到body
if (document.body) {
document.body.appendChild(panel);
console.log("[培训助手] 控制面板已添加到body");
} else {
console.error("[培训助手] document.body不存在");
document.documentElement.appendChild(panel);
console.log("[培训助手] 控制面板已添加到documentElement");
}
// 添加事件监听器
document.getElementById('start-btn').addEventListener('click', startScript);
document.getElementById('stop-btn').addEventListener('click', stopScript);
document.getElementById('debug-next').addEventListener('click', manualGoToNextContent);
document.getElementById('debug-scroll').addEventListener('click', manualScrollToBottom);
document.getElementById('debug-time').addEventListener('click', manualTriggerStudyTime);
document.getElementById('debug-refresh').addEventListener('click', refreshCourseStatus);
document.getElementById('toggle-panel').addEventListener('click', function() {
panel.classList.toggle('collapsed');
this.textContent = panel.classList.contains('collapsed') ? '+' : '−';
});
// 配置更新
document.getElementById('scroll-speed').addEventListener('change', function() {
scriptState.config.scrollSpeed = parseInt(this.value);
log(`滚动速度已更新为 ${this.value}ms`);
});
document.getElementById('min-read-time').addEventListener('change', function() {
scriptState.config.minReadTime = parseInt(this.value) * 60000; // 转换为毫秒
log(`最短阅读时间已更新为 ${this.value} 分钟`);
});
document.getElementById('auto-next-content').addEventListener('change', function() {
scriptState.config.autoNextContent = this.checked;
log(`自动切换下一课已${this.checked ? '启用' : '禁用'}`);
});
// 初始刷新
refreshCourseStatus();
updateActivityStats();
console.log("[培训助手] 控制面板创建完成");
}
// 初始化脚本
function initScript() {
console.log("[培训助手] 初始化培训助手脚本");
// 创建控制面板
createControlPanel();
console.log("[培训助手] 控制面板已创建,请点击启动按钮开始");
}
// 尝试多种方式初始化脚本
function tryInit() {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
console.log("[培训助手] 文档已就绪,立即初始化");
initScript();
} else {
console.log("[培训助手] 等待文档加载完成");
window.addEventListener('DOMContentLoaded', initScript);
window.addEventListener('load', initScript);
}
}
// 立即尝试初始化
tryInit();
// 备用初始化(如果上面的方法失败)
setTimeout(() => {
if (!document.getElementById('control-panel')) {
console.log("[培训助手] 备用初始化");
initScript();
}
}, 3000);
console.log("[培训助手] 脚本加载完成");
})();