x-twitter-add-to-list-button

1-click "add user to [xyz] list" button next to usernames while scrolling your x (twitter) feed (be sure to edit the variable "lists")

Version au 23/05/2024. Voir la dernière version.

// ==UserScript==
// @name           x-twitter-add-to-list-button
// @name:ja        x-twitter-add-to-list-button
// @namespace      x-twitter
// @version        0.2.0
// @description    1-click "add user to [xyz] list" button next to usernames while scrolling your x (twitter) feed (be sure to edit the variable "lists")
// @description:ja リストにワンクリックで追加するボタンを表示します(変数"lists"を必ず編集してください)
// @author         fuwawascoco
// @match          https://twitter.com/*
// @match          https://mobile.twitter.com/*
// @match          https://x.com/*
// @icon           https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant          none
// @license        MIT
// ==/UserScript==

(function() {
    'use strict';

    const lists = ['waitlist', 'illustrators', 'animators']; // be sure to change to the NAME of your lists (not IDs)
    const checkInterval = 512; // ms
    const tryInterval = 64; // ms

    function retryUntilSuccess(newTab, selector, callback) {
        const intervalID = setInterval(() => {
            const element = newTab.document.querySelector(selector);
            if (element && element.offsetParent !== null) { // Check if element is visible
                clearInterval(intervalID);
                callback(element);
            }
        }, tryInterval);
    }

    function onClick(userProfile, list) {
        const newTab = open(userProfile);
        newTab.addEventListener('beforeunload', () => clearInterval(intervalID));

        retryUntilSuccess(newTab, 'button[aria-label="More"]', moreButton => {
            moreButton.click();
            retryUntilSuccess(newTab, 'a[href="/i/lists/add_member"]', listButton => {
                listButton.click();
                retryUntilSuccess(newTab, '[aria-modal="true"]', modal => {
                    const listSpan = Array.from(modal.getElementsByTagName('span')).find(span => span.textContent === list);
                    if (!listSpan) {
                        newTab.alert(`"${list}" was not found, please edit the script to update the variable "lists" with your own names.`);
                        newTab.close();
                        return;
                    }
                    const checkbox = listSpan.closest('[role="checkbox"]');
                    if (checkbox && checkbox.getAttribute('aria-checked') === 'false') {
                        checkbox.click();
                    }
                });
            });
        });
    }

    function createButton(userProfile, list) {
        const button = document.createElement('button');
        button.style.fontSize = '90%';
        button.style.margin = '0 0.25em';
        button.textContent = list;
        button.addEventListener('click', () => onClick(userProfile, list));
        return button;
    }

    function createButtonContainer(userProfile) {
        const container = document.createElement('div');
        container.style.position = 'relative';
        container.style.left = '2%';
        container.style.opacity = 0.5;
        lists.forEach(list => container.appendChild(createButton(userProfile, list)));
        container.classList.add('listButtons');
        return container;
    }

    function addButtons() {
        const nodes = document.querySelectorAll('[data-testid="User-Name"]:not(:has(.listButtons))');
        nodes.forEach(node => {
            const userProfile = node.querySelector('a')?.href || '';
            if (userProfile) {
                node.appendChild(createButtonContainer(userProfile));
            }
        });
    }

    setInterval(addButtons, checkInterval);

})();