Greasy Fork

Greasy Fork is available in English.

网站保活助手

在指定网站上自动保活,定时滚动和刷新页面,用户操作会重置计时

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         网站保活助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在指定网站上自动保活,定时滚动和刷新页面,用户操作会重置计时
// @author       damu
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    const DEFAULT_SCROLL = 5, DEFAULT_REFRESH = 20, DEFAULT_ACTIVITY = 1;
    let scrollTimer, refreshTimer, lastActivityTime = Date.now();
    let globalEnabled = GM_getValue('globalEnabled', true);
    let sites = GM_getValue('keepAliveSites', []);

    // 修复旧数据格式:如果sites是字符串数组,转换为对象数组
    if (sites.length > 0 && typeof sites[0] === 'string') {
        sites = sites.map(url => ({
            url: url,
            enabled: true,
            scroll: DEFAULT_SCROLL,
            refresh: DEFAULT_REFRESH,
            activity: DEFAULT_ACTIVITY
        }));
        GM_setValue('keepAliveSites', sites);
    }

    GM_registerMenuCommand("网站保活管理", openManagerUI);
    GM_registerMenuCommand("添加当前网站", addCurrentSite);

    function addCurrentSite() {
        const currentUrl = window.location.origin + window.location.pathname;
        const sitePattern = currentUrl + (window.location.pathname.endsWith('/') ? '*' : '/*');
        if (!sites.some(s => s.url === sitePattern)) {
            sites.push({url: sitePattern, enabled: true, scroll: DEFAULT_SCROLL, refresh: DEFAULT_REFRESH, activity: DEFAULT_ACTIVITY});
            GM_setValue('keepAliveSites', sites);
            GM_notification({ text: `已添加 ${sitePattern}`, timeout: 2000 });
            if (globalEnabled) initKeepAlive();
        } else {
            GM_notification({ text: '网站已存在', timeout: 2000 });
        }
    }

    function openManagerUI() {
        if (!document.getElementById('keepAliveManager')) createManagerUI();
        document.getElementById('keepAliveOverlay').style.display = 'block';
        document.getElementById('keepAliveManager').style.display = 'block';
    }

    function closeManagerUI() {
        document.getElementById('keepAliveOverlay').style.display = 'none';
        document.getElementById('keepAliveManager').style.display = 'none';
    }

    function addSiteFromUI() {
        const url = document.getElementById('siteUrl').value.trim();
        if (url && !sites.some(s => s.url === url)) {
            sites.push({url: url, enabled: true, scroll: DEFAULT_SCROLL, refresh: DEFAULT_REFRESH, activity: DEFAULT_ACTIVITY});
            renderSiteList();
            document.getElementById('siteUrl').value = '';
        }
    }

    function saveConfig() {
        globalEnabled = document.getElementById('globalToggle').checked;
        GM_setValue('keepAliveSites', sites);
        GM_setValue('globalEnabled', globalEnabled);
        GM_notification({ text: '已保存', timeout: 1500 });
        closeManagerUI();
        clearTimers();
        if (globalEnabled && checkSite()) initKeepAlive();
    }

    function createManagerUI() {
        const overlay = document.createElement('div');
        overlay.id = 'keepAliveOverlay';
        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:none';
        overlay.onclick = closeManagerUI;

        const manager = document.createElement('div');
        manager.id = 'keepAliveManager';
        manager.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:400px;background:white;border-radius:8px;z-index:10000;display:none';

        manager.innerHTML = `
            <div style="background:#3498db;color:white;padding:15px;border-radius:8px 8px 0 0;display:flex;justify-content:space-between;align-items:center">
                <h3 style="margin:0">网站保活管理</h3>
                <button id="closeBtn" style="background:none;border:none;color:white;font-size:20px;cursor:pointer">&times;</button>
            </div>
            <div style="padding:15px">
                <div style="margin-bottom:15px">
                    <label><input type="checkbox" id="globalToggle" ${globalEnabled ? 'checked' : ''}> 全局启用</label>
                </div>
                <div style="margin-bottom:15px">
                    <input type="text" id="siteUrl" placeholder="输入网站URL" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;margin-bottom:10px">
                    <button id="addBtn" style="background:#3498db;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer;width:100%">添加网站</button>
                </div>
                <div id="siteList" style="max-height:300px;overflow-y:auto"></div>
            </div>
            <div style="padding:15px;background:#f5f5f5;border-radius:0 0 8px 8px;text-align:right">
                <button id="saveBtn" style="background:#2ecc71;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer;margin-right:10px">保存</button>
                <button id="cancelBtn" style="background:#e74c3c;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer">取消</button>
            </div>
        `;

        document.body.appendChild(overlay);
        document.body.appendChild(manager);

        document.getElementById('closeBtn').onclick = closeManagerUI;
        document.getElementById('addBtn').onclick = addSiteFromUI;
        document.getElementById('saveBtn').onclick = saveConfig;
        document.getElementById('cancelBtn').onclick = closeManagerUI;

        renderSiteList();
    }

    function renderSiteList() {
        const siteListDiv = document.getElementById('siteList');
        if (!siteListDiv) return;

        siteListDiv.innerHTML = sites.length ? '' : '<div style="text-align:center;color:#666;padding:20px">暂无网站</div>';

        sites.forEach((site, index) => {
            const siteItem = document.createElement('div');
            siteItem.style.cssText = 'padding:10px;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between';

            siteItem.innerHTML = `
                <div style="flex:1">
                    <label style="display:flex;align-items:center;cursor:pointer">
                        <input type="checkbox" ${site.enabled ? 'checked' : ''} style="margin-right:8px">
                        <span style="font-size:12px">${site.url}</span>
                    </label>
                </div>
                <button style="background:#e74c3c;color:white;border:none;padding:4px 8px;border-radius:3px;cursor:pointer;font-size:12px">删除</button>
            `;

            const checkbox = siteItem.querySelector('input[type="checkbox"]');
            checkbox.onchange = function() {
                sites[index].enabled = this.checked;
            };

            const deleteBtn = siteItem.querySelector('button');
            deleteBtn.onclick = function() {
                sites.splice(index, 1);
                renderSiteList();
            };

            siteListDiv.appendChild(siteItem);
        });
    }

    function clearTimers() {
        clearTimeout(scrollTimer);
        clearTimeout(refreshTimer);
    }

    function checkSite() {
        const currentUrl = window.location.href;
        return sites.some(site => site.enabled && new RegExp('^' + site.url.replace(/\*/g, '.*') + '$').test(currentUrl));
    }

    function getCurrentSiteSettings() {
        const currentUrl = window.location.href;
        return sites.find(site => site.enabled && new RegExp('^' + site.url.replace(/\*/g, '.*') + '$').test(currentUrl));
    }

    function simulateScroll() {
        window.scrollBy(0, 200);
        setTimeout(() => window.scrollBy(0, -100), 1000);
    }

    function refreshPage() {
        const settings = getCurrentSiteSettings();
        if (settings && Date.now() - lastActivityTime > settings.activity * 60000) {
            window.location.reload();
        } else {
            resetTimers();
        }
    }

    function resetTimers() {
        const settings = getCurrentSiteSettings();
        if (!settings) return;

        lastActivityTime = Date.now();
        clearTimers();
        scrollTimer = setTimeout(simulateScroll, settings.scroll * 60000);
        refreshTimer = setTimeout(refreshPage, settings.refresh * 60000);
    }

    function initKeepAlive() {
        ['mousemove', 'keypress', 'click', 'scroll'].forEach(event => {
            document.addEventListener(event, () => { lastActivityTime = Date.now(); }, {passive: true});
        });
        resetTimers();
    }

    // 初始化
    if (globalEnabled && checkSite()) {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initKeepAlive);
        } else {
            initKeepAlive();
        }
    }
})();