96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
// ==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);
|
|
}
|
|
});
|
|
})();
|