广告终结者(实验性版本)

更多修复和优化

// ==UserScript==
// @name         广告终结者(实验性版本)
// @namespace    http://tampermonkey.net/
// @version      3.0.5
// @description  更多修复和优化
// @author       DeepSeek
// @match        *://*/*
// @license      MIT
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const HOSTNAME = location.hostname.replace(/\./g, '\\.');
    const REGEX = {
        dynamicId: /^(?:[0-9a-f]{16,}|[\dA-F-]{20,})$/i,
        adAttributes: /(?:ad(?:s|vert|vertisement|box|frame|container|wrap|content|label)?|推广|广告|gg|sponsor|推荐|guanggao|syad|bfad|弹窗|悬浮|葡京|banner|pop(?:up|under)|track(?:ing)?)[_-]?/i,
        thirdParty: new RegExp(`^(https?:\\/\\/(.*\\.)?${HOSTNAME}(\\/|$)|data:|about:blank)`, 'i'),
        textAdKeywords: /限时(?:优惠|免费)|立即(?:下载|注册(不可用)|咨询)|微信(?:号|客服)?[::]?|vx[::]\w+|telegram[::]\w+|免广告|偷拍|黑料|点击下载/
    };

    const CONFIG = {
        modules: {
            main: false,
            dynamicSystem: false,
            layoutSystem: false,
            frameSystem: false,
            mediaSystem: false,
            textSystem: false,
            thirdPartyBlock: false,
            obfuscatedAdSystem: false,
            floating: false
        },
        protectionRules: {
            dynamicIdLength: 30,
            zIndexThreshold: 100,
            maxFrameDepth: 4,
            textAdKeywords: ['限时优惠', '立即下载', '微信', 'vx:', 'telegram', '偷拍', '黑料', '扫码关注', '点击下载']
        },
        csp: {
            enabled: false,
            rules: [
                { id: 1, name: '阻止外部脚本加载', rule: "script-src 'self'", enabled: true },
                { id: 2, name: '阻止内联脚本执行', rule: "script-src 'unsafe-inline'", enabled: false },
                { id: 3, name: '阻止动态脚本执行', 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 }
            ]
        },
        mobileOptimizations: {
            lazyLoadImages: true,
            removeImagePlaceholders: true
        },
        moduleNames: {
            dynamicSystem: '动态检测系统',
            layoutSystem: '布局检测系统',
            frameSystem: '框架过滤系统',
            mediaSystem: '媒体检测系统',
            textSystem: '文本广告检测',
            thirdPartyBlock: '第三方拦截',
            obfuscatedAdSystem: '混淆广告检测',
            floating: '浮动广告检测'
        },
        modulePriority: [
            'thirdPartyBlock',
            'frameSystem',
            'obfuscatedAdSystem',
            'dynamicSystem',
            'mediaSystem',
            'layoutSystem',
            'textSystem',
            'floating'
        ],
        keywordConfig: {
            useDefault: true,
            custom: [],
            blockThreshold: 2
        },
        performance: {
            highRiskModules: ['thirdPartyBlock', 'frameSystem', 'obfuscatedAdSystem'],
            visibleAreaPriority: true,
            styleCacheTimeout: 600000,
            mutationProcessLimit: 40,
            mutationProcessTimeLimit: 10,
            idleCallbackTimeout: 2500,
            throttleScrollDelay: 200,
            debounceMutationDelay: 300,
            longTaskThreshold: 500,
            degradeThreshold: 3
        },
        obfuscatedAdConfig: {
            useDefault: true,
            customKeywords: [],
            customRegex: [],
            blockThreshold: 2
        }
    };

    const FIRST_SCRIPT_DEFAULT_KEYWORDS = [
        '.substr(10)', '.substr(22)', 'htmlAds', 'ads_codes', '{return void 0!==b[a]?b[a]:a}).join("")}', '-${scripts[randomIndex]}', '/image/202${scripts[Math.random()', '"https://"+Date.parse(new Date())+', '"https://"+(new Date().getDate())+', 'new Function(t)()',
        'new Function(b)()', 'new Function(\'d\',e)', 'Math.floor(2147483648 * Math.random());', 'Math.floor(Math.random()*url.length)', '&&navigator[', '=navigator;', 'navigator.platform){setTimeout(function', 'disableDebugger', 'sojson.v', '<\\/\'+\'s\'+\'c\'+\'ri\'+\'pt\'+\'>\');', '</\'+\'s\'+\'c\'+\'ri\'+\'pt\'+\'>\');',
    ];

    const FIRST_SCRIPT_REGEX = {
        obfuscatedAds: {
            adUrl: /(?:\/[a-z0-9]{12,}\/|(?:adservice|tracking|promo|adsense|doubleclick)\.[a-z]{3,10}\.(?:com|net|org))(?:\/|$)(?:ads?|promo|banner|track|stat|click|log)/i,
            dynamicCode: /(?:eval|new\s+Function|Function\.constructor|(?:set(?:Timeout|Interval))\s*\(\s*)(?:['"`]|atob|decodeURIComponent|unescape|fromCharCode)/i,
            base64Data: /(?:data:.*?(?:ad|banner)|(?:[A-Za-z0-9+/]{4}){10,}(?:ad|banner)|data:image\/(?:png|jpeg|gif);base64,|%[0-9A-Fa-f]{2,})/i,
            inlineScript: /<script[^>]*>[\s\S]*?(?:ad|banner|popup|promotion|sponsor|[a-zA-Z0-9]{8})[\s\S]*?<\/script>/i,
            encodedString: /(?:base64|hex|url)\([^)]+\)/i,
            jsFramework: /(?:jQuery|React|Vue)\.min\.js/i
        }
    };

    const Logs = {
        logs: [],
        maxLogs: 30,
        add(moduleKey, element, reason) {
            const moduleName = CONFIG.moduleNames[moduleKey] || '未知模块';
            this.logs.push({
                module: moduleName,
                element: `${element.tagName}#${element.id || ''}.${element.className || ''}`,
                content: this.getContent(element),
                reason: reason || {},
                timestamp: new Date().toISOString()
            });
            if (this.logs.length > this.maxLogs) this.logs.shift();
        },
        getContent(element) {
            return element.outerHTML.slice(0, 100) + (element.outerHTML.length > 100 ? '...' : '');
        },
        clear() {
            this.logs = [];
            GM_notification('日志已清空');
        }
    };

    const perf = {
        pendingTasks: [],
        isProcessing: false,
        processed: new WeakSet(),
        adElements: new Set(),
        styleCache: new Map(),
        lastStyleCacheClear: Date.now(),
        longTaskCount: 0,
        degraded: false
    };

    const AdUtils = {
        safeRemove(element, moduleKey, reason) {
            if (!element?.parentNode) return false;
            try {
                Logs.add(moduleKey, element, reason);
                element.style.display = 'none';
                element.style.visibility = 'hidden';
                element.style.opacity = '0';
                element.setAttribute('data-ad-removed', 'true');
                this.removeEventListeners(element);
                return true;
            } catch (e) {
                console.warn('元素隐藏失败:', e);
                return false;
            }
        },

        removeEventListeners(element) {
            element.removeAttribute('onload');
            element.removeAttribute('onerror');
        }
    };

    const StyleManager = {
        styleElement: null,
        inject() {
            if (this.styleElement) return;
            this.styleElement = document.createElement('style');
            this.styleElement.id = 'ad-blocker-styles';
            let cssRules = [];
            cssRules.push(`
                [data-ad-removed] { 
                    display: none !important; 
                    visibility: hidden !important;
                    opacity: 0 !important;
                    position: absolute !important;
                    z-index: -9999 !important;
                    width: 0 !important;
                    height: 0 !important;
                }
                iframe[src*="ad"], .ad-container {
                    display: none !important;
                    height: 0 !important;
                    width: 0 !important;
                    opacity: 0 !important;
                }
            `);
            this.styleElement.textContent = cssRules.join('\n');
            document.head.appendChild(this.styleElement);
        },
        remove() {
            if (this.styleElement?.parentNode) {
                this.styleElement.parentNode.removeChild(this.styleElement);
                this.styleElement = null;
            }
        },
        toggle(enable) {
            enable ? this.inject() : this.remove();
        }
    };

    const CSPManager = {
        currentPolicy: null,
        generatePolicy() {
            return CONFIG.csp.rules
                .filter(rule => rule.enabled)
                .map(rule => rule.rule)
                .join('; ');
        },
        inject() {
            this.remove();
            if (!CONFIG.csp.enabled) return;

            const policy = this.generatePolicy();
            this.currentPolicy = policy;

            const meta = document.createElement('meta');
            meta.httpEquiv = "Content-Security-Policy";
            meta.content = policy;
            document.head.appendChild(meta);
        },
        remove() {
            const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
            if (meta) meta.remove();
        },
        toggleRule(ruleId, enable) {
            const rule = CONFIG.csp.rules.find(r => r.id === ruleId);
            if (rule) rule.enabled = enable;
        },
        injectInitialCSP() {
            if (!CONFIG.csp.enabled) return;

            const initialRules = CONFIG.csp.rules
                .filter(rule => [1, 6].includes(rule.id) && rule.enabled)
                .map(rule => rule.rule)
                .join('; ');

            if (initialRules) {
                const meta = document.createElement('meta');
                meta.httpEquiv = "Content-Security-Policy";
                meta.content = initialRules;
                document.head.appendChild(meta);
            }
        }
    };

    const Detector = {
        checkModule(moduleKey, element) {
            if (!CONFIG.modules.main || !CONFIG.modules[moduleKey]) return false;
            return this[`check${moduleKey.charAt(0).toUpperCase() + moduleKey.slice(1)}`](element);
        },

        checkThirdPartyBlock(el) {
            if (!CONFIG.modules.thirdPartyBlock) return false;
            
            if (el.tagName === 'SCRIPT') {
                const src = el.src || el.getAttribute('data-src') || '';
                if (!REGEX.thirdParty.test(src)) {
                    return FirstScriptAdUtils.safeRemove(
                        el, 
                        'thirdPartyBlock',
                        {
                            type: '第三方脚本拦截',
                            detail: `源: ${src.slice(0, 80)}`
                        }
                    );
                }
            }

            else if (el.tagName === 'IFRAME') {
                const src = el.src || el.getAttribute('data-src') || '';
                if (!REGEX.thirdParty.test(src)) {
                    return AdUtils.safeRemove(
                        el, 
                        'thirdPartyBlock',
                        {
                            type: '第三方框架拦截', 
                            detail: `源: ${src.slice(0, 80)}`
                        }
                    );
                }
            }
            return false;
        },

        checkFrameSystem(el) {
            if (!CONFIG.modules.frameSystem) return false;
            if (el.tagName === 'IFRAME') {
                let depth = 0, parent = el;
                while ((parent = parent.parentElement) && depth <= CONFIG.protectionRules.maxFrameDepth) {
                    if (parent.tagName === 'IFRAME') depth++;
                }
                if (depth > CONFIG.protectionRules.maxFrameDepth) {
                    return AdUtils.safeRemove(el, 'frameSystem', {
                        type: '深层嵌套框架',
                        detail: `嵌套层级: ${depth}`
                    });
                }
            }
            return false;
        },

        checkDynamicSystem(el) {
            if (!CONFIG.modules.dynamicSystem) return false;
            const attrs = el.getAttributeNames().map(name =>
                `${name}=${el.getAttribute(name)}`
            ).join(' ');
            if (REGEX.adAttributes.test(attrs)) {
                return AdUtils.safeRemove(el, 'dynamicSystem', {
                    type: '广告属性检测',
                    detail: `属性: ${attrs.slice(0, 100)}`
                });
            }
            if (el.id && (
                el.id.length > CONFIG.protectionRules.dynamicIdLength ||
                REGEX.dynamicId.test(el.id)
            )) {
                return AdUtils.safeRemove(el, 'dynamicSystem', {
                    type: '动态ID检测',
                    detail: `异常ID: ${el.id}`
                });
            }
            return false;
        },

        checkMediaSystem(el) {
            if (!CONFIG.modules.mediaSystem) return false;
            if (el.tagName === 'IMG' && (el.src.includes('ad') || el.src.match(/\.(gif|webp)(\?|$)/))) {
                return AdUtils.safeRemove(el, 'mediaSystem', {
                    type: '图片广告',
                    detail: `图片源: ${el.src.slice(0, 50)}`
                });
            }
            const style = this.getCachedStyle(el);
            const rect = el.getBoundingClientRect();
            if (['fixed', 'sticky'].includes(style.position)) {
                const isTopBanner = rect.top < 50;
                const isBottomBanner = rect.bottom > window.innerHeight - 50;
                if (isTopBanner || isBottomBanner) {
                    return AdUtils.safeRemove(el, 'mediaSystem', {
                        type: '浮动广告',
                        detail: `位置: ${rect.top}px`
                    });
                }
            }
            return false;
        },

        checkLayoutSystem(el) {
            if (!CONFIG.modules.layoutSystem) return false;
            const style = this.getCachedStyle(el);
            const zIndex = parseInt(style.zIndex);
            if (zIndex > CONFIG.protectionRules.zIndexThreshold) {
                return AdUtils.safeRemove(el, 'layoutSystem', {
                    type: '高堆叠元素',
                    detail: `z-index=${zIndex}`
                });
            }
            const rect = el.getBoundingClientRect();
            const isVisible = rect.width > 0 && rect.height > 0;
            if (!isVisible && el.children.length > 0) {
                return AdUtils.safeRemove(el, 'layoutSystem', {
                    type: '隐藏容器元素',
                    detail: '包含子元素的不可见容器'
                });
            }
            return false;
        },

        checkTextSystem(el) {
            if (!CONFIG.modules.textSystem) return false;
            const text = el.textContent?.toLowerCase() || '';
            const match = text.match(REGEX.textAdKeywords);
            if (match) {
                return AdUtils.safeRemove(el, 'textSystem', {
                    type: '文本广告',
                    detail: `触发规则: ${match}`,
                    keywords: [match]
                });
            }
            return false;
        },

        checkFloating(el) {
            if (!CONFIG.modules.floating) return false;
            const style = this.getCachedStyle(el);
            if (['fixed', 'sticky'].includes(style.position)) {
                return AdUtils.safeRemove(el, 'floating', {
                    type: '浮动元素',
                    detail: `定位方式: ${style.position}`
                });
            }
            return false;
        },

        getCachedStyle(el) {
            if (Date.now() - perf.lastStyleCacheClear > CONFIG.performance.styleCacheTimeout) {
                perf.styleCache.clear();
                perf.lastStyleCacheClear = Date.now();
            }
            const key = el.nodeName + Array.from(el.classList).join('');
            if (!perf.styleCache.has(key)) {
                try {
                    perf.styleCache.set(key, getComputedStyle(el));
                } catch (e) {
                    console.warn("Error getting computed style:", e);
                    return {};
                }
            }
            return perf.styleCache.get(key);
        },

        isVisible(el) {
            const rect = el.getBoundingClientRect();
            return (
                rect.top >= 0 &&
                rect.left >= 0 &&
                rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                rect.right <= (window.innerWidth || document.documentElement.clientWidth)
            );
        }
    };

    function throttle(func, limit) {
        let inThrottle;
        return function() {
            const args = arguments;
            const context = this;
            if (!inThrottle) {
                func.apply(context, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }

    function debounce(func, delay) {
        let timeout;
        return function() {
            const context = this;
            const args = arguments;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), delay);
        };
    }

    const Processor = {
        collectAds() {
            const elements = [...document.getElementsByTagName('*')];
            perf.adElements.clear();

            elements.forEach(element => {
                if (perf.processed.has(element)) return;

                let modulePriority = CONFIG.modulePriority;

                if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) {
                    modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))];
                }

                for (const moduleKey of modulePriority) {
                    if (moduleKey === 'obfuscatedAdSystem') continue;
                    if (Detector.checkModule(moduleKey, element)) {
                        perf.adElements.add(element);
                        perf.processed.add(element);
                        break;
                    }
                }
            });
        }
    };

    const observer = new MutationObserver(mutations => {
        if (!CONFIG.modules.main) return;

        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType === 1 && !perf.processed.has(node)) {
                    perf.pendingTasks.push(node);
                }
            }
        }
        debouncedScheduleProcess();
    });

    function processElements(deadline) {
        const start = performance.now();
        let processed = 0;

        while (perf.pendingTasks.length && processed < CONFIG.performance.mutationProcessLimit && deadline.timeRemaining() > 0) {
            const element = perf.pendingTasks.shift();
            if (!perf.processed.has(element)) {
                let modulePriority = CONFIG.modulePriority;

                if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) {
                    modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))];
                }

                for (const moduleKey of modulePriority) {
                    if (moduleKey === 'obfuscatedAdSystem') continue;
                    if (Detector.checkModule(moduleKey, element)) {
                        perf.adElements.add(element);
                        perf.processed.add(element);
                        break;
                    }
                }
                processed++;
            }
        }

        if (perf.pendingTasks.length) {
            scheduledProcess = requestIdleCallback(processElements, { timeout: CONFIG.performance.idleCallbackTimeout });
        } else {
            perf.isProcessing = false;
        }

        const duration = performance.now() - start;
        if (duration > CONFIG.performance.longTaskThreshold) {
            perf.longTaskCount++;
            console.warn("Long task detected:", duration, "ms");
            if (perf.longTaskCount > CONFIG.performance.degradeThreshold && !perf.degraded) {
                perf.degraded = true;
                console.warn("System degraded: Disabling non-critical modules.");
                CONFIG.modules.layoutSystem = false;
                CONFIG.modules.textSystem = false;
                CONFIG.modules.floating = false;
            }
        } else {
            perf.longTaskCount = Math.max(0, perf.longTaskCount - 1);
        }
    }

    let scheduledProcess = null;

    function scheduleProcess() {
        if (!perf.isProcessing && CONFIG.modules.main) {
            perf.isProcessing = true;
            scheduledProcess = requestIdleCallback(processElements, { timeout: CONFIG.performance.idleCallbackTimeout });
        }
    }

    const debouncedScheduleProcess = debounce(scheduleProcess, CONFIG.performance.debounceMutationDelay);

    const FirstScriptAdUtils = {
        safeRemove(element, moduleKey, reason) {
            if (!element?.parentNode || element.dataset.firstScriptAdRemoved) return false;
            try {
                Logs.add(moduleKey, element, reason);
                element.parentNode.removeChild(element);
                element.dataset.firstScriptAdRemoved = true;
                return true;
            } catch (e) {
                console.warn('元素移除失败:', e);
                return false;
            }
        },

        checkKeywords(content) {
            const keywords = [
                ...(CONFIG.obfuscatedAdConfig.useDefault ? FIRST_SCRIPT_DEFAULT_KEYWORDS : []),
                ...CONFIG.obfuscatedAdConfig.customKeywords
            ];
            return keywords.filter(k => content.includes(k));
        },

        checkRegex(content) {
            const regexList = [
                ...(CONFIG.obfuscatedAdConfig.useDefault ? Object.values(FIRST_SCRIPT_REGEX.obfuscatedAds) : []),
                ...CONFIG.obfuscatedAdConfig.customRegex.map(r => new RegExp(r.pattern, r.flags))
            ];
            return regexList.filter(r => r.test(content));
        }
    };

    const FirstScriptObfuscatedAdDetector = {
        observer: null,
        init() {
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1) this.processElement(node);
                    });
                });
            });
            this.observer.observe(document.documentElement, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['id', 'class', 'style', 'src']
            });
            this.scanExistingElements();
        },

        scanExistingElements() {
            document.querySelectorAll('script, iframe, div, span, img').forEach(el => {
                this.processElement(el);
            });
        },

        processElement(element) {
            if (element.dataset.firstScriptProcessed) return;
            element.dataset.firstScriptProcessed = true;

            const content = element.innerHTML || element.src || '';
            const matchedKeywords = FirstScriptAdUtils.checkKeywords(content);
            const matchedRegex = FirstScriptAdUtils.checkRegex(content);

            if (matchedKeywords.length + matchedRegex.length >= CONFIG.obfuscatedAdConfig.blockThreshold) {
                FirstScriptAdUtils.safeRemove(
                    element,
                    'obfuscatedAdSystem',
                    {
                        type: '规则匹配',
                        keywords: matchedKeywords,
                        regex: matchedRegex.map(r => r.toString())
                    }
                );
            }
        }
    };

    const UIController = {
        init() {
            this.loadConfig();
            this.registerMainSwitch();
            this.registerModuleSwitches();
            this.registerLogCommands();
            this.registerCSPCommands();
            this.registerObfuscatedAdCommands();
            this.registerResetCommand();
            StyleManager.toggle(CONFIG.modules.main);
        },

        registerMainSwitch() {
            GM_registerMenuCommand(
                `🔘 主开关 [${CONFIG.modules.main ? '✅' : '❌'}]`,
                () => this.toggleMain()
            );
        },

        registerModuleSwitches() {
            Object.entries(CONFIG.moduleNames).forEach(([key, name]) => {
                GM_registerMenuCommand(
                    `${name} [${CONFIG.modules[key] ? '✅' : '❌'}]`,
                    () => this.toggleModule(key, name)
                );
            });
        },

        registerLogCommands() {
            GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs());
            GM_registerMenuCommand('🧹 清空日志', () => Logs.clear());
        },

        registerCSPCommands() {
            GM_registerMenuCommand('🛡️ CSP策略管理', () => this.manageCSP());
        },

        registerObfuscatedAdCommands() {
            GM_registerMenuCommand('🔍 查看内嵌脚本', () => this.showInlineScripts());
            GM_registerMenuCommand('🛠 管理混淆规则', () => this.manageObfuscatedRules());
        },

        registerResetCommand() {
            GM_registerMenuCommand('🔄 重置设置', () => this.resetSettings());
        },

        showInlineScripts() {
            const scripts = Array.from(document.querySelectorAll('script'))
                .filter(s => s.innerHTML.trim().length > 0);
            
            const displayContent = scripts.map((s, i) => 
                `脚本 ${i+1}:\n${s.innerHTML.slice(0, 200).replace(/[\n\t]/g, '')}...`
            ).join('\n\n') || '无内嵌脚本';

            alert(displayContent);
            this.manageObfuscatedRules();
        },

        manageObfuscatedRules() {
            const menu = [
                '1. 添加关键词/正则式',
                '2. 恢复默认规则',
                '3. 查看当前规则',
                '4. 清空所有规则',
                '5. 查看内嵌脚本'
            ].join('\n');

            const choice = prompt(`混淆广告规则管理(${location.hostname}):\n${menu}`);
            if (choice === null) return;

            switch (choice) {
                case '1': this.addCustomRules(); break;
                case '2': this.restoreDefaultRules(); break;
                case '3': this.showCurrentRules(); break;
                case '4': this.clearAllRules(); break;
                case '5': this.showInlineScripts(); break;
                default: alert('无效操作');
            }
            this.manageObfuscatedRules();
        },

        addCustomRules() {
            const input = prompt('请输入要添加的内容(支持关键词和正则式,用逗号分隔):\n示例:广告词1,/regex_pattern/i');
            if (!input) return;

            const newItems = input.split(/[,,]/g).map(s => s.trim());
            let addedCount = 0;

            newItems.forEach(item => {
                if (/^\/.+\/[gimuy]*$/.test(item)) {
                    try {
                        const [_, pattern, flags] = item.match(/\/(.*)\/([gimuy]*)$/);
                        CONFIG.obfuscatedAdConfig.customRegex.push({ pattern, flags });
                        addedCount++;
                    } catch(e) {
                        alert(`无效正则表达式:${item}`);
                    }
                } else if (item) {
                    CONFIG.obfuscatedAdConfig.customKeywords.push(item);
                    addedCount++;
                }
            });

            if (addedCount > 0) {
                this.saveConfig();
                alert(`成功添加 ${addedCount} 条规则`);
            }
        },

        restoreDefaultRules() {
            if (confirm('确认恢复默认规则?')) {
                CONFIG.obfuscatedAdConfig.useDefault = true;
                CONFIG.obfuscatedAdConfig.customKeywords = [];
                CONFIG.obfuscatedAdConfig.customRegex = [];
                this.saveConfig();
                alert('已恢复全局默认规则');
            }
        },

        showCurrentRules() {
            const defaultKeywords = CONFIG.obfuscatedAdConfig.useDefault ? FIRST_SCRIPT_DEFAULT_KEYWORDS : [];
            const customKeywords = CONFIG.obfuscatedAdConfig.customKeywords;
            const defaultRegex = CONFIG.obfuscatedAdConfig.useDefault ? Object.values(FIRST_SCRIPT_REGEX.obfuscatedAds).map(r => r.toString()) : [];
            const customRegex = CONFIG.obfuscatedAdConfig.customRegex.map(r => new RegExp(r.pattern, r.flags).toString());

            alert([
                '默认关键词 (' + (CONFIG.obfuscatedAdConfig.useDefault ? '✅' : '❌') + '):',
                ...defaultKeywords.map((k, i) => `${i+1}. ${k}`),
                '\n自定义关键词:',
                ...customKeywords.map((k, i) => `${i+1}. ${k}`),
                '\n默认正则式 (' + (CONFIG.obfuscatedAdConfig.useDefault ? '✅' : '❌') + '):',
                ...defaultRegex.map((r, i) => `${i+1}. ${r}`),
                '\n自定义正则式:',
                ...customRegex.map((r, i) => `${i+1}. ${r}`)
            ].join('\n'));
        },

        clearAllRules() {
            if (confirm('⚠️ 将清除所有自定义规则并禁用默认规则?')) {
                CONFIG.obfuscatedAdConfig.useDefault = false;
                CONFIG.obfuscatedAdConfig.customKeywords = [];
                CONFIG.obfuscatedAdConfig.customRegex = [];
                this.saveConfig();
                alert('已清空所有规则');
            }
        },

        toggleMain() {
            const newState = !CONFIG.modules.main;
            Object.keys(CONFIG.modules).forEach(key => {
                if (key !== 'main') CONFIG.modules[key] = newState;
            });
            CONFIG.modules.main = newState;
            this.saveConfig();
            StyleManager.toggle(newState);
            GM_notification(`主开关已${newState ? '启用' : '禁用'}`, `所有模块${newState ? '✅ 已激活' : '❌ 已停用'}`);
            location.reload();
        },

        toggleModule(key, name) {
            CONFIG.modules[key] = !CONFIG.modules[key];
            CONFIG.modules.main = Object.values(CONFIG.modules)
                .slice(1).some(v => v);
            this.saveConfig();
            StyleManager.toggle(CONFIG.modules.main);
            GM_notification(`${name} ${CONFIG.modules[key] ? '✅ 已启用' : '❌ 已禁用'}`);
            location.reload();
        },

        showLogs() {
            const logText = Logs.logs.map(log => {
                const parts = [];
                if (log.reason.type) parts.push(`类型: ${log.reason.type}`);
                if (log.reason.detail) parts.push(`详细: ${log.reason.detail}`);
                if (log.reason.keywords) parts.push(`关键词: ${log.reason.keywords.join(', ')}`);
                if (log.reason.regex) parts.push(`正则式: ${log.reason.regex.join(', ')}`);
                return `${log.module}\n元素: ${log.element}\n内容: ${log.content}\n${parts.join('\n')}`;
            }).join('\n\n');

            alert(`广告拦截日志(最近${Logs.maxLogs}条):\n\n${logText || '暂无日志'}`);
        },

        manageCSP() {
            const rulesDisplay = CONFIG.csp.rules
                .map(r => `${r.id}. ${r.name} (${r.enabled ? '✅' : '❌'})`)
                .join('\n');

            let input = prompt(
                `当前CSP策略状态: ${CONFIG.csp.enabled ? '✅ 已启用' : '❌ 已禁用'}\n\n` +
                "当前策略规则:\n" + rulesDisplay + "\n\n" +
                "输入选项:\n1. 开启策略 (输入 'enable')\n2. 关闭策略 (输入 'disable')\n" +
                "修改规则示例:输入 '1on' 启用规则1\n输入 '23off' 禁用规则2和3\n\n"+
                "请输入你的选择:",
                ""
            );

            if (input === null) return;

            input = input.trim().toLowerCase();

            switch (input) {
                case 'enable':
                    CONFIG.csp.enabled = true;
                    this.saveConfig();
                    CSPManager.inject();
                    alert("CSP策略已启用");
                    location.reload();
                    break;
                case 'disable':
                    CONFIG.csp.enabled = false;
                    this.saveConfig();
                    CSPManager.remove();
                    alert("CSP策略已禁用");
                    location.reload();
                    break;
                default:
                    if (/^\d+(?:on|off)$/i.test(input)) {
                        this.modifyCSPRules(input);
                    } else {
                        alert("无效输入");
                    }
            }
        },

        modifyCSPRules(actionInput) {
            const matches = actionInput.match(/^(\d+)(on|off)$/i);

            if (matches) {
                const ruleIds = matches.split('').map(Number);
                const enable = matches.toLowerCase() === 'on';

                let updatedRules = [];
                ruleIds.forEach(id => {
                    const rule = CONFIG.csp.rules.find(r => r.id === id);
                    if (rule) {
                        rule.enabled = enable;
                        updatedRules.push(id);
                    }
                });

                this.saveConfig();
                CSPManager.inject();
                alert(`规则 ${updatedRules.join(',')} 已更新为 ${enable ? '启用' : '禁用'}`);
                location.reload();
            } else {
                alert("输入格式错误,请按示例输入如'1on'或'123off'");
            }
        },

        resetSettings() {
            if (confirm("确定要重置所有设置吗?")) {
                Object.assign(CONFIG, {
                    modules: {
                        main: false,
                        dynamicSystem: false,
                        layoutSystem: false,
                        frameSystem: false,
                        mediaSystem: false,
                        textSystem: false,
                        thirdPartyBlock: false,
                        obfuscatedAdSystem: false,
                        floating: false
                    },
                    csp: {
                        enabled: false,
                        rules: [
                            { id: 1, name: '阻止外部脚本加载', rule: "script-src 'self'", enabled: true },
                            { id: 2, name: '阻止内联脚本执行', rule: "script-src 'unsafe-inline'", enabled: false },
                            { id: 3, name: '阻止动态脚本执行', 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 }
                        ]
                    }
                });
                this.saveConfig();
                CSPManager.remove();
                alert("设置已重置为默认值");
                location.reload();
            }
        },

        saveConfig() {
            GM_setValue(`adblockConfig_${location.hostname}`, JSON.stringify({
                modules: CONFIG.modules,
                csp: CONFIG.csp,
                obfuscatedAdConfig: {
                    ...CONFIG.obfuscatedAdConfig,
                    customRegex: CONFIG.obfuscatedAdConfig.customRegex.map(r => ({
                        pattern: r.pattern,
                        flags: r.flags
                    }))
                }
            }));
        },

        loadConfig() {
            try {
                const savedConfig = JSON.parse(GM_getValue(`adblockConfig_${location.hostname}`, '{}'));
                if (savedConfig.modules) {
                    Object.keys(CONFIG.modules).forEach(key => {
                        if (savedConfig.modules.hasOwnProperty(key)) {
                            CONFIG.modules[key] = savedConfig.modules[key];
                        }
                    });
                }
                if (savedConfig.csp) {
                    Object.assign(CONFIG.csp, savedConfig.csp);
                }
                if (savedConfig.obfuscatedAdConfig) {
                    CONFIG.obfuscatedAdConfig.useDefault = savedConfig.obfuscatedAdConfig.useDefault;
                    CONFIG.obfuscatedAdConfig.customKeywords = savedConfig.obfuscatedAdConfig.customKeywords || [];
                    CONFIG.obfuscatedAdConfig.customRegex = (savedConfig.obfuscatedAdConfig.customRegex || [])
                        .map(r => {
                            try {
                                return new RegExp(r.pattern, r.flags);
                            } catch(e) {
                                return null;
                            }
                        })
                        .filter(r => r);
                }
            } catch (e) {
                console.error('配置加载失败:', e);
            }
        },
    };

    UIController.loadConfig();
    CSPManager.injectInitialCSP();

    (function initSystem() {
        UIController.init();
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['id', 'class', 'style', 'src', 'href']
        });

        if (CONFIG.modules.obfuscatedAdSystem) FirstScriptObfuscatedAdDetector.init();

        if (CONFIG.mobileOptimizations.lazyLoadImages) {
            document.addEventListener('scroll', throttle(() => {
                [...document.getElementsByTagName('img')].forEach(img => {
                    if (img.getBoundingClientRect().top < window.innerHeight + 500) {
                        img.src = img.dataset.src || img.src;
                    }
                });
            }, CONFIG.performance.throttleScrollDelay), { passive: true });
        }

        if (CONFIG.mobileOptimizations.removeImagePlaceholders) {
            [...document.querySelectorAll('img[width="1"], img[height="1"]')].forEach(img => {
                AdUtils.safeRemove(img, 'mediaSystem', { type: '图片占位符' });
            });
        }

        Processor.collectAds();
        scheduleProcess();
    })();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址