Greasy Fork

Greasy Fork is available in English.

2025年智能YouTube全站广告拦截器

精准广告拦截,支持多平台保护

目前为 2025-03-13 提交的版本。查看 最新版本

// ==UserScript==
// @name        2025年智能YouTube全站广告拦截器
// @version     2.3
// @description 精准广告拦截,支持多平台保护
// @icon        https://www.youtube.com/favicon.ico
// @author      little fool
// @match       *://*/*
// @grant       GM_addStyle
// @grant       unsafeWindow
// @run-at      document-start
// @namespace   https://yournamespace.com
// @license     MIT
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .adblocker-status-icon {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
            background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNMjEuNTggNy4xOWMtLjIzLS44Ni0uOTQtMS41MS0xLjgxLTEuNzNDMTguMjQ1IDAgNSAwIDUgMHMtMTguMjQgMC0xOS43Ny40NmMtLjg3LjIyLTEuNTguODctMS44IDEuNzNDMCA4LjcxIDAgMTIgMCAxMnMwIDMuMjkuMDMgNC44MWMuMjIuODYuOTMgMS41MSAxLjggMS43MyAxLjUzLjQ4IDE5Ljc3LjQ4IDE5Ljc3LjQ4czE4LjI0IDAgMTkuNzctLjQ2Yy44Ny0uMjIgMS41OC0uODcgMS44LTEuNzMuMDMtMS41Mi4wMy0zLjI5LjAzLTQuODFzLS4wMS0zLjI5LS4wMy00Ljgxek0xMCAxNWw4LjgtNUwxMCA1djEweiIgZmlsbD0iI2ZmMDAwMCIvPjwvc3ZnPg==') no-repeat center/cover;
            cursor: pointer;
            z-index: 99999;
            opacity: 0.8;
            transition: all 0.3s;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        }
        .adblocker-tooltip {
            position: fixed;
            bottom: 70px;
            right: 20px;
            background: rgba(0,0,0,0.9);
            color: #fff;
            padding: 10px 15px;
            border-radius: 6px;
            font-size: 13px;
            display: none;
            white-space: nowrap;
            backdrop-filter: blur(2px);
        }
    `);

    // 修正核心配置
    const SecurityConfig = {
        protectedDomains: {
            'youtube.com': {
                icon: true,
                selectors: [
                    'ytd-display-ad-renderer',
                    '#player-ads',
                    '.ytp-ad-module',
                    'ytd-promoted-sparkles-web-renderer'
                ],
                networkPatterns: [ // 修正正则表达式闭合
                    /\/\/doubleclick\.net\//,
                    /youtube\.com\/api\/stats\/ads/,
                    /google\.com\/pagead\//, // 正确闭合的正则表达式
                    /\/adservice\.google\./
                ],
                whitelistPatterns: [
                    /googlevideo\.com\/videoplayback/,
                    /\/vi\/.+\/hqdefault\.jpg\??/
                ]
            }
        },
        adSelectors: [
            'div[id^="ad-"]:not([id*="-container"])',
            'ytd-ad-slot-renderer',
            'div.ytd-in-feed-ad-layout-renderer',
            'ins.adsbygoogle'
        ]
    };

    class AdBlocker {
        constructor() {
            this.currentHost = location.hostname;
            this.blockedCount = 0;
            if (this.isTargetDomain()) this.initialize();
        }

        isTargetDomain() {
            return Object.keys(SecurityConfig.protectedDomains)
                .some(d => this.currentHost.includes(d));
        }

        initialize() {
            this.setupMutationObserver();
            this.hijackNetworkRequests();
            this.injectStatusWidget();
        }

        setupMutationObserver() {
            new MutationObserver(mutations => {
                mutations.forEach(({ addedNodes }) => {
                    addedNodes.forEach(node => {
                        if (node.nodeType === 1) this.purgeAds(node);
                    });
                });
                this.deepScan();
            }).observe(document, { 
                childList: true, 
                subtree: true 
            });
        }

        purgeAds(root) {
            const targets = root.querySelectorAll(
                this.getCompositeSelectors()
            );
            targets.forEach(el => {
                if (!this.isVideoContainer(el)) {
                    el.remove();
                    this.blockedCount++;
                }
            });
        }

        getCompositeSelectors() {
            const domainConfig = SecurityConfig.protectedDomains[this.currentHost];
            return [
                ...SecurityConfig.adSelectors,
                ...(domainConfig?.selectors || [])
            ].join(', ');
        }

        hijackNetworkRequests() {
            // 拦截fetch
            const nativeFetch = window.fetch;
            window.fetch = async (input, init) => {
                const reqUrl = input.url || input;
                if (this.shouldBlockRequest(reqUrl)) {
                    console.warn('[BLOCKED] Fetch:', reqUrl);
                    return new Response(null, { status: 403 });
                }
                return nativeFetch(input, init);
            };

            // 拦截XHR
            const nativeOpen = XMLHttpRequest.prototype.open;
            XMLHttpRequest.prototype.open = function(method, url) {
                this._requestURL = url;
                nativeOpen.apply(this, arguments);
            };

            const nativeSend = XMLHttpRequest.prototype.send;
            XMLHttpRequest.prototype.send = function(...args) {
                if (this._requestURL && this.shouldBlockRequest(this._requestURL)) {
                    console.warn('[BLOCKED] XHR:', this._requestURL);
                    this.abort();
                    return;
                }
                nativeSend.apply(this, args);
            };
        }

        shouldBlockRequest(url) {
            const domainConfig = SecurityConfig.protectedDomains[this.currentHost];
            if (!domainConfig) return false;

            // 白名单优先
            if (domainConfig.whitelistPatterns.some(p => p.test(url))) return false;

            // 广告规则匹配
            return domainConfig.networkPatterns.some(p => p.test(url));
        }

        injectStatusWidget() {
            const icon = document.createElement('div');
            icon.className = 'adblocker-status-icon';
            document.body.appendChild(icon);
            
            // 添加交互事件...
        }
    }

    new AdBlocker();
})();