Greasy Fork

广告终结者 v2.0

[13模块完整版] 动态检测、框架过滤、堆叠拦截、第三方拦截四大系统,增强广告拦截

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

// ==UserScript==
// @name         广告终结者 v2.0
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  [13模块完整版] 动态检测、框架过滤、堆叠拦截、第三方拦截四大系统,增强广告拦截
// @author       TMHhz
// @match        *://*/*
// @license      GPLv3
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// ==/UserScript==

(function() {
    'use strict';

    // 核心配置
    const CONFIG = {
        maxLogs: 100,
        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', '国产', '偷拍', '黑料', '横幅'
        ],
        checkAttributes: [
            'id', 'class', 'src', 'href', 'data-ad', 'name', 
            'data-src', 'data-url', 'alt', 'title', 'onclick', 'style'
        ],
        protectionRules: {
            minWidth: 50,
            minHeight: 20,
            iframeSizeThreshold: 2,
            zIndexThreshold: 50,
            sizeThresholdRatio: 0.15,
            similarityThreshold: 0.8,
            dynamicIdLength: 10,
            dynamicIdDigits: 5,
            fixedPositionThreshold: 2,
            floatingKeywords: ['float', '悬浮', '弹窗', '浮窗', '浮动','横幅','fixed','sticky','tip','tips','pop'],
            adImagePatterns: ['/*.gif', '/*.webp', '/*.swf'],
            adContainerSelectors: ['[id*="side"]', '[class*="side"]'],
            viewportEdgeThreshold: 10,
            maxFrameDepth: 3,
            textAdKeywords: ['限时优惠', '立即下载', '免费领取', 'vx:', '点击咨询', '低至1折', '加群', '抢购', '微信', 'telegram', '国产', '偷拍', '黑料']
        },
        whitelist: {
            scriptNamespaces: ['pswMgrDialog', 'userscript-'],
            protectedAttributes: [
                {name: 'id', values: ['pswMgrDialog', 'userscript-quickFill']},
                {name: 'class', values: ['userscript-quickFill']}
            ],
            selectors: ['#pswMgrDialog', '.userscript-quickFill', '.userscript-pswmgrDlg'],
            thirdparty: ['cdn.bootcss.com', 'unpkg.com']
        },
        defaultSettings: {
            // 动态检测系统
            dynamicIdDetection: true,
            attributeSimilarity: true,
            sizeAnomaly: true,
            adAttributeDetection: true,
            
            // 框架过滤系统
            iframeAttributeCheck: true,
            parentContainerCheck: true,
            nestedFrameDetection: true,
            
            // 堆叠拦截系统
            highZIndexDetection: true,
            fixedPositionCheck: true,
            overlayDetection: true,
            floatingLayerCheck: true,
            
            // 第三方拦截
            thirdpartyCheck: true,
            cookieSyncDetection: true,
            
            // 增强功能
            floatingAdDetection: true,
            imageAdDetection: true,
            sidebarAdDetection: true,
            textAdDetection: true
        }
    };

    // ======================= 工具类 =======================
    class AdUtils {
        static safeRemove(node, module, reason) {
            if (!node || !node.parentNode || this.isWhitelisted(node)) {
                console.debug('[拦截跳过] 白名单元素:', node);
                return false;
            }

            try {
                Logger.logRemoval({
                    module,
                    element: {
                        tag: node.tagName,
                        id: node.id,
                        class: node.className,
                        html: node.outerHTML.slice(0, 500)
                    },
                    reason
                });

                node.parentNode.removeChild(node);
                this.cleanAncestors(node.parentNode);
                console.log('[成功移除]', module, node);
                return true;
            } catch (e) {
                console.error('元素移除失败:', e);
                return false;
            }
        }

        static cleanAncestors(node) {
            let current = node;
            while (current && current !== document.documentElement) {
                if (current.children.length === 0 && 
                   !current.hasAttribute('data-keep-empty')) {
                    const parent = current.parentNode;
                    parent?.removeChild(current);
                    current = parent;
                } else {
                    break;
                }
            }
        }

        static isWhitelisted(element) {
            let currentElement = element;
            while (currentElement && currentElement !== document.documentElement) {
                const src = currentElement.getAttribute('src');
                if (src && CONFIG.whitelist.thirdparty.some(domain => src.includes(domain))) return true;

                if (CONFIG.whitelist.selectors.some(s => currentElement.matches(s))) return true;
                if (CONFIG.whitelist.protectedAttributes.some(attr => {
                    const attrValue = currentElement.getAttribute(attr.name);
                    return attrValue && attr.values.some(v => 
                        attr.name === 'class' ? 
                        currentElement.classList.contains(v) :
                        attrValue.startsWith(v)
                    );
                })) return true;
                if (CONFIG.whitelist.scriptNamespaces.some(ns => {
                    const id = currentElement.id || '';
                    const className = currentElement.className || '';
                    return id.startsWith(ns) || className.startsWith(ns);
                })) return true;

                currentElement = currentElement.parentElement;
            }
            return false;
        }

        static calculateSimilarity(a, b) {
            const setA = new Set(a.split(''));
            const setB = new Set(b.split(''));
            const intersection = new Set([...setA].filter(x => setB.has(x)));
            return intersection.size / Math.max(setA.size, setB.size);
        }
    }

    // ======================= 第三方拦截模块 =======================
    class ThirdpartyInterceptor {
        constructor() {
            this.baseHost = this.getCurrentBaseHost();
            this.initObserver();
        }

        getCurrentBaseHost() {
            const host = location.hostname;
            return this.isIP(host) ? host : this.getBaseDomain(host);
        }

        isIP(host) {
            return /^(?:\d{1,3}\.){3}\d{1,3}$/.test(host);
        }

        getBaseDomain(url) {
            try {
                const parsed = new URL(url.startsWith('http') ? url : `http://${url}`);
                const parts = parsed.hostname.split('.');
                if (parts.length <= 1) return parsed.hostname;
                return parts.slice(-2).join('.');
            } catch {
                return this.baseHost;
            }
        }

        isThirdparty(src) {
            if (!src || CONFIG.whitelist.thirdparty.some(d => src.includes(d))) return false;
            try {
                return this.getBaseDomain(src) !== this.baseHost;
            } catch {
                return false;
            }
        }

        processNode(node) {
            const tag = node.tagName.toUpperCase();
            if (['SCRIPT', 'IFRAME'].includes(tag)) {
                const src = node.getAttribute('src');
                if (src && this.isThirdparty(src)) {
                    AdUtils.safeRemove(node, 'thirdparty', {
                        type: '第三方资源',
                        detail: `拦截源: ${this.getBaseDomain(src)}`
                    });
                }
            }
        }

        initObserver() {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(m => {
                    m.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            this.processNode(node);
                            node.querySelectorAll('script, iframe').forEach(n => this.processNode(n));
                        }
                    });
                });
            });

            observer.observe(document, {
                childList: true,
                subtree: true
            });

            document.querySelectorAll('script, iframe').forEach(n => this.processNode(n));
        }
    }

    // ======================= 核心拦截系统 =======================
    class CoreCleaner {
        constructor() {
            if (DomainConfig.getConfig('thirdpartyCheck')) {
                this.thirdpartyInterceptor = new ThirdpartyInterceptor();
            }
            
            if (DomainConfig.getConfig('cookieSyncDetection')) {
                this.cookieSyncCheck();
            }
            
            if (this.isAnyModuleEnabled()) {
                this.initEnhancedObserver();
                this.initialCleanup();
            }
        }

        isAnyModuleEnabled() {
            return Object.keys(CONFIG.defaultSettings).some(
                key => DomainConfig.getConfig(key)
            );
        }

        initEnhancedObserver() {
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(m => {
                    m.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.shadowRoot) {
                                this.observer.observe(node.shadowRoot, {
                                    childList: true,
                                    subtree: true
                                });
                            }
                            
                            this.checkGenericElements(node);
                            if (node.tagName === 'IFRAME') this.checkIframes(node);
                            if (['DIV', 'P', 'SPAN'].includes(node.tagName)) this.checkTextAds(node);
                        }
                    });
                });
            });

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

        initialCleanup() {
            if (!this.isAnyModuleEnabled()) return;

            this.runDetectionSystems();
            this.checkElements('iframe', this.checkIframes.bind(this));
            this.checkElements('*', this.checkGenericElements.bind(this));
            this.checkElements('img', this.checkImageAds.bind(this));
            this.checkElements('*', this.checkFloatingAds.bind(this));
            this.checkSidebarContainers();
            this.checkElements('div,p,span', this.checkTextAds.bind(this));
        }

        runDetectionSystems() {
            if (DomainConfig.getConfig('highZIndexDetection')) this.checkHighZIndex();
            if (DomainConfig.getConfig('fixedPositionCheck')) this.checkFixedPosition();
            if (DomainConfig.getConfig('overlayDetection')) this.checkOverlay();
            if (DomainConfig.getConfig('floatingLayerCheck')) this.checkFloatingLayers();
        }

        checkElements(selector, checker) {
            if (!this.isAnyModuleEnabled()) return;
            document.querySelectorAll(selector).forEach(node => checker(node));
        }

        // ========== 框架过滤系统 ==========
        checkIframes(iframe) {
            if (DomainConfig.getConfig('iframeAttributeCheck')) {
                this.checkIframeAttributes(iframe);
            }
            if (DomainConfig.getConfig('parentContainerCheck')) {
                this.checkParentContainers(iframe);
            }
            if (DomainConfig.getConfig('nestedFrameDetection')) {
                this.checkNestedFrames(iframe);
            }
        }

        checkParentContainers(iframe) {
            const parents = ['div', 'section', 'article']
                .map(s => iframe.closest(s))
                .filter(Boolean);
            
            parents.forEach(parent => {
                if (!AdUtils.isWhitelisted(parent)) {
                    AdUtils.safeRemove(parent, 'frame-parent', {
                        type: '父容器过滤',
                        detail: '可疑iframe容器'
                    });
                }
            });
        }

        checkIframeAttributes(iframe) {
            const hasAdSrc = CONFIG.adKeywords.some(kw => iframe.src.includes(kw));
            const isHidden = iframe.offsetWidth < CONFIG.protectionRules.iframeSizeThreshold || 
                           iframe.offsetHeight < CONFIG.protectionRules.iframeSizeThreshold;
            
            if (isHidden || hasAdSrc) {
                AdUtils.safeRemove(iframe, 'frame-attr', {
                    type: 'iframe属性',
                    detail: hasAdSrc ? 
                        `广告源: ${iframe.src.slice(0, 50)}` : 
                        `隐藏iframe: ${iframe.offsetWidth}x${iframe.offsetHeight}`
                });
            }
        }

        checkNestedFrames(iframe) {
            try {
                const depth = this.getFrameDepth(iframe);
                if (depth > CONFIG.protectionRules.maxFrameDepth) {
                    AdUtils.safeRemove(iframe, 'nested-frame', {
                        type: '嵌套框架',
                        detail: `嵌套层级: ${depth}层`
                    });
                }
            } catch (e) {
                console.log('框架深度检测异常:', e);
            }
        }

        getFrameDepth(node) {
            let depth = 0;
            let currentNode = node;
            while (currentNode.parentNode) {
                if (currentNode.tagName === 'IFRAME') depth++;
                currentNode = currentNode.parentNode;
            }
            return depth;
        }

        // ========== 动态检测系统 ==========
        checkGenericElements(element) {
            if (DomainConfig.getConfig('dynamicIdDetection')) {
                this.checkDynamicId(element);
            }
            if (DomainConfig.getConfig('attributeSimilarity')) {
                this.checkAttributeSimilarity(element);
            }
            if (DomainConfig.getConfig('sizeAnomaly')) {
                this.checkSizeAnomaly(element);
            }
            if (DomainConfig.getConfig('adAttributeDetection')) {
                this.checkAdAttributes(element);
            }
        }

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

        checkAttributeSimilarity(element) {
            const id = element.id || '';
            const className = element.className || '';
            if (AdUtils.calculateSimilarity(id, className) > CONFIG.protectionRules.similarityThreshold) {
                AdUtils.safeRemove(element, 'attr-similarity', {
                    type: '属性相似度',
                    detail: `ID与class相似度过高`
                });
            }
        }

        checkSizeAnomaly(element) {
            const rect = element.getBoundingClientRect();
            if (rect.width * rect.height > 
                window.innerWidth * window.innerHeight * CONFIG.protectionRules.sizeThresholdRatio) {
                AdUtils.safeRemove(element, 'size-anomaly', {
                    type: '尺寸异常',
                    detail: `占用面积 ${Math.round(rect.width*rect.height)}px²`
                });
            }
        }

        checkAdAttributes(element) {
            const attrCheck = CONFIG.checkAttributes.find(attr => {
                const value = element.getAttribute(attr) || '';
                return CONFIG.adKeywords.some(kw => value.includes(kw));
            });

            if (attrCheck) {
                AdUtils.safeRemove(element, 'attr-match', {
                    type: '属性匹配',
                    attribute: attrCheck,
                    value: element.getAttribute(attrCheck).slice(0, 100)
                });
            }
        }

        // ========== 堆叠拦截系统 ==========
        checkHighZIndex() {
            document.querySelectorAll('body *').forEach(el => {
                const zIndex = parseInt(getComputedStyle(el).zIndex);
                if (!isNaN(zIndex) && zIndex > CONFIG.protectionRules.zIndexThreshold) {
                    AdUtils.safeRemove(el, 'z-index', {
                        type: '高堆叠',
                        detail: `z-index: ${zIndex}`
                    });
                }
            });
        }

        checkFixedPosition() {
            const fixedElements = [];
            document.querySelectorAll('*').forEach(el => {
                const style = getComputedStyle(el);
                if (style.position === 'fixed' && !AdUtils.isWhitelisted(el)) {
                    fixedElements.push(el);
                }
            });

            if (fixedElements.length > CONFIG.protectionRules.fixedPositionThreshold) {
                fixedElements.forEach(el => {
                    AdUtils.safeRemove(el, 'fixed-pos', {
                        type: '过量固定元素',
                        detail: `发现${fixedElements.length}个固定元素`
                    });
                });
            }
        }

        checkOverlay() {
            document.querySelectorAll('div, section').forEach(el => {
                const style = getComputedStyle(el);
                if (style.backgroundColor !== 'rgba(0, 0, 0, 0)' && 
                   parseFloat(style.opacity) > 0.5 &&
                   el.offsetWidth >= window.innerWidth * 0.8) {
                    AdUtils.safeRemove(el, 'overlay', {
                        type: '遮罩层',
                        detail: `背景色: ${style.backgroundColor}`
                    });
                }
            });
        }

        checkFloatingLayers() {
            document.querySelectorAll('*').forEach(el => {
                const style = getComputedStyle(el);
                if (style.position === 'fixed' && 
                   (el.offsetWidth < 200 || el.offsetHeight < 100)) {
                    AdUtils.safeRemove(el, 'floating-layer', {
                        type: '悬浮层',
                        detail: `小型浮动元素: ${el.offsetWidth}x${el.offsetHeight}`
                    });
                }
            });
        }

        // ========== 增强功能模块 ==========
        checkFloatingAds(element) {
            if (!DomainConfig.getConfig('floatingAdDetection')) return;

            const style = getComputedStyle(element);
            const isFixed = style.position === 'fixed';
            const isSticky = style.position === 'sticky';
            const hasFloatingClass = CONFIG.protectionRules.floatingKeywords.some(kw => 
                element.className.includes(kw) || element.id.includes(kw)
            );

            const rect = element.getBoundingClientRect();
            const edgeDetected = 
                rect.top <= CONFIG.protectionRules.viewportEdgeThreshold ||
                rect.bottom >= window.innerHeight - CONFIG.protectionRules.viewportEdgeThreshold ||
                rect.left <= CONFIG.protectionRules.viewportEdgeThreshold ||
                rect.right >= window.innerWidth - CONFIG.protectionRules.viewportEdgeThreshold;

            if ((isFixed || isSticky || hasFloatingClass) && edgeDetected) {
                AdUtils.safeRemove(element, 'floating-ad', {
                    type: '浮动广告',
                    detail: `定位方式: ${style.position}, 位置: ${Math.round(rect.top)}px`
                });
            }
        }

        checkImageAds(img) {
            if (!DomainConfig.getConfig('imageAdDetection')) return;

            const src = img.src.toLowerCase();
            const isAdImage = CONFIG.protectionRules.adImagePatterns.some(pattern => 
                src.endsWith(pattern) || src.includes('adimg')
            );

            const adDimensions = [
                [728, 90], [300, 250], [336, 280], [250, 250], [120, 600], 
                [160, 600], [468, 60], [900, 100]
            ];
            const isAdSize = adDimensions.some(([w, h]) => 
                Math.abs(img.width - w) < 10 && 
                Math.abs(img.height - h) < 10
            );

            if (isAdImage || isAdSize) {
                AdUtils.safeRemove(img, 'image-ad', {
                    type: '图片广告',
                    detail: `尺寸: ${img.width}x${img.height}, 源: ${src.slice(0, 50)}`
                });
            }
        }

        checkSidebarContainers() {
            if (!DomainConfig.getConfig('sidebarAdDetection')) return;

            document.querySelectorAll(CONFIG.protectionRules.adContainerSelectors.join(',')).forEach(container => {
                const rect = container.getBoundingClientRect();
                const isSidePositioned = 
                    rect.left <= 10 || 
                    rect.right >= window.innerWidth - 10;

                if (isSidePositioned && container.offsetHeight > 200) {
                    AdUtils.safeRemove(container, 'sidebar-ad', {
                        type: '侧边栏容器',
                        detail: `位置: ${Math.round(rect.left)}px, 高度: ${container.offsetHeight}px`
                    });
                }
            });
        }

        checkTextAds(element) {
            if (!DomainConfig.getConfig('textAdDetection')) return;
            
            const text = element.textContent.toLowerCase();
            const isSuspicious = CONFIG.protectionRules.textAdKeywords.some(kw => 
                text.includes(kw.toLowerCase())
            );
            
            if (isSuspicious && element.children.length === 0) {
                AdUtils.safeRemove(element, 'text-ad', {
                    type: '文本广告',
                    detail: `包含关键词: ${text.slice(0,30)}`
                });
            }
        }

        cookieSyncCheck() {
            document.querySelectorAll('script').forEach(script => {
                const src = script.getAttribute('src');
                if (src && src.includes('cookie') && src.includes('sync')) {
                    AdUtils.safeRemove(script, 'cookie-sync', {
                        type: 'Cookie同步',
                        detail: `检测到同步脚本: ${src.slice(0, 50)}`
                    });
                }
            });
        }

        destructor() {
            if (this.observer) this.observer.disconnect();
        }
    }

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

        static getConfig(key) {
            const allConfigs = GM_getValue('domainConfigs', {});
            const domainConfig = allConfigs[this.currentDomain] || {...CONFIG.defaultSettings};
            return key in domainConfig ? domainConfig[key] : CONFIG.defaultSettings[key];
        }

        static updateConfig(key, value) {
            const allConfigs = GM_getValue('domainConfigs', {});
            if (!allConfigs[this.currentDomain]) {
                allConfigs[this.currentDomain] = {...CONFIG.defaultSettings};
            }
            allConfigs[this.currentDomain][key] = value;
            GM_setValue('domainConfigs', allConfigs);
        }

        static resetConfig() {
            const allConfigs = GM_getValue('domainConfigs', {});
            delete allConfigs[this.currentDomain];
            GM_setValue('domainConfigs', allConfigs);
        }
    }

    // ======================= 用户界面 =======================
    class UIController {
        static init() {
            this.registerDomainControls();
            this.registerGlobalControls();
            this.registerInfoButton();
            this.registerMasterSwitch();
        }

        static registerMasterSwitch() {
            const allEnabled = Object.keys(CONFIG.defaultSettings).every(
                key => DomainConfig.getConfig(key)
            );
            GM_registerMenuCommand(
                `🔘 一键${allEnabled ? '禁用' : '启用'}所有模块`,
                () => this.toggleAllModules()
            );
        }

        static toggleAllModules() {
            const allKeys = Object.keys(CONFIG.defaultSettings);
            const currentState = allKeys.every(key => DomainConfig.getConfig(key));
            
            allKeys.forEach(key => {
                DomainConfig.updateConfig(key, !currentState);
            });

            GM_notification({
                title: '总开关已切换',
                text: `所有模块已${!currentState ? '启用' : '禁用'}`,
                timeout: 2000
            });
            
            window.location.reload();
        }

        static registerInfoButton() {
            GM_registerMenuCommand('📘 13模块说明', () => this.showInstructions());
        }

        static showInstructions() {
            const instructions = `【广告终结者v1.3 - 13大模块说明】

██ 核心拦截系统 ██
1. 第三方资源拦截
   ✔ 拦截规则:
     - 非本站域名的<script>/<iframe>
     - 排除白名单中的CDN资源
     - 自动识别跟踪脚本

2. iframe属性检测
   ✔ 拦截规则:
     - 尺寸<2x2像素的隐藏框架
     - src包含50+广告关键词
     - 移除iframe及父容器

3. 父容器检测
   ✔ 拦截规则:
     - 包裹iframe的div/section
     - 无有效内容的空容器
     - 多层嵌套广告容器

██ 动态检测系统 ██
4. 动态ID检测
   ✔ 拦截规则:
     - ID长度>10字符
     - 包含5+连续数字
     - 示例:ad_12345

5. 属性相似度
   ✔ 拦截规则:
     - ID与class相似度>80%
     - 属性值重复率过高
     - 示例:id="ad" class="ad-box"

6. 尺寸异常
   ✔ 拦截规则:
     - 元素面积>屏幕15%
     - 自动计算视窗占比
     - 响应式布局识别

7. 广告属性检测
   ✔ 拦截规则:
     - 12个属性关键词匹配
     - 50+中英文广告词库
     - 支持data-*属性

██ 堆叠拦截系统 ██
8. 高z-index检测
   ✔ 拦截规则:
     - z-index值>50
     - 排除导航菜单等白名单
     - 动态堆叠上下文分析

9. 固定定位检测
   ✔ 拦截规则:
     - fixed元素>2个
     - 识别"回到顶部"等正常元素
     - 支持sticky定位

10. 遮罩层检测
    ✔ 拦截规则:
      - 半透明背景(rgba)
      - 覆盖80%屏幕宽度
      - 防点击劫持

11. 浮动层检测
    ✔ 拦截规则:
      - 边缘吸附<10px
      - 含float/sticky定位
      - 30+中文特征词

██ 增强功能模块 ██
12. 嵌套框架检测
    ✔ 拦截规则:
      - iframe嵌套>3层
      - 空白嵌套框架
      - 跨域嵌套容器

13. 文本广告检测
    ✔ 拦截规则:
      - 包含20+营销关键词
      - 无子元素的纯文本
      - 微信号/QQ号识别`;

            alert(instructions);
        }

        static registerDomainControls() {
            const SEPARATOR = '───────────────';
            GM_registerMenuCommand(SEPARATOR, () => {});

            this.registerModuleControls([
                'thirdpartyCheck',
                'dynamicIdDetection',
                'attributeSimilarity',
                'sizeAnomaly',
                'adAttributeDetection'
            ]);

            GM_registerMenuCommand(SEPARATOR, () => {});

            this.registerModuleControls([
                'iframeAttributeCheck',
                'parentContainerCheck',
                'nestedFrameDetection'
            ]);

            GM_registerMenuCommand(SEPARATOR, () => {});

            this.registerModuleControls([
                'highZIndexDetection',
                'fixedPositionCheck',
                'overlayDetection',
                'floatingLayerCheck'
            ]);

            GM_registerMenuCommand(SEPARATOR, () => {});
            
            this.registerModuleControls([
                'floatingAdDetection',
                'imageAdDetection',
                'sidebarAdDetection',
                'textAdDetection',
                'cookieSyncDetection'
            ]);

            GM_registerMenuCommand(SEPARATOR, () => {});
            
            GM_registerMenuCommand('🔄 重置当前网站设置', () => {
                DomainConfig.resetConfig();
                GM_notification({
                    title: '配置已重置',
                    text: `${DomainConfig.currentDomain} 设置已恢复默认`,
                    timeout: 2000
                });
                window.location.reload();
            });
        }

        static registerModuleControls(keys) {
            keys.forEach(key => {
                const currentValue = DomainConfig.getConfig(key);
                GM_registerMenuCommand(
                    `   ${this.getKeyName(key)} [${currentValue ? '✅' : '❌'}]`,
                    () => this.toggleConfig(key)
                );
            });
        }

        static registerGlobalControls() {
            GM_registerMenuCommand('📜 查看拦截日志', this.showLogs.bind(this));
            GM_registerMenuCommand('🧹 清除当前日志', this.clearLogs.bind(this));
        }

        static toggleConfig(key) {
            const current = DomainConfig.getConfig(key);
            DomainConfig.updateConfig(key, !current);
            GM_notification({
                title: '配置已更新',
                text: `${this.getKeyName(key)} 已${!current ? '启用' : '禁用'}`,
                timeout: 1500
            });
            window.location.reload();
        }

        static getKeyName(key) {
            const names = {
                thirdpartyCheck: '第三方拦截',
                dynamicIdDetection: '动态ID检测',
                attributeSimilarity: '属性相似度',
                sizeAnomaly: '尺寸异常',
                adAttributeDetection: '广告属性检测',
                iframeAttributeCheck: 'iframe属性',
                parentContainerCheck: '父容器检测',
                nestedFrameDetection: '嵌套框架检测',
                highZIndexDetection: '高z-index',
                fixedPositionCheck: '固定定位',
                overlayDetection: '遮罩层检测',
                floatingLayerCheck: '浮动层检测',
                floatingAdDetection: '浮动广告检测',
                imageAdDetection: '图片广告检测',
                sidebarAdDetection: '侧边栏检测',
                textAdDetection: '文本广告检测',
                cookieSyncDetection: 'Cookie同步检测'
            };
            return names[key] || key;
        }

        static showLogs() {
            const logs = Logger.getDomainLogs();
            alert(logs.length ? 
                `【${DomainConfig.currentDomain}】拦截记录:\n\n${Logger.formatLogs(logs)}` : 
                '当前无拦截记录');
        }

        static clearLogs() {
            Logger.clearDomainLogs();
            GM_notification({title: '日志已清除', timeout: 1500});
        }
    }

    // ======================= 日志系统 =======================
    class Logger {
        static logRemoval(record) {
            const allLogs = GM_getValue('removalLogs', {});
            const domainLogs = allLogs[DomainConfig.currentDomain] || [];
            
            domainLogs.push({
                timestamp: new Date().toLocaleString(),
                ...record
            });

            if (domainLogs.length > CONFIG.maxLogs) domainLogs.shift();
            allLogs[DomainConfig.currentDomain] = domainLogs;
            GM_setValue('removalLogs', allLogs);
        }

        static getDomainLogs() {
            const allLogs = GM_getValue('removalLogs', {});
            return allLogs[DomainConfig.currentDomain] || [];
        }

        static clearDomainLogs() {
            const allLogs = GM_getValue('removalLogs', {});
            delete allLogs[DomainConfig.currentDomain];
            GM_setValue('removalLogs', allLogs);
        }

        static formatLogs(logs) {
            return logs.map((log, index) => 
                `[${index + 1}] ${log.timestamp}\n模块: ${log.module}\n` +
                `类型: ${log.reason.type}\n详情: ${log.reason.detail || ''}`
            ).join('\n\n');
        }
    }

    // ======================= 系统初始化 =======================
    let coreCleaner;

    function initialize() {
        if (coreCleaner) coreCleaner.destructor();
        coreCleaner = new CoreCleaner();
    }

    initialize();
    UIController.init();

    // CSS防护规则
    const antiRevertStyle = document.createElement('style');
    antiRevertStyle.textContent = `
        html::-webkit-scrollbar { width: 0 !important }
        [style*="overflow:hidden"] { overflow: visible !important }
        [data-ad-container] { display: none !important }
    `;
    document.head.appendChild(antiRevertStyle);
})();