X Copy Tweet Link Helper with Toggle Settings

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

目前为 2025-04-07 提交的版本。查看 最新版本

// ==UserScript==
// @name         X Copy Tweet Link Helper with Toggle Settings
// @name:zh-TW   X 複製推文連結助手,附功能開關與語言切換
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Copy tweet link via right-click, like, or button, with optional fixvx and language support. Includes toggle settings in Tampermonkey UI.
// @description:zh-TW 透過右鍵、喜歡或按鈕複製推文鏈接,並可選擇 fixvx 和語言支援。包括 Tampermonkey UI 中的切換設定。
// @author       ChatGPT
// @match        https://x.com/*
// @match        https://twitter.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // === 使用者可切換的功能開關 ===
    const defaultSettings = {
        rightClickCopy: true,     // 右鍵複製
        likeCopy: true,           // 喜歡時自動褯複製
        showCopyButton: true,     // 顯示複製按鈕
        useFixvx: false,          // fixvx 前綴
        language: 'EN'            // ZH 或 EN
    };

    const settings = {
        get(key) {
            return GM_getValue(key, defaultSettings[key]);
        },
        set(key, value) {
            GM_setValue(key, value);
        }
    };

    // === 多語系字典 ===
    const lang = {
        EN: {
            copySuccess: "Link copied!",
            copyButton: "🔗",
            rightClickCopy: 'Right-click Copy',
            likeCopy: 'Like Copy',
            showCopyButton: 'Show Copy Button',
            useFixvx: 'Use Fixvx',
            language: 'Language'
        },
        ZH: {
            copySuccess: "已複製鏈結!",
            copyButton: "🔗",
            rightClickCopy: '右鍵複製',
            likeCopy: '喜歡時複製',
            showCopyButton: '顯示複製按鈕',
            useFixvx: '使用 Fixvx',
            language: '語言'
        }
    };

    const getText = (key) => lang[settings.get('language')][key];

    // === 複製推文鏈結 ===
    function copyTweetLink(tweet) {
        const anchor = tweet.querySelector('a[href*="/status/"]');
        if (!anchor) return;
        let url = new URL(anchor.href);
        url.search = ''; // 移除 ?s=20 等參數
        if (settings.get('useFixvx')) {
            url.hostname = 'fixvx.com';
        }
        navigator.clipboard.writeText(url.toString()).then(() => {
            showToast(getText('copySuccess'));
        });
    }

    // === 顯示提示訊息 ===
    function showToast(msg) {
        const toast = document.createElement('div');
        toast.innerText = msg;
        Object.assign(toast.style, {
            position: 'fixed',
            bottom: '20px',
            left: '50%',
            transform: 'translateX(-50%)',
            background: '#1da1f2',
            color: '#fff',
            padding: '8px 16px',
            borderRadius: '20px',
            zIndex: 9999,
            fontSize: '14px'
        });
        document.body.appendChild(toast);
        setTimeout(() => toast.remove(), 1500);
    }

    // === 插入複製按鈕 ===
    function insertCopyButton(tweet) {
        if (tweet.querySelector('.my-copy-btn')) return;
        const actionBar = tweet.querySelector('[role="group"]');
        if (!actionBar) return;

        const btn = document.createElement('div');
        btn.className = 'my-copy-btn';
        btn.innerText = getText('copyButton');
        Object.assign(btn.style, {
            fontSize: '18px',
            cursor: 'pointer',
            marginLeft: '12px',
            userSelect: 'none'
        });

        btn.onclick = (e) => {
            e.stopPropagation();
            copyTweetLink(tweet);
        };

        actionBar.appendChild(btn);
    }

    // === 監聽推文動態載入 ===
    const tweetObserver = new MutationObserver(() => {
        document.querySelectorAll('article').forEach(tweet => {
            if (settings.get('showCopyButton')) insertCopyButton(tweet);

            if (settings.get('rightClickCopy') && !tweet.hasAttribute('data-rightclick')) {
                tweet.setAttribute('data-rightclick', 'true');
                tweet.addEventListener('contextmenu', (e) => {
                    if (tweet.querySelector('img, video')) {
                        copyTweetLink(tweet);
                    }
                });
            }

            if (settings.get('likeCopy') && !tweet.hasAttribute('data-likecopy')) {
                tweet.setAttribute('data-likecopy', 'true');
                const likeBtn = tweet.querySelector('[data-testid="like"]');
                if (likeBtn) {
                    likeBtn.addEventListener('click', () => {
                        copyTweetLink(tweet);
                    });
                }
            }
        });
    });

    tweetObserver.observe(document.body, { childList: true, subtree: true });

    // === 在油猴菜單中創建設定選項 ===
    function updateMenuCommands() {
        GM_unregisterMenuCommand();
        GM_registerMenuCommand(`${getText('rightClickCopy')} ( ${settings.get('rightClickCopy') ? '✅' : '❌'} )`, toggleRightClickCopy);
        GM_registerMenuCommand(`${getText('likeCopy')} ( ${settings.get('likeCopy') ? '✅' : '❌'} )`, toggleLikeCopy);
        GM_registerMenuCommand(`${getText('showCopyButton')} ( ${settings.get('showCopyButton') ? '✅' : '❌'} )`, toggleShowCopyButton);
        GM_registerMenuCommand(`${getText('useFixvx')} ( ${settings.get('useFixvx') ? '✅' : '❌'} )`, toggleUseFixvx);
        GM_registerMenuCommand(`${getText('language')} ( ${settings.get('language') === 'EN' ? 'EN' : 'ZH'} )`, toggleLanguage);
    }

    updateMenuCommands();

    function toggleRightClickCopy() {
        const currentValue = settings.get('rightClickCopy');
        settings.set('rightClickCopy', !currentValue);
        reloadPage();
    }

    function toggleLikeCopy() {
        const currentValue = settings.get('likeCopy');
        settings.set('likeCopy', !currentValue);
        reloadPage();
    }

    function toggleShowCopyButton() {
        const currentValue = settings.get('showCopyButton');
        settings.set('showCopyButton', !currentValue);
        reloadPage();
    }

    function toggleUseFixvx() {
        const currentValue = settings.get('useFixvx');
        settings.set('useFixvx', !currentValue);
        reloadPage();
    }

    function toggleLanguage() {
        const currentValue = settings.get('language');
        const newLang = currentValue === 'EN' ? 'ZH' : 'EN';
        settings.set('language', newLang);
        reloadPage();
    }

    // 重新整理頁面
    function reloadPage() {
        location.reload();
    }

})();