小说漫画网页广告拦截器

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

安裝腳本?
作者推薦腳本

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

安裝腳本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         小说漫画网页广告拦截器
// @namespace    http://tampermonkey.net/
// @version      4.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 = {
        throttle(fn, delay) {
            let last = 0;
            return function(...args) {
                const now = Date.now();
                if (now - last >= delay) {
                    fn.apply(this, args);
                    last = now;
                }
            };
        },
        debounce(fn, delay) {
            let timer = null;
            return function(...args) {
                clearTimeout(timer);
                timer = setTimeout(() => fn.apply(this, args), delay);
            };
        },
        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);
        },
        isThirdPartyHost(url, currentHost) {
            if (!url || typeof url !== 'string') return false;
            if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank')) return false;
            try {
                const resourceHost = new URL(url, location.href).hostname;
                return !(resourceHost.endsWith(`.${currentHost}`) || resourceHost === currentHost);
            } catch (e) {
                return true;
            }
        },
        getResourceURL(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 removedLogId = this.logs[0].id;
                const removedLogEntry = this.logs.shift();
                delete this.logEntryData[removedLogId];
                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].includes(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].includes(identifier);
            }
            return false;
        },
        isContentWhitelisted(domain, content) {
            if (!domain || !content) return false;
            return currentConfig.whitelist[domain] && currentConfig.whitelist[domain].includes(content);
        },
        add(domain, content) {
            if (!domain || !content || content.trim() === '') return;
            if (!currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain] = [];
            }
            if (!currentConfig.whitelist[domain].includes(content)) {
                currentConfig.whitelist[domain].push(content);
            }
        },
        clearDomainWhitelist(domain) {
            if (currentConfig.whitelist[domain]) {
                currentConfig.whitelist[domain] = [];
            }
        },
        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 = savedConfig.whitelist;
                    }
                }
            } 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;
            }
        },
        saveConfig() {
            try {
                GM_setValue(CONFIG_STORAGE_KEY, JSON.stringify({
                    modules: currentConfig.modules,
                    cspRules: currentConfig.cspRules,
                    whitelist: currentConfig.whitelist
                }));
            } catch (e) {
                console.error("AdBlocker: Error saving config:", e);
            }
        }
    };
    const SpecialUAModule = {
        originalNavigator: window.navigator,
        fakeUA: 'NokiaE7-00/5.0 UCWEB/2.0 Mozilla/5.0 (Symbian/3; Series60/5.2; Windows Phone 10.0; Android 14; Microsoft; Lumia 950 XL Dual SIM; Java) Gecko/131 Firefox/131 SearchCraft/3.10.2 (Baidu; P1 13) baiduboxapp/4.3.0.10',
        fakePlatform: 'Win32',
        fakeAppVersion: 'NokiaE7-00/5.0 UCWEB/2.0 Mozilla/5.0 (Symbian/3; Series60/5.2; Windows Phone 10.0; Android 14; Microsoft; Lumia 950 XL Dual SIM; Java) Gecko/131 Firefox/131 SearchCraft/3.10.2 (Baidu; P1 13) baiduboxapp/4.3.0.10',
        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.getResourceURL(url);
                const contentIdentifier = `FETCH_URL: ${Utils.truncateString(resourceURL, 100)}`;
                if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                    if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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.getResourceURL(url);
                const contentIdentifier = `XHR_URL: ${Utils.truncateString(resourceURL, 100)}`;
                if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                    if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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.getResourceURL(src);
                        if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                            const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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.getResourceURL(src);
                        if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                            const contentIdentifier = `${tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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.getResourceURL(src);
                        if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                            const contentIdentifier = `DOC_WRITE_SCRIPT_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                            if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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)}`;
                    if (Utils.isThirdPartyHost(location.href, Utils.getCurrentHostname())) {
                        if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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)}`;
                    if (Utils.isThirdPartyHost(location.href, Utils.getCurrentHostname())) {
                        if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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.getResourceURL(src);
                    if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) {
                        const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`;
                        if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), 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();
                alert("CSP策略已启用。");
                needsReload = true;
            } else if (input === 'disable') {
                currentConfig.modules.manageCSP = false;
                this.removeCSP();
                alert("CSP策略已禁用。");
                needsReload = true;
            } else if (input === 'allon') {
                currentConfig.cspRules.forEach(rule => rule.enabled = true);
                if (currentConfig.modules.manageCSP) this.applyCSP();
                alert("所有CSP规则已启用。");
                needsReload = true;
            } else if (input === 'alloff') {
                currentConfig.cspRules.forEach(rule => rule.enabled = false);
                if (currentConfig.modules.manageCSP) this.removeCSP();
                alert("所有CSP规则已禁用。");
                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();
                        alert(`CSP规则 ${ruleIdsToModify.join(', ')} 已${enable ? '启用' : '禁用'}。`);
                        needsReload = true;
                    } else {
                        alert("未找到指定的CSP规则或规则状态已是目标状态。");
                    }
                } else {
                    alert("无效的CSP规则指令格式。");
                }
            } else {
                alert("无效指令。");
            }
            if (needsReload) {
                StorageManager.saveConfig();
                location.reload();
            }
        }
    };
    const UIController = {
        mutationObserver: 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;
            }
        },
        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();
            alert(`广告拦截已${newState ? '启用' : '禁用'}。页面将刷新。`);
            location.reload();
        },
        toggleModule(key) {
            currentConfig.modules[key] = !currentConfig.modules[key];
            this.applyModuleSettings();
            StorageManager.saveConfig();
            alert(`${MODULE_NAMES[key]} 已${currentConfig.modules[key] ? '启用' : '禁用'}。页面将刷新。`);
            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) {
                this.mutationObserver = new MutationObserver(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();
                                        }
                                    }
                                }
                            });
                        }
                    });
                });
                this.mutationObserver.observe(document.documentElement, {
                    childList: true,
                    subtree: true,
                });
            }
        },
        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();
                alert("所有设置已重置为默认值。页面将刷新。");
                location.reload();
            }
        }
    };
    function initializeAdBlocker() {
        UIController.init();
        if (currentConfig.modules.manageCSP) {
            CSPModule.applyCSP();
        }
    }
    StorageManager.loadConfig();
    if (currentConfig.modules.manageCSP) {
        CSPModule.applyCSP();
    }
    initializeAdBlocker();
})();