Greasy Fork

Greasy Fork is available in English.

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-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.7
// @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
// @grant        chrome.storage.sync
// @grant        chrome.storage.local
// ==/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,
            'haijiao': 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)
            'cuckold': 3, 'virgin': 2, 'luoli': 2,
            'gay': 2,

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

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

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

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

        // ================== 内容检测关键词 ==================
        contentKeywords: {
            // 核心违规词(权重3-4)- 严格边界检测
            '\\b(?:po*r*n|pr[o0]n)\\b': 3,     // porn及其变体
            'nsfw': 3,
            '\\bhentai\\b': 3,
            '\\binces*t\\b': 4,
            '\\br[a@]pe\\b': 4,
            '(?:child|kid|teen)(?:po*r*n)': 4,
            '海角社区': 4,

            // 身体部位关键词(权重2)- 优化边界和上下文检测
            'puss(?:y|ies)\\b': 2,
            '\\bco*ck(?:s)?(?!tail|roach|pit|er)\\b': 2,  // 排除cocktail等
            '\\bdick(?:s)?(?!ens|tionary|tate)\\b': 2,    // 排除dickens等
            '\\bb[o0]{2,}bs?\\b': 2,
            '\\btits?\\b': 2,
            '(?<!cl|gl|gr|br|m|b|h)ass(?:es)?(?!ign|et|ist|ume|ess|ert|embl|oci|ault|essment|emble|ume|uming|ured)\\b': 2,  // 优化ass检测
            '\\bbeaver(?!s\\s+dam)\\b': 1,  // 排除海狸相关

            // 特定群体(权重2-3)- 上下文敏感
            '\\bteen(?!age\\s+mutant)\\b': 3,   // 排除 Teenage Mutant
            '\\bsis(?!ter|temp)\\b': 2,         // 排除 sister, system
            '\\bmilfs?\\b': 2,
            '\\bcuck[o0]ld\\b': 3,
            '\\bvirgins?(?!ia|\\s+islands?)\\b': 2,  // 排除地名
            'lu[o0]li': 2,
            '\\bg[a@]y(?!lord|le|le\\s+storm)\\b': 2,  // 排除人名

            // 具体违规行为(权重2-3)- 严格检测
            '\\banal(?!ys[it]|og)\\b': 3,       // 排除analysis等
            '\\bbl[o0]w\\s*j[o0]b\\b': 3,
            'cream\\s*pie(?!\\s+recipe)\\b': 2,  // 排除食物相关
            '\\bbdsm\\b': 2,
            'masturba?t(?:ion|e|ing)\\b': 2,
            '\\bhand\\s*j[o0]b\\b': 3,
            '\\bf[o0]{2}t\\s*j[o0]b\\b': 3,
            '\\brim\\s*j[o0]b\\b': 3,

            // 新增违规行为(权重2-3)
            '\\bstr[i1]p(?:p(?:er|ing)|tease)\\b': 3,
            '\\bh[o0]{2}ker(?:s)?\\b': 3,
            'pr[o0]st[i1]tut(?:e|ion)\\b': 3,
            'b[o0]{2}ty(?!\\s+call)\\b': 2,     // 排除 booty call
            'sp[a@]nk(?:ing)?\\b': 2,
            'deepthroat': 3,
            'bukk[a@]ke': 3,
            'org(?:y|ies)\\b': 3,
            'gangbang': 3,
            'thr[e3]{2}s[o0]me': 2,
            'c[u|v]msh[o0]t': 3,
            'f[e3]tish': 2,

            // 其他相关词汇(权重1-2)- 上下文敏感
            '\\bcamgirls?\\b': 2,
            '\\bwebcam(?!era)\\b': 2,           // 排除webcamera
            '\\ble[a@]ked(?!\\s+(?:pipe|gas|oil))\\b': 2,  // 排除工程相关
            '\\bf[a@]p(?:p(?:ing)?)?\\b': 2,
            '\\ber[o0]tic(?!a\\s+books?)\\b': 1, // 排除文学相关
            '\\besc[o0]rt(?!\\s+mission)\\b': 3, // 排除游戏相关
            '\\bnude(?!\\s+color)\\b': 3,        // 排除色彩相关
            'n[a@]ked(?!\\s+juice)\\b': 3,       // 排除品牌
            '\\bupskirt\\b': 2,
            '\\b[o0]nlyfans\\b': 3,

            // 多语言支持 (按原有配置)
            '情色': 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,

            // 新增中文词汇(更细致的分级)
            '色情': 3, '情色': 3, '黄色': 2,
            '淫(?:秽|荡|乱|贱|液|穴|水)': 4,
            '肉(?:棒|根|穴|缝|臀|奶|体|欲)': 3,
            '(?:巨|大|小|翘|白|圆|肥)(?:乳|臀|胸)': 2,
            '(?:舔|添|吸|吮|插|干|操|草|日|艹)(?:穴|逼|屄|阴|蜜|菊|屌|鸡|肉)': 4,
            '(?:销|骚|浪|淫)(?:魂|女|货|逼|贱|荡)': 3,

            // 新增日语词汇
            'オナニー': 3,      // 自慰
            '手コキ': 3,       // 手淫
            'パイズリ': 3,     // 乳交
            '中出し': 4,       // 中出
            '素人': 2,         // 素人
            'アヘ顔': 3,       // 阿黑颜
            '痴女': 3,         // 痴女
            '処女': 2,         // 处女

            // 新增韩语词汇
            '섹스': 3,         // 性
            '야동': 3,         // 成人视频
            '자위': 2,         // 自慰
            '음란': 3,         // 淫乱
            '성인': 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,
            /^mg\d+\.app$/i,
        ],

        // ================== 内容检测规则 ==================
        // 需要内容检测的域名规则
        contentCheckDomains: [
            /\d{3}[a-z]{2,3}/i,
            /[a-z]{2,3}\d{2,3}/i,
            /[a-z]{1,3}\d{1,3}[a-z]{1,3}/i,
            // 海角社区
            /^[a-z0-9]{0,5}(\.[a-z0-9]{0,5})?h[a-z0-9]{0,5}j[a-z0-9]{0,5}(\.[a-z]{0,5})?/i,
        ],

        // 内容检测相关配置
        contentCheck: {
            // 成人内容分数
            adultContentThreshold: 25,
            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,
            // 添加局部内容检测配置
            localizedCheck: {
                // 单个元素的内容阈值,超过此值才会影响整体评分
                elementThreshold: 8,
                // 需要触发的违规元素数量
                minViolationCount: 3,
                // 违规内容占总内容的比例阈值
                violationRatio: 0.3,
                // 排除检测的元素
                excludeSelectors: [
                    '.comment', '.reply', '.user-content',
                    '[id*="comment"]', '[class*="comment"]',
                    '[id*="reply"]', '[class*="reply"]',
                    '.social-feed', '.user-post'
                ],
                // 高风险元素选择器(权重更高)
                highRiskSelectors: [
                    'article', 'main', '.main-content',
                    '.article-content', '.post-content'
                ]
            }
        }
    };

    // 域名正则检测函数
    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() {
        console.log('\n[Content Detection] Starting content analysis...');
        let totalScore = 0;
        let totalElements = 0;
        let violationCount = 0;
        const scoreCache = new WeakMap();

        // 检查高风险元素并累加分数
        const highRiskElements = document.querySelectorAll(
            config.contentCheck.localizedCheck.highRiskSelectors.join(',')
        );
        console.log(`[High Risk Elements] Found: ${highRiskElements.length}`);
        highRiskElements.forEach(element => {
            const score = getElementScore(element, scoreCache);
            console.log(`[High Risk Element] Score: ${score}`);
            totalScore += score;
            if (score >= config.contentCheck.localizedCheck.elementThreshold) {
                violationCount++;
            }
        });

        // 排除不需要检测的元素并累加其他可疑元素分数
        const excludeSelector = config.contentCheck.localizedCheck.excludeSelectors.join(',');
        const excludeElements = new Set(document.querySelectorAll(excludeSelector));
        const mainElements = contentUtils.getSuspiciousElements();
        mainElements.forEach(element => {
            if (
                excludeElements.has(element) ||
                Array.from(excludeElements).some(excluded => excluded.contains(element))
            ) {
                return;
            }
            totalElements++;
            const score = getElementScore(element, scoreCache);
            totalScore += score;
            if (score >= config.contentCheck.localizedCheck.elementThreshold) {
                violationCount++;
            }
        });

        // 优化图片检测
        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; // 降低元数据权重
            }
        }

        console.log(`[Content Detection] Total Score: ${totalScore}`);
        console.log(`[Violations] Count: ${violationCount} / Total Elements: ${totalElements}`);
        return totalScore >= config.contentCheck.adultContentThreshold;
    }

    // 添加获取元素评分的辅助函数
    function getElementScore(element, scoreCache) {
        if (scoreCache.has(element)) {
            const cachedScore = scoreCache.get(element);
            console.log(`[Cached Element Score] ${cachedScore}`);
            return cachedScore;
        }

        const text = contentUtils.getAllText(element);
        console.log(`[Element Text] Length: ${text.length} chars`);
        const score = calculateScore(text);
        scoreCache.set(element, score);

        return score;
    }

    // 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) {
        const matches = text.match(regex) || [];
        let total = 0;
        matches.forEach(match => {
            const weight = weightMap[match.toLowerCase()] || 0;
            total += weight;
        });
        return total;
    }

    // 优化后的评分计算函数
    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) {
            const whitelistScore = sumMatches(text, regexCache.whitelistRegex, config.whitelist);
            if (whitelistScore !== 0) {
                score += whitelistScore;
            }
        }

        return score;
    };

    // 防抖函数
    const debounce = (func, wait) => {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                func(...args);
            }, 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) !important;
                        }
                        .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;
                // 增加局部检测逻辑
                const targetNode = mutations[0]?.target;
                if (targetNode) {
                    // 检查变化的元素是否在排除列表中
                    const excludeSelector = config.contentCheck.localizedCheck.excludeSelectors.join(',');
                    const isExcluded = targetNode.matches?.(excludeSelector) ||
                        targetNode.closest?.(excludeSelector);

                    if (isExcluded) {
                        pendingCheck = false;
                        return;
                    }
                }

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

    // setupDynamicContentCheck 函数之前添加新函数
    const setupTitleObserver = () => {
        let titleObserver = null;
        try {
            // 监听 title 标签变化
            const titleElement = document.querySelector('title');
            if (titleElement) {
                titleObserver = new MutationObserver(async (mutations) => {
                    for (const mutation of mutations) {
                        const newTitle = mutation.target.textContent;
                        console.log(`[Title Change] New title: "${newTitle}"`);
                        
                        // 计算新标题的分数
                        const titleScore = calculateScore(newTitle || "");
                        if (titleScore >= config.thresholds.block) {
                            console.log(`[Title Score] ${titleScore} exceeds threshold`);
                            const hostname = window.location.hostname;
                            await blacklistManager.addToBlacklist(hostname);
                            titleObserver.disconnect();
                            handleBlockedContent();
                            return;
                        }
                    }
                });

                titleObserver.observe(titleElement, {
                    subtree: true,
                    characterData: true,
                    childList: true
                });
            }

            // 监听 title 标签的添加
            const headObserver = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeName === 'TITLE') {
                            setupTitleObserver();
                            headObserver.disconnect();
                            return;
                        }
                    }
                }
            });

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

            // 设置超时清理
            setTimeout(() => {
                titleObserver?.disconnect();
                headObserver?.disconnect();
            }, config.contentCheck.observerTimeout);

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

        return titleObserver;
    };

    // ----------------- 黑名单储存 -----------------
    const blacklistManager = {
        BLACKLIST_KEY: 'pornblocker-blacklist',
        BLACKLIST_VERSION_KEY: 'pornblocker-blacklist-version',
        CURRENT_VERSION: '2.0',  // 当前版本号

        // 检查并升级黑名单版本
        async checkAndUpgradeVersion() {
            let storage;
            if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
                storage = chrome.storage.sync;
            } else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
                storage = chrome.storage.local;
            } else {
                // localStorage 的情况
                const storedVersion = localStorage.getItem(this.BLACKLIST_VERSION_KEY);
                if (storedVersion !== this.CURRENT_VERSION) {
                    // 在版本不匹配时清理旧数据
                    localStorage.setItem(this.BLACKLIST_VERSION_KEY, this.CURRENT_VERSION);
                    localStorage.setItem(this.BLACKLIST_KEY, JSON.stringify([]));
                }
                return;
            }

            try {
                // 获取存储的版本号
                const result = await new Promise(resolve => {
                    storage.get([this.BLACKLIST_VERSION_KEY, this.BLACKLIST_KEY], resolve);
                });

                const storedVersion = result[this.BLACKLIST_VERSION_KEY];
                if (storedVersion !== this.CURRENT_VERSION) {
                    // 执行版本迁移
                    await this.migrateData(storage, storedVersion, result[this.BLACKLIST_KEY]);
                }
            } catch (e) {
                console.error('Error checking version:', e);
            }
        },

        // 数据迁移函数
        async migrateData(storage, oldVersion, oldData) {
            try {
                let newData = [];

                // 处理旧版本数据
                if (oldData) {
                    if (Array.isArray(oldData)) {
                        // 如果是数组,保留有效的域名
                        newData = oldData.filter(item => typeof item === 'string' && item.includes('.'));
                    } else if (typeof oldData === 'object') {
                        // 如果是对象格式,提取域名
                        newData = Object.keys(oldData).filter(domain => domain.includes('.'));
                    }
                }

                // 保存迁移后的数据
                await new Promise(resolve => {
                    storage.set({
                        [this.BLACKLIST_KEY]: newData,
                        [this.BLACKLIST_VERSION_KEY]: this.CURRENT_VERSION
                    }, resolve);
                });

                console.log(`Blacklist migrated from ${oldVersion || 'unknown'} to ${this.CURRENT_VERSION}`);
            } catch (e) {
                console.error('Error migrating data:', e);
            }
        },

        // 获取黑名单
        async getBlacklist() {
            // 确保版本检查已完成
            await this.checkAndUpgradeVersion();

            try {
                // 优先使用同步存储
                if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
                    return new Promise((resolve) => {
                        chrome.storage.sync.get([this.BLACKLIST_KEY], (result) => {
                            const data = result[this.BLACKLIST_KEY];
                            resolve(Array.isArray(data) ? data : []);
                        });
                    });
                }
                // 降级使用本地存储
                else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
                    return new Promise((resolve) => {
                        chrome.storage.local.get([this.BLACKLIST_KEY], (result) => {
                            const data = result[this.BLACKLIST_KEY];
                            resolve(Array.isArray(data) ? data : []);
                        });
                    });
                }
                // 最后降级使用 localStorage
                else {
                    const data = localStorage.getItem(this.BLACKLIST_KEY);
                    return Promise.resolve(data ? JSON.parse(data) : []);
                }
            } catch (e) {
                console.error('Error reading blacklist:', e);
                return Promise.resolve([]);
            }
        },

        // 添加到黑名单
        async addToBlacklist(hostname) {
            try {
                if (!hostname) return false;

                const blacklist = await this.getBlacklist();
                if (blacklist.includes(hostname)) return true;

                blacklist.push(hostname);

                // 优先使用同步存储
                if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
                    return new Promise((resolve) => {
                        chrome.storage.sync.set({
                            [this.BLACKLIST_KEY]: blacklist,
                            [this.BLACKLIST_VERSION_KEY]: this.CURRENT_VERSION
                        }, () => resolve(true));
                    });
                }
                // 降级使用本地存储
                else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
                    return new Promise((resolve) => {
                        chrome.storage.local.set({
                            [this.BLACKLIST_KEY]: blacklist,
                            [this.BLACKLIST_VERSION_KEY]: this.CURRENT_VERSION
                        }, () => resolve(true));
                    });
                }
                // 最后降级使用 localStorage
                else {
                    localStorage.setItem(this.BLACKLIST_VERSION_KEY, this.CURRENT_VERSION);
                    localStorage.setItem(this.BLACKLIST_KEY, JSON.stringify(blacklist));
                    return Promise.resolve(true);
                }
            } catch (e) {
                console.error('Error adding to blacklist:', e);
                return Promise.resolve(false);
            }
        },

        // 检查是否在黑名单中
        async isBlacklisted(hostname) {
            try {
                if (!hostname) return false;

                const blacklist = await this.getBlacklist();
                return blacklist.includes(hostname);
            } catch (e) {
                console.error('Error checking blacklist:', e);
                return false;
            }
        },

        // 从黑名单中移除
        async removeFromBlacklist(hostname) {
            try {
                const blacklist = await this.getBlacklist();
                const index = blacklist.indexOf(hostname);
                if (index > -1) {
                    blacklist.splice(index, 1);

                    // 优先使用同步存储
                    if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
                        return new Promise((resolve) => {
                            chrome.storage.sync.set({ [this.BLACKLIST_KEY]: blacklist }, () => {
                                resolve(true);
                            });
                        });
                    }
                    // 降级使用本地存储
                    else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
                        return new Promise((resolve) => {
                            chrome.storage.local.set({ [this.BLACKLIST_KEY]: blacklist }, () => {
                                resolve(true);
                            });
                        });
                    }
                    // 最后降级使用 localStorage
                    else {
                        localStorage.setItem(this.BLACKLIST_KEY, JSON.stringify(blacklist));
                        return Promise.resolve(true);
                    }
                }
                return Promise.resolve(false);
            } catch (e) {
                console.error('Error removing from blacklist:', e);
                return Promise.resolve(false);
            }
        }
    };

    // 立即执行版本检查
    (async function initBlacklist() {
        await blacklistManager.checkAndUpgradeVersion();
    })();

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

        console.log(`\n[URL Check] Checking: ${url.href}`);
        console.log(`[Hostname] ${hostname}`);

        // 优化黑名单检查
        if (await 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();
            const domainScore = config.domainKeywords[keyword] || 0;
            if (domainScore !== 0) {
                console.log(`[Domain Match] "${match}" = ${domainScore}`);
                score += domainScore;
            }
        });

        // 检查路径
        const path = url.pathname + url.search;
        console.log(`[Path Check] "${path}"`);
        const pathScore = calculateScore(path) * 0.4;
        if (pathScore !== 0) {
            console.log(`[Path Score] ${pathScore} (after 0.4 multiplier)`);
            score += pathScore;
        }

        // 检查标题
        console.log(`[Title Check] "${document.title}"`);
        const titleScore = calculateScore(document.title || "");
        if (titleScore !== 0) {
            console.log(`[Title Score] ${titleScore}`);
            score += titleScore;
        }

        console.log(`[Initial Total Score] ${score}`);
        console.log(`[Block Threshold] ${config.thresholds.block}`);

        // 优化白名单评分: 如果超过阈值则进行白名单扣分
        if (score >= config.thresholds.whitelist) {
            const hostMatches = url.hostname.match(regexCache.whitelistRegex) || [];
            const titleMatches = (document.title || "").match(regexCache.whitelistRegex) || [];

            // 累加白名单分数
            let whitelistScore = 0;
            const whitelistMatchCount = (matches) => {
                matches.forEach(match => {
                    const term = match.toLowerCase();
                    const reduction = config.whitelist[term] || 0;
                    if (reduction !== 0) {
                        console.log(`[Whitelist Match] "${term}" = ${reduction}`);
                        whitelistScore += reduction;
                    }
                });
            };

            whitelistMatchCount(hostMatches);
            whitelistMatchCount(titleMatches);

            if (whitelistScore !== 0) {
                console.log(`[Whitelist Score] ${whitelistScore}`);
                score += whitelistScore;  // 加上白名单分数(负值会减分)
            }
        }

        console.log(`[Final Score] ${score}`);
        return {
            shouldBlock: score >= config.thresholds.block,
            url: url
        };
    };

    // 修改主执行函数,添加标题监听
    (async function () {
        const { shouldBlock, url: currentUrl } = await checkUrl();

        if (shouldBlock || regexCache.xxxRegex.test(currentUrl.hostname)) {
            handleBlockedContent();
        } else {
            // 添加标题监听
            setupTitleObserver();
        }
    })();
})();