Lolz.live Dynamic Time

Показывает секунды первую минуту. Обновляет значение каждую секунду. До 1 часа считает время и также каждую минуту обновляет значение без обновления страницы. Воркает на устройстве (нагрузка).

目前為 2025-08-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Lolz.live Dynamic Time
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Показывает секунды первую минуту. Обновляет значение каждую секунду. До 1 часа считает время и также каждую минуту обновляет значение без обновления страницы. Воркает на устройстве (нагрузка).
// @author       eretly
// @match        https://lolz.live/*
// @match        https://lzt.market/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const activeElements = new WeakMap();
    let updateTimer = null;

    function pluralize(number, one, few, many) {
        number = Math.abs(number);
        if (number % 100 >= 11 && number % 100 <= 19) return many;
        switch (number % 10) {
            case 1: return one;
            case 2:
            case 3:
            case 4: return few;
            default: return many;
        }
    }

    function getSafeDiff(timestamp) {
        return Math.max(0, Math.floor(Date.now() / 1000) - timestamp);
    }

    function formatRelativeTime(diff) {
        diff = Math.max(0, diff);

        if (diff < 60) {
            return `${diff} ${pluralize(diff, 'секунду', 'секунды', 'секунд')} назад`;
        } else if (diff < 120) {
            return 'Минуту назад';
        } else if (diff < 3600) {
            const mins = Math.floor(diff / 60);
            return `${mins} мин. назад`;
        }
        return null;
    }

    function formatAbsoluteTime(el) {
        const timeStr = el.getAttribute('data-timestring');
        if (!timeStr) return null;
        return `Сегодня, в ${timeStr}`;
    }

    function formatTimeWithSeconds(timestamp) {
        const date = new Date(timestamp * 1000);
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const seconds = date.getSeconds().toString().padStart(2, '0');
        return `${hours}:${minutes}:${seconds}`;
    }

    function updateTimes() {
        let needsUpdate = false;

        document.querySelectorAll('abbr.DateTime[data-time]').forEach(el => {
            if (!activeElements.has(el)) return;

            const timestamp = parseInt(el.getAttribute('data-time'));
            const diff = getSafeDiff(timestamp);

            if (diff < 3600) {
                const newText = formatRelativeTime(diff);
                if (newText && el.textContent !== newText) {
                    el.textContent = newText;
                }
                needsUpdate = true;
            } else if (diff >= 3600) {
                const absText = formatAbsoluteTime(el);
                if (absText) {
                    el.textContent = absText;
                }
                activeElements.delete(el);
            }
        });

        if (needsUpdate && !updateTimer) {
            updateTimer = setTimeout(() => {
                updateTimer = null;
                updateTimes();
            }, 1000);
        } else if (!needsUpdate && updateTimer) {
            clearTimeout(updateTimer);
            updateTimer = null;
        }
    }

    function processNewElement(el) {
        const currentText = el.textContent;
        if (/сегодня|вчера|\d{1,2} \w+ \d{4}/i.test(currentText)) return false;

        const timestamp = parseInt(el.getAttribute('data-time'));
        const diff = getSafeDiff(timestamp);

        if (diff < 3600) {
            el.setAttribute('data-original-text', currentText);
            const newText = formatRelativeTime(diff);
            if (newText) el.textContent = newText;
            activeElements.set(el, true);
            return true;
        }

        return false;
    }

    function processMessageTimeElements() {
        document.querySelectorAll('.messageDate[data-absolutetime]').forEach(el => {
            const timestamp = parseInt(el.getAttribute('data-absolutetime'));
            if (!isNaN(timestamp)) {
                const newTime = formatTimeWithSeconds(timestamp);
                el.textContent = newTime;
                el.setAttribute('data-processed', 'true');
            }
        });
    }

    const observer = new MutationObserver(mutations => {
        let shouldUpdate = false;
        let shouldProcessMessages = false;

        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === 1) {
                    const timeNodes = node.matches('abbr.DateTime[data-time]')
                        ? [node]
                        : Array.from(node.querySelectorAll('abbr.DateTime[data-time]'));

                    timeNodes.forEach(el => {
                        if (processNewElement(el)) {
                            shouldUpdate = true;
                        }
                    });

                    if (node.matches('.messageDate[data-absolutetime]') ||
                        node.querySelector('.messageDate[data-absolutetime]')) {
                        shouldProcessMessages = true;
                    }
                }
            });
        });

        if (shouldUpdate) {
            setTimeout(updateTimes, 50);
        }
        if (shouldProcessMessages) {
            setTimeout(processMessageTimeElements, 50);
        }
    });

    function init() {
        let needsUpdate = false;

        document.querySelectorAll('abbr.DateTime[data-time]').forEach(el => {
            if (processNewElement(el)) {
                needsUpdate = true;
            }
        });

        processMessageTimeElements();

        if (needsUpdate) {
            updateTimes();
        }

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

        ['click', 'scroll', 'mousemove'].forEach(event => {
            document.addEventListener(event, () => {
                setTimeout(updateTimes, 100);
            }, { passive: true });
        });
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }

    window.addEventListener('unload', () => {
        observer.disconnect();
        if (updateTimer) clearTimeout(updateTimer);
    });
})();