您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
FINAL: 透過模擬原生複製連結功能,修復主頁面 (Home) 獲取連結失敗的問題(需剪貼簿權限)。
当前为
// ==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(); })();