X.com/Twitter Automation

Automatically loads new posts on X.com/Twitter and scrolls back to the reading position.

当前为 2024-11-23 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name               X.com/Twitter Automation
// @name:de            X.com/Twitter Automatik
// @name:es            Automatización de X.com/Twitter
// @name:fr            Automatisation de X.com/Twitter
// @name:it            Automazione di X.com/Twitter
// @name:pt            Automação do X.com/Twitter
// @name:ru            Автоматизация X.com/Twitter
// @name:zh            X.com/Twitter 自动化
// @name:ja            X.com/Twitter 自動化
// @name:ko            X.com/Twitter 자동화
// @name:hi            X.com/Twitter स्वचालन
// @name:ar            أتمتة X.com/Twitter
// @namespace          http://tampermonkey.net/
// @description        Automatically loads new posts on X.com/Twitter and scrolls back to the reading position.
// @description:de     Lädt automatisch neue Beiträge auf X.com/Twitter und scrollt zur Leseposition zurück.
// @description:es     Carga automáticamente nuevos tweets en X.com/Twitter y vuelve a la posición de lectura.
// @description:fr     Charge automatiquement de nouveaux posts sur X.com/Twitter et revient à la position de lecture.
// @description:it     Carica automaticamente nuovi post su X.com/Twitter e torna alla posizione di lettura.
// @description:pt     Carrega automaticamente novos posts no X.com/Twitter e retorna à posição de leitura.
// @description:ru     Автоматически загружает новые посты на X.com/Twitter и возвращает к позиции чтения.
// @description:zh     自动加载 X.com/Twitter 上的新帖子,并返回到阅读位置。
// @description:ja     X.com/Twitterで新しい投稿を自動的に読み込み、読書位置に戻ります.
// @description:ko     X.com/Twitter에서 새 게시물을 자동으로 로드하고 읽던 위치로 돌아갑니다.
// @description:hi     X.com/Twitter पर नए पोस्ट स्वचालित रूप से लोड करता है और पढ़ने की स्थिति पर वापस ले जाता है.
// @description:ar     يقوم بتحميل المنشورات الجديدة تلقائيًا على X.com/Twitter ويعيدك إلى موضع القراءة.
// @icon               https://cdn-icons-png.flaticon.com/128/14417/14417460.png
// @supportURL         https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE
// @author             Copiis
// @version            2024.12.23
// @license            MIT
// @match              https://x.com/home
// @grant              GM_setValue
// @grant              GM_getValue
// ==/UserScript==

