Skip to content

Instantly share code, notes, and snippets.

@philippgitpush
Last active June 26, 2024 15:58
Show Gist options
  • Save philippgitpush/c955a9b51fa4870208e233212519295f to your computer and use it in GitHub Desktop.
Save philippgitpush/c955a9b51fa4870208e233212519295f to your computer and use it in GitHub Desktop.
Userscript that adds a button on X (Formerly Twitter) that allows you to copy a tweet to the obsidian embed format (user plugin)
// ==UserScript==
// @name X Embed Button
// @version 1.0.1
// @description Userscript that adds a button on X (Formerly Twitter) that allows you to copy a tweet to the obsidian embed format (user plugin)
// @author philippgitpush
// @match https://x.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
function createEmbedButton(tweetElement) {
// Check if button already exists
if (tweetElement.querySelector('.embed-button')) {
return;
}
// Create the button
const button = document.createElement('button');
button.className = 'embed-button';
button.style.marginLeft = '8px'; // Modified margin-left
button.style.border = 'none'; // Removed border
button.style.background = 'none'; // Removed background
button.style.cursor = 'pointer'; // Kept cursor pointer
button.style.display = 'flex'; // Added display flex
button.style.padding = '0'; // Removed padding
button.style.justifyContent = 'center'; // Added justify-content center
button.style.alignContent = 'center'; // Added align-content center
button.style.flexDirection = 'column'; // Added flex-direction column
// Create the SVG icon
const svgIcon = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#536471" viewBox="0 0 256 256">
<path d="M88,96a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,96Zm8,40h64a8,8,0,0,0,0-16H96a8,8,0,0,0,0,16Zm32,16H96a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16ZM224,48V156.69A15.86,15.86,0,0,1,219.31,168L168,219.31A15.86,15.86,0,0,1,156.69,224H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H152V160a8,8,0,0,1,8-8h48V48H48Zm120-40v28.7L196.69,168Z"></path>
</svg>
`;
button.innerHTML = svgIcon;
// Add hover effect
button.addEventListener('mouseenter', () => {
button.querySelector('path').setAttribute('fill', '#f62681');
});
button.addEventListener('mouseleave', () => {
button.querySelector('path').setAttribute('fill', '#536471');
});
// Append button to the tweet's action area
const actionArea = tweetElement.querySelector('[role="group"]');
if (actionArea) {
actionArea.appendChild(button);
}
// Add event listener to the button
button.addEventListener('click', () => {
const displayName = tweetElement.querySelector('[data-testid="User-Name"] span').innerText;
const usernameElement = tweetElement.querySelector('a[href*="/status/"]');
const username = usernameElement ? usernameElement.href.split('/')[3] : '';
let tweetTextElement = tweetElement.querySelector('[data-testid="tweetText"]');
let tweetText = tweetTextElement ? tweetTextElement.innerText.replace(/\n/g, ' ') : ''; // Replace newlines with spaces
tweetText = escapeSpecialCharacters(tweetText); // Escape special characters in tweetText
const tweetUrl = tweetElement.querySelector('time').parentElement.href;
let imageUrl = '';
const imageElement = tweetElement.querySelector('[data-testid="tweetPhoto"] img');
if (imageElement) {
imageUrl = getImageUrl(imageElement.src);
} else {
const videoElement = tweetElement.querySelector('video[poster]');
if (videoElement) {
imageUrl = getImageUrl(videoElement.getAttribute('poster'));
}
}
const embedCode = `
\`\`\`embed
title: "${escapeSpecialCharacters(displayName)} (@${username}) on X"
image: "${imageUrl}"
${tweetText ? `description: "${tweetText}"` : ''}
url: "${tweetUrl}"
\`\`\`
`;
navigator.clipboard.writeText(embedCode).then(() => {
showToast('Copied to clipboard');
});
});
}
// Function to handle added nodes
function handleAddedNodes(nodes) {
nodes.forEach(node => {
if (node.nodeType === 1 && node.matches('[data-testid="tweet"]')) {
createEmbedButton(node);
}
// Handle newly added tweets in nested structures
const tweets = node.querySelectorAll ? node.querySelectorAll('[data-testid="tweet"]') : [];
tweets.forEach(tweet => {
createEmbedButton(tweet);
});
});
}
// Function to escape special characters
function escapeSpecialCharacters(text) {
return text.replace(/["]/g, '\\"');
}
// Function to get the correct image URL
function getImageUrl(fullUrl) {
// Remove any query parameters and ensure '.jpg:large' suffix
const baseImageUrl = fullUrl.split(/[?#]/)[0]; // Split by '?', '#' to remove query parameters
return baseImageUrl.endsWith('.jpg') ? baseImageUrl.replace('.jpg', '') + '.jpg:large' : baseImageUrl.replace(/\.[^/.]+$/, '') + '.jpg:large';
}
// Function to show toast message
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'css-175oi2r r-aqfbo4 r-zchlnj r-1d2f490 r-1xcajam r-1p0dtai r-12vffkv';
toast.innerHTML = `
<div class="css-175oi2r r-12vffkv">
<div class="css-175oi2r r-12vffkv">
<div class="css-175oi2r r-633pao r-f8sm7e r-13qz1uu r-1ye8kvj">
<div role="alert" class="css-175oi2r r-1awozwy r-l5o3uw r-18u37iz r-1wtj0ep r-xyw6el r-105ug2t r-yz1j6i r-1kihuf0 r-z2wwpe r-zd98yo" style="transition-property: opacity; transition-duration: 170ms; transition-timing-function: cubic-bezier(0, 0, 1, 1); opacity: 1;" data-testid="toast">
<div dir="ltr" class="css-146c3p1 r-bcqeeo r-1ttztb7 r-qvutc0 r-37j5jr r-a023e6 r-rjixqe r-16dba41 r-1wbh5a2 r-3o4zer" style="white-space: unset;text-overflow: unset; color: rgb(255, 255, 255);">
<span class="css-1jxf684 r-bcqeeo r-1ttztb7 r-qvutc0 r-poiln3" style="text-overflow: unset;">${message}</span>
</div>
<div aria-hidden="true" class="css-175oi2r r-18u37iz"></div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(toast);
// Remove the toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
toast.remove();
}, 170); // Wait for the transition to finish
}, 3000);
}
// Set up the Mutation Observer
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
handleAddedNodes(mutation.addedNodes);
});
});
// Start observing the document body for added nodes
observer.observe(document.body, { childList: true, subtree: true });
// Initial run to add buttons to existing tweets
const initialTweets = document.querySelectorAll('[data-testid="tweet"]');
initialTweets.forEach(tweet => {
createEmbedButton(tweet);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment