Greasy Fork

Greasy Fork is available in English.

Twitter/X Bot and Flag Post Hider

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.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
})();