超星学习通自动播放器(章节修复版)

修复章节导航逻辑,支持多级章节结构

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         超星学习通自动播放器(章节修复版)
// @namespace    http://tampermonkey.net/
// @version      3.6
// @description  修复章节导航逻辑,支持多级章节结构
// @author       Iron_Grey_
// @match        *://mooc1.chaoxing.com/mycourse/studentstudy*
// @match        *://mooc1.chaoxing.com/mooc-ans/mycourse/studentstudy*
// @grant        none
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    const style = document.createElement('style');
    style.textContent = `
        .cx-super-player-btn {
            position: fixed !important;
            top: 20px !important;
            right: 20px !important;
            z-index: 100000 !important;
            padding: 12px 18px !important;
            background: linear-gradient(45deg, #FF6B6B, #4ECDC4) !important;
            color: white !important;
            border: none !important;
            border-radius: 25px !important;
            cursor: pointer !important;
            font-size: 14px !important;
            font-weight: bold !important;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important;
            transition: all 0.3s ease !important;
            font-family: 'Microsoft YaHei', sans-serif !important;
        }
        .cx-super-player-btn:hover {
            transform: translateY(-3px) !important;
            box-shadow: 0 6px 20px rgba(0,0,0,0.3) !important;
        }
        .cx-super-player-btn:active {
            transform: translateY(-1px) !important;
        }
        .cx-super-player-btn.playing {
            background: linear-gradient(45deg, #4ECDC4, #45B7D1) !important;
            animation: pulse 2s infinite !important;
        }
        .cx-super-player-btn.error {
            background: linear-gradient(45deg, #FF6B6B, #FF8E53) !important;
        }
        .cx-super-player-btn.completed {
            background: linear-gradient(45deg, #A166AB, #5073B8) !important;
        }
        .cx-super-player-btn.skipping {
            background: linear-gradient(45deg, #FFA726, #FF7043) !important;
        }
        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.05); }
            100% { transform: scale(1); }
        }
    `;
    document.head.appendChild(style);

    let isAutoPlaying = false;
    let controlButton = null;
    let currentProcess = null;

    // 创建控制按钮
    function createControlButton() {
        const existingBtn = document.querySelector('.cx-super-player-btn');
        if (existingBtn) existingBtn.remove();

        const button = document.createElement('button');
        button.className = 'cx-super-player-btn';
        button.innerHTML = '🎬 开始自动播放';

        button.addEventListener('click', toggleAutoPlay);
        document.body.appendChild(button);

        controlButton = button;
        return button;
    }

    // 更新按钮状态
    function updateButtonState(state, text = null) {
        if (!controlButton) return;

        controlButton.className = `cx-super-player-btn ${state}`;

        if (text) {
            controlButton.innerHTML = text;
        } else {
            switch(state) {
                case 'stopped':
                    controlButton.innerHTML = '🎬 开始自动播放';
                    break;
                case 'playing':
                    controlButton.innerHTML = '⏸️ 自动播放中...';
                    break;
                case 'error':
                    controlButton.innerHTML = '❌ 出错 - 重新开始';
                    break;
                case 'completed':
                    controlButton.innerHTML = '✅ 所有任务完成';
                    break;
                case 'skipping':
                    controlButton.innerHTML = '⏭️ 跳过非视频章节...';
                    break;
            }
        }
    }

    // 切换自动播放
    function toggleAutoPlay() {
        if (isAutoPlaying) {
            stopAutoPlay();
        } else {
            startAutoPlay();
        }
    }

    // 停止自动播放
    function stopAutoPlay() {
        isAutoPlaying = false;
        updateButtonState('stopped');

        if (currentProcess) {
            clearTimeout(currentProcess);
            currentProcess = null;
        }

        console.log('自动播放已停止');
    }

    // 开始自动播放
    function startAutoPlay() {
        isAutoPlaying = true;
        updateButtonState('playing');
        autoPlayProcess();
    }

    // 自动播放主流程
    async function autoPlayProcess() {
        try {
            while (isAutoPlaying) {
                console.log('=== 开始处理当前章节 ===');

                await wait(3000);

                const chapterResult = await processCurrentChapter();

                if (chapterResult === 'skip') {
                    console.log('跳过非视频章节,继续下一章节');
                    const hasNext = await goToNextChapter();
                    if (!hasNext) {
                        console.log('所有章节已完成');
                        updateButtonState('completed');
                        isAutoPlaying = false;
                        break;
                    }
                    await wait(3000);
                    continue;
                } else if (chapterResult === 'success') {
                    const hasNext = await goToNextChapter();
                    if (!hasNext) {
                        console.log('所有章节已完成');
                        updateButtonState('completed');
                        isAutoPlaying = false;
                        break;
                    }
                    await wait(5000);
                } else {
                    console.log('当前章节处理失败,等待后重试...');
                    await wait(5000);
                }
            }
        } catch (error) {
            console.error('自动播放出错:', error);
            updateButtonState('error');
            isAutoPlaying = false;
        }
    }

    // 处理当前章节 - 返回 'success', 'skip', 或 'error'
    async function processCurrentChapter() {
        try {
            console.log('步骤1: 查找知识卡片iframe...');

            // 获取第一层iframe (知识卡片iframe)
            const cardIframe = await waitForElement('iframe[src*="/mooc-ans/knowledge/cards"]', 10000);
            if (!cardIframe) {
                console.log('未找到知识卡片iframe,可能是非视频章节');
                return await handleNonVideoChapter();
            }

            await waitForIframeLoad(cardIframe);

            console.log('步骤2: 检查章节内容类型...');
            const chapterType = await detectChapterType(cardIframe);

            if (chapterType === 'video') {
                console.log('检测到视频章节,开始处理视频...');
                return await processVideoChapter(cardIframe);
            } else if (chapterType === 'document' || chapterType === 'text') {
                console.log(`检测到${chapterType === 'document' ? '文档' : '文本'}章节,自动跳过...`);
                return await handleNonVideoChapter();
            } else {
                console.log('未知章节类型,尝试跳过...');
                return await handleNonVideoChapter();
            }

        } catch (error) {
            console.error('处理当前章节出错:', error);
            return 'error';
        }
    }

    // 检测章节类型
    async function detectChapterType(cardIframe) {
        try {
            const cardDoc = getIframeDocument(cardIframe);
            if (!cardDoc) return 'unknown';

            // 检查是否有视频iframe
            const videoIframe = cardDoc.querySelector('iframe[src*="modules/video/index.html"]');
            if (videoIframe) {
                return 'video';
            }

            // 检查是否有文档内容
            const documentElements = cardDoc.querySelectorAll('[class*="doc"], [class*="document"], .ans-cc, .ans-doc');
            if (documentElements.length > 0) {
                return 'document';
            }

            // 检查是否有文本内容
            const textElements = cardDoc.querySelectorAll('.ans-attach-ct, .knowledge, .cata_content');
            if (textElements.length > 0) {
                return 'text';
            }

            // 检查是否有测验
            const quizElements = cardDoc.querySelectorAll('[class*="work"], [class*="quiz"], [class*="test"]');
            if (quizElements.length > 0) {
                return 'quiz';
            }

            return 'unknown';
        } catch (error) {
            console.log('检测章节类型失败:', error);
            return 'unknown';
        }
    }

    // 处理视频章节
    async function processVideoChapter(cardIframe) {
        try {
            // 获取第二层iframe (视频播放iframe)
            const videoIframe = await waitForElementInIframe(cardIframe, 'iframe[src*="modules/video/index.html"]', 10000);
            if (!videoIframe) {
                console.log('未找到视频播放iframe');
                return 'error';
            }

            await waitForIframeLoad(videoIframe);

            console.log('步骤3: 设置播放速度...');
            await setPlaybackSpeed(videoIframe);

            console.log('步骤4: 点击外层防暂停按钮...');
            await clickOuterPreventPause();

            console.log('步骤5: 点击内层播放按钮并开始播放...');
            await clickInnerPlayButtonAndStart(videoIframe);

            console.log('步骤6: 等待任务完成...');
            const completed = await waitForTaskCompletion(cardIframe, videoIframe);

            if (completed) {
                console.log('当前章节任务完成,等待20秒...');
                await wait(20000);
                return 'success';
            } else {
                console.log('任务完成检测超时,继续下一章节');
                await wait(5000);
                return 'success'; // 即使超时也继续下一章节
            }
        } catch (error) {
            console.error('处理视频章节出错:', error);
            return 'error';
        }
    }

    // 处理非视频章节
    async function handleNonVideoChapter() {
        try {
            updateButtonState('skipping');

            console.log('处理非视频章节...');

            // 检查当前章节是否已经完成
            const isCompleted = await checkIfChapterCompleted();

            if (isCompleted) {
                console.log('非视频章节已完成,直接跳过');
                return 'skip';
            } else {
                console.log('非视频章节未完成,尝试标记为完成或等待...');

                // 尝试点击可能存在的完成按钮
                const marked = await tryMarkChapterAsComplete();

                if (marked) {
                    console.log('成功标记非视频章节为完成');
                    await wait(5000); // 等待标记完成
                    return 'skip';
                } else {
                    console.log('无法自动完成非视频章节,等待30秒后跳过');
                    await wait(30000); // 等待30秒让用户手动处理
                    return 'skip';
                }
            }
        } catch (error) {
            console.log('处理非视频章节时出错:', error);
            return 'skip'; // 即使出错也跳过
        }
    }

    // 检查当前章节是否已完成
    async function checkIfChapterCompleted() {
        try {
            // 在主页面查找章节完成状态
            const currentChapter = document.querySelector('.posCatalog_select.posCatalog_active');
            if (!currentChapter) return false;

            // 检查是否有未完成任务点标记
            const unfinishedMark = currentChapter.querySelector('.orangeNew, .catalog_points_yi');
            if (unfinishedMark) {
                console.log('发现未完成任务点标记');
                return false;
            }

            // 检查隐藏的未完成任务点数
            const jobUnfinishCount = currentChapter.querySelector('.jobUnfinishCount');
            if (jobUnfinishCount && jobUnfinishCount.value !== '0') {
                console.log(`还有${jobUnfinishCount.value}个未完成任务点`);
                return false;
            }

            // 检查是否有完成状态的类名
            const completedSelectors = [
                '[class*="finish"]',
                '[class*="complete"]',
                '.ans-job-finished'
            ];

            for (let selector of completedSelectors) {
                const elements = document.querySelectorAll(selector);
                if (elements.length > 0) {
                    console.log('找到完成状态元素');
                    return true;
                }
            }

            return false;
        } catch (error) {
            console.log('检查章节完成状态失败:', error);
            return false;
        }
    }

    // 尝试标记章节为完成
    async function tryMarkChapterAsComplete() {
        try {
            // 尝试在知识卡片iframe中查找完成按钮
            const cardIframe = await waitForElement('iframe[src*="/mooc-ans/knowledge/cards"]', 5000);
            if (!cardIframe) return false;

            const cardDoc = getIframeDocument(cardIframe);
            if (!cardDoc) return false;

            // 查找可能的完成按钮
            const completeButtons = cardDoc.querySelectorAll('button, input[type="button"], a');
            for (let button of completeButtons) {
                const text = button.textContent || button.value || '';
                if (text.includes('完成') || text.includes('提交') || text.includes('确认')) {
                    console.log('点击完成按钮:', text);
                    button.click();
                    return true;
                }
            }

            return false;
        } catch (error) {
            console.log('标记章节完成失败:', error);
            return false;
        }
    }

    // 设置播放速度 - 在视频iframe中
    async function setPlaybackSpeed(videoIframe) {
        try {
            const videoDoc = getIframeDocument(videoIframe);
            if (!videoDoc) return false;

            const speedSelect = videoDoc.querySelector('select');
            if (speedSelect && speedSelect.innerHTML.includes('1.25')) {
                speedSelect.value = '1.25';
                speedSelect.dispatchEvent(new Event('change', { bubbles: true }));
                console.log('✓ 播放速度设置为1.25x');
                return true;
            }

            console.log('未找到速度选择器');
            return false;
        } catch (error) {
            console.log('设置播放速度失败:', error);
            return false;
        }
    }

    // 点击外层防暂停按钮 - 在主页面中
    async function clickOuterPreventPause() {
        try {
            // 在主页面中查找防暂停按钮
            const buttons = document.querySelectorAll('button');
            for (let button of buttons) {
                if (button.textContent.includes('阻止暂停') ||
                    button.textContent.includes('防暂停') ||
                    (button.className && button.className.includes('h_Bbutton'))) {
                    button.click();
                    console.log('✓ 已点击外层防暂停按钮');
                    return true;
                }
            }

            console.log('未找到外层防暂停按钮');
            return false;
        } catch (error) {
            console.log('点击外层防暂停按钮失败:', error);
            return false;
        }
    }

    // 点击内层播放按钮并开始播放 - 在视频iframe中
    async function clickInnerPlayButtonAndStart(videoIframe) {
        try {
            const videoDoc = getIframeDocument(videoIframe);
            if (!videoDoc) return false;

            // 首先点击播放按钮
            const playButton = videoDoc.querySelector('.vjs-big-play-button');
            if (playButton) {
                playButton.click();
                console.log('✓ 已点击内层播放按钮');
                await wait(1000); // 等待播放开始
            } else {
                console.log('未找到内层播放按钮');
            }

            // 确保视频开始播放
            const video = videoDoc.querySelector('video');
            if (video) {
                video.muted = true; // 静音以提高自动播放成功率

                // 如果视频没有在播放,尝试播放
                if (video.paused) {
                    try {
                        await video.play();
                        console.log('✓ 视频开始播放');
                    } catch (playError) {
                        console.log('自动播放被阻止,可能需要手动点击');
                    }
                } else {
                    console.log('✓ 视频已在播放');
                }

                return true;
            }

            console.log('未找到视频元素');
            return false;
        } catch (error) {
            console.log('开始视频播放失败:', error);
            return false;
        }
    }

    // 等待任务完成 - 基于aria-label属性
    async function waitForTaskCompletion(cardIframe, videoIframe) {
        return new Promise((resolve) => {
            console.log('等待任务完成...');
            let checkCount = 0;
            const maxChecks = 1800; // 30分钟超时

            // 使用MutationObserver监听DOM变化
            let observer = null;
            let cardDoc = null;

            try {
                cardDoc = getIframeDocument(cardIframe);
                if (cardDoc) {
                    // 创建MutationObserver监听任务状态变化
                    observer = new MutationObserver((mutations) => {
                        mutations.forEach((mutation) => {
                            if (mutation.type === 'attributes' && mutation.attributeName === 'aria-label') {
                                console.log('检测到aria-label属性变化');
                                if (checkTaskCompleted(cardDoc)) {
                                    observer.disconnect();
                                    resolve(true);
                                }
                            }
                        });
                    });

                    // 观察整个文档的变化,特别关注aria-label属性
                    observer.observe(cardDoc.body, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        attributeFilter: ['class', 'aria-label']
                    });
                }
            } catch (error) {
                console.log('无法创建MutationObserver,使用回退轮询方案');
            }

            function checkCompletion() {
                if (!isAutoPlaying) {
                    if (observer) observer.disconnect();
                    resolve(false);
                    return;
                }

                checkCount++;

                try {
                    // 检查任务完成状态
                    let completed = false;

                    // 方法1: 基于aria-label属性检查任务完成状态
                    if (cardDoc) {
                        completed = checkTaskCompleted(cardDoc);
                    }

                    // 方法2: 检查视频是否播放完毕(备用方案)
                    if (!completed) {
                        const videoDoc = getIframeDocument(videoIframe);
                        if (videoDoc) {
                            const video = videoDoc.querySelector('video');
                            if (video && video.duration && video.currentTime >= video.duration - 10) {
                                completed = true;
                                console.log('✓ 视频播放完毕,认为任务完成');
                            }
                        }
                    }

                    if (completed) {
                        if (observer) observer.disconnect();
                        console.log('✓ 检测到任务完成');
                        resolve(true);
                        return;
                    }

                    if (checkCount >= maxChecks) {
                        if (observer) observer.disconnect();
                        console.log('任务完成检测超时');
                        resolve(false);
                        return;
                    }

                    // 更新等待状态
                    if (checkCount % 60 === 0) {
                        const minutes = Math.floor(checkCount / 60);
                        updateButtonState('playing', `⏳ 播放中... (${minutes}分钟)`);
                    }

                    currentProcess = setTimeout(checkCompletion, 1000);
                } catch (error) {
                    console.log('检查任务状态时出错:', error);
                    currentProcess = setTimeout(checkCompletion, 5000);
                }
            }

            // 辅助函数:基于aria-label属性检查任务完成状态
            function checkTaskCompleted(doc) {
                // 查找所有任务点状态指示器
                const jobIcons = doc.querySelectorAll('.ans-job-icon, .ans-job-icon-clear');

                let allCompleted = true;
                let foundAnyJobIcon = false;

                for (let icon of jobIcons) {
                    const ariaLabel = icon.getAttribute('aria-label');
                    if (ariaLabel) {
                        foundAnyJobIcon = true;

                        if (ariaLabel.includes('任务点未完成')) {
                            console.log('发现未完成任务点:', ariaLabel);
                            allCompleted = false;
                            break;
                        } else if (ariaLabel.includes('任务点已完成')) {
                            console.log('发现已完成任务点:', ariaLabel);
                            // 继续检查其他任务点
                        } else {
                            console.log('发现未知状态任务点:', ariaLabel);
                            // 如果有未知状态,保守起见认为未完成
                            allCompleted = false;
                        }
                    }
                }

                // 如果没有找到任何任务点指示器,检查其他完成标志
                if (!foundAnyJobIcon) {
                    console.log('未找到任务点指示器,检查其他完成标志');

                    // 检查是否有完成状态的元素
                    const completedSelectors = [
                        '.ans-job-finished',
                        '[class*="finished"]',
                        '[class*="completed"]',
                        '.jobUnfinishCount[value="0"]'
                    ];

                    for (let selector of completedSelectors) {
                        const elements = doc.querySelectorAll(selector);
                        if (elements.length > 0) {
                            console.log(`✓ 找到完成状态元素: ${selector}`);
                            return true;
                        }
                    }

                    // 如果没有明确完成标志,保守起见认为未完成
                    return false;
                }

                return allCompleted;
            }

            // 立即检查一次,然后启动轮询或依赖Observer
            const initialCheck = checkTaskCompleted(cardDoc);
            if (initialCheck) {
                if (observer) observer.disconnect();
                resolve(true);
            } else {
                if (!observer) {
                    // 如果没有Observer,使用轮询方案
                    checkCompletion();
                }
            }
        });
    }

    // 切换到下一章节 - 完全重写,支持多级章节结构
    async function goToNextChapter() {
        try {
            // 获取所有可点击的章节元素(包括所有层级的)
            const allChapterItems = document.querySelectorAll('.posCatalog_select:not(.firstLayer)');

            if (allChapterItems.length === 0) {
                console.log('未找到任何章节');
                return false;
            }

            // 查找当前选中的章节
            let currentChapterIndex = -1;
            for (let i = 0; i < allChapterItems.length; i++) {
                if (allChapterItems[i].classList.contains('posCatalog_active')) {
                    currentChapterIndex = i;
                    break;
                }
            }

            if (currentChapterIndex === -1) {
                console.log('未找到当前选中的章节,尝试选择第一个未完成章节');
                // 如果没有当前选中章节,选择第一个有未完成任务点的章节
                for (let i = 0; i < allChapterItems.length; i++) {
                    const chapter = allChapterItems[i];
                    const hasUnfinished = chapter.querySelector('.orangeNew, .catalog_points_yi, .jobUnfinishCount[value!="0"]');
                    if (hasUnfinished) {
                        const chapterName = chapter.querySelector('.posCatalog_name');
                        if (chapterName) {
                            const chapterTitle = chapterName.textContent.trim();
                            console.log(`✓ 选择第一个未完成章节: ${chapterTitle}`);
                            chapterName.click();
                            return true;
                        }
                    }
                }
                return false;
            }

            // 查找下一章节(当前章节的下一个)
            const nextChapterIndex = currentChapterIndex + 1;

            if (nextChapterIndex >= allChapterItems.length) {
                console.log('已经是最后一个章节,没有下一章节了');
                return false;
            }

            const nextChapter = allChapterItems[nextChapterIndex];

            // 点击下一章节
            const chapterName = nextChapter.querySelector('.posCatalog_name');
            if (chapterName) {
                const chapterTitle = chapterName.textContent.trim();
                console.log(`✓ 切换到下一章节: ${chapterTitle}`);
                chapterName.click();
                return true;
            } else {
                console.log('未找到下一章节的名称元素');
                return false;
            }

        } catch (error) {
            console.log('切换章节时出错:', error);
            return false;
        }
    }

    // 工具函数:等待元素出现
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve) => {
            const startTime = Date.now();

            function check() {
                const element = document.querySelector(selector);
                if (element) {
                    resolve(element);
                } else if (Date.now() - startTime >= timeout) {
                    resolve(null);
                } else {
                    setTimeout(check, 500);
                }
            }

            check();
        });
    }

    // 工具函数:在iframe中等待元素出现
    function waitForElementInIframe(iframe, selector, timeout = 10000) {
        return new Promise((resolve) => {
            const startTime = Date.now();

            function check() {
                try {
                    const doc = iframe.contentDocument || iframe.contentWindow.document;
                    const element = doc.querySelector(selector);
                    if (element) {
                        resolve(element);
                    } else if (Date.now() - startTime >= timeout) {
                        resolve(null);
                    } else {
                        setTimeout(check, 500);
                    }
                } catch (error) {
                    if (Date.now() - startTime >= timeout) {
                        resolve(null);
                    } else {
                        setTimeout(check, 500);
                    }
                }
            }

            check();
        });
    }

    // 工具函数:等待iframe加载完成
    function waitForIframeLoad(iframe) {
        return new Promise((resolve) => {
            if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
                resolve();
            } else {
                iframe.addEventListener('load', () => resolve());
            }
        });
    }

    // 工具函数:安全获取iframe文档
    function getIframeDocument(iframe) {
        try {
            return iframe.contentDocument || iframe.contentWindow.document;
        } catch (error) {
            console.log('无法访问iframe文档:', error);
            return null;
        }
    }

    // 工具函数:等待指定时间
    function wait(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 初始化
    function init() {
        console.log('超星学习通自动播放器(章节修复版)初始化...');
        createControlButton();

        const observer = new MutationObserver(() => {
            if (!document.querySelector('.cx-super-player-btn')) {
                createControlButton();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        console.log('自动播放器已就绪');
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();