├── README.md ├── background.js ├── content.js ├── img ├── hide-x-ads-128.png ├── hide-x-ads-16.png └── hide-x-ads-48.png └── manifest.json /README.md: -------------------------------------------------------------------------------- 1 | ## Twitter / X.com Ad Blocker 2 | 3 | Free tool (browser extension) that hides promoted Tweets while you browse Twitter / X.com. 4 | 5 | No signup and it doesn't track anything you do. 6 | 7 | Blocks promoted Tweets, promoted Topics (What's happening), suggested Articles (You might like) and promoted Users (Who to follow) in [all languages](https://github.com/ryanckulp/twitter_ad_blocker/pull/18). 8 | 9 | **Supported Browsers** 10 | 11 | - [Chrome](https://chrome.google.com/webstore/detail/hide-twitter-ads-block-pr/bapmhjebfdbdpjjfafnkfidijkjlkakf?hl=en) 12 | - [Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/hide-twitter-ads-block-/cmnfoolkmkhjkjppnnijolhblhkefmck) 13 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.action.onClicked.addListener(function(tab) { 2 | chrome.tabs.create({"url": "https://github.com/ryanckulp/twitter_ad_blocker"}); 3 | }); 4 | -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | var adsHidden = 0; 2 | var adSelector = "div[data-testid=placementTracking]"; 3 | var trendSelector = "div[data-testid=trend]"; 4 | var userSelector = "div[data-testid=UserCell]"; 5 | var articleSelector = "article[data-testid=tweet]"; 6 | 7 | var sponsoredSvgPath = 'M20.75 2H3.25C2.007 2 1 3.007 1 4.25v15.5C1 20.993 2.007 22 3.25 22h17.5c1.243 0 2.25-1.007 2.25-2.25V4.25C23 3.007 21.993 2 20.75 2zM17.5 13.504c0 .483-.392.875-.875.875s-.875-.393-.875-.876V9.967l-7.547 7.546c-.17.17-.395.256-.62.256s-.447-.086-.618-.257c-.342-.342-.342-.896 0-1.237l7.547-7.547h-3.54c-.482 0-.874-.393-.874-.876s.392-.875.875-.875h5.65c.483 0 .875.39.875.874v5.65z'; 8 | var sponsoredBySvgPath = 'M19.498 3h-15c-1.381 0-2.5 1.12-2.5 2.5v13c0 1.38 1.119 2.5 2.5 2.5h15c1.381 0 2.5-1.12 2.5-2.5v-13c0-1.38-1.119-2.5-2.5-2.5zm-3.502 12h-2v-3.59l-5.293 5.3-1.414-1.42L12.581 10H8.996V8h7v7z'; 9 | var youMightLikeSvgPath = 'M12 1.75c-5.11 0-9.25 4.14-9.25 9.25 0 4.77 3.61 8.7 8.25 9.2v2.96l1.15-.17c1.88-.29 4.11-1.56 5.87-3.5 1.79-1.96 3.17-4.69 3.23-7.97.09-5.54-4.14-9.77-9.25-9.77zM13 14H9v-2h4v2zm2-4H9V8h6v2z'; 10 | var adsSvgPath = 'M19.498 3h-15c-1.381 0-2.5 1.12-2.5 2.5v13c0 1.38 1.119 2.5 2.5 2.5h15c1.381 0 2.5-1.12 2.5-2.5v-13c0-1.38-1.119-2.5-2.5-2.5zm-3.502 12h-2v-3.59l-5.293 5.3-1.414-1.42L12.581 10H8.996V8h7v7z'; 11 | var peopleFollowSvgPath = 'M17.863 13.44c1.477 1.58 2.366 3.8 2.632 6.46l.11 1.1H3.395l.11-1.1c.266-2.66 1.155-4.88 2.632-6.46C7.627 11.85 9.648 11 12 11s4.373.85 5.863 2.44zM12 2C9.791 2 8 3.79 8 6s1.791 4 4 4 4-1.79 4-4-1.791-4-4-4z'; 12 | var xAd = '>Ad<'; // TODO: add more languages; appears to only be used for English accounts as of 2023-08-03 13 | var removePeopleToFollow = false; // set to 'true' if you want these suggestions removed, however note this also deletes some tweet replies 14 | const promotedTweetTextSet = new Set(['Promoted Tweet', 'プロモツイート']); 15 | 16 | function getAds() { 17 | return Array.from(document.querySelectorAll('div')).filter(function(el) { 18 | var filteredAd; 19 | 20 | if (el.innerHTML.includes(sponsoredSvgPath)) { 21 | filteredAd = el; 22 | } else if (el.innerHTML.includes(sponsoredBySvgPath)) { 23 | filteredAd = el; 24 | } else if (el.innerHTML.includes(youMightLikeSvgPath)) { 25 | filteredAd = el; 26 | } else if (el.innerHTML.includes(adsSvgPath)) { 27 | filteredAd = el; 28 | } else if (removePeopleToFollow && el.innerHTML.includes(peopleFollowSvgPath)) { 29 | filteredAd = el; 30 | } else if (el.innerHTML.includes(xAd)) { 31 | filteredAd = el; 32 | } else if (promotedTweetTextSet.has(el.innerText)) { // TODO: bring back multi-lingual support from git history 33 | filteredAd = el; 34 | } 35 | 36 | return filteredAd; 37 | }) 38 | } 39 | 40 | function hideAd(ad) { 41 | if (ad.closest(adSelector) !== null) { // Promoted tweets 42 | ad.closest(adSelector).remove(); 43 | adsHidden += 1; 44 | } else if (ad.closest(trendSelector) !== null) { 45 | ad.closest(trendSelector).remove(); 46 | adsHidden += 1; 47 | } else if (ad.closest(userSelector) !== null) { 48 | ad.closest(userSelector).remove(); 49 | adsHidden += 1; 50 | } else if (ad.closest(articleSelector) !== null) { 51 | ad.closest(articleSelector).remove(); 52 | adsHidden += 1; 53 | } else if (promotedTweetTextSet.has(ad.innerText)) { 54 | ad.remove(); 55 | adsHidden += 1; 56 | } 57 | 58 | console.log('X ads hidden: ', adsHidden.toString()); 59 | } 60 | 61 | function getAndHideAds() { 62 | getAds().forEach(hideAd) 63 | } 64 | 65 | // hide ads on page load 66 | document.addEventListener('load', () => getAndHideAds()); 67 | 68 | // oftentimes, tweets render after onload. LCP should catch them. 69 | new PerformanceObserver((entryList) => { 70 | getAndHideAds(); 71 | }).observe({type: 'largest-contentful-paint', buffered: true}); 72 | 73 | // re-check as user scrolls 74 | document.addEventListener('scroll', () => getAndHideAds()); 75 | 76 | // re-check as user scrolls tweet sidebar (exists when image is opened) 77 | var sidebarExists = setInterval(function() { 78 | let timelines = document.querySelectorAll("[aria-label='Timeline: Conversation']"); 79 | 80 | if (timelines.length == 2) { 81 | let tweetSidebar = document.querySelectorAll("[aria-label='Timeline: Conversation']")[0].parentElement.parentElement; 82 | tweetSidebar.addEventListener('scroll', () => getAndHideAds()); 83 | } 84 | }, 500); 85 | 86 | var subscribeToPremiumExists = setInterval(function() { 87 | let timeline = document.querySelector("aside[aria-label='Subscribe to Premium']"); 88 | if (timeline) { timeline.remove() } 89 | }, 500); 90 | 91 | var upgradeToPremiumPlusExists = setInterval(function() { 92 | let timeline = document.querySelector("aside[aria-label='Upgrade to Premium+']"); 93 | if (timeline) { timeline.remove() } 94 | }, 500); -------------------------------------------------------------------------------- /img/hide-x-ads-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanckulp/twitter_ad_blocker/0b2dda65857d14c01ecc3ca7002e35ce01b8ab82/img/hide-x-ads-128.png -------------------------------------------------------------------------------- /img/hide-x-ads-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanckulp/twitter_ad_blocker/0b2dda65857d14c01ecc3ca7002e35ce01b8ab82/img/hide-x-ads-16.png -------------------------------------------------------------------------------- /img/hide-x-ads-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanckulp/twitter_ad_blocker/0b2dda65857d14c01ecc3ca7002e35ce01b8ab82/img/hide-x-ads-48.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Hide X.com Ads", 4 | "action": {}, 5 | "short_name": "Block Ads on X", 6 | "description": "Free tool that hides Promoted Tweets/Posts while you browse X.com (formerly Twitter).", 7 | "version": "1.2.1", 8 | "icons": { "16": "img/hide-x-ads-16.png", 9 | "48": "img/hide-x-ads-48.png", 10 | "128": "img/hide-x-ads-128.png" }, 11 | "permissions": [ 12 | ], 13 | "background": { 14 | "service_worker": "background.js" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "http://*.twitter.com/*", 20 | "https://*.twitter.com/*", 21 | "http://*.x.com/*", 22 | "https://*.x.com/*" 23 | ], 24 | "js":["content.js"] 25 | } 26 | ] 27 | } 28 | --------------------------------------------------------------------------------