反廣告攔截腳本阻擋工具

此腳本針對某些網站來阻擋反廣告腳本載入

// ==UserScript==
// @name         反廣告攔截腳本阻擋工具
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  此腳本針對某些網站來阻擋反廣告腳本載入
// @author       Stars Shine
// @match        https://dl.pgl823.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('Tampermonkey: 反廣告攔截腳本阻擋工具 已啟動。');

    // 廣告相關的域名列表
    const adDomains = [
        'googlesyndication.com',
        'googletagservices.com',
        'adservice.google.com',
        'doubleclick.net',
        'ad.doubleclick.net',
        'pagead2.googlesyndication.com',
        'tpc.googlesyndication.com',
        'googleads.g.doubleclick.net',
        // 您可以根據需要添加更多常見的廣告域名
        // 例如:'cdn.ads.example.com', 'tracker.example.com'
    ];

    // 檢查 URL 是否包含廣告域名
    function isAdUrl(url) {
        return adDomains.some(domain => url.includes(domain));
    }

    // 輔助函式:清除 #aBlock 上的所有類別
    function clearABlockClasses() {
        const aBlock = document.getElementById('aBlock');
        if (aBlock && aBlock.className !== '') {
            aBlock.className = ''; // 直接清空所有類別
            console.log('Tampermonkey: 移除了 #aBlock 上的所有類別。');
        }
    }

    // 1. 覆寫 window.onload 屬性,防止其他腳本設定它
    // 這會在原始腳本設定 window.onload 之前執行
    Object.defineProperty(window, 'onload', {
        get: function() {
            return function() {}; // 返回一個空函式
        },
        set: function(newValue) {
            console.log('Tampermonkey: 嘗試設定 window.onload 被阻止。');
        },
        configurable: true // 允許屬性被重新定義
    });

    // 2. 覆寫原始的 checkAdVisibility 函式為一個空函式
    // 這會阻止原始函式內的邏輯執行
    window.checkAdVisibility = function() {
        console.log('Tampermonkey: checkAdVisibility 函式被 Tampermonkey 腳本阻擋。');
        clearABlockClasses(); // 確保 aBlock 元素上的所有類別都被移除
    };

    // 3. 阻擋 Google AdSense 相關的全局變數和腳本載入
    // 覆寫 window.adsbygoogle 陣列,防止 AdSense 腳本初始化
    // 這樣 AdSense 腳本在執行時會發現 adsbygoogle 已經被控制,無法正常推送廣告單元
    window.adsbygoogle = window.adsbygoogle || [];
    window.adsbygoogle.length = 0; // 清空可能已存在的內容
    window.adsbygoogle.push = function() {
        console.log('Tampermonkey: window.adsbygoogle.push 被阻擋。');
    };

    // 4. 覆寫 document.createElement 方法,阻止廣告腳本和 iframe 的創建和載入
    // 儲存原始的 createElement 方法,以便在非廣告情況下使用
    const originalCreateElement = document.createElement;

    document.createElement = function(tagName) {
        // 呼叫原始的 createElement 方法創建元素
        const element = originalCreateElement.apply(this, arguments);

        const lowerTagName = tagName.toLowerCase();
        // 如果創建的是 <script> 或 <iframe> 標籤
        if (lowerTagName === 'script' || lowerTagName === 'iframe') {
            // 在元素被添加到 DOM 之前,檢查其 src 屬性
            // 使用 Object.defineProperty 來攔截 src 屬性的設置
            Object.defineProperty(element, 'src', {
                get: function() {
                    return this._src || '';
                },
                set: function(value) {
                    // 檢查是否為廣告相關的腳本或 iframe URL
                    if (isAdUrl(value)) {
                        console.log(`Tampermonkey: 偵測到廣告 ${lowerTagName} 載入嘗試,已阻擋:`, value);
                        // 阻止設置 src,使其無法載入
                        this._src = ''; // 清空 src
                        if (lowerTagName === 'script') {
                            this.type = 'text/plain'; // 阻止腳本執行,可以通過設置 type 為非 JavaScript 類型
                        }
                        return; // 阻止繼續設置 src
                    }
                    this._src = value; // 設置正常的 src
                },
                configurable: true // 允許屬性被重新定義
            });
        }
        return element; // 返回創建的元素
    };

    // 5. 攔截 XMLHttpRequest 請求
    const originalXHRopen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        if (isAdUrl(url)) {
            console.log('Tampermonkey: 偵測到廣告 XHR 請求,已阻擋:', url);
            // 阻止請求發送,或者讓它指向一個無害的地址
            // 這裡我們直接返回一個空的 open 函式,阻止真正的請求被配置
            return;
        }
        // 呼叫原始的 open 方法
        return originalXHRopen.apply(this, arguments);
    };

    // 6. 攔截 Fetch API 請求
    const originalFetch = window.fetch;
    window.fetch = function(input, init) {
        let url;
        if (typeof input === 'string') {
            url = input;
        } else if (input instanceof Request) {
            url = input.url;
        } else {
            // 處理其他類型的輸入,儘管通常是字串或 Request 物件
            url = String(input);
        }

        if (isAdUrl(url)) {
            console.log('Tampermonkey: 偵測到廣告 Fetch 請求,已阻擋:', url);
            // 返回一個空的 Promise.reject 或 Promise.resolve,模擬請求失敗或成功但無內容
            // 這裡返回一個被拒絕的 Promise,模擬網路錯誤
            return Promise.reject(new TypeError('Failed to fetch (blocked by Tampermonkey)'));
            // 或者可以返回一個成功的 Promise,但內容為空:
            // return Promise.resolve(new Response('', { status: 200 }));
        }
        // 呼叫原始的 fetch 方法
        return originalFetch.apply(this, arguments);
    };

    // 7. 移除頁面上已存在的或動態添加的廣告元素,並持續監控 #aBlock 類別
    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            // 處理新增節點
            if (mutation.addedNodes) {
                mutation.addedNodes.forEach(function(node) {
                    if (node.nodeType === 1) { // 確保是元素節點
                        // 移除 .adsbygoogle 類別的元素
                        node.querySelectorAll('.adsbygoogle').forEach(function(adElement) {
                            console.log('Tampermonkey: 偵測到並移除 .adsbygoogle 元素。', adElement);
                            adElement.remove();
                        });
                        // 移除常見的廣告 iframe
                        node.querySelectorAll('iframe[src*="googlesyndication.com"], iframe[src*="doubleclick.net"]').forEach(function(iframe) {
                            console.log('Tampermonkey: 偵測到並移除廣告 iframe。', iframe);
                            iframe.remove();
                        });
                        // 如果新增的節點是 #aBlock 或包含 #aBlock,則清除其類別
                        if (node.id === 'aBlock' || node.querySelector('#aBlock')) {
                            clearABlockClasses();
                        }
                    }
                });
            }
            // 處理屬性變化 (特別針對 #aBlock 的 class 屬性)
            if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                if (mutation.target.id === 'aBlock') {
                    // 如果 #aBlock 的 class 屬性被修改了,立即清空它
                    clearABlockClasses();
                }
            }
        });
    });

    // 開始觀察整個文檔的子節點變化 (childList) 和子樹 (subtree)
    // 同時也觀察屬性變化 (attributes),這樣可以捕捉到 className 的直接賦值
    observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
        attributes: true, // 監控屬性變化
        attributeFilter: ['class'] // 只監控 class 屬性的變化
    });

    // 在 DOMContentLoaded 時執行一次清理,以確保移除初始載入的廣告元素
    window.addEventListener('DOMContentLoaded', function() {
        document.querySelectorAll('.adsbygoogle').forEach(function(adElement) {
            console.log('Tampermonkey: DOMContentLoaded 時偵測到並移除 .adsbygoogle 元素。', adElement);
            adElement.remove();
        });
        document.querySelectorAll('iframe[src*="googlesyndication.com"], iframe[src*="doubleclick.net"]').forEach(function(iframe) {
            console.log('Tampermonkey: DOMContentLoaded 時偵測到並移除廣告 iframe。', iframe);
            iframe.remove();
        });
        // 在 DOMContentLoaded 時也檢查並移除 aBlock 上的所有類別
        clearABlockClasses();
    });

    // 8. 嘗試在頁面載入前注入 CSS 來隱藏廣告元素
    // 這種方法可以在 JavaScript 執行之前就提供視覺上的阻擋
    const style = document.createElement('style');
    style.textContent = `
        .adsbygoogle,
        [id*="google_ads_iframe"], /* 針對 AdSense 的 iframe ID 模式 */
        iframe[src*="googlesyndication.com"],
        iframe[src*="doubleclick.net"] {
            display: none !important;
            width: 0 !important;
            height: 0 !important;
            overflow: hidden !important;
            visibility: hidden !important;
            pointer-events: none !important; /* 阻止任何互動 */
        }
    `;
    // 將 style 標籤添加到文檔的頭部或根元素,使其盡早生效
    document.documentElement.appendChild(style);


    // 9. 阻止 #aBlock 元素重新添加 'hidden' 或 'ad-block' 類別
    // 覆寫 setAttribute 方法
    const originalSetAttribute = Element.prototype.setAttribute;
    Element.prototype.setAttribute = function(name, value) {
        if (this.id === 'aBlock' && name.toLowerCase() === 'class') {
            const newClasses = value.split(' ').filter(cls => cls !== 'hidden' && cls !== 'ad-block').join(' ');
            if (newClasses !== value) {
                console.log(`Tampermonkey: 阻止 #aBlock 設置 class 屬性,移除了 'hidden'/'ad-block'。原始值: "${value}", 新值: "${newClasses}"`);
                return originalSetAttribute.call(this, name, newClasses);
            }
        }
        return originalSetAttribute.apply(this, arguments);
    };

    // 覆寫 classList.add 方法
    const originalClassListAdd = DOMTokenList.prototype.add;
    DOMTokenList.prototype.add = function() {
        if (this.ownerElement && this.ownerElement.id === 'aBlock') {
            const classesToAdd = Array.from(arguments);
            const filteredClasses = classesToAdd.filter(cls => cls !== 'hidden' && cls !== 'ad-block');

            if (filteredClasses.length < classesToAdd.length) {
                console.log(`Tampermonkey: 阻止 #aBlock 添加 'hidden'/'ad-block' 類別。嘗試添加: "${classesToAdd.join(' ')}", 實際添加: "${filteredClasses.join(' ')}"`);
            }
            if (filteredClasses.length > 0) {
                return originalClassListAdd.apply(this, filteredClasses);
            }
            return; // 不添加任何類別
        }
        return originalClassListAdd.apply(this, arguments);
    };

    // 覆寫 classList.remove 方法,確保它能正常移除其他類別
    const originalClassListRemove = DOMTokenList.prototype.remove;
    DOMTokenList.prototype.remove = function() {
        return originalClassListRemove.apply(this, arguments);
    };

    // 覆寫 classList.toggle 方法
    const originalClassListToggle = DOMTokenList.prototype.toggle;
    DOMTokenList.prototype.toggle = function(token, force) {
        if (this.ownerElement && this.ownerElement.id === 'aBlock' && (token === 'hidden' || token === 'ad-block')) {
            console.log(`Tampermonkey: 阻止 #aBlock 執行 toggle 'hidden'/'ad-block' 類別。`);
            return false; // 返回 false 表示沒有添加/移除
        }
        return originalClassListToggle.apply(this, arguments);
    };

    // 10. 立即執行一次清理,以防 #aBlock 在腳本載入時就存在並帶有類別
    // 由於 @run-at document-start,這會盡可能早地執行
    clearABlockClasses();

})();