// ==UserScript== // @name X Ad Auto-Blocker // @version 0.1 // @description Automatically block accounts posting ads on x.com // @match https://x.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; const processed = new WeakSet(); function waitForSelector(selector, timeout = 3000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) return resolve(element); const observer = new MutationObserver((_, obs) => { const el = document.querySelector(selector); if (el) { obs.disconnect(); resolve(el); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); reject(new Error(`Timeout waiting for selector: ${selector}`)); }, timeout); }); } async function processAdSpan(span) { try { processed.add(span); const container = span.parentElement?.parentElement; if (!container) return; 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(); // close "buy X premium" popup const sheetClose = await waitForSelector('button[data-testid="app-bar-close"]'); sheetClose.click(); console.log('Blocked account posting ads'); } catch (err) { console.error('Error processing ad span:', err); } } const observer = new MutationObserver((mutations) => { for (const m of mutations) { m.addedNodes.forEach(node => { if (node.nodeType !== 1) return; // observe these spans to spot ads const spans = node.matches && node.matches('span.css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3') ? [node] : Array.from(node.querySelectorAll('span.css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3')); spans.forEach(span => { if (span.textContent.trim() === 'Ad' && !processed.has(span)) { processAdSpan(span); } }); }); } }); observer.observe(document.body, { childList: true, subtree: true }); // Initial scan document.querySelectorAll('span.css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3').forEach(span => { if (span.textContent.trim() === 'Ad' && !processed.has(span)) { processAdSpan(span); } }); })();