Greasy Fork

Greasy Fork is available in English.

X.com Timeline Auto-Refresh with Accurate Centering

Automatically refreshes the timeline on X.com, setting dual markers (top and bottom) around the last visible post for accurate centering after loading new posts. Includes customizable intervals via Tampermonkey settings.

目前为 2024-11-17 提交的版本。查看 最新版本

// ==UserScript==
// @name         X.com Timeline Auto-Refresh with Accurate Centering
// @namespace    http://tampermonkey.net/
// @version      4.12
// @description  Automatically refreshes the timeline on X.com, setting dual markers (top and bottom) around the last visible post for accurate centering after loading new posts. Includes customizable intervals via Tampermonkey settings.
// @icon         https://drive.google.com/uc?id=17E9lg_mYtvA66nndCOkfvfalWE1R__b1
// @author       Copiis
// @match        https://x.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @contributionURL https://paypal.me/Coopiis?country.x=DE&locale.x=de_DE
// @contributionAmount $5
//
// @description:de Aktualisiert die Timeline auf X.com automatisch, setzt Marker am Anfang und Ende des letzten sichtbaren Beitrags und zentriert ihn nach dem Neuladen der Beiträge präzise im sichtbaren Bereich. Intervall über Tampermonkey konfigurierbar.
// @description:fr Actualise automatiquement la timeline sur X.com, en plaçant des marqueurs en haut et en bas du dernier message visible pour un centrage précis après le chargement de nouveaux messages. Intervalle personnalisable via les paramètres Tampermonkey.
// @description:it Aggiorna automaticamente la timeline su X.com, posizionando marcatori nella parte superiore e inferiore dell'ultimo post visibile per centrarlo con precisione dopo il caricamento di nuovi post. Intervallo configurabile tramite le impostazioni di Tampermonkey.
// @description:es Actualiza automáticamente la línea de tiempo en X.com, colocando marcadores en la parte superior e inferior de la última publicación visible para centrarla con precisión después de cargar nuevas publicaciones. Intervalo personalizable a través de la configuración de Tampermonkey.
// @description:zh 自动刷新X.com的时间线,在最后一个可见帖子顶部和底部设置标记,并在加载新帖子后精确居中。可通过Tampermonkey配置间隔时间。
// @description:ja X.comのタイムラインを自動的に更新し、最後に表示された投稿の上部と下部にマーカーを設定し、新しい投稿の読み込み後に正確に中央揃えします。Tampermonkey設定で間隔を構成可能。
// ==/UserScript==

(function () {
    'use strict';

    // Default interval in milliseconds (10 seconds)
    const DEFAULT_INTERVAL = 10000;

    // Get the user-defined interval or fall back to the default
    let refreshInterval = GM_getValue('refreshInterval', DEFAULT_INTERVAL);
    let intervalId;

    // Function to set the interval via Tampermonkey menu
    function configureInterval() {
        const userInput = prompt(
            'Set refresh interval in seconds:',
            refreshInterval / 1000
        );
        if (userInput !== null) {
            const newInterval = parseInt(userInput, 10) * 1000;
            if (!isNaN(newInterval) && newInterval >= 1000) {
                GM_setValue('refreshInterval', newInterval);
                refreshInterval = newInterval;
                restartInterval();
                alert(`Refresh interval set to ${refreshInterval / 1000} seconds.`);
            } else {
                alert('Invalid input. Please enter a number greater than or equal to 1.');
            }
        }
    }

    // Register Tampermonkey menu command
    GM_registerMenuCommand('Set Refresh Interval', configureInterval);

    // Create a marker element
    function createScrollMarker(id) {
        const marker = document.createElement('div');
        marker.id = id;
        marker.style.position = 'absolute';
        marker.style.width = '1px';
        marker.style.height = '1px';
        marker.style.backgroundColor = 'transparent';
        marker.style.zIndex = '9999';
        document.body.appendChild(marker);
        return marker;
    }

    // Place markers at the top and bottom of the last visible post
    function placeScrollMarkers() {
        const posts = Array.from(document.querySelectorAll('article')).filter(isElementAccessible);
        if (posts.length > 0) {
            const lastVisiblePost = posts[posts.length - 1];
            const topMarker = document.getElementById('scroll-marker-top') || createScrollMarker('scroll-marker-top');
            const bottomMarker = document.getElementById('scroll-marker-bottom') || createScrollMarker('scroll-marker-bottom');
            const postRect = lastVisiblePost.getBoundingClientRect();

            // Place the top marker at the top of the post
            topMarker.style.top = `${window.scrollY + postRect.top}px`;

            // Place the bottom marker at the bottom of the post
            bottomMarker.style.top = `${window.scrollY + postRect.bottom}px`;

            console.log("Scroll markers placed around:", lastVisiblePost);
        } else {
            console.warn("No visible posts found to place the scroll markers.");
        }
    }

    // Scroll to the center of the post using precise calculations
    function scrollToMarkers() {
        const topMarker = document.getElementById('scroll-marker-top');
        const bottomMarker = document.getElementById('scroll-marker-bottom');

        if (topMarker && bottomMarker) {
            const topMarkerRect = topMarker.getBoundingClientRect();
            const bottomMarkerRect = bottomMarker.getBoundingClientRect();

            // Calculate the exact center of the post
            const postHeight = bottomMarkerRect.top - topMarkerRect.top;
            const centerY = topMarkerRect.top + postHeight / 2;

            // Scroll to center the post
            window.scrollTo({
                top: centerY + window.scrollY - window.innerHeight / 2,
                behavior: 'smooth'
            });

            console.log("Scrolled to the center of the post.");
            topMarker.remove(); // Clean up the markers
            bottomMarker.remove();
        } else {
            console.warn("Markers not found for scrolling.");
        }
    }

    // Helper function to check if an element is visible and not aria-hidden
    function isElementAccessible(element) {
        const style = window.getComputedStyle(element);
        return (
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            element.getAttribute('aria-hidden') !== 'true'
        );
    }

    // Find and click the button for new posts
    function clickShowPostButton() {
        const buttons = document.querySelectorAll('span.css-1jxf684');
        let clicked = false;
        buttons.forEach(span => {
            if (span.textContent.includes("Post anzeigen") || span.textContent.includes("neue Posts anzeigen")) {
                const button = span.closest('button');
                if (button) {
                    placeScrollMarkers(); // Place the markers before clicking the button
                    button.click();
                    console.log(`Clicked button with text: "${span.textContent}"`);
                    clicked = true;

                    // Wait for new posts to load and then scroll to the markers
                    setTimeout(() => {
                        scrollToMarkers();
                    }, 1000); // Adjust delay if necessary
                }
            }
        });
        if (!clicked) {
            console.warn("No 'Show Posts' button found.");
        }
    }

    // Refresh the timeline and handle scroll restoration
    function refreshTimeline() {
        if (window.scrollY === 0) {
            clickShowPostButton(); // Click the button to refresh and handle marker placement
        } else {
            console.log("User is not at the top of the page. Skipping refresh.");
        }
    }

    // Restart the interval with the updated value
    function restartInterval() {
        if (intervalId) {
            clearInterval(intervalId);
        }
        intervalId = setInterval(refreshTimeline, refreshInterval);
    }

    // Initialize the script
    function init() {
        restartInterval(); // Start the interval
    }

    init();
})();