Automatically loads new posts on X.com/Twitter and scrolls back to the reading position.
目前為
// ==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
// ==/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()
);
}
})();