Greasy Fork

Porn Blocker | 色情内容过滤器

A powerful content blocker that helps protect you from inappropriate websites. Features: Auto-detection of adult content, Multi-language support, Smart scoring system, Safe browsing protection.

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

// ==UserScript==
// @name         Porn Blocker | 色情内容过滤器
// @name:en      Porn Blocker
// @name:zh-CN   色情内容过滤器
// @name:zh-TW   色情內容過濾器
// @name:zh-HK   色情內容過濾器
// @name:ja      アダルトコンテンツブロッカー
// @name:ko      성인 컨텐츠 차단기
// @name:ru      Блокировщик порнографии
// @namespace    https://noctiro.moe
// @version      2.0.3
// @description     A powerful content blocker that helps protect you from inappropriate websites. Features: Auto-detection of adult content, Multi-language support, Smart scoring system, Safe browsing protection.
// @description:en     A powerful content blocker that helps protect you from inappropriate websites. Features: Auto-detection of adult content, Multi-language support, Smart scoring system, Safe browsing protection.
// @description:zh-CN 强大的网页过滤工具,帮助你远离不良网站。功能特点:智能检测色情内容,多语言支持,评分系统,安全浏览保护,支持自定义过滤规则。为了更好的网络环境,从我做起。
// @description:zh-TW 強大的網頁過濾工具,幫助你遠離不良網站。功能特點:智能檢測色情內容,多語言支持,評分系統,安全瀏覽保護,支持自定義過濾規則。為了更好的網絡環境,從我做起。
// @description:zh-HK 強大的網頁過濾工具,幫助你遠離不良網站。功能特點:智能檢測色情內容,多語言支持,評分系統,安全瀏覽保護,支持自定義過濾規則。為了更好的網絡環境,從我做起。
// @description:ja   アダルトコンテンツを自動的にブロックする強力なツールです。機能:アダルトコンテンツの自動検出、多言語対応、スコアリングシステム、カスタマイズ可能なフィルタリング。より良いインターネット環境のために。
// @description:ko   성인 컨텐츠를 자동으로 차단하는 강력한 도구입니다. 기능: 성인 컨텐츠 자동 감지, 다국어 지원, 점수 시스템, 안전 브라우징 보호, 맞춤형 필터링 규칙。
// @description:ru   Мощный инструмент для блокировки неприемлемого контента. Функции: автоматическое определение, многоязычная поддержка, система оценки, настраиваемые правила фильтрации。
// @license      Apache-2.0
// @match        *://*/*
// @run-at       document-start
// @run-at       document-end
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // 多语言支持
    const i18n = {
        'en': {
            title: '🚫 Access Blocked',
            message: 'This webpage has been identified as inappropriate content.',
            redirect: 'Redirecting in <span class="countdown">4</span> seconds...',
            footer: 'Stay healthy · Stay away from harmful content'
        },
        'zh-CN': {
            title: '🚫 访问已被拦截',
            message: '该网页已被识别为不健康内容。',
            redirect: '<span class="countdown">4</span> 秒后自动跳转...',
            footer: '注意身心健康 · 远离不良网站'
        },
        'zh-TW': {
            title: '🚫 訪問已被攔截',
            message: '該網頁已被識別為不健康內容。',
            redirect: '<span class="countdown">4</span> 秒後自動跳轉...',
            footer: '注意身心健康 · 遠離不良網站'
        },
        'zh-HK': {
            title: '🚫 訪問已被攔截',
            message: '該網頁已被識別為不健康內容。',
            redirect: '<span class="countdown">4</span> 秒後自動跳轉...',
            footer: '注意身心健康 · 遠離不良網站'
        },
        'ja': {
            title: '🚫 アクセスがブロックされました',
            message: 'このページは不適切なコンテンツとして識別されました。',
            redirect: '<span class="countdown">4</span> 秒後にリダイレクトします...',
            footer: '健康に注意 · 有害サイトに近づかない'
        },
        'ko': {
            title: '🚫 접근이 차단됨',
            message: '이 웹페이지가 부적절한 콘텐츠로 식별되었습니다.',
            redirect: '<span class="countdown">4</span>초 후 자동으로 이동됩니다...',
            footer: '건강 관리 · 유해 사이트 멀리하기'
        },
        'ru': {
            title: '🚫 Доступ заблокирован',
            message: 'Эта веб-страница определена как неподходящий контент.',
            redirect: 'Перенаправление через <span class="countdown">4</span> секунды...',
            footer: 'Будьте здоровы · Держитесь подальше от вредного контента'
        }
    };

    // 获取用户语言
    const getUserLanguage = () => {
        const lang = navigator.language || navigator.userLanguage;

        // 检查完整语言代码
        if (i18n[lang]) return lang;

        // 处理中文的特殊情况
        if (lang.startsWith('zh')) {
            const region = lang.toLowerCase();
            if (region.includes('tw') || region.includes('hant')) return 'zh-TW';
            if (region.includes('hk')) return 'zh-HK';
            return 'zh-CN';
        }

        // 检查简单语言代码
        const shortLang = lang.split('-')[0];
        if (i18n[shortLang]) return shortLang;

        return 'en';
    };

    // 浏览器检测函数
    const getBrowserType = () => {
        const ua = navigator.userAgent.toLowerCase();

        // Add more browser detection
        if (ua.includes('ucbrowser')) return 'uc';
        if (ua.includes('qqbrowser')) return 'qq';
        if (ua.includes('2345explorer')) return '2345';
        if (ua.includes('360') || ua.includes('qihu')) return '360';
        if (ua.includes('maxthon')) return 'maxthon';
        if (ua.includes('firefox')) return 'firefox';
        if (ua.includes('edg')) return 'edge';
        if (ua.includes('opr') || ua.includes('opera')) return 'opera';
        if (ua.includes('brave')) return 'brave';
        if (ua.includes('vivaldi')) return 'vivaldi';
        if (ua.includes('yabrowser')) return 'yandex';
        if (ua.includes('chrome')) return 'chrome';
        if (ua.includes('safari') && !ua.includes('chrome')) return 'safari';

        return 'other';
    };

    // 获取浏览器主页URL
    const getHomePageUrl = () => {
        switch (getBrowserType()) {
            case 'firefox':
                return 'about:home';
            case 'chrome':
                return 'chrome://newtab';
            case 'edge':
                return 'edge://newtab';
            case 'safari':
                return 'topsites://';
            case 'opera':
                return 'opera://startpage';
            case 'brave':
                return 'brave://newtab';
            case 'vivaldi':
                return 'vivaldi://newtab';
            case 'yandex':
                return 'yandex://newtab';
            case 'uc':
                return 'ucenterhome://';
            case 'qq':
                return 'qbrowser://home';
            case '360':
                return 'se://newtab';
            case 'maxthon':
                return 'mx://newtab';
            case '2345':
                return '2345explorer://newtab';
            default:
                // Fallback to a safe default
                return 'about:blank';
        }
    };

    // ----------------- 预编译正则规则 (性能优化) -----------------
    const regexCache = {
        // 色情关键词正则(预编译,避免重复生成)
        pornRegex: null,
        // 白名单正则(预编译)
        whitelistRegex: null,
        // .xxx后缀正则
        xxxRegex: /\.xxx$/i
    };

    // ----------------- 配置项(用户可按需修改) -----------------
    const config = {
        // ================== 域名专用黑名单词汇 ==================
        domainKeywords: {
            // 常见成人网站域名关键词(权重4)
            'pornhub': 4, 'xvideo': 4, 'redtube': 4,
            'xnxx': 4, 'xhamster': 4, '4tube': 4,
            'youporn': 4, 'spankbang': 4,
            'myfreecams': 4, 'missav': 4,
            'rule34': 4, 'youjizz': 4,
            'onlyfans': 4, 'paidaa': 4,
            '3c5g9': 4,

            // 核心违规词(权重3-4)
            'porn': 3, 'nsfw': 3, 'hentai': 3,
            'incest': 4, 'rape': 4, 'childporn': 4,

            // 身体部位关键词(权重2)
            'pussy': 2, 'cock': 2, 'dick': 2,
            'boobs': 2, 'tits': 2, 'ass': 2,
            'beaver': 1,

            // 特定群体(权重2-3)
            'teen': 3, 'sis': 2, 'milf': 2,
            'cuckold': 3, 'virgin': 2, 'luoli': 2,
            'gay': 2,

            // 具体违规行为(权重2-3)
            'anal': 3, 'blowjob': 3, 'creampie': 2,
            'bdsm': 2, 'masturbat': 2, 'handjob': 3,
            'footjob': 3, 'rimjob': 3,

            // 其他相关词汇(权重1-2)
            'camgirl': 2, 'webcam': 2, 'leaked': 2,
            'fap': 2, 'erotic': 1, 'escort': 3,
            'nude': 3, 'naked': 3, 'upskirt': 2,

            // 特定地区成人站点域名特征(权重4)
            'jav': 4,

            // 域名变体检测(权重3)
            'p0rn': 3, 'pr0n': 3, 'pron': 3,
            's3x': 3, 'sexx': 3,
        },

        // ================== 内容检测关键词 ==================
        contentKeywords: {
            // 核心违规词(权重3-4)
            'porn': 3, 'nsfw': 3, 'hentai': 3,
            'incest': 4, 'rape': 4, 'childporn': 4,

            // 身体部位关键词(权重2)
            'pussy': 2, 'cock': 2, 'dick': 2,
            'boobs': 2, 'tits': 2, 'ass': 2,
            'beaver': 1,

            // 特定群体(权重2-3)
            'teen': 3, 'sis': 2, 'milf': 2,
            'cuckold': 3, 'virgin': 2, 'luoli': 2,
            'gay': 2,

            // 具体违规行为(权重2-3)
            'anal': 3, 'blowjob': 3, 'creampie': 2,
            'bdsm': 2, 'masturbat': 2, 'handjob': 3,
            'footjob': 3, 'rimjob': 3,

            // 其他相关词汇(权重1-2)
            'camgirl': 2, 'webcam': 2, 'leaked': 2,
            'fap': 2, 'erotic': 1, 'escort': 3,
            'nude': 3, 'naked': 3, 'upskirt': 2,
            'onlyfans': 3, '海角社区': 4,

            // 多语言支持
            '情色': 3, '成人': 3, '做爱': 4,
            'セックス': 3, 'エロ': 3, '淫': 4,
            'секс': 3, 'порн': 3, '性爱': 3,
            '無修正': 3, 'ポルノ': 3, 'порно': 3,
            '色情': 3, '骚': 1, '啪啪': 2,
            '自慰': 3, '口交': 3, '肛交': 3,
            '吞精': 3, '诱惑': 1, '全裸': 3,
            '内射': 3, '乳交': 3, '射精': 3,
            '反差': 0.5, '调教': 1.5, '性交': 3,
            '性奴': 3, '高潮': 0.3, '白虎': 0.8,
            '少女': 0.1, '女友': 0.1, '狂操': 3,
            '捆绑': 0.1, '约炮': 3, '鸡吧': 3,
            '鸡巴': 3, '阴茎': 1, '阴道': 1,
            '女优': 3, '裸体': 3, '男优': 3,
            '乱伦': 3, '偷情': 2, '母狗': 3,
            '内射': 4, '喷水': 0.8, '潮吹': 3,
            '轮奸': 2, '少妇': 2, '熟女': 2,
        },

        // ================== 白名单减分规则 ==================
        whitelist: {
            // 强豁免词(权重-30)
            'edu': -30, 'health': -30, 'medical': -30, 'science': -30,
            'gov': -30, 'org': -30, 'official': -30,

            // 常用场景豁免(权重-15)
            'academy': -15, 'clinic': -15, 'therapy': -15,
            'university': -4, 'research': -15, 'news': -15,
            'dictionary': -15, 'library': -15, 'museum': -15,

            // 动物/自然相关(权重-1)
            'animal': -4, 'zoo': -1, 'cat': -1, 'dog': -1,
            'pet': -6, 'bird': -1, 'vet': -1,

            // 科技类(权重-5)
            'tech': -5, 'cloud': -5, 'software': -5, 'cyber': -3,
        },

        // ================== 阈值配置 ==================
        thresholds: {
            // 总分触发阈值(建议3~4)
            block: 3,
            // URL路径加分阈值
            path: 2,
            // 进行白名单减分的最低阈值
            whitelist: 2
        },

        // ================== 域名正则表达式规则 ==================
        domainPatterns: [
            /^mogu\d+\.[a-z]{2,3}$/i,
        ],

        // ================== 内容检测规则 ==================
        // 需要内容检测的域名规则
        contentCheckDomains: [
            /\d{3}[a-z]{2,3}/i,
            /[a-z]{2,3}\d{2,3}/i,
            /[a-z]{1,3}\d{0,3}[a-z]{1,3}/i,
        ],

        // 内容检测相关配置
        contentCheck: {
            // 成人内容分数
            adultContentThreshold: 20,
            suspiciousTagNames: [
                // 主要内容区域
                'article', 'main', 'section', 'content',
                // 文本块
                'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                // 列表和表格
                'li', 'td', 'th', 'figcaption',
                // 链接和按钮文本
                'a', 'button',
                // 通用容器
                'div.content', 'div.text', 'div.description',
                'span.text', 'span.content'
            ],
            // 文本节点最小长度
            textNodeMinLength: 5,
            // 防抖等待时间(毫秒) 
            debounceWait: 1000,
            // 观察者最大运行时间(毫秒)
            observerTimeout: 30000,
        }
    };

    // 域名正则检测函数
    const checkDomainPatterns = (hostname) => {
        return config.domainPatterns.some(pattern => pattern.test(hostname));
    };

    // 检查是否需要进行内容检测
    const shouldCheckContent = (hostname) => {
        return config.contentCheckDomains.some(pattern => pattern.test(hostname));
    };

    // 内容检测辅助函数
    const contentUtils = {
        // 优化文本获取算法
        getAllText: (element) => {
            if (!element) return "";
            
            // 使用Set去重
            const textSet = new Set();
            
            try {
                const walker = document.createTreeWalker(
                    element,
                    NodeFilter.SHOW_TEXT,
                    {
                        acceptNode: (node) => {
                            const parent = node.parentElement;
                            
                            // 优化过滤条件
                            if (!parent || 
                                /^(SCRIPT|STYLE|NOSCRIPT|IFRAME|META|LINK)$/i.test(parent.tagName) ||
                                parent.hidden ||
                                getComputedStyle(parent).display === 'none') {
                                return NodeFilter.FILTER_REJECT;
                            }
                            
                            const text = node.textContent.trim();
                            if (!text || text.length < config.contentCheck.textNodeMinLength) {
                                return NodeFilter.FILTER_REJECT;
                            }
                            
                            return NodeFilter.FILTER_ACCEPT;
                        }
                    }
                );

                let node;
                while (node = walker.nextNode()) {
                    textSet.add(node.textContent.trim());
                }
            } catch (e) {
                console.error('Error in getAllText:', e);
            }

            return Array.from(textSet).join(' ');
        },

        // 优化可疑元素获取
        getSuspiciousElements: () => {
            try {
                const elements = new Set();
                
                // 使用更高效的选择器
                const fastSelectors = [
                    'article', 'main', '.content', 
                    '[class*="content"]', '[class*="text"]',
                    'h1', 'h2', 'h3'
                ];
                
                fastSelectors.forEach(selector => {
                    document.querySelectorAll(selector).forEach(el => elements.add(el));
                });
                
                return Array.from(elements);
            } catch (e) {
                console.error('Error in getSuspiciousElements:', e);
                return [];
            }
        }
    };

    // 优化内容检测函数
    function detectAdultContent() {
        let totalScore = 0;
        
        try {
            // 使用WeakMap缓存已计算的元素分数
            const scoreCache = new WeakMap();
            
            // 主要内容区域检测
            const mainElements = contentUtils.getSuspiciousElements();
            for (const element of mainElements) {
                if (scoreCache.has(element)) {
                    totalScore += scoreCache.get(element);
                    continue;
                }
                
                const text = contentUtils.getAllText(element);
                const score = calculateScore(text);
                scoreCache.set(element, score);
                totalScore += score;
                
                // 提前退出优化
                if (totalScore >= config.contentCheck.adultContentThreshold) {
                    return true;
                }
            }
            
            // 优化图片检测
            const images = document.querySelectorAll('img[alt], img[title]');
            for (const img of images) {
                const imgText = `${img.alt} ${img.title}`.trim();
                if (imgText) {
                    totalScore += calculateScore(imgText) * 0.5; // 降低图片文本权重
                }
            }
            
            // 优化元数据检测
            const metaTags = document.querySelectorAll('meta[name="description"], meta[name="keywords"]');
            for (const meta of metaTags) {
                const content = meta.content;
                if (content) {
                    totalScore += calculateScore(content) * 0.3; // 降低元数据权重
                }
            }
            
        } catch (e) {
            console.error('Error in detectAdultContent:', e);
            return false;
        }
        
        return totalScore >= config.contentCheck.adultContentThreshold;
    }

    // Refactored content detector using helper function
    const checkPageContent = () => {
        return detectAdultContent();
    };

    // 预处理正则(仅初始化一次)
    (function initRegex() {
        // 域名关键词正则
        const domainTerms = Object.keys(config.domainKeywords).join('|');
        regexCache.domainRegex = new RegExp(`(${domainTerms})`, 'gi');

        // 内容关键词正则
        const contentTerms = Object.keys(config.contentKeywords).join('|');
        regexCache.contentRegex = new RegExp(`(${contentTerms})`, 'gi');

        // 白名单正则
        const whitelistTerms = Object.keys(config.whitelist).join('|');
        regexCache.whitelistRegex = new RegExp(`(${whitelistTerms})`, 'gi');
    })();

    // Helper function to sum weights from regex matches
    function sumMatches(text, regex, weightMap) {
        return (text.match(regex) || []).reduce((total, match) => {
            return total + (weightMap[match.toLowerCase()] || 0);
        }, 0);
    }

    // 优化后的评分计算函数
    const calculateScore = (text, isDomain = false) => {
        let score = isDomain
            ? sumMatches(text, regexCache.domainRegex, config.domainKeywords)
            : sumMatches(text, regexCache.contentRegex, config.contentKeywords);

        if (score >= config.thresholds.whitelist) {
            score += sumMatches(text, regexCache.whitelistRegex, config.whitelist);
        }

        return score;
    };

    // 添加内容检测防抖函数
    const debounce = (func, wait) => {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    };

    // 检测结果处理函数
    const handleBlockedContent = () => {
        const lang = getUserLanguage();
        const text = i18n[lang];
        window.stop();
        document.documentElement.innerHTML = `
            <body>
                <div class="container">
                    <div class="card">
                        <div class="icon-wrapper">
                            <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
                            </svg>
                        </div>
                        <h1>${text.title}</h1>
                        <p>${text.message}<br>${text.redirect}</p>
                        <div class="footer">${text.footer}</div>
                    </div>
                </div>
                <style>
                    :root {
                        --bg-light: #f0f2f5;
                        --card-light: #ffffff;
                        --text-light: #2d3436;
                        --text-secondary-light: #636e72;
                        --text-muted-light: #b2bec3;
                        --accent-light: #ff4757;
 
                        --bg-dark: #1a1a1a;
                        --card-dark: #2d2d2d;
                        --text-dark: #ffffff;
                        --text-secondary-dark: #a0a0a0;
                        --text-muted-dark: #808080;
                        --accent-dark: #ff6b6b;
                    }
 
                    @media (prefers-color-scheme: dark) {
                        body {
                            background: var(--bg-dark) !important;
                        }
                        .card {
                            background: var(--card-dark) !important;
                            box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
                        }
                        h1 { color: var(--text-dark) !important; }
                        p { color: var(--text-secondary-dark) !important; }
                        .footer { color: var(--text-muted-dark) !important; }
                        .icon-wrapper {
                            background: var(--accent-dark) !重要;
                        }
                        .countdown {
                            color: var(--accent-dark);
                        }
                    }
 
                    body {
                        background: var(--bg-light);
                        margin: 0;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
                        min-height: 100vh;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                    }
 
                    .container {
                        max-width: 500px;
                        width: 100%;
                    }
 
                    .card {
                        background: var(--card-light);
                        border-radius: 16px;
                        box-shadow: 0 4px 12px rgba(0,0,0,0.1);
                        padding: 32px;
                        text-align: center;
                        animation: slideIn 0.5s ease-out;
                    }
 
                    .icon-wrapper {
                        width: 64px;
                        height: 64px;
                        background: var(--accent-light);
                        border-radius: 50%;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        margin: 0 auto 24px;
                        animation: pulse 2s infinite;
                    }
 
                    .icon-wrapper svg {
                        stroke: white;
                    }
 
                    h1 {
                        color: var(--text-light);
                        margin: 0 0 16px;
                        font-size: 24px;
                        font-weight: 600;
                    }
 
                    p {
                        color: var(--text-secondary-light);
                        margin: 0 0 24px;
                        line-height: 1.6;
                        font-size: 16px;
                    }
 
                    .footer {
                        color: var(--text-muted-light);
                        font-size: 14px;
                        animation: fadeIn 1s ease-out;
                    }
 
                    .countdown {
                        font-weight: bold;
                        color: var(--accent-light);
                    }
 
                    @keyframes slideIn {
                        from { transform: translateY(20px); opacity: 0; }
                        to { transform: translateY(0); opacity: 1; }
                    }
 
                    @keyframes pulse {
                        0% { transform: scale(1); }
                        50% { transform: scale(1.05); }
                        100% { transform: scale(1); }
                    }
 
                    @keyframes fadeIn {
                        from { opacity: 0; }
                        to { opacity: 1; }
                    }
                </style>
            </body>
        `;
        let timeLeft = 4;
        const countdownEl = document.querySelector('.countdown');
        const countdownInterval = setInterval(() => {
            timeLeft--;
            if (countdownEl) countdownEl.textContent = timeLeft;
            if (timeLeft <= 0) {
                clearInterval(countdownInterval);
                try {
                    const homeUrl = getHomePageUrl();
                    if (window.history.length > 1) {
                        const iframe = document.createElement('iframe');
                        iframe.style.display = 'none';
                        document.body.appendChild(iframe);

                        iframe.onload = () => {
                            try {
                                const prevUrl = iframe.contentWindow.location.href;
                                const prevScore = calculateScore(new URL(prevUrl).hostname, true);

                                if (prevScore >= config.thresholds.block) {
                                    window.location.href = homeUrl;
                                } else {
                                    window.history.back();
                                }
                            } catch (e) {
                                window.location.href = homeUrl;
                            }
                            document.body.removeChild(iframe);
                        };

                        iframe.src = 'about:blank';
                    } else {
                        window.location.href = homeUrl;
                    }
                } catch (e) {
                    window.location.href = getHomePageUrl();
                }
            }
        }, 1000);
    };

    // 优化动态内容检测
    const setupDynamicContentCheck = () => {
        if (!document.body) return;

        let pendingCheck = false;
        let observer = null;

        const checkContent = debounce(() => {
            if (pendingCheck) return;
            pendingCheck = true;

            try {
                const hostname = window.location.hostname;
                if (detectAdultContent()) {
                    blacklistManager.addToBlacklist(hostname);
                    observer?.disconnect();
                    handleBlockedContent();
                }
            } finally {
                pendingCheck = false;
            }
        }, config.contentCheck.debounceWait);

        try {
            observer = new MutationObserver((mutations) => {
                // 过滤无关变化
                const hasRelevantChanges = mutations.some(mutation => {
                    return mutation.addedNodes.length > 0 || 
                           (mutation.type === 'characterData' && 
                            mutation.target.textContent.trim().length >= config.contentCheck.textNodeMinLength);
                });

                if (hasRelevantChanges) {
                    checkContent();
                }
            });

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

            // 清理机制
            setTimeout(() => {
                observer?.disconnect();
                observer = null;
            }, config.contentCheck.observerTimeout);

        } catch (e) {
            console.error('Error in setupDynamicContentCheck:', e);
        }

        return observer;
    };

    // ----------------- 黑名单储存优化 -----------------
    const blacklistManager = {
        BLACKLIST_KEY: 'pornblocker-blacklist',
        BLACKLIST_VERSION: '1',  // 添加版本控制
        
        // 获取黑名单
        getBlacklist() {
            try {
                const data = localStorage.getItem(this.BLACKLIST_KEY);
                if (!data) return [];
                
                const parsed = JSON.parse(data);
                return Array.isArray(parsed) ? parsed : [];
            } catch (e) {
                console.error('Error reading blacklist:', e);
                return [];
            }
        },
        
        // 添加到黑名单
        addToBlacklist(hostname) {
            try {
                if (!hostname) return false;
                
                const blacklist = this.getBlacklist();
                if (blacklist.includes(hostname)) return true;
                
                blacklist.push(hostname);
                localStorage.setItem(this.BLACKLIST_KEY, JSON.stringify(blacklist));
                return true;
            } catch (e) {
                console.error('Error adding to blacklist:', e);
                return false;
            }
        },
        
        // 检查是否在黑名单中
        isBlacklisted(hostname) {
            try {
                if (!hostname) return false;
                
                const blacklist = this.getBlacklist();
                return blacklist.includes(hostname);
            } catch (e) {
                console.error('Error checking blacklist:', e);
                return false;
            }
        },
        
        // 从黑名单中移除
        removeFromBlacklist(hostname) {
            try {
                const blacklist = this.getBlacklist();
                const index = blacklist.indexOf(hostname);
                if (index > -1) {
                    blacklist.splice(index, 1);
                    localStorage.setItem(this.BLACKLIST_KEY, JSON.stringify(blacklist));
                    return true;
                }
                return false;
            } catch (e) {
                console.error('Error removing from blacklist:', e);
                return false;
            }
        }
    };

    // ----------------- 主检测逻辑 -----------------
    const checkUrl = () => {
        const url = new URL(window.location.href);
        const hostname = url.hostname;

        // 优化黑名单检查
        if (blacklistManager.isBlacklisted(hostname)) {
            return {
                shouldBlock: true,
                url: url,
                reason: 'blacklist'
            };
        }

        // 如果域名匹配正则
        if (checkDomainPatterns(url.hostname)) {
            return {
                shouldBlock: true,
                url: url
            };
        }

        // 检查是否需要进行内容检测
        if (shouldCheckContent(url.hostname)) {
            if (document.body) {
                const hasAdultContent = checkPageContent();
                if (hasAdultContent) {
                    blacklistManager.addToBlacklist(hostname);
                    return {
                        shouldBlock: true,
                        url: url,
                        reason: 'content'
                    };
                }
                setupDynamicContentCheck();
            } else {
                document.addEventListener('DOMContentLoaded', () => {
                    if (checkPageContent()) {
                        blacklistManager.addToBlacklist(hostname);
                        handleBlockedContent();
                    }
                    setupDynamicContentCheck();
                });
            }
        }

        let score = 0;

        // 检查域名
        const pornMatches = url.hostname.match(regexCache.domainRegex) || [];
        pornMatches.forEach(match => {
            const keyword = match.toLowerCase();
            score += config.domainKeywords[keyword] || 0;
        });

        // 检查路径
        const path = url.pathname + url.search;
        const pathScore = calculateScore(path) * 0.4;
        score += pathScore;

        // 检查标题
        const titlePornScore = calculateScore(document.title || "");
        score += titlePornScore;

        // 如果超过阈值则进行白名单扣分
        if (score >= config.thresholds.whitelist) {
            const hostMatches = url.hostname.match(regexCache.whitelistRegex) || [];
            const titleMatches = document.title.match(regexCache.whitelistRegex) || [];
            const whitelistMatchCount = (matches) => {
                matches.forEach(match => {
                    const term = match.toLowerCase();
                    score += config.whitelist[term] || 0;
                });
            };
            whitelistMatchCount(hostMatches);
            whitelistMatchCount(titleMatches);
        }

        return {
            shouldBlock: score >= config.thresholds.block,
            url: url
        };
    };

    // 获取结果
    const { shouldBlock, url: currentUrl, reason } = checkUrl();

    if (shouldBlock || regexCache.xxxRegex.test(currentUrl.hostname)) {
        handleBlockedContent();
    }
})();