Greasy Fork is available in English.
强力过滤引流/币圈/擦边/博彩等机器人内容,支持快捷键添加、管理面板与用户名白名单。
// ==UserScript==
// @name X 黄推+币圈机器人终极隐藏器
// @namespace https://grok.x.ai
// @version 1.8
// @description 强力过滤引流/币圈/擦边/博彩等机器人内容,支持快捷键添加、管理面板与用户名白名单。
// @author Grok、codex、Claude
// @match https://x.com/*
// @match https://twitter.com/*
// @grant none
// @run-at document-start
// @license MIT
// @icon http://greasyfork.icu/images/blacklogo16.png
// @homepageURL http://greasyfork.icu/zh-CN/scripts/570683
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'x_block_words_v1';
const WHITELIST_KEY = 'x_whitelist_names_v1';
const HIDDEN_ATTR = 'data-x-ultra-hidden';
const KEEP_SPACE_WHEN_HIDE = false;
const CACHE_HASH_ATTR = 'data-x-last-hash';
const CACHE_SHOULD_ATTR = 'data-x-last-should';
const BUILTIN_PATTERNS = [
/快领我回家|扣1白给|陪我聊聊天|有弟弟来一起|推特第一骚|我约过她|姐姐在等你哦|你喜欢我的我都有哦/i,
/懂[得的].{0,3}(来|私|入|dd|联系|撩|进|加|懂)/i,
/免费破|私处|想被人炒|万达广场|约个有经验的大叔|找我那视频啊|找个哥哥调教|小m在线等调/i,
/男大|足控|蜜桃臀|推特刷粉丝|刷粉/i,
/主人|舔狗|小狗|线下|上门|白虎|急找|私约|野战|在线蹲|蹲一个/i,
/有没有单男|有没有游戏搭子|有没有单女|单男/i,
/急需一位|chu男|Chu男|处男|处女找个哥哥|无偿|免费破|调教|附近的有没有|满足我|看做爱|不如做爱/i,
/母狗|欲望少女|纯欲|反差/i,
/我是真人/i,
/那亲亲吧|你的娱乐群/i,
/空投|USDT.*(奖池|交易|转账|推广)|拉盘|打新项目|上币路线|Pre-IPO|合约.*(喊单|爆仓)/i,
/引流|私信|DM|加V|VX|微信|电报|TG|群号|群聊|福利群|进群|进组/i,
/开户|理财|搬砖|量化|带单|套利|返利|高收益|稳赚|保本|日赚/i,
/博彩|赌场|下注|百家乐|时时彩|快三|网投|棋牌游戏/i,
/约炮|一夜情|同城|包夜|全套|外围|小姐|嫩模|学生妹/i,
/裸聊|视频聊天|视频交友|语音交友|聊骚|骚扰|撩骚/i,
/兼职|日结|招募|代理|推广员|刷单|返现|冲量/i,
/空降|互关|互粉|互赞|点赞关注|关注我|回关|求关/i,
/[\u2190-\u21FF\u2B00-\u2BFF\u27A1]/,
/搭子|固炮/i,
/陪聊|陪玩|陪睡|宠物系/i,
/在线等(你|哥哥|弟弟)/i,
/淫/i,
/温柔主(人)?/i,
/(^|[^A-Za-z0-9])[sSmM]([^A-Za-z0-9]|$)/,
/萌猫|小烧货|千万次心跳|护士姐姐|学妹|学姐|反差婊/i,
/(萌|甜|软|奶|乖|小|可爱).*(猫|狗|兔|鹿|狐|虎|猫咪)/i,
/(护士|学妹|学姐|老师|姐姐|妹妹).*(小烧货|反差|纯欲|欲望)/i,
/\b[A-Z][a-z]{4,}[A-Z][a-z]{1,3}\d{5,}\b/,
/有没有离得近的|被.*(小烧货|骚货|sao货)|扣1白给啊?|温柔s|爹系男友/i,
/(射射|色色|涩涩|瑟瑟).{0,6}视频|瓦学弟|破处/i,
/催情|春药|迷药|催春|迷幻|迷奸|买春|迷.?药/i
];
const NAME_PATTERNS = [
/^Date-/i,
/反差|母狗|chu男|Chu男|处男|处女|臀|破处|私约|野战|处男|白虎|无偿|处女|找主人|免费|温柔主/i,
/\b[A-Z][a-z]{4,}[A-Z][a-z]{1,3}\d{5,}\b/,
/懂[得的].{0,3}(来|私|入|dd|联系|撩|进|加|懂)/i,
/男大|足控|线下|曰|日|免费线下|被人炒|后入|同城|可飞|蜜桃臀|推特刷粉丝|刷粉/i
];
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 同型字/形近字映射:黄推常用替换手段,normalize 前先还原
const CONFUSABLES = {
'\u66f0': '\u65e5', // 曰→日
'\u2f72': '\u65e5', // ⽇→日 (康熙部首)
'\u7384': '\u4e3b', // 玊→主 (少见但有)
'\u6bcb': '\u6bcd', // 毋→母
'\u72ac': '\u5927', // 犬→大
'\u5165': '\u4eba', // 入→人
'\u56d7': '\u53e3', // 囗→口
'\u7dab': '\u7ebf', // 線→线
'\u7e10': '\u7ebf', // 縐→线 (繁体)
'\u7dda': '\u7ebf', // 綫→线
'\u51e6': '\u5904', // 処→处 (日语汉字)
'\u5df3': '\u5df2', // 巳→已
'\u5df1': '\u5df2', // 己→已
};
// 全角字母/数字 → 半角 (U+FF21-FF3A → A-Z, U+FF41-FF5A → a-z, U+FF10-FF19 → 0-9)
for (let i = 0; i < 26; i++) {
CONFUSABLES[String.fromCharCode(0xFF21 + i)] = String.fromCharCode(0x41 + i);
CONFUSABLES[String.fromCharCode(0xFF41 + i)] = String.fromCharCode(0x61 + i);
}
for (let i = 0; i < 10; i++) {
CONFUSABLES[String.fromCharCode(0xFF10 + i)] = String.fromCharCode(0x30 + i);
}
const CONFUSABLE_RE = new RegExp('[' + Object.keys(CONFUSABLES).map(c =>
'\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')
).join('') + ']', 'g');
function deconfuse(text) {
return (text || '').replace(CONFUSABLE_RE, ch => CONFUSABLES[ch] || ch);
}
// 剥除零宽空格、零宽(非)连接符、方向标记、变体选择符、
// 软连字符、BOM、字节序标记等所有不可见干扰字符
const INVISIBLE_RE = /[\u200B-\u200F\u2028-\u202F\u2060-\u206F\uFEFF\u00AD\u034F\u061C\u180E\uFFF9-\uFFFC\u{E0001}\u{E0020}-\u{E007F}\u{13430}-\u{1343F}]/gu;
function stripInvisible(text) {
return (text || '').replace(INVISIBLE_RE, '');
}
function normalize(text) {
return deconfuse(stripInvisible(text))
.toLowerCase()
.replace(/[\s\.\,\!\?\-_/\\()\[\]{}"'`~@#$%^&*+=<>|:;、,。!?【】()《》“”‘’·]+/g, '')
.replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}]/gu, '');
}
function isMostlyEmoji(text) {
const stripped = (text || '').replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}\s\.,!?;:、,。!?【】()《》“”‘’·~`'"-]/gu, '');
const emojiCount = ((text || '').match(/[\p{Extended_Pictographic}]/gu) || []).length;
return stripped.length === 0 && emojiCount >= 3;
}
function emojiOnlyWithHearts(text) {
const t = text || '';
const nonEmoji = t.replace(/[\p{Extended_Pictographic}\uFE0F\u{1F3FB}-\u{1F3FF}\s\.,!?;:、,。!?【】()《》“”‘’·~`'"-]/gu, '');
if (nonEmoji.length > 2) return false;
const emojis = (t.match(/[\p{Extended_Pictographic}]/gu) || []).length;
if (emojis < 1) return false;
return /[\u2764\uFE0F\u{1F49B}\u{1F49A}\u{1F499}\u{1F49C}\u{1F90E}\u{1F90D}\u{1F5A4}\u2763]/u.test(t);
}
function loadUserWords() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
const list = raw ? JSON.parse(raw) : [];
return Array.isArray(list) ? list : [];
} catch {
return [];
}
}
function loadWhitelist() {
try {
const raw = localStorage.getItem(WHITELIST_KEY);
const list = raw ? JSON.parse(raw) : [];
return Array.isArray(list) ? list : [];
} catch {
return [];
}
}
let userWords = loadUserWords();
let userRegexes = buildUserRegexes(userWords);
let whitelistNames = loadWhitelist();
let rulesVersion = 0;
function buildUserRegexes(words) {
return words
.map(w => String(w || '').trim())
.filter(Boolean)
.map(w => new RegExp(`.*${escapeRegExp(w)}.*`, 'i'));
}
function saveUserWords() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(userWords));
userRegexes = buildUserRegexes(userWords);
rulesVersion++;
}
function saveWhitelist() {
localStorage.setItem(WHITELIST_KEY, JSON.stringify(whitelistNames));
rulesVersion++;
}
function addUserWord(word) {
const w = String(word || '').trim();
if (!w) return;
if (userWords.includes(w)) return;
userWords.push(w);
saveUserWords();
toast(`已加入屏蔽词:${w}`);
scheduleScan();
}
function addWhitelistName(word) {
const w = String(word || '').trim();
if (!w) return;
if (whitelistNames.includes(w)) return;
whitelistNames.push(w);
saveWhitelist();
toast(`已加入白名单:${w}`);
scheduleScan();
}
function isWhitelisted(nameText) {
if (!nameText) return false;
const raw = nameText.toLowerCase();
const compact = normalize(nameText);
return whitelistNames.some(w => {
const ww = String(w || '').trim().toLowerCase();
if (!ww) return false;
const wCompact = normalize(ww);
return raw.includes(ww) || compact.includes(wCompact);
});
}
function hashString(value) {
let hash = 5381;
for (let i = 0; i < value.length; i++) {
hash = ((hash << 5) + hash) ^ value.charCodeAt(i);
}
return hash >>> 0;
}
function shouldHide(text, nameText) {
const original = deconfuse(stripInvisible(text || ''));
const compact = normalize(original);
const name = deconfuse(stripInvisible(nameText || ''));
const nameCompact = normalize(name);
const whitelisted = isWhitelisted(name);
if (emojiOnlyWithHearts(original) || isMostlyEmoji(original)) return true;
// 用户黑名单(内容)优先命中,避免被 quickMatch 短路
if (userRegexes.some(r => r.test(original) || r.test(compact))) return true;
if (!whitelisted && NAME_PATTERNS.some(r => r.test(name) || r.test(nameCompact))) return true;
if (!whitelisted && BUILTIN_PATTERNS.some(r => r.test(name) || r.test(nameCompact))) return true;
if (BUILTIN_PATTERNS.some(r => r.test(original) || r.test(compact))) return true;
if (!whitelisted && userRegexes.some(r => r.test(name) || r.test(nameCompact))) return true;
return false;
}
function extractTextWithEmojis(node) {
if (!node) return '';
let text = '';
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes[i];
if (child.nodeType === 3) {
text += child.nodeValue;
} else if (child.nodeType === 1) {
if (child.tagName === 'IMG' && child.alt) {
text += child.alt;
} else {
text += extractTextWithEmojis(child);
}
}
}
return text;
}
function collectArticleText(article) {
const parts = [];
article.querySelectorAll('[data-testid="tweetText"]').forEach(node => {
const text = extractTextWithEmojis(node);
if (text) parts.push(text);
});
return parts.join(' ');
}
function collectNameText(article) {
const nameNode = article.querySelector('[data-testid="User-Name"]');
return nameNode ? (nameNode.textContent || '') : '';
}
function unhideArticle(article) {
if (!article.getAttribute(HIDDEN_ATTR)) return;
article.removeAttribute(HIDDEN_ATTR);
article.style.removeProperty('visibility');
article.style.removeProperty('display');
article.style.removeProperty('pointer-events');
const container = article.closest('div[data-testid="cellInnerDiv"]');
if (container) {
container.style.removeProperty('display');
container.style.removeProperty('visibility');
}
}
function hideArticle(article) {
const text = collectArticleText(article).trim();
const nameText = collectNameText(article).trim();
const hash = hashString(`${rulesVersion}|${text}|${nameText}`);
const cachedHash = article.getAttribute(CACHE_HASH_ATTR);
const cachedShould = article.getAttribute(CACHE_SHOULD_ATTR);
let should;
if (cachedHash && Number(cachedHash) === hash && cachedShould !== null) {
should = cachedShould === '1';
} else {
should = shouldHide(text, nameText);
article.setAttribute(CACHE_HASH_ATTR, String(hash));
article.setAttribute(CACHE_SHOULD_ATTR, should ? '1' : '0');
}
if (!should) {
unhideArticle(article);
return;
}
if (article.getAttribute(HIDDEN_ATTR)) return;
article.setAttribute(HIDDEN_ATTR, '1');
if (KEEP_SPACE_WHEN_HIDE) {
article.style.setProperty('visibility', 'hidden', 'important');
article.style.setProperty('pointer-events', 'none', 'important');
} else {
article.style.setProperty('display', 'none', 'important');
article.style.setProperty('visibility', 'hidden', 'important');
const container = article.closest('div[data-testid="cellInnerDiv"]');
if (container) container.style.setProperty('display', 'none', 'important');
}
}
function scanAll() {
document.querySelectorAll('article[data-testid="tweet"]').forEach(hideArticle);
}
let scanInProgress = false;
function scheduleScan() {
if (scanInProgress) return;
scanInProgress = true;
scanAll();
scanInProgress = false;
}
function handleMutations(mutations) {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches && node.matches('article[data-testid="tweet"]')) {
hideArticle(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('article[data-testid="tweet"]').forEach(hideArticle);
}
}
}
}
function getSelectionText() {
return String(window.getSelection && window.getSelection().toString() || '').trim();
}
function createManagePanel() {
const panel = document.createElement('div');
panel.id = 'x-ultra-block-panel';
panel.style.cssText = [
'position:fixed',
'right:16px',
'bottom:64px',
'width:280px',
'max-height:360px',
'overflow:auto',
'background:#111',
'color:#fff',
'border:1px solid #444',
'border-radius:10px',
'padding:10px',
'font-size:12px',
'z-index:2147483647',
'display:none'
].join(';');
const header = document.createElement('div');
header.textContent = '屏蔽词管理';
header.style.cssText = 'font-weight:600; margin-bottom:8px;';
const list = document.createElement('div');
const inputRow = document.createElement('div');
inputRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
const input = document.createElement('input');
input.placeholder = '手动添加屏蔽词';
input.style.cssText = 'flex:1; padding:4px 6px; border:1px solid #333; border-radius:6px; background:#0b0b0b; color:#fff;';
const addBtn = document.createElement('button');
addBtn.textContent = '添加';
addBtn.style.cssText = 'padding:4px 8px; cursor:pointer;';
inputRow.appendChild(input);
inputRow.appendChild(addBtn);
const quickRow = document.createElement('div');
quickRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
const addSelBtn = document.createElement('button');
addSelBtn.textContent = '添加选中';
addSelBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
const hint = document.createElement('div');
hint.textContent = '快捷键:Alt+B 添加选中文本';
hint.style.cssText = 'flex:2; color:#aaa; align-self:center;';
quickRow.appendChild(addSelBtn);
quickRow.appendChild(hint);
const actions = document.createElement('div');
actions.style.cssText = 'margin-top:8px; display:flex; gap:6px;';
const clearBtn = document.createElement('button');
clearBtn.textContent = '清空';
clearBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
const closeBtn = document.createElement('button');
closeBtn.textContent = '关闭';
closeBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
actions.appendChild(clearBtn);
actions.appendChild(closeBtn);
panel.appendChild(header);
panel.appendChild(inputRow);
panel.appendChild(quickRow);
panel.appendChild(list);
panel.appendChild(actions);
const wlHeader = document.createElement('div');
wlHeader.textContent = '用户名白名单';
wlHeader.style.cssText = 'font-weight:600; margin:12px 0 6px;';
const wlInputRow = document.createElement('div');
wlInputRow.style.cssText = 'display:flex; gap:6px; margin-bottom:8px;';
const wlInput = document.createElement('input');
wlInput.placeholder = '添加白名单用户名/关键词';
wlInput.style.cssText = 'flex:1; padding:4px 6px; border:1px solid #333; border-radius:6px; background:#0b0b0b; color:#fff;';
const wlAddBtn = document.createElement('button');
wlAddBtn.textContent = '添加';
wlAddBtn.style.cssText = 'padding:4px 8px; cursor:pointer;';
wlInputRow.appendChild(wlInput);
wlInputRow.appendChild(wlAddBtn);
const wlHint = document.createElement('div');
wlHint.textContent = '快捷键:Alt+W 添加选中到白名单';
wlHint.style.cssText = 'color:#aaa; margin:0 0 8px;';
const wlList = document.createElement('div');
const wlActions = document.createElement('div');
wlActions.style.cssText = 'margin-top:8px; display:flex; gap:6px;';
const wlClearBtn = document.createElement('button');
wlClearBtn.textContent = '清空白名单';
wlClearBtn.style.cssText = 'flex:1; padding:4px 6px; cursor:pointer;';
wlActions.appendChild(wlClearBtn);
panel.appendChild(wlHeader);
panel.appendChild(wlInputRow);
panel.appendChild(wlHint);
panel.appendChild(wlList);
panel.appendChild(wlActions);
document.documentElement.appendChild(panel);
function renderList(scrollToEnd = false) {
list.innerHTML = '';
if (!userWords.length) {
const empty = document.createElement('div');
empty.textContent = '暂无自定义屏蔽词';
empty.style.cssText = 'color:#888; padding:4px 0;';
list.appendChild(empty);
return;
}
userWords.forEach((w, idx) => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:4px 0; border-bottom:1px dashed #333;';
const text = document.createElement('span');
text.textContent = w;
const del = document.createElement('button');
del.textContent = '删除';
del.style.cssText = 'margin-left:8px; cursor:pointer;';
del.addEventListener('click', () => {
userWords.splice(idx, 1);
saveUserWords();
renderList();
scheduleScan();
});
row.appendChild(text);
row.appendChild(del);
list.appendChild(row);
});
if (scrollToEnd) {
list.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
}
function renderWhitelist(scrollToEnd = false) {
wlList.innerHTML = '';
if (!whitelistNames.length) {
const empty = document.createElement('div');
empty.textContent = '暂无白名单';
empty.style.cssText = 'color:#888; padding:4px 0;';
wlList.appendChild(empty);
return;
}
whitelistNames.forEach((w, idx) => {
const row = document.createElement('div');
row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; padding:4px 0; border-bottom:1px dashed #333;';
const text = document.createElement('span');
text.textContent = w;
const del = document.createElement('button');
del.textContent = '删除';
del.style.cssText = 'margin-left:8px; cursor:pointer;';
del.addEventListener('click', () => {
whitelistNames.splice(idx, 1);
saveWhitelist();
renderWhitelist();
scheduleScan();
});
row.appendChild(text);
row.appendChild(del);
wlList.appendChild(row);
});
if (scrollToEnd) {
wlList.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
}
addBtn.addEventListener('click', () => {
addUserWord(input.value);
input.value = '';
renderList(true);
});
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
addUserWord(input.value);
input.value = '';
renderList(true);
}
});
addSelBtn.addEventListener('click', () => {
const sel = getSelectionText();
if (sel) addUserWord(sel);
renderList(true);
});
wlAddBtn.addEventListener('click', () => {
addWhitelistName(wlInput.value);
wlInput.value = '';
renderWhitelist(true);
});
wlInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
addWhitelistName(wlInput.value);
wlInput.value = '';
renderWhitelist(true);
}
});
clearBtn.addEventListener('click', () => {
userWords = [];
saveUserWords();
renderList();
scheduleScan();
});
closeBtn.addEventListener('click', () => {
panel.style.display = 'none';
});
wlClearBtn.addEventListener('click', () => {
whitelistNames = [];
saveWhitelist();
renderWhitelist();
scheduleScan();
});
function togglePanel() {
renderList();
renderWhitelist();
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
return { panel, togglePanel, renderWhitelist };
}
function toast(message) {
const node = document.createElement('div');
node.textContent = message;
node.style.cssText = [
'position:fixed',
'right:16px',
'bottom:16px',
'background:#222',
'color:#fff',
'border:1px solid #444',
'padding:8px 10px',
'border-radius:8px',
'font-size:12px',
'z-index:2147483647'
].join(';');
document.documentElement.appendChild(node);
setTimeout(() => node.remove(), 1600);
}
function init() {
scanAll();
const observer = new MutationObserver(handleMutations);
observer.observe(document.documentElement, { childList: true, subtree: true });
window.addEventListener('load', scheduleScan);
window.addEventListener('popstate', scheduleScan);
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function () {
const result = originalPushState.apply(this, arguments);
scheduleScan();
return result;
};
history.replaceState = function () {
const result = originalReplaceState.apply(this, arguments);
scheduleScan();
return result;
};
window.addEventListener('scroll', () => { if (Math.random() < 0.08) scheduleScan(); }, { passive: true });
setInterval(scheduleScan, 1200);
const manage = createManagePanel();
document.addEventListener('keydown', (event) => {
if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'm') {
manage.togglePanel();
}
}, { capture: true });
document.addEventListener('keydown', (event) => {
if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'b') {
const sel = getSelectionText();
if (sel) addUserWord(sel);
}
}, { capture: true });
document.addEventListener('keydown', (event) => {
if (event.altKey && !event.ctrlKey && !event.metaKey && event.key.toLowerCase() === 'w') {
const sel = getSelectionText();
if (sel) {
addWhitelistName(sel);
manage.renderWhitelist?.(true);
}
}
}, { capture: true });
}
if (document.documentElement) {
init();
} else {
document.addEventListener('readystatechange', () => {
if (document.documentElement) init();
}, { once: true });
}
})();