Twitter/X Timeline Sync

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.

Nainstalovat skript?
Skript doporučený autorem

Mohlo by se vám také líbit YouTube Video Hider with 🚫 Icon (Transparent).

Nainstalovat skript
// ==UserScript==
// @name              Twitter/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-05-23
// @author            Copiis
// @license           MIT
// @match             https://x.com/home
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_download
// ==/UserScript==
//                    If you find this script useful and would like to support my work, consider making a small donation!
//                    Bitcoin (BTC): bc1quc5mkudlwwkktzhvzw5u2nruxyepef957p68r7
//                    PayPal: https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE

(function () {
    let lastReadPost = null;
    let isAutoScrolling = false;
    let isSearching = false;
    let isTabFocused = true;
    let downloadTriggered = false;
    let lastDownloadedPost = null;

    window.onload = async () => {
        if (!window.location.href.includes("/home")) {
            console.log("🚫 Skript deaktiviert: Nicht auf der Home-Seite.");
            return;
        }
        console.log("🚀 Seite vollständig geladen. Initialisiere Skript...");
        await loadNewestLastReadPost();
        await initializeScript();
        createButtons();
        // **Änderung 1: Periodisches Speichern starten**
        startPeriodicSave();
    };

    // **Änderung 2: Debugging für blur- und visibilitychange-Ereignisse**
    window.addEventListener("blur", async () => {
        console.log("🛠️ DEBUG: Blur-Ereignis ausgelöst. isTabFocused:", isTabFocused, "lastReadPost:", lastReadPost, "document.visibilityState:", document.visibilityState);
        isTabFocused = false;
        console.log("🌐 Tab nicht mehr fokussiert.");
        await saveAndDownloadIfNeeded();
    });

    document.addEventListener("visibilitychange", async () => {
        console.log("🛠️ DEBUG: visibilitychange ausgelöst. document.visibilityState:", document.visibilityState, "lastReadPost:", lastReadPost);
        if (document.visibilityState === "hidden") {
            isTabFocused = false;
            await saveAndDownloadIfNeeded();
        } else {
            isTabFocused = true;
            downloadTriggered = false;
            console.log("🟢 Tab wieder sichtbar.");
        }
    });

    window.addEventListener("focus", () => {
        isTabFocused = true;
        downloadTriggered = false;
        console.log("🟢 Tab wieder fokussiert.");
    });

    // **Änderung 3: Funktion für Speichern und Download**
    async function saveAndDownloadIfNeeded() {
        if (lastReadPost && !downloadTriggered) {
            if (
                !lastDownloadedPost ||
                lastDownloadedPost.timestamp !== lastReadPost.timestamp ||
                lastDownloadedPost.authorHandler !== lastReadPost.authorHandler
            ) {
                downloadTriggered = true;
                console.log("📥 Speichere und lade aktuelle Leseposition herunter...");
                await saveLastReadPostToFile();
                await new Promise(resolve => setTimeout(resolve, 100));
                await downloadLastReadPost();
                lastDownloadedPost = { ...lastReadPost };
                downloadTriggered = false;
            } else {
                console.log("⏹️ Leseposition ist identisch mit der zuletzt heruntergeladenen. Download übersprungen.");
            }
        } else {
            console.log("⚠️ Keine Leseposition oder Download bereits ausgelöst.");
        }
    }

    // **Änderung 4: Periodisches Speichern**
    function startPeriodicSave() {
        setInterval(async () => {
            if (isTabFocused && lastReadPost && !downloadTriggered) {
                console.log("🛠️ DEBUG: Periodisches Speichern ausgelöst. lastReadPost:", lastReadPost);
                await saveAndDownloadIfNeeded();
            }
        }, 30000); // Alle 30 Sekunden, wenn fokussiert
    }

    // **Änderung 5: Verbesserte downloadLastReadPost**
    async function downloadLastReadPost() {
        if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
            console.warn("⚠️ Keine gültige Leseposition zum Herunterladen.");
            showPopup("❌ Keine gültige Leseposition.");
            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 = `${timestamp}_${sanitizedHandler}.json`;
            console.log("🛠️ DEBUG: Versuche Download mit Dateiname:", fileName);

            // Fallback: Direktes Erstellen eines Download-Links
            const blob = new Blob([data], { type: "application/json" });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            console.log(`✅ Leseposition erfolgreich heruntergeladen: ${fileName}`);
            lastDownloadedPost = { ...lastReadPost };
        } catch (error) {
            console.error("❌ Fehler beim Herunterladen:", error);
            showPopup("❌ Fehler beim Download der Leseposition.");
        }
    }

    // **Änderung 6: Verbesserte Speicherung mit localStorage-Priorität**
    async function saveLastReadPostToFile() {
        try {
            if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
                console.warn("⚠️ Keine gültige Leseposition vorhanden. Speichern übersprungen.");
                return;
            }

            const existingData = localStorage.getItem("lastReadPost") || GM_getValue("lastReadPost", null);
            if (existingData) {
                const existingPost = JSON.parse(existingData);
                if (
                    existingPost.timestamp === lastReadPost.timestamp &&
                    existingPost.authorHandler === lastReadPost.authorHandler
                ) {
                    console.log("⏹️ Lesestelle ist identisch mit der gespeicherten. Speichern übersprungen.");
                    return;
                }
            }

            localStorage.setItem("lastReadPost", JSON.stringify(lastReadPost));
            GM_setValue("lastReadPost", JSON.stringify(lastReadPost));
            console.log("💾 Leseposition erfolgreich gespeichert (localStorage und GM_setValue):", lastReadPost);
        } catch (err) {
            console.error("❌ Fehler beim Speichern der Leseposition:", err);
            showPopup("❌ Fehler beim Speichern der Leseposition.");
        }
    }

    async function loadNewestLastReadPost() {
        try {
            const localData = localStorage.getItem("lastReadPost") || GM_getValue("lastReadPost", null);
            if (localData) {
                lastReadPost = JSON.parse(localData);
                console.log("✅ Lokale Leseposition beim Start geladen:", lastReadPost);
            } else {
                console.warn("⚠️ Keine gespeicherte Leseposition gefunden.");
            }
        } catch (err) {
            console.error("❌ Fehler beim Laden der neuesten Leseposition:", err);
        }
    }

    async function initializeScript() {
        console.log("🔧 Lade Leseposition...");
        await loadLastReadPostFromFile();
        observeForNewPosts();

        window.addEventListener("scroll", () => {
            if (isAutoScrolling || isSearching) {
                console.log("⏹️ Scroll-Ereignis ignoriert (automatischer Modus aktiv).");
                return;
            }
            markTopVisiblePost(true);
        }, { passive: true });
    }

    async function loadLastReadPostFromFile() {
        try {
            const data = localStorage.getItem("lastReadPost") || GM_getValue("lastReadPost", null);
            if (data) {
                lastReadPost = JSON.parse(data);
                console.log("✅ Leseposition erfolgreich geladen:", lastReadPost);
            } else {
                console.warn("⚠️ Keine gespeicherte Leseposition gefunden.");
            }
        } catch (err) {
            console.error("❌ Fehler beim Laden der Leseposition:", err);
        }
    }

    // **Änderung 5: Fallback auf localStorage**
    async function saveLastReadPostToFile() {
        try {
            if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) {
                console.warn("⚠️ Keine gültige Leseposition vorhanden. Speichern übersprungen.");
                return;
            }

            const existingData = GM_getValue("lastReadPost", null);
            if (existingData) {
                const existingPost = JSON.parse(existingData);
                if (
                    existingPost.timestamp === lastReadPost.timestamp &&
                    existingPost.authorHandler === lastReadPost.authorHandler
                ) {
                    console.log("⏹️ Lesestelle ist identisch mit der gespeicherten. Speichern übersprungen.");
                    return;
                }
            }

            GM_setValue("lastReadPost", JSON.stringify(lastReadPost));
            localStorage.setItem("lastReadPost", JSON.stringify(lastReadPost)); // Fallback
            console.log("💾 Leseposition erfolgreich gespeichert (GM_setValue und localStorage):", lastReadPost);
        } catch (err) {
            console.error("❌ Fehler beim Speichern der Leseposition:", err);
            showPopup("❌ Fehler beim Speichern der Leseposition.");
        }
    }

    function markTopVisiblePost(save = true) {
        const topPost = getTopVisiblePost();
        if (!topPost) {
            console.log("❌ Kein oberster sichtbarer Beitrag gefunden.");
            return;
        }

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

        if (postTimestamp && authorHandler) {
            const newPost = { timestamp: postTimestamp, authorHandler };
            const existingData = GM_getValue("lastReadPost", null);
            const existingPost = existingData ? JSON.parse(existingData) : null;

            if (save && (!existingPost || new Date(postTimestamp) > new Date(existingPost.timestamp))) {
                lastReadPost = newPost;
                console.log("💾 Neue Leseposition erkannt und aktualisiert:", lastReadPost);
                if (isTabFocused) {
                    saveLastReadPostToFile();
                }
            }
        }
    }

    function getTopVisiblePost() {
        const posts = Array.from(document.querySelectorAll("article"));
        return posts.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() {
        const storedData = GM_getValue("lastReadPost", null) || localStorage.getItem("lastReadPost");
        if (!storedData) {
            console.log("❌ Keine gespeicherte Leseposition gefunden.");
            showPopup("❌ Keine gespeicherte Leseposition vorhanden.");
            return;
        }

        try {
            lastReadPost = JSON.parse(storedData);
            if (!lastReadPost.timestamp || !lastReadPost.authorHandler) {
                console.log("❌ Gespeicherte Leseposition ist ungültig:", lastReadPost);
                showPopup("❌ Ungültige gespeicherte Leseposition.");
                return;
            }
        } catch (err) {
            console.error("❌ Fehler beim Parsen der gespeicherten Leseposition:", err);
            showPopup("❌ Fehler bei der gespeicherten Leseposition.");
            return;
        }

        console.log("🔍 Starte verfeinerte Suche mit gespeicherter Position:", lastReadPost);
        const popup = createSearchPopup();

        let direction = 1;
        let scrollAmount = 2000;
        let previousScrollY = -1;
        let searchAttempts = 0;
        const maxAttempts = 50;

        function handleSpaceKey(event) {
            if (event.code === "Space") {
                console.log("⏹️ Suche manuell gestoppt.");
                isSearching = false;
                popup.remove();
                window.removeEventListener("keydown", handleSpaceKey);
            }
        }

        window.addEventListener("keydown", handleSpaceKey);

        const search = () => {
            if (!isSearching || searchAttempts >= maxAttempts) {
                console.log("⏹️ Suche beendet: Maximale Versuche erreicht oder abgebrochen.");
                isSearching = false;
                popup.remove();
                window.removeEventListener("keydown", handleSpaceKey);
                return;
            }

            const visiblePosts = getVisiblePosts();
            const comparison = compareVisiblePostsToLastReadPost(visiblePosts);

            if (comparison === "match") {
                const matchedPost = findPostByData(lastReadPost);
                if (matchedPost) {
                    console.log("🎯 Beitrag gefunden:", lastReadPost);
                    isAutoScrolling = true;
                    scrollToPostWithHighlight(matchedPost);
                    isSearching = false;
                    popup.remove();
                    window.removeEventListener("keydown", handleSpaceKey);
                    return;
                }
            } else if (comparison === "older") {
                direction = -1;
            } else if (comparison === "newer") {
                direction = 1;
            }

            if (window.scrollY === previousScrollY) {
                scrollAmount = Math.max(scrollAmount / 2, 500);
                direction = -direction;
            } else {
                scrollAmount = Math.min(scrollAmount * 1.5, 3000);
            }

            previousScrollY = window.scrollY;
            searchAttempts++;

            requestAnimationFrame(() => {
                window.scrollBy({
                    top: direction * scrollAmount,
                    behavior: "smooth"
                });
                setTimeout(search, 1000);
            });
        };

        isSearching = true;
        search();
    }

    function startRefinedSearchForLastReadPostWithPosition(position) {
        if (!position || !position.timestamp || !position.authorHandler) {
            console.log("❌ Ungültige Leseposition für Suche:", position);
            showPopup("❌ Ungültige Leseposition.");
            return;
        }

        console.log("🔍 Starte verfeinerte Suche mit temporärer Position:", position);
        const popup = createSearchPopup();

        let direction = 1;
        let scrollAmount = 2000;
        let previousScrollY = -1;

        function handleSpaceKey(event) {
            if (event.code === "Space") {
                console.log("⏹️ Suche manuell gestoppt.");
                isSearching = false;
                popup.remove();
                window.removeEventListener("keydown", handleSpaceKey);
            }
        }

        window.addEventListener("keydown", handleSpaceKey);

        const search = () => {
            if (!isSearching) {
                popup.remove();
                return;
            }

            const visiblePosts = getVisiblePosts();
            const comparison = compareVisiblePostsToLastReadPost(visiblePosts, position);

            if (comparison === "match") {
                const matchedPost = findPostByData(position);
                if (matchedPost) {
                    console.log("🎯 Beitrag gefunden:", position);
                    isAutoScrolling = true;
                    scrollToPostWithHighlight(matchedPost);
                    isSearching = false;
                    popup.remove();
                    window.removeEventListener("keydown", handleSpaceKey);
                    return;
                }
            } else if (comparison === "older") {
                direction = -1;
            } else if (comparison === "newer") {
                direction = 1;
            }

            if (window.scrollY === previousScrollY) {
                scrollAmount = Math.max(scrollAmount / 2, 500);
                direction = -direction;
            } else {
                scrollAmount = Math.min(scrollAmount * 1.5, 3000);
            }

            previousScrollY = window.scrollY;

            window.scrollBy(0, direction * scrollAmount);

            setTimeout(search, 300);
        };

        isSearching = true;
        search();
    }

    function createSearchPopup() {
        const popup = document.createElement("div");
        popup.style.position = "fixed";
        popup.style.bottom = "20px";
        popup.style.left = "50%";
        popup.style.transform = "translateX(-50%)";
        popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        popup.style.color = "#ffffff";
        popup.style.padding = "10px 20px";
        popup.style.borderRadius = "8px";
        popup.style.fontSize = "14px";
        popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
        popup.style.zIndex = "10000";
        popup.textContent = "🔍 Searching... Press SPACE to cancel.";
        document.body.appendChild(popup);
        return popup;
    }

    function compareVisiblePostsToLastReadPost(posts, customPosition = lastReadPost) {
        const validPosts = posts.filter(post => post.timestamp && post.authorHandler);

        if (validPosts.length === 0) {
            console.log("⚠️ Keine sichtbaren Beiträge gefunden.");
            return null;
        }

        const lastReadTime = new Date(customPosition.timestamp);

        const allOlder = validPosts.every(post => new Date(post.timestamp) < lastReadTime);
        const allNewer = validPosts.every(post => new Date(post.timestamp) > lastReadTime);

        if (validPosts.some(post => post.timestamp === customPosition.timestamp && post.authorHandler === customPosition.authorHandler)) {
            return "match";
        } else if (allOlder) {
            return "older";
        } else if (allNewer) {
            return "newer";
        } else {
            return "mixed";
        }
    }

    function scrollToPostWithHighlight(post) {
        if (!post) {
            console.log("❌ Kein Beitrag zum Scrollen gefunden.");
            return;
        }

        isAutoScrolling = true;

        post.style.outline = "none";
        post.style.boxShadow = "0 0 20px 10px rgba(255, 215, 0, 0.9)";
        post.style.animation = "none";

        const existingStyle = document.querySelector('#glowStyle');
        if (existingStyle) {
            existingStyle.remove();
        }

        post.scrollIntoView({ behavior: "smooth", block: "center" });

        const removeHighlightOnScroll = () => {
            if (!isAutoScrolling) {
                post.style.boxShadow = "none";
                console.log("✅ Highlight entfernt nach manuellem Scroll.");
                window.removeEventListener("scroll", removeHighlightOnScroll);
            }
        };

        setTimeout(() => {
            isAutoScrolling = false;
            window.addEventListener("scroll", removeHighlightOnScroll);
            console.log("✅ Beitrag zentriert, warte auf manuellen Scroll.");
        }, 1000);
    }

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

    async function deleteOldReadingPositions(handler) {
        console.log(`🗑️ Ältere Lesestellen für den Handler "${handler}" werden simuliert entfernt.`);
    }

    function observeForNewPosts() {
        let isProcessingIndicator = false;

        const observer = new MutationObserver(() => {
            if (window.scrollY <= 1 && !isSearching && !isProcessingIndicator && lastReadPost) {
                const newPostsIndicator = getNewPostsIndicator();
                if (newPostsIndicator) {
                    console.log("🆕 Neue Beiträge erkannt. Starte automatische Suche...");
                    isProcessingIndicator = true;
                    clickNewPostsIndicator(newPostsIndicator);
                    setTimeout(() => {
                        startRefinedSearchForLastReadPost();
                        isProcessingIndicator = false;
                    }, 2000);
                }
            }
        });

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

    function getNewPostsIndicator() {
        const buttons = document.querySelectorAll('button[role="button"]');
        for (const button of buttons) {
            const span = button.querySelector('span.r-poiln3');
            if (span) {
                const textContent = span.textContent || '';
                const postIndicatorPattern = /^\d+\s*(neue|new)?\s*(Post|Posts|Beitrag|Beiträge|Tweet|Tweets|Publicación|Publications|投稿|게시물|пост|постов|mensagem|mensagens|مشاركة|مشاركات)\b/i;
                if (postIndicatorPattern.test(textContent)) {
                    if (!button.dataset.processed) {
                        console.log(`🆕 Neuer Beitrags-Indikator gefunden mit Text: "${textContent}"`);
                        button.dataset.processed = 'true';
                        return button;
                    }
                }
            }
        }
        console.log("ℹ️ Kein neuer Beitragsindikator gefunden.");
        return null;
    }

    function clickNewPostsIndicator(indicator) {
        if (!indicator) {
            console.log("⚠️ Kein neuer Beitragsindikator gefunden.");
            return;
        }

        console.log("✅ Klicke auf neuen Beitragsindikator...");
        try {
            indicator.click();
            console.log("✅ Neuer Beitragsindikator erfolgreich geklickt.");
        } catch (err) {
            console.error("❌ Fehler beim Klicken auf den Beitragsindikator:", err);
        }
    }

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

    function createButtons() {
        const buttonContainer = document.createElement("div");
        buttonContainer.style.position = "fixed";
        buttonContainer.style.top = "50%";
        buttonContainer.style.left = "3px";
        buttonContainer.style.transform = "translateY(-50%)";
        buttonContainer.style.display = "flex";
        buttonContainer.style.flexDirection = "column";
        buttonContainer.style.gap = "3px";
        buttonContainer.style.zIndex = "10000";

        const buttonsConfig = [
            {
                icon: "📂",
                title: "Load saved position",
                onClick: async () => {
                    await importLastReadPost();
                },
            },
            {
                icon: "🔍",
                title: "Start manual search",
                onClick: () => {
                    console.log("🔍 Manuelle Suche gestartet.");
                    startRefinedSearchForLastReadPost();
                },
            },
        ];

        buttonsConfig.forEach(({ icon, title, onClick }) => {
            const button = createButton(icon, title, onClick);
            buttonContainer.appendChild(button);
        });

        document.body.appendChild(buttonContainer);
    }

    function createButton(icon, title, onClick) {
        const button = document.createElement("div");
        button.style.width = "36px";
        button.style.height = "36px";
        button.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        button.style.color = "#ffffff";
        button.style.borderRadius = "50%";
        button.style.display = "flex";
        button.style.justifyContent = "center";
        button.style.alignItems = "center";
        button.style.cursor = "pointer";
        button.style.fontSize = "18px";
        button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
        button.style.transition = "transform 0.2s, box-shadow 0.3s";
        button.textContent = icon;
        button.title = title;

        button.addEventListener("click", () => {
            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)";
            }, 300);
            onClick();
        });

        button.addEventListener("mouseenter", () => {
            button.style.boxShadow = "inset 0 0 15px rgba(255, 255, 255, 0.7)";
            button.style.transform = "scale(1.1)";
        });

        button.addEventListener("mouseleave", () => {
            button.style.boxShadow = "inset 0 0 10px rgba(255, 255, 255, 0.5)";
            button.style.transform = "scale(1)";
        });

        return button;
    }

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

        input.addEventListener("change", async (event) => {
            const file = event.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = async () => {
                    try {
                        const importedData = JSON.parse(reader.result);
                        if (importedData.timestamp && importedData.authorHandler) {
                            console.log("✅ Importierte Leseposition geladen (wird nicht intern gespeichert):", importedData);
                            showPopup("✅ Position geladen. Suche startet...");

                            const tempPosition = importedData;
                            const matchedPost = findPostByData(tempPosition);
                            if (matchedPost) {
                                scrollToPostWithHighlight(matchedPost);
                            } else {
                                startRefinedSearchForLastReadPostWithPosition(tempPosition);
                            }
                        } else {
                            throw new Error("Ungültige Position");
                        }
                    } catch (error) {
                        console.error("❌ Fehler beim Importieren der Leseposition:", error);
                        showPopup("❌ Fehler: Ungültige Position.");
                    }
                };
                reader.readAsText(file);
            }
        });

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

    function showPopup(message) {
        const popup = document.createElement("div");
        popup.style.position = "fixed";
        popup.style.bottom = "20px";
        popup.style.right = "20px";
        popup.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
        popup.style.color = "#ffffff";
        popup.style.padding = "10px 20px";
        popup.style.borderRadius = "8px";
        popup.style.fontSize = "14px";
        popup.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.8)";
        popup.style.zIndex = "10000";
        popup.textContent = message;

        document.body.appendChild(popup);

        setTimeout(() => {
            popup.remove();
        }, 3000);
    }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元