Copy fxtwitter or fixupx link buttons

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Copy fxtwitter or fixupx link buttons
// @name:es      Botones de copiar enlace de fxtwitter o fixupx
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  Adds buttons at the top of tweets to easily copy Twitter / X proxies
// @description:es  Añade botones en la parte superior de tweets para copiar proxies de Twitter / X fácilmente
// @author       LuisAlfredo92
// @match        *x.com/*
// @match        *twitter.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant        none
// @license GPL3
// @supportURL https://github.com/LuisAlfredo92/copy-fxtwitter-or-fixupx-link-buttons
// ==/UserScript==

(function () {
    'use strict';
    // Variables
    const ProxyType = {
        fxtwitter: "fxtwitter",
        fixupx: "fixupx",
        vxtwitter: "vxtwitter",
        girlcockx: "girlcockx",
        stupidpenisx: "stupidpenisx"
    };
    const languages = {
        en: "English",
        es: "Español"
    };
    const re = new RegExp(".*(x.com|twitter.com)\/.+\/status\/.+");
    let exists = false;

    // Labels
    const englishLabels = {
        copyButton: "Copy {0} link",
        copied: "Copied!",
        language: "Language",
        enabledButtons: "Enabled buttons",
        save: "Save"
    };
    const spanishLabels = {
        copyButton: "Copiar enlace de {0}",
        copied: "¡Copiado!",
        language: "Idioma",
        enabledButtons: "Botones habilitados",
        save: "Guardar"
    };

    // Get data from local storage
    let savedData;
    let savedDataString = localStorage.getItem("fxtwitterFixupxButtons");
    if (!savedDataString) {
        savedData = {
            fxtwitter: true,
            fixupx: true,
            vxtwitter: false,
            girlcockx: false,
            stupidpenisx: false,
            language: "en"
        };
        localStorage.setItem("fxtwitterFixupxButtons", JSON.stringify(savedData));
    } else {
        savedData = JSON.parse(savedDataString);
    }
    // There must be a better way to do this
    let labels;
    switch (savedData.language) {
        case "es":
            labels = spanishLabels;
            break;
        default:
            labels = englishLabels;
            break;
    }

    // Function to change url
    const modifyAndSaveUrl = async (type, button) => {
        // Get URL
        const currentUrl = window.location.href;
        const isTwitter = currentUrl.includes("twitter.com");
        let modifiedUrl, proxyHost;
        switch (type) {
            case ProxyType.fxtwitter:
                proxyHost = "fxtwitter.com";
                break;

            case ProxyType.girlcockx:
                proxyHost = "girlcockx.com";
                break;

            case ProxyType.stupidpenisx:
                proxyHost = "stupidpenisx.com";
                break;

            default:
                proxyHost = "fixupx.com";
                break;
        }
        modifiedUrl = currentUrl.replace(isTwitter ? "twitter.com" : "x.com", proxyHost);

        // Copy to url
        try {
            await navigator.clipboard.writeText(modifiedUrl);
            console.log("Modified URL copied to clipboard: ", modifiedUrl);
            // Change the button text to "Copied!", then, wait 3 seconds and change it back to the original text
            const originalText = button.innerText;
            button.innerText = labels.copied;
            setTimeout(() => button.innerText = originalText, 3000);
        } catch (ex) {
            console.error("Failed to copy the modified URL: ", ex);
        }
    }

    // Function to create buttons
    const createButton = (type) => {
        const button = document.createElement("BUTTON");
        button.appendChild(document.createTextNode(labels.copyButton.replace("{0}", type)));
        button.addEventListener("click", () => modifyAndSaveUrl(type, button));
        button.style.width = 80 / totalButtons + "%";
        button.style.flexGrow = "1";
        button.style.flexShrink = "0";
        button.style.textAlign = "center";
        button.style.fontFamily = "TwitterChirp";
        button.style.border = "none";
        button.style.borderRadius = "25px";
        button.style.padding = "10px 0px";
        button.style.fontWeight = "bold";

        return button;
    }

    // Function to create settings
    const createSettings = () => {
        const settings = document.createElement("DIV");
        settings.style.width = "250px";
        settings.style.fontFamily = "TwitterChirp";
        settings.style.display = "none";
        settings.style.flexDirection = "column";
        settings.style.position = "absolute";
        settings.style.top = "10px";
        settings.style.right = "15%";
        settings.style.backgroundColor = "Black";
        settings.style.color = "White";
        settings.style.padding = "10px";
        settings.style.zIndex = "99";
        settings.style.border = "1px solid White";

        // Create checkboxes
        let checkboxesHtml = "";
        for (const key in ProxyType) {
            checkboxesHtml += `
                <div style="display:flex;flex-direction:row;gap:10px;">
                    <input type="checkbox" id="${key}" name="${key}" ${savedData[key] ? "checked" : ""}>
                    <label for="${key}">${ProxyType[key]}</label>
                </div>
            `;
        }

        // Create options for select
        let optionsHtml = "";
        for (const key in languages) {
            optionsHtml += `<option value="${key}" ${key === savedData.language ? "selected=\"selected\"" : ""}>${languages[key]}</option>`;
        }

        settings.innerHTML = `
            <div style="display:flex;flex-direction:row;gap:10px;">
                <label for="lang">${labels.language}: </label>

                <select name="lang" id="lang" value="${savedData.language}">
                    ${optionsHtml}
                </select>
                <button style="margin-left:auto;">X</button>
            </div>
            <p>${labels.enabledButtons}:</p>
            ${checkboxesHtml}
            <button style="margin-top:10px;">${labels.save}</button>
        `;

        // Add event listeners
        const buttons = settings.querySelectorAll("button");
        const closeButton = buttons[0],
            saveButton = buttons[1];
        closeButton.addEventListener("click", () => settings.style.display = "none");

        // Save settings
        saveButton.addEventListener("click", () => {
            const checkboxes = settings.querySelectorAll("input[type=checkbox]");
            checkboxes.forEach(checkbox => {
                savedData[checkbox.name] = checkbox.checked;
            });
            savedData.language = settings.querySelector("#lang").value;
            localStorage.setItem("fxtwitterFixupxButtons", JSON.stringify(savedData));
            // Update labels
            switch (savedData.language) {
                case "es":
                    labels = spanishLabels;
                    break;
                default:
                    labels = englishLabels;
                    break;
            }
            // Update buttons
            saveButton.innerText = labels.save;
        });

        return settings;
    }
    const toggleVisibility = (settings) => {
        settings.style.display = settings.style.display == "none" ? "flex" : "none";
    }

    const callback = (mutationList) => {
        // Check if the current page is a tweet
        const currentUrl = window.location.href;
        if (!re.test(currentUrl)) return;

        for (const mutation of mutationList) {
            if (mutation.type !== "attributes") return;

            // Checks if the buttons doesn't already exist
            if (exists) {
                const existingContainers = document.querySelectorAll("#fxtwitterFixupxButtons");
                if (existingContainers.length != 0)
                    return;

                exists = false;
            }

            const containers = document.querySelectorAll(".css-175oi2r.r-1iusvr4.r-16y2uox.r-ttdzmv");
            if (containers.length == 0) return;

            // Create container and changes its style
            const container = document.createElement("DIV");
            container.id = "fxtwitterFixupxButtons";
            container.style.marginBottom = "15px";
            container.style.display = "flex";
            container.style.flexDirection = "row";
            container.style.gap = "10px";

            // Add buttons
            for (const button of buttons)
                container.appendChild(button);

            // Add settings button
            const settings = createSettings();
            document.body.appendChild(settings);
            const settingsButton = document.createElement("BUTTON");
            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>`;
            settingsButton.addEventListener("click", () => toggleVisibility(settings));
            settingsButton.style.width = "10%";
            settingsButton.style.flexGrow = "1";
            settingsButton.style.flexShrink = "0";
            settingsButton.style.textAlign = "center";
            settingsButton.style.fontFamily = "TwitterChirp";
            settingsButton.style.border = "none";
            settingsButton.style.borderRadius = "25px";
            settingsButton.style.fontWeight = "bold";
            container.appendChild(settingsButton);

            // Insert container
            containers[0].prepend(container);
            exists = true;
        }
    };

    // Create buttons
    const buttons = [];
    const totalButtons = Object.keys(ProxyType).map(key => {
        return savedData[key] ? 1 : 0;
    }).reduce((a, b) => a + b, 0);
    Object.keys(ProxyType).forEach(key => {
        if (savedData[key])
            buttons.push(createButton(ProxyType[key]));
    });

    // Displays the buttons when the page is correctly loaded
    // Options for the observer (which mutations to observe)
    const config = { attributes: true, subtree: true };
    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callback);

    // Start observing the target node for configured mutations
    observer.observe(document, config);
})();