// ==UserScript== // @name X Timeline Manager // @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 // @namespace http://tampermonkey.net/ // @version 2024.11.28 // @author Copiis // @license MIT // @match https://x.com/home // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== (function () { 'use strict'; console.log("📜 Script geladen: X Timeline Manager"); // Interne Leseliste speichern const readListKey = "xReadList"; let readList = JSON.parse(localStorage.getItem(readListKey) || "[]"); const MAX_TIMESTAMPS = 20; // Funktion zum Konvertieren von Timestamps zu einem Date-Objekt function parseTimestamp(timestamp) { return new Date(timestamp); } // Funktion zum Speichern eines Timestamps, wenn dieser neuer ist function saveTimestampOnScroll(timestamp) { if (readList.length > 0) { const newestInList = parseTimestamp(readList[readList.length - 1]); const newTimestamp = parseTimestamp(timestamp); if (newTimestamp <= newestInList) { return; // Überspringen, wenn der Timestamp nicht neuer ist } } if (!readList.includes(timestamp)) { readList.push(timestamp); if (readList.length > MAX_TIMESTAMPS) { readList.shift(); // Entferne das älteste Element } localStorage.setItem(readListKey, JSON.stringify(readList)); console.log(`✅ Neues Timestamp gespeichert: ${timestamp}`); } } // Funktion zum Prüfen, ob ein Timestamp sichtbar ist function isTimestampVisible(timestamp) { const timeElements = document.querySelectorAll("time[datetime]"); for (let timeElement of timeElements) { if (timeElement.getAttribute("datetime") === timestamp) { return timeElement.getBoundingClientRect().top >= 0 && timeElement.getBoundingClientRect().bottom <= window.innerHeight; } } return false; } // Automatisches Scrollen zum neuesten Timestamp function findNewestTimestamp() { if (readList.length === 0) return; const newestTimestamp = parseTimestamp(readList[readList.length - 1]); console.log(`🔍 Suche nach neuestem Timestamp: ${newestTimestamp}`); let attempts = 0; function scrollToTimestamp() { const visibleTimestamps = [...document.querySelectorAll("time[datetime]")] .map(el => ({ timestamp: el.getAttribute("datetime"), element: el })) .sort((a, b) => parseTimestamp(a.timestamp) - parseTimestamp(b.timestamp)); for (let { timestamp, element } of visibleTimestamps) { const visibleTimestamp = parseTimestamp(timestamp); // Bedingung: Sichtbarer Timestamp ist neuer als der in der Leseliste if (visibleTimestamp > newestTimestamp) { console.log(`📅 Neuerer sichtbarer Timestamp gefunden: ${timestamp}`); newestTimestamp.setTime(visibleTimestamp.getTime()); element.scrollIntoView({ behavior: "smooth", block: "center" }); return; } } if (attempts < 50) { // Sicherheitsgrenze, um Endlosschleifen zu vermeiden console.log("⬇️ Suche weiter unten..."); window.scrollBy(0, 200); attempts++; setTimeout(scrollToTimestamp, 500); } else { console.log(`⚠️ Abbruch: Keine neuen Timestamps gefunden.`); } } scrollToTimestamp(); } // Klick auf "Neue Posts anzeigen" oder ähnliche Buttons function checkNewPostsButton() { const buttons = document.querySelectorAll("div.css-146c3p1 span.css-1jxf684"); for (let button of buttons) { if (button.textContent.includes("Post anzeigen")) { console.log("🔄 'Neue Posts anzeigen' Button gefunden. Klicke..."); button.click(); setTimeout(() => { console.log("⏳ Warte auf vollständiges Laden..."); findNewestTimestamp(); }, 2000); return; } } } // Überwachung beim Hochscrollen (nur manuelles Hochscrollen) let lastScrollTop = 0; window.addEventListener("scroll", () => { const currentScrollTop = window.scrollY; if (currentScrollTop < lastScrollTop) { // User scrollt hoch console.log("🖱️ Manuelles Hochscrollen erkannt..."); document.querySelectorAll("time[datetime]").forEach(el => { const timestamp = el.getAttribute("datetime"); saveTimestampOnScroll(timestamp); }); } lastScrollTop = currentScrollTop; }); // Event beim Laden/Aktualisieren der Seite window.addEventListener("load", () => { console.log("🌐 Seite geladen. Suche gestartet..."); findNewestTimestamp(); }); // Überwachung auf Änderungen in der Seite const observer = new MutationObserver(() => { checkNewPostsButton(); }); observer.observe(document.body, { childList: true, subtree: true }); })();