X Timeline Sync

跟踪并同步您在 Twitter/X 上的最后阅读位置,提供手动和自动选项。完美解决在查看新帖子时不丢失当前位置的问题。

目前为 2025-02-26 提交的版本。查看 最新版本

// ==UserScript==
// @name              X Timeline Sync
// @description       Tracks and syncs your last reading position on Twitter/X, with manual and automatic options. Ideal for keeping track of new posts without losing your place.
// @description:de    Verfolgt und synchronisiert Ihre letzte Leseposition auf Twitter/X, mit manuellen und automatischen Optionen. Perfekt, um neue Beiträge im Blick zu behalten, ohne die aktuelle Position zu verlieren.
// @description:es    Rastrea y sincroniza tu última posición de lectura en Twitter/X, con opciones manuales y automáticas. Ideal para mantener el seguimiento de las publicaciones nuevas sin perder tu posición.
// @description:fr    Suit et synchronise votre dernière position de lecture sur Twitter/X, avec des options manuelles et automatiques. Idéal pour suivre les nouveaux posts sans perdre votre place actuelle.
// @description:zh-CN 跟踪并同步您在 Twitter/X 上的最后阅读位置,提供手动和自动选项。完美解决在查看新帖子时不丢失当前位置的问题。
// @description:ru    Отслеживает и синхронизирует вашу последнюю позицию чтения на Twitter/X с ручными и автоматическими опциями. Идеально подходит для просмотра новых постов без потери текущей позиции.
// @description:ja    Twitter/X での最後の読み取り位置を追跡して同期します。手動および自動オプションを提供します。新しい投稿を見逃さずに現在の位置を維持するのに最適です。
// @description:pt-BR Rastrea e sincroniza sua última posição de leitura no Twitter/X, com opções manuais e automáticas. Perfeito para acompanhar novos posts sem perder sua posição atual.
// @description:hi    Twitter/X पर आपकी अंतिम पठन स्थिति को ट्रैक और सिंक करता है, मैनुअल और स्वचालित विकल्पों के साथ। नई पोस्ट देखते समय अपनी वर्तमान स्थिति को खोए बिना इसे ट्रैक करें।
// @description:ar    يتتبع ويزامن آخر موضع قراءة لك على Twitter/X، مع خيارات يدوية وتلقائية. مثالي لتتبع المشاركات الجديدة دون فقدان موضعك الحالي.
// @description:it    Traccia e sincronizza la tua ultima posizione di lettura su Twitter/X, con opzioni manuali e automatiche. Ideale per tenere traccia dei nuovi post senza perdere la posizione attuale.
// @description:ko    Twitter/X에서 마지막 읽기 위치를 추적하고 동기화합니다. 수동 및 자동 옵션 포함. 새로운 게시물을 확인하면서 현재 위치를 잃지 않도록 이상적입니다.
// @icon              https://x.com/favicon.ico
// @namespace         http://tampermonkey.net/
// @version           2025-02-26.2
// @author            Copiis
// @license           MIT
// @match             https://x.com/home
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_download
// ==/UserScript==

