Greasy Fork

Greasy Fork is available in English.

在线培训助手

党旗飘飘学习助手,自动刷课,跳转控制,JLU

当前为 2025-05-14 提交的版本,查看 最新版本

// ==UserScript==
// @name         在线培训助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  党旗飘飘学习助手,自动刷课,跳转控制,JLU
// @author       bugo
// @match        *://*/jjfz/play*
// @match        *://*/*/jjfz/play*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log("在线培训助手已加载");

    // 工具函数
    const utils = {
        getVideo: () => document.querySelector("video"),
        
        getRid: () => {
            const html = document.documentElement.innerHTML;
            const match = html.match(/rid[\s:="']*(\d+)["'\s]/);
            return match ? match[1] : null;
        },
        
        // 清除所有定时器
        clearAllTimers: () => {
            // 清除原页面定时器
            clearInterval(window.timer);
            window.clearInterval(window.loop_flag);
            window.clearInterval(window.flag);
    
            // 清除我们自己的定时器
            if (window.autoInterval) {
                clearInterval(window.autoInterval);
                window.autoInterval = null;
            }
        },
        
        // 处理各种弹窗,包括继续观看和关闭
        handleDialogs: () => {
            // .public_close .public_cancel  .public_submit  public_cont1这三种按钮 并且要判断内容
            document.querySelectorAll(".public_close, .public_cancel, .public_submit, .public_cont1").forEach(btn => {
                if (btn) {
                    // 继续观看 、我知道了 、继续
                    if (btn.textContent?.includes("继续观看") || btn.textContent?.includes("我知道了") || btn.textContent?.includes("继续")) {
                        btn.click();
                    }
                }
            });
        }
    };

    // 防止页面切换时暂停
    const originAddListener = Document.prototype.addEventListener;
    Document.prototype.addEventListener = function(type, listener, options) {
        if (type === "visibilitychange") return;
        return originAddListener.call(this, type, listener, options);
    };
    Object.defineProperty(document, 'hidden', { configurable: true, get: () => false });
    Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'visible' });

    // 添加样式
    GM_addStyle(`
        .jjfz-helper-panel {
            position: fixed;
            top: 110px;
            left: 10px;
            background: #fff;
            border-radius: 0 0 4px 4px;
            box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
            z-index: 9999;
            padding: 12px;
            width: 280px;
            font-size: 14px;
            margin-top: -1px;
        }
        .jjfz-helper-panel h3 {
            margin: 0 0 12px 0;
            padding-bottom: 8px;
            border-bottom: 1px solid #eee;
            text-align: center;
            font-size: 16px;
            color: #e61d1d;
        }
        .jjfz-helper-option {
            margin-bottom: 10px;
        }
        .jjfz-helper-option label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        .checkbox-wrapper {
            display: flex;
            align-items: center;
            margin: 5px 0;
        }
        .checkbox-wrapper input[type="checkbox"] {
            width: auto;
            margin: 0;
            cursor: pointer;
        }
        .jjfz-helper-panel select, .jjfz-helper-panel input, .jjfz-helper-panel button {
            width: 100%;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 3px;
            margin-bottom: 6px;
            cursor: pointer;
        }
        .jjfz-helper-panel select, .jjfz-helper-panel input[type="text"] {
            cursor: auto;
        }
        .jjfz-helper-panel button {
            background: #2196F3;
            color: white;
            border: none;
            font-weight: bold;
        }
        .jjfz-helper-panel button:hover {
            background: #1976D2;
        }
        .jjfz-helper-panel button.stop {
            background: #F44336;
        }
        .jjfz-helper-panel button.stop:hover {
            background: #D32F2F;
        }
        .jjfz-helper-toggle {
            position: fixed;
            top: 80px;
            left: 10px;
            background: #2196F3;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 6px 12px;
            cursor: pointer;
            z-index: 10000;
            font-weight: bold;
            width: 80px;
            box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05);
        }
        .jjfz-helper-toggle.expanded {
            border-radius: 4px 4px 0 0;
        }
        .jjfz-helper-toggle:hover {
            background: #1976D2;
        }
    `);

    // 设置
    const settings = {
        autoPlay: GM_getValue('autoPlay', true),
        autoNext: true,
        panelExpanded: GM_getValue('panelExpanded', true),
        
        // 保存设置
        save: function() {
            Object.keys(this).forEach(key => {
                if (typeof this[key] !== 'function' && key !== 'autoNext') {
                    GM_setValue(key, this[key]);
                }
            });
        }
    };

    // 核心功能
    const core = {
        
        // 添加学习时长 - 参数为要添加的秒数
        addStudyTime: (seconds) => {
            const rid = utils.getRid();
            const video = utils.getVideo();
    
            if (!video || !rid || seconds <= 0) return false;
    
            // 计算目标时间
            const currentTime = video.currentTime || 0;
            const duration = video.duration || 600;
            // 减少2秒,等待网址自动上报完成事件
            const targetTime = Math.min(duration-2, currentTime + seconds);
    
            // 发送请求并设置视频时间
            let success = false;
    
            $.ajax({
                type: "POST",
                cache: false,
                dataType: "json",
                url: "/jjfz/lesson/current_time",
                data: {
                    rid: rid,
                    time: targetTime,
                    _xsrf: $(":input[name='_xsrf']").val()
                },
                async: false,
                success: function(data) {
                    if (data && data.code === 1) {
                        try {
                            video.currentTime = targetTime;
                            success = true;
                        } catch (e) {
                            success = false;
                        }
                    } else {
                        success = false;
                    }
                },
                error: function() {
                    success = false;
                }
            });
    
            return success;
        },
    
        // 自动播放下一集
        playNextVideo: () => {
            const current = document.querySelector('.video_red1')?.closest('li');
            const next = current?.nextElementSibling;
    
            if (next && next.querySelector('a')) {
                next.querySelector('a').click();
                return true;
            } else {
                GM_notification({ text: '✅ 本章播放完成,跳转课程列表页~', timeout: 4000 });
                location.href = `${location.protocol}//${location.host}/jjfz/lesson`;
                return false;
            }
        },
    
        // 处理视频结束事件
        handleVideoEnded: () => {
            utils.clearAllTimers();
            if (settings.autoNext) {
                // 等待2秒,确保视频结束事件触发
                setTimeout(core.playNextVideo, 2000);
            }
        },
    
        // 启动自动学习
        startAutoLearning: () => {
            if (!settings.autoPlay || window.autoInterval) return;
    
            // 处理弹窗
            utils.handleDialogs();
    
            // 监听视频结束事件
            const video = utils.getVideo();
            if (video && !video.hasEndedListener) {
                video.addEventListener('ended', function() {
                    console.log("视频播放结束");
                    core.handleVideoEnded();
                });
                video.hasEndedListener = true;
            }
    
            // 启动定时器
            window.autoInterval = setInterval(() => {
                const video = utils.getVideo();
                if (!video) return;
    
                // 尝试点击播放按钮
                if (video.paused) {
                    const playButton = document.querySelector('.plyr__controls [data-plyr="play"], .plyr__play-large, [aria-label="播放"]');
                    if (playButton) {
                        playButton.click();
                    } else {
                        // 触发点击事件
                        video.dispatchEvent(new MouseEvent('click', {
                            bubbles: true,
                            cancelable: true
                        }));
                    }
                    video.play();
                }
    
                // 自动处理弹窗
                utils.handleDialogs();
    
                // 更新界面
                ui.update();
    
                // 检测视频是否接近结束但尚未触发ended事件
                if (video.currentTime > video.duration * 0.98 && !video.ended) {
                    video.currentTime = video.duration; // 强制跳到结尾触发ended事件
                }
            }, 1500);
        }
    };

    // UI 相关功能
    const ui = {
        create: () => {
            // 创建切换按钮
            const toggleButton = document.createElement('button');
            toggleButton.className = 'jjfz-helper-toggle' + (settings.panelExpanded ? ' expanded' : '');
            toggleButton.textContent = settings.panelExpanded ? '收起' : '展开';
            toggleButton.onclick = function() {
                const panel = document.querySelector('.jjfz-helper-panel');
                settings.panelExpanded = !settings.panelExpanded;
                panel.style.display = settings.panelExpanded ? 'block' : 'none';
                this.textContent = settings.panelExpanded ? '收起' : '展开';
    
                // 根据展开状态更新按钮样式
                this.classList.toggle('expanded', settings.panelExpanded);
                settings.save();
            };
            document.body.appendChild(toggleButton);
    
            // 创建控制面板
            const panel = document.createElement('div');
            panel.className = 'jjfz-helper-panel';
            panel.style.display = settings.panelExpanded ? 'block' : 'none';
            panel.innerHTML = `
                <h3>培训助手</h3>
    
                <div class="jjfz-helper-option">
                    <button id="jjfz-toggle-auto" class="${settings.autoPlay ? 'stop' : ''}">${settings.autoPlay ? '⏸️ 暂停刷课' : '▶️ 开始刷课'}</button>
                </div>
    
                <div class="jjfz-helper-option">
                    <label>跳转控制</label>
                    <div style="display:flex;gap:5px;">
                        <input type="number" id="jjfz-jump-time" value="30" min="1" max="3600" style="flex:2;">
                        <button id="jjfz-jump-btn" style="flex:1;">快进(秒)⏩</button>
                    </div>
                </div>
    
                <div class="jjfz-helper-status">
                    <p>当前视频: <span id="jjfz-current-video">获取中...</span></p>
                    <p>学习进度: <span id="jjfz-progress">0/0</span></p>
                </div>
            `;
            document.body.appendChild(panel);
    
            // 添加事件监听
            ui.setupEventListeners();
        },
        
        // 设置事件监听
        setupEventListeners: () => {
            document.getElementById('jjfz-toggle-auto').addEventListener('click', function() {
                settings.autoPlay = !settings.autoPlay;
                this.textContent = settings.autoPlay ? '⏸️ 暂停刷课' : '▶️ 开始刷课';
                this.className = settings.autoPlay ? 'stop' : '';
                settings.save();
    
                if (settings.autoPlay) {
                    core.startAutoLearning();
                } else {
                    utils.clearAllTimers();
                }
            });
    
            document.getElementById('jjfz-jump-btn').addEventListener('click', function() {
                const seconds = parseInt(document.getElementById('jjfz-jump-time').value) || 30;
                core.addStudyTime(seconds); // 快进
            });
        },
        
        // 更新UI
        update: () => {
            const video = utils.getVideo();
            if (!video) return;
    
            // 更新当前视频信息
            const videoTitle = document.querySelector('.video_red1 a')?.textContent.trim() || "未知";
            const currentVideoEl = document.getElementById('jjfz-current-video');
            if (currentVideoEl) {
                currentVideoEl.textContent = videoTitle;
            }
    
            // 更新课程进度
            const total = document.querySelectorAll('.video_lists ul li').length;
            const current = Array.from(document.querySelectorAll('.video_lists ul li')).findIndex(li => li.classList.contains('video_red1')) + 1;
            const progressEl = document.getElementById('jjfz-progress');
            if (progressEl) {
                progressEl.textContent = `${current}/${total}`;
            }
        }
    };

    // 主函数
    function main() {
        // 创建UI
        ui.create();

        // 监听视频加载
        let checkCount = 0;
        const videoCheckInterval = setInterval(() => {
            const video = utils.getVideo();
            checkCount++;

            if (video) {
                clearInterval(videoCheckInterval);

                // 监听播放进度
                video.addEventListener('timeupdate', ui.update);

                // 自动学习模式
                if (settings.autoPlay) {
                    core.startAutoLearning();
                }

                ui.update();
            } else if (checkCount > 20) {
                // 20秒后仍未找到视频,停止检查
                clearInterval(videoCheckInterval);
                console.log("未能找到视频元素");
            }
        }, 1000);
    }

    // 启动脚本
    main();
})();