Greasy Fork

Greasy Fork is available in English.

网页保活助手(无操作后自动刷新)

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

当前为 2025-02-17 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         网页保活助手(无操作后自动刷新)
// @namespace    http://tampermonkey.net/
// @version      5.2.1
// @description  【多站点独立配置】智能状态同步 + 跨Tab保活控制(每个网站独立设置)+自动刷新+AJAX心跳
// @author       DavidZhang53
// @match        *://*/*
// @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(10, parseInt(target.value) || 300);
                GM_setValue(getSiteKey('keepAliveInterval'), CONFIG.keepAliveInterval);
                resetKeepAlive();
                break;
            case 'inactivityLimit':
                CONFIG.inactivityLimit = Math.max(60, 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 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();
})();