// ==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 // @downloadURL none // ==/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); } })();