中原教师
点击查看代码
// ==UserScript==
// @name SPA页面检测与自动学习脚本
// @namespace http://tampermonkey.net/
// @version 1.5
// @description 检测SPA页面变化,自动点击第一个未完成的课程,视频播放完成后等待3秒点击返回按钮
// @author YourName
// @match http://zy.teacheredu.org.cn/px/*
// @grant none
// @run-at document-start
// ==/UserScript==
// 使用立即执行函数封装代码,避免污染全局命名空间
(function() {
'use strict'; // 启用严格模式,提高代码安全性和性能
// 存储当前页面状态的变量,用于避免重复输出相同信息
let currentPage = '';
// 存储视频检测的定时器ID
let videoCheckInterval = null;
// 存储视频结束事件监听器,以便后续移除
let videoEndedListeners = new Map();
// 存储跳转定时器,以便在需要时取消
let redirectTimer = null;
// 存储列表检测定时器
let listCheckInterval = null;
// 标记是否已经点击了未完成课程
let hasClickedUncompleted = false;
/**
* 检测页面类型并根据URL变化输出相应信息到控制台
* 这个函数检查当前URL是否包含特定路径,并只在页面状态变化时输出信息
*/
function checkPage() {
// 获取当前页面的完整URL
const url = window.location.href;
// 检查是否为列表页且当前状态不是列表页
if (url.includes('/#/courselist') && currentPage !== 'list') {
currentPage = 'list'; // 更新当前页面状态
console.log('我是列表页'); // 输出信息到控制台
// 停止视频检测(如果正在运行)
stopVideoCheck();
// 移除所有视频结束事件监听器
removeAllVideoEndedListeners();
// 取消任何待处理的跳转
cancelRedirect();
// 重置点击状态
hasClickedUncompleted = false;
// 启动列表检测
startListCheck();
}
// 检查是否为播放页且当前状态不是播放页
else if (url.includes('/#/video') && currentPage !== 'video') {
currentPage = 'video'; // 更新当前页面状态
console.log('我是播放页面'); // 输出信息到控制台
// 停止列表检测
stopListCheck();
// 启动视频检测
startVideoCheck();
}
// 如果URL既不包含列表页路径也不包含播放页路径
else if (!url.includes('/#/courselist') && !url.includes('/#/video')) {
currentPage = ''; // 重置当前页面状态
// 停止视频检测(如果正在运行)
stopVideoCheck();
// 停止列表检测
stopListCheck();
// 移除所有视频结束事件监听器
removeAllVideoEndedListeners();
// 取消任何待处理的跳转
cancelRedirect();
// 重置点击状态
hasClickedUncompleted = false;
}
}
/**
* 启动列表检测,查找并点击第一个未完成的课程
*/
function startListCheck() {
// 如果已经有检测在运行,先停止它
stopListCheck();
console.log('开始列表检测,查找未完成的课程...');
// 设置定期检查列表状态的间隔
listCheckInterval = setInterval(function() {
// 如果已经点击过未完成课程,不再继续检测
if (hasClickedUncompleted) {
console.log('已点击未完成课程,停止列表检测');
stopListCheck();
return;
}
// 查找所有课程行
const courseRows = document.querySelectorAll('.list table tr');
let foundUncompleted = false;
// 遍历所有课程行
for (let i = 0; i < courseRows.length; i++) {
const row = courseRows[i];
// 跳过章节标题行
if (row.querySelector('.section') || row.querySelector('.sectionTitle')) {
continue;
}
// 检查是否包含"未完成"状态
const statusCell = row.querySelector('.done');
if (statusCell && statusCell.textContent.includes('未完成')) {
console.log('找到未完成的课程:', row.textContent);
// 查找可点击的元素(knob类元素)
const clickableElement = row.querySelector('.knob');
if (clickableElement) {
console.log('点击课程:', clickableElement.textContent);
clickableElement.click();
hasClickedUncompleted = true;
foundUncompleted = true;
break; // 只点击第一个未完成的课程
}
}
}
// 如果没有找到未完成的课程
if (!foundUncompleted) {
console.log('没有找到未完成的课程或所有课程已完成');
stopListCheck();
}
}, 2000); // 每2秒检查一次
}
/**
* 停止列表检测
*/
function stopListCheck() {
if (listCheckInterval) {
console.log('停止列表检测');
clearInterval(listCheckInterval);
listCheckInterval = null;
}
}
/**
* 启动视频检测,定期检查视频状态并在暂停时自动播放
* 同时为视频添加结束事件监听器,播放完成后等待3秒点击返回按钮
*/
function startVideoCheck() {
// 如果已经有检测在运行,先停止它
stopVideoCheck();
console.log('开始视频检测...');
// 设置定期检查视频状态的间隔
videoCheckInterval = setInterval(function() {
// 查找页面中的视频元素
const videos = document.querySelectorAll('video');
if (videos.length > 0) {
// 遍历所有找到的视频元素
videos.forEach((video, index) => {
// 检查视频是否暂停
if (video.paused && !video.ended) {
console.log(`检测到视频 ${index + 1} 暂停,尝试播放...`);
// 尝试播放视频
video.play()
.then(() => {
console.log(`视频 ${index + 1} 播放成功`);
})
.catch(error => {
console.warn(`视频 ${index + 1} 播放失败:`, error.message);
});
}
// 为视频添加结束事件监听器(如果尚未添加)
if (!videoEndedListeners.has(video)) {
const endedListener = function() {
console.log(`视频 ${index + 1} 播放完成,等待3秒后点击返回按钮`);
// 取消任何现有的跳转定时器
cancelRedirect();
// 设置3秒后点击返回按钮
redirectTimer = setTimeout(() => {
console.log('尝试点击返回按钮...');
const backIndexElement = document.querySelector('.backIndex');
if (backIndexElement) {
console.log('找到返回按钮,正在点击...');
backIndexElement.click();
} else {
console.warn('未找到返回按钮(.backIndex),无法自动返回');
// 如果没找到返回按钮,可以选择跳转到列表页作为备选方案
// 或者什么都不做
}
}, 3000); // 等待3秒
};
video.addEventListener('ended', endedListener);
videoEndedListeners.set(video, endedListener);
console.log(`已为视频 ${index + 1} 添加结束事件监听器`);
}
});
} else {
console.log('未找到视频元素');
}
}, 2000); // 每2秒检查一次
}
/**
* 停止视频检测
*/
function stopVideoCheck() {
if (videoCheckInterval) {
console.log('停止视频检测');
clearInterval(videoCheckInterval);
videoCheckInterval = null;
}
}
/**
* 移除所有视频结束事件监听器
*/
function removeAllVideoEndedListeners() {
videoEndedListeners.forEach((listener, video) => {
video.removeEventListener('ended', listener);
console.log('移除视频结束事件监听器');
});
videoEndedListeners.clear();
}
/**
* 取消待处理的跳转
*/
function cancelRedirect() {
if (redirectTimer) {
console.log('取消待处理的跳转');
clearTimeout(redirectTimer);
redirectTimer = null;
}
}
/**
* 初始化函数,设置各种事件监听器和定期检查机制
* 确保在单页面应用中页面变化时能够及时检测到
*/
function init() {
// 初始检查,确保页面加载时立即执行一次检测
checkPage();
// 监听hash变化事件(适用于基于hash路由的单页面应用)
window.addEventListener('hashchange', checkPage);
// 监听popstate事件(适用于HTML5 History API路由的单页面应用)
window.addEventListener('popstate', checkPage);
// 使用MutationObserver监听DOM变化
// 这是一种高级的DOM变化检测机制,可以捕获单页面应用中的动态内容更新
const observer = new MutationObserver(function(mutations) {
checkPage(); // 当DOM发生变化时执行页面检测
});
// 配置并启动MutationObserver,监听body元素的子节点和后代节点的变化
observer.observe(document.body, {
childList: true, // 监听子节点的添加和移除
subtree: true // 监听所有后代节点的变化
});
// 设置定期检查作为备用方案,每秒执行一次页面检测
// 这是为了确保即使其他检测机制失效,也能捕获页面变化
setInterval(checkPage, 1000);
}
// 根据文档加载状态决定如何执行初始化
// 如果文档仍在加载中,等待DOMContentLoaded事件触发后再初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
// 如果文档已经加载完成,直接执行初始化
init();
}
})();
版本2
点击查看代码
// ==UserScript==
// @name SPA页面检测与自动学习脚本 (优化版)
// @namespace http://tampermonkey.net/
// @version 1.6
// @description 检测SPA页面变化,自动点击第一个未完成的课程,视频播放完成后等待3秒点击返回按钮
// @author YourName
// @match http://zy.teacheredu.org.cn/px/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 状态变量
let currentPage = '';
let hasClickedUncompleted = false;
let videoPlaying = false;
// 定时器
let mainInterval = null;
// 缓存DOM元素
let cachedElements = {
courseRows: null,
videos: null,
backButton: null
};
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 清除元素缓存
function clearElementCache() {
cachedElements.courseRows = null;
cachedElements.videos = null;
cachedElements.backButton = null;
}
// 获取课程行(带缓存)
function getCourseRows() {
if (!cachedElements.courseRows) {
cachedElements.courseRows = document.querySelectorAll('.list table tr');
}
return cachedElements.courseRows;
}
// 获取视频元素(带缓存)
function getVideos() {
if (!cachedElements.videos) {
cachedElements.videos = document.querySelectorAll('video');
}
return cachedElements.videos;
}
// 检查页面类型
const debouncedCheckPage = debounce(function() {
const url = window.location.href;
if (url.includes('/#/courselist') && currentPage !== 'list') {
currentPage = 'list';
console.log('检测到列表页');
clearElementCache();
hasClickedUncompleted = false;
}
else if (url.includes('/#/video') && currentPage !== 'video') {
currentPage = 'video';
console.log('检测到播放页面');
clearElementCache();
}
else if (!url.includes('/#/courselist') && !url.includes('/#/video')) {
currentPage = '';
console.log('检测到其他页面');
clearElementCache();
hasClickedUncompleted = false;
}
}, 300);
// 检查课程列表
function checkCourseList() {
if (hasClickedUncompleted) return;
const courseRows = getCourseRows();
if (!courseRows.length) return;
for (let i = 0; i < courseRows.length; i++) {
const row = courseRows[i];
// 跳过章节标题行
if (row.querySelector('.section') || row.querySelector('.sectionTitle')) {
continue;
}
// 检查未完成状态
const statusCell = row.querySelector('.done');
if (statusCell && statusCell.textContent.includes('未完成')) {
const clickableElement = row.querySelector('.knob');
if (clickableElement) {
console.log('点击未完成课程');
clickableElement.click();
hasClickedUncompleted = true;
break;
}
}
}
}
// 检查视频播放
function checkVideoPlayback() {
const videos = getVideos();
if (!videos.length) return;
videos.forEach((video, index) => {
// 处理暂停的视频
if (video.paused && !video.ended) {
video.play().catch(error => {
console.warn(`视频播放失败: ${error.message}`);
});
}
// 更新播放状态
videoPlaying = !video.paused && !video.ended;
// 添加结束事件监听
if (!video.hasListener) {
video.addEventListener('ended', function() {
console.log('视频播放完成,3秒后返回');
setTimeout(() => {
const backButton = document.querySelector('.backIndex');
if (backButton) {
backButton.click();
}
}, 3000);
});
video.hasListener = true;
}
});
}
// 启动主定时器
function startMainInterval() {
if (mainInterval) return;
mainInterval = setInterval(() => {
debouncedCheckPage();
if (currentPage === 'list') checkCourseList();
if (currentPage === 'video') checkVideoPlayback();
}, 1000);
}
// 停止主定时器
function stopMainInterval() {
if (mainInterval) {
clearInterval(mainInterval);
mainInterval = null;
}
}
// 初始化
function init() {
// 初始页面检查
debouncedCheckPage();
// 监听页面变化
window.addEventListener('hashchange', debouncedCheckPage);
window.addEventListener('popstate', debouncedCheckPage);
// 使用MutationObserver监听DOM变化
const observer = new MutationObserver(debouncedCheckPage);
observer.observe(document.body, {
childList: true,
subtree: true
});
// 启动主定时器
startMainInterval();
console.log('优化版SPA页面检测脚本已启动');
}
// 根据文档状态执行初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

浙公网安备 33010602011771号