Greasy Fork

Greasy Fork is available in English.

山东省大数据工程专业技术人员专业课自动看课

自动遍历视频列表,找到第一个未完成的视频,修改其li标签class并模拟点击,引入短时延迟以避免循环卡顿,并连续播放直到全部完成。自动处理alert弹窗。

// ==UserScript==
// @name         山东省大数据工程专业技术人员专业课自动看课
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  自动遍历视频列表,找到第一个未完成的视频,修改其li标签class并模拟点击,引入短时延迟以避免循环卡顿,并连续播放直到全部完成。自动处理alert弹窗。
// @match        http://119.148.160.19:18006/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 立即重写 alert 函数,确保最早生效 ---
    const originalAlert = window.alert;
    window.alert = function(message) {
        console.log('🚫 alert 弹窗被拦截,内容:', message);
        return true; // 模拟点击“确定”
    };

    // --- 配置项 ---
    // 用于标记视频已完成的文本。现在使用不带括号的“已完成”。
    const COMPLETED_PREFIX = '已完成';
    // 目标 class 名称,用于触发视频加载。
    const TARGET_CLASS = 'lesson-select';
    // 脚本检查视频状态或寻找下一个视频的频率(毫秒)。
    const MAIN_CHECK_INTERVAL_MS = 3000; // 3秒
    // 脚本在页面加载完毕后,额外等待的秒数(毫秒)。
    const INITIAL_DELAY_MS = 5000; // 5秒
    // 在激活一个视频后(设置class并点击),等待多长时间再允许主循环重新检查列表(毫秒)。
    // 这给视频播放器留出加载和开始的时间,避免快速切换。
    const ACTIVATION_COOLDOWN_MS = 3000; // 3秒冷却时间

    // --- 内部状态变量 ---
    let mainLoopIntervalId = null;
    let currentVideoPlayingIndex = -1;
    let isActivatingVideo = false; // 新增标志,防止在激活视频时重复触发

    /**
     * 查找列表中**第一个**未完成的视频,并修改其 class 属性,然后模拟点击来触发播放。
     * @returns {boolean} 如果找到并处理了未完成视频,返回 true;否则(所有视频都已完成)返回 false。
     */
    function findAndActivateNextUnfinishedVideo() {
        // 如果我们正在激活一个视频,就暂停查找,等待冷却期结束
        if (isActivatingVideo) {
            console.log('⏳ 正在激活视频冷却中,暂停查找下一个视频...');
            return true; // 假装已激活,但实际是等待冷却
        }

        console.log('🤖 正在寻找下一个未完成的视频...');

        const ulControl = document.querySelector('ul.ul-control');
        if (!ulControl) {
            console.warn('⚠️ 未找到 <ul class="ul-control"> 元素。请检查选择器是否正确或页面是否已完全加载。');
            return false;
        }

        const allVideoListItems = ulControl.querySelectorAll('li');
        if (allVideoListItems.length === 0) {
            console.warn('⚠️ 在 <ul class="ul-control"> 中未找到任何 <li> 元素。请检查选择器。');
            return false;
        }

        for (let i = 0; i < allVideoListItems.length; i++) {
            const liElement = allVideoListItems[i];
            const spanElement = liElement.querySelector('span');

            if (spanElement && spanElement.innerText) {
                const videoTitle = spanElement.innerText.trim();

                // 检查视频标题是否**不包含**“已完成”
                if (!videoTitle.includes(COMPLETED_PREFIX)) { // <-- 已修改为 includes
                    console.log(`✅ 找到未完成视频: "${videoTitle}" (索引: ${i})。`);

                    // 检查这是否就是我们当前正在处理的视频,并且它已经包含了目标 class。
                    // 如果是,表示它已经在加载或播放中,无需重复操作。
                    if (i === currentVideoPlayingIndex && liElement.classList.contains(TARGET_CLASS)) {
                        console.log(`ℹ️ 视频 "${videoTitle}" (索引: ${i}) 正在处理中(已包含 ${TARGET_CLASS})。等待其完成。`);
                        return true;
                    }

                    // --- 核心激活逻辑 ---
                    // 设置激活视频标志,阻止重复触发
                    isActivatingVideo = true;
                    currentVideoPlayingIndex = i; // 更新当前正在处理的视频索引。

                    console.log(`➡️ 尝试修改所有 <li> 标签的 class,并将当前 <li> 的 class 设置为 "${TARGET_CLASS}"。`);
                    // 首先移除所有其他 li 的 TARGET_CLASS,确保只有一个被选中
                    allVideoListItems.forEach(item => {
                        if (item !== liElement && item.classList.contains(TARGET_CLASS)) {
                            item.classList.remove(TARGET_CLASS);
                        }
                    });
                    liElement.className = TARGET_CLASS; // 这将替换当前 <li> 上原有的所有 class

                    console.log(`➡️ 模拟点击该 <li> 标签。`);
                    liElement.click();
                    console.log("我已点击" + liElement);

                    // 在成功激活视频后,设置一个冷却时间,防止 mainLoop 立即再次触发查找
                    setTimeout(() => {
                        isActivatingVideo = false; // 冷却时间结束后,允许再次查找
                        console.log('✅ 视频激活冷却结束,允许下次循环查找...');
                    }, ACTIVATION_COOLDOWN_MS);

                    return true; // 找到并处理了一个未完成视频,立即退出循环。
                }
            }
        }

        console.log('🎉 所有视频已全部完成!脚本将停止运行。');
        clearInterval(mainLoopIntervalId);
        currentVideoPlayingIndex = -1;
        return false;
    }

    /**
     * 处理当前页面上存在的视频播放器元素:静音、播放,并设置播放结束监听器。
     */
    function handleVideoPlayer() {
        const videoElement = document.querySelector('video');

        if (videoElement) {
            console.log('🎥 检测到视频播放器,正在管理播放...');

            // 确保视频静音,这是自动播放的关键
            if (!videoElement.muted) {
                videoElement.muted = true;
                console.log('🔇 视频已静音。');
            }

            // 如果视频处于暂停状态,尝试播放它。
            if (videoElement.paused) {
                 videoElement.play()
                    .then(() => {
                        console.log('▶️ 视频已开始/恢复播放。');
                    })
                    .catch(error => {
                        console.warn('⚠️ 视频播放被浏览器阻止或失败:', error);
                        // 如果播放失败,立即重置索引并尝试查找下一个视频,防止卡死。
                        currentVideoPlayingIndex = -1;
                        isActivatingVideo = false; // 允许立即查找下一个
                        findAndActivateNextUnfinishedVideo();
                    });
            }

            // 设置视频播放结束的监听器。
            if (videoElement.__gm_onended_listener) {
                videoElement.removeEventListener('ended', videoElement.__gm_onended_listener);
            }
            videoElement.__gm_onended_listener = () => {
                console.log('⏹️ 当前视频播放结束。');
                // 视频播放结束后,给网站留出足够的时间来更新 DOM。
                setTimeout(() => {
                    currentVideoPlayingIndex = -1;
                    isActivatingVideo = false; // 允许立即查找下一个
                    findAndActivateNextUnfinishedVideo();
                }, 8000);
            };
            videoElement.addEventListener('ended', videoElement.__gm_onended_listener);

        } else {
            console.log('🔍 当前页面未找到视频播放器元素。');
            currentVideoPlayingIndex = -1;
            isActivatingVideo = false; // 如果没有视频元素,也允许再次查找
        }
    }

    /**
     * 主循环函数:**始终优先**查找并激活下一个未完成的视频,然后管理视频播放器。
     */
    function mainLoop() {
        // 如果当前正在激活一个视频(处于冷却期),或者没有未完成视频需要处理,则不执行 handleVideoPlayer。
        // findAndActivateNextUnfinishedVideo 内部会处理 isActivatingVideo 的逻辑。
        const videoActivated = findAndActivateNextUnfinishedVideo();

        if (videoActivated) {
            handleVideoPlayer();
        }
    }

    // --- 脚本初始化 ---
    window.addEventListener('load', function() {
        console.log('🚀 页面已加载完毕。脚本将在 ' + (INITIAL_DELAY_MS / 1000) + ' 秒后开始执行主要逻辑...');
        setTimeout(() => {
            console.log('🚀 油猴脚本启动成功!开始定期检查并自动播放视频...');
            // 首次调用主循环,立即开始处理。
            mainLoop();
            mainLoopIntervalId = setInterval(mainLoop, MAIN_CHECK_INTERVAL_MS);
        }, INITIAL_DELAY_MS);
    });

})();