U校园自动挂机脚本

U校园挂机脚本。自动处理"长时间未操作"弹窗,保持学习时长有效计算。

// ==UserScript==
// @name         U校园自动挂机脚本 
// @namespace    https://github.com/Yishun3762/UnipusAutoScript
// @version      2.0.0
// @description  U校园挂机脚本。自动处理"长时间未操作"弹窗,保持学习时长有效计算。
// @author       AsagiYuumoya
// @match        *://*.unipus.cn/*
// @match        *://u.unipus.cn/*
// @match        *://ucontent.unipus.cn/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 ---
    const CONFIG = {
        APP_NAME: 'U校园自动挂机',
        INACTIVITY_PROMPT_TEXT: '由于你长时间未操作,请点确定继续使用。',
        POPUP_CONFIRM_TEXT: '确定',
        DEFAULT_DURATION_MIN: 600,
    };

    // --- 日志 ---
    class Logger {
        constructor(prefix) {
            this.prefix = `[${prefix}]`;
        }
        log(...args) {
            console.log(this.prefix, ...args);
        }
        warn(...args) {
            console.warn(this.prefix, ...args);
        }
        error(...args) {
            console.error(this.prefix, ...args);
        }
    }

    // --- 挂机处理器 ---
    class InactivityHandler {
        constructor(logger) {
            this.logger = logger;
            this.observer = null;
            this.timer = null;
        }

        start() {
            const check = this._checkAndClick.bind(this);
            
            // 立即检查一次
            check();

            // 使用 MutationObserver 监视弹窗出现
            this.observer = new MutationObserver(check);
            this.observer.observe(document.body, {
                childList: true,
                subtree: true,
            });

            // 同时使用 setInterval 作为备用方案
            this.timer = setInterval(check, 2000); // 2秒检查一次

            this.logger.log('长时间未操作检测已启动。');
        }

        stop() {
            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }
            if (this.timer) {
                clearInterval(this.timer);
                this.timer = null;
            }
            this.logger.log('长时间未操作检测已停止。');
        }

        _checkAndClick() {
            try {
                const paragraphs = Array.from(document.querySelectorAll('p'));
                const targetNotice = paragraphs.find(p => p.textContent.includes(CONFIG.INACTIVITY_PROMPT_TEXT));

                if (!targetNotice) return;
                
                const container = targetNotice.closest('div');
                if (!container) return;

                // 查找按钮
                const button = container.querySelector('button[id^="_mask_notice_id_"]') ||
                               Array.from(container.querySelectorAll('button')).find(
                                   btn => btn.textContent.includes(CONFIG.POPUP_CONFIRM_TEXT)
                               );

                if (button) {
                    button.click();
                    this.logger.log('已自动点击按钮。');
                }
            } catch (e) {
                this.logger.error('处理未操作提示时出错:', e);
            }
        }
    }

    // --- UI面板 ---
    class UIPanel {
        constructor(appName, defaultDuration) {
            this.appName = appName;
            this.defaultDuration = defaultDuration;
            this.callbacks = {};
            this._injectCSS();
            this._createPanel();
            this._addEventListeners();
        }

        on(event, callback) {
            this.callbacks[event] = callback;
        }

        getDuration() {
            const duration = parseInt(this.durationInput.value, 10);
            return isNaN(duration) || duration < 1 ? this.defaultDuration : duration;
        }

        updateState({ isRunning, elapsedTime = 0, statusText }) {
            this.statusText.textContent = statusText;
            this.elapsedTime.textContent = elapsedTime;
            if (isRunning) {
                this.startBtn.textContent = '停止挂机';
                this.startBtn.classList.add('stop');
            } else {
                this.startBtn.textContent = '开始挂机';
                this.startBtn.classList.remove('stop');
            }
        }
        
        _injectCSS() {
            GM_addStyle(`
                #unipus-auto-panel {
                    position: fixed; top: 10px; right: 10px; background: rgba(255, 255, 255, 0.95);
                    border: 1px solid #ccc; border-radius: 8px; padding: 15px; z-index: 9999;
                    font-size: 14px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); transition: all 0.3s ease;
                    cursor: move; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                }
                #unipus-auto-panel.minimized {
                    padding: 5px; width: auto; height: auto;
                }
                #unipus-auto-panel.minimized .controls { display: none; }
                #unipus-auto-panel h3 {
                    margin: 0 0 10px 0; font-size: 16px; user-select: none; display: flex;
                    justify-content: space-between; align-items: center; color: #333;
                }
                #unipus-auto-panel .toggle-btn {
                    cursor: pointer; margin-left: 10px; font-size: 20px; width: 22px; height: 22px;
                    line-height: 22px; text-align: center; color: #888;
                }
                #unipus-auto-panel .controls {
                    display: flex; flex-direction: column; gap: 10px;
                }
                #unipus-auto-panel input[type="number"] {
                    width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ddd;
                    border-radius: 4px;
                }
                #unipus-auto-panel button {
                    padding: 8px 12px; cursor: pointer; background: #28a745; color: white;
                    border: none; border-radius: 4px; font-size: 14px; transition: background 0.2s ease;
                }
                #unipus-auto-panel button:hover { background: #218838; }
                #unipus-auto-panel button.stop { background: #dc3545; }
                #unipus-auto-panel button.stop:hover { background: #c82333; }
                #unipus-auto-panel .status {
                    margin-top: 8px; font-size: 12px; color: #555; line-height: 1.5;
                }
                #unipus-auto-panel .elapsed { font-weight: bold; color: #28a745; }
            `);
        }

        _createPanel() {
            const panel = document.createElement('div');
            panel.id = 'unipus-auto-panel';
            panel.innerHTML = `
                <h3>
                    <span class="title">${this.appName}</span>
                    <span class="toggle-btn">-</span>
                </h3>
                <div class="controls">
                    <div>
                        <label for="duration">挂机时长(分钟):</label>
                        <input type="number" id="duration" min="1" value="${this.defaultDuration}">
                    </div>
                    <button id="start-btn">开始挂机</button>
                    <div class="status">
                        状态: <span id="status-text">未运行</span><br>
                        已挂机: <span id="elapsed-time" class="elapsed">0</span> 分钟
                    </div>
                </div>
            `;
            document.body.appendChild(panel);
            this.panel = panel;
            this.durationInput = panel.querySelector('#duration');
            this.startBtn = panel.querySelector('#start-btn');
            this.statusText = panel.querySelector('#status-text');
            this.elapsedTime = panel.querySelector('#elapsed-time');
        }

        _addEventListeners() {
            this.startBtn.addEventListener('click', () => {
                const isRunning = this.startBtn.classList.contains('stop');
                if (isRunning) {
                    this.callbacks.stop && this.callbacks.stop();
                } else {
                    this.callbacks.start && this.callbacks.start();
                }
            });

            const toggleBtn = this.panel.querySelector('.toggle-btn');
            toggleBtn.addEventListener('click', () => {
                this.panel.classList.toggle('minimized');
                toggleBtn.textContent = this.panel.classList.contains('minimized') ? '+' : '-';
            });
            
            this._makeDraggable();
        }

        _makeDraggable() {
            let isDragging = false;
            let offsetX, offsetY;

            const header = this.panel.querySelector('h3');
            header.addEventListener('mousedown', (e) => {
                isDragging = true;
                offsetX = e.clientX - this.panel.getBoundingClientRect().left;
                offsetY = e.clientY - this.panel.getBoundingClientRect().top;
                this.panel.style.userSelect = 'none';
            });

            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    this.panel.style.left = `${e.clientX - offsetX}px`;
                    this.panel.style.top = `${e.clientY - offsetY}px`;
                    this.panel.style.right = 'auto';
                }
            });

            document.addEventListener('mouseup', () => {
                isDragging = false;
                this.panel.style.userSelect = 'auto';
            });
        }
    }

    // --- 主控制器 ---
    class MainController {
        constructor() {
            this.logger = new Logger(CONFIG.APP_NAME);
            this.inactivityHandler = new InactivityHandler(this.logger);
            this.uiPanel = new UIPanel(CONFIG.APP_NAME, CONFIG.DEFAULT_DURATION_MIN);
            
            this.isRunning = false;
            this.startTime = null;
            this.elapsedTimer = null;
            this.targetDuration = CONFIG.DEFAULT_DURATION_MIN;
        }

        init() {
            this.uiPanel.on('start', this.start.bind(this));
            this.uiPanel.on('stop', this.stop.bind(this));

            window.addEventListener('beforeunload', (e) => {
                if (this.isRunning) {
                    e.preventDefault();
                    e.returnValue = '';
                }
            });

            this.logger.log('脚本已加载并初始化。');
        }

        start() {
            if (this.isRunning) return;
            this.isRunning = true;
            
            this.targetDuration = this.uiPanel.getDuration();
            this.startTime = new Date();

            this.inactivityHandler.start();
            
            // 每分钟更新一次UI时间
            this.elapsedTimer = setInterval(this._updateElapsedTime.bind(this), 60000);
            this._updateElapsedTime(); // 立即更新一次

            this.uiPanel.updateState({
                isRunning: true,
                statusText: '运行中'
            });
            
            this.logger.log(`挂机已启动, 目标时长: ${this.targetDuration}分钟`);
        }

        stop(reason = '用户手动停止') {
            if (!this.isRunning) return;
            this.isRunning = false;

            this.inactivityHandler.stop();

            clearInterval(this.elapsedTimer);
            this.elapsedTimer = null;
            this.startTime = null;

            this.uiPanel.updateState({
                isRunning: false,
                statusText: '未运行',
                elapsedTime: 0
            });
            
            this.logger.log(`挂机已停止。原因: ${reason}`);
        }

        _updateElapsedTime() {
            if (!this.startTime) return;
            
            const now = new Date();
            const elapsedMinutes = Math.floor((now - this.startTime) / 60000);
            
            this.uiPanel.updateState({
                isRunning: true,
                statusText: '运行中',
                elapsedTime: elapsedMinutes
            });

            if (elapsedMinutes >= this.targetDuration) {
                this.stop(`达到目标时长 ${this.targetDuration} 分钟`);
                alert(`已完成设定的挂机时长: ${this.targetDuration}分钟!`);
            }
        }
    }

    // --- 启动脚本 ---
    function bootstrap() {
        try {
            new MainController().init();
        } catch (e) {
            console.error(`[${CONFIG.APP_NAME}] 启动失败:`, e);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bootstrap);
    } else {
        bootstrap();
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址