X.com Timeline Auto-Load with Uninterrupted Reading

Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.

目前為 2024-11-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name               X.com Timeline Auto-Load with Uninterrupted Reading
// @name:de            X.com Timeline Auto-Load mit unterbrechungsfreiem Lesen
// @name:fr            X.com Timeline Auto-Load avec lecture ininterrompue
// @name:es            Carga automática de la línea de tiempo de X.com con lectura sin interrupciones
// @name:it            Caricamento automatico della timeline di X.com con lettura ininterrotta
// @name:zh            X.com 时间线自动加载,无缝阅读
// @name:ja            X.com タイムライン自動読み込みと中断のない読書
// @namespace          http://tampermonkey.net/
// @description        Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.
// @description:de     Lädt automatisch neue Beiträge auf X.com, ohne die Leseposition zu verlieren. Setzt eine virtuelle Markierung am letzten sichtbaren Handler (z. B. @Benutzername) vor dem Laden neuer Beiträge und stellt die Ansicht zu dieser Markierung wieder her.
// @description:fr     Charge automatiquement les nouveaux messages sur X.com tout en conservant la position de lecture. Place un marqueur virtuel au dernier handle visible (par exemple, @nomutilisateur) avant de charger les nouveaux messages et restaure la vue à ce marqueur.
// @description:es     Carga automáticamente nuevos posts en X.com mientras mantiene la posición de lectura intacta. Coloca un marcador virtual en el último manejador visible (por ejemplo, @nombredeusuario) antes de cargar nuevos posts y restaura la vista a ese marcador.
// @description:it     Carica automaticamente nuovi post su X.com mantenendo intatta la posizione di lettura. Imposta un segnalibro virtuale sull'ultimo handle visibile (es. @nomeutente) prima di caricare nuovi post e ripristina la vista su quel segnalibro.
// @description:zh     在X.com上自动加载新帖子,同时保持阅读位置不变。在加载新帖子之前,在最后一个可见的处理器(例如@用户名)处设置一个虚拟标记,并将视图恢复到该标记。
// @description:ja     X.comで新しい投稿を自動的に読み込み、読書位置をそのまま保持します。新しい投稿を読み込む前に、最後に見えるハンドル(例:@ユーザー名)に仮想マーカーを設定し、このマーカーにビューを復元します。
// @author             Copiis
// @version            2024.12.22-7
// @license            MIT
// @match              https://x.com/home
// @icon               https://cdn-icons-png.flaticon.com/128/14417/14417460.png
// @grant              GM_setValue
// @grant              GM_getValue
// ==/UserScript==

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

    function isHomePage() {
        return window.location.pathname === "/home";
    }

    // Reagiere auf Tab-Wechsel
    document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === "visible" && isHomePage()) {
            console.log("Tab wurde wieder aktiv. Überprüfe Automatik...");
            if (!isAutomationActive && window.scrollY === 0) {
                activateAutomation();
            }
        }
    });

    window.onload = () => {
        if (!isHomePage()) {
            console.log("Nicht auf der /home-Seite. Automatik deaktiviert.");
            return;
        }
        console.log("Auf der /home-Seite. 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 (!isHomePage()) {
                console.log("Nicht auf der /home-Seite. Beobachter deaktiviert.");
                observer.disconnect();
                return;
            }

            if (isAtTopOfPage() && !isAutomationActive) {
                activateAutomation();
            }

            if (isAutomationActive) {
                const newPostsButton = getNewPostsButton();
                if (newPostsButton) {
                    console.log("Neue Beiträge erkannt. Klicke auf den 'Neue Posts anzeigen'-Button...");
                    clickNewPostsButton(newPostsButton);
                }
            }
        });

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

        window.addEventListener("scroll", () => {
            if (!isHomePage()) {
                console.log("Nicht auf der /home-Seite. Scroll-Listener deaktiviert.");
                return;
            }

            if (isAutoScrolling) return;

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

    function clickNewPostsButton(button) {
        const maxAttempts = 5;
        let attempts = 0;

        const attemptClick = () => {
            if (attempts >= maxAttempts) {
                console.log("Maximale Versuche, neue Beiträge zu laden, erreicht.");
                return;
            }

            button.click();
            console.log("Klick auf 'Neue Posts anzeigen'-Button ausgeführt.");

            setTimeout(() => {
                const currentPostCount = document.querySelectorAll("article").length;

                if (currentPostCount > previousPostCount) {
                    console.log("Neue Beiträge erfolgreich geladen.");
                    previousPostCount = currentPostCount;
                    scrollToSavedPost();
                } else {
                    console.log("Beiträge wurden nicht geladen. Versuche erneut...");
                    attempts++;
                    attemptClick();
                }
            }, 2000); // Wartezeit für das Laden neuer Beiträge
        };

        attemptClick();
    }

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

            const matchedPost = findPostByData(savedTopPostData);
            const oldestVisibleTimestamp = getOldestVisibleTimestamp();

            if (matchedPost) {
                clearInterval(interval);
                console.log("Gespeicherter Beitrag gefunden. Scrollen...");
                scrollToPost(matchedPost, "center");
            } else if (isTimestampOlderThanThreeHours(oldestVisibleTimestamp)) {
                clearInterval(interval);
                console.log("Ältester sichtbarer Beitrag ist mehr als 3 Stunden älter als der gespeicherte Beitrag. Scrollen gestoppt.");
            } 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("Weitere Beiträge werden geladen...");
            }
        }, 1000);
    }

    function isTimestampOlderThanThreeHours(timestamp) {
        if (!timestamp || !savedTopPostData || !savedTopPostData.timestamp) return false;

        const savedDate = new Date(savedTopPostData.timestamp);
        const visibleDate = new Date(timestamp);

        const diffInHours = (savedDate - visibleDate) / (1000 * 60 * 60);
        return diffInHours >= 3;
    }

    function getOldestVisibleTimestamp() {
        const posts = Array.from(document.querySelectorAll("article"));
        let oldestTimestamp = null;

        posts.forEach((post) => {
            const timestamp = getPostTimestamp(post);
            if (timestamp) {
                const postDate = new Date(timestamp);
                if (!oldestTimestamp || postDate < new Date(oldestTimestamp)) {
                    oldestTimestamp = timestamp;
                }
            }
        });

        return oldestTimestamp;
    }

    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 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 postTimestamp === data.timestamp && postAuthorHandler === data.authorHandler;
        });
    }

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址