Skip to content

Instantly share code, notes, and snippets.

@bogorad
Last active July 30, 2025 17:01
Show Gist options
  • Select an option

  • Save bogorad/b677bacdb49a9eed4fdb4665dea5caba to your computer and use it in GitHub Desktop.

Select an option

Save bogorad/b677bacdb49a9eed4fdb4665dea5caba to your computer and use it in GitHub Desktop.
block x ads, unhide text - tampermonkey script
// ==UserScript==
// @name Twitter/X Ad Hider & Auto-Expander
// @namespace http://tampermonkey.net/
// @version 1.4
// @description Hides promoted tweets (ads) and automatically clicks "Show more" to expand long tweets in the timeline.
// @author Your Name (or alias)
// @match *://*.twitter.com/*
// @match *://x.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- Selectors ---
const AD_TEXT_SELECTOR = './/span[text()="Ad"]'; // XPath selector for the "Ad" text
const AD_CONTAINER_SELECTOR = 'div[data-testid="cellInnerDiv"]'; // CSS selector for the container to hide
// NEW: Selector for the "Show more" button in tweets.
const SHOW_MORE_BUTTON_SELECTOR = 'button[data-testid="tweet-text-show-more-link"]';
// --- Core Functions ---
// Function to find and hide ads within a given node or the whole document
function hideAds(node = document) {
try {
const xpathResult = document.evaluate(
AD_TEXT_SELECTOR,
node,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE,
null
);
let adSpan;
while ((adSpan = xpathResult.iterateNext())) {
const adContainer = adSpan.closest(AD_CONTAINER_SELECTOR);
if (adContainer && adContainer.style.display !== 'none') {
// console.log('Hiding Ad:', adContainer);
adContainer.style.display = 'none';
}
}
} catch (error) {
console.error('Twitter/X Ad Hider Error during hiding:', error);
}
}
// NEW: Function to find and click "Show more" buttons within a given node.
// This is adapted from the logic you provided.
function autoExpandTweets(node = document) {
try {
// Find all potential "Show more" buttons within the scanned node.
const showMoreButtons = node.querySelectorAll(SHOW_MORE_BUTTON_SELECTOR);
showMoreButtons.forEach(button => {
// The offsetParent check is a reliable way to see if an element is currently visible on the page.
if (button.offsetParent !== null) {
// console.log('Expanding tweet...');
button.click();
}
});
} catch (error) {
console.error('Twitter/X Auto-Expander Error:', error);
}
}
// NEW: A combined function to process new content.
function processNode(node) {
hideAds(node);
autoExpandTweets(node);
}
// --- MutationObserver Setup ---
const targetNode = document.body;
if (!targetNode) {
console.error('Twitter/X Script: Could not find target node to observe.');
return;
}
const config = {
childList: true,
subtree: true
};
// Callback function now calls our combined processor.
const callback = function(mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
// We only need to check element nodes.
if (node.nodeType === Node.ELEMENT_NODE) {
// Process each new node for both ads and "Show more" links.
processNode(node);
}
});
}
}
};
const observer = new MutationObserver(callback);
try {
observer.observe(targetNode, config);
// console.log('Twitter/X Script: Observer started.');
} catch (error) {
console.error('Twitter/X Script Error starting observer:', error);
}
// --- Initial Run ---
// Run once on script load to catch content already present.
setTimeout(() => {
// console.log('Twitter/X Script: Running initial scan...');
// Run the combined processor on the whole document body.
processNode(document.body);
}, 500);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment