中原教师

点击查看代码
// ==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();
    }
})();
posted @ 2025-09-06 15:26  神仙不在  阅读(30)  评论(0)    收藏  举报