NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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.1
// @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/MKbdzqBy/onelickblock.png
// @contributionURL https://www.blockchain.com/explorer/addresses/btc/16JXciLoAs6R8iQjmpKqWLSNreC1epfz6R
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible safari
// @compatible edge
// @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;
}
/* Ensure profile button aligns visually with action buttons */
.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();
})();