🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

小说漫画网页广告拦截器

一个手机端via浏览器能用的强大的广告拦截器

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         小说漫画网页广告拦截器
// @namespace    http://tampermonkey.net/
// @version      4.5.0
// @author       DeepSeek&Gemini
// @description  一个手机端via浏览器能用的强大的广告拦截器
// @match        *://*/*
// @license      MIT
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    const DEFAULT_MODULE_STATE = {
        smartInterception: false,
        removeInlineScripts: false,
        removeExternalScripts: false,
        interceptThirdPartyResources: false,
        interceptSubdomains: false,
        manageCSP: false,
    };
    const MODULE_NAMES = {
        smartInterception: '智能拦截',
        removeInlineScripts: '移除内嵌脚本',
        removeExternalScripts: '移除外联脚本',
        interceptThirdPartyResources: '拦截第三方资源',
        interceptSubdomains: '拦截子域名资源',
        manageCSP: 'CSP策略管理',
    };
    const DEFAULT_CSP_RULES_TEMPLATE = [
        { id: 1, name: '仅允许同源脚本', rule: "script-src 'self'", enabled: false },
        { id: 2, name: '禁止内联脚本', rule: "script-src 'unsafe-inline'", enabled: false },
        { id: 3, name: '禁止eval执行', rule: "script-src 'unsafe-eval'", enabled: false },
        { id: 4, name: '阻止外部样式加载', rule: "style-src 'self'", enabled: false },
        { id: 5, name: '阻止内联样式执行', rule: "style-src 'unsafe-inline'", enabled: false },
        { id: 6, name: '阻止外部图片加载', rule: "img-src 'self'", enabled: false },
        { id: 7, name: '禁止所有框架加载', rule: "frame-src 'none'", enabled: false },
        { id: 8, name: '阻止媒体资源加载', rule: "media-src 'none'", enabled: false },
        { id: 9, name: '阻止对象嵌入', rule: "object-src 'none'", enabled: false }
    ];
    const CONFIG_STORAGE_KEY_PREFIX = `customAdBlockerConfig_`;
    let currentConfig = {
        modules: { ...DEFAULT_MODULE_STATE },
        cspRules: DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })),
        whitelist: {},
    };

    class LRUCache {
        constructor(capacity = 100, defaultTTL = 0) {
            this.capacity = capacity;
            this.defaultTTL = defaultTTL;
            this.cache = new Map();
        }

        get(key) {
            if (!this.cache.has(key)) return null;
            const entry = this.cache.get(key);
            if (this.defaultTTL > 0 && (Date.now() - entry.timestamp) > this.defaultTTL) {
                this.cache.delete(key);
                return null;
            }
            this.cache.delete(key);
            this.cache.set(key, entry);
            return entry.value;
        }

        set(key, value, ttl) {
            const finalTTL = ttl !== undefined ? ttl : this.defaultTTL;
            const entry = {
                value: value,
                timestamp: Date.now(),
                ttl: finalTTL
            };
            if (this.cache.has(key)) {
                this.cache.delete(key);
            } else if (this.cache.size >= this.capacity) {
                const firstKey = this.cache.keys().next().value;
                this.cache.delete(firstKey);
            }
            this.cache.set(key, entry);
        }

        has(key) {
            const entry = this.cache.get(key);
            if (!entry) return false;
            if (this.defaultTTL > 0 && (Date.now() - entry.timestamp) > this.defaultTTL) {
                this.cache.delete(key);
                return false;
            }
            return true;
        }

        delete(key) {
            return this.cache.delete(key);
        }

        clear() {
            this.cache.clear();
        }

        get size() {
            return this.cache.size;
        }
    }

    class URLResolutionCache {
        constructor() {
            this.hostnameCache = new LRUCache(1000, 300000);
            this.domainCache = new LRUCache(1000, 300000);
            this.absoluteUrlCache = new LRUCache(1000, 300000);
            this.thirdPartyCache = new LRUCache(1000, 300000);
        }

        isDynamicURL(url) {
            if (!url || typeof url !== 'string') return false;
            const dynamicPatterns = [
                /\?.*[tT]=/,
                /\?.*timestamp/,
                /\?.*rand/,
                /\?.*rnd/,
                /\?.*[0-9]{13,}/,
                /\?.*\d{10,}/,
                /\?.*[a-zA-Z0-9]{32,}/,
                /\/\d{10,}\./,
                /\/[0-9a-f]{32,}\./
            ];
            return dynamicPatterns.some(pattern => pattern.test(url));
        }

        getHostname(url) {
            if (!url || typeof url !== 'string') return null;
            const cacheKey = `hostname_${url}`;
            
            if (this.hostnameCache.has(cacheKey)) {
                return this.hostnameCache.get(cacheKey);
            }

            if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank')) {
                this.hostnameCache.set(cacheKey, null, 60000);
                return null;
            }

            try {
                const hostname = new URL(url, location.href).hostname;
                const ttl = this.isDynamicURL(url) ? 30000 : 300000;
                this.hostnameCache.set(cacheKey, hostname, ttl);
                return hostname;
            } catch (e) {
                this.hostnameCache.set(cacheKey, null, 30000);
                return null;
            }
        }

        getDomain(hostname) {
            if (!hostname) return null;
            const cacheKey = `domain_${hostname}`;
            
            if (this.domainCache.has(cacheKey)) {
                return this.domainCache.get(cacheKey);
            }

            const parts = hostname.split('.');
            const domain = parts.length <= 2 ? hostname : parts.slice(-2).join('.');
            this.domainCache.set(cacheKey, domain, 300000);
            return domain;
        }

        getAbsoluteURL(url) {
            if (!url) return '';
            const cacheKey = `absolute_${url}_${location.href}`;
            
            if (this.absoluteUrlCache.has(cacheKey)) {
                return this.absoluteUrlCache.get(cacheKey);
            }

            try {
                const absoluteUrl = new URL(url, location.href).href;
                const ttl = this.isDynamicURL(url) ? 30000 : 300000;
                this.absoluteUrlCache.set(cacheKey, absoluteUrl, ttl);
                return absoluteUrl;
            } catch (e) {
                this.absoluteUrlCache.set(cacheKey, url, 30000);
                return url;
            }
        }

        isThirdPartyHost(resourceHostname, currentHost, interceptSubdomains = false) {
            if (!resourceHostname) return false;
            
            const cacheKey = `thirdparty_${resourceHostname}_${currentHost}_${interceptSubdomains}`;
            if (this.thirdPartyCache.has(cacheKey)) {
                return this.thirdPartyCache.get(cacheKey);
            }

            const currentHostDomain = this.getDomain(currentHost);
            const resourceHostDomain = this.getDomain(resourceHostname);
            
            let isThirdParty = false;
            
            if (!currentHostDomain || !resourceHostDomain) {
                isThirdParty = false;
            } else if (resourceHostDomain === currentHostDomain) {
                if (interceptSubdomains && resourceHostname !== currentHost) {
                    isThirdParty = true;
                } else {
                    isThirdParty = false;
                }
            } else {
                isThirdParty = true;
            }
            
            this.thirdPartyCache.set(cacheKey, isThirdParty, 300000);
            return isThirdParty;
        }

        clear() {
            this.hostnameCache.clear();
            this.domainCache.clear();
            this.absoluteUrlCache.clear();
            this.thirdPartyCache.clear();
        }
    }

    const urlCache = new URLResolutionCache();

    const Utils = {
        truncateString(str, maxLength) {
            if (typeof str !== 'string') return '';
            if (str.length <= maxLength) return str;
            return str.slice(0, maxLength) + '...';
        },
        getCurrentHostname() {
            return location.hostname;
        },
        isElement(el) {
            return el instanceof Element;
        },
        getScriptContentPreview(scriptElement) {
            if (!scriptElement || scriptElement.tagName !== 'SCRIPT') return '';
            const content = scriptElement.textContent;
            if (content.length > 200) {
                return content.slice(0, 100) + '...';
            }
            return content.split(/\s+/).join(' ').slice(0, 100);
        },
        getIframeSrcPreview(iframeElement) {
            if (!iframeElement || iframeElement.tagName !== 'IFRAME') return '';
            return Utils.truncateString(iframeElement.src, 100);
        },
        getResourceHostname(url) {
            return urlCache.getHostname(url);
        },
        getDomain(hostname) {
            return urlCache.getDomain(hostname);
        },
        isThirdPartyHost(resourceHostname, currentHost) {
            return urlCache.isThirdPartyHost(resourceHostname, currentHost, currentConfig.modules.interceptSubdomains);
        },
        getAbsoluteURL(url) {
            return urlCache.getAbsoluteURL(url);
        },
        getContentIdentifier(element, reasonType = null) {
            if (!element && !reasonType) return null; // 必须有 element 或 reasonType
            
            if (element && Utils.isElement(element)) {
                const tagName = element.tagName;
                const src = element.src || element.getAttribute('data-src') || element.href || element.action || '';

                if (tagName === 'SCRIPT') {
                    return element.src ? `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}` : `SCRIPT_CONTENT: ${Utils.getScriptContentPreview(element)}`;
                } else if (tagName === 'IFRAME') {
                    return `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`;
                } else if (tagName === 'IMG') {
                    return src ? `IMG_SRC: ${Utils.truncateString(src, 100)}` : null;
                } else if (tagName === 'A') {
                    return src ? `A_HREF: ${Utils.truncateString(src, 100)}` : null;
                } else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
                    return `CSS_HREF: ${Utils.truncateString(element.href, 100)}`;
                } else if (['VIDEO', 'AUDIO', 'SOURCE'].includes(tagName) && src) {
                    return `${tagName}_SRC: ${Utils.truncateString(src, 100)}`;
                } else if (tagName === 'STYLE') {
                    return `STYLE_CONTENT: ${Utils.truncateString(element.textContent, 100)}`;
                }
                // 移除了 SVG/CANVAS 的识别逻辑
                return null;
            } else if (reasonType && typeof reasonType.detail === 'string') {
                if (reasonType.detail.startsWith('SRC:')) {
                    return `${reasonType.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reasonType.detail.substring(4).trim(), 100)}`;
                } else if (reasonType.detail.startsWith('URL:')) {
                    return `${reasonType.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reasonType.detail.substring(5).trim(), 100)}`;
                } else if (reasonType.type === 'EVAL') {
                    return `EVAL_CODE: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'FUNCTION_CONSTRUCTOR') {
                    return `FUNCTION_CODE: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'SETTIMEOUT') {
                    return `SETTIMEOUT: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'SETINTERVAL') {
                    return `SETINTERVAL: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'STYLE_ADS') {
                    return `STYLE_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'PRELOAD_ADS') {
                    return `PRELOAD_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
                } else if (reasonType.type === 'SCROLL_ADS') {
                    return `SCROLL_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
                }
                return `LOG_DETAIL: ${Utils.truncateString(reasonType.detail, 100)}`;
            }
            return null;
        },
        isParentProcessed(element) {
            let parent = element.parentElement;
            while (parent) {
                if (parent.dataset.adblockProcessed === 'true' || ProcessedElementsCache.isProcessed(parent)) {
                    return true;
                }
                parent = parent.parentElement;
            }
            return false;
        },
        debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        },
        throttle(func, limit) {
            let inThrottle;
            return function(...args) {
                if (!inThrottle) {
                    func.apply(this, args);
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, limit);
                }
            };
        }
    };

    const LogManager = {
        logs: [],
        maxLogs: 150,
        logEntryData: new LRUCache(150, 300000),
        loggedContentIdentifiers: new LRUCache(150, 300000),

        add(moduleKey, element, reason) {
            if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.removeInlineScripts && !currentConfig.modules.removeExternalScripts && !currentConfig.modules.smartInterception) {
                return;
            }
            if (!Utils.isElement(element) && element !== null && typeof reason !== 'object') return;

            const currentDomain = Utils.getCurrentHostname();

            if (Whitelisting.isElementWhitelisted(element) || Whitelisting.isReasonWhitelisted(reason)) {
                return;
            }

            let elementIdentifier = '[未知元素]';
            let interceptedContent = '[无法获取内容]';
            let contentIdentifier = null;

            if (Utils.isElement(element)) {
                const tagName = element.tagName;
                const id = element.id ? `#${element.id}` : '';
                const className = element.className ? `.${element.className.split(/\s+/).join('.')}` : '';
                elementIdentifier = `${tagName}${id}${className}`;

                contentIdentifier = Utils.getContentIdentifier(element);

                if (tagName === 'SCRIPT') {
                    interceptedContent = element.src ? `SRC: ${Utils.truncateString(element.src, 100)}` : Utils.getScriptContentPreview(element);
                } else if (tagName === 'IFRAME') {
                    interceptedContent = Utils.getIframeSrcPreview(element);
                } else if (tagName === 'IMG') {
                    const src = element.src || element.dataset.src;
                    interceptedContent = Utils.truncateString(src || '', 100);
                } else if (tagName === 'A') {
                    interceptedContent = Utils.truncateString(element.href || '', 100);
                } else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
                    interceptedContent = Utils.truncateString(element.href || '', 100);
                } else if (['VIDEO', 'AUDIO', 'SOURCE'].includes(tagName)) {
                    interceptedContent = Utils.truncateString(element.src || '', 100);
                } else if (tagName === 'STYLE') {
                    interceptedContent = Utils.truncateString(element.textContent, 100);
                } else {
                    interceptedContent = Utils.truncateString(element.outerHTML, 100);
                }
            } else if (reason && typeof reason.detail === 'string') {
                interceptedContent = Utils.truncateString(reason.detail, 100);
                elementIdentifier = reason.type ? `[${reason.type}]` : '[未知类型]';
                contentIdentifier = Utils.getContentIdentifier(null, reason);
            }

            if (!contentIdentifier) {
                return;
            }

            if (this.loggedContentIdentifiers.has(contentIdentifier)) {
                return;
            }

            const logId = this.logs.length + 1;
            const logEntry = {
                id: logId,
                module: MODULE_NAMES[moduleKey] || moduleKey,
                element: elementIdentifier,
                content: interceptedContent,
            };
            
            this.logs.push(logEntry);
            this.logEntryData.set(logId, {
                contentIdentifier: contentIdentifier,
                element: element,
                reason: reason,
            });
            this.loggedContentIdentifiers.set(contentIdentifier, true);

            if (this.logs.length > this.maxLogs) {
                const removedLogEntry = this.logs.shift();
                this.logEntryData.delete(removedLogEntry.id);
            }
        },

        showInAlert() {
            const currentDomain = Utils.getCurrentHostname();
            const logEntries = this.logs.map(log =>
                `序号: ${log.id}\n` +
                `模块: ${log.module}\n` +
                `元素: ${log.element}\n` +
                `内容: ${log.content}`
            ).join('\n\n');
            const promptMessage = `广告拦截日志(最近${this.logs.length}条):\n\n${logEntries || '暂无日志'}\n\n` +
                `添加白名单方式:\n` +
                `1. 输入序号(如 1-3, 1,3,5)\n` +
                `2. 输入关键词(如 aaa, bbb)\n` +
                `3. 输入 0 清空当前域名所有白名单\n\n` +
                `请输入:`;
            let input = prompt(promptMessage, "");
            if (input === null) return;
            input = input.trim();

            if (input === "0") {
                if (confirm(`清空 ${currentDomain} 的所有白名单?`)) {
                    Whitelisting.clearDomainWhitelist(currentDomain);
                    StorageManager.saveConfig();
                    alert("白名单已清空,页面将刷新。");
                    location.reload();
                }
            } else {
                const indicesToWhitelist = new Set();
                const keywordsToWhitelist = new Set();
                const parts = input.replace(/,/g, ',').split(/[\s,]+/);

                parts.forEach(part => {
                    if (part.includes('-')) {
                        const range = part.split('-').map(Number);
                        if (range.length === 2 && !isNaN(range[0]) && !isNaN(range[1])) {
                            const start = Math.min(range[0], range[1]);
                            const end = Math.max(range[0], range[1]);
                            for (let i = start; i <= end; i++) {
                                indicesToWhitelist.add(i);
                            }
                        }
                    } else {
                        const num = parseInt(part, 10);
                        if (!isNaN(num)) {
                            indicesToWhitelist.add(num);
                        } else if (part.length > 0) {
                            keywordsToWhitelist.add(part.toLowerCase());
                        }
                    }
                });

                let addedCount = 0;
                let whitelistedIdentifiers = new Set();

                indicesToWhitelist.forEach(index => {
                    const logEntryInfo = this.logEntryData.get(index);
                    if (logEntryInfo && logEntryInfo.contentIdentifier) {
                        const { contentIdentifier } = logEntryInfo;
                        if (!Whitelisting.isContentWhitelisted(currentDomain, contentIdentifier)) {
                            whitelistedIdentifiers.add(contentIdentifier);
                        }
                    }
                });

                keywordsToWhitelist.forEach(keyword => {
                    for (const [key, entry] of this.logEntryData.cache) {
                        const logEntryInfo = entry.value;
                        if (logEntryInfo.contentIdentifier && logEntryInfo.contentIdentifier.toLowerCase().includes(keyword)) {
                            if (!Whitelisting.isContentWhitelisted(currentDomain, logEntryInfo.contentIdentifier)) {
                                whitelistedIdentifiers.add(logEntryInfo.contentIdentifier);
                            }
                        }
                    }
                });

                whitelistedIdentifiers.forEach(identifier => {
                    Whitelisting.add(currentDomain, identifier);
                    addedCount++;
                });

                if (addedCount > 0) {
                    StorageManager.saveConfig();
                    alert(`${addedCount} 项已添加到白名单,页面将刷新。`);
                    location.reload();
                } else if (input.length > 0) {
                    alert("未找到匹配项,或已在白名单中。");
                }
            }
        }
    };

    const Whitelisting = {
        isElementWhitelisted(element) {
            if (!element || !Utils.isElement(element)) return false;
            const currentDomain = Utils.getCurrentHostname();
            const contentIdentifier = Utils.getContentIdentifier(element);
            return !!(contentIdentifier && currentDomain && currentConfig.whitelist[currentDomain] && currentConfig.whitelist[currentDomain].has(contentIdentifier));
        },
        isReasonWhitelisted(reason) {
            if (!reason || typeof reason.detail !== 'string') return false;
            const currentDomain = Utils.getCurrentHostname();
            const contentIdentifier = Utils.getContentIdentifier(null, reason);
            return !!(contentIdentifier && currentDomain && currentConfig.whitelist[currentDomain] && currentConfig.whitelist[currentDomain].has(contentIdentifier));
        },
        isContentWhitelisted(domain, contentIdentifier) {
            if (!domain || !contentIdentifier) return false;
            return !!(currentConfig.whitelist[domain] && currentConfig.whitelist[domain].has(contentIdentifier));
        },
        add(domain, contentIdentifier) {
            if (!domain || !contentIdentifier || contentIdentifier.trim() === '') return;
            if (!currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain] = new Set();
            }
            currentConfig.whitelist[domain].add(contentIdentifier);
        },
        clearDomainWhitelist(domain) {
            if (currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain].clear();
            }
        },
        clearAllWhitelists() {
            currentConfig.whitelist = {};
        }
    };

    const StorageManager = {
        getConfigKey(hostname) {
            return `${CONFIG_STORAGE_KEY_PREFIX}${hostname}`;
        },
        loadConfig() {
            const hostname = Utils.getCurrentHostname();
            const key = this.getConfigKey(hostname);
            try {
                const savedConfig = JSON.parse(GM_getValue(key, '{}'));
                if (savedConfig) {
                    if (savedConfig.modules) {
                        Object.assign(currentConfig.modules, savedConfig.modules);
                    }
                    if (savedConfig.cspRules) {
                        currentConfig.cspRules = savedConfig.cspRules.map(rule => ({ ...rule }));
                    }
                    if (savedConfig.whitelist) {
                        currentConfig.whitelist = {};
                        for (const domain in savedConfig.whitelist) {
                            if (Array.isArray(savedConfig.whitelist[domain])) {
                                currentConfig.whitelist[domain] = new Set(savedConfig.whitelist[domain]);
                            }
                        }
                    }
                }
            } catch (e) {
                Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
                currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
                currentConfig.whitelist = {};
            }
            Object.keys(DEFAULT_MODULE_STATE).forEach(key => {
                if (currentConfig.modules[key] === undefined) {
                    currentConfig.modules[key] = DEFAULT_MODULE_STATE[key];
                }
            });
            if (currentConfig.modules.manageCSP === undefined) currentConfig.modules.manageCSP = false;
            if (currentConfig.modules.interceptSubdomains === undefined) currentConfig.modules.interceptSubdomains = false;
            if (currentConfig.modules.smartInterception === undefined) currentConfig.modules.smartInterception = false;
        },
        saveConfig() {
            const hostname = Utils.getCurrentHostname();
            const key = this.getConfigKey(hostname);
            try {
                const whitelistForStorage = {};
                for (const domain in currentConfig.whitelist) {
                    whitelistForStorage[domain] = Array.from(currentConfig.whitelist[domain]);
                }
                GM_setValue(key, JSON.stringify({
                    modules: currentConfig.modules,
                    cspRules: currentConfig.cspRules,
                    whitelist: whitelistForStorage
                }));
            } catch (e) {}
        }
    };

    const ProcessedElementsCache = {
        _processedElements: new WeakSet(),

        isProcessed(element) {
            if (!Utils.isElement(element)) return false;
            return this._processedElements.has(element) || element.dataset.adblockProcessed === 'true';
        },

        markAsProcessed(element) {
            if (!Utils.isElement(element)) return;
            this._processedElements.add(element);
            element.dataset.adblockProcessed = 'true';
        },

        clear() {
            this._processedElements = new WeakSet();
        }
    };

    const ResourceCanceller = {
        cancelResourceLoading(element) {
            if (!Utils.isElement(element) || ProcessedElementsCache.isProcessed(element)) return;
            
            const tagName = element.tagName;
            
            if (['IMG', 'VIDEO', 'AUDIO', 'SOURCE'].includes(tagName) && element.src) {
                element.src = '';
                element.srcset = '';
                element.removeAttribute('srcset');
                if (element.load) element.load();
            } else if (tagName === 'IFRAME' && element.src) {
                element.src = 'about:blank';
            } else if (tagName === 'SCRIPT' && element.src) {
                element.src = '';
            } else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
                element.href = '';
            } else if (tagName === 'STYLE') {
                element.textContent = '';
            }
            
            if (element.parentNode) {
                element.parentNode.removeChild(element);
            }
        }
    };

    const SmartInterceptionModule = {
        originalSetTimeout: null,
        originalSetInterval: null,
        originalClearTimeout: null,
        originalClearInterval: null,
        processedTimers: new WeakSet(),
        stylePatterns: [
            /position\s*:\s*fixed\s*!\s*important/i,
            /position\s*:\s*sticky\s*!\s*important/i,
            /z-index\s*:\s*[1-9][0-9]{3,}\s*!\s*important/i,
            /display\s*:\s*none\s*!\s*important.*(ad|banner|popup)/i,
            /visibility\s*:\s*hidden\s*!\s*important.*(ad|banner|popup)/i,
            /opacity\s*:\s*0\s*!\s*important.*(ad|banner|popup)/i,
            /width\s*:\s*[0-9]{1,3}px.*height\s*:\s*[0-9]{1,3}px.*(fixed|sticky)/i,
            /height\s*:\s*[0-9]{1,3}px.*width\s*:\s*[0-9]{1,3}px.*(fixed|sticky)/i,
            /background\s*.*(url\s*\(.*(ad|banner|popup|track|analytics))|(ad\.[a-z]+|ads\.[a-z]+|banner\.[a-z]+)/i,
            /\.ad[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*sticky|z-index\s*:\s*[1-9][0-9]{3,})/i,
            /#ad[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*sticky|z-index\s*:\s*[1-9][0-9]{3,})/i,
            /\.ads?[\w-]*\s*{.*(display\s*:\s*block|visibility\s*:\s*visible).*(width|height).*px/i,
            /\.popup[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*absolute).*(top|left|right|bottom).*0/i
        ],
        init() {
            if (currentConfig.modules.smartInterception) {
                this.enable();
            }
        },
        enable() {
            this.setupTimerInterception();
            this.setupFastStyleChecker();
        },
        disable() {
            this.disableTimerInterception();
        },
        setupTimerInterception() {
            this.originalSetTimeout = window.setTimeout;
            this.originalSetInterval = window.setInterval;
            this.originalClearTimeout = window.clearTimeout;
            this.originalClearInterval = window.clearInterval;
            
            const self = this;
            
            window.setTimeout = function(callback, delay, ...args) {
                if (typeof callback === 'function') {
                    const callbackStr = callback.toString();
                    if (self.isAdTimerCallback(callbackStr)) {
                        const contentIdentifier = Utils.getContentIdentifier(null, { type: 'SETTIMEOUT', detail: `代码: ${Utils.truncateString(callbackStr, 100)}` });
                        if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                            LogManager.add('smartInterception', null, { type: 'SETTIMEOUT', detail: `广告定时器: ${Utils.truncateString(callbackStr, 100)}` });
                            const timerId = self.originalSetTimeout(() => {}, delay);
                            self.processedTimers.add(timerId);
                            return timerId;
                        }
                    }
                }
                return self.originalSetTimeout.call(this, callback, delay, ...args);
            };
            
            window.setInterval = function(callback, delay, ...args) {
                if (typeof callback === 'function') {
                    const callbackStr = callback.toString();
                    if (self.isAdTimerCallback(callbackStr)) {
                        const contentIdentifier = Utils.getContentIdentifier(null, { type: 'SETINTERVAL', detail: `代码: ${Utils.truncateString(callbackStr, 100)}` });
                        if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                            LogManager.add('smartInterception', null, { type: 'SETINTERVAL', detail: `广告定时器: ${Utils.truncateString(callbackStr, 100)}` });
                            const timerId = self.originalSetInterval(() => {}, delay);
                            self.processedTimers.add(timerId);
                            return timerId;
                        }
                    }
                }
                return self.originalSetInterval.call(this, callback, delay, ...args);
            };
            
            window.clearTimeout = function(timerId) {
                if (self.processedTimers.has(timerId)) {
                    self.processedTimers.delete(timerId);
                    return;
                }
                self.originalClearTimeout.call(this, timerId);
            };
            
            window.clearInterval = function(timerId) {
                if (self.processedTimers.has(timerId)) {
                    self.processedTimers.delete(timerId);
                    return;
                }
                self.originalClearInterval.call(this, timerId);
            };
        },
        disableTimerInterception() {
            if (this.originalSetTimeout) window.setTimeout = this.originalSetTimeout;
            if (this.originalSetInterval) window.setInterval = this.originalSetInterval;
            if (this.originalClearTimeout) window.clearTimeout = this.originalClearTimeout;
            if (this.originalClearInterval) window.clearInterval = this.originalClearInterval;
        },
        isAdTimerCallback(callbackStr) {
            const adPatterns = [
                /\b(ad|ads|banner|popup|promo|sponsor)\b/i,
                /ad\./i,
                /ads\./i,
                /banner\./i,
                /popup\./i,
                /showAd/i,
                /displayAd/i,
                /loadAd/i,
                /\.ad[\w-]*\(/i,
                /google_ad/i,
                /doubleclick/i,
                
                /['"](ad|ads|banner|popup)['"]/i,
                /['"]\s*\+\s*['"](ad|ads)['"]/i,
                /String\.fromCharCode.*(97|100).*61|64|100/i,
                
                /\w\s*=\s*\w\s*\+\s*['"](ad|ads|banner)['"]/i,
                /\w\s*\+\s*=\s*['"](ad|ads|banner)['"]/i,
                
                /\w\s*\.\s*\w\s*\(\s*['"](ad|ads|banner)['"]/i,
                /\w\s*\[\s*['"](ad|ads|banner)['"]\s*\]/i,
                
                /(adserver|adservice|advert|advertising)/i,
                /\.(doubleclick|googlesyndication|googleadservices)\.[a-z]+\//i,
                /\/ads?\//i,
                /\/banners?\//i,
                /\/popups?\//i,
                
                /function\s+\w*[Aa]d\w*/i,
                /var\s+\w*[Aa]d\w*\s*=/i,
                /const\s+\w*[Aa]d\w*\s*=/i,
                /let\s+\w*[Aa]d\w*\s*=/i,
                
                /addEventListener.*(load|show).*(ad|ads|banner)/i,
                
                /createElement.*(div|iframe).*(ad|ads|banner)/i,
                /\.src\s*=\s*['"][^'"]*(ad|ads|banner)[^'"]*['"]/i,
                
                /(adsbygoogle|amazon-adsystem|taboola|outbrain)/i
            ];
            
            const isMinified = callbackStr.length > 50 && 
                              (callbackStr.includes(';') && 
                               callbackStr.split(';').length > 10 &&
                               callbackStr.replace(/\s+/g, '').length / callbackStr.length > 0.9);
            
            if (isMinified) {
                const compressedAdPatterns = [
                    /a\s*d\s*s/i,
                    /b\s*a\s*n\s*n\s*e\s*r/i,
                    /p\s*o\s*p\s*u\s*p/i,
                    /[\w\.]+ad[\w\.]*/i,
                    /ad[\w]*\s*=/i
                ];
                
                if (compressedAdPatterns.some(pattern => pattern.test(callbackStr))) {
                    return true;
                }
            }
            
            return adPatterns.some(pattern => pattern.test(callbackStr));
        },
        setupFastStyleChecker() {
            const observer = new MutationObserver(Utils.throttle((mutations) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType !== Node.ELEMENT_NODE) continue;
                        if (ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) continue;
                        
                        this.fastCheckElement(node);
                    }
                }
            }, 200));
            
            observer.observe(document.documentElement, {
                childList: true,
                subtree: true
            });
        },
        fastCheckElement(element) {
            const tagName = element.tagName;
            
            if (tagName === 'STYLE') {
                this.checkStyleElement(element);
            }
        },
        checkStyleElement(styleElement) {
            const styleContent = styleElement.textContent;
            if (this.stylePatterns.some(pattern => pattern.test(styleContent))) {
                const contentIdentifier = Utils.getContentIdentifier(styleElement);
                if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('smartInterception', styleElement, { type: 'STYLE_ADS', detail: `广告样式: ${Utils.truncateString(styleContent, 100)}` });
                    ResourceCanceller.cancelResourceLoading(styleElement);
                    ProcessedElementsCache.markAsProcessed(styleElement);
                }
            }
        },
        check(element) {
            if (!currentConfig.modules.smartInterception || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
            
            const tagName = element.tagName;
            
            if (tagName === 'STYLE') {
                this.checkStyleElement(element);
                return ProcessedElementsCache.isProcessed(element);
            }
            
            return false;
        }
    };

    const RemoveInlineScriptsModule = {
        mutationObserver: null,
        init() {
            if (currentConfig.modules.removeInlineScripts) {
                this.enable();
            }
        },
        enable() {
            this.mutationObserver = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && !node.src && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
                            const contentIdentifier = Utils.getContentIdentifier(node);
                            if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                                LogManager.add('removeInlineScripts', node, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(node)}` });
                                ProcessedElementsCache.markAsProcessed(node);
                                node.remove();
                            }
                        }
                    }
                }
            });
            this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
            document.querySelectorAll('script:not([src])').forEach(script => {
                if (ProcessedElementsCache.isProcessed(script) || Utils.isParentProcessed(script)) return;
                const contentIdentifier = Utils.getContentIdentifier(script);
                if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeInlineScripts', script, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(script)}` });
                    ProcessedElementsCache.markAsProcessed(script);
                    script.remove();
                }
            });
        },
        disable() {
            if (this.mutationObserver) {
                this.mutationObserver.disconnect();
                this.mutationObserver = null;
            }
        },
        check(element) {
            if (!currentConfig.modules.removeInlineScripts || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
            if (element.tagName === 'SCRIPT' && !element.src) {
                const contentIdentifier = Utils.getContentIdentifier(element);
                if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeInlineScripts', element, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(element)}` });
                    ProcessedElementsCache.markAsProcessed(element);
                    return true;
                }
            }
            return false;
        }
    };

    const RemoveExternalScriptsModule = {
        mutationObserver: null,
        init() {
            if (currentConfig.modules.removeExternalScripts) {
                this.enable();
            }
        },
        enable() {
            this.mutationObserver = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && node.src && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
                            const contentIdentifier = Utils.getContentIdentifier(node);
                            if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                                LogManager.add('removeExternalScripts', node, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(node.src, 100)}` });
                                ResourceCanceller.cancelResourceLoading(node);
                                ProcessedElementsCache.markAsProcessed(node);
                            }
                        }
                    }
                }
            });
            this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
            document.querySelectorAll('script[src]').forEach(script => {
                if (ProcessedElementsCache.isProcessed(script) || Utils.isParentProcessed(script)) return;
                const contentIdentifier = Utils.getContentIdentifier(script);
                if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeExternalScripts', script, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(script.src, 100)}` });
                    ResourceCanceller.cancelResourceLoading(script);
                    ProcessedElementsCache.markAsProcessed(script);
                }
            });
        },
        disable() {
            if (this.mutationObserver) {
                this.mutationObserver.disconnect();
                this.mutationObserver = null;
            }
        },
        check(element) {
            if (!currentConfig.modules.removeExternalScripts || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
            if (element.tagName === 'SCRIPT' && element.src) {
                const contentIdentifier = Utils.getContentIdentifier(element);
                if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeExternalScripts', element, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(element.src, 100)}` });
                    ResourceCanceller.cancelResourceLoading(element);
                    ProcessedElementsCache.markAsProcessed(element);
                    return true;
                }
            }
            return false;
        }
    };

    const ThirdPartyModule = {
        originalFetch: null,
        originalXhrOpen: null,
        originalXhrSend: null,
        originalCreateElement: null,
        originalAppendChild: null,
        originalDocumentWrite: null,
        originalEval: null,
        originalFunction: null,
        init() {
            if (currentConfig.modules.interceptThirdPartyResources) {
                this.enable();
            }
        },
        enable() {
            const cspBlockEval = currentConfig.cspRules.find(rule => rule.id === 3 && rule.enabled);
            if (!cspBlockEval) {
                this.originalEval = unsafeWindow.eval;
                unsafeWindow.eval = (code) => {
                    const contentIdentifier = Utils.getContentIdentifier(null, { type: 'EVAL', detail: code });
                    if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                        LogManager.add('interceptThirdPartyResources', null, { type: 'EVAL拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
                        return undefined;
                    }
                    return this.originalEval.call(unsafeWindow, code);
                };
            }

            const cspBlockFunction = currentConfig.cspRules.find(rule => rule.id === 3 && rule.enabled);
            if (!cspBlockFunction) {
                this.originalFunction = unsafeWindow.Function;
                unsafeWindow.Function = function(...args) {
                    const code = args.length > 0 ? args[args.length - 1] : '';
                    const contentIdentifier = Utils.getContentIdentifier(null, { type: 'FUNCTION_CONSTRUCTOR', detail: code });
                    if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                        LogManager.add('interceptThirdPartyResources', null, { type: 'Function构造拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
                        return () => {};
                    }
                    return this.originalFunction.apply(unsafeWindow, args);
                };
            }

            this.originalFetch = window.fetch;
            window.fetch = (input, init) => {
                if (!currentConfig.modules.interceptThirdPartyResources) {
                    return this.originalFetch.call(this, input, init);
                }
                const url = typeof input === 'string' ? input : input.url;
                const resourceURL = Utils.getAbsoluteURL(url);
                const resourceHostname = Utils.getResourceHostname(resourceURL);
                const currentHost = Utils.getCurrentHostname();
                const contentIdentifier = Utils.getContentIdentifier(null, { type: 'FETCH', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });

                if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                    if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                        LogManager.add('interceptThirdPartyResources', null, { type: '第三方请求拦截', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });
                        return Promise.reject(new Error('Third-party resource blocked by AdBlocker.'));
                    }
                }
                return this.originalFetch.call(this, input, init);
            };

            this.originalXhrOpen = XMLHttpRequest.prototype.open;
            XMLHttpRequest.prototype.open = function(method, url) {
                if (!currentConfig.modules.interceptThirdPartyResources) {
                    return this.originalXhrOpen.apply(this, arguments);
                }
                const resourceURL = Utils.getAbsoluteURL(url);
                const resourceHostname = Utils.getResourceHostname(resourceURL);
                const currentHost = Utils.getCurrentHostname();
                const contentIdentifier = Utils.getContentIdentifier(null, { type: 'XHR', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });

                if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                    if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                        LogManager.add('interceptThirdPartyResources', null, { type: '第三方请求拦截', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });
                        this._adblockBlocked = true;
                        return;
                    }
                }
                return this.originalXhrOpen.apply(this, arguments);
            };
            this.originalXhrSend = XMLHttpRequest.prototype.send;
            XMLHttpRequest.prototype.send = function() {
                if (this._adblockBlocked) {
                    return;
                }
                this.originalXhrSend.apply(this, arguments);
            };

            this.originalCreateElement = document.createElement;
            document.createElement = (tagName, options) => {
                const element = this.originalCreateElement.call(document, tagName, options);
                if (!currentConfig.modules.interceptThirdPartyResources || ProcessedElementsCache.isProcessed(element)) {
                    return element;
                }
                const resourceURL = Utils.getAbsoluteURL(element.src || element.getAttribute('data-src') || element.href || element.action);
                if (resourceURL) {
                    const resourceHostname = Utils.getResourceHostname(resourceURL);
                    const currentHost = Utils.getCurrentHostname();
                    const contentIdentifier = Utils.getContentIdentifier(element);

                    if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                        if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                            LogManager.add('interceptThirdPartyResources', element, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                            ResourceCanceller.cancelResourceLoading(element);
                            ProcessedElementsCache.markAsProcessed(element);
                            return element;
                        }
                    }
                }
                return element;
            };

            this.originalAppendChild = Node.prototype.appendChild;
            Node.prototype.appendChild = function(node) {
                if (!currentConfig.modules.interceptThirdPartyResources || !Utils.isElement(node) || ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) {
                    return this.originalAppendChild.call(this, node);
                }
                const resourceURL = Utils.getAbsoluteURL(node.src || node.getAttribute('data-src') || node.href || node.action);
                if (resourceURL) {
                    const resourceHostname = Utils.getResourceHostname(resourceURL);
                    const currentHost = Utils.getCurrentHostname();
                    const contentIdentifier = Utils.getContentIdentifier(node);

                    if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                        if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                            LogManager.add('interceptThirdPartyResources', node, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                            ResourceCanceller.cancelResourceLoading(node);
                            ProcessedElementsCache.markAsProcessed(node);
                            return node;
                        }
                    }
                }
                return this.originalAppendChild.call(this, node);
            };

            this.originalDocumentWrite = document.write;
            document.write = function(content) {
                if (!currentConfig.modules.interceptThirdPartyResources) {
                    return this.originalDocumentWrite.call(document, content);
                }
                const tempDiv = document.createElement('div');
                tempDiv.innerHTML = content;
                const scripts = tempDiv.querySelectorAll('script[src]');
                let modifiedContent = content;

                for (const script of scripts) {
                    const src = script.src;
                    if (src) {
                        const resourceURL = Utils.getAbsoluteURL(src);
                        const resourceHostname = Utils.getResourceHostname(resourceURL);
                        const currentHost = Utils.getCurrentHostname();
                        const contentIdentifier = Utils.getContentIdentifier(script);

                        if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                            if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                                LogManager.add('interceptThirdPartyResources', script, { type: '第三方脚本通过document.write拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                                ResourceCanceller.cancelResourceLoading(script);
                                ProcessedElementsCache.markAsProcessed(script);
                            }
                        }
                    }
                }
                modifiedContent = tempDiv.innerHTML;
                this.originalDocumentWrite.call(document, modifiedContent);
            }.bind(this);
        },
        disable() {
            if (this.originalFetch) window.fetch = this.originalFetch;
            if (this.originalXhrOpen) XMLHttpRequest.prototype.open = this.originalXhrOpen;
            if (this.originalXhrSend) XMLHttpRequest.prototype.send = this.originalXhrSend;
            if (this.originalCreateElement) document.createElement = this.originalCreateElement;
            if (this.originalAppendChild) Node.prototype.appendChild = this.originalAppendChild;
            if (this.originalDocumentWrite) document.write = this.originalDocumentWrite;
            if (this.originalEval) unsafeWindow.eval = this.originalEval;
            if (this.originalFunction) unsafeWindow.Function = this.originalFunction;
        },
        check(element) {
            if (!currentConfig.modules.interceptThirdPartyResources || !Utils.isElement(element) || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;

            const resourceURL = Utils.getAbsoluteURL(element.src || element.getAttribute('data-src') || element.href || element.action);
            if (resourceURL) {
                const resourceHostname = Utils.getResourceHostname(resourceURL);
                const currentHost = Utils.getCurrentHostname();
                const contentIdentifier = Utils.getContentIdentifier(element);

                if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                    if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                        LogManager.add('interceptThirdPartyResources', element, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                        ResourceCanceller.cancelResourceLoading(element);
                        ProcessedElementsCache.markAsProcessed(element);
                        return true;
                    }
                }
            }
            return false;
        }
    };

    const CSPModule = {
        init() {},
        applyCSP() {
            if (!currentConfig.modules.manageCSP) return;
            const existingMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
            if (existingMeta) {
                existingMeta.remove();
            }
            const enabledRules = currentConfig.cspRules.filter(rule => rule.enabled);
            if (enabledRules.length === 0) {
                return;
            }
            const policyString = enabledRules.map(rule => rule.rule).join('; ');
            const meta = document.createElement('meta');
            meta.httpEquiv = "Content-Security-Policy";
            meta.content = policyString;
            if (document.head) {
                document.head.appendChild(meta);
            } else {
                document.documentElement.prepend(meta);
            }
        },
        removeCSP() {
            const existingMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
            if (existingMeta) {
                existingMeta.remove();
            }
        },
        updateRule(ruleId, enabled) {
            const rule = currentConfig.cspRules.find(r => r.id === ruleId);
            if (rule) {
                rule.enabled = enabled;
            }
        },
        showManagementPanel() {
            const ruleDescriptions = currentConfig.cspRules.map(r => ({
                id: r.id,
                name: r.name,
                description: this.getRuleDescription(r),
                enabled: r.enabled
            }));

            const rulesDisplay = ruleDescriptions
                .map(r => `${r.id}. ${r.name} (${r.enabled ? '✅启用' : '❌禁用'})`)
                .join('\n');

            const promptMessage = `CSP策略管理:\n` +
                `状态: ${currentConfig.modules.manageCSP ? '启用' : '禁用'}\n\n` +
                `可用规则:\n${rulesDisplay}\n\n` +
                `操作指令:\n` +
                `enable - 启用CSP\n` +
                `disable - 禁用CSP\n` +
                `1on - 启用规则1\n` +
                `23off - 禁用规则2和3\n` +
                `allon - 启用所有规则\n` +
                `alloff - 禁用所有规则\n\n` +
                `请输入指令:`;

            let input = prompt(promptMessage, "");
            if (input === null) return;
            input = input.trim().toLowerCase();
            let needsReload = false;

            if (input === 'enable') {
                currentConfig.modules.manageCSP = true;
                this.applyCSP();
                needsReload = true;
            } else if (input === 'disable') {
                currentConfig.modules.manageCSP = false;
                this.removeCSP();
                needsReload = true;
            } else if (input === 'allon') {
                currentConfig.cspRules.forEach(rule => rule.enabled = true);
                if (currentConfig.modules.manageCSP) this.applyCSP();
                needsReload = true;
            } else if (input === 'alloff') {
                currentConfig.cspRules.forEach(rule => rule.enabled = false);
                if (currentConfig.modules.manageCSP) this.removeCSP();
                needsReload = true;
            } else if (/^(\d+|all)(\d*on|off)$/.test(input)) {
                const match = input.match(/^(\d+|all)(\d*on|off)$/);
                if (match) {
                    const ruleSpec = match[1];
                    const action = match[2];
                    const enable = action.endsWith('on');
                    let ruleIdsToModify = [];
                    if (ruleSpec === 'all') {
                        ruleIdsToModify = currentConfig.cspRules.map(r => r.id);
                    } else {
                        for (const char of ruleSpec) {
                            const id = parseInt(char, 10);
                            if (!isNaN(id)) ruleIdsToModify.push(id);
                        }
                    }
                    let modified = false;
                    ruleIdsToModify.forEach(id => {
                        const rule = currentConfig.cspRules.find(r => r.id === id);
                        if (rule && rule.enabled !== enable) {
                            rule.enabled = enable;
                            modified = true;
                        }
                    });
                    if (modified) {
                        if (currentConfig.modules.manageCSP) this.applyCSP();
                        needsReload = true;
                    } else {
                        alert("未找到指定规则。");
                    }
                } else {
                    alert("无效格式。");
                }
            } else {
                alert("无效指令。");
            }

            if (needsReload) {
                StorageManager.saveConfig();
                location.reload();
            }
        },
        getRuleDescription(rule) {
            const descriptions = {
                1: "只允许本网站脚本",
                2: "禁止页面内脚本",
                3: "禁止eval执行代码",
                4: "只允许本网站样式",
                5: "禁止页面内样式",
                6: "只允许本网站图片",
                7: "禁止框架加载",
                8: "禁止媒体加载",
                9: "阻止对象嵌入"
            };
            return descriptions[rule.id] || "无描述";
        }
    };

    const UIController = {
        mutationObserver: null,
        batchProcessingQueue: [],
        batchSize: 15,
        isProcessingBatch: false,
        lastProcessTime: 0,
        init() {
            StorageManager.loadConfig();
            this.applyInitialModuleStates();
            this.registerMenuCommands();
            this.applyModuleSettings();
            this.setupObservers();
        },
        applyInitialModuleStates() {
            Object.keys(DEFAULT_MODULE_STATE).forEach(key => {
                if (currentConfig.modules[key] === undefined) {
                    currentConfig.modules[key] = DEFAULT_MODULE_STATE[key];
                }
            });
            if (currentConfig.modules.manageCSP === undefined) {
                currentConfig.modules.manageCSP = false;
            }
            if (currentConfig.modules.interceptSubdomains === undefined) {
                currentConfig.modules.interceptSubdomains = false;
            }
            if (currentConfig.modules.smartInterception === undefined) {
                currentConfig.modules.smartInterception = false;
            }
        },
        registerMenuCommands() {
            GM_registerMenuCommand(`🔘 广告拦截 [${this.isAnyModuleEnabled() ? '✅' : '❌'}]`, () => this.toggleAllModules());
            const moduleOrder = ['smartInterception', 'removeInlineScripts', 'removeExternalScripts', 'interceptThirdPartyResources', 'interceptSubdomains', 'manageCSP'];
            moduleOrder.forEach(key => {
                GM_registerMenuCommand(
                    `${MODULE_NAMES[key]} [${currentConfig.modules[key] ? '✅' : '❌'}]`,
                    () => this.toggleModule(key)
                );
            });
            GM_registerMenuCommand('📜 查看拦截日志', () => LogManager.showInAlert());
            GM_registerMenuCommand('🚫 清空当前域名白名单', () => {
                const currentDomain = Utils.getCurrentHostname();
                if (confirm(`确定清空当前域名 (${currentDomain}) 的白名单吗?`)) {
                    Whitelisting.clearDomainWhitelist(currentDomain);
                    StorageManager.saveConfig();
                    alert("当前域名的白名单已清空。页面将刷新。");
                    location.reload();
                }
            });
            GM_registerMenuCommand('🛡️ CSP策略管理', () => CSPModule.showManagementPanel());
            GM_registerMenuCommand('🔄 重置所有设置', () => this.resetSettings());
        },
        isAnyModuleEnabled() {
            return Object.keys(MODULE_NAMES).some(key => currentConfig.modules[key]);
        },
        toggleAllModules() {
            const newState = !this.isAnyModuleEnabled();
            Object.keys(MODULE_NAMES).forEach(key => {
                currentConfig.modules[key] = newState;
            });
            this.applyModuleSettings();
            StorageManager.saveConfig();
            location.reload();
        },
        toggleModule(key) {
            if (key === 'interceptSubdomains') {
                if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.interceptSubdomains) {
                    if (confirm('开启子域名拦截需要同时开启第三方资源拦截,是否同时开启?')) {
                        currentConfig.modules.interceptThirdPartyResources = true;
                        currentConfig.modules.interceptSubdomains = true;
                    } else {
                        return;
                    }
                } else {
                    currentConfig.modules[key] = !currentConfig.modules[key];
                }
            } else if (key === 'interceptThirdPartyResources') {
                const newState = !currentConfig.modules.interceptThirdPartyResources;
                currentConfig.modules.interceptThirdPartyResources = newState;
                if (!newState && currentConfig.modules.interceptSubdomains) {
                    currentConfig.modules.interceptSubdomains = false;
                }
            } else if (key === 'smartInterception') {
                currentConfig.modules[key] = !currentConfig.modules[key];
            } else {
                currentConfig.modules[key] = !currentConfig.modules[key];
            }
            
            this.applyModuleSettings();
            StorageManager.saveConfig();
            location.reload();
        },
        applyModuleSettings() {
            SmartInterceptionModule.init();
            RemoveInlineScriptsModule.init();
            RemoveExternalScriptsModule.init();
            ThirdPartyModule.init();
            CSPModule.init();
        },
        setupObservers() {
            const relevantModulesEnabled = Object.keys(MODULE_NAMES).some(key =>
                currentConfig.modules[key] && (
                    key === 'removeInlineScripts' ||
                    key === 'removeExternalScripts' ||
                    key === 'interceptThirdPartyResources' ||
                    key === 'smartInterception'
                )
            );
            if (!relevantModulesEnabled) return;

            this.mutationObserver = new MutationObserver(Utils.throttle((mutations) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
                            this.addToBatchProcessingQueue(node);
                        }
                    }
                }
            }, 100));
            
            this.mutationObserver.observe(document.documentElement, {
                childList: true,
                subtree: true,
            });

            this.processExistingElementsBatch();
        },
        addToBatchProcessingQueue(element) {
            this.batchProcessingQueue.push(element);
            if (!this.isProcessingBatch) {
                this.processBatch();
            }
        },
        processBatch() {
            this.isProcessingBatch = true;
            
            const processChunk = () => {
                const now = Date.now();
                if (now - this.lastProcessTime < 16) {
                    requestAnimationFrame(processChunk);
                    return;
                }
                
                const chunk = this.batchProcessingQueue.splice(0, this.batchSize);
                this.lastProcessTime = now;
                
                chunk.forEach(node => {
                    if (ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) return;
                    
                    if (node.tagName === 'SCRIPT') {
                        if (currentConfig.modules.removeInlineScripts && !node.src) {
                            if (RemoveInlineScriptsModule.check(node)) node.remove();
                        } else if (currentConfig.modules.removeExternalScripts && node.src) {
                            if (RemoveExternalScriptsModule.check(node)) ResourceCanceller.cancelResourceLoading(node);
                        } else if (currentConfig.modules.interceptThirdPartyResources) {
                            if (ThirdPartyModule.check(node)) ResourceCanceller.cancelResourceLoading(node);
                        }
                    } else if (currentConfig.modules.interceptThirdPartyResources) {
                        if (ThirdPartyModule.check(node)) ResourceCanceller.cancelResourceLoading(node);
                    }
                    
                    if (currentConfig.modules.smartInterception) {
                        SmartInterceptionModule.check(node);
                    }
                });
                
                if (this.batchProcessingQueue.length > 0) {
                    requestAnimationFrame(processChunk);
                } else {
                    this.isProcessingBatch = false;
                }
            };
            
            requestAnimationFrame(processChunk);
        },
        processExistingElementsBatch() {
            // 移除了 svg, canvas 从选择器中
            const selector = 'script, iframe, img, a[href], link[rel="stylesheet"], form, video, audio, source, img[data-src], style, link[rel="preload"], link[rel="prefetch"]';
            const elementsToProcess = Array.from(document.querySelectorAll(selector));
            
            const processInChunks = () => {
                const chunk = elementsToProcess.splice(0, this.batchSize * 2);
                
                chunk.forEach(element => {
                    if (!ProcessedElementsCache.isProcessed(element) && !Utils.isParentProcessed(element)) {
                        this.addToBatchProcessingQueue(element);
                    }
                });
                
                if (elementsToProcess.length > 0) {
                    setTimeout(processInChunks, 0);
                }
            };
            
            processInChunks();
        },
        resetSettings() {
            if (confirm("确定要重置所有设置吗?这将清除所有配置和白名单。")) {
                Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
                currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
                Whitelisting.clearAllWhitelists();
                ProcessedElementsCache.clear();
                CSPModule.removeCSP();
                StorageManager.saveConfig();
                location.reload();
            }
        }
    };

    function initializeAdBlocker() {
        UIController.init();
        if (currentConfig.modules.manageCSP) {
            CSPModule.applyCSP();
        }
    }

    if (document.readyState === 'loading') {
        initializeAdBlocker();
    } else {
        setTimeout(initializeAdBlocker, 0);
    }

})();