四川大学自动抢课简单脚本

川大自动抢课脚本 - Web Worker优化版

// ==UserScript==
// @name         四川大学自动抢课简单脚本
// @namespace    http://tampermonkey.net/
// @version      1.02
// @description  川大自动抢课脚本 - Web Worker优化版
// @author       Cloud Hypnos
// @license      GPL-3.0
// @match        http://zhjw.scu.edu.cn/student/courseSelect/courseSelect/*
// @match        https://zhjw.scu.edu.cn/student/courseSelect/courseSelect/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';
    // 切换这里的值来改变配色: 'PINK' 或 'GREEN'
    const CURRENT_THEME = 'GREEN';

    const THEMES = {
        PINK: {
            primary: '#ff6f91',
            primaryLight: '#ff8fa3',
            primaryDark: '#e6537c',
            success: '#4caf50',
            error: '#f44336',
            warning: '#ff9800',
            info: '#2196f3',
            gray: '#6c757d'
        },
        GREEN: {
            primary: '#00c9a7',
            primaryLight: '#00d9b8',
            primaryDark: '#008f7a',
            success: '#4caf50',
            error: '#f44336',
            warning: '#ff9800',
            info: '#2196f3',
            gray: '#6c757d'
        }
    };

    const THEME_COLORS = THEMES[CURRENT_THEME];

    let isRunning = false;
    let isDragging = false;
    let dragOffset = { x: 0, y: 0 };
    let queryCount = 0;
    let worker = null;
    let startTime = null;
    let timeInterval = null;

    // 创建Web Worker
    function createWorker() {
        const workerCode = `
            let interval = null;

            self.addEventListener('message', function(e) {
                if (e.data.command === 'start') {
                    if (interval) clearInterval(interval);

                    interval = setInterval(() => {
                        self.postMessage({ type: 'tick' });
                    }, e.data.interval || 600);

                } else if (e.data.command === 'stop') {
                    if (interval) {
                        clearInterval(interval);
                        interval = null;
                    }
                }
            });
        `;

        const blob = new Blob([workerCode], { type: 'application/javascript' });
        const workerUrl = URL.createObjectURL(blob);
        return new Worker(workerUrl);
    }

    // 创建控制面板
    function createButton() {
        const panel = document.createElement('div');
        panel.id = 'autoGrabPanel';
        panel.innerHTML = `
            <div style="display: flex; border-radius: 5px; overflow: hidden; box-shadow: 0 3px 12px rgba(0,0,0,0.2);">
                <!-- 左侧拖动区域 -->
                <div id="dragArea" style="
                    width: 20px;
                    background: linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%);
                    cursor: move;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    padding: 6px 2px;
                    user-select: none;
                    transition: all 0.2s ease;
                " title="拖动">
                    <svg width="10" height="10" viewBox="0 0 24 24" fill="white" opacity="0.8">
                        <path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>
                    </svg>
                </div>

                <!-- 右侧功能区域 -->
                <div style="
                    background: white;
                    padding: 6px 8px;
                    min-width: 120px;
                    border: 1px solid #e0e0e0;
                    border-left: none;
                ">
                    <!-- 查询计数 -->
                    <div id="queryCounter" style="
                        margin-bottom: 5px;
                        font-size: 10px;
                        color: #666;
                        display: flex;
                        align-items: center;
                        justify-content: space-between;
                    ">
                        <span>查询: <span id="queryCount" style="font-weight: 700; color: ${THEME_COLORS.primary};">0</span></span>
                        <span id="timeElapsed" style="display: none;">运行: <span id="runTime">0</span>s</span>
                    </div>

                    <!-- 主按钮 -->
                    <button id="actionBtn" style="
                        width: 100%;
                        padding: 6px 8px;
                        background: linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%);
                        color: white;
                        border: none;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 12px;
                        font-weight: 700;
                        transition: all 0.2s ease;
                        box-shadow: 0 2px 6px rgba(0,0,0,0.15);
                        line-height: 1;
                    ">
                        开始抢课
                    </button>
                </div>
            </div>
        `;

        panel.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        `;

        document.body.appendChild(panel);
        setupDragEvents(panel);
        setupButtonEvents();
    }

    // 设置拖动事件
    function setupDragEvents(panel) {
        const dragArea = document.getElementById('dragArea');

        dragArea.onmouseenter = function() {
            if (!isDragging) {
                dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primaryLight} 0%, ${THEME_COLORS.primary} 100%)`;
            }
        };

        dragArea.onmouseleave = function() {
            if (!isDragging) {
                dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`;
            }
        };

        dragArea.onmousedown = function(e) {
            isDragging = true;
            const rect = panel.getBoundingClientRect();
            dragOffset.x = e.clientX - rect.left;
            dragOffset.y = e.clientY - rect.top;

            dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primaryDark} 0%, ${THEME_COLORS.primary} 100%)`;
            dragArea.style.transform = 'scale(0.95)';
            document.body.style.cursor = 'move';

            const overlay = document.createElement('div');
            overlay.id = 'dragOverlay';
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                z-index: 9998;
                cursor: move;
            `;
            document.body.appendChild(overlay);

            e.preventDefault();
        };

        document.onmousemove = function(e) {
            if (isDragging) {
                panel.style.left = (e.clientX - dragOffset.x) + 'px';
                panel.style.top = (e.clientY - dragOffset.y) + 'px';
                panel.style.right = 'auto';
            }
        };

        document.onmouseup = function() {
            if (isDragging) {
                isDragging = false;
                dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`;
                dragArea.style.transform = 'scale(1)';
                document.body.style.cursor = 'auto';

                const overlay = document.getElementById('dragOverlay');
                if (overlay) overlay.remove();
            }
        };

        document.oncontextmenu = function(e) {
            if (isDragging) {
                isDragging = false;
                dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`;
                dragArea.style.transform = 'scale(1)';
                document.body.style.cursor = 'auto';

                const overlay = document.getElementById('dragOverlay');
                if (overlay) overlay.remove();

                e.preventDefault();
                return false;
            }
        };
    }

    // 设置按钮事件
    function setupButtonEvents() {
        const actionBtn = document.getElementById('actionBtn');

        actionBtn.onclick = function(e) {
            e.stopPropagation();
            toggleAutoGrab();
        };

        actionBtn.onmouseenter = function() {
            if (!isRunning) {
                this.style.transform = 'translateY(-1px)';
                this.style.boxShadow = '0 3px 8px rgba(0,0,0,0.15)';
            }
        };

        actionBtn.onmouseleave = function() {
            this.style.transform = 'translateY(0)';
            this.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
        };
    }

    // 更新按钮
    function updateButton(text, color) {
        const actionBtn = document.getElementById('actionBtn');
        if (actionBtn) {
            actionBtn.textContent = text;
            actionBtn.style.background = color.includes('gradient') ? color :
                `linear-gradient(135deg, ${color} 0%, ${color} 100%)`;
        }
    }

    // 更新查询计数和运行时间
    function updateQueryCount() {
        queryCount++;
        const countElement = document.getElementById('queryCount');
        if (countElement) {
            countElement.textContent = queryCount;
        }
    }

    function startTimer() {
        startTime = Date.now();
        document.getElementById('timeElapsed').style.display = 'block';

        timeInterval = setInterval(() => {
            if (startTime) {
                const elapsed = Math.floor((Date.now() - startTime) / 1000);
                document.getElementById('runTime').textContent = elapsed;
            }
        }, 1000);
    }

    function stopTimer() {
        startTime = null;
        if (timeInterval) {
            clearInterval(timeInterval);
            timeInterval = null;
        }
        document.getElementById('timeElapsed').style.display = 'none';
    }

    function resetQueryCount() {
        queryCount = 0;
        const countElement = document.getElementById('queryCount');
        if (countElement) {
            countElement.textContent = queryCount;
        }
    }

    // 查询课程并获取结果
    function searchCourses() {
        return new Promise((resolve, reject) => {
            try {
                const iframe = document.querySelector("iframe");
                if (!iframe || !iframe.contentWindow) {
                    throw new Error('找不到iframe');
                }

                const iframeWin = iframe.contentWindow;
                if (typeof iframeWin.guolv !== 'function') {
                    throw new Error('找不到查询函数');
                }

                const original$ = iframeWin.$;
                const originalAjax = original$.ajax;

                original$.ajax = function(options) {
                    if (options.url && options.url.includes('/student/courseSelect/freeCourse/courseList')) {
                        const originalSuccess = options.success;

                        options.success = function(data) {
                            original$.ajax = originalAjax;
                            resolve(data);

                            if (originalSuccess) {
                                originalSuccess.call(this, data);
                            }
                        };

                        const originalError = options.error;
                        options.error = function(xhr, status, error) {
                            original$.ajax = originalAjax;
                            reject(new Error(`查询失败: ${error}`));

                            if (originalError) {
                                originalError.call(this, xhr, status, error);
                            }
                        };
                    }

                    return originalAjax.call(this, options);
                };

                iframeWin.guolv(1);

                setTimeout(() => {
                    original$.ajax = originalAjax;
                    reject(new Error('查询超时'));
                }, 8000);

            } catch (error) {
                reject(error);
            }
        });
    }

    // 选中课程
    function selectCourse(course) {
        try {
            const iframe = document.querySelector("iframe");
            const iframeWin = iframe.contentWindow;

            if (typeof iframeWin.dealHiddenData !== 'function') {
                throw new Error('找不到选课函数');
            }

            iframeWin.dealHiddenData(course, true);
            console.log(`选中课程: ${course.kcm} - ${course.skjs} (余量:${course.bkskyl})`);
            return true;

        } catch (error) {
            console.error('选中课程失败:', error);
            return false;
        }
    }

    // 提交选课
    function submitCourse() {
        return new Promise((resolve, reject) => {
            const yzmArea = $("#yzm_area");
            if (yzmArea.length > 0 && yzmArea.css("display") !== "none" && !$("#submitCode").val()) {
                reject(new Error('请先输入验证码'));
                return;
            }

            const originalAjax = $.ajax;
            $.ajax = function(options) {
                if (options.url && options.url.includes('checkInputCodeAndSubmit')) {
                    const originalSuccess = options.success;
                    const originalError = options.error;

                    options.success = function(data) {
                        $.ajax = originalAjax;
                        if (originalSuccess) originalSuccess.call(this, data);

                        if (data.result === 'ok') {
                            resolve('选课成功');
                        } else {
                            reject(new Error(data.result));
                        }
                    };

                    options.error = function(xhr) {
                        $.ajax = originalAjax;
                        if (originalError) originalError.call(this, xhr);
                        reject(new Error(`提交失败: ${xhr.status}`));
                    };
                }
                return originalAjax.call(this, options);
            };

            // 直接调用,不做判断
            window.tijiao();
        });
    }

    // 主抢课逻辑
    async function performGrab() {
        try {
            updateButton('查询中...', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`);
            updateQueryCount();

            const data = await searchCourses();

            if (!data || !data.rwRxkZlList || data.rwRxkZlList.length === 0) {
                updateButton('未找到课程', `linear-gradient(135deg, ${THEME_COLORS.gray} 0%, #495057 100%)`);
                console.log('未找到课程');
                return false;
            }

            const availableCourse = data.rwRxkZlList.find(course => course.bkskyl > 0);

            if (!availableCourse) {
                updateButton('暂无余量', `linear-gradient(135deg, ${THEME_COLORS.gray} 0%, #495057 100%)`);
                console.log('暂无余量');
                return false;
            }

            // 发现有余量的课程,立即停止Worker
            if (worker) {
                worker.postMessage({ command: 'stop' });
            }

            updateButton('发现目标课程', `linear-gradient(135deg, ${THEME_COLORS.success} 0%, #388e3c 100%)`);
            console.log(`发现有余量课程: ${availableCourse.kcm}`);

            if (!selectCourse(availableCourse)) {
                updateButton('选中失败', `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`);
                console.error('选中课程失败');
                stopAutoGrab();
                return false;
            }

            await new Promise(resolve => setTimeout(resolve, 500));
            updateButton('提交中...', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`);

            const result = await submitCourse();

            updateButton('✅ 选课成功', `linear-gradient(135deg, ${THEME_COLORS.success} 0%, #388e3c 100%)`);
            console.log(`✅ 选课成功: ${availableCourse.kcm} - ${availableCourse.skjs}`);

            stopAutoGrab();
            return true;

        } catch (error) {
            if (error.message.includes('验证码')) {
                updateButton('需要验证码', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`);
                console.log('需要输入验证码');
                stopAutoGrab();
                alert('请输入验证码后重新开始');
                return false;
            } else {
                updateButton('出现错误', `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`);
                console.error('抢课出错:', error.message);
                stopAutoGrab();
                return false;
            }
        }
    }

    // 开始/停止抢课
    function toggleAutoGrab() {
        if (isRunning) {
            stopAutoGrab();
        } else {
            startAutoGrab();
        }
    }

    // 开始自动抢课
    function startAutoGrab() {
        isRunning = true;
        resetQueryCount();
        startTimer();
        console.log('🚀 开始自动抢课');
        updateButton('停止抢课', `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`);

        // 初始化Web Worker
        if (!worker) {
            worker = createWorker();

            worker.addEventListener('message', async function(e) {
                if (e.data.type === 'tick' && isRunning) {
                    const success = await performGrab();
                    if (success) {
                        // 抢课成功,Worker已在performGrab中停止
                        console.log('抢课成功,已停止Worker');
                    }
                }
            });
        }

        // 立即执行一次
        performGrab();

        // 启动Worker定时器
        const interval = Math.floor(Math.random() * 100 + 400);
        worker.postMessage({ command: 'start', interval: interval });
    }

    // 停止自动抢课
    function stopAutoGrab() {
        isRunning = false;
        stopTimer();
        updateButton('开始抢课', `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`);

        // 停止Worker
        if (worker) {
            worker.postMessage({ command: 'stop' });
        }

        console.log('已停止抢课');
    }

    // 初始化
    function init() {
        const checkReady = setInterval(() => {
            const iframe = document.querySelector("iframe");
            if (iframe && iframe.contentWindow && document.querySelector('#myTab4')) {
                clearInterval(checkReady);
                setTimeout(() => {
                    createButton();
                    console.log('抢课脚本已就绪');
                }, 1000);
            }
        }, 1000);
    }

    // 启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 页面卸载时清理Worker
    window.addEventListener('beforeunload', () => {
        if (worker) {
            worker.postMessage({ command: 'stop' });
            worker.terminate();
        }
    });

})();