Greasy Fork is available in English.
Hides posts from suspected bot accounts, users with specific flags, or users with certain keywords in their name. Hover the counter to see who's hidden.
// ==UserScript==
// @name Twitter/X Bot and Flag Post Hider
// @namespace http://tampermonkey.net/
// @version 1.9
// @description Hides posts from suspected bot accounts, users with specific flags, or users with certain keywords in their name. Hover the counter to see who's hidden.
// @author CL
// @match *://x.com/*
// @match *://twitter.com/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
const BOT_USERNAME_PATTERN = /[a-zA-Z].*\d{2,}$/;
const HIDE_IF_NAME_INCLUDES = ['🇮🇱', '🇺🇦'];
const FORBIDDEN_WORDS = ['democracy', 'ukraine'];
const CHECK_INTERVAL = 1000;
// --- End Configuration ---
// --- Script State ---
let hiddenPostCount = 0;
const processedPosts = new Set();
const hiddenUsernames = new Set();
// --- UI Elements ---
let counterElement;
let tooltipElement;
/**
* Creates UI elements (counter, tooltip) and attaches event listeners.
*/
function setupUI() {
// Create the main counter element
counterElement = document.createElement('div');
counterElement.id = 'bot-hider-counter';
Object.assign(counterElement.style, {
position: 'fixed',
top: '15px',
left: '15px',
backgroundColor: 'rgba(29, 155, 240, 0.9)',
color: 'white',
padding: '5px 12px',
borderRadius: '15px',
zIndex: '10000',
fontSize: '14px',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif',
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
userSelect: 'none',
cursor: 'pointer', // Add a pointer cursor to indicate it's interactive
transition: 'opacity 0.3s ease-in-out'
});
document.body.appendChild(counterElement);
// Create the tooltip element for showing hidden names
tooltipElement = document.createElement('div');
tooltipElement.id = 'bot-hider-tooltip';
Object.assign(tooltipElement.style, {
position: 'fixed',
top: '55px', // Position it just below the counter
left: '15px',
display: 'none', // Initially hidden
backgroundColor: 'rgba(20, 20, 20, 0.95)',
color: 'white',
padding: '10px',
borderRadius: '8px',
zIndex: '9999',
fontSize: '13px',
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
maxHeight: '400px',
overflowY: 'auto',
border: '1px solid #333'
});
document.body.appendChild(tooltipElement);
// Event listener for showing the tooltip on hover
counterElement.addEventListener('mouseenter', () => {
if (hiddenUsernames.size === 0) {
tooltipElement.innerHTML = '<em>No accounts hidden yet.</em>';
} else {
// Format the list of names from the Set
const userList = [...hiddenUsernames].map(name => `<div>${name}</div>`).join('');
tooltipElement.innerHTML = `<strong>Hidden Accounts (${hiddenUsernames.size}):</strong>${userList}`;
}
tooltipElement.style.display = 'block';
});
// Event listener for hiding the tooltip when the mouse leaves
counterElement.addEventListener('mouseleave', () => {
tooltipElement.style.display = 'none';
});
}
/**
* Checks if a user's display name contains any of the strings specified in HIDE_IF_NAME_INCLUDES.
* @param {string} displayName - The user's display name.
* @returns {boolean}
*/
function nameContainsHiddenString(displayName) {
return HIDE_IF_NAME_INCLUDES.some(str => displayName.includes(str));
}
/**
* Checks if a user's display name or username contains any of the exact words specified in FORBIDDEN_WORDS.
* @param {string} displayName - The user's display name.
* @param {string} username - The user's handle.
* @returns {boolean}
*/
function nameContainsForbiddenWord(displayName, username) {
const combinedText = `${displayName} ${username}`;
return FORBIDDEN_WORDS.some(word => {
const regex = new RegExp(`\\b${word}\\b`, 'i');
return regex.test(combinedText);
});
}
/**
* The main function that finds and hides posts based on configured rules.
*/
function hidePosts() {
const articles = document.querySelectorAll('article[data-testid="tweet"]');
articles.forEach(article => {
const articleId = article.getAttribute('aria-labelledby');
if (!articleId || processedPosts.has(articleId)) {
return;
}
processedPosts.add(articleId);
const userLink = article.querySelector('a[href^="/"]:not([href*="/status/"])');
const userDisplayNameElement = article.querySelector('[data-testid="User-Name"]');
if (userLink && userDisplayNameElement) {
const href = userLink.getAttribute('href');
const username = href.substring(1);
const displayName = userDisplayNameElement.textContent || '';
const shouldHide = BOT_USERNAME_PATTERN.test(username) ||
nameContainsHiddenString(displayName) ||
nameContainsForbiddenWord(displayName, username);
if (shouldHide) {
const postContainer = article.closest('[data-testid="cellInnerDiv"]');
if (postContainer) {
postContainer.style.display = 'none';
hiddenPostCount++;
// Add the user's name to our set for the tooltip display
hiddenUsernames.add(`${displayName.trim()} (@${username})`);
}
}
}
});
counterElement.textContent = `Hiding ${hiddenPostCount} posts`;
}
// --- Script Execution ---
console.log('Twitter/X Bot & Flag Hider script is now active.');
setupUI(); // Set up the counter and tooltip elements
setInterval(hidePosts, CHECK_INTERVAL); // Start the main loop
})();