Twitter/X 增强版阅读跟踪器

自动跟踪并保存您在 Twitter/X 上的最后阅读位置,允许在刷新或导航后无缝恢复。

目前为 2024-11-25 提交的版本。查看 最新版本

// ==UserScript==
// @name         Twitter/X Enhanced Reading Tracker
// @name:de      Twitter/X Erweiterter Lesefortschritt-Tracker
// @name:fr      Twitter/X Suivi Amélioré de la Lecture
// @name:es      Twitter/X Rastreador Mejorado de Lectura
// @name:it      Twitter/X Tracker Avanzato di Lettura
// @name:pt      Twitter/X Rastreador Avançado de Leitura
// @name:ru      Twitter/X Улучшенный Трекер Прочтения
// @name:zh-CN   Twitter/X 增强版阅读跟踪器
// @name:ja      Twitter/X 強化型読書トラッカー
// @name:ko      Twitter/X 향상된 읽기 추적기
// @name:hi      Twitter/X उन्नत पठन ट्रैकर
// @name:ar      Twitter/X متتبع القراءة المحسن
// @description  Automatically tracks and saves your last reading position on Twitter/X, allowing seamless resumption after refreshing or navigating away.
// @description:de  Verfolgt und speichert automatisch Ihren letzten Lesefortschritt auf Twitter/X, sodass Sie nach einem Refresh oder Verlassen der Seite nahtlos fortfahren können.
// @description:fr  Suit et enregistre automatiquement votre dernière position de lecture sur Twitter/X, permettant une reprise facile après un rafraîchissement ou un changement de page.
// @description:es  Realiza un seguimiento y guarda automáticamente tu última posición de lectura en Twitter/X, permitiendo continuar sin problemas después de actualizar o cambiar de página.
// @description:it  Tiene traccia e salva automaticamente la tua ultima posizione di lettura su Twitter/X, consentendo una ripresa fluida dopo il refresh o la navigazione altrove.
// @description:pt  Acompanha e salva automaticamente sua última posição de leitura no Twitter/X, permitindo retomar sem interrupções após atualizar ou navegar para outro lugar.
// @description:ru  Автоматически отслеживает и сохраняет вашу последнюю позицию чтения в Twitter/X, позволяя беспрепятственно продолжить чтение после обновления или перехода на другую страницу.
// @description:zh-CN  自动跟踪并保存您在 Twitter/X 上的最后阅读位置,允许在刷新或导航后无缝恢复。
// @description:ja  Twitter/X での最後の読書位置を自動的に追跡して保存し、更新やページ遷移後にシームレスに再開できるようにします。
// @description:ko  Twitter/X에서 마지막 읽기 위치를 자동으로 추적하고 저장하여 새로 고침하거나 다른 페이지로 이동한 후에도 원활하게 이어갈 수 있습니다.
// @description:hi  Twitter/X पर आपके अंतिम पढ़ने की स्थिति को स्वचालित रूप से ट्रैक और सहेजता है, जिससे ताज़ा करने या दूसरी जगह नेविगेट करने के बाद भी आसानी से फिर से शुरू किया जा सके।
// @description:ar  يتتبع ويحفظ تلقائيًا آخر موضع قراءة لك على Twitter/X، مما يسمح بالاستئناف بسلاسة بعد التحديث أو التنقل بعيدًا。
// @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.11.25
// @license            MIT
// @match              https://x.com/home
// @grant              GM_setValue
// @grant              GM_getValue
// @namespace http://tampermonkey.net/
// ==/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 (isNearTopOfPage() && !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 (isNearTopOfPage() && !isAutomationActive) {
                activateAutomation();
            } else if (window.scrollY > 3 && isAutomationActive) {
                deactivateAutomation();
            }
        });
    }

    function saveTopPostData() {
        const posts = Array.from(document.querySelectorAll("article")); // Alle sichtbaren Beiträge
        if (posts.length === 0) {
            console.log("Keine Beiträge sichtbar. Daten können nicht gespeichert werden.");
            return;
        }

        for (const post of posts) {
            const postTimestamp = getPostTimestamp(post);

            if (!postTimestamp) {
                console.log("Beitrag hat keinen gültigen Timestamp. Überspringe...");
                continue;
            }

            const savedTimestamp = savedTopPostData?.timestamp;

            // Wenn kein gespeicherter Timestamp existiert oder der neue aktueller ist, speichere ihn
            if (!savedTimestamp || new Date(postTimestamp) > new Date(savedTimestamp)) {
                savedTopPostData = {
                    timestamp: postTimestamp,
                    authorHandler: getPostAuthorHandler(post),
                };
                saveData(savedTopPostData); // Speichere die neuen Daten
                console.log(`Neuer Beitrag gespeichert: Handler: ${savedTopPostData.authorHandler}, Timestamp: ${savedTopPostData.timestamp}`);
                return; // Beende die Schleife, wenn gespeichert wurde
            } else {
                console.log(`Beitrag mit Timestamp ${postTimestamp} ist älter als gespeicherter Timestamp ${savedTimestamp}. Prüfe nächsten Beitrag...`);
            }
        }

        console.log("Kein neuer Beitrag gefunden. Keine Daten gespeichert.");
    }

    function waitForNewPostsToLoad(callback) {
        const interval = setInterval(() => {
            const posts = document.querySelectorAll("article");
            if (posts.length > 0) {
                console.log("Neue Beiträge geladen.");
                clearInterval(interval);
                callback();
            } else {
                console.log("Warte auf das Laden neuer Beiträge...");
            }
        }, 500);
    }

    function isNearTopOfPage() {
        return window.scrollY <= 3;
    }

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

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

    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. Warte auf vollständiges Laden...");
                waitForPostToLoad(matchedPost, () => {
                    console.log("Gespeicherter Beitrag vollständig geladen. Scrollen...");
                    scrollToPost(matchedPost, "center");
                });
            } else if (!isAtBottomOfPage()) {
                console.log("Scrollen nach unten, um mehr Beiträge zu laden...");
                scrollWithLazyLoading();
            } else {
                console.log("Gespeicherter Beitrag konnte nicht gefunden werden. Weitere Beiträge werden geladen...");
            }
        }, 1000);
    }

    function waitForPostToLoad(post, callback) {
        const interval = setInterval(() => {
            if (isPostFullyLoaded(post)) {
                clearInterval(interval);
                callback();
            } else {
                console.log("Warte darauf, dass der Beitrag vollständig geladen wird...");
            }
        }, 500);
    }

    function isPostFullyLoaded(post) {
        const timeElement = post.querySelector("time");
        const isTimeLoaded = timeElement && timeElement.getAttribute("datetime");

        const authorElement = post.querySelector(".css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3");
        const isAuthorLoaded = authorElement && authorElement.textContent.trim();

        const images = Array.from(post.querySelectorAll("img"));
        const areImagesLoaded = images.every(img => img.complete && img.naturalWidth > 0);

        return isTimeLoaded && isAuthorLoaded && areImagesLoaded;
    }

    function scrollWithLazyLoading() {
        const interval = setInterval(() => {
            const posts = Array.from(document.querySelectorAll("article"));
            const lastPost = posts[posts.length - 1];

            if (!lastPost || !isPostFullyLoaded(lastPost)) {
                console.log("Warte darauf, dass der letzte Beitrag vollständig geladen wird...");
                return;
            }

            console.log("Letzter Beitrag vollständig geladen. Scrolle weiter...");
            clearInterval(interval);
            window.scrollBy({ top: 500, behavior: "smooth" });
        }, 1000);
    }

    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 isAtBottomOfPage() {
        return window.innerHeight + window.scrollY >= document.body.scrollHeight - 1;
    }

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

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

    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 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 &&
                postAuthorHandler &&
                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.getFullYear() === savedDate.getFullYear() &&
            postDate.getMonth() === savedDate.getMonth() &&
            postDate.getDate() === savedDate.getDate() &&
            postDate.getHours() === savedDate.getHours() &&
            postDate.getMinutes() === savedDate.getMinutes()
        );
    }
})();

QingJ © 2025

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