Greasy Fork

Greasy Fork is available in English.

网页保活助手

【多站点独立配置】智能状态同步 + 跨Tab保活控制(每个网站独立设置)+AJAX心跳

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         网页保活助手
// @namespace    http://tampermonkey.net/
// @version      5.2.2
// @description  【多站点独立配置】智能状态同步 + 跨Tab保活控制(每个网站独立设置)+AJAX心跳
// @match        *://ecp.sgcc.com.cn/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 生成带网站标识的存储键
    const currentSite = window.location.hostname;
    const getSiteKey = (key) => `${currentSite}_${key}`;

    // 配置存储(每个网站独立)
    const CONFIG = {
        keepAliveEnabled: GM_getValue(getSiteKey('keepAliveEnabled'), true),
        keepAliveInterval: GM_getValue(getSiteKey('keepAliveInterval'), 300),
        inactivityLimit: GM_getValue(getSiteKey('inactivityLimit'), 600),
        forceRefreshEnabled: GM_getValue(getSiteKey('forceRefreshEnabled'), false),
        lastActivity: GM_getValue(getSiteKey('lastActivity'), Date.now())
    };

    // 运行时状态
    let state = {
        refreshTimer: null,
        keepAliveTimer: null,
        pendingRefresh: false
    };

    // 视觉样式
    GM_addStyle(`
        #control-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 50px;
            height: 50px;
            z-index: 1000000;
            cursor: pointer;
        }
        #control-container:hover #control-panel {
            display: block !important;
        }
        #control-panel {
            display: none;
            position: absolute;
            left: 0;
            top: 50px;
            background: rgba(255,255,255,0.98);
            border-radius: 8px;
            padding: 15px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            width: 320px;
            z-index: 1000001;
        }
        #refresh-warning {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: lightyellow;
            color: #000;
            padding: 20px 30px;
            border-radius: 10px;
            font-size: 18px;
            z-index: 999999;
            text-align: center;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
        }
        .status-indicator {
            position: fixed;
            bottom: 10px;
            right: 10px;
            padding: 5px 10px;
            border-radius: 3px;
            font-size: 12px;
            z-index: 999998;
        }
        .active-status { background: #28a745; color: white; }
        .inactive-status { background: #dc3545; color: white; }
    `);

    // 控制面板
    function createControlPanel() {
        const container = document.createElement('div');
        container.id = 'control-container';
        const panel = document.createElement('div');
        panel.id = 'control-panel';
        panel.innerHTML = `
            <h3 style="margin:0 0 15px 0;">保活控制中心 - ${currentSite}</h3>
            <label style="display:block; margin:10px 0;">
                <input type="checkbox" id="keepAliveEnabled" ${CONFIG.keepAliveEnabled ? 'checked' : ''}>
                启用智能保活
            </label>
            <label style="display:block; margin:10px 0;">
                保活间隔(秒):
                <input type="number" id="keepAliveInterval" value="${CONFIG.keepAliveInterval}" style="width:80px;">
            </label>
            <label style="display:block; margin:10px 0;">
                无操作超时(秒):
                <input type="number" id="inactivityLimit" value="${CONFIG.inactivityLimit}" style="width:80px;">
            </label>
            <label style="display:block; margin:10px 0;">
                <input type="checkbox" id="forceRefreshEnabled" ${CONFIG.forceRefreshEnabled ? 'checked' : ''}>
                强制全页刷新
            </label>
            <div style="margin-top:15px; color:#666; font-size:12px;">
                当前状态: <span id="statusText">检测中...</span>
            </div>
        `;
        container.appendChild(panel);
        document.body.appendChild(container);

        const inputs = panel.querySelectorAll('input, select');
        inputs.forEach(input => {
            input.addEventListener('change', handleSettingChange);
        });
        updateStatusDisplay();
    }

    // 设置变更处理
    function handleSettingChange(e) {
        const target = e.target;
        switch(target.id) {
            case 'keepAliveEnabled':
                CONFIG.keepAliveEnabled = target.checked;
                GM_setValue(getSiteKey('keepAliveEnabled'), target.checked);
                if (!target.checked) stopKeepAlive();
                break;
            case 'keepAliveInterval':
                CONFIG.keepAliveInterval = Math.max(0, parseInt(target.value) || 300);
                GM_setValue(getSiteKey('keepAliveInterval'), CONFIG.keepAliveInterval);
                resetKeepAlive();
                break;
            case 'inactivityLimit':
                CONFIG.inactivityLimit = Math.max(0, parseInt(target.value) || 600);
                GM_setValue(getSiteKey('inactivityLimit'), CONFIG.inactivityLimit);
                break;
            case 'forceRefreshEnabled':
                CONFIG.forceRefreshEnabled = target.checked;
                GM_setValue(getSiteKey('forceRefreshEnabled'), target.checked);
                break;
        }
        updateStatusDisplay();
    }
    // 设置菜单
    // 菜单项注册函数
    function registerMenu() {
        GM_registerMenuCommand(`【${CONFIG.keepAliveEnabled ? '✔' : '✘'}】启用智能保活`, () => {
            CONFIG.keepAliveEnabled = !CONFIG.keepAliveEnabled;
            GM_setValue(getSiteKey('keepAliveEnabled'), CONFIG.keepAliveEnabled);
            registerMenu(); // 重新注册菜单,使显示更新
        });

        GM_registerMenuCommand(`保活间隔: ${CONFIG.keepAliveInterval}秒`, () => {
            const input = prompt("请输入保活间隔(秒):", CONFIG.keepAliveInterval);
            if (input !== null) {
                CONFIG.keepAliveInterval = parseInt(input) || 300;
                GM_setValue(getSiteKey('keepAliveInterval'), CONFIG.keepAliveInterval);
                registerMenu();
            }
        });

        GM_registerMenuCommand(`无操作超时: ${CONFIG.inactivityLimit}秒`, () => {
            const input = prompt("请输入无操作超时(秒):", CONFIG.inactivityLimit);
            if (input !== null) {
                CONFIG.inactivityLimit = parseInt(input) || 600;
                GM_setValue(getSiteKey('inactivityLimit'), CONFIG.inactivityLimit);
                registerMenu();
            }
        });

        GM_registerMenuCommand(`【${CONFIG.forceRefreshEnabled ? '✔' : '✘'}】强制全页刷新`, () => {
            CONFIG.forceRefreshEnabled = !CONFIG.forceRefreshEnabled;
            GM_setValue(getSiteKey('forceRefreshEnabled'), CONFIG.forceRefreshEnabled);
            registerMenu();
        });

    }
    // 初始化菜单
    registerMenu();

    // 核心保活逻辑
    function checkActivityState() {
        const now = Date.now();
        const elapsed = now - CONFIG.lastActivity;
        const isInactive = elapsed > CONFIG.inactivityLimit * 1000;

        if ((document.hidden || isInactive) && !state.keepAliveTimer) {
            startKeepAlive();
        } else if (!document.hidden && !isInactive && state.keepAliveTimer) {
            stopKeepAlive();
        }
    }

    function startKeepAlive() {
        if (!CONFIG.keepAliveEnabled) return;
        performKeepAlive();
        state.keepAliveTimer = setInterval(() => {
            performKeepAlive();
        }, CONFIG.keepAliveInterval * 1000);
    }

    function performKeepAlive() {
        GM_xmlhttpRequest({
            method: 'HEAD',
            url: window.location.href,
            onload: (res) => {
                if (CONFIG.forceRefreshEnabled && shouldForceRefresh()) {
                    scheduleForceRefresh();
                }
            },
            onerror: (err) => {
                if (CONFIG.forceRefreshEnabled && shouldForceRefresh()) {
                    scheduleForceRefresh();
                }
            }
        });
    }

    function shouldForceRefresh() {
        return document.hidden || (Date.now() - CONFIG.lastActivity > CONFIG.inactivityLimit * 1000);
    }

    // 刷新控制相关
    function scheduleForceRefresh() {
        if (state.pendingRefresh) return;
        state.pendingRefresh = true;
        showRefreshWarning();
        state.refreshTimer = setTimeout(() => {
            location.reload();
        }, CONFIG.keepAliveInterval * 1000);
        document.addEventListener('visibilitychange', handleVisibilityChange);
    }

    function handleVisibilityChange() {
        if (!document.hidden && state.pendingRefresh) {
            cancelRefresh();
            stopKeepAlive();
        }
    }

    function cancelRefresh() {
        clearTimeout(state.refreshTimer);
        state.refreshTimer = null;
        state.pendingRefresh = false;
        const warning = document.getElementById('refresh-warning');
        warning && warning.remove();
    }

    function showRefreshWarning() {
        const warning = document.createElement('div');
        warning.id = 'refresh-warning';
        warning.innerHTML = `
            <div>系统即将自动刷新</div>
            <div style="font-size:14px; margin-top:8px;">
                倒计时: <span id="refreshCountdown">${CONFIG.keepAliveInterval}</span>秒
            </div>
        `;
        document.body.appendChild(warning);
        startCountdown();
    }

    function startCountdown() {
        const countdownEl = document.getElementById('refreshCountdown');
        let remaining = CONFIG.keepAliveInterval;
        const timer = setInterval(() => {
            remaining--;
            countdownEl.textContent = remaining;
            if (remaining <= 0) clearInterval(timer);
        }, 1000);
    }
    // 辅助功能
    function stopKeepAlive() {
        clearInterval(state.keepAliveTimer);
        state.keepAliveTimer = null;
        cancelRefresh();
    }

    function resetKeepAlive() {
        stopKeepAlive();
        checkActivityState();
    }

    function updateStatusDisplay() {
        const statusText = document.querySelector('#statusText');
        if (!statusText) return;
        statusText.textContent = state.keepAliveTimer
            ? `工作中 (间隔:${CONFIG.keepAliveInterval}秒)`
            : '待机中 (检测无操作状态)';
    }

    // 事件监听
    function initEventListeners() {
        ['mousemove', 'keydown', 'click'].forEach(event => {
            document.addEventListener(event, () => {
                CONFIG.lastActivity = Date.now();
                GM_setValue(getSiteKey('lastActivity'), CONFIG.lastActivity);
                if (!document.hidden) {
                    cancelRefresh();
                    stopKeepAlive();
                }
                checkActivityState();
            });
        });

        document.addEventListener('visibilitychange', () => {
            checkActivityState();
            updateStatusDisplay();
        });

        setInterval(() => {
            checkActivityState();
            updateStatusDisplay();
        }, 1000);
    }

    // 初始化
    function init() {
        if (document.body) {
            createControlPanel();
            initEventListeners();
            checkActivityState();
        } else {
            window.addEventListener('DOMContentLoaded', init);
        }
    }

    init();
})();