(function () {
    let isAutomationActive = false;
    let isAutoScrolling = false;
    let savedTopPostData = null;

    window.onload = () => {
        console.log("Seite vollständig geladen. Initialisiere Script...");
        initializeScript();
    };

    function initializeScript() {
        loadSavedData();
        if (savedTopPostData) {
            console.log(`Gespeicherte Daten gefunden. Versuche zum gespeicherten Beitrag zu scrollen: Handler: ${savedTopPostData.authorHandler}, Timestamp: ${savedTopPostData.timestamp}`);
            scrollToSavedPost();
        } else {
            console.log("Keine gespeicherten Daten gefunden. Automatik startet erst, wenn der Benutzer manuell scrollt.");
        }

        const observer = new MutationObserver(() => {
            if (isAtTopOfPage() && !isAutomationActive) {
                activateAutomation();
            }

            if (isAutomationActive) {
                const newPostsButton = getNewPostsButton();
                if (newPostsButton) {
                    newPostsButton.click();
                    waitForNewPostsToLoad(() => scrollToSavedPost());
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });

        window.addEventListener('scroll', () => {
            if (isAutoScrolling) return;

            if (window.scrollY === 0 && !isAutomationActive) {
                activateAutomation();
            } else if (window.scrollY > 0 && isAutomationActive) {
                deactivateAutomation();
            }
        });
    }

    function scrollToSavedPost() {
        const interval = setInterval(() => {
            if (!isPageFullyLoaded()) {
                console.log("Warte auf vollständiges Laden der Seite...");
                return;
            }

            const matchedPost = findPostByData(savedTopPostData);

            if (matchedPost) {
                clearInterval(interval);
                console.log("Gespeicherter Beitrag gefunden. Scrollen...");
                scrollToPost(matchedPost, "center");
            } else if (!isAtBottomOfPage()) {
                console.log("Scrollen nach unten, um mehr Beiträge zu laden...");
                scrollToBottomWithDelay(() => {
                    console.log("Warten auf neue Beiträge nach Scrollen...");
                });
            } else {
                console.log("Gespeicherter Beitrag konnte nicht gefunden werden. Weitere Beiträge werden geladen...");
            }
        }, 1000);
    }

    function scrollToBottomWithDelay(callback) {
        window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
        setTimeout(() => {
            if (isPageFullyLoaded()) {
                callback();
            } else {
                console.log("Seite ist noch nicht vollständig geladen. Weitere Wartezeit...");
                setTimeout(callback, 2000); // Zusätzliche Wartezeit, wenn noch nicht geladen
            }
        }, 3000); // Wartezeit nach Scrollen, angepasst für langsame Ladezeiten
    }

    function activateAutomation() {
        isAutomationActive = true;
        console.log("Automatik aktiviert.");
        saveTopPostData();
    }

    function deactivateAutomation() {
        isAutomationActive = false;
        console.log("Automatik deaktiviert.");
    }

    function saveTopPostData() {
        const topPost = getTopVisiblePost();
        if (topPost) {
            savedTopPostData = {
                timestamp: getPostTimestamp(topPost),
                authorHandler: getPostAuthorHandler(topPost),
            };
            saveData(savedTopPostData);
        }
    }

    function loadSavedData() {
        const savedData = GM_getValue("topPostData", null);
        if (savedData) {
            savedTopPostData = JSON.parse(savedData);
        }
    }

    function saveData(data) {
        GM_setValue("topPostData", JSON.stringify(data));
        console.log(`Daten dauerhaft gespeichert: Handler: ${data.authorHandler}, Timestamp: ${data.timestamp}`);
    }

    function waitForNewPostsToLoad(callback) {
        const checkInterval = setInterval(() => {
            if (isPageFullyLoaded()) {
                clearInterval(checkInterval);
                callback();
            }
        }, 500);
    }

    function findPostByData(data) {
        if (!data || !data.timestamp || !data.authorHandler) return null;

        const posts = Array.from(document.querySelectorAll("article"));
        return posts.find((post) => {
            const postTimestamp = getPostTimestamp(post);
            const postAuthorHandler = getPostAuthorHandler(post);
            return isTimestampMatching(postTimestamp, data.timestamp) && postAuthorHandler === data.authorHandler;
        });
    }

    function isTimestampMatching(postTimestamp, savedTimestamp) {
        if (!postTimestamp || !savedTimestamp) return false;

        const postDate = new Date(postTimestamp);
        const savedDate = new Date(savedTimestamp);

        return (
            postDate.getHours() === savedDate.getHours() &&
            postDate.getMinutes() === savedDate.getMinutes()
        );
    }

    function getPostTimestamp(post) {
        const timeElement = post.querySelector("time");
        return timeElement?.getAttribute("datetime") || null;
    }

    function getPostAuthorHandler(post) {
        const authorElement = post.querySelector(".css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3");
        return authorElement?.textContent.trim() || null;
    }

    function getTopVisiblePost() {
        const posts = Array.from(document.querySelectorAll("article"));
        return posts.length > 0 ? posts[0] : null;
    }

    function getNewPostsButton() {
        return Array.from(document.querySelectorAll("button, span")).find((button) =>
            /neue Posts anzeigen|Post anzeigen/i.test(button.textContent.trim())
        );
    }

    function scrollToPost(post, position = "center") {
        isAutoScrolling = true;
        post.scrollIntoView({ behavior: "smooth", block: position });
        setTimeout(() => {
            isAutoScrolling = false;
        }, 1000);
    }

    function isPageFullyLoaded() {
        return document.readyState === "complete";
    }

    function isAtTopOfPage() {
        return window.scrollY === 0;
    }

    function isAtBottomOfPage() {
        return window.innerHeight + window.scrollY >= document.body.scrollHeight - 1;
    }
})();