您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
support twitter to copy, easy to share.
当前为
// ==UserScript== // @name share-tweet-copy // @namespace https://screw-hand.com/ // @version 0.2 // @description support twitter to copy, easy to share. // @author screw-hand // @match https://twitter.com/* // @icon https://abs.twimg.com/favicons/twitter.3.ico // @license MIT // @grant GM_addStyle // @homepage https://github.com/screw-hand/tampermonkey-user.js // @supportURL https://github.com/screw-hand/tampermonkey-user.js/issues/new // ==/UserScript== (function () { 'use strict'; /** * Change Log * * Version 0.2 (2023-12-13) * - Added styles for the copy-tweet button, including support for dark mode. * - Implemented a tooltip to show the result of the copy action. * - Enhanced dark mode support for better user experience in various lighting conditions. * * Version 0.1 (2023-12-13) * - Initial release. * - Basic functionality to copy tweet text to clipboard with a simple template. */ /** * TODO * 1. copy emoji https://twitter.com/sanxiaozhizi/status/1734793603900485822 * 2. Statistics the pic(both git) / videos total count * 3. support user custom input the share template * * FIXME * 1. cannot copy https://twitter.com/Man_Kei/status/1602787456578985984 * 2. notify about copy failed */ /** * Defines the styles for the copy button. * This includes support for dark mode and styling for various states like hover and focus. */ const copyBtnStyle = ` .copy-tweet-button { --button-bg: #e5e6eb; --button-hover-bg: #d7dbe2; --button-text-color: #4e5969; --button-hover-text-color: #164de5; --button-border-radius: 6px; --button-diameter: 24px; --button-outline-width: 2px; --button-outline-color: #9f9f9f; --tooltip-bg: #1d2129; --toolptip-border-radius: 4px; --tooltip-font-family: JetBrains Mono, Consolas, Menlo, Roboto Mono, monospace; --tooltip-font-size: 12px; --tootip-text-color: #fff; --tooltip-padding-x: 7px; --tooltip-padding-y: 7px; --tooltip-offset: 8px; /* --tooltip-transition-duration: 0.3s; */ } @media (prefers-color-scheme: dark) { .copy-tweet-button { --button-bg: #353434; --button-hover-bg: #464646; --button-text-color: #ccc; --button-outline-color: #999; --button-hover-text-color: #8bb9fe; --tooltip-bg: #f4f3f3; --tootip-text-color: #111; } } .copy-tweet-button { box-sizing: border-box; width: var(--button-diameter); height: var(--button-diameter); margin-left: 8px; border-radius: var(--button-border-radius); background-color: var(--button-bg); color: var(--button-text-color); border: none; cursor: pointer; position: relative; outline: var(--button-outline-width) solid transparent; transition: all 0.2s ease; } .tooltip { position: absolute; opacity: 0; left: calc(100% + var(--tooltip-offset)); top: 50%; transform: translateY(-50%); white-space: nowrap; font: var(--tooltip-font-size) var(--tooltip-font-family); color: var(--tootip-text-color); background: var(--tooltip-bg); padding: var(--tooltip-padding-y) var(--tooltip-padding-x); border-radius: var(--toolptip-border-radius); pointer-events: none; transition: all var(--tooltip-transition-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55); } .tooltip::before { content: attr(data-text-initial); } .tooltip::after { content: ""; width: var(--tooltip-padding-y); height: var(--tooltip-padding-y); background: inherit; position: absolute; top: 50%; left: calc(var(--tooltip-padding-y) / 2 * -1); transform: translateY(-50%) rotate(45deg); z-index: -999; pointer-events: none; } .copy-tweet-button svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .checkmark { display: none; } .copy-tweet-button:hover .tooltip, .copy-tweet-button:focus:not(:focus-visible) .tooltip { opacity: 1; visibility: visible; } .copy-tweet-button:focus:not(:focus-visible) .tooltip::before { content: attr(data-text-end); } .copy-tweet-button:focus:not(:focus-visible) .clipboard { display: none; } .copy-tweet-button:focus:not(:focus-visible) .checkmark { display: block; } .copy-tweet-button:hover, .copy-tweet-button:focus { background-color: var(--button-hover-bg); } .copy-tweet-button:active { outline: var(--button-outline-width) solid var(--button-outline-color); } .copy-tweet-button:hover svg { color: var(--button-hover-text-color); } `; /** * Adds styles using the DOM method. * @param {string} cssText - The CSS style text to be added. */ function addStyleWithDOM(cssText) { const styleNode = document.createElement('style') styleNode.appendChild(document.createTextNode(cssText)); (document.querySelector('head') || document.documentElement).appendChild(styleNode) } /** * Adds styles using GM_addStyle and returns whether it was successful. * @param {string} cssText - The CSS style text to be added. * @returns {boolean} Whether the style was successfully added. */ function addStyleWithGM(cssText) { const isGMAddStyleAvailable = typeof GM_addStyle !== 'undefined'; if (isGMAddStyleAvailable) { GM_addStyle(cssText); } return isGMAddStyleAvailable; } // Execute style addition and check if it was successful const resultsOfEnforcement = addStyleWithGM(copyBtnStyle) // If unsuccessful, fallback to adding styles using the DOM method if (!resultsOfEnforcement) { addStyleWithDOM(copyBtnStyle) } /** * Contains functions to extract various pieces of data from a tweet element. */ const tweetDataExtractors = { username: ({ tweetElement }) => tweetElement.querySelector('[dir]').innerText, userid: ({ tweetElement }) => findUserID({ tweetElement }), tweetText: ({ tweetElement }) => tweetElement.querySelector('div[data-testid="tweetText"]').innerText, link: ({ tweetElement }) => 'https://twitter.com' + tweetElement.querySelector('a[href*="/status/"]').getAttribute('href') }; /** * User-defined template for formatting tweet data. */ let userTemplate = `{{username}} ({{userid}}) {{tweetText}} {{link}}`; /** * Adds a copy button to a tweet element. * @param {Object} param - Object containing the tweet element. * @param {Element} param.tweetElement - The tweet element. */ function addCopyButtonToTweet({ tweetElement }) { if (tweetElement.querySelector('.copy-tweet-button')) { return; } let copyButton = document.createElement('button'); copyButton.className = 'copy-tweet-button'; // 添加一个类名以避免重复添加 copyButton.innerHTML = ` <span data-text-end="Copied" data-text-initial="Copy to clipboard" class="tooltip"></span> <span> <svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 6.35 6.35" y="0" x="0" height="14" width="14" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg" class="clipboard"> <g> <path fill="currentColor" d="M2.43.265c-.3 0-.548.236-.573.53h-.328a.74.74 0 0 0-.735.734v3.822a.74.74 0 0 0 .735.734H4.82a.74.74 0 0 0 .735-.734V1.529a.74.74 0 0 0-.735-.735h-.328a.58.58 0 0 0-.573-.53zm0 .529h1.49c.032 0 .049.017.049.049v.431c0 .032-.017.049-.049.049H2.43c-.032 0-.05-.017-.05-.049V.843c0-.032.018-.05.05-.05zm-.901.53h.328c.026.292.274.528.573.528h1.49a.58.58 0 0 0 .573-.529h.328a.2.2 0 0 1 .206.206v3.822a.2.2 0 0 1-.206.205H1.53a.2.2 0 0 1-.206-.205V1.529a.2.2 0 0 1 .206-.206z"> </path> </g> </svg> <svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 24 24" y="0" x="0" height="14" width="14" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg" class="checkmark"> <g> <path data-original="#000000" fill="currentColor" d="M9.707 19.121a.997.997 0 0 1-1.414 0l-5.646-5.647a1.5 1.5 0 0 1 0-2.121l.707-.707a1.5 1.5 0 0 1 2.121 0L9 14.171l9.525-9.525a1.5 1.5 0 0 1 2.121 0l.707.707a1.5 1.5 0 0 1 0 2.121z"> </path> </g> </svg> </span> ` let nameElement = tweetElement.querySelector('[data-testid="User-Name"]'); if (nameElement) { nameElement.appendChild(copyButton); } copyButton.addEventListener('click', e => handleTweetCopyClick({ e, tweetElement })); } /** * Handles the copy button click event. * @param {Object} param - Object containing the event and tweet element. * @param {Event} param.e - The click event. * @param {Element} param.tweetElement - The tweet element. */ function handleTweetCopyClick({ e, tweetElement }) { e.stopPropagation(); let formattedText = formatTweet({ tweetElement }); copyTextToClipboard(formattedText); } /** * Finds the user ID from a tweet element. * @param {Object} param - Object containing the tweet element. * @param {Element} param.tweetElement - The tweet element. * @returns {string} User ID. */ function findUserID({ tweetElement }) { // 使用您提供的选择器获取匹配的元素 let links = tweetElement.querySelectorAll('a[href^="/"][role="link"][tabindex="-1"]'); let userIDElement = links[links.length - 1]; return userIDElement ? userIDElement.textContent : ''; } /** * Formats the tweet data according to the user-defined template. * @param {Object} param - Object containing the tweet element. * @param {Element} param.tweetElement - The tweet element. * @returns {string} Formatted tweet data. */ function formatTweet({ tweetElement }) { let formatted = userTemplate.replace(/\\{{/g, '{').replace(/\\}}/g, '}'); return formatted.replace(/{{(\w+)}}/g, (match, key) => { if (tweetDataExtractors[key]) { return tweetDataExtractors[key]({ tweetElement }); } return match; }); } /** * Copies text to the clipboard. * @param {string} text - Text to be copied. */ function copyTextToClipboard(text) { navigator.clipboard.writeText(text).then(function () { console.log('Tweet copied to clipboard'); }).catch(function (err) { console.error('Could not copy tweet: ', err); }); } /** * Observes DOM mutations to add a copy button to new tweets. */ let observer = new MutationObserver(function (mutations) { let articles = document.querySelectorAll('article[role="article"]'); articles.forEach(function (tweetElement) { if (!tweetElement.querySelector('.copy-tweet-button')) { addCopyButtonToTweet({ tweetElement }); } }); }); // Start observing observer.observe(document.body, { childList: true, subtree: true }); })();