// ==UserScript== // @name X Auto-Block by Keyword // @version 0.3 // @description Automatically block accounts whose tweets contain banned words // @match https://x.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 1) Banned words/phrases (case-insensitive) const bannedKeywords = [ 'like to see my', 'wanna see my' ]; // 2) Banned patterns (RegEx) const bannedRegexes = [ /(?:^|\s)https?:\/\/t\.me\/[^\s]+/i, /(?:^|\s)t\.me\/[^\s]+/i ]; function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // Build keyword regex const keywordRegex = new RegExp( `\\b(?:${bannedKeywords.map(escapeRegex).join('|')})\\b`, 'i' ); function containsBannedRegex(text) { return bannedRegexes.some(regex => regex.test(text)); } // Avoid reprocessing the same tweet/container const processed = new WeakSet(); function waitForSelector(selector, timeout = 3000) { return new Promise((resolve, reject) => { const el = document.querySelector(selector); if (el) return resolve(el); const obs = new MutationObserver((_, ob) => { const found = document.querySelector(selector); if (found) { ob.disconnect(); resolve(found); } }); obs.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { obs.disconnect(); reject(new Error(`Timeout waiting for selector: ${selector}`)); }, timeout); }); } async function inspectAndBlock(textEl) { if (processed.has(textEl)) return; processed.add(textEl); const text = textEl.innerText || textEl.textContent; if (!keywordRegex.test(text) && !containsBannedRegex(text)) return; let container = textEl; while (container && container.getAttribute('data-testid') !== 'tweet') { container = container.parentElement; } if (!container) return; try { const moreBtn = container.querySelector('button[data-testid="caret"]'); if (!moreBtn) return; moreBtn.click(); const blockItem = await waitForSelector('div[data-testid="block"]'); if (/^\s*Block\s+@.+/i.test(blockItem.innerText)) { blockItem.click(); } else { const closeBtn = await waitForSelector('button[aria-label="Close"]'); closeBtn.click(); return; } const confirmBtn = await waitForSelector('button[data-testid="confirmationSheetConfirm"]'); confirmBtn.click(); let matchedBanTerm = null; const wordMatch = text.match(keywordRegex); if (wordMatch) { matchedBanTerm = wordMatch[0]; } else { matchedBanTerm = bannedRegexes .map(rx => { const m = text.match(rx); return m ? m[0].trim() : null; }) .find(Boolean); } console.log(`Blocked account for tweet containing banned term: "${matchedBanTerm}"`); } catch (err) { console.error('Error during block flow:', err); } } const observer = new MutationObserver(mutations => { for (const mut of mutations) { mut.addedNodes.forEach(node => { if (node.nodeType !== 1) return; const texts = node.matches && ( node.matches('[data-testid="tweetText"]') || node.matches('[data-testid="card.layoutSmall.detail"]') || node.matches('[data-testid="User-Name"]') ) ? [node] : Array.from(node.querySelectorAll( '[data-testid="tweetText"], [data-testid="card.layoutSmall.detail"], [data-testid="User-Name"]' )); texts.forEach(el => inspectAndBlock(el)); }); } }); observer.observe(document.body, { childList: true, subtree: true }); document.querySelectorAll( '[data-testid="tweetText"], [data-testid="card.layoutSmall.detail"], [data-testid="User-Name"]' ).forEach(el => inspectAndBlock(el)); })();