// ==UserScript== // @name X.com/Twitter Automation // @name:de X.com/Twitter Automatik // @name:es Automatización de X.com/Twitter // @name:fr Automatisation de X.com/Twitter // @name:it Automazione di X.com/Twitter // @name:pt Automação do X.com/Twitter // @name:ru Автоматизация X.com/Twitter // @name:zh X.com/Twitter 自动化 // @name:ja X.com/Twitter 自動化 // @name:ko X.com/Twitter 자동화 // @name:hi X.com/Twitter स्वचालन // @name:ar أتمتة X.com/Twitter // @namespace http://tampermonkey.net/ // @description Automatically loads new posts on X.com/Twitter and scrolls back to the reading position. // @description:de Lädt automatisch neue Beiträge auf X.com/Twitter und scrollt zur Leseposition zurück. // @description:es Carga automáticamente nuevos tweets en X.com/Twitter y vuelve a la posición de lectura. // @description:fr Charge automatiquement de nouveaux posts sur X.com/Twitter et revient à la position de lecture. // @description:it Carica automaticamente nuovi post su X.com/Twitter e torna alla posizione di lettura. // @description:pt Carrega automaticamente novos posts no X.com/Twitter e retorna à posição de leitura. // @description:ru Автоматически загружает новые посты на X.com/Twitter и возвращает к позиции чтения. // @description:zh 自动加载 X.com/Twitter 上的新帖子,并返回到阅读位置。 // @description:ja X.com/Twitterで新しい投稿を自動的に読み込み、読書位置に戻ります. // @description:ko X.com/Twitter에서 새 게시물을 자동으로 로드하고 읽던 위치로 돌아갑니다. // @description:hi X.com/Twitter पर नए पोस्ट स्वचालित रूप से लोड करता है और पढ़ने की स्थिति पर वापस ले जाता है. // @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 // @author Copiis // @version 2024.12.24-1 // @license MIT // @match https://x.com/home // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== (function () { let isAutomationActive = false; let isAutoScrolling = false; let savedTopPostData = null; window.onload = () => { console.log("Seite vollständig geladen. Initialisiere Script..."); initializeScript(); }; function initializeScript() { loadSavedData(); if (savedTopPostData) { console.log(`Gespeicherte Daten gefunden. Versuche zum gespeicherten Beitrag zu scrollen: Handler: ${savedTopPostData.authorHandler}, Timestamp: ${savedTopPostData.timestamp}`); scrollToSavedPost(); } else { console.log("Keine gespeicherten Daten gefunden. Automatik startet erst, wenn der Benutzer manuell scrollt."); } const observer = new MutationObserver(() => { if (isNearTopOfPage() && !isAutomationActive) { activateAutomation(); } if (isAutomationActive) { const newPostsButton = getNewPostsButton(); if (newPostsButton) { newPostsButton.click(); waitForNewPostsToLoad(() => scrollToSavedPost()); } } }); observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener('scroll', () => { if (isAutoScrolling) return; if (isNearTopOfPage() && !isAutomationActive) { activateAutomation(); } else if (window.scrollY > 3 && isAutomationActive) { deactivateAutomation(); } }); } function saveTopPostData() { const posts = Array.from(document.querySelectorAll("article")); // Alle sichtbaren Beiträge if (posts.length === 0) { console.log("Keine Beiträge sichtbar. Daten können nicht gespeichert werden."); return; } for (const post of posts) { const postTimestamp = getPostTimestamp(post); if (!postTimestamp) { console.log("Beitrag hat keinen gültigen Timestamp. Überspringe..."); continue; } const savedTimestamp = savedTopPostData?.timestamp; // Wenn kein gespeicherter Timestamp existiert oder der neue aktueller ist, speichere ihn if (!savedTimestamp || new Date(postTimestamp) > new Date(savedTimestamp)) { savedTopPostData = { timestamp: postTimestamp, authorHandler: getPostAuthorHandler(post), }; saveData(savedTopPostData); // Speichere die neuen Daten console.log(`Neuer Beitrag gespeichert: Handler: ${savedTopPostData.authorHandler}, Timestamp: ${savedTopPostData.timestamp}`); return; // Beende die Schleife, wenn gespeichert wurde } else { console.log(`Beitrag mit Timestamp ${postTimestamp} ist älter als gespeicherter Timestamp ${savedTimestamp}. Prüfe nächsten Beitrag...`); } } console.log("Kein neuer Beitrag gefunden. Keine Daten gespeichert."); } function waitForNewPostsToLoad(callback) { const interval = setInterval(() => { const posts = document.querySelectorAll("article"); if (posts.length > 0) { console.log("Neue Beiträge geladen."); clearInterval(interval); callback(); } else { console.log("Warte auf das Laden neuer Beiträge..."); } }, 500); } function isNearTopOfPage() { return window.scrollY <= 3; } function isPageFullyLoaded() { return document.readyState === "complete"; } function loadSavedData() { const savedData = GM_getValue("topPostData", null); if (savedData) { savedTopPostData = JSON.parse(savedData); console.log("Daten geladen:", savedTopPostData); } } function scrollToSavedPost() { const interval = setInterval(() => { if (!isPageFullyLoaded()) { console.log("Warte auf vollständiges Laden der Seite..."); return; } const matchedPost = findPostByData(savedTopPostData); if (matchedPost) { clearInterval(interval); console.log("Gespeicherter Beitrag gefunden. Warte auf vollständiges Laden..."); waitForPostToLoad(matchedPost, () => { console.log("Gespeicherter Beitrag vollständig geladen. Scrollen..."); scrollToPost(matchedPost, "center"); }); } else if (!isAtBottomOfPage()) { console.log("Scrollen nach unten, um mehr Beiträge zu laden..."); scrollWithLazyLoading(); } else { console.log("Gespeicherter Beitrag konnte nicht gefunden werden. Weitere Beiträge werden geladen..."); } }, 1000); } function waitForPostToLoad(post, callback) { const interval = setInterval(() => { if (isPostFullyLoaded(post)) { clearInterval(interval); callback(); } else { console.log("Warte darauf, dass der Beitrag vollständig geladen wird..."); } }, 500); } function isPostFullyLoaded(post) { const timeElement = post.querySelector("time"); const isTimeLoaded = timeElement && timeElement.getAttribute("datetime"); const authorElement = post.querySelector(".css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3"); const isAuthorLoaded = authorElement && authorElement.textContent.trim(); const images = Array.from(post.querySelectorAll("img")); const areImagesLoaded = images.every(img => img.complete && img.naturalWidth > 0); return isTimeLoaded && isAuthorLoaded && areImagesLoaded; } function scrollWithLazyLoading() { const interval = setInterval(() => { const posts = Array.from(document.querySelectorAll("article")); const lastPost = posts[posts.length - 1]; if (!lastPost || !isPostFullyLoaded(lastPost)) { console.log("Warte darauf, dass der letzte Beitrag vollständig geladen wird..."); return; } console.log("Letzter Beitrag vollständig geladen. Scrolle weiter..."); clearInterval(interval); window.scrollBy({ top: 500, behavior: "smooth" }); }, 1000); } function getNewPostsButton() { return Array.from(document.querySelectorAll("button, span")).find((button) => /neue Posts anzeigen|Post anzeigen/i.test(button.textContent.trim()) ); } function scrollToPost(post, position = "center") { isAutoScrolling = true; post.scrollIntoView({ behavior: "smooth", block: position }); setTimeout(() => { isAutoScrolling = false; }, 1000); } function isAtBottomOfPage() { return window.innerHeight + window.scrollY >= document.body.scrollHeight - 1; } function activateAutomation() { isAutomationActive = true; console.log("Automatik aktiviert."); saveTopPostData(); } function deactivateAutomation() { isAutomationActive = false; console.log("Automatik deaktiviert."); } function getPostTimestamp(post) { const timeElement = post.querySelector("time"); return timeElement?.getAttribute("datetime") || null; } function getPostAuthorHandler(post) { const authorElement = post.querySelector(".css-1jxf684.r-bcqeeo.r-1ttztb7.r-qvutc0.r-poiln3"); return authorElement?.textContent.trim() || null; } function saveData(data) { GM_setValue("topPostData", JSON.stringify(data)); console.log(`Daten dauerhaft gespeichert: Handler: ${data.authorHandler}, Timestamp: ${data.timestamp}`); } function findPostByData(data) { if (!data || !data.timestamp || !data.authorHandler) return null; const posts = Array.from(document.querySelectorAll("article")); return posts.find((post) => { const postTimestamp = getPostTimestamp(post); const postAuthorHandler = getPostAuthorHandler(post); return ( postTimestamp && postAuthorHandler && isTimestampMatching(postTimestamp, data.timestamp) && postAuthorHandler === data.authorHandler ); }); } function isTimestampMatching(postTimestamp, savedTimestamp) { if (!postTimestamp || !savedTimestamp) return false; const postDate = new Date(postTimestamp); const savedDate = new Date(savedTimestamp); return ( postDate.getFullYear() === savedDate.getFullYear() && postDate.getMonth() === savedDate.getMonth() && postDate.getDate() === savedDate.getDate() && postDate.getHours() === savedDate.getHours() && postDate.getMinutes() === savedDate.getMinutes() ); } })();