广告终结者优化增强版(完整功能)

完整功能广告拦截器,支持内容关键词过滤与智能检测

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

// ==UserScript==
// @name         广告终结者优化增强版(完整功能)
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  完整功能广告拦截器,支持内容关键词过滤与智能检测
// @author       TMHhz
// @match        *://*/*
// @license      GPLv3
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @connect      self
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 核心配置对象
    const CONFIG = {
        maxLogs: 150,
        adKeywords: [
            'ad', 'ads', 'advert', 'banner', 'popup', '推广', '广告', 'gg', 'adv',
            'guanggao', 'syad', 'bfad', '弹窗', '悬浮', '浮窗', 'fixed', 'sticky'
        ],
        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 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();

            if (whitelist.some(k => text.includes(k))) return false;
            return blacklist.some(k => text.includes(k));
        }

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

        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.initObservers();
            this.initialClean();
            this.setupContentScanner();
            this.injectProtectionStyles();
        }

        initObservers() {
            new MutationObserver(mutations => {
                mutations.forEach(m => {
                    m.addedNodes.forEach(n => {
                        if(n.nodeType === 1) this.processElement(n);
                    });
                });
            }).observe(document, {childList: true, subtree: true});
        }

        initialClean() {
            this.checkElements('*', el => this.processElement(el));
            this.checkIframes();
            this.checkThirdParty();
            this.scanForContent(document.documentElement);
        }

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

        setupContentScanner() {
            if (!Config.get('contentFilter')) return;
            
            new MutationObserver(mutations => {
                mutations.forEach(m => {
                    m.addedNodes.forEach(n => {
                        if (n.nodeType === 1) this.scanForContent(n);
                    });
                });
            }).observe(document, {
                childList: true,
                subtree: true
            });
        }

        scanForContent(element) {
            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 
                }
                iframe[src*="ad"] { 
                    display: none !important 
                }
                .ad-shield-protected {
                    border: 2px solid #4CAF50 !important;
                }
            `);
        }

        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(',')
            );
        }
    }

    // ======================= 用户界面 =======================
    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());
        }

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

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

QingJ © 2025

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