Remove ads and promoted tweets on Twitter

Removes ads and promoted tweets on Twitter, as well as the large bold "Promoted Tweet" headings

  1. // ==UserScript==
  2. // @name Remove ads and promoted tweets on Twitter
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Removes ads and promoted tweets on Twitter, as well as the large bold "Promoted Tweet" headings
  6. // @author https://gf.qytechs.cn/en/users/728793-keyboard-shortcuts
  7. // @match https://twitter.com/*
  8. // @match https://*.twitter.com/*
  9. // @match https://x.com/*
  10. // @match https://*.x.com/*
  11. // @icon https://www.google.com/s2/favicons?sz=128&domain=twitter.com
  12. // @grant none
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. /***************** CONFIGURATION STARTS HERE *****************/
  20. const FIND_AND_REMOVE_FREQUENCY_MS = 500; // how often to search for promoted tweets to remove them, in milliseconds (by default 500, to do it twice a second)
  21. const LOG_REMOVALS = false; // set to true if you want the removals to be reported in the web developer console (not usually visible unless you open it yourself)
  22. /****************** CONFIGURATION ENDS HERE ******************/
  23.  
  24. const PROMOTED_ARTICLE_PATH = '//span[text()="Promoted"]/ancestor::article[@aria-labelledby]'; // xPath query to find promoted tweets, specifically their <article> DOM node
  25. const PROMOTED_TWEET_HEADING_PATH = '//span[text()="Promoted Tweet"]/parent::div/parent::h2/parent::div'; // Finds "<div><h2><div><span>Promoted tweet</span>…", selects the outer <div>.
  26. const PROMOTED_TREND_PATH = '//span[starts-with(text(),"Promoted by")]/ancestor::div[@aria-labelledby]'; // xPath query to find promoted trending topics, specifically their <div> DOM node
  27. const ADS_ARTICLE_PATH = '//span[text()="Ad"]/ancestor::article[@aria-labelledby]'; // xPath query to find advertising tweets, specifically their <article> DOM node
  28.  
  29. /**
  30. * For an <article> element, take its list of IDs in aria-labelledby and return the DOM nodes with these IDs
  31. * e.g. for <article aria-labelledby="foo bar">, return the nodes with id="foo" and id="bar".
  32. */
  33. function getLabelNodes(article) {
  34. const labelledBy = article.getAttribute('aria-labelledby'); // space-separated node IDs
  35. const selector = labelledBy.split(' ').map(id => '#' + id).join(', '); // convert to a selector like '#foo, #bar'
  36. return Array.from(document.querySelectorAll(selector)); // returns the list of nodes found with these IDs
  37. }
  38.  
  39. /** Returns whether one of the "label nodes" this article is labeled by has the text "Promoted" as its contents **/
  40. function isPromotedArticle(article) {
  41. return getLabelNodes(article)
  42. .some(node => ('' + node.textContent).toLowerCase() === 'promoted');
  43. }
  44.  
  45. /** Returns whether the article contains a span with only the text "Ad" **/
  46. function isAdvertisement(article) {
  47. return Array.from(article.querySelectorAll('span'))
  48. .some(node => ('' + node.textContent).toLowerCase() === 'ad');
  49. }
  50.  
  51. /** Runs an XPath query with a snapshot of the results, returns an array of the resulting elements **/
  52. function xPathQuerySnapshot(path) {
  53. const queryResult = document.evaluate(path, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  54. return [...Array(queryResult.snapshotLength).keys()] // [0,1,2… N-1]
  55. .map(i => queryResult.snapshotItem(i));
  56. }
  57.  
  58. /** Remove element and log reason, if needed **/
  59. function removeAndMaybeLog(element, logMessage) {
  60. if (LOG_REMOVALS) {
  61. console.log('Removing ' + logMessage);
  62. }
  63. element.style.display = 'none';
  64. }
  65.  
  66. /** Finds and removes promoted tweets and the headings above these tweets **/
  67. function findAndRemoveUnwantedTweets() {
  68. xPathQuerySnapshot(PROMOTED_ARTICLE_PATH)
  69. .filter(article => isPromotedArticle(article))
  70. .forEach(article => removeAndMaybeLog(article, 'promoted tweet:' + article.textContent));
  71.  
  72. xPathQuerySnapshot(ADS_ARTICLE_PATH)
  73. .filter(article => isAdvertisement(article))
  74. .forEach(article => removeAndMaybeLog(article, 'advertisement:' + article.textContent));
  75.  
  76. xPathQuerySnapshot(PROMOTED_TWEET_HEADING_PATH)
  77. .forEach(div => removeAndMaybeLog(div, 'promoted tweet heading'));
  78.  
  79. xPathQuerySnapshot(PROMOTED_TREND_PATH)
  80. .forEach(div => removeAndMaybeLog(div, 'promoted trending topic:' + div.textContent));
  81. }
  82.  
  83. findAndRemoveUnwantedTweets(); // do it once when the page loads
  84. setInterval(findAndRemoveUnwantedTweets, FIND_AND_REMOVE_FREQUENCY_MS); // and then at regular intervals after that
  85. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址