Created
April 1, 2025 19:06
-
-
Save TechupBusiness/77b5f18d339f3ef9fc0e349868504557 to your computer and use it in GitHub Desktop.
Browser-Javascript (Greasemonkey/Violentmonkey etc) to add a button to X to open the x-thread in a new tab
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Twitter Thread Button | |
// @namespace https://techupbusiness.com/ | |
// @version 0.1 | |
// @description Adds a button next to the "..." menu on tweet pages that opens a twitter-thread URL in a new tab. | |
// @match https://x.com/*/status/* | |
// @match http://x.com/*/status/* | |
// @grant GM_addStyle | |
// ==/UserScript== | |
GM_addStyle(` | |
.my-thread-btn { | |
display: inline-flex; | |
align-items: center; | |
padding: 4px; | |
margin-right: 4px; | |
background: transparent; | |
border: none; | |
cursor: pointer; | |
/* Default text color for light mode */ | |
color: #14171a; | |
} | |
@media (prefers-color-scheme: dark) { | |
.my-thread-btn { | |
/* Adjust text color for dark mode */ | |
color: #e1e8ed; | |
} | |
} | |
.my-thread-btn:hover { | |
background-color: rgba(15, 20, 25, 0.1); | |
border-radius: 9999px; | |
} | |
`); | |
function addThreadButton() { | |
// Do nothing if our button already exists. | |
if (document.getElementById('thread-button')) return; | |
// Try to find the reference "more" button on the page. | |
let refButton = document.querySelector('[aria-label="More"]') || | |
document.querySelector('[data-testid="caret"]'); | |
if (!refButton) return; | |
// Use the same container as the reference button. | |
let container = refButton.parentElement; | |
if (!container) return; | |
let threadButton = document.createElement('button'); | |
threadButton.id = 'thread-button'; | |
threadButton.className = 'my-thread-btn'; | |
// Inline SVG icon updated with fill="currentColor" for theme compatibility. | |
threadButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="16" height="16" viewBox="0 0 24 24"> | |
<path d="M3.9,12c0-1.4,0.5-2.8,1.5-3.9l3.4-3.4c1.1-1.1,2.5-1.5,3.9-1.5s2.8,0.5,3.9,1.5l1.4,1.4c0.6,0.6,0.6,1.5,0,2.1 | |
c-0.6,0.6-1.5,0.6-2.1,0L11,7.4c-0.4-0.4-1-0.4-1.4,0c-0.4,0.4-0.4,1,0,1.4l4.8,4.8c0.6,0.6,0.6,1.5,0,2.1l-1.4,1.4 | |
c-1.1,1.1-2.5,1.5-3.9,1.5s-2.8-0.5-3.9-1.5L5.4,15.9C4.4,14.8,3.9,13.4,3.9,12z M20.1,12c0,1.4-0.5,2.8-1.5,3.9l-3.4,3.4 | |
c-1.1,1.1-2.5,1.5-3.9,1.5s-2.8-0.5-3.9-1.5l-1.4-1.4c-0.6-0.6-0.6-1.5,0-2.1c0.6-0.6,1.5-0.6,2.1,0l1.4,1.4c0.4,0.4,1,0.4,1.4,0 | |
c0.4-0.4,0.4-1,0-1.4L8.3,10.6c-0.6-0.6-0.6-1.5,0-2.1l1.4-1.4c1.1-1.1,2.5-1.5,3.9-1.5s2.8,0.5,3.9,1.5l3.4,3.4 | |
C19.6,9.2,20.1,10.6,20.1,12z"/> | |
</svg>`; | |
threadButton.addEventListener('click', function(e) { | |
e.stopPropagation(); | |
// Extract tweet ID from the current URL using the provided regex. | |
const regex = /https?:\/\/x\.com\/.+?\/status\/(\d+)(?:\?.*|#.*|$)/; | |
const match = window.location.href.match(regex); | |
if (match && match[1]) { | |
const tweetId = match[1]; | |
const newTabUrl = `https://twitter-thread.com/t/${tweetId}`; | |
window.open(newTabUrl, '_blank'); | |
} else { | |
console.error("Tweet ID not found in URL."); | |
} | |
}); | |
// Insert our button before the reference "more" button. | |
container.insertBefore(threadButton, refButton); | |
} | |
// Use MutationObserver to watch for DOM changes in case Twitter loads content dynamically. | |
const observer = new MutationObserver(() => { | |
addThreadButton(); | |
}); | |
observer.observe(document.body, { childList: true, subtree: true }); | |
// Fallback: periodically call addThreadButton() in case the MutationObserver misses updates. | |
const fallbackInterval = setInterval(() => { | |
addThreadButton(); | |
if (document.getElementById('thread-button')) { | |
clearInterval(fallbackInterval); | |
} | |
}, 1000); | |
// Ensure our script runs after the full page load. | |
window.addEventListener('load', () => { | |
addThreadButton(); | |
}); | |
// Initial attempt to add the button in case the DOM is ready. | |
addThreadButton(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment