// ==UserScript== // @name 色情过滤器 // @version 5.1 // @description 高效过滤敏感内容,支持临时访问确认、弹幕提示 // @author 菜羽凡 // @match *://*/* // @grant GM_registerMenuCommand // @grant unsafeWindow // @run-at document-end // @license MIT // @namespace http://tampermonkey.net/ // ==/UserScript== (function() { 'use strict'; // Base64解码函数 function base64Decode(str) { return decodeURIComponent(escape(atob(str))); } const encodedKeywords = "WyLlj6PkuqQiLCLlm73kuqciLCLlkIPnk5zpu5HmlpkiLCI5OOWggiIsIuaXpemfqeeyvuWTgSIsIuaXoOeggSIsIueIseeIsee9kSIsIjU45aCCIiwi5pyJ56CBIiwi5Lit5paH5a2X5bmVIiwi5qyn576O6KeG6aKRIiwi5pyA5paw5Zyw5Z2A5Y+R5biDIiwi5LiJ57qnIiwi5by65aW4Iiwi5Lmx5LymIiwi6L+36I2vIiwi5rerIiwicG9ybiIsIuWls+S8mCIsIuS4neiinCIsIuavjeWtkCIsIuiHquaFsCIsIumfqea8qyIsIuemgea8qyIsIueci+eJhyIsIuaSuOeuoSIsIuawuOS5hee9keWdgCIsIlNXQUciLCLlj5HluIPpobUiLCLns5blv4MiLCLnmoflrrYiLCLpurvosYYiLCLlq6nojYkiLCLlv6vnjKsiLCLonJzmoYMiLCLlpbPlhKoiLCLkvKbnkIYiLCLmtqkiLCLmjaLohLgiLCLmrKfnvo4iLCLkuprmtLIiLCLnvo7lpbMiLCLlgbfmi40iLCLkvKDlqpIiLCLmgKfniLEiLCLnsr7kuJwiLCI5MeWkp+elniIsIjkx5o+t6ZyyIiwi5pqX572RIiwi5ZCD55OcIiwi5q+P5pel5aSn6LWbIiwiMTjnpoEiLCLlhoXlsIQiLCLokJ3ojokiLCLpu5HkuJ0iLCLnmb3kuJ0iLCLmgKfmhJ8iLCLlhZTlrZDlhYjnlJ8iLCI5MeWItueJhyIsIuWkqee+jiIsIuaOouiKsSIsIuWQjuWFpSIsIuemgeeJhyIsIuS5heS5hSIsIuaegeWTgSIsIuiCjyIsIuiNieamtCIsIuWls+elniIsIum7hOiJsiIsIuaDheiJsiIsICLmiJDkuroiLCLlgZrniLEiLCAi6Imy5oOFIiwi6aqaIiwgIuWVquWVqiIsICLogpvkuqQiLCAi5ZCe57K+Iiwi6K+x5oORIiwgIuWFqOijuCIsICLkubPkuqQiLCAi5bCE57K+IiwgIuWPjeW3riIsICLosIPmlZkiLCAi5oCn5LqkIiwi5oCn5aW0IiwgIumrmOa9riIsICLnmb3omY4iLCAi5bCR5aWzIiwgIuWls+WPiyIsICLni4Lmk40iLCAi5o2G57uRIiwgIue6pueCriIsICLpuKHlkKciLCLpmLTojI4iLCAi6Zi06YGTIiwi6KO45L2TIiwgIueUt+S8mCIsICLlgbfmg4UiLCLmr43ni5ciLCAi5Za35rC0Iiwi5r2u5ZC5IiwgIui9ruWluCIsICLlsJHlpociLCLnhp/lpbMiLCLplIDprYIiLCLplIDlpbMiLCLplIDotKciLCLplIDpgLwiLCLplIDotLEiLCLplIDojaEiLCLpqprprYIiLCLpqprlpbMiLCLpqprotKciLCLpqprpgLwiLCLpqprotLEiLCLpqprojaEiLCLmtarprYIiLCLmtarlpbMiLCLmtarotKciLCLmtarpgLwiLCLmtarotLEiLCLmtarojaEiLCLmt6vprYIiLCLmt6vlpbMiLCLmt6votKciLCLmt6vpgLwiLCLmt6votLEiLCLmt6vojaEiLCLoiJTnqbQiLCLoiJTpgLwiLCLoiJTlsYQiLCLoiJTpmLQiLCLoiJTonJwiLCLoiJToj4oiLCLoiJTlsYwiLCLoiJTpuKEiLCLoiJTogokiLCLlkLjnqbQiLCLlkLjpgLwiLCLlkLjlsYQiLCLlkLjpmLQiLCLlkLjonJwiLCLlkLjoj4oiLCLlkLjlsYwiLCLlkLjpuKEiLCLlkLjogokiLCLlkK7nqbQiLCLlkK7pgLwiLCLlkK7lsYQiLCLlkK7pmLQiLCLlkK7onJwiLCLlkK7oj4oiLCLlkK7lsYwiLCLlkK7puKEiLCLlkK7ogokiLCLmj5LnqbQiLCLmj5LpgLwiLCLmj5LlsYQiLCLmj5LpmLQiLCLmj5LonJwiLCLmj5Loj4oiLCLmj5LlsYwiLCLmj5LpuKEiLCLmj5LogokiLCLlubLnqbQiLCLlubLpgLwiLCLlubLlsYQiLCLlubLpmLQiLCLlubLonJwiLCLlubLoj4oiLCLlubLlsYwiLCLlubLpuKEiLCLlubLogokiLCLmk43nqbQiLCLmk43pgLwiLCLmk43lsYQiLCLmk43pmLQiLCLmk43onJwiLCLmk43oj4oiLCLmk43lsYwiLCLmk43puKEiLCLmk43ogokiLCLojYnnqbQiLCLojYnpgLwiLCLojYnlsYQiLCLojYnpmLQiLCLojYnonJwiLCLojYnoj4oiLCLojYnlsYwiLCLojYnpuKEiLCLojYnogokiLCLlt6jkubMiLCLlt6joh4AiLCLlt6jog7giLCLlpKfkubMiLCLlpKfoh4AiLCLlpKfog7giLCLlsI/kubMiLCLlsI/oh4AiLCLlsI/og7giLCLnv5jkubMiLCLnv5joh4AiLCLnv5jog7giLCLnmb3kubMiLCLnmb3oh4AiLCLnmb3og7giLCLogqXkubMiLCLogqXoh4AiLCLogqXog7giLCLogonmo5IiLCLogonmoLkiLCLogonnqbQiLCLogonnvJ0iLCLogonoh4AiLCLogonlpbYiLCLogonkvZMiLCLogonmrLIiLCAi5rer56e9Iiwi5rer6I2hIiwi5rer5LmxIiwi5rer6LSxIiwi5rer5rayIiwi5rer56m0Iiwi5rer5rC0Iiwi5rap5rapIiwgIua2qeWbviIsICLmtqnmsJQiLCAi6Imy5om5IiwgIuiJsuWbviIsICLnuqZwIiwgIue6puWVqiIsIuiAgeW4iCIsIuaVmeW4iCIsIuW4iOeUnyIsIuWutuaVmSIsIueGn+WlsyIsIuWwhOeyviIsIuaMieaRqSIsIuS/neWnhiIsIue9kee6oiIsIuS5seS8piIsIumHjuWkliIsIui3r+S6uiIsIue0oOS6uiIsIue+juiEmiJd" const decodedKeywords = base64Decode(encodedKeywords); // 解析JSON数组并初始化敏感词相关变量 let BLOCKED_KEYWORDS = JSON.parse(decodedKeywords); let uniqueKeywords = [...new Set(BLOCKED_KEYWORDS)]; let keywordRegex = new RegExp(uniqueKeywords.join('|'), 'i'); // 白名单类名(保持不变) let WHITELIST_CLASSES = ['history-list', 'listbox', 'search-suggest']; // 新增:全局白名单域名(替换原搜索引擎配置) let GLOBAL_WHITELIST_DOMAINS = [ 'zhihu.com', 'quark.cn', 'baidu.com', // 百度搜索页面 'sogou.com', // 搜狗搜索页面 'so.com', // 360搜索页面 'bing.com', // 必应搜索页面 'google.com', // Google搜索页面 'yandex.com', // Yandex搜索页面 'duckduckgo.com'// DuckDuckGo搜索页面 ]; // 配置持久化与临时白名单(修复:使用localStorage存储临时白名单) const CONFIG_KEYS = { keywords: 'pornFilter_keywords', whitelist: 'pornFilter_whitelist', tempDomains: 'pornFilter_tempDomains' // 存储临时白名单域名 }; let tempDomainWhitelist = new Set(); // 临时白名单(本次访问不扫描的根域名) const CONFIRM_TEXT = "我已知晓浏览黄色网站对健康有害,我发誓以后做一个充满正能量的人!我现在将要浏览这个页面,请批准。"; // -------------------------- 工具函数 -------------------------- // 加载本地配置(修复:加载临时白名单域名) function loadConfig() { const savedKeywords = localStorage.getItem(CONFIG_KEYS.keywords); const savedWhitelist = localStorage.getItem(CONFIG_KEYS.whitelist); const savedTempDomains = localStorage.getItem(CONFIG_KEYS.tempDomains); if (savedKeywords) { BLOCKED_KEYWORDS = JSON.parse(savedKeywords); uniqueKeywords = [...new Set(BLOCKED_KEYWORDS)]; keywordRegex = new RegExp(uniqueKeywords.join('|'), 'i'); } if (savedWhitelist) WHITELIST_CLASSES = JSON.parse(savedWhitelist); // 修复:从localStorage加载临时白名单 if (savedTempDomains) { JSON.parse(savedTempDomains).forEach(domain => tempDomainWhitelist.add(domain)); } } // 保存配置(修复:保存临时白名单域名) function saveConfig() { localStorage.setItem(CONFIG_KEYS.keywords, JSON.stringify(BLOCKED_KEYWORDS)); localStorage.setItem(CONFIG_KEYS.whitelist, JSON.stringify(WHITELIST_CLASSES)); // 修复:保存临时白名单到localStorage localStorage.setItem(CONFIG_KEYS.tempDomains, JSON.stringify([...tempDomainWhitelist])); } // 判断是否为全局白名单域名 function isGlobalWhitelistDomain() { const hostname = window.location.hostname; return GLOBAL_WHITELIST_DOMAINS.some(domain => hostname.includes(domain)); } // 提取根域名(保持不变) function getRootDomain() { const hostname = window.location.hostname; const parts = hostname.split('.'); if (parts.length <= 2) return hostname; return parts.slice(-2).join('.'); } // 检查元素是否在白名单(保持不变) function isInWhitelist(element) { let current = element; while (current && current.tagName !== 'HTML') { if (current.className) { const classes = current.className.trim().split(/\s+/); if (classes.some(cls => WHITELIST_CLASSES.includes(cls) || cls.includes('history'))) { return true; } } current = current.parentElement; } return false; } // 显示弹幕提示(保持不变) function showBulletScreen(text, color = '#ff3300') { const bullet = document.createElement('div'); bullet.style.cssText = ` position: fixed; top: ${Math.random() * 60 + 20}vh; right: -300px; padding: 8px 16px; background: rgba(0,0,0,0.7); color: ${color}; border-radius: 4px; white-space: nowrap; z-index: 9999998; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); animation: bulletMove 8s linear forwards; `; bullet.textContent = text; document.body.appendChild(bullet); const style = document.createElement('style'); style.textContent = ` @keyframes bulletMove { 0% { right: -300px; } 100% { right: 100vw; } } `; document.head.appendChild(style); setTimeout(() => bullet.remove(), 8000); } // 其他工具函数(保持不变) function getElementHierarchy(element) { if (!element) return '未知元素'; const hierarchy = []; let current = element; while (current && current.tagName !== 'HTML') { let info = `<${current.tagName.toLowerCase()}`; if (current.id) info += `#${current.id}`; if (current.className) { const classes = current.className.trim().split(/\s+/).slice(0, 2); info += classes.length ? `.${classes.join('.')}` : ''; } info += '>'; hierarchy.push(info); current = current.parentElement; } return hierarchy.join(' ← '); } function hasSensitiveWord(text) { return text && keywordRegex.test(text.toLowerCase()); } // -------------------------- 核心检查逻辑 -------------------------- // 检查URL(修改:基于全局白名单和临时白名单) function checkUrl() { const rootDomain = getRootDomain(); // 若在全局白名单或临时白名单,直接返回空 if (tempDomainWhitelist.has(rootDomain) || isGlobalWhitelistDomain()) return []; const url = window.location.href; if (hasSensitiveWord(url)) { return [{ keyword: 'URL包含敏感内容', elementHierarchy: '', source: '(当前URL)', elementTag: 'url' }]; } return []; } // 检查链接(修改:基于全局白名单和临时白名单) function checkLinks() { const rootDomain = getRootDomain(); if (tempDomainWhitelist.has(rootDomain) || isGlobalWhitelistDomain()) return []; const blocked = []; document.querySelectorAll('a[href]:not([style*="display:none"])').forEach(link => { if (isInWhitelist(link)) return; const linkText = (link.textContent || '').trim(); const href = link.href; if (hasSensitiveWord(linkText) || hasSensitiveWord(href)) { blocked.push({ keyword: getMatchedKeyword(linkText + href), elementHierarchy: getElementHierarchy(link), source: ``, elementTag: 'a' }); } }); return blocked; } // 检查文本元素(修改:基于全局白名单和临时白名单) function checkTextElements() { const rootDomain = getRootDomain(); if (tempDomainWhitelist.has(rootDomain) || isGlobalWhitelistDomain()) return []; const blocked = []; const textElements = document.querySelectorAll('div, p, span, h1, h2, h3, h4, li, a, td'); textElements.forEach(element => { if (isInWhitelist(element)) return; const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden') return; const text = element.textContent || ''; if (hasSensitiveWord(text)) { blocked.push({ keyword: getMatchedKeyword(text), elementHierarchy: getElementHierarchy(element), source: `(${element.tagName.toLowerCase()})`, elementTag: element.tagName.toLowerCase() }); } }); return blocked; } // 辅助函数:获取匹配的敏感词(保持不变) function getMatchedKeyword(text) { const lowerText = text.toLowerCase(); return uniqueKeywords.find(keyword => lowerText.includes(keyword.toLowerCase())) || '敏感内容'; } // 总检查逻辑(修改:基于全局白名单和临时白名单) function checkCurrentPage() { const rootDomain = getRootDomain(); // 若在全局白名单或临时白名单,直接返回不拦截 if (tempDomainWhitelist.has(rootDomain) || isGlobalWhitelistDomain()) { return { blocked: false }; } const allBlocked = []; const urlBlocked = checkUrl(); if (urlBlocked.length > 0) { allBlocked.push(...urlBlocked); return { blocked: true, reason: "URL包含敏感词", keywords: [...new Set(urlBlocked.map(item => item.keyword))], elements: allBlocked, url: window.location.href, title: document.title || "无标题" }; } const linkBlocked = checkLinks(); const textBlocked = checkTextElements(); if (linkBlocked.length > 0 || textBlocked.length > 0) { allBlocked.push(...linkBlocked, ...textBlocked); return { blocked: true, reason: "页面内容包含敏感词", keywords: [...new Set(allBlocked.map(item => item.keyword))], elements: allBlocked, url: window.location.href, title: document.title || "无标题" }; } return { blocked: false }; } // -------------------------- 拦截与确认机制 -------------------------- // 拦截页面并显示确认界面(保持不变) function blockPage(result) { if (document.getElementById('porn-filter-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'porn-filter-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #000; z-index: 9999999; color: #fff; padding: 20px; overflow-y: auto; box-sizing: border-box; font-family: Arial, sans-serif; `; showBulletScreen("检测到敏感内容,已拦截访问!", '#ffcc00'); overlay.innerHTML = `

