X.com 分享連結轉換 (FX/VX Twitter)

FINAL: 透過模擬原生複製連結功能,修復主頁面 (Home) 獲取連結失敗的問題(需剪貼簿權限)。

目前為 2025-10-01 提交的版本,檢視 最新版本

// ==UserScript==
// @name         X.com 分享連結轉換 (FX/VX Twitter)
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  FINAL: 透過模擬原生複製連結功能,修復主頁面 (Home) 獲取連結失敗的問題(需剪貼簿權限)。
// @author       Customized for User
// @match        https://x.com/*
// @match        https://twitter.com/*
// @icon         https://pbs.twimg.com/media/GGmfzX_bUAAUUFw?format=png
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const POTENTIAL_ITEM_SELECTOR = 'div[role="menuitem"]';
    const MENU_CONTAINER_SELECTOR = 'div[role="menu"]';
    const TARGET_TEXT = '複製連結';
    const PROCESSED_ATTR = 'data-fxvx-processed';

    /**
     * 創建新的選單項 DOM 元素
     */
    function createMenuItem(text, service, originalItem) {
        const newItem = originalItem.cloneNode(true);
        newItem.classList.add('fx-vx-share-item');
        newItem.setAttribute('data-service', service);

        const textSpan = newItem.querySelector('span');
        if (textSpan) {
            textSpan.textContent = text;
        }

        newItem.removeAttribute('tabindex');

        return newItem;
    }

    /**
     * 處理點擊事件:複製轉換後的連結到剪貼簿
     */
    function handleCopy(event) {
        event.stopPropagation();
        event.preventDefault();

        const item = event.currentTarget;
        const service = item.getAttribute('data-service');

        // 1. 取得原生「複製連結」按鈕 (必須是可見的)
        const originalCopyText = document.querySelector(MENU_CONTAINER_SELECTOR + ' span');
        let originalCopyItem = null;

        if (originalCopyText) {
            // 向上搜尋,找到包含 "複製連結" 文字的 menuitem 元素
            const menuItems = originalCopyText.closest(MENU_CONTAINER_SELECTOR).querySelectorAll(POTENTIAL_ITEM_SELECTOR);
            menuItems.forEach(mi => {
                if (mi.textContent && mi.textContent.includes(TARGET_TEXT)) {
                    originalCopyItem = mi;
                }
            });
        }

        if (!originalCopyItem) {
            console.error('無法找到原生「複製連結」按鈕。');
            alert('無法獲取貼文連結。請檢查瀏覽器控制台錯誤。');
            return;
        }

        // 2. 模擬點擊原生按鈕,讓 X.com 將正確的 URL 寫入剪貼簿
        // 這是獲取貼文 URL 的最穩定方法,特別是在 /home 頁面。
        originalCopyItem.click();

        // 3. 延遲讀取剪貼簿 (給 X.com 程式碼反應時間)
        setTimeout(() => {
            navigator.clipboard.readText()
                .then(originalUrl => {
                    if (!originalUrl || !originalUrl.includes('/status/')) {
                        console.error('剪貼簿中未讀取到有效的貼文連結。請確認剪貼簿權限。');
                        alert('無法從剪貼簿讀取貼文連結,操作失敗。');
                        return;
                    }

                    // 4. 轉換 URL (V1.8 的精確替換邏輯)
                    const regex = /:\/\/(?:x|twitter)\.com/i;
                    const newUrl = originalUrl.replace(regex, `://${service}.com`);

                    if (newUrl === originalUrl) {
                        console.error("網址轉換失敗:原始 URL 格式不符合預期。");
                        return;
                    }

                    // 5. 將轉換後的 URL 寫回剪貼簿
                    navigator.clipboard.writeText(newUrl)
                        .then(() => {
                            console.log(`已複製 ${service.toUpperCase()} 連結: ${newUrl}`);
                        })
                        .catch(err => {
                            console.error('寫入剪貼簿時發生錯誤:', err);
                            alert('寫入剪貼簿權限不足,請檢查瀏覽器設定。');
                        });
                })
                .catch(err => {
                    console.error('讀取剪貼簿時發生錯誤:', err);
                    alert('讀取剪貼簿權限不足,請檢查瀏覽器設定。');
                });
        }, 100);
    }

    /**
     * 尋找並插入新的分享選項
     */
    function insertShareOptions() {
        const menuContainers = document.querySelectorAll(MENU_CONTAINER_SELECTOR);

        menuContainers.forEach(menuContainer => {
            if (menuContainer.hasAttribute(PROCESSED_ATTR)) {
                return;
            }

            const menuItems = menuContainer.querySelectorAll(POTENTIAL_ITEM_SELECTOR);
            let originalItem = null;

            menuItems.forEach(item => {
                if (item.textContent && item.textContent.includes(TARGET_TEXT)) {
                     originalItem = item;
                }
            });

            if (originalItem) {
                // 標記選單容器為已處理
                menuContainer.setAttribute(PROCESSED_ATTR, 'true');

                // 建立並插入 FX 連結選項
                const fxItem = createMenuItem('複製 FxTwitter 連結', 'fxtwitter', originalItem);
                fxItem.addEventListener('click', handleCopy);
                menuContainer.insertBefore(fxItem, originalItem.nextSibling);

                // 建立並插入 VX 連結選項
                const vxItem = createMenuItem('複製 VxTwitter 連結', 'vxtwitter', originalItem);
                vxItem.addEventListener('click', handleCopy);
                menuContainer.insertBefore(vxItem, fxItem.nextSibling);

                console.log("FX/VX 連結選項已成功插入。✅");
            }
        });
    }

    // --- MutationObserver 監聽 DOM 變化 ---

    const targetNode = document.body;
    const config = { childList: true, subtree: true };

    const observer = new MutationObserver(function(mutationsList, observer) {
        insertShareOptions();
    });

    observer.observe(targetNode, config);

    insertShareOptions();
})();