(function() {
    'use strict';

    let lastReadPost = null;
    let isAutoScrolling = false;
    let isSearching = false;
    let isTabFocused = true;
    let downloadTriggered = false;
    let isPostLoading = false;
    let hasScrolledAfterLoad = false;
    let saveToDownloadFolder = GM_getValue("saveToDownloadFolder", true);

    const translations = {
        en: {
            scriptDisabled: "🚫 Script disabled: Not on the home page.",
            pageLoaded: "🚀 Page fully loaded. Initializing script...",
            tabBlur: "🌐 Tab lost focus.",
            downloadStart: "📥 Starting download of last read position...",
            alreadyDownloaded: "🗂️ Position already downloaded.",
            tabFocused: "🟢 Tab refocused.",
            saveSuccess: "✅ Last read position saved:",
            saveFail: "⚠️ No valid position to save.",
            noPostFound: "❌ No top visible post found.",
            highlightSuccess: "✅ Post highlighted successfully.",
            searchStart: "🔍 Refined search started...",
            searchCancel: "⏹️ Search manually canceled.",
            contentLoadWait: "⌛ Waiting for content to load...",
            toggleSaveOn: "💾 Save to download folder enabled",
            toggleSaveOff: "🚫 Save to download folder disabled"
        },
        de: {
            scriptDisabled: "🚫 Skript deaktiviert: Nicht auf der Home-Seite.",
            pageLoaded: "🚀 Seite vollständig geladen. Initialisiere Skript...",
            tabBlur: "🌐 Tab hat den Fokus verloren.",
            downloadStart: "📥 Starte Download der letzten Leseposition...",
            alreadyDownloaded: "🗂️ Leseposition bereits im Download-Ordner vorhanden.",
            tabFocused: "🟢 Tab wieder fokussiert.",
            saveSuccess: "✅ Leseposition erfolgreich gespeichert:",
            saveFail: "⚠️ Keine gültige Leseposition zum Speichern.",
            noPostFound: "❌ Kein oberster sichtbarer Beitrag gefunden.",
            highlightSuccess: "✅ Beitrag erfolgreich hervorgehoben.",
            searchStart: "🔍 Verfeinerte Suche gestartet...",
            searchCancel: "⏹️ Suche manuell abgebrochen.",
            contentLoadWait: "⌛ Warte darauf, dass der Inhalt geladen wird...",
            toggleSaveOn: "💾 Speichern im Download-Ordner aktiviert",
            toggleSaveOff: "🚫 Speichern im Download-Ordner deaktiviert"
        }
    };

    const userLang = navigator.language.split('-')[0];
    const t = (key) => translations[userLang]?.[key] || translations.en[key];

    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    const observer = new IntersectionObserver(
        entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const postData = {
                        timestamp: getPostTimestamp(entry.target),
                        authorHandler: getPostAuthorHandler(entry.target)
                    };
                }
            });
        },
        { threshold: 0.1 }
    );

    function observeVisiblePosts() {
        const articles = document.querySelectorAll('article');
        const viewportHeight = window.innerHeight;
        const buffer = viewportHeight * 2;

        for (let article of articles) {
            const rect = article.getBoundingClientRect();
            if (rect.top < buffer && rect.bottom > -buffer) {
                observer.observe(article);
            } else {
                observer.unobserve(article);
            }
        }
    }

    function loadNewestLastReadPost() {
        const data = GM_getValue("lastReadPost", null);
        if (data) {
            lastReadPost = JSON.parse(data);
            console.log(t("saveSuccess"), lastReadPost);
        } else {
            console.warn(t("saveFail"));
        }
    }

    function loadLastReadPostFromFile() {
        loadNewestLastReadPost();
    }

    function saveLastReadPostToFile() {
        if (lastReadPost && lastReadPost.timestamp && lastReadPost.authorHandler) {
            GM_setValue("lastReadPost", JSON.stringify(lastReadPost));
            console.log(t("saveSuccess"), lastReadPost);
        } else {
            console.warn(t("saveFail"));
        }
    }

    function downloadLastReadPost() {
        if (!saveToDownloadFolder) {
            console.log("Saving to download folder is disabled.");
            return;
        }
        if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
            console.warn(t("saveFail"));
            return;
        }
        try {
            const data = JSON.stringify(lastReadPost, null, 2);
            const sanitizedHandler = lastReadPost.authorHandler.replace(/[^a-zA-Z0-9-_]/g, "");
            const timestamp = new Date(lastReadPost.timestamp).toISOString().replace(/[:.-]/g, "_");
            const fileName = `${sanitizedHandler}_${timestamp}.json`;

            GM_download({
                url: `data:application/json;charset=utf-8,${encodeURIComponent(data)}`,
                name: fileName,
                onload: () => console.log(`${t("saveSuccess")} ${fileName}`),
                onerror: (err) => console.error("❌ Error downloading:", err),
            });
        } catch (error) {
            console.error("❌ Download error:", error);
        }
    }

    function markTopVisiblePost(save = true) {
        const topPost = getTopVisiblePost();
        if (!topPost) {
            console.log(t("noPostFound"));
            return;
        }

        const postTimestamp = getPostTimestamp(topPost);
        const authorHandler = getPostAuthorHandler(topPost);

        if (postTimestamp && authorHandler) {
            if (save && (!lastReadPost || new Date(postTimestamp) > new Date(lastReadPost.timestamp))) {
                lastReadPost = { timestamp: postTimestamp, authorHandler };
                saveLastReadPostToFile();
            }
        }
    }

    function getTopVisiblePost() {
        return Array.from(document.querySelectorAll("article")).find(post => {
            const rect = post.getBoundingClientRect();
            return rect.top >= 0 && rect.bottom > 0;
        });
    }

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

    function getPostAuthorHandler(post) {
        const handlerElement = post.querySelector('[role="link"][href*="/"]');
        return handlerElement ? handlerElement.getAttribute("href").slice(1) : null;
    }

    function startRefinedSearchForLastReadPost() {
        if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler || isPostLoading) return;

        console.log(t("searchStart"));
        const popup = createSearchPopup();
        let direction = 1;
        let scrollAmount = 200;
        let scrollSpeed = 1000;
        let scrollInterval = 200;
        let lastComparison = null;
        let initialAdjusted = false;
        let lastScrollDirection = null;
        let lastScrollY = 0;
        let jumpMultiplier = 1;

        function handleSpaceKey(event) {
            if (event.code === "Space") {
                console.log(t("searchCancel"));
                isSearching = false;
                popup.remove();
                window.removeEventListener("keydown", handleSpaceKey);
            }
        }

        window.addEventListener("keydown", handleSpaceKey);

        function adjustScrollParameters(lastReadTime) {
            const visiblePosts = getVisiblePosts();
            if (visiblePosts.length === 0) return { amount: 200, speed: 1000 };

            const nearestPost = visiblePosts.reduce((closest, post) => {
                const postTime = new Date(post.timestamp);
                const diffCurrent = Math.abs(postTime - lastReadTime);
                const diffClosest = Math.abs(new Date(closest.timestamp) - lastReadTime);
                return diffCurrent < diffClosest ? post : closest;
            });

            const nearestTime = new Date(nearestPost.timestamp);
            const timeDifference = Math.abs(lastReadTime - nearestTime) / (1000 * 60);

            let newScrollAmount = 200;
            let newScrollSpeed = 1000;

            if (timeDifference < 5) {
                newScrollAmount = 50;
                newScrollSpeed = 500;
                jumpMultiplier = 1;
            } else if (timeDifference < 30) {
                newScrollAmount = 100;
                newScrollSpeed = 1000;
                jumpMultiplier = 1;
            } else if (timeDifference < 60) {
                newScrollAmount = 200;
                newScrollSpeed = 1500;
                jumpMultiplier = 1.5;
            } else if (timeDifference < 1440) {
                newScrollAmount = 500;
                newScrollSpeed = 2000;
                jumpMultiplier = 2;
            } else {
                newScrollAmount = 1000;
                newScrollSpeed = 3000;
                jumpMultiplier = 3;
            }

            newScrollAmount = Math.max(50, Math.min(newScrollAmount * jumpMultiplier, window.innerHeight * 2));
            return { amount: newScrollAmount, speed: newScrollSpeed };
        }

        async function search() {
            if (!isSearching) {
                popup.remove();
                return;
            }

            const visiblePosts = getVisiblePosts();
            if (visiblePosts.length === 0 && !isPostLoading) {
                setTimeout(search, scrollInterval);
                return;
            }

            if (!initialAdjusted) {
                const comparison = compareVisiblePostsToLastReadPost(visiblePosts);
                adjustInitialScroll(comparison);
                initialAdjusted = true;
            }

            const comparison = compareVisiblePostsToLastReadPost(visiblePosts);
            const lastReadTime = new Date(lastReadPost.timestamp);
            let nearestVisiblePostTime = null;

            if (visiblePosts.length > 0) {
                nearestVisiblePostTime = new Date(visiblePosts[0].timestamp);
            }

            const { amount, speed } = adjustScrollParameters(lastReadTime);
            scrollAmount = amount;
            scrollSpeed = speed;
            scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));

            const distanceToBottom = document.documentElement.scrollHeight - (window.scrollY + window.innerHeight);
            if (distanceToBottom < window.innerHeight) {
                scrollAmount = Math.max(50, scrollAmount / 2);
                scrollSpeed = Math.max(500, scrollSpeed / 2);
                scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));
            }

            if (comparison === "match") {
                const matchedPost = findPostByData(lastReadPost);
                if (matchedPost) {
                    scrollToPostWithHighlight(matchedPost);
                    isSearching = false;
                    popup.remove();
                    window.removeEventListener("keydown", handleSpaceKey);
                    setTimeout(() => {
                        isAutoScrolling = false;
                    }, 1000);
                    return;
                }
            } else if (comparison === "older") {
                direction = -1;
                if (lastComparison === "newer") jumpMultiplier *= 0.5;
            } else if (comparison === "newer") {
                direction = 1;
                if (lastComparison === "older") jumpMultiplier *= 0.5;
            } else if (comparison === "mixed") {
                scrollAmount = Math.max(50, scrollAmount / 2);
                scrollSpeed = Math.max(500, scrollSpeed / 2);
                scrollInterval = Math.max(30, 1000 / (scrollSpeed / scrollAmount));
            }

            if (window.scrollY === 0 && direction === -1) {
                direction = 1;
                jumpMultiplier *= 2;
            } else if (distanceToBottom < 50 && direction === 1) {
                direction = -1;
                jumpMultiplier *= 2;
            }

            lastComparison = comparison;
            lastScrollDirection = direction;
            lastScrollY = window.scrollY;

            console.log(`Scroll-Richtung: ${direction}, Betrag: ${scrollAmount}px, Geschwindigkeit: ${scrollSpeed}px/s, Intervall: ${scrollInterval}ms, Position: ${window.scrollY}, Zeitdifferenz: ${nearestVisiblePostTime ? Math.abs(lastReadTime - nearestVisiblePostTime) : 'N/A'}`);

            requestAnimationFrame(() => {
                window.scrollBy(0, direction * scrollAmount);
                setTimeout(search, scrollInterval);
            });
        }

        isSearching = true;
        search();
    }

    function adjustInitialScroll(comparison) {
        const initialScrollAmount = 2000;
        if (comparison === "older") {
            for (let i = 0; i < 3; i++) {
                window.scrollBy(0, -initialScrollAmount / 3);
            }
        } else if (comparison === "newer") {
            for (let i = 0; i < 3; i++) {
                window.scrollBy(0, initialScrollAmount / 3);
            }
        }
    }

    function createSearchPopup() {
        const popup = document.createElement("div");
        popup.style.cssText = `position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.9); color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 14px; box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); z-index: 10000;`;
        popup.textContent = "🔍 Refined search in progress... Press SPACE to cancel.";
        document.body.appendChild(popup);
        return popup;
    }

    function compareVisiblePostsToLastReadPost(posts) {
        const validPosts = posts.filter(post => post.timestamp && post.authorHandler);
        if (validPosts.length === 0) return null;

        const lastReadTime = new Date(lastReadPost.timestamp);

        if (validPosts.some(post => post.timestamp === lastReadPost.timestamp && post.authorHandler === lastReadPost.authorHandler)) {
            return "match";
        } else if (validPosts.every(post => new Date(post.timestamp) < lastReadTime)) {
            return "older";
        } else if (validPosts.every(post => new Date(post.timestamp) > lastReadTime)) {
            return "newer";
        } else {
            return "mixed";
        }
    }

    function scrollToPostWithHighlight(post) {
        if (!post) return;
        isAutoScrolling = true;

        post.style.cssText = `outline: none; box-shadow: 0 0 30px 10px rgba(255, 223, 0, 1); background-color: rgba(255, 223, 0, 0.3); border-radius: 12px; transform: scale(1.1); transition: all 0.3s ease;`;

        const postRect = post.getBoundingClientRect();
        const viewportHeight = window.innerHeight;
        const scrollY = window.scrollY;
        const scrollTo = scrollY + postRect.top - viewportHeight / 2 + postRect.height / 2;

        window.scrollTo({
            top: scrollTo,
            behavior: 'smooth'
        });

        setTimeout(() => {
            let scrollHandler = function() {
                post.style.cssText = "";
                window.removeEventListener('scroll', scrollHandler);
                console.log(t("highlightSuccess"));
            };
            window.addEventListener('scroll', scrollHandler);
        }, 500);
    }

    function getVisiblePosts() {
        return Array.from(document.querySelectorAll("article")).map(post => ({
            element: post,
            timestamp: getPostTimestamp(post),
            authorHandler: getPostAuthorHandler(post)
        })).filter(post => post.timestamp && post.authorHandler);
    }

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

    function createButtons() {
        const container = document.createElement("div");
        container.style.cssText = `position: fixed; top: 50%; left: 3px; transform: translateY(-50%); display: flex; flex-direction: column; gap: 3px; z-index: 10000;`;

        let toggleButton;

        const buttons = [
            { icon: "📂", title: "Load saved reading position", onClick: importLastReadPost },
            { icon: "🔍", title: "Start manual search", onClick: startRefinedSearchForLastReadPost },
            {
                icon: saveToDownloadFolder ? "💾" : "🚫",
                title: "Toggle save to download folder",
                onClick: function() {
                    saveToDownloadFolder = !saveToDownloadFolder;
                    GM_setValue("saveToDownloadFolder", saveToDownloadFolder);
                    toggleButton.style.background = saveToDownloadFolder ? "rgba(0, 255, 0, 0.9)" : "rgba(255, 0, 0, 0.9)";
                    toggleButton.textContent = saveToDownloadFolder ? "💾" : "🚫";
                    console.log(saveToDownloadFolder ? t("toggleSaveOn") : t("toggleSaveOff"));
                }
            }
        ];

        buttons.forEach(({ icon, title, onClick }) => {
            const button = document.createElement("div");
            button.style.cssText = `width: 36px; height: 36px; background: ${icon === "💾" || icon === "🚫" ? (saveToDownloadFolder ? "rgba(0, 255, 0, 0.9)" : "rgba(255, 0, 0, 0.9)") : "rgba(0, 0, 0, 0.9)"}; color: #fff; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; font-size: 18px; box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5); transition: all 0.2s ease;`;
            button.title = title;
            button.textContent = icon;

            button.addEventListener("click", function() {
                button.style.boxShadow = "inset 0 0 20px rgba(255, 255, 255, 0.8)";
                button.style.transform = "scale(0.9)";
                setTimeout(() => {
                    button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
                    button.style.transform = "scale(1)";
                    onClick();
                }, 300);
            });

            ["mouseenter", "mouseleave"].forEach(event =>
                button.addEventListener(event, () => button.style.transform = event === "mouseenter" ? "scale(1.1)" : "scale(1)")
            );

            if (icon === "💾" || icon === "🚫") {
                toggleButton = button;
            }

            container.appendChild(button);
        });

        document.body.appendChild(container);
    }

    function importLastReadPost() {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = "application/json";
        input.style.display = "none";

        input.addEventListener("change", (event) => {
            const file = event.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = () => {
                    try {
                        const importedData = JSON.parse(reader.result);
                        if (importedData.timestamp && importedData.authorHandler) {
                            lastReadPost = importedData;
                            saveLastReadPostToFile();
                            startRefinedSearchForLastReadPost();
                        } else {
                            throw new Error("Invalid reading position");
                        }
                    } catch (error) {
                        console.error("❌ Error importing reading position:", error);
                    }
                };
                reader.readAsText(file);
            }
        });

        document.body.appendChild(input);
        input.click();
        document.body.removeChild(input);
    }

    function observeForNewPosts() {
        const targetNode = document.querySelector('div[aria-label="Timeline: Your Home Timeline"]') || document.body;

        const mutationObserver = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    checkForNewPosts();
                }
            }
        });

        mutationObserver.observe(targetNode, { childList: true, subtree: true });
    }

    function getNewPostsIndicator() {
        const indicator = document.querySelector('div[aria-label*="undefined"]') ||
                         document.querySelector('div[aria-label*="new"]');
        console.log(`[Debug] Neuer Beitragsindikator gefunden: ${indicator ? 'Ja' : 'Nein'}`);
        return indicator;
    }

    function clickNewPostsIndicator(indicator, preservedScrollY) {
        if (indicator && indicator.offsetParent !== null) {
            console.log("Versuche, auf den neuen Beitrag-Indikator zu klicken.");
            indicator.click();
            console.log("Klick auf den neuen Beitrag-Indikator war erfolgreich.");

            const timelineNode = document.querySelector('div[aria-label="Timeline: Your Home Timeline"]') || document.body;
            let mutationTimeout;

            const observer = new MutationObserver(() => {
                if (window.scrollY !== preservedScrollY) {
                    window.scrollTo(0, preservedScrollY);
                    console.log(`[Debug] Scroll-Position korrigiert auf: ${preservedScrollY}`);
                }

                clearTimeout(mutationTimeout);
                mutationTimeout = setTimeout(() => {
                    observer.disconnect();
                    console.log(t("newPostsLoaded"));
                }, 3000);
            });

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

            setTimeout(() => {
                window.scrollTo(0, preservedScrollY);
            }, 100);
        } else {
            console.log("Kein klickbarer Indikator gefunden.");
        }
    }

    async function checkForNewPosts() {
        if (window.scrollY <= 10) {
            const newPostsIndicator = getNewPostsIndicator();
            if (newPostsIndicator) {
                const preservedScrollY = window.scrollY;
                console.log("🆕 Neue Beiträge in der Nähe des oberen Randes erkannt. Klicke auf Indikator...");
                clickNewPostsIndicator(newPostsIndicator, preservedScrollY);
                hasScrolledAfterLoad = false;

                try {
                    console.log(t("contentLoadWait"));
                    await waitForTimelineLoad(5000);
                    console.log("Inhalt geladen, starte verfeinerte Suche...");
                    startRefinedSearchForLastReadPost();
                } catch (error) {
                    console.error("❌ Fehler beim Warten auf das Laden der Timeline:", error.message);
                    console.log("[Debug] Starte Suche trotz Fehler.");
                    startRefinedSearchForLastReadPost();
                }
            } else {
                console.log("[Debug] Kein neuer Beitragsindikator gefunden.");
            }
        } else {
            console.log("[Debug] Scroll-Position nicht oben, keine neuen Beiträge geprüft.");
        }
    }

    async function waitForTimelineLoad(maxWaitTime = 3000) {
        const startTime = Date.now();
        return new Promise((resolve) => {
            const checkInterval = setInterval(() => {
                const loadingSpinner = document.querySelector('div[role="progressbar"]') ||
                                       document.querySelector('div.css-175oi2r.r-1pi2tsx.r-1wtj0ep.r-ymttw5.r-1f1sjgu');
                const timeElapsed = Date.now() - startTime;

                if (!loadingSpinner) {
                    console.log("[Debug] Ladeindikator verschwunden, resolve.");
                    clearInterval(checkInterval);
                    resolve(true);
                } else if (timeElapsed > maxWaitTime) {
                    console.log("[Debug] Timeout erreicht, starte Suche trotz Ladeindikator.");
                    clearInterval(checkInterval);
                    resolve(true);
                }
            }, 200);
        });
    }

    async function initializeScript() {
        console.log(t("pageLoaded"));
        try {
            await loadLastReadPostFromFile();
            observeForNewPosts();
            observeVisiblePosts();

            window.addEventListener("scroll", debounce(() => {
                observeVisiblePosts();
                if (!isAutoScrolling && !isSearching) {
                    if (hasScrolledAfterLoad) {
                        markTopVisiblePost(true);
                    } else {
                        hasScrolledAfterLoad = true;
                    }
                }
            }, 500));
        } catch (error) {
            console.error("❌ Fehler bei der Initialisierung des Skripts:", error);
        }
    }

    window.onload = async () => {
        if (!window.location.href.includes("/home")) {
            console.log(t("scriptDisabled"));
            return;
        }
        console.log(t("pageLoaded"));
        try {
            await loadNewestLastReadPost();
            await initializeScript();
            createButtons();
        } catch (error) {
            console.error("❌ Fehler beim Seitenladen:", error);
        }
    };

    window.addEventListener("blur", async () => {
        console.log(t("tabBlur"));
        if (lastReadPost && !downloadTriggered) {
            downloadTriggered = true;
            if (!(await isFileAlreadyDownloaded())) {
                console.log(t("downloadStart"));
                await downloadLastReadPost();
                await markDownloadAsComplete();
            } else {
                console.log(t("alreadyDownloaded"));
            }
            downloadTriggered = false;
        }
    });

    window.addEventListener("focus", () => {
        isTabFocused = true;
        downloadTriggered = false;
        console.log(t("tabFocused"));
    });

    async function isFileAlreadyDownloaded() {
        const localFiles = await GM_getValue("downloadedPosts", []);
        const fileSignature = `${lastReadPost.authorHandler}_${lastReadPost.timestamp}`;
        return localFiles.includes(fileSignature);
    }

    async function markDownloadAsComplete() {
        const localFiles = await GM_getValue("downloadedPosts", []);
        const fileSignature = `${lastReadPost.authorHandler}_${lastReadPost.timestamp}`;
        if (!localFiles.includes(fileSignature)) {
            localFiles.push(fileSignature);
            GM_setValue("downloadedPosts", localFiles);
        }
    }
})();