Telegram Web - Robust Scroll Fix (v1.4 - Image/Load Handling)

Attempts to fix scroll issues more robustly, handling late-loading images and initial load scenarios.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Telegram Web - Robust Scroll Fix (v1.4 - Image/Load Handling)
// @name:zh-CN   电报网页版 - 健壮滚动修复 (v1.4 - 图片/加载处理)
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Attempts to fix scroll issues more robustly, handling late-loading images and initial load scenarios.
// @description:zh-CN 更健壮地修复滚动问题,尝试处理延迟加载的图片和初始加载场景。
// @author       AI Assistant & You
// @match        *://web.telegram.org/k/*
// @grant        GM_addStyle
// @icon         https://web.telegram.org/favicon.ico
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 ---
    const SCROLL_THRESHOLD = 300;         // 判断“接近底部”的阈值 (像素)
    const POST_CORRECTION_CHECK_DELAY = 150; // rAF修正后再次检查的延迟(ms), 处理图片等慢加载
    const INITIAL_SCROLL_DELAY = 1800;    // 初始加载后检查滚动的延迟(ms), 增加时间
    const CHECK_INTERVAL = 1000;        // 查找容器间隔
    const SCROLL_CONTAINER_SELECTOR = '#column-center .scrollable.scrollable-y'; // K 版滚动容器
    // --- --- ---

    let targetNode = null; // 滚动容器
    let observer = null;
    let correctionFrameId = null; // 用于 requestAnimationFrame
    let postCorrectionTimeoutId = null; // 后置检查计时器
    let wasNearBottom = false; // 由滚动事件更新
    let isScrolling = false; // 标记用户是否正在滚动
    let scrollDebounceTimeout = null;
    let styleAdded = false; // 标记样式是否已添加

    console.log("[ScrollFixer v1.4] Script initializing...");

    // 注入 CSS 样式
    function applyStyles() {
        if (!styleAdded) {
            try {
                GM_addStyle(`
                    ${SCROLL_CONTAINER_SELECTOR} {
                        overflow-anchor: auto !important;
                    }
                    .scrollable-y.script-scrolling {
                         scroll-behavior: auto !important;
                    }
                `);
                styleAdded = true;
                console.log("[ScrollFixer v1.4] Applied CSS via GM_addStyle.");
            } catch (e) {
                console.error("[ScrollFixer v1.4] GM_addStyle failed.", e);
                const styleElement = document.createElement('style');
                 styleElement.textContent = `
                    ${SCROLL_CONTAINER_SELECTOR} { overflow-anchor: auto !important; }
                    .scrollable-y.script-scrolling { scroll-behavior: auto !important; }
                 `;
                 document.head.appendChild(styleElement);
                 console.warn("[ScrollFixer v1.4] Using fallback style injection.");
            }
        }
    }

    function isNearBottom(element) {
        if (!element) return false;
        const isScrollable = element.scrollHeight > element.clientHeight;
        return !isScrollable || (element.scrollHeight - element.scrollTop - element.clientHeight < SCROLL_THRESHOLD);
    }

     // 检查是否 *明确地* 远离底部
     function isFarFromBottom(element) {
        if (!element) return false; // 如果没有元素,不认为远离 (避免错误滚动)
        const isScrollable = element.scrollHeight > element.clientHeight;
        // 只有可滚动且距离底部大于阈值才算远离
        return isScrollable && (element.scrollHeight - element.scrollTop - element.clientHeight >= SCROLL_THRESHOLD);
    }

    function scrollToBottom(element, reason = "correction", behavior = 'auto') {
        if (!element || !document.contains(element)) return;
        const currentScroll = element.scrollTop;
        const maxScroll = element.scrollHeight - element.clientHeight;
        // 增加容错
        if (maxScroll - currentScroll < 1) {
             // console.log(`[ScrollFixer v1.4] Already at bottom (${reason}), scroll skipped.`);
            // 即使跳过滚动,也要确保状态正确
            if (!wasNearBottom) { // 如果之前认为不在底部,现在强制设为在底部
                wasNearBottom = true;
                // console.log("[ScrollFixer v1.4] State corrected to wasNearBottom=true after skip.");
            }
            return;
        }
        console.log(`[ScrollFixer v1.4] Forcing scroll to bottom (Reason: ${reason}, Behavior: ${behavior}).`);

        element.classList.add('script-scrolling');
        element.scrollTo({ top: element.scrollHeight, behavior: behavior });
        // 滚动后立即更新状态
        wasNearBottom = true;

        requestAnimationFrame(() => {
             requestAnimationFrame(() => {
                 if (element) element.classList.remove('script-scrolling');
             });
        });
    }

    // 处理滚动事件
    function handleScroll() {
        if (!targetNode) return;
        if (targetNode.classList.contains('script-scrolling')) {
             return; // 忽略脚本触发的滚动
        }
        isScrolling = true;
        clearTimeout(scrollDebounceTimeout);
        wasNearBottom = isNearBottom(targetNode);
        // console.log(`[ScrollFixer v1.4] Scroll event. Near bottom: ${wasNearBottom}`);
        scrollDebounceTimeout = setTimeout(() => {
            isScrolling = false;
            // console.log("[ScrollFixer v1.4] Scroll ended.");
        }, 150);
    }


    // 处理 DOM 变化的函数 - 即时 rAF + 后置检查
    function handleMutations(mutationsList, observer) {
        if (isScrolling) {
            return;
        }

        if (wasNearBottom && targetNode) {
            // console.log("[ScrollFixer v1.4] Mutation detected while near bottom. Scheduling immediate rAF correction.");

            // 取消上一个可能未执行的帧和后置检查
            if (correctionFrameId) cancelAnimationFrame(correctionFrameId);
            clearTimeout(postCorrectionTimeoutId); // 清除上一个后置检查

            // 立即请求下一帧执行修正
            correctionFrameId = requestAnimationFrame(() => {
                correctionFrameId = null; // 清除 ID
                if (targetNode && document.contains(targetNode)) {
                    // console.log("[ScrollFixer v1.4] Performing immediate rAF scroll correction.");
                    scrollToBottom(targetNode, "mutation_rAF", 'auto');

                    // --- 新增:rAF 修正后,安排一个后置检查 ---
                    postCorrectionTimeoutId = setTimeout(() => {
                        postCorrectionTimeoutId = null; // 清除 ID
                        if (targetNode && document.contains(targetNode) && !isScrolling) {
                             // console.log("[ScrollFixer v1.4] Performing post-correction check.");
                             if (!isNearBottom(targetNode)) { // 如果此时因为图片加载等原因又不在底部了
                                 console.warn("[ScrollFixer v1.4] Post-correction check failed. Scrolling again.");
                                 scrollToBottom(targetNode, "post_check", 'auto'); // 再次滚动
                             } else {
                                 // console.log("[ScrollFixer v1.4] Post-correction check passed.");
                                 // 确保状态正确
                                 wasNearBottom = true;
                             }
                        }
                    }, POST_CORRECTION_CHECK_DELAY);
                    // --- --- ---

                } else {
                    // console.warn("[ScrollFixer v1.4] Target node lost before immediate rAF execution.");
                }
            });
        }
    }

    // 查找并观察消息列表容器
    function findAndObserve() {
        const targetSelector = SCROLL_CONTAINER_SELECTOR;
        let potentialNode = document.querySelector(targetSelector);

        if (observer && targetNode === potentialNode && document.contains(targetNode)) {
            return;
        }

        // Cleanup
        if (observer) { /* ... 清理逻辑 ... */ }
        targetNode = potentialNode;

        if (targetNode) {
            console.log("[ScrollFixer v1.4] Chat scroll container found:", targetNode);
            applyStyles();

            // 初始滚动检查 - 更保守
            setTimeout(() => {
                if (targetNode && document.contains(targetNode)) {
                    console.log("[ScrollFixer v1.4] Performing initial check...");
                    // 只有当明确检测到远离底部时,才执行初始滚动
                    if (isFarFromBottom(targetNode)) {
                        console.log("[ScrollFixer v1.4] Far from bottom on initial check. Scrolling down.");
                        scrollToBottom(targetNode, "initial_load", 'auto');
                        wasNearBottom = true;
                    } else {
                        console.log("[ScrollFixer v1.4] Already near bottom or content not fully loaded? Initial scroll skipped/deferred.");
                        // 确保状态与实际情况一致
                        wasNearBottom = isNearBottom(targetNode);
                    }
                    console.log(`[ScrollFixer v1.4] Initial state - near bottom: ${wasNearBottom}`);

                    // Setup listeners/observer
                    if (!observer) {
                        targetNode.addEventListener('scroll', handleScroll, { passive: true });
                        console.log("[ScrollFixer v1.4] Scroll listener added.");
                        observer = new MutationObserver(handleMutations);
                        const config = { childList: true, subtree: true };
                        observer.observe(targetNode, config);
                        console.log("[ScrollFixer v1.4] MutationObserver started.");
                    }
                } else {
                    console.log("[ScrollFixer v1.4] Target node disappeared before initial setup.");
                }
            }, INITIAL_SCROLL_DELAY);

        } else {
             console.log(`[ScrollFixer v1.4] Chat container not found, retrying...`);
             setTimeout(findAndObserve, CHECK_INTERVAL);
        }
    }

    // --- SPA Navigation Handling & Initial Start ---
    // (与 v1.3 类似,注意清理 postCorrectionTimeoutId)
     let currentUrl = location.href;
     const urlObserver = new MutationObserver(() => {
         if (location.href !== currentUrl) {
             console.log(`[ScrollFixer v1.4] URL changed. Re-initializing for ${location.href}`);
             currentUrl = location.href;
             // Cleanup state and timers
             if (observer) { observer.disconnect(); observer = null; }
             if (targetNode && typeof targetNode.removeEventListener === 'function') {
                  targetNode.removeEventListener('scroll', handleScroll);
             }
             if (correctionFrameId) { cancelAnimationFrame(correctionFrameId); correctionFrameId = null; }
             clearTimeout(postCorrectionTimeoutId); // 清理后置检查计时器
             targetNode = null;
             wasNearBottom = false;
             isScrolling = false;
             clearTimeout(scrollDebounceTimeout);
             // styleAdded = false; // GM_addStyle persists
             setTimeout(findAndObserve, 500);
         }
     });
     urlObserver.observe(document.body, { childList: true, subtree: false });

     setTimeout(findAndObserve, 750);

})();