One Click Copy Link Button for Twitter(X)

Add a button to copy the URL of a tweet on Twitter.

目前為 2023-12-17 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name One Click Copy Link Button for Twitter(X)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Add a button to copy the URL of a tweet on Twitter.
  6. // @author Dinomcworld
  7. // @match https://twitter.com/*
  8. // @match https://mobile.twitter.com/*
  9. // @match https://tweetdeck.twitter.com/*
  10. // @match https://x.com/*
  11. // @icon https://www.google.com/s2/favicons?domain=twitter.com
  12. // @grant none
  13. // @license MIT
  14.  
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. const baseUrl = 'https://vxtwitter.com';
  21.  
  22. const defaultSVG = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard" viewBox="0 0 24 24" stroke-width="2" stroke="#71767C" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /></svg>';
  23. const copiedSVG = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard-check" viewBox="0 0 24 24" stroke-width="2" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg>';
  24.  
  25. function addCopyButtonToTweets() {
  26. const tweets = document.querySelectorAll('article[data-testid="tweet"]');
  27.  
  28. tweets.forEach(tweet => {
  29. if (!tweet.querySelector('.custom-copy-icon')) {
  30. const copyIcon = document.createElement('span');
  31. copyIcon.classList.add('custom-copy-icon');
  32. copyIcon.innerHTML = defaultSVG;
  33. adjustIconStyle(tweet, copyIcon);
  34. copyIcon.addEventListener('click', (event) => {
  35. event.stopPropagation();
  36. const tweetUrl = extractTweetUrl(tweet);
  37. if (tweetUrl) {
  38. navigator.clipboard.writeText(tweetUrl)
  39. .then(() => {
  40. console.log('Tweet link copied!');
  41. copyIcon.innerHTML = copiedSVG;
  42. })
  43. .catch(err => console.error('Error copying link: ', err));
  44. }
  45. });
  46.  
  47. tweet.appendChild(copyIcon);
  48. }
  49. });
  50. }
  51.  
  52. function extractTweetUrl(tweetElement) {
  53. const linkElement = tweetElement.querySelector('a[href*="/status/"]');
  54.  
  55. if (!linkElement) {
  56. return;
  57. }
  58.  
  59. let url = linkElement.getAttribute('href').split('?')[0]; // Remove any query parameters
  60.  
  61. if (url.includes('/photo/')) {
  62. url = url.split('/photo/')[0];
  63. }
  64. return `${baseUrl}${url}`;
  65. }
  66.  
  67. function adjustIconStyle(tweet, copyIcon) {
  68. // Find all spans within the tweet
  69. const spans = tweet.querySelectorAll('span');
  70. let isIndividualTweet = false;
  71.  
  72. spans.forEach(span => {
  73. if (span.textContent.includes("Views")) {
  74. isIndividualTweet = true;
  75. }
  76. });
  77.  
  78. if (isIndividualTweet) {
  79. copyIcon.style.cssText = 'width: 22px; height: 22px; cursor: pointer; padding: 2px 5px; margin: 2px; position: absolute; bottom: 9px; right: 64px';
  80. } else {
  81. copyIcon.style.cssText = 'width: 19px; height: 19px; cursor: pointer; padding: 2px 5px; margin: 2px; position: absolute; bottom: 9px; right: 72px';
  82. }
  83. }
  84.  
  85.  
  86. const observer = new MutationObserver(addCopyButtonToTweets);
  87. observer.observe(document.body, { childList: true, subtree: true });
  88.  
  89. addCopyButtonToTweets();
  90. })();