Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.
目前為
// ==UserScript==
// @name X.com Timeline Auto-Load with Uninterrupted Reading
// @name:de X.com Timeline Auto-Load mit unterbrechungsfreiem Lesen
// @name:fr X.com Timeline Auto-Load avec lecture ininterrompue
// @name:es Carga automática de la línea de tiempo de X.com con lectura sin interrupciones
// @name:it Caricamento automatico della timeline di X.com con lettura ininterrotta
// @name:zh X.com 时间线自动加载,无缝阅读
// @name:ja X.com タイムライン自動読み込みと中断のない読書
// @namespace http://tampermonkey.net/
// @description Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.
// @description:de Lädt automatisch neue Beiträge auf X.com, ohne die Leseposition zu verlieren. Setzt eine virtuelle Markierung am letzten sichtbaren Handler (z. B. @Benutzername) vor dem Laden neuer Beiträge und stellt die Ansicht zu dieser Markierung wieder her.
// @description:fr Charge automatiquement les nouveaux messages sur X.com tout en conservant la position de lecture. Place un marqueur virtuel au dernier handle visible (par exemple, @nomutilisateur) avant de charger les nouveaux messages et restaure la vue à ce marqueur.
// @description:es Carga automáticamente nuevos posts en X.com mientras mantiene la posición de lectura intacta. Coloca un marcador virtual en el último manejador visible (por ejemplo, @nombredeusuario) antes de cargar nuevos posts y restaura la vista a ese marcador.
// @description:it Carica automaticamente nuovi post su X.com mantenendo intatta la posizione di lettura. Imposta un segnalibro virtuale sull'ultimo handle visibile (es. @nomeutente) prima di caricare nuovi post e ripristina la vista su quel segnalibro.
// @description:zh 在X.com上自动加载新帖子,同时保持阅读位置不变。在加载新帖子之前,在最后一个可见的处理器(例如@用户名)处设置一个虚拟标记,并将视图恢复到该标记。
// @description:ja X.comで新しい投稿を自動的に読み込み、読書位置をそのまま保持します。新しい投稿を読み込む前に、最後に見えるハンドル(例:@ユーザー名)に仮想マーカーを設定し、このマーカーにビューを復元します。
// @author Copiis
// @version 2024.12.22-1
// @license MIT
// @match https://x.com/home
// @icon https://cdn-icons-png.flaticon.com/128/14417/14417460.png
// @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 (isAtTopOfPage() && !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 (window.scrollY === 0 && !isAutomationActive) {
activateAutomation();
} else if (window.scrollY > 0 && isAutomationActive) {
deactivateAutomation();
}
});
}
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. Scrollen...");
scrollToPost(matchedPost, "center");
} else if (!isAtBottomOfPage()) {
console.log("Scrollen nach unten, um mehr Beiträge zu laden...");
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
} else {
console.log("Gespeicherter Beitrag konnte nicht gefunden werden. Weitere Beiträge werden geladen...");
}
}, 500);
}
function activateAutomation() {
isAutomationActive = true;
console.log("Automatik aktiviert.");
saveTopPostData();
}
function deactivateAutomation() {
isAutomationActive = false;
console.log("Automatik deaktiviert.");
}
function saveTopPostData() {
const topPost = getTopVisiblePost();
if (topPost) {
savedTopPostData = {
timestamp: getPostTimestamp(topPost),
authorHandler: getPostAuthorHandler(topPost),
};
saveData(savedTopPostData);
}
}
function loadSavedData() {
const savedData = GM_getValue("topPostData", null);
if (savedData) {
savedTopPostData = JSON.parse(savedData);
}
}
function saveData(data) {
GM_setValue("topPostData", JSON.stringify(data));
console.log(`Daten dauerhaft gespeichert: Handler: ${data.authorHandler}, Timestamp: ${data.timestamp}`);
}
function waitForNewPostsToLoad(callback) {
const checkInterval = setInterval(() => {
if (isPageFullyLoaded()) {
clearInterval(checkInterval);
callback();
}
}, 100);
}
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 === data.timestamp && postAuthorHandler === data.authorHandler;
});
}
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 getTopVisiblePost() {
const posts = Array.from(document.querySelectorAll("article"));
return posts.length > 0 ? posts[0] : null;
}
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 isPageFullyLoaded() {
return document.readyState === "complete";
}
function isAtTopOfPage() {
return window.scrollY === 0;
}
function isAtBottomOfPage() {
return window.innerHeight + window.scrollY >= document.body.scrollHeight - 1;
}
})();