X.com Quick Block Button

Adds a 'Quick Block' button to tweets on x.com for one-click blocking. Robust version.

  1. // ==UserScript==
  2. // @name X.com Quick Block Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Adds a 'Quick Block' button to tweets on x.com for one-click blocking. Robust version.
  6. // @author Your Name Here
  7. // @match https://x.com/*
  8. // @match https://twitter.com/*
  9. // @grant none
  10. // @run-at document-idle
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // --- Configuration ---
  17. const CHECK_INTERVAL = 1000;
  18. const CLICK_DELAY = 300;
  19. const BUTTON_TEXT = 'Block';
  20. const MAX_RETRIES = 3;
  21.  
  22. // --- Styling for the Button ---
  23. const buttonStyle = `
  24. color: lightgray;
  25. background-color: transparent;
  26. border: none;
  27. margin-left: 10px;
  28. font-family: monospace;
  29. font-weight: bold;
  30. cursor: pointer;
  31. overflow: hidden; /* Clip the extra part of the shadow when scaled */
  32. position: relative; /* Needed for the pseudo-element */
  33. `;
  34.  
  35. // --- Helper Function: Wait for an element to appear (RETRYING) ---
  36. async function waitForElement(selector, timeout = 2000, interval = 200, maxRetries = MAX_RETRIES) {
  37. return new Promise((resolve, reject) => {
  38. let elapsedTime = 0;
  39. let retries = 0;
  40. const timer = setInterval(() => {
  41. const element = document.querySelector(selector);
  42. if (element) {
  43. clearInterval(timer);
  44. resolve(element);
  45. } else {
  46. elapsedTime += interval;
  47. if (elapsedTime >= timeout) {
  48. if (retries < maxRetries) {
  49. retries++;
  50. elapsedTime = 0;
  51. console.warn(`Quick Block: Element not found, retrying (${retries}/${maxRetries}): ${selector}`);
  52. } else {
  53. clearInterval(timer);
  54. reject(new Error(`Element not found after ${timeout}ms and ${maxRetries} retries: ${selector}`));
  55. }
  56. }
  57. }
  58. }, interval);
  59. });
  60. }
  61.  
  62. // --- Helper Function: Simulate a click with delay ---
  63. async function clickElement(element) {
  64. if (element && typeof element.click === 'function') {
  65. try {
  66. element.click();
  67. await new Promise(resolve => setTimeout(resolve, CLICK_DELAY));
  68. return true;
  69. } catch (e) {
  70. console.error("Quick Block: Error clicking element:", e, element);
  71. return false;
  72. }
  73. } else {
  74. console.error("Quick Block: Invalid element or no click method:", element);
  75. return false;
  76. }
  77. }
  78.  
  79. // --- Helper Function: Find Parent Tweet Article (Robust) ---
  80. function findParentTweetArticle(node) {
  81. let current = node;
  82. while (current && current !== document.body) {
  83. if (current.tagName === 'ARTICLE' && current.getAttribute('data-testid') === 'tweet') {
  84. return current;
  85. }
  86. current = current.parentNode;
  87. }
  88. return null;
  89. }
  90.  
  91. // --- Helper Function: Find Action Bar (More Robust) ---
  92. function findActionBar(tweetArticle) {
  93. const selectors = [
  94. 'div[role="group"]',
  95. 'div:has(> button[data-testid="reply"])',
  96. 'div:has(> button[data-testid="like"])',
  97. 'div:has(> button[data-testid="retweet"])'
  98. ];
  99.  
  100. for (const selector of selectors) {
  101. const found = tweetArticle.querySelector(selector);
  102. if (found) {
  103. return found;
  104. }
  105. }
  106. return null;
  107. }
  108.  
  109. // --- Main Blocking Logic (Robust) ---
  110. async function quickBlockUser(event) {
  111. const button = event.target;
  112. button.disabled = true;
  113. button.textContent = 'Blocking...';
  114. button.style.backgroundImage = 'linear-gradient(to bottom right, #ff9800, #f57c00)'; // Orange gradient
  115. button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)';
  116.  
  117. const tweetArticle = findParentTweetArticle(button);
  118. if (!tweetArticle) {
  119. console.error('Quick Block: Could not find parent tweet article.', button, event.currentTarget);
  120. button.textContent = 'Error';
  121. button.style.backgroundColor = 'orange';
  122. return;
  123. }
  124.  
  125. try {
  126. let moreButton = null;
  127. for (let i = 0; i < MAX_RETRIES; i++) {
  128. moreButton = tweetArticle.querySelector('button[data-testid="caret"]');
  129. if (moreButton) break;
  130. await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2));
  131. }
  132. if (!moreButton) {
  133. throw new Error('Could not find More button (caret) after retries.');
  134. }
  135.  
  136. if (!await clickElement(moreButton)) {
  137. throw new Error('Failed to click More button.');
  138. }
  139.  
  140. let blockMenuItem = null;
  141. for (let i = 0; i < MAX_RETRIES; i++) {
  142. try{
  143. blockMenuItem = await waitForElement('div[data-testid="block"]', 2000, 200, 1);
  144. if(blockMenuItem){
  145. break;
  146. }
  147. }catch(e){
  148. await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2));
  149. }
  150. }
  151. if (!blockMenuItem) {
  152. throw new Error('Could not find Block menu item after retries.');
  153. }
  154. if (!await clickElement(blockMenuItem)) {
  155. throw new Error('Failed to click Block menu item.');
  156. }
  157.  
  158. let confirmButton = null;
  159. for (let i = 0; i < MAX_RETRIES; i++) {
  160. try{
  161. confirmButton = await waitForElement('button[data-testid="confirmationSheetConfirm"]', 2000, 200, 1);
  162. if(confirmButton){
  163. break;
  164. }
  165. }catch(e){
  166. await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2));
  167. }
  168. }
  169. if (!confirmButton) {
  170. throw new Error('Could not find confirmation Block button after retries.');
  171. }
  172. if (!await clickElement(confirmButton)) {
  173. throw new Error('Failed to click confirmation Block button.');
  174. }
  175.  
  176. console.log('Quick Block: User blocked successfully!');
  177. button.textContent = 'Blocked!';
  178. button.style.backgroundImage = 'linear-gradient(to bottom right, #4CAF50, #388E3C)'; // Green gradient
  179. button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)';
  180.  
  181. } catch (error) {
  182. console.error('Quick Block Error:', error.message, tweetArticle);
  183. button.textContent = 'Error';
  184. button.style.backgroundColor = 'orange';
  185. button.style.backgroundImage = 'none';
  186. setTimeout(() => {
  187. button.disabled = false;
  188. button.textContent = BUTTON_TEXT;
  189. button.style.backgroundImage = 'linear-gradient(to bottom right, #f44336, #d32f2f)';
  190. button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)';
  191. }, 2000);
  192. }
  193. }
  194.  
  195. // --- Function to Add Buttons to Tweets (Robust) ---
  196. function addBlockButtons() {
  197. const tweets = document.querySelectorAll('article[data-testid="tweet"]:not([data-quickblock-added])');
  198.  
  199. tweets.forEach(tweet => {
  200. tweet.setAttribute('data-quickblock-added', 'true');
  201.  
  202. const actionBar = findActionBar(tweet);
  203. if (actionBar) {
  204. const blockButton = document.createElement('button');
  205. blockButton.textContent = BUTTON_TEXT;
  206. blockButton.style.cssText = buttonStyle;
  207. blockButton.title = "Quickly block the user who posted this tweet";
  208.  
  209. blockButton.addEventListener('mouseover', () => {
  210. if (!blockButton.disabled) {
  211. blockButton.style.cssText = buttonHoverStyle;
  212. }
  213. });
  214. blockButton.addEventListener('mouseout', () => {
  215. if (!blockButton.disabled && blockButton.textContent === BUTTON_TEXT) {
  216. blockButton.style.cssText = buttonStyle;
  217. }
  218. });
  219. blockButton.addEventListener('mousedown', () => {
  220. if (!blockButton.disabled) {
  221. blockButton.style.cssText = buttonActiveStyle;
  222. }
  223. });
  224. blockButton.addEventListener('mouseup', () => {
  225. if (!blockButton.disabled) {
  226. blockButton.style.cssText = buttonStyle;
  227. }
  228. });
  229.  
  230. blockButton.addEventListener('click', quickBlockUser);
  231.  
  232. if (!actionBar.querySelector('.quick-block-button')) {
  233. blockButton.classList.add('quick-block-button');
  234. actionBar.appendChild(blockButton);
  235. }
  236.  
  237. } else {
  238. const tweetLinkElement = tweet.querySelector('a[href*="/status/"]');
  239. let tweetIdentifier = 'Tweet content unavailable or structure changed';
  240. if (tweetLinkElement && tweetLinkElement.href) {
  241. tweetIdentifier = tweetLinkElement.href;
  242. } else {
  243. const userHandleElement = tweet.querySelector('a[href^="/"][role="link"] > div[dir="ltr"] > span');
  244. if (userHandleElement && userHandleElement.textContent.startsWith('@')) {
  245. tweetIdentifier = `Tweet by ${userHandleElement.textContent}`;
  246. }
  247. }
  248. console.warn(`Quick Block: Could not find action bar for tweet: ${tweetIdentifier}`, tweet);
  249. }
  250. });
  251. }
  252.  
  253. // --- Run Periodically and Use MutationObserver ---
  254. addBlockButtons();
  255.  
  256. const observer = new MutationObserver((mutationsList) => {
  257. let foundTweets = false;
  258. for (const mutation of mutationsList) {
  259. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  260. mutation.addedNodes.forEach(node => {
  261. if (node.nodeType === Node.ELEMENT_NODE && (node.matches('article[data-testid="tweet"]') || node.querySelector('article[data-testid="tweet"]'))) {
  262. foundTweets = true;
  263. }
  264. });
  265. }
  266. if (foundTweets) break;
  267. }
  268.  
  269. if (foundTweets) {
  270. requestAnimationFrame(addBlockButtons);
  271. }
  272. });
  273.  
  274. const mainContentArea = document.querySelector('main');
  275. if (mainContentArea) {
  276. observer.observe(mainContentArea, { childList: true, subtree: true });
  277. } else {
  278. console.warn("Quick Block: Could not find <main> element, observing document.body. This might be less efficient.");
  279. observer.observe(document.body, { childList: true, subtree: true });
  280. }
  281.  
  282. setInterval(addBlockButtons, CHECK_INTERVAL * 2);
  283.  
  284. console.log('X.com Quick Block script loaded (v1.3 - Stylish).');
  285.  
  286. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址