Make Twitter Great Again

为Twitter增加两个按钮,快速让Twitter算法知道你“不感兴趣的推文“ From Chrome Extension "Make Twitter Great Again" not interesting this post

  1. // ==UserScript==
  2. // @name Make Twitter Great Again
  3. // @namespace https://github.com/androidcn/userscripts/
  4. // @version 2024-12-25
  5. // @description 为Twitter增加两个按钮,快速让Twitter算法知道你“不感兴趣的推文“ From Chrome Extension "Make Twitter Great Again" not interesting this post
  6. // @author theopenprojects.io
  7. // @match https://twitter.com/home
  8. // @match https://x.com/home
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. var hideButton = document.createElement('button');
  16. function createButton() {
  17. // Create a button element
  18. hideButton.textContent = '隐藏';
  19. hideButton.style.position = 'fixed';
  20. hideButton.style.top = '60px';
  21. hideButton.style.right = '20px';
  22. hideButton.style.zIndex = '9999';
  23. // Append the button to the body
  24. document.body.appendChild(hideButton);
  25. // Add click event listener to the button
  26. hideButton.addEventListener('click', performHidedAction);
  27. }
  28. function hideIt(){
  29. document.querySelector("header[role='banner']").style="display:none;";
  30. document.querySelector('div[aria-label="Home timeline"] div:first-child').style="display:none;";
  31. //console.log('已隐藏');
  32. hideButton.textContent = '显示';
  33. //GM_setValue("leftSideBar_hide",true);
  34. }
  35. function showIt(){
  36. document.querySelector("header[role='banner']").style="";
  37. document.querySelector('div[aria-label="Home timeline"] div:first-child').style="";
  38. //console.log('已显示');
  39. hideButton.textContent = '隐藏';
  40. //GM_setValue("leftSideBar_hide",false);
  41. }
  42. function performHidedAction() {
  43. var HideText = hideButton.textContent ;
  44. if (HideText == "隐藏"){
  45. hideIt();
  46. }
  47. else{
  48. showIt();
  49. }
  50. }
  51.  
  52. // Wait for the page to load
  53. window.addEventListener('load', createButton);
  54. //Scroll to hide lefe bar and top bar
  55. //window.addEventListener('scroll',hideIt);
  56.  
  57. const silencePath = 'path[d="M18 6.59V1.2L8.71 7H5.5C4.12 7 3 8.12 3 9.5v5C3 15.88 4.12 17 5.5 17h2.09l-2.3 2.29 1.42 1.42 15.5-15.5-1.42-1.42L18 6.59zm-8 8V8.55l6-3.75v3.79l-6 6zM5 9.5c0-.28.22-.5.5-.5H8v6H5.5c-.28 0-.5-.22-.5-.5v-5zm6.5 9.24l1.45-1.45L16 19.2V14l2 .02v8.78l-6.5-4.06z"]'
  58. const shitPath = 'path[d="M9.5 7c.828 0 1.5 1.119 1.5 2.5S10.328 12 9.5 12 8 10.881 8 9.5 8.672 7 9.5 7zm5 0c.828 0 1.5 1.119 1.5 2.5s-.672 2.5-1.5 2.5S13 10.881 13 9.5 13.672 7 14.5 7zM12 22.25C6.348 22.25 1.75 17.652 1.75 12S6.348 1.75 12 1.75 22.25 6.348 22.25 12 17.652 22.25 12 22.25zm0-18.5c-4.549 0-8.25 3.701-8.25 8.25s3.701 8.25 8.25 8.25 8.25-3.701 8.25-8.25S16.549 3.75 12 3.75zM8.947 17.322l-1.896-.638C7.101 16.534 8.322 13 12 13s4.898 3.533 4.949 3.684l-1.897.633c-.031-.09-.828-2.316-3.051-2.316s-3.021 2.227-3.053 2.322z"]'
  59. const moreProfilePath = 'path[d="M3 12c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm9 2c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm7 0c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"]'
  60.  
  61. const sleep = ms => new Promise(r => setTimeout(r, ms));
  62.  
  63. const waitForElm = (selector) => {
  64. return new Promise((resolve, reject) => {
  65. const observer = new MutationObserver((mutations) => {
  66. mutations.forEach((mutation) => {
  67. const elm = mutation.target.querySelector(selector);
  68. if (elm) {
  69. observer.disconnect();
  70. resolve(elm);
  71. }
  72. });
  73. });
  74. observer.observe(document.body, {
  75. childList: true,
  76. subtree: true,
  77. });
  78. });
  79. };
  80.  
  81. const setTabStatusToBody = () => {
  82. const body = document.querySelector('body')
  83. if (!body || !document.querySelectorAll('[role="tablist"] [role="tab"]')) return
  84. //if (document.URL !== 'https://twitter.com/home') return;
  85.  
  86. if (!Array.from(document.querySelectorAll('[role="tablist"] [role="tab"][data-index]')).length > 0) Array.from(document.querySelectorAll('[role="tablist"] [role="tab"]')).map((el, idx) => el.closest('div').setAttribute('data-index', idx + 1))
  87.  
  88. const selectedIndexElm = document.querySelector('[role="tablist"] [role="tab"][aria-selected="true"]')
  89. if (selectedIndexElm) {
  90. const parent = selectedIndexElm.closest('div')
  91. if (parent) {
  92. body.setAttribute('data-make-twitter-great-again', parent.getAttribute('data-index'))
  93. }
  94. }
  95. }
  96.  
  97. const createShityBtn = (tweet) => {
  98. if (!tweet || tweet.querySelector('.shitBtn')) return;
  99. //if (document.URL !== 'https://twitter.com/home') return;
  100.  
  101. const button = document.createElement('button');
  102. tweet.setAttribute('data-shit', true);
  103. button.classList.add('shitBtn');
  104. button.innerHTML = '没兴趣';
  105.  
  106. const navAction = tweet.querySelector('div[role="group"][id*="id__"]');
  107. if (navAction) navAction.appendChild(button);
  108. }
  109.  
  110. const createSilenceBtn = tweet => {
  111. if (!tweet || tweet.querySelector('.silenceBtn')) return;
  112. // if (document.URL !== 'https://twitter.com/home') return;
  113.  
  114. const button = document.createElement('button');
  115. tweet.setAttribute('data-silence', true);
  116. button.classList.add('silenceBtn');
  117. button.innerHTML = '封禁';
  118.  
  119. const navAction = tweet.querySelector('div[role="group"][id*="id__"]');
  120. if (navAction) navAction.appendChild(button);
  121. }
  122.  
  123. const handleBtnClick = async (e, selector) => {
  124. const tweet = e.target.closest('article');
  125. document.querySelector('body').setAttribute('data-pop-open', true)
  126.  
  127. const btnDropdown = tweet.querySelector('[aria-haspopup="menu"][role="button"][data-testid="caret"]');
  128. if (!btnDropdown) return
  129.  
  130. btnDropdown.click();
  131.  
  132. await sleep(5);
  133. const dropdown = document.querySelector('[data-testid="Dropdown"],[data-testid="sheetDialog"]');
  134. if (!dropdown) return;
  135. const item = dropdown.querySelector(selector)
  136. if (item) item.closest('[role]').click()
  137.  
  138. btnDropdown.click();
  139.  
  140. if (document.querySelector('body').getAttribute('data-make-twitter-great-again') !== "1" || document.querySelector('body').getAttribute('data-make-twitter-great-again') !== "2") tweet.remove()
  141. }
  142.  
  143. const handleProfileBtnClick = async (e, selector) => {
  144. const userActions = document.querySelector('[role="main"] [data-testid="userActions"]')
  145. if (!userActions) return
  146.  
  147. document.querySelector('body').setAttribute('data-pop-open', true)
  148.  
  149. const path = userActions.querySelector(moreProfilePath);
  150. if (!path) return
  151. const btnDropdown = path.closest('div[dir]')
  152. if (!btnDropdown) return
  153.  
  154. btnDropdown.click();
  155.  
  156. await sleep(5);
  157. const dropdown = document.querySelector('[data-testid="Dropdown"]');
  158. if (!dropdown) return;
  159.  
  160. const item = dropdown.querySelector(selector)
  161. if (item) item.closest('[role]').click()
  162.  
  163. dropdown.remove()
  164. }
  165.  
  166. const addBtnToTweets = () => {
  167. const tweets = document.querySelectorAll('[role="region"] article:not([data-shit]):not([data-silence])');
  168. if (tweets && tweets.length > 0) tweets.forEach(tweet => {
  169. createShityBtn(tweet)
  170. createSilenceBtn(tweet)
  171. });
  172. }
  173.  
  174. const addProfileSilenceBtn = () => {
  175. const userActions = document.querySelector('[role="main"] [data-testid="userActions"]')
  176. if (!userActions) return
  177.  
  178. const contentActions = userActions.parentElement
  179. if (!contentActions || contentActions.getAttribute('data-silence') === 'true') return
  180.  
  181. const button = document.createElement('button');
  182. contentActions.setAttribute('data-silence', true);
  183. button.classList.add('profileSilence');
  184. button.innerHTML = '🤫';
  185.  
  186. contentActions.insertBefore(button, contentActions.firstChild);
  187. }
  188.  
  189. const isProfile = () => {
  190. if (document.querySelector('head meta[content*="twitter://user?screen_name="]')) {
  191. document.querySelector('body').setAttribute('data-profile', true)
  192.  
  193. addProfileSilenceBtn()
  194. }
  195. else document.querySelector('body').removeAttribute('data-profile')
  196. }
  197.  
  198. const observeTweets = () => {
  199. const observer = new MutationObserver((mutations) => {
  200. setTabStatusToBody();
  201. isProfile()
  202. if (!document.querySelector('[data-make-twitter-great-again] [role="group"] > div > [role="menu"]')) document.querySelector('body').removeAttribute('data-pop-open')
  203. mutations.forEach(() => addBtnToTweets());
  204. });
  205. observer.observe(document.body, {
  206. childList: true,
  207. subtree: true,
  208. });
  209. return observer;
  210. }
  211.  
  212. (async () => {
  213. await waitForElm('[role="region"] article');
  214.  
  215. setTabStatusToBody();
  216. isProfile();
  217. addBtnToTweets();
  218.  
  219. document.addEventListener('click', (e) => {
  220. if (e.target.classList.contains('shitBtn')) handleBtnClick(e, shitPath);
  221. if (e.target.classList.contains('silenceBtn')) handleBtnClick(e, silencePath);
  222. if (e.target.classList.contains('profileSilence')) handleProfileBtnClick(e, silencePath);
  223. })
  224.  
  225. const tweetsObserver = observeTweets();
  226. window.addEventListener('beforeunload', () => {
  227. tweetsObserver.disconnect();
  228. });
  229. })();