X Copy tweet link assistant, with function switch and language switch

Copy tweet link via right-click, like, or button, with optional fixvx and language support. Includes toggle settings in Tampermonkey UI.

当前为 2025-04-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name X Copy tweet link assistant, with function switch and language switch
  3. // @name:zh-TW X 複製推文連結助手,附功能開關與語言切換
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.1
  6. // @description Copy tweet link via right-click, like, or button, with optional fixvx and language support. Includes toggle settings in Tampermonkey UI.
  7. // @description:zh-TW 透過右鍵、喜歡或按鈕複製推文鏈接,並可選擇 fixvx 和語言支援。包括 Tampermonkey UI 中的切換設定。
  8. // @author ChatGPT
  9. // @match https://x.com/*
  10. // @match https://twitter.com/*
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_unregisterMenuCommand
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. 'use strict';
  20.  
  21. // === 使用者可切換的功能開關 ===
  22. const defaultSettings = {
  23. rightClickCopy: true, // 右鍵複製
  24. likeCopy: true, // 喜歡時自動褯複製
  25. showCopyButton: true, // 顯示複製按鈕
  26. useFixvx: false, // fixvx 前綴
  27. language: 'EN' // ZH 或 EN
  28. };
  29.  
  30. const settings = {
  31. get(key) {
  32. return GM_getValue(key, defaultSettings[key]);
  33. },
  34. set(key, value) {
  35. GM_setValue(key, value);
  36. }
  37. };
  38.  
  39. // === 多語系字典 ===
  40. const lang = {
  41. EN: {
  42. copySuccess: "Link copied!",
  43. copyButton: "🔗",
  44. rightClickCopy: 'Right-click Copy',
  45. likeCopy: 'Like Copy',
  46. showCopyButton: 'Show Copy Button',
  47. useFixvx: 'Use Fixvx',
  48. language: 'Language'
  49. },
  50. ZH: {
  51. copySuccess: "已複製鏈結!",
  52. copyButton: "🔗",
  53. rightClickCopy: '右鍵複製',
  54. likeCopy: '喜歡時複製',
  55. showCopyButton: '顯示複製按鈕',
  56. useFixvx: '使用 Fixvx',
  57. language: '語言'
  58. }
  59. };
  60.  
  61. const getText = (key) => lang[settings.get('language')][key];
  62.  
  63. // === 複製推文鏈結 ===
  64. function copyTweetLink(tweet) {
  65. const anchor = tweet.querySelector('a[href*="/status/"]');
  66. if (!anchor) return;
  67. let url = new URL(anchor.href);
  68. url.search = ''; // 移除 ?s=20 等參數
  69. if (settings.get('useFixvx')) {
  70. url.hostname = 'fixvx.com';
  71. }
  72. navigator.clipboard.writeText(url.toString()).then(() => {
  73. showToast(getText('copySuccess'));
  74. });
  75. }
  76.  
  77. // === 顯示提示訊息 ===
  78. function showToast(msg) {
  79. const toast = document.createElement('div');
  80. toast.innerText = msg;
  81. Object.assign(toast.style, {
  82. position: 'fixed',
  83. bottom: '20px',
  84. left: '50%',
  85. transform: 'translateX(-50%)',
  86. background: '#1da1f2',
  87. color: '#fff',
  88. padding: '8px 16px',
  89. borderRadius: '20px',
  90. zIndex: 9999,
  91. fontSize: '14px'
  92. });
  93. document.body.appendChild(toast);
  94. setTimeout(() => toast.remove(), 1500);
  95. }
  96.  
  97. // === 插入複製按鈕 ===
  98. function insertCopyButton(tweet) {
  99. if (tweet.querySelector('.my-copy-btn')) return;
  100. const actionBar = tweet.querySelector('[role="group"]');
  101. if (!actionBar) return;
  102.  
  103. const btn = document.createElement('div');
  104. btn.className = 'my-copy-btn';
  105. btn.innerText = getText('copyButton');
  106. Object.assign(btn.style, {
  107. fontSize: '18px',
  108. cursor: 'pointer',
  109. marginLeft: '12px',
  110. userSelect: 'none'
  111. });
  112.  
  113. btn.onclick = (e) => {
  114. e.stopPropagation();
  115. copyTweetLink(tweet);
  116. };
  117.  
  118. actionBar.appendChild(btn);
  119. }
  120.  
  121. // === 監聽推文動態載入 ===
  122. const tweetObserver = new MutationObserver(() => {
  123. document.querySelectorAll('article').forEach(tweet => {
  124. if (settings.get('showCopyButton')) insertCopyButton(tweet);
  125.  
  126. if (settings.get('rightClickCopy') && !tweet.hasAttribute('data-rightclick')) {
  127. tweet.setAttribute('data-rightclick', 'true');
  128. tweet.addEventListener('contextmenu', (e) => {
  129. if (tweet.querySelector('img, video')) {
  130. copyTweetLink(tweet);
  131. }
  132. });
  133. }
  134.  
  135. if (settings.get('likeCopy') && !tweet.hasAttribute('data-likecopy')) {
  136. tweet.setAttribute('data-likecopy', 'true');
  137. const likeBtn = tweet.querySelector('[data-testid="like"]');
  138. if (likeBtn) {
  139. likeBtn.addEventListener('click', () => {
  140. copyTweetLink(tweet);
  141. });
  142. }
  143. }
  144. });
  145. });
  146.  
  147. tweetObserver.observe(document.body, { childList: true, subtree: true });
  148.  
  149. // === 在油猴菜單中創建設定選項 ===
  150. function updateMenuCommands() {
  151. GM_unregisterMenuCommand();
  152. GM_registerMenuCommand(`${getText('rightClickCopy')} ( ${settings.get('rightClickCopy') ? '✅' : '❌'} )`, toggleRightClickCopy);
  153. GM_registerMenuCommand(`${getText('likeCopy')} ( ${settings.get('likeCopy') ? '✅' : '❌'} )`, toggleLikeCopy);
  154. GM_registerMenuCommand(`${getText('showCopyButton')} ( ${settings.get('showCopyButton') ? '✅' : '❌'} )`, toggleShowCopyButton);
  155. GM_registerMenuCommand(`${getText('useFixvx')} ( ${settings.get('useFixvx') ? '✅' : '❌'} )`, toggleUseFixvx);
  156. GM_registerMenuCommand(`${getText('language')} ( ${settings.get('language') === 'EN' ? 'EN' : 'ZH'} )`, toggleLanguage);
  157. }
  158.  
  159. updateMenuCommands();
  160.  
  161. function toggleRightClickCopy() {
  162. const currentValue = settings.get('rightClickCopy');
  163. settings.set('rightClickCopy', !currentValue);
  164. reloadPage();
  165. }
  166.  
  167. function toggleLikeCopy() {
  168. const currentValue = settings.get('likeCopy');
  169. settings.set('likeCopy', !currentValue);
  170. reloadPage();
  171. }
  172.  
  173. function toggleShowCopyButton() {
  174. const currentValue = settings.get('showCopyButton');
  175. settings.set('showCopyButton', !currentValue);
  176. reloadPage();
  177. }
  178.  
  179. function toggleUseFixvx() {
  180. const currentValue = settings.get('useFixvx');
  181. settings.set('useFixvx', !currentValue);
  182. reloadPage();
  183. }
  184.  
  185. function toggleLanguage() {
  186. const currentValue = settings.get('language');
  187. const newLang = currentValue === 'EN' ? 'ZH' : 'EN';
  188. settings.set('language', newLang);
  189. reloadPage();
  190. }
  191.  
  192. // 重新整理頁面
  193. function reloadPage() {
  194. location.reload();
  195. }
  196.  
  197. })();