您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
荷包蛋自用净化推特贴文的脚本,全自动隐藏MTF相关贴文,检测内容包括贴文正文,贴文标签,用户名,用户简介,支持对emoji的检测
// ==UserScript== // @name Twitter MTF killer // @namespace http://tampermonkey.net/ // @version 2.0 // @description 荷包蛋自用净化推特贴文的脚本,全自动隐藏MTF相关贴文,检测内容包括贴文正文,贴文标签,用户名,用户简介,支持对emoji的检测 // @author Ayase // @match https://twitter.com/* // @match https://x.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect x.com // @connect twitter.com // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 关键词配置 --- const BLOCKED_KEYWORDS_RAW = [ '男娘', '伪娘', '药娘', '男同', 'mtf', '🏳️⚧️', '🏳️🌈', '跨性别', '扶她', 'futa', '性转', 'LGBT', '🍥', 'furry', '男童', '福瑞' ]; const keywords = BLOCKED_KEYWORDS_RAW.map(k => k.trim().toLowerCase()).filter(k => k.length > 0); const userBioCache = new Map(); let isCurrentProfileBlocked = false; let lastCheckedUrl = ''; GM_addStyle(` #blocker-toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 10px; } .blocker-toast-message { background-color: rgba(29, 155, 240, 0.9); color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); opacity: 0; transform: translateX(100%); transition: all 0.4s cubic-bezier(0.21, 1.02, 0.73, 1); font-size: 14px; line-height: 1.4; } .blocker-toast-message.show { opacity: 1; transform: translateX(0); } .blocker-toast-message b { font-weight: bold; } `); const initToastContainer = () => { if (!document.getElementById('blocker-toast-container')) { const container = document.createElement('div'); container.id = 'blocker-toast-container'; document.body.appendChild(container); } }; const showNotification = (message) => { const container = document.getElementById('blocker-toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = 'blocker-toast-message'; toast.innerHTML = message; container.appendChild(toast); setTimeout(() => toast.classList.add('show'), 10); setTimeout(() => { toast.classList.remove('show'); toast.addEventListener('transitionend', () => toast.remove()); }, 2500); }; const getElementTextWithEmojiAlt = (element) => { if (!element) return ''; let fullText = element.textContent || ''; const emojiImages = element.querySelectorAll('img[alt]'); emojiImages.forEach(img => { fullText += ` ${img.alt}`; }); return fullText; }; const findMatchingKeyword = (text) => { if (!text || keywords.length === 0) return null; const lowerText = text.toLowerCase(); for (const keyword of keywords) { if (lowerText.includes(keyword)) return keyword; } return null; }; const checkUserBioInBackground = (username, tweetElement) => { if (userBioCache.get(username)) return; userBioCache.set(username, 'checking'); GM_xmlhttpRequest({ method: 'GET', url: `https://x.com/${username}`, onload: function(response) { const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); const bioElement = doc.querySelector('[data-testid="UserDescription"]'); const bioText = getElementTextWithEmojiAlt(bioElement); const matchedKeyword = findMatchingKeyword(bioText); if (matchedKeyword) { userBioCache.set(username, 'blocked'); hideTweet(tweetElement, `简介含 "<b>${matchedKeyword}</b>"`, `@${username}`); } else { userBioCache.set(username, 'safe'); } }, onerror: function(response) { console.error(`[Twitter Blocker] 获取 @${username} 的主页失败:`, response); userBioCache.set(username, 'safe'); } }); }; const hideTweet = (tweetElement, reason, source) => { const message = `已屏蔽 (<b>${source}</b>)<br>原因: ${reason}`; showNotification(message); console.log(`[Twitter Blocker] ${message.replace(/<br>|<b>|<\/b>/g, ' ')}`); const parentCell = tweetElement.closest('div[data-testid="cellInnerDiv"]'); if (parentCell) { parentCell.style.display = 'none'; } else { tweetElement.style.display = 'none'; } }; const processTweet = (tweetElement) => { if (tweetElement.dataset.blockerChecked) return; tweetElement.dataset.blockerChecked = 'true'; if (isCurrentProfileBlocked) { hideTweet(tweetElement, "当前主页已被屏蔽", "主页状态"); return; } let matchedKeyword, reason, source; const tweetTextElement = tweetElement.querySelector('[data-testid="tweetText"]'); const tweetText = getElementTextWithEmojiAlt(tweetTextElement); matchedKeyword = findMatchingKeyword(tweetText); if (matchedKeyword) { hideTweet(tweetElement, `内容含 "<b>${matchedKeyword}</b>"`, "推文内容"); return; } const userLinkElement = tweetElement.querySelector('[data-testid="User-Name"] a[href^="/"]'); if (!userLinkElement) return; const username = userLinkElement.getAttribute('href').substring(1); const userDisplayName = getElementTextWithEmojiAlt(userLinkElement); source = `<b>${userDisplayName}</b> (@${username})`; matchedKeyword = findMatchingKeyword(username) || findMatchingKeyword(userDisplayName); if (matchedKeyword) { hideTweet(tweetElement, `用户名含 "<b>${matchedKeyword}</b>"`, source); return; } const cacheResult = userBioCache.get(username); if (cacheResult === 'blocked') { hideTweet(tweetElement, "简介(来自缓存)", source); return; } if (!cacheResult) { checkUserBioInBackground(username, tweetElement); } }; const processProfile = () => { const bioElement = document.querySelector('[data-testid="UserDescription"]'); if (bioElement && !bioElement.dataset.blockerChecked) { bioElement.dataset.blockerChecked = 'true'; const bioText = getElementTextWithEmojiAlt(bioElement); const matchedKeyword = findMatchingKeyword(bioText); if (matchedKeyword) { isCurrentProfileBlocked = true; const message = `用户主页已屏蔽<br>原因: 简介含 "<b>${matchedKeyword}</b>"`; showNotification(message); console.log(`[Twitter Blocker] ${message.replace(/<br>|<b>|<\/b>/g, ' ')}`); scanAndBlock(true); } } }; const scanAndBlock = (forceScan = false) => { if (window.location.href !== lastCheckedUrl) { lastCheckedUrl = window.location.href; isCurrentProfileBlocked = false; } const path = window.location.pathname; const isProfilePage = path.split('/').length === 2 && path.length > 1 && !path.includes('/i/') && !/^\/(home|explore|notifications|messages|search|settings)/.test(path); if (isProfilePage) { processProfile(); } const tweets = document.querySelectorAll('article[data-testid="tweet"]:not([data-blocker-checked])'); tweets.forEach(processTweet); }; const observer = new MutationObserver(() => scanAndBlock()); const start = () => { if (keywords.length === 0) return console.log('[Twitter Blocker] 关键词列表为空,脚本未启动。'); console.log('[Twitter Blocker] 智能屏蔽脚本已启动,当前关键词:', keywords); initToastContainer(); window.requestIdleCallback ? requestIdleCallback(scanAndBlock) : setTimeout(scanAndBlock, 500); observer.observe(document.body, { childList: true, subtree: true }); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })();