您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to share a tweet with an alternative URL to the "X" link
// ==UserScript== // @name Twitter Fix URL // @namespace https://github.com/btcode23 // @version 0.3.2 // @description Adds a button to share a tweet with an alternative URL to the "X" link // @author btcode23 // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @match https://twitter.com/* // @match https://x.com/* // @icon https://abs.twimg.com/responsive-web/client-web/icon-ios.77d25eba.png // ==/UserScript== const baseUrl = 'https://fixvx.com'; if (GM_getValue('ALT_URL') == undefined) { GM_setValue('ALT_URL', baseUrl); } const svg = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' + '<path d="M8 5.00005C7.01165 5.00082 6.49359 5.01338 6.09202 5.21799C5.71569 5.40973 5.40973 5.71569 5.21799 6.09202C5 6.51984 5 7.07989 5 8.2V17.8C5 18.9201 5 19.4802 5.21799 19.908C5.40973 20.2843 5.71569 20.5903 6.09202 20.782C6.51984 21 7.07989 21 8.2 21H15.8C16.9201 21 17.4802 21 17.908 20.782C18.2843 20.5903 18.5903 20.2843 18.782 19.908C19 19.4802 19 18.9201 19 17.8V8.2C19 7.07989 19 6.51984 18.782 6.09202C18.5903 5.71569 18.2843 5.40973 17.908 5.21799C17.5064 5.01338 16.9884 5.00082 16 5.00005M8 5.00005V7H16V5.00005M8 5.00005V4.70711C8 4.25435 8.17986 3.82014 8.5 3.5C8.82014 3.17986 9.25435 3 9.70711 3H14.2929C14.7456 3 15.1799 3.17986 15.5 3.5C15.8201 3.82014 16 4.25435 16 4.70711V5.00005M12 11V17M12 17L10 15M12 17L14 15" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>' + '</svg>'; function displayConfirmation() { const layers = document.getElementById('layers'); let confirmation = document.getElementById('confirmCopyAltLink'); if (!confirmation) { confirmation = document.createElement('div'); confirmation.id = 'confirmCopyAltLink'; confirmation.style.cssText = 'position: fixed; bottom: 10px; left: 0; right: 0; margin-left: auto; margin-right: auto; width: 100px; height: 40px;' + 'background-color: rgba(29, 161, 242, 0.9); border-radius: 5px;' confirmation.innerHTML = '<p style="line-height: 40px; margin: 0; padding: auto; vertical-align: middle; text-align: center; font-weight: bold;">Copied</p>'; layers.append(confirmation); } // show confirmation and then fade out confirmation.style.opacity = '1'; confirmation.style.visibility = 'visible'; confirmation.style.transition = ''; setTimeout(function() { confirmation.style.opacity = '0'; confirmation.style.visibility = 'hidden'; confirmation.style.transition = '1s'; }, 1000); } function copyAlternativeTwitterUrl(tweet) { // the part of the tweet with time seems to give the path to the tweet const tweetPath = tweet.querySelector('a:has(time)').getAttribute('href'); // new share URL let newUrl = GM_getValue('ALT_URL') + tweetPath; navigator.clipboard.writeText(newUrl); displayConfirmation(); } function designButton(tweet) { const group = tweet.querySelector('div[role="group"]'); const otherIcon = group.querySelector('button[aria-label="Share post"]'); const newIconPadding = document.createElement('div'); group.insertBefore(newIconPadding, otherIcon.sibling); newIconPadding.classList.add('custom-copy-icon'); const computedStyle = getComputedStyle(otherIcon); console.log(getComputedStyle(otherIcon)); const adjustedIconSize = Number(computedStyle.height.split('px')[0]); // square size based on share icon's height newIconPadding.style.height = adjustedIconSize + 'px'; newIconPadding.style.width = adjustedIconSize + 'px'; const adjustedPadding = adjustedIconSize / 4; // move new share icon over relative to size newIconPadding.style.paddingLeft = adjustedPadding + 'px'; const newIcon = document.createElement('div'); // create element to fit highlighted circle newIconPadding.append(newIcon); const computedStyleCircle = getComputedStyle(otherIcon.firstChild.firstChild.firstChild); const adjustedIconCircleSize = Number(computedStyleCircle.height.split('px')[0]); newIcon.style.height = adjustedIconSize + 'px'; newIcon.style.width = adjustedIconSize + 'px'; newIcon.style.padding = (adjustedIconSize - adjustedIconCircleSize) + 'px'; newIcon.style.borderRadius = '50%'; // make background border a circle newIcon.innerHTML = svg; // add clipboard svg const icon = newIcon.querySelector('svg'); const computedStyleIcon = getComputedStyle(otherIcon.querySelector('svg')); const iconOriginalColor = computedStyleIcon.color; icon.querySelector('path').style.stroke = iconOriginalColor; // set color to same as other icon //icon.style.padding = 50 / adjustedIconCircleSize + 'px'; // highlight icon when mouseover event using twitter blue color // should look the same as the other icons newIcon.addEventListener('mouseover', function() { newIcon.style.backgroundColor = 'rgba(29, 161, 242, 0.1)'; newIcon.querySelector('path').style.stroke = 'rgba(29, 161, 242, 1)'; }); // return to original style when mouseleave event newIcon.addEventListener('mouseleave', function() { newIcon.style.backgroundColor = ''; newIcon.querySelector('path').style.stroke = iconOriginalColor; }); // copy link using alternative domain newIcon.addEventListener('click', function(e) { e.stopPropagation(); copyAlternativeTwitterUrl(tweet); }); } function addNewShareButtons() { const tweets = document.querySelectorAll('article[data-testid="tweet"]'); tweets.forEach(tweet => { if (!tweet.querySelector('.custom-copy-icon')) { designButton(tweet); } }); } (function() { 'use strict'; // add new share button to each loaded tweet const observer = new MutationObserver(addNewShareButtons); observer.observe( document.body, { childList: true, subtree: true }); })(); // change the alternative URL for twitter GM_registerMenuCommand('Setting', () => config()); function config() { let configPopupContainer = document.getElementById('ConfigTwitterFixUrlContainer') if (!configPopupContainer) { configPopupContainer = document.createElement('div'); } configPopupContainer.id = 'ConfigTwitterFixUrlContainer'; configPopupContainer.style.cssText = 'position: fixed; width: 100%; height: 100%; left: 0; top: 0; background: rgba(51,51,51,0.7);'; document.body.appendChild(configPopupContainer); const configPopup = document.createElement('form'); configPopup.innerHTML = '<p style="text-align: center;">Twitter Fix URL Config</p>'; configPopup.style.cssText = 'position: fixed; top: 50%; left: 50%; padding: 10px; margin-top: -100px; margin-left: -150px; width: 325px; height: 200px; background-color: black; border: 2px solid white; border-radius: 25px; color: white; font-size: 24px;'; const formFontSize = 'font-size: 18px;'; const altUrlLabel = document.createElement('label'); altUrlLabel.style.cssText = formFontSize; altUrlLabel.setAttribute('for', 'AltUrlTwttierFixUrl'); altUrlLabel.innerHTML = 'Alternative URL:'; const altUrlInput = document.createElement('input'); altUrlInput.style.cssText = formFontSize + ' width: 280px;' altUrlInput.setAttribute('type', 'text'); altUrlInput.setAttribute('id', 'AltUrlTwttierFixUrl'); altUrlInput.setAttribute('name', 'AltUrlTwttierFixUrl'); altUrlInput.setAttribute('value', GM_getValue('ALT_URL')); configPopup.append(altUrlLabel); configPopup.append(document.createElement('br')); configPopup.append(altUrlInput); const buttonCSS = formFontSize + ' width: 75px; height: 30px; border: 2px solid white; text-align: center; padding: 0px; border-radius: 10px; margin-left: 12.25px; margin-right: 12.25px;'; const altUrlResetButton = document.createElement('button'); altUrlResetButton.setAttribute('type', 'button'); altUrlResetButton.setAttribute('value', 'reset'); altUrlResetButton.style.cssText = buttonCSS; altUrlResetButton.innerHTML = 'Reset'; altUrlResetButton.addEventListener('click', function(e) { altUrlInput.value = baseUrl; }); const altUrlCancelButton = document.createElement('button'); altUrlCancelButton.setAttribute('type', 'button'); altUrlCancelButton.setAttribute('value', 'cancel'); altUrlCancelButton.style.cssText = buttonCSS; altUrlCancelButton.innerHTML = 'Cancel'; altUrlCancelButton.addEventListener('click', function(e) { configPopupContainer.style.display = 'none'; }); const altUrlSubmitButton = document.createElement('button'); altUrlSubmitButton.setAttribute('type', 'button'); altUrlSubmitButton.setAttribute('value', 'submit'); altUrlSubmitButton.style.cssText = buttonCSS; altUrlSubmitButton.innerHTML = 'Submit'; altUrlSubmitButton.addEventListener('click', function(e) { GM_setValue('ALT_URL', altUrlInput.value); configPopupContainer.style.display = 'none'; }); configPopup.append(document.createElement('br')); configPopup.append(document.createElement('br')); configPopup.appendChild(altUrlResetButton); configPopup.appendChild(altUrlCancelButton); configPopup.appendChild(altUrlSubmitButton); configPopupContainer.appendChild(configPopup); document.addEventListener('mouseup', function(e) { if (e.target.id == 'ConfigTwitterFixUrlContainer') { configPopupContainer.style.display = 'none'; } }); }