X/Twitter 优化推文按钮

可以自由显示/隐藏,推文上的按钮,包括,回覆、转推、喜欢、观看次数、书签、分享等按钮,并且有中英两种功能语言可以切换

目前为 2025-05-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name X/Twitter Optimized Tweet Buttons
  3. // @name:zh-TW X/Twitter 優化推文按鈕
  4. // @name:zh-CN X/Twitter 优化推文按钮
  5. // @namespace http://tampermonkey.net/
  6. // @version 5.5
  7. // @description You can freely show or hide the buttons on a tweet, including Reply, Retweet, Like, View Count, Bookmark, and Share. The interface supports switching between Chinese and English.
  8. // @description:zh-TW 可以自由顯示/隱藏,推文上的按鈕,包括,回覆、轉推、喜歡、觀看次數、書籤、分享等按鈕,並且有中英兩種功能語言可以切換
  9. // @description:zh-CN 可以自由显示/隐藏,推文上的按钮,包括,回覆、转推、喜欢、观看次数、书签、分享等按钮,并且有中英两种功能语言可以切换
  10. // @author chatgpt
  11. // @match https://twitter.com/*
  12. // @match https://x.com/*
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. // === 性能優化核心 ===
  23. const OPT = {
  24. debounceTime: 500,
  25. observerConfig: {
  26. childList: true,
  27. subtree: true,
  28. attributes: false,
  29. characterData: false
  30. }
  31. };
  32.  
  33. // === 配置系統 ===
  34. const CONFIG_KEY = 'XButtonSettings';
  35. const defaults = {
  36. hideReply: true,
  37. hideRetweet: true,
  38. hideBookmark: true,
  39. hideViews: true,
  40. hideShare: true,
  41. hideLike: false,
  42. language: 'EN'
  43. };
  44.  
  45. const config = {
  46. get() {
  47. return { ...defaults, ...GM_getValue(CONFIG_KEY, {}) };
  48. },
  49. update(key, value) {
  50. const current = this.get();
  51. GM_setValue(CONFIG_KEY, { ...current, [key]: value });
  52. }
  53. };
  54.  
  55. // === 多語言系統 ===
  56. const i18n = {
  57. EN: {
  58. reply: 'Reply',
  59. retweet: 'Retweet',
  60. bookmark: 'Bookmark',
  61. views: 'View count',
  62. share: 'Share',
  63. like: 'Like',
  64. language: 'Language'
  65. },
  66. 'ZH-TW': {
  67. reply: '回覆',
  68. retweet: '轉推',
  69. bookmark: '書籤',
  70. views: '觀看次數',
  71. share: '分享',
  72. like: '喜歡',
  73. language: '語言'
  74. }
  75. };
  76.  
  77. function t() {
  78. const { language } = config.get();
  79. return i18n[language] || i18n.EN;
  80. }
  81.  
  82. // === 樣式管理 ===
  83. const style = {
  84. element: null,
  85. rules: new Map([
  86. ['hideReply', '[data-testid="reply"] { display: none !important; }'],
  87. ['hideRetweet', '[data-testid="retweet"] { display: none !important; }'],
  88. ['hideBookmark', '[data-testid="bookmark"] { display: none !important; }'],
  89. ['hideViews', 'a[href*="/analytics"] { display: none !important; }'],
  90. // 只隱藏原生分享,不隱藏帶有下載圖示的(通常是腳本插入)
  91. ['hideShare', `
  92. button[aria-label="Share Post"]:not(:has(svg g.download)),
  93. button[aria-label="分享貼文"]:not(:has(svg g.download)),
  94. button[aria-label="分享"]:not(:has(svg g.download)),
  95. button[aria-label="Compartir publicación"]:not(:has(svg g.download))
  96. { display: none !important; }
  97. `],
  98. ['hideLike', '[data-testid="like"], [data-testid="unlike"] { display: none !important; }']
  99. ]),
  100. init() {
  101. if (!document.getElementById('x-btn-hider-styles')) {
  102. this.element = document.createElement('style');
  103. this.element.id = 'x-btn-hider-styles';
  104. document.head.appendChild(this.element);
  105. } else {
  106. this.element = document.getElementById('x-btn-hider-styles');
  107. }
  108. this.update();
  109. },
  110. update() {
  111. const currentConfig = config.get();
  112. const activeRules = Array.from(this.rules.entries())
  113. .filter(([key]) => currentConfig[key])
  114. .map(([, rule]) => rule);
  115. this.element.textContent = activeRules.join('\n');
  116. }
  117. };
  118.  
  119. // === 選單系統 ===
  120. const menu = {
  121. cmds: [],
  122. build() {
  123. this.cmds = [];
  124. const currentConfig = config.get();
  125. const items = [
  126. { key: 'hideReply', label: t().reply },
  127. { key: 'hideRetweet', label: t().retweet },
  128. { key: 'hideBookmark', label: t().bookmark },
  129. { key: 'hideViews', label: t().views },
  130. { key: 'hideShare', label: t().share },
  131. { key: 'hideLike', label: t().like }
  132. ];
  133. items.forEach(({ key, label }) => {
  134. const status = currentConfig[key] ? '✅' : '❌';
  135. this.cmds.push(GM_registerMenuCommand(
  136. `${label} ${status}`,
  137. () => {
  138. config.update(key, !config.get()[key]);
  139. location.reload(); // 直接刷新頁面
  140. }
  141. ));
  142. });
  143. // 語言切換
  144. let langStatus = '';
  145. if (currentConfig.language === 'EN') {
  146. langStatus = 'EN';
  147. } else {
  148. langStatus = '中文';
  149. }
  150. this.cmds.push(GM_registerMenuCommand(
  151. `${t().language}: ${langStatus}`,
  152. () => {
  153. config.update('language', currentConfig.language === 'EN' ? 'ZH-TW' : 'EN');
  154. location.reload(); // 直接刷新頁面
  155. }
  156. ));
  157. }
  158. };
  159.  
  160. // === 防抖工具函數 ===
  161. function debounce(func, delay) {
  162. let timer;
  163. return (...args) => {
  164. clearTimeout(timer);
  165. timer = setTimeout(() => func(...args), delay);
  166. };
  167. }
  168.  
  169. const debouncedStyleUpdate = debounce(() => style.update(), OPT.debounceTime);
  170.  
  171. // === 初始化流程 ===
  172. (function init() {
  173. style.init();
  174. menu.build();
  175. const observer = new MutationObserver(mutations => {
  176. if (mutations.some(m => m.addedNodes.length > 0)) {
  177. debouncedStyleUpdate();
  178. }
  179. });
  180. observer.observe(document.body, OPT.observerConfig);
  181. })();
  182. })();