广告终结者 v1.1

[完美整合] 动态检测、框架过滤、堆叠拦截、第三方拦截四大系统

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

// ==UserScript==
// @name         广告终结者 v1.1
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  [完美整合] 动态检测、框架过滤、堆叠拦截、第三方拦截四大系统
// @author       TMHhz
// @match        *://*/*
// @license      GPLv3
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// ==/UserScript==

(function() {
    'use strict';

    // 核心配置
    const CONFIG = {
        maxLogs: 50,
        adKeywords: ['ad', 'ads', 'advert', 'banner', 'popup', '推广', '广告', 'gg', 'advertisement', 'sponsor', '推荐', 'adv', 'guanggao', 'syad', 'bfad'],
        checkAttributes: ['id', 'class', 'src', 'href', 'data-ad', 'name'],
        protectionRules: {
            minWidth: 50,
            minHeight: 20,
            iframeSizeThreshold: 2,
            zIndexThreshold: 50,
            sizeThresholdRatio: 0.15,
            similarityThreshold: 0.8,
            dynamicIdLength: 10,
            dynamicIdDigits: 5
        },
        whitelist: {
            scriptNamespaces: ['pswMgrDialog', 'userscript-'],
            protectedAttributes: [
                {name: 'id', values: ['pswMgrDialog', 'userscript-quickFill']},
                {name: 'class', values: ['userscript-quickFill']}
            ],
            selectors: ['#pswMgrDialog', '.userscript-quickFill', '.userscript-pswmgrDlg'],
            thirdparty: []
        },
        defaultSettings: {
            // 动态检测系统
            dynamicIdDetection: true,     
            attributeSimilarity: true,    
            sizeAnomaly: true,            
            adAttributeDetection: true,   
            
            // 框架过滤系统
            iframeAttributeCheck: true,   
            parentContainerCheck: true,   
            
            // 堆叠拦截系统
            highZIndexDetection: true,    
            fixedPositionCheck: true,     
            overlayDetection: true,

            // 新增第三方拦截
            thirdpartyCheck: 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.includes(src)) 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.includes(src)) 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 (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) {
                            // Shadow DOM支持
                            if (node.shadowRoot) {
                                this.observer.observe(node.shadowRoot, {
                                    childList: true,
                                    subtree: true
                                });
                            }
                            
                            this.checkGenericElements(node);
                            if (node.tagName === 'IFRAME') this.checkIframes(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));
        }

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

        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);
            }
        }

        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}`
                });
            }
        }

        // ========== 动态检测系统 ==========
        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() {
            document.querySelectorAll('*').forEach(el => {
                const position = getComputedStyle(el).position;
                if (position === 'fixed' && !AdUtils.isWhitelisted(el)) {
                    AdUtils.safeRemove(el, 'fixed-pos', {
                        type: '固定定位',
                        detail: 'position: fixed'
                    });
                }
            });
        }

        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}`
                    });
                }
            });
        }

        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('📘 功能说明', () => this.showInstructions());
        }

        static showInstructions() {
            const instructions = `【广告终结者功能说明】\n\n
            ====== 动态检测系统 ======\n
            1. 动态ID检测
            - 检测长度超过10字符的ID
            - 包含5个以上连续数字的ID
            
            2. 属性相似度
            - 比较ID和class的相似度
            - 相似度阈值:80%
            
            3. 尺寸异常检测
            - 检测超过视窗15%面积的元素
            
            4. 广告属性检测
            - 扫描属性:id/class/src/href/data-ad/name
            
            ====== 框架过滤系统 ======\n
            5. iframe属性检测
            - 检测src包含广告关键词
            
            6. 父容器检测
            - 检查iframe父级容器
            
            ====== 堆叠拦截系统 ======\n
            7. 高z-index检测
            - 阈值:z-index > 50
            
            8. 固定定位检测
            - 检测position: fixed元素
            
            9. 遮罩层检测
            - 背景色不透明检测

            ====== 第三方拦截系统 ======\n
            10. 跨域资源拦截
            - 自动识别主域名
            - 拦截第三方脚本和iframe

            【系统配置】
            - 各模块独立开关
            - 域名级配置存储`;

            alert(instructions.replace(/ {4,}/g, ''));
        }

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

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

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

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

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

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

            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: '父容器检测',
                highZIndexDetection: '高z-index',
                fixedPositionCheck: '固定定位',
                overlayDetection: '遮罩层检测'
            };
            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();
})();

QingJ © 2025

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