Skip to content

Instantly share code, notes, and snippets.

@TechupBusiness
Created April 1, 2025 19:06
Show Gist options
  • Save TechupBusiness/77b5f18d339f3ef9fc0e349868504557 to your computer and use it in GitHub Desktop.
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
// ==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