Simple 1-Click Block for X (Twitter)

Adds a 1-click block button to tweets and user profiles on X (Twitter).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name               Simple 1-Click Block for X (Twitter)
// @name:pt-BR         Bloqueio com 1 Clique para X (Twitter)
// @name:es            Bloqueo con 1 Clic para X (Twitter)
// @name:zh-CN         适用于 X(Twitter)的一键拉黑脚本
// @name:ja            X (Twitter) 1クリックブロック
// @name:id            Blokir 1-Klik Simpel para X (Twitter)
// @name:pl            Proste blokowanie jednym kliknięciem dla X (Twitter)
// @name:hi            X (Twitter) के लिए सरल 1-क्लिक ब्लॉक
// @name:tr            X (Twitter) için Kolay Tek Tıkla Engelleme
// @name:de            Einfache 1-Klick-Sperre für X (Twitter)
// @name:ar            حظر بسيط بنقرة واحدة لـ X (Twitter)
// @name:th            บล็อกเพียงคลิกเดียวสำหรับ X (Twitter)
// @name:fr            Blocage simple en un clic pour X (Twitter)
// @name:ko            X (Twitter) 간편 클릭 차단 스크립트
// @name:zh-HK         適用於 X (Twitter) 的一鍵封鎖指令碼
// @namespace          Violentmonkey Scripts
// @version            1.2
// @description        Adds a 1-click block button to tweets and user profiles on X (Twitter).
// @description:pt-BR  Adiciona um botão de bloqueio com 1 clique aos tweets e perfis de usuários no X (Twitter).
// @description:es     Añade un botón de bloqueo con 1 clic a los tweets y perfiles de usuario en X (Twitter).
// @description:zh-CN  在 X(Twitter)的推文和用户主页中添加一键拉黑按钮。
// @description:ja     X (Twitter) のツイートとユーザープロファイルに1クリックブロックボタンを追加します。
// @description:id     Menambahkan tombol blokir 1-klik ke tweet dan profil pengguna di X (Twitter).
// @description:pl     Dodaje przycisk blokowania jednym kliknięciem do tweetów i profili użytkowników w serwisie X (Twitter).
// @description:hi     X (Twitter) पर ट्वीट्स और उपयोगकर्ता प्रोफाइल में 1-क्लिक ब्लॉक बटन जोड़ता है।
// @description:tr     X (Twitter) üzerindeki tweetlere ve kullanıcı profillerine tek tıkla engelleme butonu ekler.
// @description:de     Fügt Tweets und Benutzerprofilen auf X (Twitter) eine 1-Klick-Sperrschaltfläche hinzu.
// @description:ar     يضيف زر حظر بنقرة واحدة إلى التغريدات وملفات تعريف المستخدمين على X (Twitter).
// @description:th     เพิ่มปุ่มบล็อกเพียงคลิกเดียวในการทวีตและโปรไฟล์ผู้ใช้บน X (Twitter)
// @description:fr     Ajoute un bouton de blocage en un clic aux tweets et aux profils d'utilisateurs sur X (Twitter).
// @description:ko     X (Twitter) 트윗과 사용자 프로필에 간편 클릭 차단 버튼을 추가합니다.
// @description:zh-HK  在 X (Twitter) 的推文和用戶個人檔案中添加一鍵封鎖按鈕。
// @license            MIT
// @author             Big Naturals
// @homepage           https://greasyfork.org/en/users/1568161-big-naturals
// @icon               https://i.postimg.cc/ZqvPgDmS/simple_block.png
// @contributionURL    bitcoin:14DqoZwVtb5X7mG1ddAfHDDQtVz829fiES
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// @compatible edge
// @compatible brave
// @match              *://x.com/*
// @match              *://twitter.com/*
// @grant              none
// @run-at             document-idle
// ==/UserScript==

(function() {
    'use strict';

    const blockMenuPathSnippet = 'M12 3.75c-4.55';

    const blockSvg = `
        <svg viewBox="0 0 24 24" aria-hidden="true" class="quick-block-svg">
            <g><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8 0-1.87.64-3.6 1.72-5.01l11.29 11.29C15.6 19.36 13.87 20 12 20zm6.28-2.99L6.99 5.72C8.4 4.64 10.13 4 12 4c4.41 0 8 3.59 8 8 0 1.87-.64 3.6-1.72 5.01z"></path></g>
        </svg>
    `;

    const btnStyle = `
        background-color: transparent;
        border: none;
        border-radius: 9999px;
        width: 30px;
        height: 30px;
        margin-right: 2px;
        cursor: pointer;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.2s;
        z-index: 10;
    `;

    const styleSheet = document.createElement("style");
    styleSheet.textContent = `
        .quick-block-svg {
            width: 18px;
            height: 18px;
            fill: #71767b;
            transition: fill 0.2s ease-in-out;
        }
        .quick-block-btn-feed:hover .quick-block-svg,
        .quick-block-btn-profile:hover .quick-block-svg {
            fill: rgb(244, 33, 46);
        }
        .quick-block-btn-feed:hover,
        .quick-block-btn-profile:hover {
            background-color: rgba(244, 33, 46, 0.1) !important;
        }
        .quick-block-btn-profile {
            margin-left: 8px;
            margin-right: 8px;
        }
    `;
    document.head.appendChild(styleSheet);

    const randomDelay = (min, max) =>
        new Promise(res => setTimeout(res, Math.floor(Math.random() * (max - min + 1) + min)));

    function getLoggedUsername() {
        const accountBtn = document.querySelector('[data-testid="SideNav_AccountSwitcher_Button"]');
        if (!accountBtn) return null;

        const usernameSpan = accountBtn.querySelector('span');
        const allSpans = accountBtn.querySelectorAll('span');
        
        for (let span of allSpans) {
            const text = span.textContent.trim();
            if (text.startsWith('@')) {
                return text.substring(1);
            }
        }

        const avatarContainer = accountBtn.querySelector('[data-testid^="UserAvatar-Container-"]');
        if (avatarContainer) {
            return avatarContainer.getAttribute('data-testid').replace('UserAvatar-Container-', '');
        }

        return null;
    }

    function getProfileUsernameFromPath() {
        const m = window.location.pathname.match(/^\/([^\/?#]+)/);
        return m ? m[1] : null;
    }

    function isOwnTweet(tweet, loggedUsername) {
        if (!loggedUsername) return false;

        const analyticsLink = tweet.querySelector('a[href$="/analytics"]');
        if (!analyticsLink) return false;

        const href = analyticsLink.getAttribute('href');
        const regex = new RegExp(`^\\/${loggedUsername}\\/status\\/\\d+\\/analytics$`);
        return regex.test(href);
    }

    async function performBlock(triggerElement) {
        if (!triggerElement) return;

        triggerElement.click();
        await randomDelay(50, 100);

        const menuItems = document.querySelectorAll('[role="menuitem"], [role="menuitemradio"]');
        const blockOption = Array.from(menuItems).find(item => {
            const pathEl = item.querySelector('svg path');
            const d = pathEl && pathEl.getAttribute && pathEl.getAttribute('d');
            return d && d.includes(blockMenuPathSnippet);
        });

        if (blockOption) {
            blockOption.click();
            await randomDelay(50, 100);
            const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
            if (confirmBtn) confirmBtn.click();
        } else {
            triggerElement.click();
        }
    }

    function createBlockBtnForCaret(caret) {
        const btn = document.createElement('button');
        btn.innerHTML = blockSvg;
        btn.setAttribute('style', btnStyle);
        btn.className = 'quick-block-btn-feed';
        btn.title = 'Block user';
        btn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            performBlock(caret);
        };
        return btn;
    }

    function createBlockBtnForProfile(moreButton) {
        const btn = moreButton.cloneNode(true);

        btn.removeAttribute('data-testid');
        btn.removeAttribute('aria-expanded');
        btn.removeAttribute('aria-haspopup');

        btn.classList.add('quick-block-btn-profile');

        btn.setAttribute('style', moreButton.getAttribute('style') || '');

        const svgContainer = btn.querySelector('svg');
        if (svgContainer) {
            svgContainer.outerHTML = `
                <svg viewBox="0 0 24 24" aria-hidden="true" class="${svgContainer.className.baseVal}">
                    <g>
                        <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8 0-1.87.64-3.6 1.72-5.01l11.29 11.29C15.6 19.36 13.87 20 12 20zm6.28-2.99L6.99 5.72C8.4 4.64 10.13 4 12 4c4.41 0 8 3.59 8 8 0 1.87-.64 3.6-1.72 5.01z"></path>
                    </g>
                </svg>
            `;
        }

        btn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            performBlock(moreButton);
        };

        return btn;
    }

    function injectFeedButtons(loggedUsername) {
        if (!loggedUsername) return;

        const tweets = document.querySelectorAll('[data-testid="tweet"]');

        tweets.forEach(tweet => {
            if (tweet.querySelector('.quick-block-btn-feed')) return;
            if (isOwnTweet(tweet, loggedUsername)) return;

            const caret = tweet.querySelector('[data-testid="caret"]');
            if (caret && caret.parentNode) {
                const blockBtn = createBlockBtnForCaret(caret);
                caret.parentNode.insertBefore(blockBtn, caret);
            }
        });
    }

    function injectProfileButton(loggedUsername) {
        const profileUsername = getProfileUsernameFromPath();
        if (!profileUsername) return;
        if (!loggedUsername) return;
        if (profileUsername === loggedUsername) return;

        const moreButton = document.querySelector('[data-testid="userActions"]');
        if (!moreButton) return;

        const parent = moreButton.parentNode;
        if (!parent) return;
        if (parent.querySelector('.quick-block-btn-profile')) return;

        const blockBtn = createBlockBtnForProfile(moreButton);
        parent.insertBefore(blockBtn, moreButton);
    }

    function injectAll() {
        const loggedUsername = getLoggedUsername();
        if (!loggedUsername) return;

        injectFeedButtons(loggedUsername);
        injectProfileButton(loggedUsername);
    }

    let timeout = null;
    const observer = new MutationObserver(() => {
        clearTimeout(timeout);
        timeout = setTimeout(injectAll, 150);
    });

    observer.observe(document.body, { childList: true, subtree: true });

    injectAll();
})();