Twitter Scroller & Like Clicker

Scroll e like automation para Twitter (PageDown e inicia no primeiro post sem like)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Twitter Scroller & Like Clicker
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Scroll e like automation para Twitter (PageDown e inicia no primeiro post sem like)
// @author       luascfl
// @match        https://x.com/*
// @icon         https://www.google.com/s2/favicons?domain=twitter.com
// @home         https://github.com/luascfl/x-auto-scroll-and-like
// @supportURL   https://github.com/luascfl/x-auto-scroll-and-like/issues
// @license      MIT
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    // Configurações
    const SETTINGS = {
        scrollSpeed: 1000,           // intervalo de rolagem (ms)
        likeDelay: 500,
        maxReloads: 200,
        initialScrollDelay: 2000     // delay para rolar até o primeiro tweet sem like (ms)
    };
    let isActive = {
        scroll: false,
        like: false
    };

    // Função para criar botões na barra de navegação
    function createNavButton(text, svgPath, clickHandler) {
        const button = document.createElement('a');
        button.href = '#';
        button.className = 'css-175oi2r r-6koalj r-eqz5dr r-16y2uox r-1habvwh r-13qz1uu r-1mkv55d r-1ny4l3l r-1loqt21';
        button.innerHTML = `
            <div class="css-175oi2r r-sdzlij r-dnmrzs r-1awozwy r-18u37iz r-1777fci r-xyw6el r-o7ynqc r-6416eg">
                <div class="css-175oi2r">
                    <svg viewBox="0 0 24 24" class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-1nao33i r-lwhw9o r-cnnz9e">
                        <path d="${svgPath}"></path>
                    </svg>
                    <span class="css-1qaijid r-bcqeeo r-qvutc0 r-poiln3" style="color: inherit;">${text}</span>
                </div>
            </div>
        `;
        button.addEventListener('click', (e) => {
            e.preventDefault();
            clickHandler();
        });
        return button;
    }

    // Adiciona os controles (botões) à barra de navegação do Twitter
    function addControlsToNav() {
        const moreButton = document.querySelector('[data-testid="AppTabBar_More_Menu"]');
        if (!moreButton) return;
        const parent = moreButton.parentNode;
        if (!parent) return;

        // Botão de Auto Scroll
        const scrollButton = createNavButton(
            'Auto Scroll',
            'M12 4.656l8.72 8.72c.293.293.768.293 1.06 0s.294-.768 0-1.06l-9.25-9.25c-.292-.294-.767-.294-1.06 0l-9.25 9.25c-.146.145-.22.337-.22.53s.073.383.22.53c.293.292.768.292 1.06 0L12 4.656z',
            () => {
                isActive.scroll = !isActive.scroll;
                scrollButton.style.color = isActive.scroll ? 'rgb(29, 155, 240)' : '';
            }
        );

        // Botão de Auto Like – ao ativá-lo, rola para o primeiro tweet sem like
        const likeButton = createNavButton(
            'Auto Like',
            'M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12z',
            () => {
                isActive.like = !isActive.like;
                likeButton.style.color = isActive.like ? 'rgb(249, 24, 128)' : '';
                if (isActive.like) {
                    scrollToFirstUnlikedTweet();
                }
            }
        );

        // Insere os botões antes do botão "Mais"
        parent.insertBefore(likeButton, moreButton);
        parent.insertBefore(scrollButton, moreButton);
    }

    // Função que rola para o primeiro tweet que não foi curtido
    function scrollToFirstUnlikedTweet(attempt = 0) {
        const tweets = document.querySelectorAll('[data-testid="tweet"]');
        for (const tweet of tweets) {
            const likeButton = tweet.querySelector('[data-testid="like"]');
            if (likeButton && !likeButton.querySelector('svg[aria-label="Curtido"]')) {
                tweet.scrollIntoView({ behavior: 'smooth', block: 'center' });
                console.log('Rolando para o primeiro tweet sem like');
                return;
            }
        }
        // Se não encontrar, tenta novamente (até 5 vezes)
        if (attempt < 5) {
            console.log('Tweet sem like não encontrado, tentando novamente...');
            setTimeout(() => scrollToFirstUnlikedTweet(attempt + 1), 2000);
        } else {
            console.log('Nenhum tweet sem like encontrado após várias tentativas.');
        }
    }

    // Lógica de scroll (simula a tecla PageDown)
    function autoScroll() {
        window.scrollBy({
            top: window.innerHeight,
            left: 0,
            behavior: 'smooth'
        });
        console.log('PageDown executado');
    }

    // Lógica de likes – clica nos botões de like que ainda não foram ativados
    function autoLike() {
        const likeButtons = document.querySelectorAll('[data-testid="like"]');
        likeButtons.forEach(btn => {
            if (!btn.querySelector('svg[aria-label="Curtido"]')) {
                setTimeout(() => btn.click(), Math.random() * SETTINGS.likeDelay);
                console.log('Tweet curtido');
            }
        });
        if (likeButtons.length === 0) {
            console.log('Nenhum botão de like encontrado');
        }
    }

    // Loop principal que alterna as funções de scroll e like
    let reloadCount = 0;
    setInterval(() => {
        if (isActive.scroll) {
            autoScroll();
            reloadCount++;
            if (reloadCount > SETTINGS.maxReloads) {
                window.location.reload();
                reloadCount = 0;
            }
        }
        if (isActive.like) {
            autoLike();
        }
    }, SETTINGS.scrollSpeed);

    // Inicialização
    const init = () => {
        addControlsToNav();
        // Observador para garantir que os botões sejam adicionados caso a navegação seja carregada dinamicamente
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1 && node.querySelector('[data-testid="AppTabBar_More_Menu"]')) {
                        addControlsToNav();
                    }
                });
            });
        });
        observer.observe(document.body, { childList: true, subtree: true });
        // Após um pequeno delay, rola até o primeiro tweet sem like
        setTimeout(() => {
            scrollToFirstUnlikedTweet();
        }, SETTINGS.initialScrollDelay);
    };

    // Aguarda o carregamento completo da página
    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }
})();