Twitter Search Tweet Conversation

Extract tweet ID from clipboard or current page and uses search algorithm to sort tweets (both quotes and replies)

// ==UserScript==
// @name         Twitter Search Tweet Conversation
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Extract tweet ID from clipboard or current page and uses search algorithm to sort tweets (both quotes and replies)
// @author       colleidoscope
// @match        https://x.com/*
// @match        https://www.x.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let uiContainer = null;
    let debounceTimeout = null;

    function extractTweetID(url) {
        const regex = /^https?:\/\/x\.com\/[^\/]+\/status\/(\d+)/;
        const match = url.match(regex);
        return match ? match[1] : null;
    }

    function buildSearchQuery(tweetID, option) {
        let query = `filter:has_engagement (-filter:safe OR filter:safe)`;

        switch (option) {
            case 'min_faves_50':
                query = `min_faves:50 ${query} (quoted_tweet_id:${tweetID} OR conversation_id:${tweetID})`;
                break;
            case 'min_faves_5':
                query = `min_faves:5 ${query} (quoted_tweet_id:${tweetID} OR conversation_id:${tweetID})`;
                break;
            case 'no_min_faves':
                query = `${query} (quoted_tweet_id:${tweetID} OR conversation_id:${tweetID})`;
                break;
            case 'no_min_faves_no_conversation':
                query = `${query} quoted_tweet_id:${tweetID}`;
                break;
        }

        return query;
    }

    async function handleSearch(option, source) {
        try {
            let url;
            if (source === 'clipboard') {
                const text = await navigator.clipboard.readText();
                url = text.trim();
            } else if (source === 'current') {
                url = window.location.href;
            }

            const tweetID = extractTweetID(url);

            if (tweetID) {
                const query = buildSearchQuery(tweetID, option);
                const encodedQuery = encodeURIComponent(query);
                const searchURL = `https://x.com/search?q=${encodedQuery}`;
                window.open(searchURL, '_blank');
            } else {
                alert("The selected source does not contain a valid X post URL.");
            }
        } catch (err) {
            console.error('Error accessing source:', err);
            alert("Failed to read from the selected source. Please ensure permissions are granted.");
        }
    }

    function removeUI() {
        if (uiContainer && document.body.contains(uiContainer)) {
            document.body.removeChild(uiContainer);
            uiContainer = null;
        }
    }

    function addUI() {
        if (uiContainer) return; // Don't add if already exists

        // Create the container
        uiContainer = document.createElement('div');
        uiContainer.style.position = 'fixed';
        uiContainer.style.bottom = '60px';
        uiContainer.style.right = '80px'
        uiContainer.style.zIndex = '1000';
        uiContainer.style.display = 'flex';
        uiContainer.style.alignItems = 'center';
        uiContainer.style.backgroundColor = '#2f3336';
        uiContainer.style.padding = '10px 15px';
        uiContainer.style.borderRadius = '30px';
        uiContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
        uiContainer.style.transition = 'all 0.3s ease-in-out';

        // Create the dropdown for search options
        const dropdown = document.createElement('select');
        dropdown.style.marginRight = '15px';
        dropdown.style.padding = '8px 12px';
        dropdown.style.borderRadius = '20px';
        dropdown.style.border = 'none';
        dropdown.style.backgroundColor = '#1c1c1e';
        dropdown.style.color = '#fff';
        dropdown.style.fontSize = '14px';
        dropdown.style.fontWeight = '500';
        dropdown.style.cursor = 'pointer';
        dropdown.style.outline = 'none';

        // Add options to the dropdown
        const options = [
            { value: 'min_faves_50', text: 'Min Faves: 50' },
            { value: 'min_faves_5', text: 'Min Faves: 5' },
            { value: 'no_min_faves', text: 'No Min Faves' },
            { value: 'no_min_faves_no_conversation', text: 'Just Quotes' }
        ];

        options.forEach(opt => {
            const optionElement = document.createElement('option');
            optionElement.value = opt.value;
            optionElement.innerText = opt.text;
            dropdown.appendChild(optionElement);
        });

        // Create the toggle switch for source selection
        const toggleContainer = document.createElement('div');
        toggleContainer.style.display = 'flex';
        toggleContainer.style.alignItems = 'center';
        toggleContainer.style.marginRight = '15px';

        const toggleLabel = document.createElement('span');
        toggleLabel.innerText = 'Clipboard:';
        toggleLabel.style.color = '#fff';
        toggleLabel.style.marginRight = '8px';
        toggleLabel.style.fontSize = '14px';
        toggleLabel.style.fontWeight = '500';

        const toggleSwitch = document.createElement('label');
        toggleSwitch.style.position = 'relative';
        toggleSwitch.style.display = 'inline-block';
        toggleSwitch.style.width = '50px';
        toggleSwitch.style.height = '24px';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.style.opacity = '0';
        checkbox.style.width = '0';
        checkbox.style.height = '0';

        const slider = document.createElement('span');
        slider.style.position = 'absolute';
        slider.style.cursor = 'pointer';
        slider.style.top = '0';
        slider.style.left = '0';
        slider.style.right = '0';
        slider.style.bottom = '0';
        slider.style.backgroundColor = '#ccc';
        slider.style.transition = '.4s';
        slider.style.borderRadius = '24px';

        // Create the slider's circle
        const sliderBefore = document.createElement('span');
        sliderBefore.style.position = 'absolute';
        sliderBefore.style.height = '18px';
        sliderBefore.style.width = '18px';
        sliderBefore.style.left = '3px';
        sliderBefore.style.bottom = '3px';
        sliderBefore.style.backgroundColor = 'white';
        sliderBefore.style.transition = '.4s';
        sliderBefore.style.borderRadius = '50%';

        slider.appendChild(sliderBefore);
        toggleSwitch.appendChild(checkbox);
        toggleSwitch.appendChild(slider);

        // Initialize toggle state from localStorage
        const savedSource = localStorage.getItem('searchSource') || 'clipboard';
        if (savedSource === 'clipboard') {
            checkbox.checked = true;
        }

        // Update slider color based on state
        function updateSlider() {
            if (checkbox.checked) {
                slider.style.backgroundColor = '#4caf50';
                sliderBefore.style.transform = 'translateX(26px)';
            } else {
                slider.style.backgroundColor = '#ccc';
                sliderBefore.style.transform = 'translateX(0)';
            }
        }

        updateSlider();

        // Add event listener to toggle switch
        checkbox.addEventListener('change', () => {
            const source = checkbox.checked ? 'clipboard' : 'current';
            localStorage.setItem('searchSource', source);
            updateSlider();
        });

        toggleContainer.appendChild(toggleLabel);
        toggleContainer.appendChild(toggleSwitch);

        // Create the search button
        const button = document.createElement('button');
        button.innerHTML = '🔍';
        button.style.padding = '8px 16px';
        button.style.border = 'none';
        button.style.borderRadius = '20px';
        button.style.backgroundColor = '#1da1f2';
        button.style.color = '#fff';
        button.style.fontSize = '14px';
        button.style.fontWeight = '600';
        button.style.cursor = 'pointer';
        button.style.transition = 'background-color 0.3s ease, transform 0.2s ease';

        button.onmouseover = () => {
            button.style.backgroundColor = '#0d8ddb';
            button.style.transform = 'scale(1.05)';
        };
        button.onmouseout = () => {
            button.style.backgroundColor = '#1da1f2';
            button.style.transform = 'scale(1)';
        };

        button.addEventListener('click', () => {
            const selectedOption = dropdown.value;
            const source = checkbox.checked ? 'clipboard' : 'current';
            handleSearch(selectedOption, source);
        });

        // Append all elements to the container
        uiContainer.appendChild(dropdown);
        uiContainer.appendChild(toggleContainer);
        uiContainer.appendChild(button);

        // Append the container to the body
        if (!document.body.contains(uiContainer)) {
            document.body.appendChild(uiContainer);
        }
    }

    function debounce(func, wait) {
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(debounceTimeout);
                func(...args);
            };
            clearTimeout(debounceTimeout);
            debounceTimeout = setTimeout(later, wait);
        };
    }

    const debouncedInitialize = debounce(() => {
        if (document.body && !uiContainer) {
            addUI();
        }
    }, 1000);

    // Initialize on page load
    if (document.readyState === 'loading') {
        window.addEventListener('load', debouncedInitialize);
    } else {
        debouncedInitialize();
    }

    // Monitor URL changes
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            debouncedInitialize();
        }
    }).observe(document, {subtree: true, childList: true});

    // Clean up when navigating away
    window.addEventListener('beforeunload', () => {
        removeUI();
        clearTimeout(debounceTimeout);
    });
})();