// ==UserScript== // @name Twitter自用增强 // @namespace http://viayoo.com/bhivy // @version 2.6 // @description 自用脚本,过滤推特(Twitter)评论区,去广告,尝试解锁敏感限制。 // @author Via && Deepseek // @match https://x.com/* // @exclude https://x.com/*/status/*/video/* // @exclude https://x.com/*/status/*/photo/* // @exclude https://x.com/*/status/*/mediaViewer* // @exclude https://x.com/settings // @exclude https://x.com/i/*/creators/ // @exclude https://x.com/i/bookmarks // @exclude https://x.com/i/premium-business // @exclude https://x.com/*/lists // @exclude https://x.com/i/follow_people // @exclude https://x.com/i/chat // @exclude https://x.com/notifications // @exclude https://x.com/i/grok // @icon data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA0pJREFUWAntVk1oE1EQnnlJbFK3KUq9VJPYWgQVD/5QD0qpfweL1YJQoZAULBRPggp6kB78PQn14kHx0jRB0UO9REVFb1YqVBEsbZW2SbVS0B6apEnbbMbZ6qbZdTempqCHPAjvzcw3P5mdmfcAiquYgX+cAVwu/+5AdDMQnSPCHUhQA0hf+Rxy2OjicIvzm+qnKhito0qpb2wvJhWeJgCPP7oPELeHvdJ1VSGf3eOPnSWga0S0Qo9HxEkEusDBuNjbEca8G291nlBxmgDc/ukuIvAJxI6wr+yKCsq1ewLxQ2lZfpQLo8oQ4ZXdCkfnACrGWpyDCl+oQmVn5xuVPU102e2P3qoJkFOhzVb9S7KSnL5jJs/mI+As01PJFPSlZeFSZZoAGBRXBZyq9lk5NrC+e7pJ5en30c+JWk59pZ5vRDOuhAD381c/H/FKz1SMNgCE16rg505r5TT0uLqme93d0fbq+1SeLSeU83Ke0RHYFPGVPcjQfNDUwIa7M665+dQAEEjZoMwZMcEF9RxIDAgBQ2mCcqJ0Z0b+h4MNbZ4RnyOSDbNmE2iRk5jCNgIIckFoZAs4IgfLGrlKGjkzS16iwj6pV9I4mUvCPf73JVytH9nRJj24QHrqU8NCIWrMaGqAC+Ut/3ZzAS63cx4v2K/x/IvQBOCwWzu5KmJGwEJ5PIgeG9nQBDDcXPpFoDjJ7ThvBC6EZxXWkJG+JgAFwGM4KBAOcibeGCn8FQ/hyajXPmSk+1sACogn4hYk7OdiHDFSWipPkPWSmY6mCzIghEEuxJvcEYUvxIdhX2mvmSHDDPBF9AJRnDZTyp+P40671JYLbxiAohDxSTfQIg4oNxgPzCWPHaWQBViOf2jGqVwBaEaxGbAqOFMrp+SefC8eNhoFIY5lXzpmtnMGUB2IbU3JdIqVW9m5zcxINn/hAYKiIexdaTh4srHKORMAP0b28PNgJyGt5gvHzQVYx91QpVcwpRFl/p63HSR1DLbid1OcTpAJQOG7u+KH+aI5Qwj13IsamU5vkUSIc8uGLDa8OtoivV8U5HcydFLtT7hlSDVy2nfxI2Ibg9awuVU8IeJAOMF5m2B6jFs1tM5R9rS3GRP5uSuiihn4DzPwA7z7GDH+43gqAAAAAElFTkSuQmCC // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setClipboard // @run-at document-end // ==/UserScript== (function() { 'use strict'; const api = { getValue: (key, def) => { if (typeof GM_getValue !== 'undefined') return GM_getValue(key, def); const val = localStorage.getItem(key); return val === null ? def : JSON.parse(val); }, setValue: (key, val) => { if (typeof GM_setValue !== 'undefined') { GM_setValue(key, val); } else { localStorage.setItem(key, JSON.stringify(val)); } }, setClipboard: (text) => { if (typeof GM_setClipboard !== 'undefined') { GM_setClipboard(text); } else { navigator.clipboard.writeText(text); } } }; const DEFAULT_CONFIG = { badUsers: ["@xiaonm88","@ruantang992","约炮.","约.炮.",".约炮","处男免费","约炮.","免费破处","同城上门","上门服务","上门破处","同城无偿","听主人的话","处男无偿","处男免费","宇宙第一骚","chu男无偿","想找单男","找个搭子","找个男大弟弟","母狗找主人","寻男大固炮","寻固炮","每晚准时色播","每晚准时大秀"], badWords: ["/有(哥哥|弟弟).*线(上|下)(约|吗)/i","/^[\\p{Emoji}\\n]+$/u","/^(?:线上|线下)?(?:蹲|找)一?个(?:男|女)?(?:长期|疼人|临时|温柔)?(?:固炮|搭子|主人|爸爸|哥哥|弟弟|主|[Ss])/","/^(?=.*我是真人)(?=.*线[上下][的吗])/","/(?:dd?|个?|线上|线下)+的?(?:姐姐|弟弟|哥哥|妹妹).{0,6}/i","/有没有离[得的]近[得的].{0,4}$/","/^线下[Dd][Dd]$/","/(?:急需?|找)(?:一个|一位)?(?:主人|爸爸|固炮|搭子).{0,6}$/","/(?:主人|爸爸)快来(?:领|找)我.{0,6}$/","/^..找..(?:主人|主|爸爸|固炮|搭子).{0,6}$/","/小(?:狗|🐶|[Mm])(?:在线|想[和跟])(?:等|找|大家|主人|主|爸爸|固炮|搭子|调|你).{0,6}/","/^有?万达广场附近的吗$/","/^(?:男大|女大)大?(?:哥哥|弟弟|姐姐|妹妹)快?来$/","/^谁来当我(?:主人|爸爸|狗狗|小狗|骚母狗).{0,6}$/","/(?:同城|附近).*(?:满足|草|操|艹|男大|女大|体育生|母狗)/","/^(?:小|母)狗求(?:主人|爸爸|哥哥)(?:抱抱|操|艹|草|抱走)/","/^有没有(?:单男|母狗|骚逼).{0,6}$/","/pan\\.(?:quark\\.cn|xunlei\\.com)|drive\\.uc\\.cn/","@xiaonm88","@ruantang992","@designksh1","/(?:喜欢我|颠勺|大|这个骚货|线下我就曰|线下骚货)+/","/(?:推特|第一骚)\\@.+/","/(?:她好(瑟|色|骚|[Ss][Aa][Oo])|我不行了|刷了半天|就她的主页能打)+/","/比她(?:骚|好看)的没/","/线下(?:骚|[Ss][Aa][Oo]|比她|货)+/"] }; let config = api.getValue('filterConfig', DEFAULT_CONFIG); const compileRules = (words) => words.map(r => { try { if (r.startsWith('/') && r.lastIndexOf('/') > 0) { const lastSlashIndex = r.lastIndexOf('/'); return new RegExp(r.substring(1, lastSlashIndex), r.substring(lastSlashIndex + 1)); } return new RegExp(r); } catch (e) { return null; } }).filter(Boolean); let compiledRules = compileRules(config.badWords); const PROCESSED_TWEETS = new WeakSet(); const FILTER_CACHE = new Map(); const cleanText = (text) => { if (!text) return ""; return text.replace(/[\u0000-\u001F\u007F-\u009F\u00AD\u061C\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF\u200D\u200C\u180E\u202F\u205F\u2060\uFE00-\uFE0F]/g, '').trim(); }; const isSpamBot = (tweetTextEl, tweetNode) => { if (!tweetTextEl) return false; const pureText = cleanText(tweetTextEl.innerText || ""); const nameEl = tweetNode?.querySelector('[data-testid="User-Name"]'); const nickName = nameEl ? cleanText(nameEl.innerText.split(/[\n·]/)[0]) : ""; const nameEmojiEls = nameEl ? nameEl.querySelectorAll('img[src*="emoji"]') : []; const tweetEmojiEls = tweetTextEl.querySelectorAll('img[src*="emoji"]'); const spamEmojiList = ["⬆️","🍓","🌸","💊","👠","💯","🍆","🍑","🚪","🅱️","🔞","❤"]; let nameEmojiCount = 0; for (let img of nameEmojiEls) { if (spamEmojiList.includes(img.getAttribute('alt'))) nameEmojiCount++; } const nameSpamStrict = /(?:约[ .]?炮|chu男|破处|处男|寻|找|蹲|求|想找)(?:免费|无偿|单男|固炮|搭子|主[人子]|哥哥|弟弟|男大|女大|线上|线下)|(?:每晚|准时)(?:大秀|色播|直播)|(?:母狗|骚货|第一骚|体育生|女大|男大)(?:求|找|寻|固炮|搭子|主[人子])|[(\uff08](?:饥渴|处女|寂寞|男大来)/i; const nameSpamSuspect = /(?:同城|附近|蹲|找个|野生|约|萝莉|小乔|欣|萌白|呆萌|成人|限制|破除|破限|AI|丝丝|小猫|茜茜|上门|服务|满足|线下|联系|安排|男大|女大|单男|体育生|附近的)/i; if (nickName) { if (nameSpamStrict.test(nickName)) return true; if (nameEmojiCount > 0 && nameSpamSuspect.test(nickName)) return true; } let totalSpamEmojiCount = nameEmojiCount; for (let img of tweetEmojiEls) { if (spamEmojiList.includes(img.getAttribute('alt'))) totalSpamEmojiCount++; } if (totalSpamEmojiCount >= 3) return true; const hasPunctuation = /[\u3002\uff0c\uff1f\uff01\uff1b\uff1a\u3001.?!,;:]/.test(pureText); if (tweetEmojiEls.length > 0 && !hasPunctuation && pureText.length < 4) return true; const specialSymbols = /[𖬺༄𖬚✫꙳⸝⸝𖨋༺꙼꙽༻ꦿ⬆↑⇑]/g; const symbolMatches = pureText.match(specialSymbols); if (symbolMatches && symbolMatches.length >= 3) return true; return false; }; const twitterContentUnblocker = (() => { const TARGET_ENDPOINTS = ['UserTweets', 'TweetDetail', 'SearchTimeline', 'HomeTimeline']; const rewriteResponse = (rawText) => { try { return rawText .replace(/"possibly_sensitive":\s*true/g, '"possibly_sensitive":false') .replace(/"profile_interstitial_type":\s*"sensitive_media"/g, '"profile_interstitial_type":""') .replace(/"mediaVisibilityResults":/g, '"mediaVisibilityResults_bypass":'); } catch (e) { return rawText; } }; return () => { const XHR = XMLHttpRequest.prototype; const send = XHR.send; const open = XHR.open; XHR.open = function(method, url) { this._url = url; return open.apply(this, arguments);}; XHR.send = function() { if (this._url && TARGET_ENDPOINTS.some(ep => this._url.includes(ep))) { const responseTextGetter = Object.getOwnPropertyDescriptor(XHR, 'responseText').get; Object.defineProperty(this, 'responseText', { get: function() { const originalText = responseTextGetter.call(this); return typeof originalText === 'string' ? rewriteResponse(originalText) : originalText; }, configurable: true }); } return send.apply(this, arguments); }; }; })(); const twitterAdCleaner = (() => { const PROCESSED_NODES = new WeakSet(); const AD_SELECTORS = ["div[data-testid='placementTracking']", "button[aria-label='Grok actions']", "aside[role='complementary']"]; const isAdText = (t) => ['Promoted', '广告', '推廣', '広告', 'Sponsorisé'].includes(t); const processNode = (node) => { if (!node || node.nodeType !== 1 || PROCESSED_NODES.has(node)) return; try { if (node.getAttribute('data-testid') === 'cellInnerDiv') { const hasAdSignal = node.querySelector(AD_SELECTORS[0]); const adLabel = Array.from(node.querySelectorAll('article span')).find(s => isAdText(s.textContent)); if (hasAdSignal || adLabel) { node.style.setProperty('display', 'none', 'important'); PROCESSED_NODES.add(node); return; } } if (node.matches?.(AD_SELECTORS[1]) || node.matches?.(AD_SELECTORS[2])) { const isGrok = node.matches(AD_SELECTORS[1]); const isAdSidebar = node.innerText && isAdText(node.innerText.split('\n')[0]); if (isGrok || isAdSidebar) { node.style.display = 'none'; } } } catch (e) {} }; return () => { const observer = new MutationObserver((m) => { requestAnimationFrame(() => m.forEach(mu => mu.addedNodes.forEach(processNode))); }); observer.observe(document.body, { childList: true, subtree: true }); document.querySelectorAll('div[data-testid="cellInnerDiv"]').forEach(processNode); }; })(); const quickFilterUI = (tweetData, tweetNode) => { const existing = document.getElementById('tf-quick-panel'); if (existing) existing.remove(); const box = document.createElement('div'); box.id = 'tf-quick-panel'; const shadow = box.attachShadow({mode: 'closed'}); const style = document.createElement('style'); style.textContent = ` :host { position: fixed; inset: 0; z-index: 1000000; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); font-family: -apple-system, system-ui; } .card { background: white; width: 320px; padding: 20px; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } @media (prefers-color-scheme: dark) { .card { background: #15202b; color: white; } } h4 { margin: 0 0 15px 0; font-size: 16px; } .opt { background: rgba(128,128,128,0.1); padding: 10px; border-radius: 8px; margin-bottom: 8px; cursor: pointer; font-size: 13px; word-break: break-all; border: 1px solid transparent; } .opt:hover { border-color: #1d9bf0; background: rgba(29,155,240,0.1); } textarea { width: 100%; height: 60px; margin-top: 5px; border-radius: 8px; border: 1px solid #ccc; padding: 5px; font-size: 12px; resize: none; box-sizing: border-box; } .btns { display: flex; gap: 8px; margin-top: 15px; } button { flex: 1; padding: 8px; border-radius: 20px; border: none; font-weight: bold; cursor: pointer; } .add { background: #1d9bf0; color: white; } .cancel { background: #eee; color: #333; } `; const cleanVal = cleanText(tweetData.text); const regexVal = `/${cleanVal.substring(0, 10).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*/`; const getFilterName = (name) => { if (!name) return ""; const parts = name.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]|\u200D/); if (parts.length <= 1) return name; let maxPart = ""; for (let i = 0; i < parts.length; i++) { const p = parts[i].trim(); if (p.length >= maxPart.length) maxPart = p; } return maxPart || name; }; const filterName = getFilterName(tweetData.userName); shadow.appendChild(style); const card = document.createElement('div'); card.className = 'card'; card.innerHTML = `
${regexVal}