Twitter Search Tweet Conversation

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    });
})();