new script: X autoblock by keywords

This commit is contained in:
Sam Hadow
2026-03-22 17:15:14 +01:00
parent e3b65a4d48
commit 06b64bd65d
2 changed files with 143 additions and 0 deletions
+139
View File
@@ -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));
})();