// ==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-06-01-2 // @author Copiis // @license MIT // @match https://x.com/home // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @downloadURL none // ==/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 isScriptActivated = false; let isDownloadEnabled = GM_getValue("isDownloadEnabled", true); // Standardmäßig aktiviert // Speichert die Lesestelle mit GM_setValue function saveLastReadPost(data) { let attempts = 0; const maxAttempts = 3; function trySave() { try { const bookmarkData = JSON.stringify(data); GM_setValue("lastReadPost", bookmarkData); console.log("💾 Leseposition erfolgreich mit GM_setValue gespeichert:", bookmarkData); localStorage.setItem("lastReadPost", bookmarkData); // Optionaler Fallback console.log("💾 Fallback in localStorage gespeichert:", localStorage.getItem("lastReadPost")); } catch (err) { attempts++; console.error(`❌ Fehler beim Speichern der Leseposition (Versuch ${attempts}/${maxAttempts}):`, err); if (attempts < maxAttempts) { console.log("🔄 Wiederhole Speicherversuch..."); setTimeout(trySave, 1000); } else { console.error("❌ Maximale Speicherversuche erreicht. Fallback auf localStorage."); localStorage.setItem("lastReadPost", JSON.stringify(data)); promptManualFallback(data); } } } trySave(); } // Speichert die heruntergeladene Lesestelle mit GM_setValue function saveLastDownloadedPost(data) { try { const bookmarkData = JSON.stringify(data); GM_setValue("lastDownloadedPost", bookmarkData); console.log("💾 Zuletzt heruntergeladene Leseposition gespeichert:", bookmarkData); } catch (err) { console.error("❌ Fehler beim Speichern der heruntergeladenen Leseposition:", err); } } // Lädt die Lesestelle mit GM_getValue function loadLastReadPost(callback) { try { const storedData = GM_getValue("lastReadPost", null); if (storedData) { const data = JSON.parse(storedData); console.log("✅ Leseposition geladen:", data); callback(data); } else { console.warn("⚠️ Keine gespeicherte Leseposition gefunden."); callback(null); } } catch (err) { console.error("❌ Fehler beim Laden der Leseposition:", err); const storedPost = JSON.parse(localStorage.getItem("lastReadPost") || "{}"); callback(storedPost); } } // Lädt die zuletzt heruntergeladene Lesestelle function loadLastDownloadedPost(callback) { try { const storedData = GM_getValue("lastDownloadedPost", null); if (storedData) { const data = JSON.parse(storedData); console.log("✅ Zuletzt heruntergeladene Leseposition geladen:", data); callback(data); } else { console.warn("⚠️ Keine heruntergeladene Leseposition gefunden."); callback(null); } } catch (err) { console.error("❌ Fehler beim Laden der heruntergeladenen Leseposition:", err); callback(null); } } // Fallback: Manuelle Benachrichtigung function promptManualFallback(data) { const content = JSON.stringify(data); const message = `📝 Neue Leseposition: ${content}\nBitte speichere dies manuell, da der Speichervorgang fehlschlug.`; showPopup(message, 10000); console.log("📝 Bitte manuell speichern:", content); } // Toggle-Funktion für den Download-Status function toggleDownloadStatus() { isDownloadEnabled = !isDownloadEnabled; GM_setValue("isDownloadEnabled", isDownloadEnabled); console.log(`🛠️ Download-Status geändert: ${isDownloadEnabled ? "aktiviert" : "deaktiviert"}`); showPopup(`💾 Download der Leseposition ${isDownloadEnabled ? "aktiviert" : "deaktiviert"}.`, 3000); updateDownloadButton(); // Button aktualisieren if (isDownloadEnabled) { console.log("🛠️ DEBUG: Download aktiviert, Seite wird aktualisiert..."); setTimeout(() => { location.reload(); // Seite aktualisieren, wenn Download aktiviert wird }, 1000); // Kurze Verzögerung, um Popup sichtbar zu machen } } // Einstellung in Violentmonkey/Tampermonkey-Menü GM_registerMenuCommand(`Download der Leseposition ${isDownloadEnabled ? "deaktivieren" : "aktivieren"}`, toggleDownloadStatus); // Initialisierung function initializeWhenDOMReady() { if (!window.location.href.includes("/home")) { console.log("🚫 Skript deaktiviert: Nicht auf der Home-Seite."); return; } console.log("🚀 Initialisiere Skript..."); const observer = new MutationObserver((mutations, obs) => { if (document.body) { obs.disconnect(); initializeScript().then(() => { createButtons(); startPeriodicSave(); }).catch(err => { console.error("❌ Fehler bei der Initialisierung:", err); showPopup("❌ Fehler beim Laden des Skripts."); }); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } window.addEventListener("load", initializeWhenDOMReady); // Event-Listener für Fokusverlust window.addEventListener("blur", () => { isTabFocused = false; console.log("🔴 Tab hat Fokus verloren."); saveLastReadPostToFile(); }); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { isTabFocused = false; console.log("🔴 Tab ist unsichtbar."); saveLastReadPostToFile(); } else { isTabFocused = true; console.log("🟢 Tab wieder sichtbar."); } }); window.addEventListener("focus", () => { isTabFocused = true; console.log("🟢 Tab wieder fokussiert."); }); function startPeriodicSave() { setInterval(() => { if (isTabFocused && lastReadPost && isScriptActivated) { loadLastReadPost(existingPost => { if (!existingPost || new Date(lastReadPost.timestamp) > new Date(existingPost.timestamp) || (lastReadPost.timestamp === existingPost.timestamp && lastReadPost.authorHandler !== existingPost.authorHandler)) { saveLastReadPost(lastReadPost); // Nur speichern, wenn neuer oder anderer Autor console.log("💾 Periodische Speicherung: Neue Leseposition gespeichert:", lastReadPost); } else { console.log("⏹️ Periodische Speicherung übersprungen: Leseposition nicht neuer oder identisch."); } }); } }, 30000); } // Funktion zum Speichern der Lesestelle als Datei im Download-Ordner function saveLastReadPostToFile() { try { console.log("🛠️ DEBUG: Download-Status:", isDownloadEnabled); if (!isDownloadEnabled) { console.log("⏹️ Download deaktiviert. Datei wird nicht gespeichert."); return; } if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) { console.warn("⚠️ Keine gültige Leseposition zum Speichern:", lastReadPost); return; } // Prüfen, ob die Lesestelle neuer ist als die zuletzt heruntergeladene loadLastDownloadedPost(lastDownloadedPost => { console.log("🛠️ DEBUG: Vergleich - lastReadPost:", lastReadPost, "lastDownloadedPost:", lastDownloadedPost); if (lastDownloadedPost && new Date(lastReadPost.timestamp) <= new Date(lastDownloadedPost.timestamp) && lastReadPost.authorHandler === lastDownloadedPost.authorHandler) { console.log("⏹️ Datei-Download übersprungen: Lesestelle nicht neuer oder identisch."); return; } // Datum und Uhrzeit aus dem Timestamp extrahieren const date = new Date(lastReadPost.timestamp); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const hour = String(date.getHours()).padStart(2, "0"); const minute = String(date.getMinutes()).padStart(2, "0"); const second = String(date.getSeconds()).padStart(2, "0"); // Dateinamen im gewünschten Format erstellen const fileName = `${year}_${month}_${day}_${hour}_${minute}_${second}-${lastReadPost.authorHandler}.json`; // Dateiinhalt erstellen (JSON-Format) const fileContent = JSON.stringify(lastReadPost, null, 2); // Blob erstellen und Download auslösen const blob = new Blob([fileContent], { type: "application/json" }); const url = URL.createObjectURL(blob); console.log("🛠️ DEBUG: Blob-URL erstellt:", url); const a = document.createElement("a"); a.href = url; a.download = fileName; a.style.display = "none"; // Verstecktes Element console.log("🛠️ DEBUG: -Element erstellt:", { href: a.href, download: a.download }); document.body.appendChild(a); setTimeout(() => { try { a.click(); console.log("🛠️ DEBUG: Download ausgelöst für:", fileName); } catch (clickErr) { console.error("❌ Fehler beim Auslösen des Downloads:", clickErr); // Fallback: Öffne die Blob-URL direkt showPopup("⚠️ Automatischer Download fehlgeschlagen. Öffne Datei manuell.", 5000); window.open(url, "_blank"); } setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); console.log("🛠️ DEBUG: -Element entfernt, URL widerrufen."); }, 200); }, 200); // Längere Verzögerung für Browser-Verarbeitung // Zuletzt heruntergeladene Lesestelle speichern saveLastDownloadedPost(lastReadPost); console.log(`💾 Leseposition als Datei gespeichert: ${fileName}`); }); } catch (err) { console.error("❌ Fehler beim Speichern der Datei:", err); promptManualFallback(lastReadPost); } } // Funktion zum Laden der Lesestelle aus einer Datei function loadLastReadPostFromFile() { try { const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; input.style.display = "none"; document.body.appendChild(input); input.addEventListener("change", (event) => { const file = event.target.files[0]; if (!file) { console.warn("⚠️ Keine Datei ausgewählt."); showPopup("❌ Bitte wähle eine JSON-Datei aus.", 5000); document.body.removeChild(input); return; } const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); if (data.timestamp && data.authorHandler) { lastReadPost = data; saveLastReadPost(data); // Speichert in GM_setValue und localStorage console.log("✅ Leseposition aus Datei geladen:", lastReadPost); showPopup("✅ Leseposition erfolgreich geladen!", 3000); // Optional: Automatische Suche nach der geladenen Position startRefinedSearchForLastReadPost(); } else { console.warn("⚠️ Ungültige Leseposition in der Datei:", data); showPopup("❌ Ungültige Leseposition in der Datei.", 5000); } } catch (err) { console.error("❌ Fehler beim Lesen der Datei:", err); showPopup("❌ Fehler beim Lesen der Datei.", 5000); } document.body.removeChild(input); }; reader.readAsText(file); }); input.click(); } catch (err) { console.error("❌ Fehler beim Öffnen des Datei-Dialogs:", err); showPopup("❌ Fehler beim Öffnen des Datei-Dialogs.", 5000); } } function loadNewestLastReadPost() { return new Promise(resolve => { loadLastReadPost(storedPost => { if (storedPost && storedPost.timestamp && storedPost.authorHandler) { lastReadPost = storedPost; console.log("✅ Leseposition geladen:", lastReadPost); } else { const localPost = JSON.parse(localStorage.getItem("lastReadPost") || "{}"); if (localPost && localPost.timestamp && localPost.authorHandler) { lastReadPost = localPost; console.log("✅ Leseposition aus localStorage:", lastReadPost); } else { console.warn("⚠️ Keine Leseposition gefunden."); showPopup("ℹ️ Scrolle, um eine Leseposition zu setzen."); } } resolve(); }); }); } async function initializeScript() { console.log("🔧 Lade Leseposition..."); await loadNewestLastReadPost(); window.addEventListener("scroll", () => { if (!isScriptActivated) { isScriptActivated = true; console.log("🛠️ DEBUG: Skript durch Scrollen aktiviert."); observeForNewPosts(); } if (isAutoScrolling || isSearching) { console.log("⏹️ Scroll-Ereignis ignoriert."); return; } markTopVisiblePost(true); }, { passive: true }); } function markTopVisiblePost(save = true) { const topPost = getTopVisiblePost(); if (!topPost) { console.log("❌ Kein sichtbarer Beitrag."); return; } const postTimestamp = getPostTimestamp(topPost); const postAuthorHandler = getPostAuthorHandler(topPost); if (postTimestamp && postAuthorHandler) { const newPost = { timestamp: postTimestamp, authorHandler: postAuthorHandler }; if (save && isScriptActivated) { loadLastReadPost(existingPost => { console.log("🛠️ DEBUG: markTopVisiblePost - newPost:", newPost, "existingPost:", existingPost); if (!existingPost || new Date(postTimestamp) > new Date(existingPost.timestamp) || (postTimestamp === existingPost.timestamp && postAuthorHandler !== existingPost.authorHandler)) { lastReadPost = newPost; console.log("💾 Neue Leseposition gesetzt:", lastReadPost); saveLastReadPost(lastReadPost); // Immer speichern, wenn neuer oder anderer Autor } else { console.log("⏹️ Interne Speicherung übersprungen: Leseposition nicht neuer oder identisch."); } }); } } } 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() { if (!isScriptActivated) { console.log("⏹️ Suche abgebrochen: Skript nicht aktiviert."); showPopup("ℹ️ Bitte scrollen oder Lupe klicken."); return; } loadLastReadPost(storedData => { if (!storedData) { const localData = JSON.parse(localStorage.getItem("lastReadPost") || "{}"); if (localData && localData.timestamp && localData.authorHandler) { lastReadPost = localData; } else { console.log("❌ Keine Leseposition gefunden."); showPopup("❌ Keine Leseposition vorhanden."); return; } } else { lastReadPost = storedData; } if (!lastReadPost.timestamp || !lastReadPost.authorHandler) { console.log("❌ Ungültige Leseposition:", lastReadPost); showPopup("❌ Ungültige Leseposition."); return; } console.log("🔍 Starte Suche:", 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 gestoppt."); isSearching = false; popup.remove(); window.removeEventListener("keydown", handleSpaceKey); } } window.addEventListener("keydown", handleSpaceKey); const search = () => { if (!isSearching || searchAttempts >= maxAttempts) { console.log("⏹️ Suche beendet: Max Versuche 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 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."); 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."); 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."); window.removeEventListener("scroll", removeHighlightOnScroll); } }; setTimeout(() => { isAutoScrolling = false; window.addEventListener("scroll", removeHighlightOnScroll); console.log("✅ Beitrag zentriert."); }, 1000); } function getVisiblePosts() { const posts = Array.from(document.querySelectorAll("article")); return posts.map(post => ({ element: post, timestamp: getPostTimestamp(post), authorHandler: getPostAuthorHandler(post), })); } function observeForNewPosts() { let isProcessingIndicator = false; const observer = new MutationObserver(() => { if (!isScriptActivated) { console.log("⏹️ Beobachtung abgebrochen: Skript nicht aktiviert."); return; } if (window.scrollY <= 1 && !isSearching && !isProcessingIndicator && lastReadPost) { const newPostsIndicator = getNewPostsIndicator(); if (newPostsIndicator) { console.log("🆕 Neue Beiträge erkannt."); 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(`🆕 Indikator gefunden: "${textContent}"`); button.dataset.processed = 'true'; return button; } } } } console.log("ℹ️ Kein Beitragsindikator gefunden."); return null; } function clickNewPostsIndicator(indicator) { if (!indicator) { console.log("⚠️ Kein Indikator gefunden."); return; } console.log("✅ Klicke auf Indikator..."); try { indicator.click(); console.log("✅ Indikator geklickt."); } catch (err) { console.error("❌ Fehler beim Klicken:", 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; }); } let downloadButton = null; // Referenz zum Download-Button function updateDownloadButton() { if (downloadButton) { downloadButton.textContent = isDownloadEnabled ? "💾" : "⛔"; downloadButton.style.backgroundColor = isDownloadEnabled ? "rgba(0, 128, 0, 0.9)" : "rgba(255, 0, 0, 0.9)"; downloadButton.title = isDownloadEnabled ? "Download der Leseposition aktiviert (Klicken zum Deaktivieren)" : "Download der Leseposition deaktiviert (Klicken zum Aktivieren)"; } } function createButtons() { setTimeout(() => { try { if (!document.body) { console.warn("⚠️ document.body nicht verfügbar."); return; } const buttonContainer = document.createElement("div"); let targetDiv = document.querySelector('div.css-175oi2r.r-dnmrzs.r-1559e4e'); if (!targetDiv) { targetDiv = document.querySelector('div[role="heading"]'); console.warn("⚠️ Primäres Ziel-
nicht gefunden. Fallback auf div[role='heading']."); } let leftOffset = 60; let topOffset = 15; if (targetDiv) { const rect = targetDiv.getBoundingClientRect(); leftOffset = rect.right + 10; topOffset = rect.top + (rect.height / 2) - 18; console.log("🛠️ DEBUG: Ziel-
gefunden. Position:", { left: leftOffset, top: topOffset }); } else { console.warn("⚠️ Kein Ziel-
gefunden. Fallback-Position."); } buttonContainer.style.position = "fixed"; buttonContainer.style.top = `${topOffset}px`; buttonContainer.style.left = `${leftOffset}px`; buttonContainer.style.zIndex = "10000"; buttonContainer.style.display = "flex"; buttonContainer.style.alignItems = "center"; buttonContainer.style.visibility = "visible"; const buttonsConfig = [ { icon: "🔍", title: "Start manual search", onClick: () => { console.log("🔍 Manuelle Suche gestartet."); if (!isScriptActivated) { isScriptActivated = true; console.log("🛠️ DEBUG: Skript durch Lupen-Klick aktiviert."); observeForNewPosts(); } startRefinedSearchForLastReadPost(); }, }, { icon: "📂", title: "Load last read position from file", onClick: () => { console.log("📂 Lade Leseposition aus Datei..."); loadLastReadPostFromFile(); }, }, { icon: isDownloadEnabled ? "💾" : "⛔", title: isDownloadEnabled ? "Download der Leseposition aktiviert (Klicken zum Deaktivieren)" : "Download der Leseposition deaktiviert (Klicken zum Aktivieren)", onClick: () => { toggleDownloadStatus(); }, isDownloadButton: true, }, ]; buttonsConfig.forEach(({ icon, title, onClick, isDownloadButton }) => { const button = createButton(icon, title, onClick); if (isDownloadButton) { downloadButton = button; button.style.backgroundColor = isDownloadEnabled ? "rgba(0, 128, 0, 0.9)" : "rgba(255, 0, 0, 0.9)"; } buttonContainer.appendChild(button); }); document.body.appendChild(buttonContainer); console.log("🛠️ DEBUG: Button-Container erstellt:", buttonContainer); } catch (err) { console.error("❌ Fehler beim Erstellen der Buttons:", err); showPopup("❌ Fehler beim Anzeigen der Buttons."); } }, 10000); } 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 = "0 0 10px rgba(255, 255, 255, 0.5)"; button.style.transition = "transform 0.2s, box-shadow 0.3s"; button.style.zIndex = "10001"; button.style.visibility = "visible"; button.style.marginRight = "8px"; // Abstand zwischen Buttons button.textContent = icon; button.title = title; button.addEventListener("click", () => { button.style.boxShadow = "0 0 20px rgba(255, 255, 255, 0.8)"; button.style.transform = "scale(0.9)"; setTimeout(() => { button.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.5)"; button.style.transform = "scale(1)"; }, 300); onClick(); }); button.addEventListener("mouseenter", () => { button.style.boxShadow = "0 0 15px rgba(255, 255, 255, 0.7)"; button.style.transform = "scale(1.1)"; }); button.addEventListener("mouseleave", () => { button.style.boxShadow = "0 0 10px rgba(255, 255, 255, 0.5)"; button.style.transform = "scale(1)"; }); return button; } function showPopup(message, duration = 3000) { 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.style.maxWidth = "400px"; popup.style.whiteSpace = "pre-wrap"; popup.textContent = message; document.body.appendChild(popup); setTimeout(() => { popup.remove(); }, duration); } })();