Greasy Fork

广告终结者

广告终结者(尝试优化性能开销)

目前为 2025-02-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         广告终结者
// @namespace    http://tampermonkey.net/
// @version      2.7
// @description  广告终结者(尝试优化性能开销)
// @author       TMHhz
// @match        *://*/*
// @exclude     *://*.bing.com/*
// @exclude     *://*.iqiyi.com/*
// @exclude     *://*.qq.com/*
// @exclude     *://*.v.qq.com/*
// @exclude     *://*.sohu.com/*
// @exclude     *://*.mgtv.com/*
// @exclude     *://*.ifeng.com/*
// @exclude     *://*.pptv.com/*
// @exclude     *://*.sina.com.cn/*
// @exclude     *://*.56.com/*
// @exclude     *://*.cntv.cn/*
// @exclude     *://*.tudou.com/*
// @exclude     *://*.baofeng.com/*
// @exclude     *://*.le.com/*
// @exclude     *://*.pps.tv/*
// @exclude     *://*.www.fun.tv/*
// @exclude     *://*.baidu.com/*
// @exclude     *://*.ku6.com/*
// @exclude     *://*.tvsou.com/*
// @exclude     *://*.kankan.com/*
// @exclude     *://*.douyu.com/*
// @exclude     *://*.weibo.com/*
// @exclude     *://*.people.com.cn/*
// @exclude     *://*.cctv.com/*
// @exclude     *://*.gdtv.com.cn/*
// @exclude     *://*.ahtv.cn/*
// @exclude     *://*.tvb.com/*
// @exclude     *://*.tvmao.com/*
// @exclude     *://*.douban.com/*
// @exclude     *://*.163.com/*
// @exclude     *://*.bilibili.com/*
// @exclude     *://*.www.gov.cn/*
// @exclude     *://*.thepaper.cn/*
// @exclude     *://*.xinhuanet.com/*
// @exclude     *://*.china.com/*
// @exclude     *://*.guancha.cn/*
// @exclude     *://*.jianshu.com/*
// @exclude     *://*.amazon.cn/*
// @exclude     *://*.cnblogs.com/*
// @exclude     *://*.cnstock.com/*
// @exclude     *://*.baike.com/*
// @exclude     *://*.guokr.com/*
// @exclude     *://*.360doc.com/*
// @exclude     *://*.qiushibaike.com/*
// @exclude     *://*.zol.com.cn/*
// @exclude     *://*.pconline.com.cn/*
// @exclude     *://*.pcpop.com/*
// @exclude     *://*.it168.com/*
// @exclude     *://*.gfan.com/*
// @exclude     *://*.feng.com/*
// @exclude     *://*.xiaomi.cn/*
// @exclude     *://*.10086.cn/*
// @exclude     *://*.10010.com/*
// @license      GPLv3
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    // ======================= 小说网站UA伪装 =======================
    (function detectNovelSite() {
        const novelKeywords = [
            'novel', 'xiaoshuo', '小说', '阅读', 
            'book', '章节', '文学', '小说网',
            'txt', 'download', '免费小说'
        ];
        
        const isNovelSite = () => {
            const urlCheck = novelKeywords.some(k => 
                window.location.href.toLowerCase().includes(k)
            );
            const titleCheck = novelKeywords.some(k => 
                document.title.toLowerCase().includes(k)
            );
            const metaKeywords = document.querySelector('meta[name="keywords"]')?.content || '';
            const metaDescription = document.querySelector('meta[name="description"]')?.content || '';
            const contentCheck = novelKeywords.some(k => 
                metaKeywords.includes(k) || metaDescription.includes(k)
            );
            return urlCheck || titleCheck || contentCheck;
        };

        if (isNovelSite()) {
            const symbianUA = 'NokiaN8-00/5.0 (Symbian/3; Series60/5.2 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/533.4 (KHTML, like Gecko) BrowserNG/7.3.1.37';
            Object.defineProperty(window.navigator, 'userAgent', {
                value: symbianUA,
                writable: false,
                configurable: false
            });
        }
    })();

    // ======================= 核心配置 =======================
    const CONFIG = {
        maxLogs: 150,
        adKeywords: [
            'ad', 'ads', 'advert', 'banner', 'popup', '推广', '广告', 'gg', 
            'advertisement', 'sponsor', '推荐', 'adv', 'guanggao', 'syad', 
            'bfad', '男男', '女女', '弹窗', '悬浮', '浮动', '浮窗', '葡京', 
            'pop', 'sticky', 'fixed', 'tip', 'tips', 'adbox', 'adsense', 
            'adserver', 'advertmarket', 'advertising', 'cookie-sync', 
            '偷拍', '黑料', '横幅', '乱伦'
        ],
        protectionRules: {
            dynamicIdLength: 12,
            zIndexThreshold: 50,
            maxFrameDepth: 3,
            textAdKeywords: ['限时优惠', '立即下载', '微信', 'vx:', 'telegram', '偷拍', '黑料']
        },
        contentFilter: {
            scanDepth: 3,
            minLength: 2,
            maxKeywords: 50,
            timeout: 300
        },
        defaultSettings: {
            dynamicSystem: true,
            layoutSystem: true,
            frameSystem: true,
            mediaSystem: true,
            textSystem: true,
            thirdPartyBlock: true,
            contentFilter: true
        }
    };

    // ======================= 广告工具类 =======================
    class AdUtils {
        static safeRemove(node, module, reason) {
            if (!node?.parentNode || this.isWhitelisted(node)) return false;
            try {
                Logger.logRemoval({
                    module,
                    element: {
                        tag: node.tagName,
                        id: node.id,
                        class: node.className,
                        html: node.outerHTML?.slice(0, 200)
                    },
                    reason
                });
                node.parentNode.removeChild(node);
                return true;
            } catch(e) {
                console.warn('元素移除失败:', e);
                return false;
            }
        }

        static handleScriptContent(script) {
            const content = script.innerHTML.toLowerCase();
            const blacklist = KeywordManager.getBlacklist();
            
            const foundKeywords = blacklist.filter(k => {
                return content.includes(k.toLowerCase().trim());
            });

            if (foundKeywords.length > 0) {
                Logger.logRemoval({
                    module: 'ScriptFilter',
                    element: {
                        tag: script.tagName,
                        id: script.id,
                        class: script.className,
                        html: script.outerHTML.slice(0, 200)
                    },
                    reason: {
                        type: '脚本关键词拦截',
                        detail: `匹配关键词: ${foundKeywords.join(', ')}`
                    }
                });
                script.remove();
                return true;
            }
            return false;
        }

        static isWhitelisted(element) {
            return element.closest('[data-protected]') || this.hasWhitelistContent(element);
        }

        static shouldBlockByContent(element) {
            if (!Config.get('contentFilter')) return false;
            const text = this.getCleanText(element);
            const blacklist = KeywordManager.getBlacklist();
            const whitelist = KeywordManager.getWhitelist();
            return !whitelist.some(k => text.includes(k)) && 
                   blacklist.some(k => text.includes(k));
        }

        static getCleanText(element) {
            return this.normalizeText(this.extractText(element).trim());
        }

        static extractText(element, depth = 0) {
            if (depth > CONFIG.contentFilter.scanDepth) return '';
            return Array.from(element.childNodes).map(n => {
                if (n.nodeType === Node.TEXT_NODE) return n.textContent;
                if (n.nodeType === Node.ELEMENT_NODE) return this.extractText(n, depth + 1);
                return '';
            }).join(' ');
        }

        static normalizeText(text) {
            return text
                .replace(/\s+/g, ' ')
                .replace(/[【】《》「」“”‘’]/g, '')
                .toLowerCase();
        }

        static hasWhitelistContent(element) {
            const text = this.normalizeText(element.textContent);
            return KeywordManager.getWhitelist().some(k => text.includes(k));
        }
    }

    // ======================= 核心拦截系统 =======================
    class CoreSystem {
        constructor() {
            this.observerConfig = {
                childList: true, 
                subtree: true,
                attributeFilter: ['id', 'class', 'style', 'src']
            };
            this.processedElements = new WeakSet();
            this.initObservers();
            this.initialClean();
            this.injectProtectionStyles();
        }

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

        initObservers() {
            const processMutations = this.debounce(this.handleMutations.bind(this), 100);
            new MutationObserver(mutations => processMutations(mutations))
                .observe(document, this.observerConfig);
        }

        handleMutations(mutations) {
            const elements = new Set();
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1 && !this.processedElements.has(node)) {
                        elements.add(node);
                        this.processedElements.add(node);
                    }
                }
            }
            this.batchProcessElements([...elements]);
        }

        batchProcessElements(elements) {
            if (!elements.length) return;
            
            const BATCH_SIZE = 25;
            for (let i = 0; i < elements.length; i += BATCH_SIZE) {
                const batch = elements.slice(i, i + BATCH_SIZE);
                requestIdleCallback(() => {
                    batch.forEach(el => this.processElement(el));
                }, { timeout: 500 });
            }
        }

        initialClean() {
            this.checkPriorityElements(['script', 'iframe', 'img', 'div']);
            requestIdleCallback(() => this.checkElements('*'), { timeout: 1000 });
        }

        checkPriorityElements(selectors) {
            selectors.forEach(selector => {
                const elements = document.querySelectorAll(selector);
                this.batchProcessElements([...elements]);
            });
        }

        processElement(el) {
            if(Config.get('dynamicSystem')) {
                this.checkDynamicId(el);
                this.checkAdAttributes(el);
            }
            if(Config.get('layoutSystem')) {
                this.checkZIndex(el);
                this.checkFixedPosition(el);
            }
            if(Config.get('mediaSystem')) {
                this.checkImageAds(el);
                this.checkFloatingAds(el);
            }
            if(Config.get('textSystem')) {
                this.checkTextAds(el);
            }
        }

        scanForContent(element) {
            if (element.tagName === 'SCRIPT') {
                if (AdUtils.handleScriptContent(element)) return;
            }

            if (AdUtils.shouldBlockByContent(element)) {
                AdUtils.safeRemove(element, 'ContentFilter', {
                    type: '内容关键词匹配',
                    detail: '黑名单内容触发'
                });
            }

            Array.from(element.children).forEach(child => this.scanForContent(child));
        }

        checkDynamicId(el) {
            const id = el.id || '';
            if(id.length > CONFIG.protectionRules.dynamicIdLength || /\d{5}/.test(id)) {
                AdUtils.safeRemove(el, 'DynamicSystem', {
                    type: '动态ID检测',
                    detail: `异常ID: ${id.slice(0, 20)}`
                });
            }
        }

        checkAdAttributes(el) {
            ['id', 'class', 'src'].forEach(attr => {
                const val = el.getAttribute(attr) || '';
                if(CONFIG.adKeywords.some(k => val.includes(k))) {
                    AdUtils.safeRemove(el, 'DynamicSystem', {
                        type: '广告属性检测',
                        detail: `${attr}=${val.slice(0, 30)}`
                    });
                }
            });
        }

        checkZIndex(el) {
            const zIndex = parseInt(getComputedStyle(el).zIndex);
            if(zIndex > CONFIG.protectionRules.zIndexThreshold) {
                AdUtils.safeRemove(el, 'LayoutSystem', {
                    type: '高堆叠元素',
                    detail: `z-index=${zIndex}`
                });
            }
        }

        checkFixedPosition(el) {
            const style = getComputedStyle(el);
            if(style.position === 'fixed' && el.offsetWidth < 200) {
                AdUtils.safeRemove(el, 'LayoutSystem', {
                    type: '固定定位元素',
                    detail: `尺寸: ${el.offsetWidth}x${el.offsetHeight}`
                });
            }
        }

        checkImageAds(el) {
            if(el.tagName === 'IMG' && (el.src.includes('ad') || el.src.endsWith('.gif'))) {
                AdUtils.safeRemove(el, 'MediaSystem', {
                    type: '图片广告',
                    detail: `图片源: ${el.src.slice(0, 50)}`
                });
            }
        }

        checkFloatingAds(el) {
            const rect = el.getBoundingClientRect();
            const style = getComputedStyle(el);
            if(['fixed', 'sticky'].includes(style.position) && 
              (rect.top < 10 || rect.bottom > window.innerHeight - 10)) {
                AdUtils.safeRemove(el, 'MediaSystem', {
                    type: '浮动广告',
                    detail: `位置: ${rect.top}px`
                });
            }
        }

        checkTextAds(el) {
            const text = el.textContent?.toLowerCase() || '';
            if (CONFIG.protectionRules.textAdKeywords.some(k => text.includes(k))) {
                AdUtils.safeRemove(el, 'TextSystem', {
                    type: '文本广告',
                    detail: `关键词: ${text.slice(0, 50)}`
                });
            }
        }

        checkIframes() {
            if(!Config.get('frameSystem')) return;
            document.querySelectorAll('iframe').forEach(iframe => {
                let depth = 0, parent = iframe;
                while((parent = parent.parentNode)) {
                    if(parent.tagName === 'IFRAME') depth++;
                }
                if(depth > CONFIG.protectionRules.maxFrameDepth) {
                    AdUtils.safeRemove(iframe, 'FrameSystem', {
                        type: '深层嵌套框架',
                        detail: `嵌套层级: ${depth}`
                    });
                }

                const container = iframe.closest('div, section');
                if(container && !AdUtils.isWhitelisted(container)) {
                    AdUtils.safeRemove(container, 'FrameSystem', {
                        type: '广告容器',
                        detail: 'iframe父容器'
                    });
                }
            });
        }

        checkThirdParty() {
            if(!Config.get('thirdPartyBlock')) return;
            document.querySelectorAll('script, iframe').forEach(el => {
                try {
                    const src = new URL(el.src).hostname;
                    const current = new URL(location.href).hostname;
                    if(!src.endsWith(current)) {
                        AdUtils.safeRemove(el, 'ThirdParty', {
                            type: '第三方资源',
                            detail: `源域: ${src}`
                        });
                    }
                } catch {}
            });
        }

        injectProtectionStyles() {
            GM_addStyle(`
                [style*="fixed"], [style*="sticky"] { 
                    position: static !important;
                    top: auto !important;
                    bottom: auto !important;
                }
                iframe[src*="ad"], .ad-container { 
                    display: none !important;
                    height: 0 !important;
                    width: 0 !important;
                    opacity: 0 !important;
                }
                .ad-shield-protected {
                    border: 2px solid #4CAF50 !important;
                    padding: 5px !important;
                }
                pre {
                    white-space: pre-wrap;
                    word-wrap: break-word;
                    font-family: 'Courier New', monospace;
                    font-size: 12px;
                    background: #1e1e1e;
                    padding: 15px;
                    border-radius: 5px;
                    margin: 10px 0;
                    color: #d4d4d4;
                }
                .keyword-highlight {
                    background: #4a4a4a;
                    padding: 2px 4px;
                    border-radius: 3px;
                }
            `);
        }

        checkElements(selector, fn) {
            document.querySelectorAll(selector).forEach(fn);
        }
    }

    // ======================= 配置管理系统 =======================
    class Config {
        static get currentDomain() {
            return location.hostname.replace(/^www\./, '');
        }

        static get allKeys() {
            return Object.keys(CONFIG.defaultSettings);
        }

        static get(key) {
            const data = GM_getValue('config') || {};
            const domainConfig = data[this.currentDomain] || {};
            return domainConfig[key] ?? CONFIG.defaultSettings[key];
        }

        static set(key, value) {
            const data = GM_getValue('config') || {};
            data[this.currentDomain] = {...CONFIG.defaultSettings, ...data[this.currentDomain], [key]: value};
            GM_setValue('config', data);
        }

        static toggleAll(status) {
            const data = GM_getValue('config') || {};
            data[this.currentDomain] = Object.fromEntries(
                Config.allKeys.map(k => [k, status])
            );
            GM_setValue('config', data);
        }
    }

    // ======================= 关键词管理系统 =======================
    class KeywordManager {
        static getStorageKey(type) {
            return `content_${type}_${Config.currentDomain}`;
        }

        static getBlacklist() {
            return this.getKeywords('blacklist');
        }

        static getWhitelist() {
            return this.getKeywords('whitelist');
        }

        static getKeywords(type) {
            const raw = GM_getValue(this.getStorageKey(type), '');
            return this.parseKeywords(raw);
        }

        static parseKeywords(raw) {
            return raw.split(',')
                .map(k => k.trim())
                .filter(k => k.length >= CONFIG.contentFilter.minLength)
                .slice(0, CONFIG.contentFilter.maxKeywords)
                .map(k => k.toLowerCase());
        }

        static updateKeywords(type, keywords) {
            const valid = [...new Set(keywords)]
                .map(k => k.trim())
                .filter(k => k.length >= CONFIG.contentFilter.minLength)
                .slice(0, CONFIG.contentFilter.maxKeywords);
            GM_setValue(this.getStorageKey(type), valid.join(','));
        }

        static updateScriptKeywords(keywords) {
            GM_setValue('script_keywords', JSON.stringify(keywords));
        }

        static getScriptKeywords() {
            return JSON.parse(GM_getValue('script_keywords', '[]'));
        }
    }

    // ======================= 用户界面控制器 =======================
    class UIController {
        static init() {
            this.registerMainMenu();
            this.registerModuleCommands();
            this.registerContentMenu();
            this.registerUtilityCommands();
        }

        static registerMainMenu() {
            const allEnabled = Config.allKeys.every(k => Config.get(k));
            GM_registerMenuCommand(
                `🔘 主开关 [${allEnabled ? '✅' : '❌'}]`,
                () => this.toggleAllModules(!allEnabled)
            );
        }

        static registerModuleCommands() {
            const modules = [
                ['dynamicSystem', '动态检测系统 (ID/属性)'],
                ['layoutSystem', '布局检测系统 (定位/z-index)'],
                ['frameSystem', '框架过滤系统 (iframe)'],
                ['mediaSystem', '媒体检测系统 (图片/浮动)'],
                ['textSystem', '文本广告检测'],
                ['thirdPartyBlock', '第三方拦截'],
                ['contentFilter', '内容过滤系统']
            ];

            modules.forEach(([key, name]) => {
                GM_registerMenuCommand(
                    `${name} [${Config.get(key) ? '✅' : '❌'}]`,
                    () => this.toggleModule(key, name)
                );
            });
        }

        static registerContentMenu() {
            GM_registerMenuCommand('🔠 内容过滤管理', () => {
                GM_registerMenuCommand('➕ 添加黑名单关键词', () => 
                    this.handleAddKeyword('blacklist'));
                GM_registerMenuCommand('➕ 添加白名单关键词', () => 
                    this.handleAddKeyword('whitelist'));
                GM_registerMenuCommand('📋 显示当前关键词', () => 
                    this.showCurrentKeywords());
                GM_registerMenuCommand('🗑️ 清除所有关键词', () => 
                    this.clearKeywords());
            });
        }

        static registerUtilityCommands() {
            GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs());
            GM_registerMenuCommand('🧹 清除当前日志', () => Logger.clear());
            GM_registerMenuCommand('⚙️ 重置所有配置', () => this.resetConfig());
            GM_registerMenuCommand('📜 查看内嵌脚本', () => this.showInlineScripts());
        }

        static showInlineScripts() {
            const scripts = Array.from(document.querySelectorAll('script'))
                .filter(script => script.innerHTML.trim().length > 0);
            
            if (scripts.length === 0) {
                alert('当前网页没有内嵌脚本');
                return;
            }

            let message = `📜 发现 ${scripts.length} 个内嵌脚本:\n\n`;
            scripts.forEach((script, index) => {
                const content = script.innerHTML
                    .replace(/[\r\n]+/g, ' ')    // 替换换行为空格
                    .replace(/\s{2,}/g, ' ')     // 压缩多个空格
                    .trim()
                    .slice(0, 300);              // 截取前300字符
                
                message += `▦ 脚本 ${index + 1} ▦\n${content}\n\n`;
            });

            alert(message);
        }

        static enhanceContentFilter(keyword) {
            let scriptRemoved = false;
            
            // 处理脚本内容
            document.querySelectorAll('script').forEach(script => {
                if (script.innerHTML.includes(keyword)) {
                    script.remove();
                    Logger.logRemoval({
                        module: 'ContentFilter',
                        element: {
                            tag: script.tagName,
                            id: script.id,
                            class: script.className,
                            html: script.outerHTML.slice(0, 200)
                        },
                        reason: {
                            type: '立即拦截脚本',
                            detail: `关键词: ${keyword}`
                        }
                    });
                    scriptRemoved = true;
                }
            });

            // 重新扫描所有元素(优化性能版)
            const scanElements = () => {
                const walker = document.createTreeWalker(
                    document.documentElement,
                    NodeFilter.SHOW_ELEMENT
                );

                while (walker.nextNode()) {
                    const node = walker.currentNode;
                    if (AdUtils.shouldBlockByContent(node)) {
                        AdUtils.safeRemove(node, 'ContentFilter', {
                            type: '立即拦截元素',
                            detail: `关键词: ${keyword}`
                        });
                    }
                }
            };

            // 分批次执行防止卡顿
            setTimeout(scanElements, 0);
            setTimeout(scanElements, 300);
            setTimeout(scanElements, 600);
        }

        static toggleModule(key, name) {
            const value = !Config.get(key);
            Config.set(key, value);
            this.showNotification(`${name} ${value ? '✅ 已启用' : '❌ 已禁用'}`);
            setTimeout(() => location.reload(), 500);
        }

        static toggleAllModules(status) {
            Config.toggleAll(status);
            this.showNotification(`所有模块已${status ? '启用' : '禁用'}`);
            setTimeout(() => location.reload(), 500);
        }

        static handleAddKeyword(type) {
            const promptText = type === 'blacklist' 
                ? '输入要屏蔽的关键词(支持中文):' 
                : '输入要豁免的关键词:';
            const input = prompt(promptText);
            if (!input) return;

            const current = KeywordManager.getKeywords(type);
            KeywordManager.updateKeywords(type, [...current, input]);
            this.showNotification(`已添加${type === 'blacklist' ? '黑' : '白'}名单关键词:${input}`);

            if (type === 'blacklist') {
                this.enhanceContentFilter(input);
            }
        }

        static enhanceContentFilter(keyword) {
            let scriptRemoved = false;
            document.querySelectorAll('script').forEach(script => {
                if (script.innerHTML.includes(keyword)) {
                    script.remove();
                    Logger.logRemoval({
                        module: 'ContentFilter',
                        element: {
                            tag: script.tagName,
                            id: script.id,
                            class: script.className,
                            html: script.outerHTML.slice(0, 200)
                        },
                        reason: {
                            type: '立即拦截脚本',
                            detail: `关键词: ${keyword}`
                        }
                    });
                    scriptRemoved = true;
                }
            });

            if (!scriptRemoved) {
                document.querySelectorAll('*').forEach(element => {
                    const text = AdUtils.getCleanText(element);
                    if (text.includes(keyword)) {
                        AdUtils.safeRemove(element, 'ContentFilter', {
                            type: '立即拦截元素',
                            detail: `关键词: ${keyword}`
                        });
                    }
                });
            }
        }

        static showCurrentKeywords() {
            const black = KeywordManager.getBlacklist();
            const white = KeywordManager.getWhitelist();
            
            alert(`【当前内容过滤规则 - ${location.hostname}】
            
■ 黑名单 (${black.length}个):
${black.join(', ') || '无'}

■ 白名单 (${white.length}个):
${white.join(', ') || '无'}`);
        }

        static clearKeywords() {
            if (!confirm('确定清除所有关键词吗?')) return;
            ['blacklist', 'whitelist'].forEach(type => {
                GM_setValue(KeywordManager.getStorageKey(type), '');
            });
            this.showNotification('已清除所有关键词');
        }

        static resetConfig() {
            if (!confirm('确定重置所有配置吗?')) return;
            const data = GM_getValue('config') || {};
            delete data[Config.currentDomain];
            GM_setValue('config', data);
            this.showNotification('配置已重置');
            setTimeout(() => location.reload(), 500);
        }

        static showLogs() {
            const logs = Logger.getLogs();
            alert(logs.length ? 
                `📃 最近${CONFIG.maxLogs}条拦截记录:\n\n${logs.map(l => 
                    `[${l.time}] ${l.module}\n类型: ${l.type}\n元素: ${l.element}`
                ).join('\n\n')}` : 
                '暂无拦截记录'
            );
        }

        static showNotification(text, duration = 2000) {
            GM_notification({
                title: '广告终结者',
                text: text,
                silent: true,
                timeout: duration
            });
        }
    }

    // ======================= 日志系统 =======================
    class Logger {
        static logRemoval(data) {
            const logs = GM_getValue('logs', []);
            logs.push({
                time: new Date().toLocaleTimeString(),
                module: data.module,
                type: data.reason.type,
                detail: data.reason.detail,
                element: `${data.element.tag}#${data.element.id}`
            });
            GM_setValue('logs', logs.slice(-CONFIG.maxLogs));
        }

        static getLogs() {
            return GM_getValue('logs', []);
        }

        static clear() {
            GM_setValue('logs', []);
            UIController.showNotification('日志已清空');
        }
    }

    // ======================= 初始化入口 =======================
    (function init() {
        new CoreSystem();
        UIController.init();
        console.log('✅ 广告拦截系统已激活 - 当前域名:', location.hostname);
    })();
})();