访问拦截(增强版)

拦截原因:${result.reason}

当前URL:${result.url}

页面标题:${result.title}

检测到的敏感词

${result.keywords.map(k => `${k}`).join('')}

如需访问,请输入以下短句

${CONFIRM_TEXT}

敏感词所在位置

${result.elements.map(item => `
敏感词:${item.keyword}
来源:${item.source}
元素结构:${item.elementHierarchy}
`).join('')}
`; document.documentElement.innerHTML = ''; document.documentElement.appendChild(overlay); // 确认按钮逻辑(修改:保存临时白名单并刷新) document.getElementById('confirm-btn').addEventListener('click', () => { const input = document.getElementById('confirm-input').value.trim(); const errorMsg = document.getElementById('error-msg'); if (input === CONFIRM_TEXT) { const rootDomain = getRootDomain(); tempDomainWhitelist.add(rootDomain); saveConfig(); // 保存临时白名单 showBulletScreen(`已批准访问${rootDomain},本次会话不再拦截`, '#4cd964'); setTimeout(() => location.reload(), 1000); } else { errorMsg.textContent = "输入不匹配,请重新输入!"; } }); window.stop(); } // 页面监听逻辑(保持不变) function setupListeners() { let currentUrl = window.location.href; const urlCheckInterval = setInterval(() => { if (window.location.href !== currentUrl) { currentUrl = window.location.href; const result = checkCurrentPage(); if (result.blocked) blockPage(result); } }, 500); if (window.MutationObserver) { const observer = new MutationObserver(() => { const result = checkCurrentPage(); if (result.blocked) blockPage(result); }); observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); } window.addEventListener('beforeunload', () => clearInterval(urlCheckInterval)); } // -------------------------- 初始化 -------------------------- function init() { loadConfig(); // 若在全局白名单,直接启动监听(不拦截) if (isGlobalWhitelistDomain()) { console.log(`当前为白名单域名(${window.location.hostname}),跳过过滤扫描`); setupListeners(); return; } // 非白名单域名:执行检查 const result = checkCurrentPage(); if (result.blocked) { blockPage(result); } else { setupListeners(); } } init(); })();