广西高等学校师资培训中心-自动学习课程

自动完成当前课程并跳转下一节

// ==UserScript==
// @name         广西高等学校师资培训中心-自动学习课程
// @namespace    http://tampermonkey.net/
// @version      1.00
// @description  自动完成当前课程并跳转下一节
// @author       RrOrange
// @match        *://gxgs.study.gspxonline.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @icon         https://s2.loli.net/2025/02/01/mphjJoCDTgaYFLZ.png
// @icon64       https://s2.loli.net/2025/02/01/mphjJoCDTgaYFLZ.png
// ==/UserScript==

(function() {
    'use strict';

    // 用户配置
    const config = {
        playbackSpeed: GM_getValue('playbackSpeed', 3), // 默认3倍速
        refreshInterval: GM_getValue('refreshInterval', 5) * 60 * 1000, // 默认5分钟刷新一次
        progressThreshold: 100, // 进度阈值改为100%
        maxCheckTimes: 5, // 最大检查次数
        autoClickStudy: GM_getValue('autoClickStudy', true), // 自动点击继续学习
        autoExpand: GM_getValue('autoExpand', false) // 自动展开所有小节
    };

    // 生成标签页唯一ID
    function generateTabId() {
        return 'tab_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }

    // 获取当前标签页ID
    function getTabId() {
        let tabId = sessionStorage.getItem('courseTabId');
        if (!tabId) {
            tabId = generateTabId();
            sessionStorage.setItem('courseTabId', tabId);
        }
        return tabId;
    }

    // 获取最后刷新时间
    function getLastRefresh() {
        const tabId = getTabId();
        const lastRefresh = GM_getValue(`lastRefresh_${tabId}`);
        // 如果没有上次刷新时间,设置为当前时间减去一个很小的值,确保倒计时立即开始
        return lastRefresh || (Date.now() - 1000);
    }

    // 设置最后刷新时间
    function setLastRefresh(time) {
        const tabId = getTabId();
        GM_setValue(`lastRefresh_${tabId}`, time);
    }

    // 清理过期的标签页刷新时间(可选)
    function cleanupOldTabs() {
        const now = Date.now();
        const allKeys = Object.keys(GM_getValue('', {}));
        allKeys.forEach(key => {
            if (key.startsWith('lastRefresh_')) {
                const lastRefresh = GM_getValue(key);
                if (now - lastRefresh > 24 * 60 * 60 * 1000) { // 24小时后清理
                    GM_deleteValue(key);
                }
            }
        });
    }

    // 修改添加设置界面函数
    function addSettingsUI() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            padding: 10px;
            border-radius: 5px;
            color: white;
            z-index: 9999;
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 14px;
        `;

        // 播放速度设置区域
        const speedContainer = document.createElement('div');
        speedContainer.style.display = 'flex';
        speedContainer.style.alignItems = 'center';
        speedContainer.style.gap = '5px';

        const speedLabel = document.createElement('label');
        speedLabel.textContent = '播放速度:';

        const speedInput = document.createElement('input');
        speedInput.type = 'number';
        speedInput.min = '0.1';
        speedInput.max = '16';
        speedInput.step = '0.1';
        speedInput.value = config.playbackSpeed;
        speedInput.style.cssText = `
            width: 50px;
            padding: 2px 5px;
            border: 1px solid #ccc;
            border-radius: 3px;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
        `;

        // 刷新时间设置区域
        const refreshContainer = document.createElement('div');
        refreshContainer.style.cssText = `
            display: flex;
            align-items: center;
            gap: 5px;
            margin-left: 15px;
            padding-left: 15px;
            border-left: 1px solid rgba(255, 255, 255, 0.3);
        `;

        const refreshLabel = document.createElement('label');
        refreshLabel.textContent = '刷新间隔(分钟):';

        const refreshInput = document.createElement('input');
        refreshInput.type = 'number';
        refreshInput.min = '1';
        refreshInput.max = '60';
        refreshInput.step = '1';
        refreshInput.value = config.refreshInterval / (60 * 1000);
        refreshInput.style.cssText = `
            width: 50px;
            padding: 2px 5px;
            border: 1px solid #ccc;
            border-radius: 3px;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
        `;

        const saveBtn = document.createElement('button');
        saveBtn.textContent = '保存';
        saveBtn.style.cssText = `
            padding: 2px 8px;
            background: #4CAF50;
            border: none;
            border-radius: 3px;
            color: white;
            cursor: pointer;
        `;

        // 当前速度显示
        const currentSpeed = document.createElement('span');
        currentSpeed.style.marginLeft = '10px';
        currentSpeed.textContent = `当前: ${config.playbackSpeed}x`;

        // 倒计时显示
        const timeDisplay = document.createElement('span');
        timeDisplay.style.marginLeft = '15px';
        timeDisplay.style.borderLeft = '1px solid rgba(255, 255, 255, 0.3)';
        timeDisplay.style.paddingLeft = '15px';
        updateTimeDisplay(timeDisplay);

        saveBtn.onclick = () => {
            const newSpeed = parseFloat(speedInput.value);
            const newInterval = parseInt(refreshInput.value);
            
            if (newSpeed >= 0.1 && newSpeed <= 16 && newInterval >= 1 && newInterval <= 60) {
                const speedChanged = newSpeed !== config.playbackSpeed;
                const intervalChanged = newInterval !== config.refreshInterval / (60 * 1000);
                
                // 保存播放速度
                config.playbackSpeed = newSpeed;
                GM_setValue('playbackSpeed', newSpeed);
                setVideoSpeed(newSpeed);
                currentSpeed.textContent = `当前: ${newSpeed}x`;
                
                // 保存刷新间隔
                config.refreshInterval = newInterval * 60 * 1000;
                GM_setValue('refreshInterval', newInterval);
                setLastRefresh(Date.now()); // 重置刷新时间
                
                // 如果设置有改变,提示用户并刷新页面
                if (speedChanged || intervalChanged) {
                    alert('设置已保存,页面将自动刷新以应用新设置');
                    window.location.reload();
                } else {
                    alert('设置已保存');
                }
            } else {
                alert('请输入有效的数值\n播放速度: 0.1-16\n刷新间隔: 1-60分钟');
            }
        };

        speedContainer.appendChild(speedLabel);
        speedContainer.appendChild(speedInput);
        
        refreshContainer.appendChild(refreshLabel);
        refreshContainer.appendChild(refreshInput);
        
        container.appendChild(speedContainer);
        container.appendChild(refreshContainer);
        container.appendChild(saveBtn);
        container.appendChild(currentSpeed);
        container.appendChild(timeDisplay);
        
        document.body.appendChild(container);

        return timeDisplay;
    }

    // 更新倒计时显示并检查是否需要刷新
    function updateTimeDisplay(element) {
        const now = Date.now();
        const lastRefresh = getLastRefresh();
        const nextRefresh = lastRefresh + config.refreshInterval;
        const remaining = Math.max(0, nextRefresh - now);
        
        // 更新倒计时显示
        const minutes = Math.floor(remaining / 60000);
        const seconds = Math.floor((remaining % 60000) / 1000);
        element.textContent = `下次刷新: ${minutes}:${seconds.toString().padStart(2, '0')}`;
        
        // 如果剩余时间小于等于0,且当前页面没有在刷新中
        if (remaining <= 0 && !window.isRefreshing) {
            window.isRefreshing = true;
            console.log('倒计时结束,准备刷新页面...');
            setTimeout(() => {
                refreshPage();
            }, 100);
            return;
        }
    }

    // 设置视频播放速度
    function setVideoSpeed(speed) {
        const video = document.querySelector('video');
        if (video) {
            video.playbackRate = speed;
        }
    }

    // 刷新页面
    function refreshPage() {
        if (window.isRefreshing) {
            console.log('正在刷新页面...');
            setLastRefresh(Date.now());
            window.location.reload();
        }
    }

    // 等待播放器加载完成
    function waitForPlayer() {
        return new Promise(resolve => {
            const checkPlayer = setInterval(() => {
                const player = document.querySelector('#aliplayer');
                if (player) {
                    clearInterval(checkPlayer);
                    resolve(player);
                }
            }, 1000);
        });
    }

    // 展开所有章节
    function expandAllSections() {
        const expandIcons = document.querySelectorAll('.section .title.section-name i.icon-plus');
        expandIcons.forEach(icon => {
            icon.click();
        });
    }

    // 获取所有课程的进度信息
    function getAllCoursesProgress() {
        const courses = Array.from(document.querySelectorAll('.resource li'));
        return courses.map(course => {
            const progressIcon = course.querySelector('.learn-status');
            const progressText = progressIcon ? progressIcon.textContent : '0%';
            const progress = parseInt(progressText);
            return {
                element: course,
                progress: isNaN(progress) ? 0 : progress,
                title: course.getAttribute('title')
            };
        });
    }

    // 检查是否所有课程都已完成
    function checkAllCoursesCompleted() {
        // 先展开所有章节
        expandAllSections();
        
        // 等待一下确保章节都已展开
        return new Promise(resolve => {
            setTimeout(() => {
                const courses = getAllCoursesProgress();
                const totalCourses = courses.length;
                const completedCourses = courses.filter(c => c.progress >= 100).length;
                
                console.log(`课程完成情况: ${completedCourses}/${totalCourses}`);
                resolve(completedCourses === totalCourses);
            }, 500); // 等待500ms确保章节展开完成
        });
    }

    // 修改查找下一个需要学习的课程函数
    async function findNextCourseToLearn() {
        const courses = getAllCoursesProgress();
        const currentCourse = document.querySelector('.resource li.active');
        const currentIndex = courses.findIndex(c => c.element === currentCourse);

        // 统计完成情况
        const totalCourses = courses.length;
        const completedCourses = courses.filter(c => c.progress >= 100).length;
        console.log(`课程完成情况: ${completedCourses}/${totalCourses}`);

        // 检查是否真的所有课程都完成了
        const allCompleted = await checkAllCoursesCompleted();
        if (allCompleted) {
            alert('恭喜!所有课程已学习完成!');
            return null;
        }

        // 优先查找未完成的课程(进度小于100%)
        let nextUnfinished = courses.find((course, index) => 
            index > currentIndex && course.progress < 100
        );

        // 如果当前位置后面没有未完成的课程,从头开始查找
        if (!nextUnfinished) {
            nextUnfinished = courses.find(course => course.progress < 100);
        }

        // 如果找到未完成的课程
        if (nextUnfinished) {
            console.log(`跳转到未完成课程: ${nextUnfinished.title} (进度: ${nextUnfinished.progress}%)`);
            return nextUnfinished.element;
        }

        return null;
    }

    // 修改跳转到下一节的函数
    async function goToNextSection() {
        const nextCourse = await getNextCourseLink();
        if (nextCourse) {
            nextCourse.click();
            return true;
        } else {
            const allCompleted = await checkAllCoursesCompleted();
            if (allCompleted) {
                console.log('所有课程已学习完成');
                alert('恭喜!所有课程已学习完成!');
            } else {
                // 当前课程未完成,继续学习
                ensureVideoPlaying();
            }
            return false;
        }
    }

    // 修改原有的getNextCourseLink函数
    async function getNextCourseLink() {
        // 先展开所有章节
        expandAllSections();
        
        // 获取当前激活的课程
        const activeCourse = document.querySelector('.resource li.active');
        if (!activeCourse) return null;

        // 获取当前课程进度
        const currentProgress = checkProgress();
        
        // 如果当前课程未完成(进度小于100%),继续学习当前课程
        if (currentProgress < 100) {
            return null;
        }

        // 查找下一个需要学习的课程
        return await findNextCourseToLearn();
    }

    // 检查学习进度
    function checkProgress() {
        // 检查当前课程的进度标签
        const activeLesson = document.querySelector('.resource li.active');
        if (!activeLesson) return 0;

        const progressIcon = activeLesson.querySelector('.learn-status');
        if (progressIcon) {
            const progressText = progressIcon.textContent;
            // 提取百分比数字
            const progress = parseInt(progressText);
            return isNaN(progress) ? 0 : progress;
        }

        return 0;
    }

    // 检查并确保视频在播放
    function ensureVideoPlaying() {
        const video = document.querySelector('video');
        if (video) {
            if (video.paused) {
                clickPlayButton();
            }
            // 设置播放速度
            setVideoSpeed(config.playbackSpeed);
        }
    }

    // 点击播放按钮
    function clickPlayButton() {
        // 查找大播放按钮
        const bigPlayBtn = document.querySelector('.prism-big-play-btn');
        if (bigPlayBtn && bigPlayBtn.style.display !== 'none') {
            bigPlayBtn.click();
            return true;
        }
        
        // 查找控制栏播放按钮
        const playBtn = document.querySelector('.prism-play-btn');
        if (playBtn) {
            playBtn.click();
            return true;
        }
        
        return false;
    }

    // 添加开关按钮
    function addToggleButtons(container) {
        // 添加分隔线
        const divider = document.createElement('div');
        divider.style.cssText = `
            border-left: 1px solid rgba(255, 255, 255, 0.3);
            margin: 0 15px;
            height: 20px;
        `;

        // 自动点击开关
        const clickToggle = document.createElement('div');
        clickToggle.style.cssText = `
            display: flex;
            align-items: center;
            gap: 5px;
        `;
        
        const clickCheckbox = document.createElement('input');
        clickCheckbox.type = 'checkbox';
        clickCheckbox.checked = config.autoClickStudy;
        clickCheckbox.style.cursor = 'pointer';
        
        const clickLabel = document.createElement('label');
        clickLabel.textContent = '自动点击继续学习';
        clickLabel.style.cursor = 'pointer';
        
        clickCheckbox.onchange = (e) => {
            config.autoClickStudy = e.target.checked;
            GM_setValue('autoClickStudy', config.autoClickStudy);
        };

        // 展开/收缩开关
        const expandToggle = document.createElement('div');
        expandToggle.style.cssText = `
            display: flex;
            align-items: center;
            gap: 5px;
            margin-left: 15px;
        `;
        
        const expandBtn = document.createElement('button');
        expandBtn.textContent = config.autoExpand ? '收缩所有' : '展开所有';
        expandBtn.style.cssText = `
            padding: 2px 8px;
            background: #2196F3;
            border: none;
            border-radius: 3px;
            color: white;
            cursor: pointer;
        `;
        
        expandBtn.onclick = () => {
            config.autoExpand = !config.autoExpand;
            GM_setValue('autoExpand', config.autoExpand);
            expandBtn.textContent = config.autoExpand ? '收缩所有' : '展开所有';
            toggleAllSections(config.autoExpand);
        };

        clickToggle.appendChild(clickCheckbox);
        clickToggle.appendChild(clickLabel);
        expandToggle.appendChild(expandBtn);

        container.appendChild(divider);
        container.appendChild(clickToggle);
        container.appendChild(expandToggle);
    }

    // 展开/收缩所有小节
    function toggleAllSections(expand) {
        const sections = document.querySelectorAll('.section .title.section-name');
        sections.forEach(section => {
            const icon = section.querySelector('i.iconfont');
            if (icon) {
                const isExpanded = icon.classList.contains('icon-minus');
                if ((expand && !isExpanded) || (!expand && isExpanded)) {
                    section.click();
                }
            }
        });
    }

    // 修改检查并点击"继续学习"按钮函数
    function checkAndClickStudyButton() {
        // 如果自动点击已关闭,直接返回
        if (!config.autoClickStudy) {
            return;
        }

        let checkCount = 0;
        let hasClicked = false;
        const courseUrls = [
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1068',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1065',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1067',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1066',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1069',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1070',
            'http://gxgs.study.gspxonline.com/resource/index?route=/study/course/1071',
        ];

        // 检查当前URL是否匹配课程URL
        if (!courseUrls.some(url => window.location.href.includes(url))) {
            return;
        }

        const checkInterval = setInterval(() => {
            if (hasClicked || checkCount >= config.maxCheckTimes) {
                clearInterval(checkInterval);
                return;
            }

            const studyButton = document.querySelector('a.btn.btn-primary.btn-study');
            if (studyButton && studyButton.textContent.trim() === '继续学习') {
                console.log('找到继续学习按钮,准备点击...');
                studyButton.click();
                hasClicked = true;
                clearInterval(checkInterval);
            }

            checkCount++;
            if (checkCount >= config.maxCheckTimes) {
                console.log('已达到最大检查次数,停止检查');
            }
        }, 1000); // 每秒检查一次
    }

    // 主要逻辑
    async function main() {
        // 重置刷新标记
        window.isRefreshing = false;

        // 清理过期标签页数据
        cleanupOldTabs();

        // 检查是否需要立即刷新
        const now = Date.now();
        const lastRefresh = getLastRefresh();
        if (now - lastRefresh >= config.refreshInterval) {
            console.log('检测到需要刷新...');
            refreshPage();
            return;
        }

        // 如果是首次加载,设置初始刷新时间
        if (!GM_getValue(`lastRefresh_${getTabId()}`)) {
            setLastRefresh(Date.now());
        }

        // 添加设置界面
        const timeDisplay = addSettingsUI();
        
        // 添加开关按钮
        addToggleButtons(timeDisplay.parentElement);

        // 检查并点击"继续学习"按钮
        checkAndClickStudyButton();
        
        // 等待播放器加载
        const player = await waitForPlayer();
        
        // 监听视频播放结束事件
        player.addEventListener('ended', goToNextSection);

        // 定期检查进度和播放状态
        setInterval(() => {
            const progress = checkProgress();
            console.log('当前进度:', progress + '%');
            
            if (progress >= config.progressThreshold) {
                goToNextSection();
            } else {
                // 如果进度小于100%,确保视频在播放
                ensureVideoPlaying();
            }
        }, 5000); // 每5秒检查一次进度

        // 定期更新倒计时并检查是否需要刷新
        setInterval(() => {
            updateTimeDisplay(timeDisplay);
        }, 1000);

        // 初始自动播放视频
        ensureVideoPlaying();
    }

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