您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a 'Quick Block' button to tweets on x.com for one-click blocking. Robust version.
// ==UserScript== // @name X.com Quick Block Button // @namespace http://tampermonkey.net/ // @version 1.3 // @description Adds a 'Quick Block' button to tweets on x.com for one-click blocking. Robust version. // @author Your Name Here // @match https://x.com/* // @match https://twitter.com/* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const CHECK_INTERVAL = 1000; const CLICK_DELAY = 300; const BUTTON_TEXT = 'Block'; const MAX_RETRIES = 3; // --- Styling for the Button --- const buttonStyle = ` color: lightgray; background-color: transparent; border: none; margin-left: 10px; font-family: monospace; font-weight: bold; cursor: pointer; overflow: hidden; /* Clip the extra part of the shadow when scaled */ position: relative; /* Needed for the pseudo-element */ `; // --- Helper Function: Wait for an element to appear (RETRYING) --- async function waitForElement(selector, timeout = 2000, interval = 200, maxRetries = MAX_RETRIES) { return new Promise((resolve, reject) => { let elapsedTime = 0; let retries = 0; const timer = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(timer); resolve(element); } else { elapsedTime += interval; if (elapsedTime >= timeout) { if (retries < maxRetries) { retries++; elapsedTime = 0; console.warn(`Quick Block: Element not found, retrying (${retries}/${maxRetries}): ${selector}`); } else { clearInterval(timer); reject(new Error(`Element not found after ${timeout}ms and ${maxRetries} retries: ${selector}`)); } } } }, interval); }); } // --- Helper Function: Simulate a click with delay --- async function clickElement(element) { if (element && typeof element.click === 'function') { try { element.click(); await new Promise(resolve => setTimeout(resolve, CLICK_DELAY)); return true; } catch (e) { console.error("Quick Block: Error clicking element:", e, element); return false; } } else { console.error("Quick Block: Invalid element or no click method:", element); return false; } } // --- Helper Function: Find Parent Tweet Article (Robust) --- function findParentTweetArticle(node) { let current = node; while (current && current !== document.body) { if (current.tagName === 'ARTICLE' && current.getAttribute('data-testid') === 'tweet') { return current; } current = current.parentNode; } return null; } // --- Helper Function: Find Action Bar (More Robust) --- function findActionBar(tweetArticle) { const selectors = [ 'div[role="group"]', 'div:has(> button[data-testid="reply"])', 'div:has(> button[data-testid="like"])', 'div:has(> button[data-testid="retweet"])' ]; for (const selector of selectors) { const found = tweetArticle.querySelector(selector); if (found) { return found; } } return null; } // --- Main Blocking Logic (Robust) --- async function quickBlockUser(event) { const button = event.target; button.disabled = true; button.textContent = 'Blocking...'; button.style.backgroundImage = 'linear-gradient(to bottom right, #ff9800, #f57c00)'; // Orange gradient button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)'; const tweetArticle = findParentTweetArticle(button); if (!tweetArticle) { console.error('Quick Block: Could not find parent tweet article.', button, event.currentTarget); button.textContent = 'Error'; button.style.backgroundColor = 'orange'; return; } try { let moreButton = null; for (let i = 0; i < MAX_RETRIES; i++) { moreButton = tweetArticle.querySelector('button[data-testid="caret"]'); if (moreButton) break; await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2)); } if (!moreButton) { throw new Error('Could not find More button (caret) after retries.'); } if (!await clickElement(moreButton)) { throw new Error('Failed to click More button.'); } let blockMenuItem = null; for (let i = 0; i < MAX_RETRIES; i++) { try{ blockMenuItem = await waitForElement('div[data-testid="block"]', 2000, 200, 1); if(blockMenuItem){ break; } }catch(e){ await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2)); } } if (!blockMenuItem) { throw new Error('Could not find Block menu item after retries.'); } if (!await clickElement(blockMenuItem)) { throw new Error('Failed to click Block menu item.'); } let confirmButton = null; for (let i = 0; i < MAX_RETRIES; i++) { try{ confirmButton = await waitForElement('button[data-testid="confirmationSheetConfirm"]', 2000, 200, 1); if(confirmButton){ break; } }catch(e){ await new Promise(resolve => setTimeout(resolve, CLICK_DELAY * 2)); } } if (!confirmButton) { throw new Error('Could not find confirmation Block button after retries.'); } if (!await clickElement(confirmButton)) { throw new Error('Failed to click confirmation Block button.'); } console.log('Quick Block: User blocked successfully!'); button.textContent = 'Blocked!'; button.style.backgroundImage = 'linear-gradient(to bottom right, #4CAF50, #388E3C)'; // Green gradient button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)'; } catch (error) { console.error('Quick Block Error:', error.message, tweetArticle); button.textContent = 'Error'; button.style.backgroundColor = 'orange'; button.style.backgroundImage = 'none'; setTimeout(() => { button.disabled = false; button.textContent = BUTTON_TEXT; button.style.backgroundImage = 'linear-gradient(to bottom right, #f44336, #d32f2f)'; button.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)'; }, 2000); } } // --- Function to Add Buttons to Tweets (Robust) --- function addBlockButtons() { const tweets = document.querySelectorAll('article[data-testid="tweet"]:not([data-quickblock-added])'); tweets.forEach(tweet => { tweet.setAttribute('data-quickblock-added', 'true'); const actionBar = findActionBar(tweet); if (actionBar) { const blockButton = document.createElement('button'); blockButton.textContent = BUTTON_TEXT; blockButton.style.cssText = buttonStyle; blockButton.title = "Quickly block the user who posted this tweet"; blockButton.addEventListener('mouseover', () => { if (!blockButton.disabled) { blockButton.style.cssText = buttonHoverStyle; } }); blockButton.addEventListener('mouseout', () => { if (!blockButton.disabled && blockButton.textContent === BUTTON_TEXT) { blockButton.style.cssText = buttonStyle; } }); blockButton.addEventListener('mousedown', () => { if (!blockButton.disabled) { blockButton.style.cssText = buttonActiveStyle; } }); blockButton.addEventListener('mouseup', () => { if (!blockButton.disabled) { blockButton.style.cssText = buttonStyle; } }); blockButton.addEventListener('click', quickBlockUser); if (!actionBar.querySelector('.quick-block-button')) { blockButton.classList.add('quick-block-button'); actionBar.appendChild(blockButton); } } else { const tweetLinkElement = tweet.querySelector('a[href*="/status/"]'); let tweetIdentifier = 'Tweet content unavailable or structure changed'; if (tweetLinkElement && tweetLinkElement.href) { tweetIdentifier = tweetLinkElement.href; } else { const userHandleElement = tweet.querySelector('a[href^="/"][role="link"] > div[dir="ltr"] > span'); if (userHandleElement && userHandleElement.textContent.startsWith('@')) { tweetIdentifier = `Tweet by ${userHandleElement.textContent}`; } } console.warn(`Quick Block: Could not find action bar for tweet: ${tweetIdentifier}`, tweet); } }); } // --- Run Periodically and Use MutationObserver --- addBlockButtons(); const observer = new MutationObserver((mutationsList) => { let foundTweets = false; for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && (node.matches('article[data-testid="tweet"]') || node.querySelector('article[data-testid="tweet"]'))) { foundTweets = true; } }); } if (foundTweets) break; } if (foundTweets) { requestAnimationFrame(addBlockButtons); } }); const mainContentArea = document.querySelector('main'); if (mainContentArea) { observer.observe(mainContentArea, { childList: true, subtree: true }); } else { console.warn("Quick Block: Could not find <main> element, observing document.body. This might be less efficient."); observer.observe(document.body, { childList: true, subtree: true }); } setInterval(addBlockButtons, CHECK_INTERVAL * 2); console.log('X.com Quick Block script loaded (v1.3 - Stylish).'); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址