Copy fxtwitter or fixupx link buttons

Adds buttons at the top of tweets to easily copy Twitter / X proxies

  1. // ==UserScript==
  2. // @name Copy fxtwitter or fixupx link buttons
  3. // @name:es Botones de copiar enlace de fxtwitter o fixupx
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.1.0
  6. // @description Adds buttons at the top of tweets to easily copy Twitter / X proxies
  7. // @description:es Añade botones en la parte superior de tweets para copiar proxies de Twitter / X fácilmente
  8. // @author LuisAlfredo92
  9. // @match *x.com/*
  10. // @match *twitter.com/*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=x.com
  12. // @grant none
  13. // @license GPL3
  14. // @supportURL https://github.com/LuisAlfredo92/copy-fxtwitter-or-fixupx-link-buttons
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19. // Variables
  20. const ProxyType = {
  21. fxtwitter: "fxtwitter",
  22. fixupx: "fixupx",
  23. vxtwitter: "vxtwitter",
  24. girlcockx: "girlcockx",
  25. stupidpenisx: "stupidpenisx"
  26. };
  27. const languages = {
  28. en: "English",
  29. es: "Español"
  30. };
  31. const re = new RegExp(".*(x.com|twitter.com)\/.+\/status\/.+");
  32. let exists = false;
  33.  
  34. // Labels
  35. const englishLabels = {
  36. copyButton: "Copy {0} link",
  37. copied: "Copied!",
  38. language: "Language",
  39. enabledButtons: "Enabled buttons",
  40. save: "Save"
  41. };
  42. const spanishLabels = {
  43. copyButton: "Copiar enlace de {0}",
  44. copied: "¡Copiado!",
  45. language: "Idioma",
  46. enabledButtons: "Botones habilitados",
  47. save: "Guardar"
  48. };
  49.  
  50. // Get data from local storage
  51. let savedData;
  52. let savedDataString = localStorage.getItem("fxtwitterFixupxButtons");
  53. if (!savedDataString) {
  54. savedData = {
  55. fxtwitter: true,
  56. fixupx: true,
  57. vxtwitter: false,
  58. girlcockx: false,
  59. stupidpenisx: false,
  60. language: "en"
  61. };
  62. localStorage.setItem("fxtwitterFixupxButtons", JSON.stringify(savedData));
  63. } else {
  64. savedData = JSON.parse(savedDataString);
  65. }
  66. // There must be a better way to do this
  67. let labels;
  68. switch (savedData.language) {
  69. case "es":
  70. labels = spanishLabels;
  71. break;
  72. default:
  73. labels = englishLabels;
  74. break;
  75. }
  76.  
  77. // Function to change url
  78. const modifyAndSaveUrl = async (type, button) => {
  79. // Get URL
  80. const currentUrl = window.location.href;
  81. const isTwitter = currentUrl.includes("twitter.com");
  82. let modifiedUrl, proxyHost;
  83. switch (type) {
  84. case ProxyType.fxtwitter:
  85. proxyHost = "fxtwitter.com";
  86. break;
  87.  
  88. case ProxyType.girlcockx:
  89. proxyHost = "girlcockx.com";
  90. break;
  91.  
  92. case ProxyType.stupidpenisx:
  93. proxyHost = "stupidpenisx.com";
  94. break;
  95.  
  96. default:
  97. proxyHost = "fixupx.com";
  98. break;
  99. }
  100. modifiedUrl = currentUrl.replace(isTwitter ? "twitter.com" : "x.com", proxyHost);
  101.  
  102. // Copy to url
  103. try {
  104. await navigator.clipboard.writeText(modifiedUrl);
  105. console.log("Modified URL copied to clipboard: ", modifiedUrl);
  106. // Change the button text to "Copied!", then, wait 3 seconds and change it back to the original text
  107. const originalText = button.innerText;
  108. button.innerText = labels.copied;
  109. setTimeout(() => button.innerText = originalText, 3000);
  110. } catch (ex) {
  111. console.error("Failed to copy the modified URL: ", ex);
  112. }
  113. }
  114.  
  115. // Function to create buttons
  116. const createButton = (type) => {
  117. const button = document.createElement("BUTTON");
  118. button.appendChild(document.createTextNode(labels.copyButton.replace("{0}", type)));
  119. button.addEventListener("click", () => modifyAndSaveUrl(type, button));
  120. button.style.width = 80 / totalButtons + "%";
  121. button.style.flexGrow = "1";
  122. button.style.flexShrink = "0";
  123. button.style.textAlign = "center";
  124. button.style.fontFamily = "TwitterChirp";
  125. button.style.border = "none";
  126. button.style.borderRadius = "25px";
  127. button.style.padding = "10px 0px";
  128. button.style.fontWeight = "bold";
  129.  
  130. return button;
  131. }
  132.  
  133. // Function to create settings
  134. const createSettings = () => {
  135. const settings = document.createElement("DIV");
  136. settings.style.width = "250px";
  137. settings.style.fontFamily = "TwitterChirp";
  138. settings.style.display = "none";
  139. settings.style.flexDirection = "column";
  140. settings.style.position = "absolute";
  141. settings.style.top = "10px";
  142. settings.style.right = "15%";
  143. settings.style.backgroundColor = "Black";
  144. settings.style.color = "White";
  145. settings.style.padding = "10px";
  146. settings.style.zIndex = "99";
  147. settings.style.border = "1px solid White";
  148.  
  149. // Create checkboxes
  150. let checkboxesHtml = "";
  151. for (const key in ProxyType) {
  152. checkboxesHtml += `
  153. <div style="display:flex;flex-direction:row;gap:10px;">
  154. <input type="checkbox" id="${key}" name="${key}" ${savedData[key] ? "checked" : ""}>
  155. <label for="${key}">${ProxyType[key]}</label>
  156. </div>
  157. `;
  158. }
  159.  
  160. // Create options for select
  161. let optionsHtml = "";
  162. for (const key in languages) {
  163. optionsHtml += `<option value="${key}" ${key === savedData.language ? "selected=\"selected\"" : ""}>${languages[key]}</option>`;
  164. }
  165.  
  166. settings.innerHTML = `
  167. <div style="display:flex;flex-direction:row;gap:10px;">
  168. <label for="lang">${labels.language}: </label>
  169.  
  170. <select name="lang" id="lang" value="${savedData.language}">
  171. ${optionsHtml}
  172. </select>
  173. <button style="margin-left:auto;">X</button>
  174. </div>
  175. <p>${labels.enabledButtons}:</p>
  176. ${checkboxesHtml}
  177. <button style="margin-top:10px;">${labels.save}</button>
  178. `;
  179.  
  180. // Add event listeners
  181. const buttons = settings.querySelectorAll("button");
  182. const closeButton = buttons[0],
  183. saveButton = buttons[1];
  184. closeButton.addEventListener("click", () => settings.style.display = "none");
  185.  
  186. // Save settings
  187. saveButton.addEventListener("click", () => {
  188. const checkboxes = settings.querySelectorAll("input[type=checkbox]");
  189. checkboxes.forEach(checkbox => {
  190. savedData[checkbox.name] = checkbox.checked;
  191. });
  192. savedData.language = settings.querySelector("#lang").value;
  193. localStorage.setItem("fxtwitterFixupxButtons", JSON.stringify(savedData));
  194. // Update labels
  195. switch (savedData.language) {
  196. case "es":
  197. labels = spanishLabels;
  198. break;
  199. default:
  200. labels = englishLabels;
  201. break;
  202. }
  203. // Update buttons
  204. saveButton.innerText = labels.save;
  205. });
  206.  
  207. return settings;
  208. }
  209. const toggleVisibility = (settings) => {
  210. settings.style.display = settings.style.display == "none" ? "flex" : "none";
  211. }
  212.  
  213. const callback = (mutationList) => {
  214. // Check if the current page is a tweet
  215. const currentUrl = window.location.href;
  216. if (!re.test(currentUrl)) return;
  217.  
  218. for (const mutation of mutationList) {
  219. if (mutation.type !== "attributes") return;
  220.  
  221. // Checks if the buttons doesn't already exist
  222. if (exists) {
  223. const existingContainers = document.querySelectorAll("#fxtwitterFixupxButtons");
  224. if (existingContainers.length != 0)
  225. return;
  226.  
  227. exists = false;
  228. }
  229.  
  230. const containers = document.querySelectorAll(".css-175oi2r.r-1iusvr4.r-16y2uox.r-ttdzmv");
  231. if (containers.length == 0) return;
  232.  
  233. // Create container and changes its style
  234. const container = document.createElement("DIV");
  235. container.id = "fxtwitterFixupxButtons";
  236. container.style.marginBottom = "15px";
  237. container.style.display = "flex";
  238. container.style.flexDirection = "row";
  239. container.style.gap = "10px";
  240.  
  241. // Add buttons
  242. for (const button of buttons)
  243. container.appendChild(button);
  244.  
  245. // Add settings button
  246. const settings = createSettings();
  247. document.body.appendChild(settings);
  248. const settingsButton = document.createElement("BUTTON");
  249. settingsButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 25px;filter: invert(1);isolation: isolate;mix-blend-mode: difference;"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>`;
  250. settingsButton.addEventListener("click", () => toggleVisibility(settings));
  251. settingsButton.style.width = "10%";
  252. settingsButton.style.flexGrow = "1";
  253. settingsButton.style.flexShrink = "0";
  254. settingsButton.style.textAlign = "center";
  255. settingsButton.style.fontFamily = "TwitterChirp";
  256. settingsButton.style.border = "none";
  257. settingsButton.style.borderRadius = "25px";
  258. settingsButton.style.fontWeight = "bold";
  259. container.appendChild(settingsButton);
  260.  
  261. // Insert container
  262. containers[0].prepend(container);
  263. exists = true;
  264. }
  265. };
  266.  
  267. // Create buttons
  268. const buttons = [];
  269. const totalButtons = Object.keys(ProxyType).map(key => {
  270. return savedData[key] ? 1 : 0;
  271. }).reduce((a, b) => a + b, 0);
  272. Object.keys(ProxyType).forEach(key => {
  273. if (savedData[key])
  274. buttons.push(createButton(ProxyType[key]));
  275. });
  276.  
  277. // Displays the buttons when the page is correctly loaded
  278. // Options for the observer (which mutations to observe)
  279. const config = { attributes: true, subtree: true };
  280. // Create an observer instance linked to the callback function
  281. const observer = new MutationObserver(callback);
  282.  
  283. // Start observing the target node for configured mutations
  284. observer.observe(document, config);
  285. })();