From 06b64bd65d60183a1953bfac2f179db5e0aae051 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Sun, 22 Mar 2026 17:15:14 +0100 Subject: [PATCH] new script: X autoblock by keywords --- README.md | 4 + scripts/X-autoblock-keywords.js | 139 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 scripts/X-autoblock-keywords.js diff --git a/README.md b/README.md index a768650..95f1781 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,7 @@ Scripts are in the script folder. Please use a userscript manager like [greasemo ## X-adblock Script to automatically block accounts posting ads on x.com. [Ublock Origin](https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/) is a much better option. But this script was written thinking "If I block every single account posting ads would I stop seeing ads on twitter? How much time would it take?", still unanswered for now. + +## X-autoblock-keywords + +Script to automatically block accounts tweeting banned words/phrases or banned patterns. Manually modify the arrays bannedKeywords and bannedRegexes in the userscript to add your own banned words and patterns. diff --git a/scripts/X-autoblock-keywords.js b/scripts/X-autoblock-keywords.js new file mode 100644 index 0000000..7c7b031 --- /dev/null +++ b/scripts/X-autoblock-keywords.js @@ -0,0 +1,139 @@ +// ==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)); + +})();