// ==UserScript== // @name 戒色提醒助手(增强版) // @namespace http://tampermonkey.net/ // @version 0.9 // @description 识别色情内容并提醒,支持处理嵌入在JS中的内容,确保页面正常交互,新增Base64解码功能,优化加载中内容检测 // @author Your Name // @match *://*/* // @grant GM_xmlhttpRequest // @connect * // ==/UserScript== (function() { 'use strict'; // Base64解码函数 function base64Decode(encodedString) { try { encodedString = encodedString.replace(/-/g, '+').replace(/_/g, '/'); const decodedBytes = atob(encodedString); const decodedString = decodeURIComponent(escape(decodedBytes)); return decodedString; } catch (e) { console.error('Base64解码失败:', e); return ''; } } // 配置部分 const config = { whitelist: [ "baidu.com", "google.com", "bing.com", "zhihu.com", "github.com", "stackoverflow.com", "wikipedia.org", "youtube.com", "bilibili.com", "weibo.com", "taobao.com", "jd.com" ], sensitiveWordsEncoded: "WwogICAgICAgICAgICAi5Y+j5LqkIiwi5Zu95LqnIiwi5ZCD55Oc6buR5paZIiwiOTjloIIiLCLml6Xpn6nnsr7lk4EiLCLml6DnoIEiLCLniLHniLHnvZEiLCI1OOWggiIsIuacieeggSIsCiAgICAgICAgICAgICLkuK3mloflrZfluZUiLCLmrKfnvo7op4bpopEiLCLmnIDmlrDlnLDlnYDlj5HluIMiLCLkuInnuqciLCLlvLrlpbgiLCLkubHkvKYiLCLov7foja8iLCLmt6siLCJwb3JuIiwKICAgICAgICAgICAgIuWls+S8mCIsIuS4neiinCIsIuavjeWtkCIsIuiHquaFsCIsIumfqea8qyIsIuemgea8qyIsIueci+eJhyIsIuaSuOeuoSIsIuawuOS5hee9keWdgCIsIlNXQUciLAogICAgICAgICAgICAi5Y+R5biD6aG1Iiwi57OW5b+DIiwi55qH5a62Iiwi6bq76LGGIiwi5aup6I2JIiwi5b+r54yrIiwi6Jyc5qGDIiwi5aWz5YSqIiwi5Lym55CGIiwi5rapIiwi5o2i6IS4IiwKICAgICAgICAgICAgIuasp+e+jiIsIuS6mua0siIsIue+juWlsyIsIuWBt+aLjSIsIuS8oOWqkiIsIuaAp+eIsSIsIueyvuS4nCIsIjkx5aSn56WeIiwiOTHmj63pnLIiLCLmmpfnvZEiLAogICAgICAgICAgICAi5ZCD55OcIiwi5q+P5pel5aSn6LWbIiwiMTjnpoEiLCLlhoXlsIQiLCLokJ3ojokiLCLpu5HkuJ0iLCLnmb3kuJ0iLCLmgKfmhJ8iLCLlhZTlrZDlhYjnlJ8iLAogICAgICAgICAgICAiOTHliLbniYciLCLlpKnnvo4iLCLmjqLoirEiLCLlkI7lhaUiLCLnpoHniYciLCLkuYXkuYUiLCLmnoHlk4EiLCLogo8iLCLojYnmprQiLCLlpbPnpZ4iLCLpu4ToibIiLAogICAgICAgICAgICAi5oOF6ImyIiwgIuaIkOS6uiIsIuWBmueIsSIsICLoibLmg4UiLCLpqpoiLCAi5ZWq5ZWqIiwgIuiCm+S6pCIsICLlkJ7nsr4iLCLor7Hmg5EiLCAi5YWo6KO4IiwKICAgICAgICAgICAgIuS5s+S6pCIsICLlsITnsr4iLCAi5Y+N5beuIiwgIuiwg+aVmSIsICLmgKfkuqQiLCLmgKflpbQiLCAi6auY5r2uIiwgIueZveiZjiIsICLlsJHlpbMiLCAi5aWz5Y+LIiwKICAgICAgICAgICAgIueLguaTjSIsICLmjYbnu5EiLCAi57qm54KuIiwgIum4oeWQpyIsIumYtOiMjiIsICLpmLTpgZMiLCLoo7jkvZMiLCAi55S35LyYIiwgIuWBt+aDhSIsIuavjeeLlyIsCiAgICAgICAgICAgICLllrfmsLQiLCLmva7lkLkiLCAi6L2u5aW4IiwgIuWwkeWmhyIsIueGn+WlsyIsIumUgOmtgiIsIumUgOWlsyIsIumUgOi0pyIsIumUgOmAvCIsIumUgOi0sSIsIumUgOiNoSIsCiAgICAgICAgICAgICLpqprprYIiLCLpqprlpbMiLCLpqprotKciLCLpqprpgLwiLCLpqprotLEiLCLpqprojaEiLCLmtarprYIiLCLmtarlpbMiLCLmtarotKciLCLmtarpgLwiLCLmtarotLEiLAogICAgICAgICAgICAi5rWq6I2hIiwi5rer6a2CIiwi5rer5aWzIiwi5rer6LSnIiwi5rer6YC8Iiwi5rer6LSxIiwi5rer6I2hIiwi6IiU56m0Iiwi6IiU6YC8Iiwi6IiU5bGEIiwi6IiU6Zi0IiwKICAgICAgICAgICAgIuiIlOicnCIsIuiIlOiPiiIsIuiIlOWxjCIsIuiIlOm4oSIsIuiIlOiCiSIsIuWQuOeptCIsIuWQuOmAvCIsIuWQuOWxhCIsIuWQuOmYtCIsIuWQuOicnCIsIuWQuOiPiiIsCiAgICAgICAgICAgICLlkLjlsYwiLCLlkLjpuKEiLCLlkLjogokiLCLlkK7nqbQiLCLlkK7pgLwiLCLlkK7lsYQiLCLlkK7pmLQiLCLlkK7onJwiLCLlkK7oj4oiLCLlkK7lsYwiLCLlkK7puKEiLAogICAgICAgICAgICAi5ZCu6IKJIiwi5o+S56m0Iiwi5o+S6YC8Iiwi5o+S5bGEIiwi5o+S6Zi0Iiwi5o+S6JycIiwi5o+S6I+KIiwi5o+S5bGMIiwi5o+S6bihIiwi5o+S6IKJIiwi5bmy56m0IiwKICAgICAgICAgICAgIuW5sumAvCIsIuW5suWxhCIsIuW5sumYtCIsIuW5suicnCIsIuW5suiPiiIsIuW5suWxjCIsIuW5sum4oSIsIuW5suiCiSIsIuaTjeeptCIsIuaTjemAvCIsIuaTjeWxhCIsCiAgICAgICAgICAgICLmk43pmLQiLCLmk43onJwiLCLmk43oj4oiLCLmk43lsYwiLCLmk43puKEiLCLmk43ogokiLCLojYnnqbQiLCLojYnpgLwiLCLojYnlsYQiLCLojYnpmLQiLCLojYnonJwiLAogICAgICAgICAgICAi6I2J6I+KIiwi6I2J5bGMIiwi6I2J6bihIiwi6I2J6IKJIiwi5beo5LmzIiwi5beo6IeAIiwi5beo6IO4Iiwi5aSn5LmzIiwi5aSn6IeAIiwi5aSn6IO4Iiwi5bCP5LmzIiwKICAgICAgICAgICAgIuWwj+iHgCIsIuWwj+iDuCIsIue/mOS5syIsIue/mOiHgCIsIue/mOiDuCIsIueZveS5syIsIueZveiHgCIsIueZveiDuCIsIuiCpeS5syIsIuiCpeiHgCIsIuiCpeiDuCIsCiAgICAgICAgICAgICLogonmo5IiLCLogonmoLkiLCLogonnqbQiLCLogonnvJ0iLCLogonoh4AiLCLogonlpbYiLCLogonkvZMiLCLogonmrLIiLCAi5rer56e9Iiwi5rer6I2hIiwi5rer5LmxIiwKICAgICAgICAgICAgIua3q+i0sSIsIua3q+a2siIsIua3q+eptCIsIua3q+awtCIsIua2qea2qSIsICLmtqnlm74iLCAi5rap5rCUIiwgIuiJsuaJuSIsICLoibLlm74iLCAi57qmcCIsICLnuqbllaoiLAogICAgICAgICAgICAi6ICB5biIIiwi5pWZ5biIIiwi5biI55SfIiwi5a625pWZIiwi54af5aWzIiwi5bCE57K+Iiwi5oyJ5pGpIiwi5L+d5aeGIiwi572R57qiIiwi5Lmx5LymIiwi6YeO5aSWIiwKICAgICAgICAgICAgIui3r+S6uiIsIue0oOS6uiIsIue+juiEmiIsIuegtOWkhCIsIuaKlumYtCIsIuaKluiNqyIsIuWFjei0ueinhumikSIsIuWFqOWbveepuumZjSIsIumYv+WnqCIsICLmjInmkakiLAogICAgICAgICAgICAi55m96JmOIiwgIueZvemihiIsICLnmb3kuJ0iLCAi54iG5LmzIiwgIuiiq+WKqCIsICLlj6PniIYiLCAi6Ieq5ouNIiwgIuWBt+aLjSIsCiAgICAgICAgICAgICLmjYbnu5EiLCAi6L2m6ZyHIiwgIuiQneiOiSIsICLli77lvJUiLCAi5Y+N5beuIiwgIuaXoOavmyIsICLlpJrkuroiLCAi6aqa6LSnIiwgIuW+oeWnkCIsCiAgICAgICAgICAgICLlkJ7nsr4iLCAi5YG35oOFIiwgIuiAgeW4iCIsICLlpbPku4YiLCAi576O5LmzIiwgIuaDheS+oyIsICLph47lpJYiLCAi5pCt6K6qIiwgIuenmOS5piIsICLnvo7ohJoiLAogICAgICAgICAgICAi57u/5bi9IiwgIuS4iuS9jSIsICLpopzlsIQiLCAi6ZW/6IW/IiwgIuminOWAvCIsICLlpKflpbYiLCAi5a2m5aa5IiwgIuiHquaFsCIsICLpl7ronJwiLCAKICAgICAgICAgICAgIuiJr+WutiIsICLpq5jot58iLCAi5LiK5Y+4IiwgIua3q+WmuyIsICLlq6nmqKEiLCAi5aSE5aWzIiwgIua3seWWiSIsICLpq5jmva4iLCAKICAgICAgICAgICAgIuaIt+WkliIsICLkuJ3oopwiLCAi5re36KGAIiwgIum7keS4nSIsICLlsKTniakiLCAi54m55YaZIiwgIue0oOS6uiIsICLplIDllK4iLAogICAgICAgICAgICAi55Gc5Ly9IiwgIuWmqeWqmiIsICLlt6jkubMiLCAi5peg5aWXIiwgIua4lOe9kSIsICLlr7nnmb0iLCAi57OW5b+DIiwgIui2s+S6pCIsCiAgICAgICAgICAgICLkurrlprsiLCAi546p5YG2IiwgIuWls+elniIsICLmnoHlk4EiLCAi54af5aWzIiwgIueyieWrqSIsICLokIzlprkiLCAi5Y+j5LqkIiwKICAgICAgICAgICAgIuWHuui9qCIsICLliLbmnI0iLCAi56C05aSEIiwgIuengeaLjSIsICLogonmhJ8iLCAi576O6bKNIiwgIuWwkeWmhyIsCiAgICAgICAgICAgICLnqbrlp5AiLCAi5rOE5a+GIiwgIue6pueCriIsICLnl4nmjJsiLCAi5by65aW4IiwgIue+jueptCIsICLmjqLoirEiLAogICAgICAgICAgICAi6L+35aW4IiwgIumBk+WFtyIsICLlj4zpo54iLCAi5Lmx5LymIiwgIuS8muaJgCIsICLnl7TlpbMiLCAi5YaF5bCEIiwgIuWJp+aDhSIsICLml5fooo0iXQ==", sensitiveWords: [], blacklist: [ "porn", "sex", "xxx", "xvideos", "xhamster", "pornhub", "youporn", "erotic", "hentai", "jav", "adult", "tube8", "redtube" ], contentHandling: "replace", replacementText: "[敏感内容已屏蔽]", noticeStyle: { backgroundColor: "rgba(255, 68, 68, 0.9)", color: "white", padding: "8px 12px", borderRadius: "4px", fontSize: "12px", margin: "3px 0", display: "inline-block" }, globalWarning: { enable: true, message: "检测到可能包含不良内容的网站,请注意保护身心健康!", style: { position: "fixed", top: "20px", left: "50%", transform: "translateX(-50%)", backgroundColor: "#ff4444", color: "white", padding: "15px 20px", borderRadius: "8px", zIndex: "999999", fontSize: "14px", boxShadow: "0 2px 10px rgba(0,0,0,0.3)", maxWidth: "90%" }, closeBtn: { marginLeft: "15px", padding: "3px 8px", backgroundColor: "rgba(255,255,255,0.3)", border: "none", color: "white", borderRadius: "4px", cursor: "pointer", fontSize: "12px" } }, enhancedScanning: { scanScripts: true, monitorAjax: true, processJson: true, scanDelay: 300, // 缩短扫描延迟,更快响应 initialScanIntervals: [100, 300, 500, 1000, 2000] // 初始扫描时间点(毫秒) } }; // 解码敏感词列表 function decodeSensitiveWords() { try { const decodedStr = base64Decode(config.sensitiveWordsEncoded); if (decodedStr) { config.sensitiveWords = JSON.parse(decodedStr); console.log(`成功解码敏感词列表,共 ${config.sensitiveWords.length} 个敏感词`); } } catch (e) { console.error('解析敏感词列表失败:', e); config.sensitiveWords = ['色情', '低俗', '淫秽', '色情图片', '色情视频']; } } decodeSensitiveWords(); // 检查是否在白名单内 function isWhitelisted() { const hostname = window.location.hostname.toLowerCase(); return config.whitelist.some(domain => hostname.includes(domain.toLowerCase()) ); } // 检查是否在黑名单内 function isBlacklisted() { const url = window.location.href.toLowerCase(); const hostname = window.location.hostname.toLowerCase(); return config.blacklist.some(keyword => url.includes(keyword) || hostname.includes(keyword) ); } // 检查文本是否包含敏感词 function hasSensitiveWord(text) { if (!text || typeof text !== 'string') return false; const lowerText = text.toLowerCase(); return config.sensitiveWords.some(word => lowerText.includes(word.toLowerCase()) ); } // 获取文本中的敏感词 function getSensitiveWords(text) { if (!text || typeof text !== 'string') return []; const lowerText = text.toLowerCase(); return config.sensitiveWords.filter(word => lowerText.includes(word.toLowerCase()) ); } // 处理敏感内容元素 function handleSensitiveElement(element) { if (element.dataset.processed) return; element.dataset.processed = "true"; const content = element.textContent; const sensitiveWords = getSensitiveWords(content); if (sensitiveWords.length === 0) return; switch(config.contentHandling) { case "hide": element.style.visibility = "hidden"; break; case "blur": element.style.filter = "blur(4px)"; element.title = `包含敏感词: ${sensitiveWords.join(', ')}`; break; case "replace": const replacement = document.createElement('span'); const noticeStyle = config.noticeStyle || { backgroundColor: "rgba(255, 68, 68, 0.9)", color: "white", padding: "8px 12px", borderRadius: "4px", fontSize: "12px", margin: "3px 0", display: "inline-block" }; replacement.style.cssText = ` background: ${noticeStyle.backgroundColor}; color: ${noticeStyle.color}; padding: ${noticeStyle.padding}; border-radius: ${noticeStyle.borderRadius}; font-size: ${noticeStyle.fontSize}; margin: ${noticeStyle.margin}; display: ${noticeStyle.display}; `; replacement.textContent = `${config.replacementText || '[敏感内容已屏蔽]'} [识别到: ${sensitiveWords.join(', ')}]`; element.dataset.originalContent = content; element.innerHTML = ''; element.appendChild(replacement); break; } } // 从JS文本中提取可能的内容并处理 function processScriptContent(scriptText) { if (!config.enhancedScanning.scanScripts || !scriptText) return; const stringPattern = /(["'])(.*?)\1/g; let match; const extractedStrings = []; while ((match = stringPattern.exec(scriptText)) !== null) { if (match[2] && match[2].length > 5) { extractedStrings.push(match[2]); } } extractedStrings.forEach(str => { if (hasSensitiveWord(str)) { console.log(`检测到脚本中包含敏感内容: ${getSensitiveWords(str).join(', ')}`); setTimeout(() => { findAndProcessElementsWithText(str); }, config.enhancedScanning.scanDelay); } }); } // 查找并处理包含特定文本的元素 function findAndProcessElementsWithText(text) { const xpath = `//*[contains(text(), '${escapeXpathString(text)}')]`; const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < result.snapshotLength; i++) { const element = result.snapshotItem(i); if (element && !element.dataset.processed) { handleSensitiveElement(element); } } } // 转义XPath字符串中的特殊字符 function escapeXpathString(str) { return str.replace(/"/g, '\\"').replace(/'/g, "\\'"); } // 处理JSON数据 function processJsonData(jsonData) { if (!config.enhancedScanning.processJson || !jsonData) return; try { const jsonString = typeof jsonData === 'string' ? jsonData : JSON.stringify(jsonData); if (hasSensitiveWord(jsonString)) { console.log(`检测到JSON数据中包含敏感内容: ${getSensitiveWords(jsonString).join(', ')}`); setTimeout(scanContent, config.enhancedScanning.scanDelay); } } catch (e) { console.error('处理JSON数据时出错:', e); } } // 扫描内容(优化性能,减少DOM操作) function scanContent() { if (isWhitelisted()) return; // 1. 扫描常规文本元素 const targetTags = ['p', 'div', 'span', 'a', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'title', 'meta']; targetTags.forEach(tag => { const elements = document.getElementsByTagName(tag); const maxScan = 200; const scanCount = Math.min(elements.length, maxScan); for (let i = 0; i < scanCount; i++) { const element = elements[i]; if (!element.dataset.processed) { const content = tag === 'meta' ? element.getAttribute('content') : element.textContent; if (hasSensitiveWord(content)) { handleSensitiveElement(element); } } } }); // 2. 扫描script标签内容(如果启用) if (config.enhancedScanning.scanScripts) { const scripts = document.getElementsByTagName('script'); const maxScan = 50; const scanCount = Math.min(scripts.length, maxScan); for (let i = 0; i < scanCount; i++) { const script = scripts[i]; if (!script.dataset.processed && script.textContent) { script.dataset.processed = "true"; processScriptContent(script.textContent); } } } } // 显示全局警告(仅黑名单网站) function showGlobalWarning() { if (!config.globalWarning.enable || isWhitelisted()) return; if (document.getElementById('porn-warning-global')) return; const warning = document.createElement('div'); warning.id = 'porn-warning-global'; Object.keys(config.globalWarning.style).forEach(key => { warning.style[key] = config.globalWarning.style[key]; }); const message = document.createElement('span'); message.textContent = config.globalWarning.message; warning.appendChild(message); const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; Object.keys(config.globalWarning.closeBtn).forEach(key => { closeBtn.style[key] = config.globalWarning.closeBtn[key]; }); closeBtn.addEventListener('click', () => warning.remove()); warning.appendChild(closeBtn); document.body.appendChild(warning); } // 监控AJAX请求 function monitorAjaxRequests() { if (!config.enhancedScanning.monitorAjax || isWhitelisted()) return; // 重写XMLHttpRequest const originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this.addEventListener('load', function() { try { if (this.responseText && hasSensitiveWord(this.responseText)) { console.log(`检测到AJAX响应中包含敏感内容: ${url}`); processJsonData(this.responseText); setTimeout(scanContent, config.enhancedScanning.scanDelay); } } catch (e) { console.error('处理AJAX响应时出错:', e); } }); return originalXhrOpen.apply(this, arguments); }; // 监控fetch请求 const originalFetch = window.fetch; window.fetch = async function(input, init) { const response = await originalFetch.apply(this, arguments); const clonedResponse = response.clone(); try { const text = await clonedResponse.text(); if (hasSensitiveWord(text)) { console.log(`检测到Fetch响应中包含敏感内容: ${input}`); processJsonData(text); setTimeout(scanContent, config.enhancedScanning.scanDelay); } } catch (e) { console.error('处理Fetch响应时出错:', e); } return response; }; } // 初始化函数 function init() { if (isWhitelisted()) return; // 1. 显示全局警告(如果是黑名单网站) if (isBlacklisted()) { showGlobalWarning(); } // 2. 监控AJAX请求 monitorAjaxRequests(); // 3. 多时间点初始扫描(解决加载中内容检测问题) config.enhancedScanning.initialScanIntervals.forEach(interval => { setTimeout(scanContent, interval); }); // 4. 增强版DOM监听(更早启动,更快响应) const observer = new MutationObserver((mutations) => { observer.disconnect(); // 防止监听过程中触发新事件导致重复处理 let needRescan = false; mutations.forEach(mutation => { // 处理新增节点 mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { // 元素节点 if (!node.dataset.processed) { // 立即检查当前节点 if (hasSensitiveWord(node.textContent)) { handleSensitiveElement(node); } // 处理脚本内容 if (config.enhancedScanning.scanScripts && node.tagName === 'SCRIPT' && node.textContent) { processScriptContent(node.textContent); } needRescan = true; } } else if (node.nodeType === 3) { // 文本节点 if (hasSensitiveWord(node.textContent)) { needRescan = true; } } }); // 处理文本内容变化 if (mutation.type === 'characterData' && mutation.target.nodeType === 3) { if (hasSensitiveWord(mutation.target.textContent)) { needRescan = true; } } }); // 必要时重新扫描 if (needRescan) { setTimeout(scanContent, config.enhancedScanning.scanDelay); } // 重新启用监听(短延迟避免频繁触发) setTimeout(() => { observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: false }); }, 100); // 更短的重新连接延迟 }); // 立即启动DOM监听,不等待页面加载完成 observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: false }); } // 优化初始化时机:DOM开始解析就执行,不等待完全加载 if (document.readyState === 'loading') { // DOM还在加载中,立即启动初始化 init(); } else if (document.readyState === 'interactive') { // DOM已解析完成,但资源可能还在加载 init(); } else { // 页面已完全加载 init(); } // 同时监听DOMContentLoaded事件(DOM解析完成时) document.addEventListener('DOMContentLoaded', init); })();