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")

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           x-twitter-add-to-list-button
// @name:ja        x-twitter-add-to-list-button
// @namespace      x-twitter
// @version        0.2.2
// @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
    const maxRetries = 100; // maximum number of retries to avoid infinite loops

    function createNotification(message) {
        const notification = document.createElement('div');
        notification.style.position = 'fixed';
        notification.style.bottom = '10px';
        notification.style.right = '10px';
        notification.style.padding = '10px';
        notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        notification.style.color = 'white';
        notification.style.borderRadius = '5px';
        notification.style.zIndex = '10000';
        notification.textContent = message;
        document.body.appendChild(notification);
        setTimeout(() => {
            document.body.removeChild(notification);
        }, 5000);
    }

    function retryUntilSuccess(newTab, selector, innerText, callback) {
        let attempts = 0;
        const intervalID = setInterval(() => {
            let element;
            const elements = newTab.document.querySelectorAll(selector);
            if (innerText != '') {
                element = Array.from(elements).find(el => el.textContent.trim() === innerText);
            } else {
                element = elements[0];
            }
            if (element && element.offsetParent != null) { // Check if element is visible
                clearInterval(intervalID);
                callback(element);
            }
            if (++attempts >= maxRetries) {
                clearInterval(intervalID);
                console.error(`Failed to find element: ${selector} with innerText: ${innerText}`);
                createNotification(`"${innerText}" was not found, please edit the script to update the variable "lists" with your own names.`);
                newTab.close();
            }
        }, tryInterval);
    }

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

        retryUntilSuccess(newTab, 'button[aria-label="More"][data-testid="userActions"]', '', moreButton => {
            moreButton.click();
            retryUntilSuccess(newTab, 'a[href="/i/lists/add_member"][role="menuitem"]', '', listButton => {
                listButton.click();
                retryUntilSuccess(newTab, '[aria-modal="true"]', '', modal => {
                    let attempts = 0;
                    const intervalID = setInterval(() => {
                        const listSpan = Array.from(modal.getElementsByTagName('span')).find(span => span.textContent === list);
                        if (listSpan) {
                            clearInterval(intervalID);
                            const checkbox = listSpan.closest('[role="checkbox"]');
                            if (checkbox && checkbox.getAttribute('aria-checked') === 'false') {
                                checkbox.click();
                            }
                        } else if (++attempts >= maxRetries) {
                            clearInterval(intervalID);
                            createNotification(`"${list}" was not found, please edit the script to update the variable "lists" with your own names.`);
                            newTab.close();
                        }
                    }, tryInterval);
                });
            });
        });
    }

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