Greasy Fork

Greasy Fork is available in English.

X.com Timeline Auto-Load with Uninterrupted Reading

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.

当前为 2024-11-21 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 タイムライン自動読み込みと中断のない読書
// @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で新しい投稿を自動的に読み込み、読書位置をそのまま保持します。新しい投稿を読み込む前に、最後に見えるハンドル(例:@ユーザー名)に仮想マーカーを設定し、このマーカーにビューを復元します。
// @namespace          http://tampermonkey.net/
// @version            2024.12.22
// @icon               https://cdn-icons-png.flaticon.com/128/14417/14417460.png
// @author             Copiis
// @match              https://x.com/home
// @grant              none
// @license            MIT
// ==/UserScript==

(function () {
    let isAutomationActive = false;
    let isAutoScrolling = false;
    let savedTopPostTimestamp = null;
    let regularSaveInterval = null;

    document.addEventListener('DOMContentLoaded', () => {
        console.log("[DEBUG] Seite geladen. Starte regelmäßiges Speichern des obersten Beitrags...");
        startRegularTopPostSave();
    });

    const observer = new MutationObserver(() => {
        if (isAtTopOfPage() && !isAutomationActive) {
            console.log("[DEBUG] Scrollen zum oberen Ende erkannt. Automatik wird aktiviert.");
            extractTopPostTimestamp();
            isAutomationActive = true;
        }

        if (isAutomationActive) {
            const newPostsButton = getNewPostsButton();
            if (newPostsButton) {
                console.log("[DEBUG] 'Neue Beiträge anzeigen'-Button gefunden. Speichere obersten Beitrag und klicke.");
                extractTopPostTimestamp(); // Timestamp des obersten Beitrags speichern
                newPostsButton.click(); // Button klicken
                waitForNewPostsToLoad();
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    window.addEventListener('scroll', () => {
        if (isAutoScrolling) {
            console.log("[DEBUG] Automatisches Scrollen erkannt. Scroll-Event ignoriert.");
            return;
        }

        if (window.scrollY === 0 && !isAutomationActive) {
            console.log("[DEBUG] Scrollen zum oberen Ende erkannt. Automatik wird aktiviert.");
            extractTopPostTimestamp();
            isAutomationActive = true;
        } else if (window.scrollY > 0 && isAutomationActive) {
            console.log("[DEBUG] Automatik deaktiviert, da nicht oben.");
            isAutomationActive = false;
        }
    });

    function startRegularTopPostSave() {
        regularSaveInterval = setInterval(() => {
            if (isAtTopOfPage()) {
                console.log("[DEBUG] Speichere regelmäßig Timestamp des obersten Beitrags...");
                extractTopPostTimestamp();
            }
        }, 1000); // Speichere alle 1000ms, wenn ganz oben
    }

    function isAtTopOfPage() {
        return window.scrollY === 0;
    }

    function getNewPostsButton() {
        return Array.from(document.querySelectorAll('button, span')).find((button) =>
            /neue Posts anzeigen|Post anzeigen/i.test(button.textContent.trim())
        );
    }

    function extractTopPostTimestamp() {
        const topPost = getTopVisiblePost();
        if (topPost) {
            savedTopPostTimestamp = getPostTimestamp(topPost);
            console.log("[DEBUG] Timestamp des obersten Beitrags gespeichert:", savedTopPostTimestamp);
        } else {
            console.log("[DEBUG] Kein oberster Beitrag gefunden.");
        }
    }

    function getPostTimestamp(post) {
        const timeElement = post.querySelector('time');
        return timeElement?.getAttribute('datetime') || null;
    }

    function getTopVisiblePost() {
        const posts = Array.from(document.querySelectorAll('article'));
        console.log(`[DEBUG] Anzahl gefundener Beiträge: ${posts.length}`);
        return posts.length > 0 ? posts[0] : null;
    }

    function waitForNewPostsToLoad() {
        console.log("[DEBUG] Warte auf das Laden neuer Beiträge...");
        const checkInterval = setInterval(() => {
            const matchedPost = findPostByTimestamp(savedTopPostTimestamp);
            if (matchedPost) {
                clearInterval(checkInterval);
                console.log("[DEBUG] Gespeicherter Beitrag nach Laden gefunden. Scrollen...");
                scrollToPost(matchedPost, 'end');
            }
        }, 100);
    }

    function findPostByTimestamp(timestamp) {
        if (!timestamp) return null;
        const posts = Array.from(document.querySelectorAll('article'));
        return posts.find((post) => {
            const timeElement = post.querySelector('time');
            const postTimestamp = timeElement?.getAttribute('datetime');
            return postTimestamp === timestamp;
        });
    }

    function scrollToPost(post, position = 'end') {
        console.log(`[DEBUG] Scrollen zum gespeicherten Beitrag (Position: ${position})...`);
        isAutoScrolling = true;
        post.scrollIntoView({ behavior: 'smooth', block: position });
        setTimeout(() => {
            isAutoScrolling = false;
            isAutomationActive = false;
            console.log("[DEBUG] Automatisches Scrollen beendet. Automatik deaktiviert.");
        }, 1000);
    }
})();