Anti Adblock Nuker + Scroll Unlock (Lite/Adaptive)

Non-intrusive: deteksi dulu baru tindak. Hapus overlay & balikin scroll tanpa ngerusak SPA berat.

目前為 2025-08-16 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Anti Adblock Nuker + Scroll Unlock (Lite/Adaptive)
// @namespace    boss.tm.antiadblock
// @version      1.6
// @description  Non-intrusive: deteksi dulu baru tindak. Hapus overlay & balikin scroll tanpa ngerusak SPA berat.
// @author       Boss
// @match        *://*/*
// @license      MIT
// @run-at       document-start
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  const SKIP_HOSTS = [
    /web\.whatsapp\.com/i,
    /youtube\.com/i,
    /music\.youtube\.com/i,
    /studio\.youtube\.com/i,
    /mail\.google\.com/i,
    /drive\.google\.com/i,
    /docs\.google\.com/i,
    /discord\.com/i,
    /app\.slack\.com/i
  ];
  if (SKIP_HOSTS.some(rx => rx.test(location.host))) return;

  const NOP = function(){};
  let armed = false;
  let mo;
  let killHandlers = null;

  (function stub() {
    const names = ['BlockAdBlock','blockAdBlock','FuckAdBlock','fuckAdBlock','AdBlockDetector'];
    const bools = ['adblock','adBlock','adblocker','isAdBlockActive','hasAdblock','usesAdblock'];
    const noopCtor = function(){};
    Object.assign(noopCtor.prototype, { setOption: NOP, on: NOP, check: NOP, detect: NOP });

    const def = (k, v) => {
      try { Object.defineProperty(window, k, { configurable: true, get(){return v;}, set(){}}); }
      catch { window[k] = v; }
    };
    names.forEach(n => def(n, noopCtor));
    def('blockAdBlock', new noopCtor());
    bools.forEach(n => def(n, false));
  })();

  const addStyle = css => {
    try { GM_addStyle ? GM_addStyle(css) : (()=>{ const s=document.createElement('style'); s.textContent=css; document.documentElement.appendChild(s); })(); }
    catch {}
  };
  addStyle(`
    /* sembunyikan overlay anti-adblock umum */
    :where([class*="adblock"],[id*="adblock"],[class*="anti-ad"],[id*="anti-ad"],
           [class*="adblocker"],[id*="adblocker"],.fc-ab-root,.tp-backdrop,.tp-modal){
      display:none !important; visibility:hidden !important; pointer-events:none !important;
    }
  `);

  const OVERLAY_RX = /adblock|ad-block|anti.?ad|adblocker|paywall|adsbox|tp-backdrop|fc-ab-root/i;
  const LOCK_CLASSES = ['no-scroll','stop-scrolling','overlay-open','modal-open','disable-scroll','noScroll'];

  const isLocked = () => {
    const de = document.documentElement, b = document.body;
    if (!de || !b) return false;
    const co = (el) => el && getComputedStyle(el);
    const deCS = co(de), bCS = co(b);
    const overflowLocked = (deCS && deCS.overflow !== 'visible' && deCS.overflow !== 'auto') ||
                           (bCS && bCS.overflow !== 'visible' && bCS.overflow !== 'auto');
    const classLocked = LOCK_CLASSES.some(c => de.classList.contains(c) || b.classList.contains(c));
    const bigOverlay = !!document.querySelector([
      '[class*="adblock"]','[id*="adblock"]','[class*="anti-ad"]','[id*="anti-ad"]',
      '.fc-ab-root','.tp-backdrop','.tp-modal','[class*="paywall"]','[id*="paywall"]'
    ].join(','));
    return overflowLocked || classLocked || bigOverlay;
  };

  function arm() {
    if (armed) return;
    armed = true;

    const restore = () => {
      const de = document.documentElement, b = document.body;
      if (!de || !b) return;
      de.style.overflow = 'auto'; b.style.overflow = 'auto'; b.style.position = 'static';
      LOCK_CLASSES.forEach(c => { de.classList.remove(c); b.classList.remove(c); });
    };
    restore();

    const handler = (ev) => {
      if (!isLocked()) return;
      if (ev.cancelable) ev.stopImmediatePropagation();
    };
    ['wheel','mousewheel','DOMMouseScroll','touchmove','scroll','keydown'].forEach(evt => {
      window.addEventListener(evt, handler, { capture: true, passive: false });
      document.addEventListener(evt, handler, { capture: true, passive: false });
    });
    killHandlers = handler;

    mo = new MutationObserver(muts => {
      let touched = false;
      for (const m of muts) {
        for (const n of m.addedNodes) {
          if (n instanceof HTMLElement) {
            const cls = (n.className + ' ' + n.id);
            if (OVERLAY_RX.test(cls)) { n.remove(); touched = true; }
            else {
              const cs = getComputedStyle(n);
              const big = cs.position === 'fixed' && parseInt(cs.zIndex||'0',10) >= 1000 &&
                          n.clientHeight > innerHeight*0.9 && n.clientWidth > innerWidth*0.9;
              if (big) { n.remove(); touched = true; }
            }
          }
        }
      }
      if (touched) restore();
    });
    mo.observe(document.documentElement, { childList: true, subtree: true, attributes: false });
  }

  function maybeArm() {
    if (isLocked()) arm();
  }

  let checks = 0, maxChecks = 20;
  const tick = () => {
    maybeArm();
    checks++;
    if (armed || checks > maxChecks) return;
    setTimeout(tick, 180);
  };
  tick();

  addEventListener('keydown', e => {
    if (e.altKey && e.shiftKey && e.code === 'KeyA') location.reload();
  });

  addEventListener('beforeunload', () => {
    try { mo && mo.disconnect(); } catch {}
    if (killHandlers) {
      ['wheel','mousewheel','DOMMouseScroll','touchmove','scroll','keydown'].forEach(evt => {
        window.removeEventListener(evt, killHandlers, { capture: true });
        document.removeEventListener(evt, killHandlers, { capture: true });
      });
    }
  });
})();