您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
跟踪并通过Tampermonkey的内部存储在Twitter/X上同步您的最后阅读位置。
当前为
// ==UserScript== // @name X Timeline Manager // @description Tracks and syncs your last reading position on Twitter/X using Tampermonkey's internal storage. // @description:de Verfolgt und synchronisiert Ihre letzte Leseposition auf Twitter/X mit der internen Speicherung von Tampermonkey. // @description:es Rastrea y sincroniza tu última posición de lectura en Twitter/X utilizando el almacenamiento interno de Tampermonkey. // @description:fr Suit et synchronise votre dernière position de lecture sur Twitter/X en utilisant le stockage interne de Tampermonkey. // @description:zh-CN 跟踪并通过Tampermonkey的内部存储在Twitter/X上同步您的最后阅读位置。 // @description:ru Отслеживает и синхронизирует вашу последнюю позицию чтения на Twitter/X, используя внутреннее хранилище Tampermonkey. // @description:ja Tampermonkeyの内部ストレージを使用して、Twitter/Xでの最後の読書位置を追跡および同期します。 // @description:pt-BR Rastrea e sincroniza sua última posição de leitura no Twitter/X usando o armazenamento interno do Tampermonkey. // @description:hi Tampermonkey के आंतरिक संग्रहण का उपयोग करके Twitter/X पर आपकी अंतिम पठन स्थिति को ट्रैक और सिंक करता है. // @description:ar يتتبع ويزامن آخر موضع قراءة لك على Twitter/X باستخدام التخزين الداخلي لـ Tampermonkey. // @icon https://x.com/favicon.ico // @namespace http://tampermonkey.net/ // @version 2024.12.2 // @author Copiis // @license MIT // @match https://x.com/home // @grant GM_setValue // @grant GM_getValue // ==/UserScript== /* If you find this script useful and would like to support my work, consider making a small donation! Your generosity helps me maintain and improve projects like this one. 😊 Bitcoin (BTC): bc1quc5mkudlwwkktzhvzw5u2nruxyepef957p68r7 PayPal: https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE Thank you for your support! ❤️ */ (function () { let lastReadPost = null; // Letzte Leseposition let isAutoScrolling = false; let isSearching = false; window.onload = async () => { console.log("🚀 Seite vollständig geladen. Initialisiere Skript..."); await initializeScript(); }; async function initializeScript() { console.log("🔧 Lade Leseposition..."); await loadLastReadPostFromFile(); if (lastReadPost?.timestamp && lastReadPost?.authorHandler) { console.log(`📍 Geladene Leseposition: ${lastReadPost.timestamp}, @${lastReadPost.authorHandler}`); await startSearchForLastReadPost(); } else { console.warn("⚠️ Keine gültige Leseposition gefunden. Suche übersprungen."); } console.log("🔍 Starte Beobachtung für neue Beiträge..."); observeForNewPosts(); window.addEventListener("scroll", () => { if (!isAutoScrolling && !isSearching) { markCentralVisiblePost(true); } }); } async function loadLastReadPostFromFile() { try { const data = GM_getValue("lastReadPost", null); if (data) { lastReadPost = JSON.parse(data); console.log("✅ Leseposition erfolgreich geladen:", lastReadPost); } else { console.warn("⚠️ Keine gespeicherte Leseposition gefunden."); lastReadPost = null; } } catch (err) { console.error("⚠️ Fehler beim Laden der Leseposition:", err); lastReadPost = null; } } async function saveLastReadPostToFile() { try { if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) { console.warn("⚠️ Keine gültige Leseposition vorhanden. Speichern übersprungen."); return; } GM_setValue("lastReadPost", JSON.stringify(lastReadPost)); console.log("💾 Leseposition erfolgreich gespeichert:", lastReadPost); } catch (err) { console.error("❌ Fehler beim Speichern der Leseposition:", err); } } function observeForNewPosts() { const observer = new MutationObserver(() => { const newPostsIndicator = getNewPostsIndicator(); // Schwellenwert für die Scrollposition von oben auf <= 3 ändern if (newPostsIndicator && window.scrollY <= 3) { console.log("🆕 Neue Beiträge erkannt. Klicke auf den Indikator."); clickNewPostsIndicator(newPostsIndicator); } }); observer.observe(document.body, { childList: true, subtree: true }); } function getNewPostsIndicator() { return document.querySelector('div[aria-label*="ungelesene Elemente"]'); } function clickNewPostsIndicator(indicator) { if (!indicator) { console.warn("⚠️ Kein Indikator für neue Beiträge gefunden."); return; } indicator.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { indicator.click(); console.log("✅ Indikator für neue Beiträge geklickt."); if (!isSearching) { console.log("🔄 Starte die Suche nach der Lesestelle nach dem Laden neuer Beiträge..."); startSearchForLastReadPost(); } }, 500); } async function startSearchForLastReadPost() { if (!lastReadPost || !lastReadPost.timestamp || !lastReadPost.authorHandler) { console.log("❌ Keine gültige Leseposition verfügbar. Suche übersprungen."); return; } isSearching = true; isAutoScrolling = true; console.log("🔍 Suche nach der letzten Leseposition gestartet..."); const searchInterval = setInterval(() => { const matchedPost = findPostByData(lastReadPost); if (matchedPost) { clearInterval(searchInterval); isSearching = false; isAutoScrolling = false; scrollToPost(matchedPost); console.log(`🎯 Zuletzt gelesenen Beitrag gefunden: ${lastReadPost.timestamp}, @${lastReadPost.authorHandler}`); } else { console.log("🔄 Beitrag nicht direkt gefunden. Suche weiter unten."); window.scrollBy({ top: 500, behavior: "smooth" }); } }, 1000); } 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 getPostTimestamp(post) { const timeElement = post.querySelector("time"); return timeElement ? timeElement.getAttribute("datetime") : null; } function getPostAuthorHandler(post) { const handlerElement = post.querySelector('[role="link"][href*="/"]'); if (handlerElement) { const handler = handlerElement.getAttribute("href"); return handler && handler.startsWith("/") ? handler.slice(1) : null; } return null; } function markCentralVisiblePost(save = true) { const centralPost = getCentralVisiblePost(); if (!centralPost) { console.log("❌ Kein zentral sichtbarer Beitrag gefunden."); return; } const postTimestamp = getPostTimestamp(centralPost); const authorHandler = getPostAuthorHandler(centralPost); if (!postTimestamp || !authorHandler) { console.log("❌ Zentral sichtbarer Beitrag hat keine gültigen Daten."); return; } if (!lastReadPost || new Date(postTimestamp) > new Date(lastReadPost.timestamp)) { lastReadPost = { timestamp: postTimestamp, authorHandler }; console.log(`💾 Neuste Leseposition aktualisiert: ${postTimestamp}, @${authorHandler}`); if (save) saveLastReadPostToFile(); } } function getCentralVisiblePost() { const posts = Array.from(document.querySelectorAll("article")); const centerY = window.innerHeight / 2; return posts.reduce((closestPost, currentPost) => { const rect = currentPost.getBoundingClientRect(); const distanceToCenter = Math.abs(centerY - (rect.top + rect.bottom) / 2); if (!closestPost) return currentPost; const closestRect = closestPost.getBoundingClientRect(); const closestDistance = Math.abs(centerY - (closestRect.top + closestRect.bottom) / 2); return distanceToCenter < closestDistance ? currentPost : closestPost; }, null); } function scrollToPost(post) { if (!post) { console.log("❌ Kein Beitrag zum Scrollen gefunden."); return; } isAutoScrolling = true; post.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { isAutoScrolling = false; console.log("✅ Beitrag wurde erfolgreich zentriert!"); }, 1000); } })();