Greasy Fork

Greasy Fork is available in English.

Sex Blocker

白名单动态扣分策略

当前为 2025-02-14 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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