Greasy Fork is available in English.
在指定域名作用范围内维持页面为可见/活跃状态。内置配置面板,支持动态维护作用域列表、快捷键操作与持久化存储;未启用域名不执行任何逻辑。
// ==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);
}
})();