Greasy Fork

Sex Blocker

白名单动态扣分策略

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

// ==UserScript==
// @name         Sex Blocker
// @namespace    https://noctiro.moe
// @version      1.1
// @description 白名单动态扣分策略
// @author       Noctiro
// @license      Apache-2.0
// @match        *://*/*
// @run-at       document-start
// @run-at       document-end
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    
    // 添加浏览器检测函数
    const getBrowserType = () => {
        const ua = navigator.userAgent.toLowerCase();
        
        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';
            default: 
                // Fallback to a safe default
                return 'about:blank';
        }
    };

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

    // ----------------- 配置项(用户可按需修改) -----------------
    const config = {
        // ================== 色情关键词评分规则 ==================
        pornKeywords: {
            // 核心高危词(权重≥3)
            'porn': 3, 'nsfw': 3, 'xxx': 3, 'xxxvideos': 3,
            'hentai': 3, 'incest': 4, 'rape': 4, 'childporn': 4,

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

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

            // 具体性行为(权重2~3)
            'anal': 3, 'blowjob': 2, 'creampie': 2, 'bdsm': 2, 'masturbat': 2,

            // 其他相关词汇(权重1~2)
            'camgirl': 2, 'webcam': 2, 'onlyfans': 2, 'nsfwgifs': 3,
            'leaked': 2, 'fap': 2, 'erotic': 1, 'escort': 3,
            'tube': 1,

            // 新增高危词
            'pornhub': 4, 'xvideo': 4, 'redtube': 4,
            'javhd': 4, 'youporn': 4, 'spankbang': 4,
            'xnxx':4, 'xhamster': 4, '4tube': 4,
            'myfreecams': 4,

            // 新增变体检测
            'p0rn': 3, 'pr0n': 3, 'pron': 3,
            's3xy': 3, 'sexx': 3, 'adultt': 3,

            // 新增多语言支持
            '情色': 3, '成人': 3, '做爱': 4,
            'セックス': 3, 'エロ': 3, '淫': 4,
            'секс': 3, 'порн': 3, '性爱': 3, '無修正': 3,
            'ポルノ': 3, 'порно': 3
        },

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

            // 常用场景豁免(权重-2)
            'academy': -2, 'clinic': -2, 'therapy': -2,
            'university': -2, 'research': -2,

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

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

            // 其他安全保障
            'gov': -2, 'org': -1, 'official': -2
        },

        // ================== 阈值配置 ==================
        thresholds: {
            domain: 3,       // 域名基础分触发阈值(建议3~4)
            path: 2,        // URL路径加分阈值
            chain: 2         // 域名包含多个敏感词的链式加分(如"teen.porn"得3+2=5)
        },
    };

    // 预处理正则(性能关键:仅初始化一次)
    (function initRegex() {
        // 色情关键词正则:生成 /porn|xxx|sex|.../gi
        const pornTerms = Object.keys(config.pornKeywords).join('|');
        regexCache.pornRegex = new RegExp(`(${pornTerms})`, 'gi');
        // 白名单正则:生成 /education|health|animal/gi
        const whitelistTerms = Object.keys(config.whitelist).join('|');
        regexCache.whitelistRegex = new RegExp(`(${whitelistTerms})`, 'gi');
    })();

    // ----------------- 评分计算函数 -----------------
    const calculateScore = (text) => {
        let score = 0;
        // 色情关键词匹配(使用预编译正则提升性能)
        const pornMatches = text.match(regexCache.pornRegex) || [];
        pornMatches.forEach(match => {
            const keyword = match.toLowerCase();
            score += config.pornKeywords[keyword] || 0;
        });
        // 白名单关键词匹配(扣分机制)
        const whitelistMatches = text.match(regexCache.whitelistRegex) || [];
        whitelistMatches.forEach(match => {
            const term = match.toLowerCase();
            score += config.whitelist[term] || 0;
        });
        return Math.max(score, 0); // 确保不会出现负分
    };

    // ----------------- 主检测逻辑 -----------------
    const currentUrl = new URL(window.location.href);
    const domain = currentUrl.hostname;
    const path = currentUrl.pathname + currentUrl.search;

    // Step1: 域名基础评分(含白名单扣分)
    let totalScore = calculateScore(domain);

    // Step2: 路径附加分
    if (totalScore > 0) {
        const pathScore = calculateScore(path);
        totalScore += (pathScore >= config.thresholds.path) ? pathScore : 0;
    }

    // Step3: .xxx后缀强制拦截(可关闭)
    const isXXX = regexCache.xxxRegex.test(domain);

    // =============== 判定拦截 ===============
    if (totalScore >= config.thresholds.domain || isXXX) {
        window.stop();
        document.documentElement.innerHTML = `
            <body style="background:#f0f2f5;margin:0;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;">
                <div style="max-width:500px;background:white;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,0.1);padding:32px;text-align:center;animation:slideIn 0.5s ease-out">
                    <div style="width:64px;height:64px;background:#ff4757;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 24px;animation:pulse 2s infinite">
                        <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" 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 style="color:#2d3436;margin:0 0 16px;font-size:24px;font-weight:600">🚫 访问已被拦截</h1>
                    <p style="color:#636e72;margin:0 0 24px;line-height:1.6;font-size:16px">
                        该网页已被识别为不健康内容。<br>
                        为了您的身心健康,系统将在几秒后自动跳转。
                    </p>
                    <div style="color:#b2bec3;font-size:14px;animation:fadeIn 1s ease-out">
                        注意身体健康 · 远离不良网站
                    </div>
                </div>
                <style>
                    @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>
        `;
        setTimeout(() => {
            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);
                            
                            if (prevScore >= config.thresholds.domain) {
                                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();
            }
        }, 4000);
    }
})();