YouTube广告拦截器 Pro (Stealth Mode)

动态监测+请求拦截双重方案,支持最新YouTube广告结构 (Stealth Mode)

// ==UserScript==
// @name         YouTube广告拦截器 Pro (Stealth Mode)
// @namespace    safe-adblock-pro
// @version      6.0
// @match        *://*.youtube.com/*
// @grant        none
// @run-at       document-start
// @description  动态监测+请求拦截双重方案,支持最新YouTube广告结构 (Stealth Mode)
// ==/UserScript==

(function () {
    'use strict';

    // Configuration - Adjust these carefully!
    const AD_SELECTORS = [
        'ytd-ad-slot-renderer',
        'ytd-rich-item-renderer.ytd-rich-grid-row[is-ad]',
        'div.ytd-promoted-sparkles-web-renderer',
        'div#player-ads:not(#movie_player)',
        'ytd-action-companion-ad-renderer',
        'div.ad-showing',
        'div.ad-container',
        'ytm-promoted-sparkles-web-renderer',
        'div.ytp-ad-module',
        'div.video-ads.ytp-ad-module'
    ];

    const AD_URL_PATTERNS = [
        /\/pagead\/|\/ad_|\/get_midroll\//,
        /doubleclick\.net/,
        /google\.com\/pagead/,
        /\/ads\//,
        /\/adformat\//,
        /\/log_event\?action_type=ad/,
        /\/generate_204\?ad/
    ];

    const MUTATION_OBSERVER_CONFIG = {
        childList: true,
        subtree: true,
        characterData: true // Important for detecting subtle changes
    };

    // Helper Functions (More Stealthy)
    function getRandomDelay(max) {
        return Math.floor(Math.random() * max);
    }

    function safeRemove(element, reason) {
        try {
            if (element && element.isConnected) { // Check if still connected to DOM
                element.style.display = 'none';
                setTimeout(() => element.remove(), getRandomDelay(500));  // Remove with a slight delay
                console.log(`[拦截日志] ${reason}: ${element.tagName} 已移除`);
            }
        } catch (e) {
            console.warn('移除元素时出错:', e);
        }
    }

    // Ad Cleaner (Stealthier Approach)
    const adCleaner = {
        observer: null,
        init: function() {
            this.cleanAds();
            this.observer = new MutationObserver(mutations => {
                for (let mutation of mutations) {
                    if (mutation.addedNodes.length || mutation.characterData) { // Check for character data changes too
                        setTimeout(() => this.cleanAds(), getRandomDelay(500));  // Delay cleaning to avoid patterns
                    }
                }
            });

            this.observer.observe(document, MUTATION_OBSERVER_CONFIG);
        },
        cleanAds: function() {
            AD_SELECTORS.forEach(selector => {
                let elements = document.querySelectorAll(selector);
                for (let i = 0; i < elements.length; i++) {
                    safeRemove(elements[i], `选择器移除 (${selector})`);
                }
            });

            this.handleVideoAds();
        },
        handleVideoAds: function() {
            const skipButton = document.querySelector('.ytp-ad-skip-button-modern, .ytp-ad-skip-button');
            if (skipButton) {
                setTimeout(() => skipButton.click(), getRandomDelay(300)); // Delay click
                console.log('[广告拦截] 已跳过视频广告');
            }

            const playerOverlay = document.querySelector('.ytp-ad-player-overlay');
            if (playerOverlay) {
                setTimeout(() => playerOverlay.style.display = 'none', getRandomDelay(200)); // Delay style change
            }

            const video = document.querySelector('video');
            if (video && video.played && video.currentTime < 1) {
                setTimeout(() => {
                    video.currentTime = 1e4;
                    video.play().catch(() => {});
                }, getRandomDelay(400)); // Delay playback reset
            }
        }
    };

    // Request Blocker (More Subtle)
    const requestBlocker = {
        init: function() {
            // Use a proxy to avoid direct modification of XMLHttpRequest
            window.originalXMLHttpRequest = window.XMLHttpRequest;
            window.XMLHttpRequest = function(method, url) {
                let xhr = new window.originalXMLHttpRequest(method, url);
                if (AD_URL_PATTERNS.some(pattern => pattern.test(url))) {
                    console.log(`[请求拦截] 已阻止广告请求: ${url}`);
                    xhr.abort(); // Abort instead of preventing the request entirely
                    return xhr;
                }
                return xhr;
            };

            // Fetch interception - similar proxy approach
            window.originalFetch = window.fetch;
            window.fetch = function(...args) {
                let fetchResult = window.originalFetch(...args);
                const url = args[0] ? typeof args[0] === 'string' ? args[0] : args[0].url : null;

                if (url && AD_URL_PATTERNS.some(pattern => pattern.test(url))) {
                    console.log(`[请求拦截] 已阻止广告请求: ${url}`);
                    return new Response(null, { status: 403 }); // Return a blocked response
                }

                return fetchResult;
            };
        }
    };


    // Player Protector (Less Aggressive)
    const playerProtector = {
        init: function() {
            let observer = new MutationObserver(() => {
                const player = document.getElementById('movie_player');
                if (player) {
                    try{
                        player.classList.remove('ad-showing'); // Less aggressive removal of class
                    } catch(e){
                        console.warn("移除类时出错:", e);
                    }

                }
            });

            observer.observe(document.body, MUTATION_OBSERVER_CONFIG);
        }
    };


    // Initialization
    const init = () => {
        requestBlocker.init();
        playerProtector.init();
        adCleaner.init();
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Anti-Detection (Subtle)
    Object.defineProperty(document, 'hidden', { value: false, configurable: true });
    Object.defineProperty(document, 'visibilityState', { value: 'visible', configurable: true });

})();