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