安徽继续教育全自动刷课终极版

[稳定版]

// ==UserScript==
// @name         安徽继续教育全自动刷课终极版
// @namespace    http://tampermonkey.net/
// @version      7.6
// @description  [稳定版]
// @author       xiaohanxi
// @match        *://main.ahjxjy.cn/study/html/content/studying/*
// @grant        GM_addStyle
// @grant        GM_notification
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @run-at       document-end
// @license      GPL 3
// ==/UserScript==

(function() {
    'use strict';

    const unsafe = unsafeWindow || window;
    const state = {
        panel: null,
        isCollapsed: GM_getValue('panelCollapsed', false),
        currentSpeed: GM_getValue('playbackSpeed', 1),
        volumeLevel: GM_getValue('volumeLevel', 1),
        userInteracted: false,
        isDragging: false,
        dragStartX: 0,
        dragStartY: 0,
        isAutoJumpEnabled: GM_getValue('isAutoJumpEnabled', true),
        logMessages: [],
        isPlaying: false,
        hasConfirmedGroup: GM_getValue('hasConfirmedGroup', false)
    };

    // QQ群信息(替换为实际群号)
    const QQ_GROUP_INFO = {
        number: "1038024672", // 你的QQ群号
        description: "获取脚本更新、使用帮助和问题反馈",
        // QQ加群链接格式:https://qm.qq.com/cgi-bin/qm/qr?k=群密钥&jump_from=webapi
        // 可通过QQ群设置中的"群推广"获取具体链接
        url: "https://qm.qq.com/q/dVaitVAAQ8"
    };

    // 错误捕获函数
    function logError(message, error) {
        const errorMsg = `[错误] ${message}: ${error.message || error}`;
        console.error(errorMsg);
        state.logMessages.push(`[${new Date().toLocaleTimeString()}] ${errorMsg}`);
    }

    // 显示QQ群提示(可点击跳转)
    function showGroupPrompt() {
        if (state.hasConfirmedGroup) return;

        const promptDiv = document.createElement('div');
        promptDiv.style.cssText = `
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            z-index: 2147483648;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            padding: 24px;
            width: 320px;
            text-align: center;
        `;

        promptDiv.innerHTML = `
            <h3 style="margin-top: 0;">欢迎使用刷课脚本</h3>
            <p>为了更好地获取脚本更新和使用帮助,建议加入QQ群:</p>
            <a href="${QQ_GROUP_INFO.url}" target="_blank" style="font-size: 1.2em; font-weight: bold; margin: 15px 0; display: inline-block; color: #4CAF50; text-decoration: underline;">
                ${QQ_GROUP_INFO.number}
            </a>
            <p>${QQ_GROUP_INFO.description}</p>
            <div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center;">
                <button class="confirm-btn" style="padding: 8px 20px; border: none; border-radius: 6px; background: #4CAF50; color: white; cursor: pointer;">
                    已加入/知道了
                </button>
            </div>
        `;

        document.body.appendChild(promptDiv);

        promptDiv.querySelector('.confirm-btn').addEventListener('click', () => {
            document.body.removeChild(promptDiv);
            state.hasConfirmedGroup = true;
            GM_setValue('hasConfirmedGroup', true);
        });
    }

    // 修复混合内容问题
    function fixMixedContent() {
        try {
            document.querySelectorAll('script[src^="http://"]').forEach(script => {
                if (script.src.includes('socket.io')) {
                    const httpsSrc = script.src.replace('http://', 'https://');
                    script.src = httpsSrc;
                }
            });
        } catch (error) {
            logError('修复混合内容时出错', error);
        }
    }

    // 强化样式
    GM_addStyle(`
    .control-panel {
        position: fixed;
        left: 20px;
        top: 50%;
        transform: translateY(-50%);
        z-index: 2147483647;
        background: rgba(255,255,255,0.98);
        border-radius: 24px;
        box-shadow: 0 12px 32px rgba(0,0,0,0.18);
        backdrop-filter: blur(16px);
        border: 1px solid rgba(255,255,255,0.2);
        transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        overflow: hidden;
        cursor: grab;
        opacity: 0.95;
        width: ${state.isCollapsed ? '60px' : '320px'};
        max-height: ${state.isCollapsed ? '60px' : '500px'};
        display: block !important;
    }

    .panel-content {
        padding: 24px;
        display: flex;
        flex-direction: column;
        gap: 20px;
    }

    .function-toggle {
        display: flex;
        align-items: center;
        gap: 12px;
    }

    .toggle-switch {
        position: relative;
        width: 60px;
        height: 34px;
    }

    .toggle-switch input {
        display: none;
    }

    .slider-switch {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #ccc;
        border-radius: 34px;
        transition: 0.4s;
    }

    .slider-switch:before {
        position: absolute;
        content: "";
        height: 26px;
        width: 26px;
        left: 4px;
        bottom: 4px;
        background-color: white;
        border-radius: 50%;
        transition: 0.4s;
    }

    input:checked + .slider-switch {
        background-color: #4CAF50;
    }

    input:checked + .slider-switch:before {
        transform: translateX(26px);
    }

    .log-container {
        height: 120px;
        overflow-y: auto;
        border: 1px solid #e0e0e0;
        border-radius: 12px;
        padding: 12px;
        font-size: 0.9em;
        color: #666;
    }

    .log-message {
        margin: 4px 0;
    }

    .control-btn {
        padding: 12px 24px;
        border-radius: 24px;
        border: none;
        background: #f0f0f0;
        cursor: pointer;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        gap: 12px;
    }

    .control-btn:hover {
        background: #e0e0e0;
    }

    .control-btn:active {
        transform: scale(0.98);
    }

    .control-btn.play-btn {
        background: #4CAF50;
        color: white;
    }

    .control-btn.play-btn:hover {
        background: #45a049;
    }

    .speed-slider-container {
        display: flex;
        align-items: center;
        gap: 12px;
    }

    .speed-value {
        min-width: 30px;
        text-align: center;
        font-weight: bold;
    }

    .slider {
        -webkit-appearance: none;
        width: 100%;
        height: 4px;
        background: #ddd;
        border-radius: 2px;
        margin-top: 8px;
        cursor: pointer;
    }

    .slider::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 24px;
        height: 24px;
        background: #4CAF50;
        border-radius: 50%;
        cursor: pointer;
    }

    .volume-slider {
        -webkit-appearance: none;
        width: 100%;
        height: 4px;
        background: #ddd;
        border-radius: 2px;
        margin-top: 8px;
        cursor: pointer;
    }

    .volume-slider::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 24px;
        height: 24px;
        background: #4CAF50;
        border-radius: 50%;
        cursor: pointer;
    }

    /* 群号链接样式 */
    .group-link {
        color: #4CAF50;
        text-decoration: underline;
        cursor: pointer;
        font-weight: bold;
    }
    .group-link:hover {
        color: #45a049;
    }
    `);

    // 创建控制面板
    function createControlPanel() {
        try {
            const panel = document.createElement('div');
            panel.className = `control-panel${state.isCollapsed ? ' collapsed' : ''}`;
            panel.id = 'autoCoursePanel';

            panel.innerHTML = `
                <div class="panel-content">
                    <button class="control-btn play-btn">
                        ${state.isPlaying ? '⏸ 暂停' : '▶ 播放'}
                    </button>

                    <div class="speed-slider-container">
                        <span>倍速:</span>
                        <input type="range" class="slider speed-slider" min="0.5" max="16" step="0.1" value="${state.currentSpeed}">
                        <span class="speed-value">${state.currentSpeed.toFixed(1)}×</span>
                    </div>

                    <div style="display: flex; align-items: center; gap: 12px;">
                        <span>音量:</span>
                        <input type="range" class="volume-slider" min="0" max="1" step="0.1" value="${state.volumeLevel}">
                    </div>

                    <div class="function-toggle">
                        <span>自动跳转:</span>
                        <div class="toggle-switch">
                            <input type="checkbox" id="autoJumpToggle" ${state.isAutoJumpEnabled ? 'checked' : ''}>
                            <label class="slider-switch" for="autoJumpToggle"></label>
                        </div>
                    </div>

                    <!-- 群聊信息提示(可点击跳转) -->
                    <div style="padding: 10px; background: #f5f5f5; border-radius: 8px; font-size: 0.9em;">
                        <p style="margin: 0 0 5px 0; font-weight: bold;">获取帮助与更新</p>
                        <p style="margin: 0; color: #666;">
                            QQ群: <a href="${QQ_GROUP_INFO.url}" target="_blank" class="group-link">${QQ_GROUP_INFO.number}</a>
                        </p>
                    </div>

                    <div class="log-container">
                        <span>操作日志:</span>
                        <div id="logDisplay"></div>
                    </div>

                    <button class="control-btn collapse-btn">
                        ${state.isCollapsed ? '展开' : '折叠'}
                        <svg viewBox="0 0 24 24" style="width: 16px; height: 16px;">
                            <path d="${state.isCollapsed ? 'M12 8l-6 6 6 6' : 'M19 9l-6 6-6-6'}"/>
                        </svg>
                    </button>
                </div>
            `;

            // 事件绑定
            panel.addEventListener('click', function(e) {
                const btn = e.target.closest('.control-btn');
                if (btn) {
                    if (btn.classList.contains('play-btn')) {
                        state.userInteracted = true;
                        togglePlay();
                    } else if (btn.classList.contains('collapse-btn')) {
                        togglePanel();
                    }
                } else if (state.isCollapsed) {
                    togglePanel();
                }
            });

            // 功能开关事件
            const toggleSwitch = panel.querySelector('#autoJumpToggle');
            if (toggleSwitch) {
                toggleSwitch.addEventListener('change', function() {
                    state.isAutoJumpEnabled = this.checked;
                    GM_setValue('isAutoJumpEnabled', state.isAutoJumpEnabled);
                    logMessage(`自动跳转功能已${this.checked ? '启用' : '禁用'}`);
                });
            }

            // 倍速滑块
            const speedSlider = panel.querySelector('.speed-slider');
            speedSlider.addEventListener('input', function() {
                const speedValue = parseFloat(this.value);
                setSpeed(speedValue);
                panel.querySelector('.speed-value').textContent = `${speedValue.toFixed(1)}×`;
                logMessage(`设置倍速为 ${speedValue.toFixed(1)}×`);
            });

            // 音量滑块
            const volumeSlider = panel.querySelector('.volume-slider');
            volumeSlider.addEventListener('input', function() {
                const volumeValue = parseFloat(this.value);
                setVolume(volumeValue);
                logMessage(`设置音量为 ${(volumeValue * 100).toFixed(0)}%`);
            });

            // 拖拽事件
            if (!state.isCollapsed) {
                panel.addEventListener('mousedown', startDrag);
                document.addEventListener('mousemove', handleDrag);
                document.addEventListener('mouseup', stopDrag);
            }

            return panel;
        } catch (error) {
            logError('创建控制面板时出错', error);
            return null;
        }
    }

    // 操作日志
    function logMessage(message) {
        state.logMessages.push(`[${new Date().toLocaleTimeString()}] ${message}`);
        if (state.logMessages.length > 20) state.logMessages.shift();

        const logDisplay = state.panel?.querySelector('#logDisplay');
        if (logDisplay) {
            logDisplay.innerHTML = state.logMessages.map(msg => `<div class="log-message">${msg}</div>`).join('');
        }
    }

    // 播放控制
    function togglePlay() {
        try {
            const video = document.querySelector('.jw-video.jw-reset');
            if (video) {
                state.isPlaying = !state.isPlaying;
                if (state.isPlaying) {
                    video.play()
                       .then(() => logMessage('视频开始播放'))
                       .catch(error => {
                            logMessage(`播放失败: ${error.message}`);
                            video.muted = true;
                            video.play()
                               .then(() => logMessage('已静音播放'))
                               .catch(err => logMessage(`静音播放仍失败: ${err.message}`));
                        });
                } else {
                    video.pause();
                    logMessage('视频已暂停');
                }
                // 更新播放/暂停按钮文本
                const playBtn = state.panel.querySelector('.play-btn');
                if (playBtn) {
                    playBtn.innerHTML = state.isPlaying ? '⏸ 暂停' : '▶ 播放';
                }
            } else {
                logMessage('未找到视频元素');
            }
        } catch (error) {
            logError('播放控制出错', error);
        }
    }

    // 面板折叠
    function togglePanel() {
        try {
            state.isCollapsed = !state.isCollapsed;
            GM_setValue('panelCollapsed', state.isCollapsed);
            state.panel.classList.toggle('collapsed', state.isCollapsed);
            state.panel.style.width = state.isCollapsed ? '60px' : '320px';
            state.panel.style.maxHeight = state.isCollapsed ? '60px' : '500px';
            autoDockPanel();
            logMessage(`面板状态变更为 ${state.isCollapsed ? '折叠' : '展开'}`);

            const collapseBtn = state.panel.querySelector('.collapse-btn');
            if (collapseBtn) {
                if (state.isCollapsed) {
                    collapseBtn.innerHTML = `展开<svg viewBox="0 0 24 24" style="width: 16px; height: 16px;"><path d="M12 8l-6 6 6 6"/></svg>`;
                    state.panel.removeEventListener('mousedown', startDrag);
                    document.removeEventListener('mousemove', handleDrag);
                    document.removeEventListener('mouseup', stopDrag);
                } else {
                    collapseBtn.innerHTML = `折叠<svg viewBox="0 0 24 24" style="width: 16px; height: 16px;"><path d="M19 9l-6 6-6-6"/></svg>`;
                    state.panel.addEventListener('mousedown', startDrag);
                    document.addEventListener('mousemove', handleDrag);
                    document.addEventListener('mouseup', stopDrag);
                }
            }
        } catch (error) {
            logError('面板折叠操作出错', error);
        }
    }

    // 自动靠边
    function autoDockPanel() {
        try {
            const savedLeft = GM_getValue('panelPosition', 20);
            const savedTop = GM_getValue('panelTop', '50%');
            state.panel.style.left = state.isCollapsed ?
                `${window.innerWidth - 60}px` :
                `${Math.max(20, Math.min(savedLeft, window.innerWidth - 340))}px`;
            state.panel.style.top = state.isCollapsed ? '50%' : savedTop;
        } catch (error) {
            logError('自动靠边时出错', error);
        }
    }

    // 设置播放速度
    function setSpeed(speed) {
        try {
            speed = Math.min(Math.max(speed, 0.5), 16);
            state.currentSpeed = speed;
            GM_setValue('playbackSpeed', speed);

            const video = document.querySelector('.jw-video.jw-reset');
            if (video) {
                video.playbackRate = speed;
            }
        } catch (error) {
            logError('设置播放速度时出错', error);
        }
    }

    // 设置音量
    function setVolume(volume) {
        try {
            volume = Math.min(Math.max(volume, 0), 1);
            state.volumeLevel = volume;
            GM_setValue('volumeLevel', volume);

            const video = document.querySelector('.jw-video.jw-reset');
            if (video) {
                video.volume = volume;
            }
        } catch (error) {
            logError('设置音量时出错', error);
        }
    }

    // 自动跳转
    function handleVideoEnd() {
        try {
            if (!state.isAutoJumpEnabled) {
                logMessage('自动跳转已禁用,跳过跳转');
                return;
            }

            logMessage('检测到视频播放结束');
            const nextLink = findValidNextLink();

            if (nextLink) {
                logMessage(`找到链接:${nextLink.textContent}`);
                logMessage('10秒后执行跳转');
                setTimeout(() => {
                    triggerNavigation(nextLink.href);
                    logMessage('跳转完成');
                }, 10000);
            } else {
                logMessage('未找到有效链接,5秒后重试');
                setTimeout(() => handleVideoEnd(), 5000);
            }
        } catch (error) {
            logError('处理视频结束事件时出错', error);
        }
    }

    // 查找有效链接
    function findValidNextLink() {
        try {
            return Array.from(document.querySelectorAll('a.btn.btn-green'))
               .find(a => {
                    const text = a.textContent.trim();
                    return text === '进入下一单元' &&
                        a.href &&
                        a.href !== location.href &&
                        !a.href.includes('javascript');
                });
        } catch (error) {
            logError('查找下一课链接时出错', error);
            return null;
        }
    }

    // 触发导航
    function triggerNavigation(url) {
        try {
            if (!url) return;

            logMessage(`正在跳转到: ${url}`);
            window.location.href = url;
        } catch (error) {
            logError('导航跳转时出错', error);
        }
    }

    // 初始化视频监听
    function initVideoListener() {
        try {
            const video = document.querySelector('.jw-video.jw-reset');
            if (video) {
                video.addEventListener('ended', handleVideoEnd);
                video.playbackRate = state.currentSpeed;
                video.volume = state.volumeLevel;

                // 尝试自动播放
                if (state.userInteracted && video.paused) {
                    video.play()
                       .catch(error => {
                            logMessage(`自动播放失败: ${error.message}`);
                            logMessage('请点击播放按钮开始播放');
                        });
                }
            } else {
                logMessage('未找到视频元素,将继续监控');
            }
        } catch (error) {
            logError('初始化视频监听时出错', error);
        }
    }

    // 拖拽处理
    function startDrag(e) {
        try {
            // 检查是否点击了滑块或开关,避免干扰
            const isSlider = e.target.classList.contains('speed-slider') ||
                           e.target.classList.contains('volume-slider') ||
                           e.target.classList.contains('slider') ||
                           e.target.classList.contains('slider-switch');

            if (isSlider) return;

            state.isDragging = true;
            state.dragStartX = e.clientX - parseFloat(state.panel.style.left);
            state.dragStartY = e.clientY - (parseFloat(state.panel.style.top) || 50);
            state.panel.style.cursor = 'grabbing';
        } catch (error) {
            logError('开始拖拽时出错', error);
        }
    }

    function handleDrag(e) {
        try {
            if (!state.isDragging || state.isCollapsed) return;

            const newLeft = e.clientX - state.dragStartX;
            const newTop = e.clientY - state.dragStartY;

            state.panel.style.left = `${Math.max(20, Math.min(newLeft, window.innerWidth - 340))}px`;
            state.panel.style.top = `${Math.max(20, Math.min(newTop, window.innerHeight - 520))}px`;
        } catch (error) {
            logError('拖拽过程中出错', error);
        }
    }

    function stopDrag() {
        try {
            if (!state.isDragging) return;

            state.isDragging = false;
            state.panel.style.cursor = 'grab';
            GM_setValue('panelPosition', parseFloat(state.panel.style.left));
            GM_setValue('panelTop', parseFloat(state.panel.style.top));
        } catch (error) {
            logError('结束拖拽时出错', error);
        }
    }

    // 主初始化函数
    function init() {
        try {
            console.log('脚本开始初始化 v7.6');

            logMessage('脚本启动成功 v7.6');
            logMessage(`自动跳转状态: ${state.isAutoJumpEnabled ? '启用' : '禁用'}`);

            // 显示QQ群提示
            showGroupPrompt();

            // 尝试修复混合内容
            fixMixedContent();

            // 创建控制面板
            if (!document.querySelector('#autoCoursePanel')) {
                state.panel = createControlPanel();
                if (state.panel) {
                    document.body.appendChild(state.panel);
                    autoDockPanel();
                    logMessage('控制面板已创建');
                } else {
                    logMessage('控制面板创建失败');
                    // 创建一个简易备用面板
                    const backupPanel = document.createElement('div');
                    backupPanel.style.position = 'fixed';
                    backupPanel.style.left = '20px';
                    backupPanel.style.top = '20px';
                    backupPanel.style.zIndex = '99999';
                    backupPanel.style.backgroundColor = 'red';
                    backupPanel.style.color = 'white';
                    backupPanel.style.padding = '10px';
                    backupPanel.textContent = '刷课脚本已运行,点击打开完整面板';
                    backupPanel.addEventListener('click', () => {
                        document.body.removeChild(backupPanel);
                        state.panel = createControlPanel();
                        document.body.appendChild(state.panel);
                        autoDockPanel();
                    });
                    document.body.appendChild(backupPanel);
                }
            }

            // 初始化视频监听
            initVideoListener();
            logMessage('视频监控已启动');

            // 持续监控视频元素
            new MutationObserver(() => {
                if (document.querySelector('.jw-video.jw-reset')) {
                    initVideoListener();
                }
            }).observe(document.body, {
                childList: true,
                subtree: true
            });

            // 每10秒检查播放状态
            setInterval(() => {
                const video = document.querySelector('.jw-video.jw-reset');
                if (video && video.paused && video.currentTime > 0) {
                    logMessage('检测到视频暂停,尝试恢复播放');
                    if (state.userInteracted) {
                        video.play()
                           .catch(err => logMessage(`恢复播放失败: ${err.message}`));
                    }
                }
            }, 10000);
        } catch (error) {
            logError('初始化脚本时出错', error);
            // 显示错误提示
            const errorDiv = document.createElement('div');
            errorDiv.style.position = 'fixed';
            errorDiv.style.left = '50%';
            errorDiv.style.top = '50%';
            errorDiv.style.transform = 'translate(-50%, -50%)';
            errorDiv.style.zIndex = '99999';
            errorDiv.style.backgroundColor = 'red';
            errorDiv.style.color = 'white';
            errorDiv.style.padding = '20px';
            errorDiv.style.borderRadius = '5px';
            errorDiv.innerHTML = `脚本初始化失败: ${error.message}<br>请刷新页面重试`;
            document.body.appendChild(errorDiv);
        }
    }

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