Greasy Fork is available in English.

Twitter Like and Send to Discord

Send tweets to Discord on like and with custom button, using vxtwitter.com links

Від 28.07.2024. Дивіться остання версія.

// ==UserScript==
// @name         Twitter Like and Send to Discord
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Send tweets to Discord on like and with custom button, using vxtwitter.com links
// @match        https://twitter.com/*
// @match        https://x.com/*
// @author       dr.bobo0
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @connect      discord.com
// ==/UserScript==

(function() {
    'use strict';

    let DISCORD_WEBHOOK_URL = GM_getValue('discordWebhookUrl', '');
    let sentTweets = new Set(JSON.parse(GM_getValue('sentTweets', '[]')));

    const baseUrl = 'https://vxtwitter.com';
    const defaultSVG = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard" viewBox="0 0 24 24" stroke-width="2" stroke="#71767C" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /></svg>';
    const copiedSVG = '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard-check" viewBox="0 0 24 24" stroke-width="2" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M9 14l2 2l4 -4" /></svg>';

    function addSendButtonToTweets() {
        const tweets = document.querySelectorAll('button[data-testid="bookmark"]');
        tweets.forEach(bookmarkButton => {
            const parentDiv = bookmarkButton.parentElement;
            const tweet = parentDiv.closest('article[data-testid="tweet"]');
            if (tweet && !tweet.querySelector('.custom-send-icon')) {
                const sendIcon = document.createElement('div');
                sendIcon.classList.add('custom-send-icon');
                sendIcon.setAttribute('aria-label', 'Send to Discord');
                sendIcon.setAttribute('role', 'button');
                sendIcon.setAttribute('tabindex', '0');
                sendIcon.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 19px; height: 19px; border-radius: 9999px; transition-duration: 0.2s; cursor: pointer;';

                const tweetUrl = extractTweetUrl(tweet);
                const tweetId = tweetUrl.split('/').pop();
                const isSent = sentTweets.has(tweetId);

                sendIcon.innerHTML = isSent ? copiedSVG : defaultSVG;

                sendIcon.addEventListener('click', (event) => {
                    event.stopPropagation();
                    if (tweetUrl) {
                        if (!isSent) {
                            sendToDiscord(tweetId, tweetUrl, sendIcon);
                        } else {
                            showNotification('This tweet has already been sent to Discord', 'info');
                        }
                    }
                });

                const parentDivClone = parentDiv.cloneNode(true);
                parentDivClone.style.cssText = 'display: flex; align-items: center;';
                parentDiv.parentNode.insertBefore(parentDivClone, parentDiv.nextSibling);
                parentDivClone.innerHTML = '';
                parentDivClone.appendChild(sendIcon);

                // Add listener to the like button
                const likeButton = tweet.querySelector('[data-testid="like"]');
                if (likeButton) {
                    likeButton.addEventListener('click', (e) => {
                        if (e.target.closest('[data-testid="like"]')) {
                            if (!isSent) {
                                sendToDiscord(tweetId, tweetUrl, sendIcon);
                            }
                        }
                    });
                }
            }
        });
    }

    function extractTweetUrl(tweetElement) {
        const linkElement = tweetElement.querySelector('a[href*="/status/"]');
        if (!linkElement) {
            return;
        }
        let url = linkElement.getAttribute('href').split('?')[0]; // Remove any query parameters
        if (url.includes('/photo/')) {
            url = url.split('/photo/')[0];
        }
        return `${baseUrl}${url}`;
    }

    function sendToDiscord(tweetId, link, buttonElement) {
        if (!DISCORD_WEBHOOK_URL) {
            showNotification('Discord webhook URL is not set. Please set it in the script settings.', 'error');
            return;
        }

        if (sentTweets.has(tweetId)) {
            showNotification('This tweet has already been sent to Discord', 'info');
            return;
        }

        GM_xmlhttpRequest({
            method: 'POST',
            url: DISCORD_WEBHOOK_URL,
            headers: {
                'Content-Type': 'application/json'
            },
            data: JSON.stringify({ content: link }),
            onload: function(response) {
                if (response.status === 204) {
                    showNotification('Tweet sent to Discord successfully', 'success');
                    sentTweets.add(tweetId);
                    GM_setValue('sentTweets', JSON.stringify([...sentTweets]));
                    buttonElement.innerHTML = copiedSVG;
                } else {
                    showNotification('Failed to send tweet to Discord. Please check your webhook URL.', 'error');
                    console.error('Failed to send tweet to Discord', response);
                }
            },
            onerror: function(error) {
                showNotification('Error sending tweet to Discord. Please check your internet connection.', 'error');
                console.error('Error sending tweet to Discord', error);
            }
        });
    }

    function showNotification(message, type = 'info') {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 10px 20px;
            border-radius: 5px;
            font-size: 14px;
            z-index: 10000;
            transition: opacity 0.5s ease-in-out;
        `;

        switch(type) {
            case 'success':
                notification.style.backgroundColor = '#4CAF50';
                notification.style.color = 'white';
                break;
            case 'error':
                notification.style.backgroundColor = '#F44336';
                notification.style.color = 'white';
                break;
            default:
                notification.style.backgroundColor = '#2196F3';
                notification.style.color = 'white';
        }

        document.body.appendChild(notification);

        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                document.body.removeChild(notification);
            }, 500);
        }, 3000);
    }

    const observer = new MutationObserver(addSendButtonToTweets);
    observer.observe(document.body, { childList: true, subtree: true });

    // Run the script
    setTimeout(addSendButtonToTweets, 2000);  // Delay initialization to ensure the page is loaded

    // Add settings UI
    GM_registerMenuCommand('Set Discord Webhook URL', () => {
        const url = prompt('Enter your Discord webhook URL:', DISCORD_WEBHOOK_URL);
        if (url !== null) {
            GM_setValue('discordWebhookUrl', url);
            DISCORD_WEBHOOK_URL = url;
            showNotification('Discord webhook URL updated. Refresh the page for changes to take effect.', 'success');
        }
    });

    // Add command to clear sent tweets
    GM_registerMenuCommand('Clear Sent Tweets History', () => {
        if (confirm('Are you sure you want to clear the history of sent tweets? This will allow re-sending of previously sent tweets.')) {
            sentTweets.clear();
            GM_setValue('sentTweets', '[]');
            showNotification('Sent tweets history has been cleared.', 'success');
        }
    });
})();