您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected
// ==UserScript== // @name Anti-Visibility Cloak // @namespace https://spin.rip/ // @version 1.1 // @description Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected // @author Spinfal // @match *://*/* // @run-at document-start // @grant none // @all-frames true // @icon https://cdn.spin.rip/r/antivisibility.png // @license gpl-3.0-or-later // ==/UserScript== (function() { 'use strict'; const CONFIG = { notify: { properties: true, hasFocusCall: false, addListener: { visibilitychange: true, webkitvisibilitychange: true, mozvisibilitychange: true, msvisibilitychange: true, pagehide: true, freeze: true, resume: true, pageshow: true, blur: false, focus: false, mouseout: true, mouseleave: true, pointerout: true, pointerleave: true, mouseover: true, mouseenter: true, pointerover: true, mousemove: false }, invoke: { visibilitychange: true, webkitvisibilitychange: true, mozvisibilitychange: true, msvisibilitychange: true, pagehide: true, freeze: true, resume: true, pageshow: true, blur: false, focus: false, mouseout: true, mouseleave: true, pointerout: true, pointerleave: true, mouseover: true, mouseenter: true, pointerover: true, mousemove: false } }, suppressFocusOnEditable: true, mouse: { spoofExit: true, // block/neutralize exit-intent listeners on window/document ignoreGlobalLeave: true, // ignore global leave events where relatedTarget is null blockGlobalEnter: true, // swallow global enter events where relatedTarget is null initialEnterOnce: true, // allow exactly one initial global enter after load fakeMovement: true, // periodically dispatch synthetic mousemove to suggest presence fakeIntervalMs: 12000, // how often to synthesize a mousemove moveJitterPx: 2 // tiny jitter so it doesn’t look robotic } }; // popup system const makeNotifier = () => { let shadow, wrap, root, last, lastAt = 0, hideTimer; root = document.createElement('div'); root.style.all = 'initial'; root.style.position = 'fixed'; root.style.zIndex = '2147483647'; root.style.right = '10px'; root.style.bottom = '10px'; root.style.pointerEvents = 'none'; shadow = root.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = ` .wrap{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:12px;line-height:1.2;background:#111;color:#fff;border-radius:8px;padding:8px 10px;box-shadow:0 2px 12px rgba(0,0,0,.35);opacity:.95;max-width:260px;pointer-events:auto} .wrap{display:flex;gap:8px;align-items:start} .dot{width:8px;height:8px;border-radius:50%;background:#4ade80;margin-top:4px;flex:0 0 8px} .msg{white-space:pre-line} .hide{animation:fadeout .4s forwards} @keyframes fadeout{to{opacity:0;transform:translateY(4px)}} `; wrap = document.createElement('div'); wrap.className = 'wrap'; wrap.innerHTML = `<div class="dot"></div><div class="msg"></div>`; shadow.append(style, wrap); const set = (text) => { if (!root.isConnected) document.documentElement.appendChild(root); wrap.querySelector('.msg').textContent = text; wrap.classList.remove('hide'); clearTimeout(hideTimer); hideTimer = setTimeout(() => { wrap.classList.add('hide'); setTimeout(() => { if (root.isConnected) root.remove(); }, 450); }, 1600); }; return (what) => { const now = Date.now(); if (what === last && (now - lastAt) < 250) return; last = what; lastAt = now; set(`visibility/mouse check bypassed:\n${what}`); }; }; const notify = makeNotifier(); // tiny helper for safe define const define = (target, key, descriptor) => { try { const desc = Object.getOwnPropertyDescriptor(target, key); if (desc && desc.configurable === false) return false; Object.defineProperty(target, key, { configurable: true, ...descriptor }); return true; } catch { return false; } }; // force document.* visibility values const forceVisibilityProps = () => { const docProto = Document.prototype; const makeGetter = (valName, value) => function() { if (CONFIG.notify.properties) notify(`${valName} read`); return value; }; define(docProto, 'visibilityState', { get: makeGetter('document.visibilityState', 'visible') }) || define(document, 'visibilityState', { get: makeGetter('document.visibilityState', 'visible') }); define(docProto, 'hidden', { get: makeGetter('document.hidden', false) }) || define(document, 'hidden', { get: makeGetter('document.hidden', false) }); const legacy = [ ['webkitVisibilityState','visible'], ['mozVisibilityState','visible'], ['msVisibilityState','visible'], ['webkitHidden',false], ['mozHidden',false], ['msHidden',false] ]; for (const [k, v] of legacy) { define(docProto, k, { get: makeGetter(`document.${k}`, v) }) || define(document, k, { get: makeGetter(`document.${k}`, v) }); } const origHasFocus = docProto.hasFocus; const announceHasFocus = () => { if (CONFIG.notify.hasFocusCall) notify('document.hasFocus() call'); return true; }; define(docProto, 'hasFocus', { value: announceHasFocus }) || define(document, 'hasFocus', { value: announceHasFocus }); if (!document.__origHasFocus) Object.defineProperty(document, '__origHasFocus', { value: origHasFocus, configurable: true }); }; // patch event listeners to swallow exit/enter-intent and wrap handlers const forceVisibilityEvents = () => { const TYPES = [ 'visibilitychange','webkitvisibilitychange','mozvisibilitychange','msvisibilitychange', 'pagehide','freeze','resume','pageshow','blur','focus', 'mouseleave','mouseout','pointerleave','pointerout', 'mouseover','mouseenter','pointerover', 'mousemove' ]; const EXIT_TYPES = new Set(['mouseleave','mouseout','pointerleave','pointerout']); const ENTER_TYPES = new Set(['mouseover','mouseenter','pointerover']); const isEditableTarget = (ev) => { if (!ev || !ev.target) return false; const t = ev.target; if (t.isContentEditable) return true; const tag = (t.tagName || '').toLowerCase(); return tag === 'input' || tag === 'textarea' || tag === 'select'; }; const isGlobal = (ctx) => ctx === window || ctx === document || ctx === document.documentElement; let allowOneGlobalEnter = !!CONFIG.mouse.initialEnterOnce; const wrapHandler = (type, fn, ctx) => { return function(event) { try { Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true }); Object.defineProperty(document, 'hidden', { get: () => false, configurable: true }); } catch {} // block global "leaving window" signals if ( CONFIG.mouse.ignoreGlobalLeave && EXIT_TYPES.has(type) && isGlobal(ctx) && (event == null || event.relatedTarget == null) ) { if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global exit)`); return; } // block global "entering window" signals if ( CONFIG.mouse.blockGlobalEnter && ENTER_TYPES.has(type) && isGlobal(ctx) && (event == null || event.relatedTarget == null) ) { if (allowOneGlobalEnter) { allowOneGlobalEnter = false; // let exactly one through } else { if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global enter)`); return; } } if (CONFIG.notify.invoke[type]) { if (!(CONFIG.suppressFocusOnEditable && (type === 'focus' || type === 'blur') && isEditableTarget(event))) { notify(`${type} listener invoked`); } } try { return fn.call(this, event); } catch(e) { setTimeout(() => { throw e; }); } }; }; const patchAEL = (proto, label) => { const orig = proto.addEventListener; define(proto, 'addEventListener', { value: function(type, listener, options) { const t = String(type); if (!listener) return orig.call(this, type, listener, options); if (TYPES.includes(t)) { // block registration of exit-intent listeners on global targets if (CONFIG.mouse.spoofExit && EXIT_TYPES.has(t) && isGlobal(this)) { if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`); return; } // block registration of enter-intent listeners on global targets if (CONFIG.mouse.blockGlobalEnter && ENTER_TYPES.has(t) && isGlobal(this)) { if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`); return; } if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}")`); const wrapped = wrapHandler(t, listener, this); try { Object.defineProperty(listener, '__visible_wrap__', { value: wrapped }); } catch {} return orig.call(this, type, wrapped, options); } return orig.call(this, type, listener, options); } }); const origRel = proto.removeEventListener; define(proto, 'removeEventListener', { value: function(type, listener, options) { const l = listener && listener.__visible_wrap__ ? listener.__visible_wrap__ : listener; return origRel.call(this, type, l, options); } }); }; patchAEL(EventTarget.prototype, 'EventTarget'); }; // synthetic mouse activity to suggest presence in page const installFakeMouse = () => { if (!CONFIG.mouse.fakeMovement) return; let lastX = Math.max(10, Math.min(window.innerWidth - 10, Math.floor(window.innerWidth / 2))); let lastY = Math.max(10, Math.min(window.innerHeight - 10, Math.floor(window.innerHeight / 2))); const update = (e) => { if (!e) return; if (typeof e.clientX === 'number') lastX = e.clientX; if (typeof e.clientY === 'number') lastY = e.clientY; }; window.addEventListener('mousemove', update, { passive: true, capture: true }); const tick = () => { try { const jx = (Math.random() * CONFIG.mouse.moveJitterPx * 2 - CONFIG.mouse.moveJitterPx) | 0; const jy = (Math.random() * CONFIG.mouse.moveJitterPx * 2 - CONFIG.mouse.moveJitterPx) | 0; const x = Math.max(0, Math.min(window.innerWidth - 1, lastX + jx)); const y = Math.max(0, Math.min(window.innerHeight - 1, lastY + jy)); const ev = new MouseEvent('mousemove', { bubbles: true, cancelable: false, clientX: x, clientY: y, screenX: x, screenY: y, view: window }); document.dispatchEvent(ev); if (CONFIG.notify.invoke.mousemove) notify('synthetic mousemove dispatched'); } catch {} }; setInterval(tick, Math.max(4000, CONFIG.mouse.fakeIntervalMs | 0)); }; // one-time visibility ping const dispatchInitialPing = () => { try { const ev = new Event('visibilitychange'); document.dispatchEvent(ev); } catch {} }; // apply forceVisibilityProps(); forceVisibilityEvents(); installFakeMouse(); document.addEventListener('DOMContentLoaded', dispatchInitialPing, { once: true }); // reapply on dom mutations (defensive) const reapply = () => { try { if (document.visibilityState !== 'visible') forceVisibilityProps(); if (document.hidden !== false) forceVisibilityProps(); } catch {} }; const mo = new MutationObserver(reapply); mo.observe(document.documentElement, { childList: true, subtree: true }); })();