Greasy Fork

Greasy Fork is available in English.

【网页防挂机】 网页可见性管理

在指定域名作用范围内维持页面为可见/活跃状态。内置配置面板,支持动态维护作用域列表、快捷键操作与持久化存储;未启用域名不执行任何逻辑。

当前为 2026-04-14 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         【网页防挂机】 网页可见性管理
// @namespace    https://github.com/realSilasYang
// @version      2026-04-14
// @description  在指定域名作用范围内维持页面为可见/活跃状态。内置配置面板,支持动态维护作用域列表、快捷键操作与持久化存储;未启用域名不执行任何逻辑。
// @author       阳熙来
// @match        *://*/*
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @icon         data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpY29uIiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIGRhdGEtZGFya3JlYWRlci1pbmxpbmUtZmlsbD0iIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4LnNlYXJjaF9pbmRleC4wLmkxLjYxYjMzYTgxc0dDRFVlIiB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiI+CiAgPHBhdGggZD0iTTUxNS4wNTE1MiA1MDUuNTIzMmMtMjEuNTA0LTEuNTM2LTQwLjQ0OCAxNC4zMzYtNDEuOTg0IDM1Ljg0IDAgMTMuMzEyIDcuNjggMjUuNiAxOS40NTYgMzIuMjU2djQ5LjE1MmMwIDEwLjc1MiA4LjcwNCAxOS40NTYgMTkuNDU2IDE5LjQ1NiAxMC43NTIgMCAxOS40NTYtOC43MDQgMTkuNDU2LTE5LjQ1NnYtNDkuMTUyYzExLjc3Ni02LjY1NiAxOC45NDQtMTguOTQ0IDE5LjQ1Ni0zMi4yNTYtMS41MzYtMTguOTQ0LTE2Ljg5Ni0zNC4zMDQtMzUuODQtMzUuODR6TTYwOS4yNTk1MiAzOTIuMzcxMmMtMi41Ni01MC4xNzYtNDIuNDk2LTkwLjExMi05Mi42NzItOTIuNjcyLTUzLjc2LTIuNTYtOTguODE2IDM4LjkxMi0xMDEuMzc2IDkyLjY3MnY0NS4wNTZoMTk0LjA0OHYtNDUuMDU2eiIgZmlsbD0iI2Y3NmY1MyIgc3R5bGU9Ii0tZGFya3JlYWRlci1pbmxpbmUtZmlsbDogdmFyKC0tZGFya3JlYWRlci1iYWNrZ3JvdW5kLWY3NmY1MywgIzk0MmUxOSk7IiBkYXRhLWRhcmtyZWFkZXItaW5saW5lLWZpbGw9IiIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pMC42MWIzM2E4MXNHQ0RVZSIgY2xhc3M9InNlbGVjdGVkIj48L3BhdGg+CiAgPHBhdGggZD0iTTk2Ni4xMjM1MiAyOTEuNTA3MmMtMS4wMjQtNy4xNjgtNS4xMi0xNC4zMzYtMTAuNzUyLTE4Ljk0NGwtMjczLjkyLTI0Mi4xNzZjLTkuNzI4LTguMTkyLTIyLjAxNi0xMS4yNjQtMzQuMzA0LTguNzA0aC00NTEuMDcyYy03Mi43MDQtNC4wOTYtMTM1LjY4IDUxLjItMTQwLjI4OCAxMjQuNDE2djY4My4wMDhjNC42MDggNzIuNzA0IDY3LjU4NCAxMjguNTEyIDE0MC4yODggMTI0LjQxNmg2MzEuODA4YzcyLjcwNCA0LjA5NiAxMzUuNjgtNTEuMiAxNDAuMjg4LTEyNC40MTZ2LTUyOC4zODRjMC0zLjA3Mi0wLjUxMi02LjY1Ni0yLjA0OC05LjIxNnogbS02NDcuMTY4IDE4OS45NTJjMC41MTItMjUuMDg4IDIwLjk5Mi00NC41NDQgNDYuMDgtNDQuMDMyaDExLjI2NHYtNDMuNTItMi41NmMxLjUzNi03My4yMTYgNjIuNDY0LTEzMS41ODQgMTM1LjY4LTEzMC4wNDhoMi41NmM3My4yMTYgMCAxMzMuMTIgNTkuMzkyIDEzMy4xMiAxMzMuMTJ2NDMuNTJoMTEuMjY0YzI1LjA4OC0wLjUxMiA0NS41NjggMTguOTQ0IDQ2LjA4IDQ0LjAzMnYxODYuODhjLTAuNTEyIDI1LjA4OC0yMC45OTIgNDQuNTQ0LTQ2LjA4IDQ0LjAzMmgtMjk0LjRjLTI1LjA4OCAwLjUxMi00NS41NjgtMTguOTQ0LTQ2LjA4LTQ0LjAzMnYtMTg3LjM5MnogbTM4Ny41ODQgMzQ1LjZoLTM4OS42MzJjLTExLjI2NCAwLTIwLjQ4LTkuMjE2LTIwLjQ4LTIwLjQ4czkuMjE2LTIwLjQ4IDIwLjQ4LTIwLjQ4aDM4OS42MzJjMTEuMjY0IDAgMjAuNDggOS4yMTYgMjAuNDggMjAuNDhzLTguNzA0IDIwLjQ4LTIwLjQ4IDIwLjQ4eiIgZmlsbD0iIzU4NUU2NiIgc3R5bGU9Ii0tZGFya3JlYWRlci1pbmxpbmUtZmlsbDogdmFyKC0tZGFya3JlYWRlci1iYWNrZ3JvdW5kLTU4NWU2NiwgIzUwNTU1Nyk7IiBkYXRhLWRhcmtyZWFkZXItaW5saW5lLWZpbGw9IiIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pMi42MWIzM2E4MXNHQ0RVZSIgY2xhc3M9IiI+PC9wYXRoPgo8L3N2Zz4K
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // === 1. 数据读取与判断 ===
    const STORE_KEY = 'anti_idle_domains';
    const currentHost = window.location.hostname;
    let enabledDomains = GM_getValue(STORE_KEY, []);

    // 极速拦截:不在白名单内直接停止后续逻辑,实现“0资源占用”
    const isEnabled = enabledDomains.includes(currentHost);

    // 注册油猴菜单
    GM_registerMenuCommand('⚙️ 生效域名管理', showManagerPanel);

    // 若未启用,直接退出
    if (!isEnabled) return;


    // === 2. 核心突破逻辑 (仅在白名单域名执行) ===
    const stopEvent = function(e) {
        e.stopImmediatePropagation();
    };

    window.addEventListener('blur', stopEvent, true);
    document.addEventListener('visibilitychange', stopEvent, true);
    document.addEventListener('webkitvisibilitychange', stopEvent, true);

    const defineProp = (obj, prop, val) => {
        try {
            Object.defineProperty(obj, prop, {
                get: function() { return val; },
                configurable: true
            });
        } catch (e) {} // 静默失败,防止污染控制台
    };

    defineProp(document, 'visibilityState', 'visible');
    defineProp(document, 'hidden', false);
    defineProp(document, 'webkitVisibilityState', 'visible');
    defineProp(document, 'webkitHidden', false);

    console.log(`[网页防挂机] 当前域名已在列表内,防挂机功能已激活: ${currentHost}`);


    // === 3. 管理面板 UI 与逻辑 ===
    function showManagerPanel() {
        if (document.getElementById('anti-idle-manager-mask')) return;

        let currentDomains = GM_getValue(STORE_KEY, []);
        let isCurrentEnabled = currentDomains.includes(currentHost);

        // 注入面板 HTML
        const mask = document.createElement('div');
        mask.id = 'anti-idle-manager-mask';
        mask.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
            background: rgba(0, 0, 0, 0.5); z-index: 999999999;
            display: flex; justify-content: center; align-items: center;
            font-family: system-ui, -apple-system, sans-serif;
            backdrop-filter: blur(2px);
        `;

        mask.innerHTML = `
            <div style="background: #fff; padding: 24px; border-radius: 10px; width: 420px; max-width: 90%; color: #333; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
                <h3 style="margin: 0 0 16px 0; font-size: 18px; border-bottom: 1px solid #eaeaea; padding-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
                    <span>🛡️ 防挂机域名列表</span>
                </h3>
                <div style="margin-bottom: 16px; font-size: 14px; display: flex; align-items: center; justify-content: space-between;">
                    <div>当前域名: <strong style="color:#0056b3;">${currentHost}</strong></div>
                    <button id="anti-idle-toggle-btn" style="padding: 6px 10px; cursor: pointer; border: 1px solid transparent; background: ${isCurrentEnabled ? '#ffebee' : '#e8f5e9'}; color: ${isCurrentEnabled ? '#c62828' : '#2e7d32'}; border-radius: 6px; font-weight: 500; transition: all 0.2s;">
                        ${isCurrentEnabled ? '🚫 移出列表' : '✅ 加入列表'}
                    </button>
                </div>
                <div style="margin-bottom: 8px; font-size: 13px; color: #555; font-weight: bold;">
                    生效域名列表 (每行一个,支持手动编辑):
                </div>
                <textarea id="anti-idle-textarea" style="width: 100%; height: 160px; padding: 10px; box-sizing: border-box; border: 1px solid #ddd; border-radius: 6px; font-family: 'Consolas', monospace; font-size: 13px; resize: vertical; outline: none;" placeholder="在此输入需要生效的域名..."></textarea>
                <div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
                    <button id="anti-idle-close-btn" style="padding: 8px 16px; cursor: pointer; border: 1px solid #ddd; background: #f8f9fa; border-radius: 6px; color: #444; font-size: 13px; transition: background 0.2s;">取消 (Esc)</button>
                    <button id="anti-idle-save-btn" style="padding: 8px 16px; cursor: pointer; border: none; background: #007bff; color: white; border-radius: 6px; font-size: 13px; font-weight: 500; transition: background 0.2s;">💾 保存并刷新 (Ctrl+S)</button>
                </div>
            </div>
        `;

        document.body.appendChild(mask);

        const textarea = document.getElementById('anti-idle-textarea');
        textarea.value = currentDomains.join('\n');
        // 自动聚焦文本框
        textarea.focus();

        // 统一的关闭函数
        const closePanel = () => {
            mask.remove();
            document.removeEventListener('keydown', handleKeydown);
        };

        // 统一的保存函数
        const saveAndReload = () => {
            let newList = textarea.value.split('\n').map(s => s.trim()).filter(s => s);
            newList = [...new Set(newList)]; // 数组去重
            GM_setValue(STORE_KEY, newList);
            window.location.reload();
        };

        // 事件绑定:切换当前域名状态
        document.getElementById('anti-idle-toggle-btn').addEventListener('click', function() {
            let list = textarea.value.split('\n').map(s => s.trim()).filter(s => s);
            if (isCurrentEnabled) {
                list = list.filter(d => d !== currentHost);
                this.innerHTML = '✅ 加入列表';
                this.style.background = '#e8f5e9';
                this.style.color = '#2e7d32';
            } else {
                if (!list.includes(currentHost)) list.push(currentHost);
                this.innerHTML = '🚫 移出列表';
                this.style.background = '#ffebee';
                this.style.color = '#c62828';
            }
            isCurrentEnabled = !isCurrentEnabled;
            textarea.value = list.join('\n');
        });

        // 事件绑定:鼠标点击按钮或遮罩层关闭
        document.getElementById('anti-idle-close-btn').addEventListener('click', closePanel);
        mask.addEventListener('click', (e) => { if (e.target === mask) closePanel(); });

        // 事件绑定:鼠标点击保存
        document.getElementById('anti-idle-save-btn').addEventListener('click', saveAndReload);

        // 事件绑定:全局快捷键监听
        const handleKeydown = (e) => {
            // Esc 退出
            if (e.key === 'Escape') {
                e.preventDefault();
                closePanel();
            }
            // Ctrl+S 或 Cmd+S 保存
            else if ((e.ctrlKey || e.metaKey) && e.key === 's') {
                e.preventDefault();
                saveAndReload();
            }
        };
        document.addEventListener('keydown', handleKeydown);
    }

})();