小说漫画网页广告拦截器

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

安裝腳本?
作者推薦腳本

您可能也會喜歡 优雅的广告拦截脚本.轻量版

安裝腳本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         小说漫画网页广告拦截器
// @namespace    http://tampermonkey.net/
// @version      4.1.1
// @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 = {
        specialUA: true,
        removeInlineScripts: false,
        removeExternalScripts: false,
        interceptThirdPartyResources: false,
        manageCSP: false,
    };
    const MODULE_NAMES = {
        specialUA: '添加特殊UA',
        removeInlineScripts: '移除内嵌脚本',
        removeExternalScripts: '移除外联脚本',
        interceptThirdPartyResources: '拦截第三方资源',
        manageCSP: 'CSP策略管理',
    };
    const DEFAULT_CSP_RULES_TEMPLATE = [
        { id: 1, name: '仅允许同源脚本', rule: "script-src 'self'", enabled: true },
        { 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: true },
        { 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 = `customAdBlockerConfig_${location.hostname}`;
    let currentConfig = {
        modules: { ...DEFAULT_MODULE_STATE },
        cspRules: DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })),
        whitelist: {},
    };
    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 '';
            return Utils.truncateString(scriptElement.textContent.replace(/\s+/g, ' '), 100);
        },
        getIframeSrcPreview(iframeElement) {
            if (!iframeElement || iframeElement.tagName !== 'IFRAME') return '';
            return Utils.truncateString(iframeElement.src, 100);
        },
        getResourceHostname(url) {
            if (!url || typeof url !== 'string') return null;
            if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank')) return null;
            try {
                return new URL(url, location.href).hostname;
            } catch (e) {
                return null;
            }
        },
        getDomain(hostname) {
            if (!hostname) return null;
            const parts = hostname.split('.');
            if (parts.length <= 2) {
                return hostname;
            }
            return parts.slice(-2).join('.');
        },
        isThirdPartyHost(resourceHostname, currentHost) {
            if (!resourceHostname) return false;
            const currentHostDomain = this.getDomain(currentHost);
            const resourceHostDomain = this.getDomain(resourceHostname);
            if (!currentHostDomain || !resourceHostDomain) return false;
            return !(resourceHostDomain === currentHostDomain);
        },
        getAbsoluteURL(url) {
            if (!url) return '';
            try {
                return new URL(url, location.href).href;
            } catch (e) {
                return url;
            }
        }
    };
    const LogManager = {
        logs: [],
        maxLogs: 100,
        logEntryData: {},
        loggedContentIdentifiers: new Set(),
        add(moduleKey, element, reason) {
            if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.removeInlineScripts && !currentConfig.modules.removeExternalScripts) {
                return;
            }
            if (!Utils.isElement(element) && element !== null) return;
            const currentDomain = Utils.getCurrentHostname();
            if (Whitelisting.isElementWhitelisted(element) || Whitelisting.isReasonWhitelisted(reason)) {
                return;
            }
            let elementIdentifier = '[未知元素]';
            let interceptedContent = '[无法获取内容]';
            let contentIdentifier = '';
            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}`;
                if (tagName === 'SCRIPT') {
                    if (element.src) {
                        interceptedContent = `SRC: ${Utils.truncateString(element.src, 100)}`;
                        contentIdentifier = `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}`;
                    } else {
                        interceptedContent = Utils.getScriptContentPreview(element);
                        contentIdentifier = `SCRIPT_CONTENT: ${Utils.truncateString(element.textContent.replace(/\s+/g, ' '), 100)}`;
                    }
                } else if (tagName === 'IFRAME') {
                    interceptedContent = Utils.getIframeSrcPreview(element);
                    contentIdentifier = `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`;
                } else if (tagName === 'IMG') {
                    const src = element.src || element.dataset.src;
                    interceptedContent = Utils.truncateString(src || '', 100);
                    if (src) {
                        contentIdentifier = `IMG_SRC: ${Utils.truncateString(src, 100)}`;
                    }
                } else if (tagName === 'A') {
                    interceptedContent = Utils.truncateString(element.href || '', 100);
                    if (element.href) {
                        contentIdentifier = `A_HREF: ${Utils.truncateString(element.href, 100)}`;
                    }
                } else {
                    interceptedContent = Utils.truncateString(element.outerHTML, 100);
                }
            } else if (reason && typeof reason.detail === 'string') {
                interceptedContent = Utils.truncateString(reason.detail, 100);
                if (reason.type) {
                    elementIdentifier = `[${reason.type}]`;
                }
                if (reason.detail.startsWith('SRC:')) {
                    contentIdentifier = `${reason.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reason.detail.substring(4).trim(), 100)}`;
                } else if (reason.detail.startsWith('URL:')) {
                    contentIdentifier = `${reason.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reason.detail.substring(5).trim(), 100)}`;
                } else {
                    contentIdentifier = `LOG_DETAIL: ${Utils.truncateString(reason.detail, 100)}`;
                }
            }
            if (!contentIdentifier) return;
            if (this.loggedContentIdentifiers.has(contentIdentifier)) {
                return;
            }
            const logId = this.logs.length + 1;
            this.logs.push({
                id: logId,
                module: MODULE_NAMES[moduleKey] || moduleKey,
                element: elementIdentifier,
                content: interceptedContent,
            });
            this.logEntryData[logId] = {
                contentIdentifier: contentIdentifier,
                element: element,
                reason: reason,
            };
            this.loggedContentIdentifiers.add(contentIdentifier);
            if (this.logs.length > this.maxLogs) {
                const removedLogEntry = this.logs.shift();
                delete this.logEntryData[removedLogEntry.id];
                const removedContentIdentifier = (() => {
                    if (removedLogEntry.content.startsWith('SRC: ')) return `SCRIPT_SRC: ${removedLogEntry.content.substring(5)}`;
                    if (removedLogEntry.content.startsWith('内容: ')) return `SCRIPT_CONTENT: ${removedLogEntry.content.substring(4)}`;
                    if (removedLogEntry.content.startsWith('URL: ')) return `FETCH_URL: ${removedLogEntry.content.substring(5)}`;
                    return removedLogEntry.content;
                })();
                this.loggedContentIdentifiers.delete(removedContentIdentifier);
            }
        },
        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-3, 1,3,5)将对应内容加入当前域名 (${currentDomain}) 白名单。\n` +
                `输入 0 清空当前域名 (${currentDomain}) 的所有白名单条目。\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 parts = input.replace(/,/g, ',').split(/[\s,]+/);
                for (const part of parts) {
                    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);
                        }
                    }
                }
                let addedCount = 0;
                indicesToWhitelist.forEach(index => {
                    const logEntryInfo = this.logEntryData[index];
                    if (logEntryInfo && logEntryInfo.contentIdentifier) {
                        const { contentIdentifier, element, reason } = logEntryInfo;
                        if (!Whitelisting.isContentWhitelisted(currentDomain, contentIdentifier)) {
                            Whitelisting.add(currentDomain, contentIdentifier);
                            addedCount++;
                        }
                    }
                });
                if (addedCount > 0) {
                    StorageManager.saveConfig();
                    alert(`${addedCount} 项内容已添加到当前域名 (${currentDomain}) 的白名单。页面将刷新。`);
                    location.reload();
                } else if (input.length > 0) {
                    alert("未找到匹配的日志序号或内容已在白名单中。");
                }
            }
        }
    };
    const Whitelisting = {
        isElementWhitelisted(element) {
            if (!element || !Utils.isElement(element)) return false;
            const currentDomain = Utils.getCurrentHostname();
            const elementContent = (() => {
                if (element.tagName === 'SCRIPT' && !element.src) return `SCRIPT_CONTENT: ${Utils.getScriptContentPreview(element)}`;
                if (element.tagName === 'SCRIPT' && element.src) return `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}`;
                if (element.tagName === 'IFRAME' && element.src) return `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`;
                if (element.tagName === 'IMG' && (element.src || element.dataset.src)) return `IMG_SRC: ${Utils.truncateString(element.src || element.dataset.src, 100)}`;
                if (element.tagName === 'A' && element.href) return `A_HREF: ${Utils.truncateString(element.href, 100)}`;
                return null;
            })();
            if (elementContent && currentDomain && currentConfig.whitelist[currentDomain]) {
                return currentConfig.whitelist[currentDomain].has(elementContent);
            }
            return false;
        },
        isReasonWhitelisted(reason) {
            if (!reason || !reason.detail || typeof reason.detail !== 'string') return false;
            const currentDomain = Utils.getCurrentHostname();
            const identifier = (() => {
                if (reason.detail.startsWith('SRC:')) {
                    return `${reason.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reason.detail.substring(4).trim(), 100)}`;
                } else if (reason.detail.startsWith('URL:')) {
                    return `${reason.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reason.detail.substring(5).trim(), 100)}`;
                }
                return null;
            })();
            if (identifier && currentDomain && currentConfig.whitelist[currentDomain]) {
                return currentConfig.whitelist[currentDomain].has(identifier);
            }
            return false;
        },
        isContentWhitelisted(domain, content) {
            if (!domain || !content) return false;
            if (!currentConfig.whitelist[domain]) return false;
            return currentConfig.whitelist[domain].has(content);
        },
        add(domain, content) {
            if (!domain || !content || content.trim() === '') return;
            if (!currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain] = new Set();
            }
            currentConfig.whitelist[domain].add(content);
        },
        clearDomainWhitelist(domain) {
            if (currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain].clear();
            }
        },
        clearAllWhitelists() {
            currentConfig.whitelist = {};
        }
    };
    const StorageManager = {
        loadConfig() {
            try {
                const savedConfig = JSON.parse(GM_getValue(CONFIG_STORAGE_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]);
                            } else {
                                currentConfig.whitelist[domain] = new Set();
                            }
                        }
                    }
                }
            } catch (e) {
                console.error("AdBlocker: Error loading config, using defaults:", e);
                Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
                currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
                currentConfig.whitelist = {};
            }
            if (currentConfig.modules.specialUA === undefined) {
                currentConfig.modules.specialUA = true;
            }
            if (currentConfig.modules.removeInlineScripts === undefined) {
                currentConfig.modules.removeInlineScripts = false;
            }
            if (currentConfig.modules.removeExternalScripts === undefined) {
                currentConfig.modules.removeExternalScripts = false;
            }
            if (currentConfig.modules.interceptThirdPartyResources === undefined) {
                currentConfig.modules.interceptThirdPartyResources = false;
            }
            if (currentConfig.modules.manageCSP === undefined) {
                currentConfig.modules.manageCSP = false;
            }
        },
        saveConfig() {
            try {
                const whitelistForStorage = {};
                for (const domain in currentConfig.whitelist) {
                    whitelistForStorage[domain] = Array.from(currentConfig.whitelist[domain]);
                }
                GM_setValue(CONFIG_STORAGE_KEY, JSON.stringify({
                    modules: currentConfig.modules,
                    cspRules: currentConfig.cspRules,
                    whitelist: whitelistForStorage
                }));
            } catch (e) {
                console.error("AdBlocker: Error saving config:", e);
            }
        }
    };
    const SpecialUAModule = {
        originalNavigator: window.navigator,
        fakeUA: 'UCWEB/2.0 Mozilla/5.0 (SymbianOS/9.3; Series60/3.2 NokiaE5-00.2/071.003; Profile/MIDP-2.1 Configuration/CLDC-1.1) NokiaBrowser/7.3.1.26 Mobile Safari/533.4 3gpp-gba AppleWebCore/2.0 Device/Nokia-E5-00.2 NetType/2G CPU/ARM9 Screen/320x240',
        fakePlatform: 'Symbian OS',
        fakeAppVersion: 'UCWEB/2.0',
        navigatorProxy: null,
        originalDescriptor: null,
        init() {
            if (currentConfig.modules.specialUA) {
                this.enable();
            } else {
                this.disable();
            }
        },
        enable() {
            if (!this.navigatorProxy) {
                this.navigatorProxy = new Proxy(this.originalNavigator, {
                    get: (target, prop) => {
                        if (prop === 'userAgent') return this.fakeUA;
                        if (prop === 'platform') return this.fakePlatform;
                        if (prop === 'appVersion') return this.fakeAppVersion;
                        return Reflect.get(target, prop);
                    }
                });
            }
            if (window.navigator !== this.navigatorProxy) {
                try {
                    this.originalDescriptor = Object.getOwnPropertyDescriptor(window, 'navigator');
                    Object.defineProperty(window, 'navigator', {
                        value: this.navigatorProxy,
                        configurable: true,
                        writable: this.originalDescriptor ? this.originalDescriptor.writable : false
                    });
                } catch (e) {
                    console.warn("AdBlocker: Failed to override navigator for special UA.");
                }
            }
        },
        disable() {
            if (this.navigatorProxy && window.navigator === this.navigatorProxy) {
                try {
                    if (this.originalDescriptor) {
                        Object.defineProperty(window, 'navigator', this.originalDescriptor);
                    } else {
                        Object.defineProperty(window, 'navigator', {
                            value: this.originalNavigator,
                            configurable: true,
                            writable: false
                        });
                    }
                } catch (e) {
                    console.warn("AdBlocker: Failed to restore original navigator.");
                }
            }
        },
        check(element) {
            return false;
        }
    };
    const RemoveInlineScriptsModule = {
        mutationObserver: null,
        init() {
            if (currentConfig.modules.removeInlineScripts) {
                this.enable();
            } else {
                this.disable();
            }
        },
        enable() {
            this.mutationObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && !node.src) {
                            if (node.dataset.adblockProcessed === 'true') return;
                            const elementContent = Utils.getScriptContentPreview(node);
                            const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`;
                            if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                                LogManager.add('removeInlineScripts', node, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` });
                                node.dataset.adblockProcessed = 'true';
                                node.remove();
                            }
                        }
                    });
                });
            });
            this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
            document.querySelectorAll('script:not([src])').forEach(script => {
                if (script.dataset.adblockProcessed === 'true') return;
                const elementContent = Utils.getScriptContentPreview(script);
                const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`;
                if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeInlineScripts', script, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` });
                    script.dataset.adblockProcessed = 'true';
                    script.remove();
                }
            });
        },
        disable() {
            if (this.mutationObserver) {
                this.mutationObserver.disconnect();
                this.mutationObserver = null;
            }
        },
        check(element) {
            if (!currentConfig.modules.removeInlineScripts) return false;
            if (element.dataset.adblockProcessed === 'true') return false;
            if (element.tagName === 'SCRIPT' && !element.src) {
                const elementContent = Utils.getScriptContentPreview(element);
                const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`;
                if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeInlineScripts', element, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` });
                    element.dataset.adblockProcessed = 'true';
                    return true;
                }
            }
            return false;
        }
    };
    const RemoveExternalScriptsModule = {
        mutationObserver: null,
        init() {
            if (currentConfig.modules.removeExternalScripts) {
                this.enable();
            } else {
                this.disable();
            }
        },
        enable() {
            this.mutationObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && node.src) {
                            if (node.dataset.adblockProcessed === 'true') return;
                            const src = Utils.truncateString(node.src, 100);
                            const contentIdentifier = `SCRIPT_SRC: ${src}`;
                            if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                                LogManager.add('removeExternalScripts', node, { type: '外联脚本移除', detail: `SRC: ${src}` });
                                node.dataset.adblockProcessed = 'true';
                                node.remove();
                            }
                        }
                    });
                });
            });
            this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
            document.querySelectorAll('script[src]').forEach(script => {
                if (script.dataset.adblockProcessed === 'true') return;
                const src = Utils.truncateString(script.src, 100);
                const contentIdentifier = `SCRIPT_SRC: ${src}`;
                if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeExternalScripts', script, { type: '外联脚本移除', detail: `SRC: ${src}` });
                    script.dataset.adblockProcessed = 'true';
                    script.remove();
                }
            });
        },
        disable() {
            if (this.mutationObserver) {
                this.mutationObserver.disconnect();
                this.mutationObserver = null;
            }
        },
        check(element) {
            if (!currentConfig.modules.removeExternalScripts) return false;
            if (element.dataset.adblockProcessed === 'true') return false;
            if (element.tagName === 'SCRIPT' && element.src) {
                const src = Utils.truncateString(element.src, 100);
                const contentIdentifier = `SCRIPT_SRC: ${src}`;
                if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
                    LogManager.add('removeExternalScripts', element, { type: '外联脚本移除', detail: `SRC: ${src}` });
                    element.dataset.adblockProcessed = 'true';
                    return true;
                }
            }
            return false;
        }
    };
    const ThirdPartyModule = {
        originalFetch: null,
        originalXhrOpen: null,
        originalXhrSend: null,
        originalCreateElement: null,
        originalAppendChild: null,
        originalDocumentWrite: null,
        overrideEval: false,
        overrideFunction: false,
        init() {
            if (currentConfig.modules.interceptThirdPartyResources) {
                this.enable();
            } else {
                this.disable();
            }
        },
        enable() {
            this.overrideEval = !currentConfig.cspRules.find(rule => rule.id === 3)?.enabled;
            this.overrideFunction = !currentConfig.cspRules.find(rule => rule.id === 3)?.enabled;
            this.originalFetch = window.fetch;
            window.fetch = (input, init) => {
                if (!currentConfig.modules.interceptThirdPartyResources) {
                    return this.originalFetch.apply(this, arguments);
                }
                const url = typeof input === 'string' ? input : input.url;
                const resourceURL = Utils.getAbsoluteURL(url);
                const resourceHostname = Utils.getResourceHostname(resourceURL);
                const currentHost = Utils.getCurrentHostname();
                const contentIdentifier = `FETCH_URL: ${Utils.truncateString(resourceURL, 100)}`;
                if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                    if (!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.apply(this, arguments);
            };
            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 = `XHR_URL: ${Utils.truncateString(resourceURL, 100)}`;
                if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                    if (!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 || element.dataset.adblockProcessed === 'true') {
                    return element;
                }
                const processElement = (el) => {
                    const src = el.src || el.getAttribute('data-src') || el.href || el.action || '';
                    if (src) {
                        const resourceURL = Utils.getAbsoluteURL(src);
                        const resourceHostname = Utils.getResourceHostname(resourceURL);
                        const currentHost = Utils.getCurrentHostname();
                        if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                            const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                                LogManager.add('interceptThirdPartyResources', el, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                                el.dataset.adblockProcessed = 'true';
                                el.remove();
                                return true;
                            }
                        }
                    }
                    return false;
                };
                if (['SCRIPT', 'IFRAME', 'IMG', 'A', 'LINK', 'FORM', 'VIDEO', 'AUDIO', 'SOURCE'].includes(tagName.toUpperCase())) {
                    if (processElement(element)) return element;
                }
                return element;
            };
            this.originalAppendChild = Node.prototype.appendChild;
            Node.prototype.appendChild = function(node) {
                if (!currentConfig.modules.interceptThirdPartyResources || node.dataset.adblockProcessed === 'true') {
                    return this.originalAppendChild.call(this, node);
                }
                if (node.nodeType === Node.ELEMENT_NODE) {
                    const tagName = node.tagName;
                    const src = node.src || node.getAttribute('data-src') || node.href || node.action || '';
                    if (src) {
                        const resourceURL = Utils.getAbsoluteURL(src);
                        const resourceHostname = Utils.getResourceHostname(resourceURL);
                        const currentHost = Utils.getCurrentHostname();
                        if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                            const contentIdentifier = `${tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                                LogManager.add('interceptThirdPartyResources', node, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                                node.dataset.adblockProcessed = 'true';
                                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]');
                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();
                        if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                            const contentIdentifier = `DOC_WRITE_SCRIPT_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                                LogManager.add('interceptThirdPartyResources', null, { type: '第三方脚本通过document.write拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                                script.remove();
                                return;
                            }
                        }
                    }
                }
                if (tempDiv.innerHTML !== content) {
                    content = tempDiv.innerHTML;
                }
                this.originalDocumentWrite.call(document, content);
            }.bind(this);
            this.overrideEvalAndFunction();
        },
        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;
            this.restoreEvalAndFunction();
        },
        overrideEvalAndFunction() {
            if (this.overrideEval) {
                const originalEval = unsafeWindow.eval;
                unsafeWindow.eval = function(code) {
                    const contentIdentifier = `EVAL_CODE: ${Utils.truncateString(code.replace(/\s+/g, ' '), 100)}`;
                    const resourceHostname = Utils.getResourceHostname(location.href);
                    const currentHost = Utils.getCurrentHostname();
                    if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                        if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                            LogManager.add('interceptThirdPartyResources', null, { type: 'eval拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
                            return undefined;
                        }
                    }
                    return originalEval.call(unsafeWindow, code);
                };
            }
            if (this.overrideFunction) {
                const originalFunction = unsafeWindow.Function;
                unsafeWindow.Function = function(...args) {
                    const code = args.length > 0 ? args[args.length - 1] : '';
                    const contentIdentifier = `FUNCTION_CODE: ${Utils.truncateString(code.replace(/\s+/g, ' '), 100)}`;
                    const resourceHostname = Utils.getResourceHostname(location.href);
                    const currentHost = Utils.getCurrentHostname();
                    if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                        if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                            LogManager.add('interceptThirdPartyResources', null, { type: 'Function构造拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
                            return () => {};
                        }
                    }
                    return originalFunction.apply(unsafeWindow, args);
                };
            }
        },
        restoreEvalAndFunction() {
            if (this.overrideEval) unsafeWindow.eval = unsafeWindow.originalEval;
            if (this.overrideFunction) unsafeWindow.Function = unsafeWindow.originalFunction;
        },
        check(element) {
            if (!currentConfig.modules.interceptThirdPartyResources || element.dataset.adblockProcessed === 'true') return false;
            const processElement = (el) => {
                const src = el.src || el.getAttribute('data-src') || el.href || el.action || '';
                if (src) {
                    const resourceURL = Utils.getAbsoluteURL(src);
                    const resourceHostname = Utils.getResourceHostname(resourceURL);
                    const currentHost = Utils.getCurrentHostname();
                    if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
                        const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                        if (!Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
                            LogManager.add('interceptThirdPartyResources', el, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
                            el.dataset.adblockProcessed = 'true';
                            return true;
                        }
                    }
                }
                return false;
            };
            if (['SCRIPT', 'IFRAME', 'IMG', 'A', 'LINK', 'FORM', 'VIDEO', 'AUDIO', 'SOURCE'].includes(element.tagName.toUpperCase())) {
                if (processElement(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 rulesDisplay = currentConfig.cspRules
                .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("未找到指定的CSP规则或规则状态已是目标状态。");
                    }
                } else {
                    alert("无效的CSP规则指令格式。");
                }
            } else {
                alert("无效指令。");
            }
            if (needsReload) {
                StorageManager.saveConfig();
                location.reload();
            }
        }
    };
    const UIController = {
        mutationObserver: null,
        throttledMutationHandler: null,
        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.specialUA === undefined) {
                currentConfig.modules.specialUA = true;
            }
            if (currentConfig.modules.manageCSP === undefined) {
                currentConfig.modules.manageCSP = false;
            }
        },
        registerMenuCommands() {
            GM_registerMenuCommand(`🔘 广告拦截 [${this.isAnyModuleEnabled() ? '✅' : '❌'}]`, () => this.toggleAllModules());
            Object.keys(MODULE_NAMES).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) {
            currentConfig.modules[key] = !currentConfig.modules[key];
            this.applyModuleSettings();
            StorageManager.saveConfig();
            location.reload();
        },
        applyModuleSettings() {
            SpecialUAModule.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'
                )
            );
            if (!relevantModulesEnabled) return;
            this.throttledMutationHandler = this.throttle(this.handleMutations, 50);
            this.mutationObserver = new MutationObserver(mutations => {
                this.throttledMutationHandler(mutations);
            });
            this.mutationObserver.observe(document.documentElement, {
                childList: true,
                subtree: true,
            });
            const processExistingElements = () => {
                if (!currentConfig.modules.removeInlineScripts && !currentConfig.modules.removeExternalScripts && !currentConfig.modules.interceptThirdPartyResources) return;
                const processNode = (node) => {
                    if (node.nodeType === Node.ELEMENT_NODE && node.dataset.adblockProcessed !== 'true') {
                        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)) {
                                    node.remove();
                                }
                            } else if (currentConfig.modules.interceptThirdPartyResources) {
                                if (ThirdPartyModule.check(node)) {
                                    node.remove();
                                }
                            }
                        } else if (currentConfig.modules.interceptThirdPartyResources) {
                            if (ThirdPartyModule.check(node)) {
                                node.remove();
                            }
                        }
                    }
                };
                const queryAndProcess = (selector) => {
                    document.querySelectorAll(selector).forEach(el => {
                        if (el.dataset.adblockProcessed !== 'true') {
                           if (el.tagName === 'SCRIPT') {
                                if (currentConfig.modules.removeInlineScripts && !el.src) {
                                    if (RemoveInlineScriptsModule.check(el)) {
                                        el.remove();
                                    }
                                } else if (currentConfig.modules.removeExternalScripts && el.src) {
                                    if (RemoveExternalScriptsModule.check(el)) {
                                        el.remove();
                                    }
                                } else if (currentConfig.modules.interceptThirdPartyResources) {
                                    if (ThirdPartyModule.check(el)) {
                                        el.remove();
                                    }
                                }
                            } else if (currentConfig.modules.interceptThirdPartyResources) {
                                if (ThirdPartyModule.check(el)) {
                                    el.remove();
                                }
                            }
                        }
                    });
                };
                if (currentConfig.modules.removeInlineScripts) queryAndProcess('script:not([src])');
                if (currentConfig.modules.removeExternalScripts) queryAndProcess('script[src]');
                if (currentConfig.modules.interceptThirdPartyResources) {
                    queryAndProcess('script[src], iframe[src], img[src], a[href], link[href], form[action], video[src], audio[src], source[src]');
                    document.querySelectorAll('img[data-src]').forEach(el => {
                        if (el.dataset.adblockProcessed !== 'true') {
                            if (ThirdPartyModule.check(el)) {
                                el.remove();
                            }
                        }
                    });
                }
            };
            processExistingElements();
        },
        handleMutations(mutations) {
            mutations.forEach(mutation => {
                if (mutation.addedNodes) {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.dataset.adblockProcessed !== 'true') {
                            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)) {
                                        node.remove();
                                    }
                                } else if (currentConfig.modules.interceptThirdPartyResources) {
                                    if (ThirdPartyModule.check(node)) {
                                        node.remove();
                                    }
                                }
                            } else if (currentConfig.modules.interceptThirdPartyResources) {
                                if (ThirdPartyModule.check(node)) {
                                    node.remove();
                                }
                            }
                        }
                    });
                }
            });
        },
        throttle(func, delay) {
            let inThrottle;
            return function(...args) {
                const context = this;
                if (!inThrottle) {
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, delay);
                    func.apply(context, args);
                }
            };
        },
        resetSettings() {
            if (confirm("确定要重置所有设置吗?这将清除所有配置和白名单。")) {
                Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
                currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
                Whitelisting.clearAllWhitelists();
                CSPModule.removeCSP();
                StorageManager.saveConfig();
                location.reload();
            }
        }
    };
    function initializeAdBlocker() {
        UIController.init();
        if (currentConfig.modules.manageCSP) {
            CSPModule.applyCSP();
        }
    }
    StorageManager.loadConfig();
    if (currentConfig.modules.manageCSP) {
        CSPModule.applyCSP();
    }
    initializeAdBlocker();
})();