X.com 推文複製鏈結

含右鍵、喜歡、複製按鈕、fixvx Discord 模式與功能開關的推文複製工具。

目前為 2025-04-07 提交的版本,檢視 最新版本

// ==UserScript==
// @name         X.com 推文複製鏈結
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  含右鍵、喜歡、複製按鈕、fixvx Discord 模式與功能開關的推文複製工具。
// @author       CHATGPT
// @match        https://x.com/*
// @icon         https://x.com/favicon.ico
// @grant        GM_setClipboard
// @grant        GM_registerMenuCommand
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const defaultSettings = {
        contextCopy: true,
        likeCopy: true,
        showButton: true,
        useFixvx: false  // 🔧 新增的 Discord fixvx 模式
    };

    let settings = JSON.parse(localStorage.getItem("copyTweetSettings") || JSON.stringify(defaultSettings));

    function toggleSetting(key) {
        settings[key] = !settings[key];
        localStorage.setItem("copyTweetSettings", JSON.stringify(settings));
        location.reload();
    }

    // 功能開關
    GM_registerMenuCommand(`右鍵複製:${settings.contextCopy ? '✅' : '❌'}`, () => toggleSetting("contextCopy"));
    GM_registerMenuCommand(`喜歡時複製:${settings.likeCopy ? '✅' : '❌'}`, () => toggleSetting("likeCopy"));
    GM_registerMenuCommand(`顯示複製按鈕:${settings.showButton ? '✅' : '❌'}`, () => toggleSetting("showButton"));
    GM_registerMenuCommand(`Discord fixvx 模式:${settings.useFixvx ? '✅' : '❌'}`, () => toggleSetting("useFixvx"));

    if (settings.contextCopy) {
        document.addEventListener('contextmenu', function (e) {
            const tweet = e.target.closest('article');
            if (!tweet || !hasMedia(tweet)) return;
            const url = convertTweetUrl(getTweetUrl(tweet));
            if (url) {
                GM_setClipboard(url, "text");
                showNotification("✅ 已複製推文鏈結!");
                console.log("✅ [右鍵] 複製成功:" + url);
            }
        }, true);
    }

    const observer = new MutationObserver(() => {
        document.querySelectorAll('article').forEach(article => {
            if (!hasMedia(article)) return;

            const tweetUrl = convertTweetUrl(getTweetUrl(article));
            if (!tweetUrl) return;

            // 插入複製按鈕
            if (settings.showButton && !article.querySelector('.copy-link-button')) {
                const likeButton = article.querySelector('[data-testid="like"]');
                const viewCountButton = article.querySelector('[data-testid="viewCount"]');
                if (!likeButton || !likeButton.parentElement) return;

                const button = document.createElement('button');
                button.textContent = '🔗';
                button.className = 'copy-link-button';
                button.title = '複製推文鏈結';
                button.style.marginLeft = '6px';
                button.style.background = 'none';
                button.style.border = 'none';
                button.style.color = 'inherit';
                button.style.cursor = 'pointer';
                button.style.fontSize = '18px';
                button.style.lineHeight = '1';
                button.style.padding = '0';
                button.style.display = 'inline-flex';
                button.style.alignItems = 'center';
                button.style.justifyContent = 'center';

                button.addEventListener('click', (e) => {
                    e.stopPropagation();
                    GM_setClipboard(tweetUrl, "text");
                    showNotification("✅ 已複製推文鏈結!");
                    console.log("✅ [按鈕] 複製成功:" + tweetUrl);
                });

                if (viewCountButton && viewCountButton.parentElement) {
                    viewCountButton.parentElement.insertAdjacentElement('beforebegin', button);
                } else {
                    likeButton.parentElement.insertAdjacentElement('afterend', button);
                }
            }

            // 喜歡時自動複製
            if (settings.likeCopy) {
                const likeBtn = article.querySelector('[data-testid="like"]');
                if (likeBtn && !likeBtn.dataset.listenerAdded) {
                    likeBtn.dataset.listenerAdded = 'true';
                    likeBtn.addEventListener('click', () => {
                        const url = convertTweetUrl(getTweetUrl(article));
                        if (url) {
                            GM_setClipboard(url, "text");
                            showNotification("✅ 已複製推文鏈結!");
                            console.log("✅ [喜歡] 自動複製:" + url);
                        }
                    });
                }
            }
        });
    });

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

    function hasMedia(tweet) {
        return (
            tweet.querySelector('img[src*="twimg.com/media"]') ||
            tweet.querySelector('video') ||
            tweet.querySelector('div[data-testid="videoPlayer"]')
        );
    }

    function getTweetUrl(tweet) {
        const anchors = tweet.querySelectorAll('a[href*="/status/"]');
        for (let a of anchors) {
            const href = a.getAttribute('href');
            if (/\/\w+\/status\/\d+/.test(href)) {
                return "https://x.com" + href;
            }
        }
        return null;
    }

    function convertTweetUrl(url) {
        if (!url) return null;
        if (settings.useFixvx) {
            return url.replace("https://x.com", "https://fixvx.com");
        }
        return url;
    }

    function showNotification(message) {
        const box = document.createElement('div');
        box.textContent = message;
        box.style.position = 'fixed';
        box.style.top = '20px';
        box.style.right = '20px';
        box.style.background = '#1da1f2';
        box.style.color = 'white';
        box.style.padding = '10px 15px';
        box.style.borderRadius = '8px';
        box.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
        box.style.zIndex = '9999';
        box.style.fontSize = '14px';
        box.style.transition = 'opacity 0.5s';
        document.body.appendChild(box);
        setTimeout(() => { box.style.opacity = '0'; }, 2000);
        setTimeout(() => { box.remove(); }, 2500);
    }
})();