LZT Contests Info

Информация о розыгрышах в профиле LZT с кэшем на 10 минут

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         LZT Contests Info
// @namespace    LZTContestsInfo
// @version      1.4
// @description  Информация о розыгрышах в профиле LZT с кэшем на 10 минут
// @author       llimonix
// @match        https://zelenka.guru/*
// @match        https://lzt.market/*
// @match        https://lolz.guru/*
// @match        https://lolz.live/*
// @match        https://zelenka.guru
// @match        https://lzt.market
// @match        https://lolz.guru
// @match        https://lolz.live
// @icon         https://cdn-icons-png.flaticon.com/512/5899/5899678.png
// @license      MIT
// ==/UserScript==

(function() {
    $('<style>').text(`
        .contestsInfoContainer {
            border-top: 1px solid #3D3D3D;
            display: flex;
            margin-bottom: 10px;
            flex-wrap: wrap;
            align-items: flex-start;
            justify-content: space-between;
            display: -webkit-box;
            display: -webkit-flex;
            -webkit-align-items: flex-start;
            -webkit-flex-wrap: wrap;
            -webkit-justify-content: space-between;
        }
        .contestsInfo {
            margin: 15px 0 0;
            padding: 0 10px;
            background: #2f2f2f;
            border-radius: 8px;
            height: 42px;
            line-height: 40px;
            box-sizing: border-box;
            display: inline-block;
            font-weight: 600;
            width: 49%;
            transition: 0.3s;
            box-sizing: border-box;
        }
        .contestsInfo .data {
            margin: 0 0 0 10px;
            white-space: nowrap;
            overflow: hidden;
            max-width: 78%;
            display: inline-block;
            text-overflow: ellipsis;
            user-select: all;
        }
        .contestsInfo .contactIcon {
            font-size: 20px;
            line-height: 42px;
            float: left;
        }
        .constestsInfoDetails.button {
            background-color: #1c1c1c;
        }
        .LZTContestsInfo {
            display: flex;
            flex-wrap: wrap;
            gap: 12px;
        }
        .LZTContestsInfo .LZTContestsItem {
            height: 63px;
            background: #2D2D2D;
            border-radius: 8px;
            padding: 12px;
            box-sizing: border-box;
            display: flex;
            align-items: center;
            white-space: normal;
            flex: 1 1 100%;
            max-width: 100%;
            font-weight: 700;
        }
        @media screen and (min-width: 700px) {
            .LZTContestsInfo .LZTContestsItem {
                flex: 1 1 calc((100% / 2) - 24px);
            }
        }
        .LZTContestsIcon {
            margin-right: 12px;
        }
        .LZTContestsInfo .LZTContestsItem i {
            width: 24px;
            height: 24px;
            font-size: 24px;
        }
        .LZTContestsInfo--status {
            display: flex;
            color: #949494;
            background: #303030;
            padding: 15px 20px;
            border-radius: 12px;
            flex-direction: column;
        }
        .LZTContestsInfo--status .statusTitle {
            font-size: 16px;
            font-weight: 700;
            margin: 0 0 4px;
            color: #FF6A70;
        }
        .LZTContestsInfoWrapper {
            display: flex;
            flex-wrap: wrap;
        }
        .xenOverlay .userStatCounters.hasContacts,
        .Responsive .xenOverlay .userStatCounters.hasContacts {
            margin-bottom: 0px;
        }
        .xenOverlay .userStatCounters {
            margin-bottom: 20px!important;
        }
        @media (max-width: 610px) {
            .Responsive .xenOverlay.memberCard .bottom .contestsInfo {
                margin: 10px 0 0;
                width: 100%;
            }
        }
     `).appendTo('head');

    function getUserContestData(userId) {
        return new Promise(function(resolve, reject) {
            const cacheKey = 'LZTUserData_' + userId;
            const cachedRaw = sessionStorage.getItem(cacheKey);
            const TEN_MIN = 10 * 60 * 1000;

            if (cachedRaw) {
                try {
                    const cached = JSON.parse(cachedRaw);
                    if (Date.now() - cached.timestamp < TEN_MIN) {
                        console.log('[LZT Contests Info] Использую кэш для ' + userId);
                        return resolve(cached.data);
                    } else {
                        console.log('[LZT Contests Info] Кэш устарел, обновляю...');
                    }
                } catch(e) {
                    console.warn('[LZT Contests Info] Ошибка чтения кэша:', e);
                }
            }

            $.ajax({
                url: 'https://lzt-winners-contests.vercel.app/user/' + userId + '?include_ranks=true',
                dataType: 'json',
                success: function(data) {
                    sessionStorage.setItem(cacheKey, JSON.stringify({
                        timestamp: Date.now(),
                        data: data
                    }));
                    resolve(data);
                },
                error: function(xhr, status, err) {
                    reject(err || status);
                }
            });
        });
    }

    XenForo.register(".profilePage .insuranceDeposit", function() {
        var $el = $(this);

        var userLink = $('.userContentLinks a').first();
        if (!userLink.length) return;

        var userIdMatch = userLink.attr('href').match(/\/(\d+)\/?$/);
        if (!userIdMatch) return;
        var userId = userIdMatch[1];

        var contestsBlock = $('.section.contestsInfoWin, .section.contestsInfoCreate').length > 0;
        if (contestsBlock) return;

        $el.after(`
            <div class="section contestsInfoWin">
                <div class="secondaryContent">
                    <h3 style="margin-bottom:6px;">Победы в розыгрышах</h3>
                    <p class="amount mainc" id="totalWinnings" style="font-size:18px;font-weight:600;">Загрузка...</p>
                </div>
            </div>
            <div class="section contestsInfoCreate">
                <div class="secondaryContent">
                    <h3 style="margin-bottom:6px;">Проведённые розыгрыши</h3>
                    <p class="amount redc" id="totalGiveaway" style="font-size:18px;font-weight:600;">Загрузка...</p>
                </div>
            </div>
            <div class="section button block constestsInfoDetails">Подробнее</div>
        `);

        $('.constestsInfoDetails.button').on('click', () => renderContestsInfo(userId));

        getUserContestData(userId).then(function(data){
            $('#totalWinnings').text(
                (data.total_winnings || 0).toLocaleString('ru-RU') + ' ₽'
            );
            $('#totalGiveaway').text(
                (data.total_giveaway || 0).toLocaleString('ru-RU') + ' ₽'
            );
        }).catch(function(err){
            console.error('[LZT Contests Info] Ошибка загрузки данных:', err);
            $('#totalWinnings').text('Ошибка');
            $('#totalGiveaway').text('Ошибка');
        });
    });

    $(document).on('XFOverlay', function(e){
        var $overlay = e.overlay.getOverlay();
        if (!$overlay.is('.memberCard')) return;

        var $stat = $overlay.find('.userStatCounters');
        if (!$stat.length || $overlay.find('.contestsInfoContainer').length) return;

        var userLink = $overlay.find('.userContentLinks a').first();
        if (!userLink.length) return;
        var userIdMatch = userLink.attr('href').match(/\/(\d+)\/?$/);
        if (!userIdMatch) return;
        var userId = userIdMatch[1];

        var $block = $(`
            <div class="contestsInfoContainer">
                <div class="contestsInfo">
                    <span class="contactIcon fas fa-plus-circle mainc"></span>
                    <span class="data" id="totalWinnings">Выиграно: загрузка...</span>
                </div>
                <div class="contestsInfo">
                    <span class="contactIcon fas fa-minus-circle redc"></span>
                    <span class="data" id="totalGiveaway">Разыграно: загрузка...</span>
                </div>
            </div>
        `);
        $stat.after($block);

        var $menuElement = $overlay.find('.memberCardInner a[rel="Menu"]');

        if ($menuElement.length) {
            var $buttonOverlay = $('.Menu .blockLinksList').last();
            var $buttonInfo = $('<li><a id="constestsInfoDetails" class="OverlayTrigger">Статистика розыгрышей</a></li>');
            $buttonInfo.on('click', () => renderContestsInfo(userId));
            $buttonOverlay.append($buttonInfo);
        }
        getUserContestData(userId).then(function(data){
            $block.find('#totalWinnings').text(
                'Выиграно: ' + (data.total_winnings || 0).toLocaleString('ru-RU') + ' ₽'
            );
            $block.find('#totalGiveaway').text(
                'Разыграно: ' + (data.total_giveaway || 0).toLocaleString('ru-RU') + ' ₽'
            );
        }).catch(function(err){
            console.error('[LZT Contests Info] Ошибка загрузки данных:', err);
            $block.find('#totalWinnings, #totalGiveaway').text('Ошибка');
        });
    });

    async function renderContestsInfo(userId) {
        const cacheKey = 'LZTUserData_' + userId;
        const cachedRaw = sessionStorage.getItem(cacheKey);

        let cached = null;
        try {
            cached = cachedRaw ? JSON.parse(cachedRaw) : null;
        } catch (e) {
            console.warn('Ошибка парсинга кэша:', e);
        }

        const TOTAL_WINNINGS = (cached?.data?.total_winnings || 0).toLocaleString('ru-RU');
        const TOTAL_GIVEAWAY = (cached?.data?.total_giveaway || 0).toLocaleString('ru-RU');
        const WINS_COUNT = (cached?.data?.wins_count || 0).toLocaleString('ru-RU');
        const CONTESTS_CREATED = (cached?.data?.contests_created || 0).toLocaleString('ru-RU');
        const MAX_SINGLE_WIN = (cached?.data?.max_single_win || 0).toLocaleString('ru-RU');
        const MAX_SINGLE_GIVEAWAY = (cached?.data?.max_single_giveaway || 0).toLocaleString('ru-RU');
        const RANK_BY_WINNINGS = (cached?.data?.rank_by_winnings || 0).toLocaleString('ru-RU');
        const RANK_BY_WINS_COUNT = (cached?.data?.rank_by_wins_count || 0).toLocaleString('ru-RU');
        const RANK_BY_GIVEAWAY = (cached?.data?.rank_by_giveaway || 0).toLocaleString('ru-RU');
        const RANK_BY_CONTESTS_COUNT = (cached?.data?.rank_by_contests_count || 0).toLocaleString('ru-RU');

        const statsData = [
            { label: 'Выиграно', icon: '', value: TOTAL_WINNINGS, symbol: '₽' },
            { label: 'Разыграно', icon: '', value: TOTAL_GIVEAWAY, symbol: '₽' },
            { label: 'Количество побед', icon: '', value: WINS_COUNT },
            { label: 'Количество розыгрышей', icon: '', value: CONTESTS_CREATED },
            { label: 'Максимальная сумма победы', icon: '', value: MAX_SINGLE_WIN, symbol: '₽' },
            { label: 'Максимальная сумма розыгрыша', icon: '', value: MAX_SINGLE_GIVEAWAY, symbol: '₽' },
            { label: 'Топ по сумме побед', icon: '', value: RANK_BY_WINNINGS, prefix: '#' },
            { label: 'Топ по сумме розыгрышей', icon: '', value: RANK_BY_GIVEAWAY, prefix: '#' },
            { label: 'Топ по количеству побед', icon: '', value: RANK_BY_WINS_COUNT, prefix: '#' },
            { label: 'Топ по количеству розыгрышей', icon: '', value: RANK_BY_CONTESTS_COUNT, prefix: '#' },
        ];

        const $items = statsData.map(({ label, icon, value, prefix = '', symbol = '' }) => `
            <div class="LZTContestsItem">
                <div class="LZTContestsIcon">
                    <i class="${icon}"></i>
                </div>
                <div><p>${label}</p>${prefix}${value} ${symbol}</div>
            </div>
        `).join('');

        const $warningContestsInfo = `
            <div class="LZTContestsInfo--statusContainer">
                <div class="LZTContestsInfo--status">
                  <div class="statusTitle">Важная информация</div>
                  <div>Топ и суммы могут отличаться от официального значения форума, так как средствами парсера не возможно получать удалённые темы, а форум может.</div>
                  <div>Данная статистика учитывает только денежные розыгрыши с неудалённых тем.</div>
                </div>
            </div>
        `;

        const $contestsInfo = $(`
            <div class="LZTContestsInfoWrapper">
              <div class="LZTContestsInfo">
                ${$items}
              </div>
              ${$warningContestsInfo}
            </div>
        `);

        XenForo.alert($contestsInfo.prop('outerHTML'), 'LZT Contests Info');
    }
})();