Card Helper AStars | AnimeStars | ASStars

Отображения спроса карт и Авто-Лут карточек с просмотра

当前为 2025-06-10 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Card Helper AStars | AnimeStars | ASStars
// @namespace    animestars.org
// @version      7.24
// @description  Отображения спроса карт и Авто-Лут карточек с просмотра
// @author       bmr
// @match        https://astars.club/*
// @match        https://asstars.club/*
// @match        https://asstars1.astars.club/*
// @match        https://animestars.org/*
// @match        https://as1.astars.club/*
// @match        https://asstars.tv/*
// @match        https://ass.astars.club/*
// @license      MIT
// @grant        none
// ==/UserScript==

const DELAY = 1800;

let isAutoLootEnabled = localStorage.getItem('isAutoLootEnabled') === 'true';
let autoLootIntervalId = null;
const AUTO_LOOT_INTERVAL_DURATION = 170000;

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

let cardCounter = 0;
const cardClasses = '.remelt__inventory-item, .lootbox__card, .anime-cards__item, .trade__inventory-item, .trade__main-item, .card-filter-list__card, .deck__item, .history__body-item, .history__body-item, .card-pack__card';

let currentNotification = {
    element: null,
    id: null,
    type: null,
    timeoutId: null
};

function displayNotification(id, message, type = 'temporary', options = {}) {
    if (window.location.pathname.includes('/pm/') || window.location.pathname.includes('emotions.php') || window.location.pathname.includes('/messages/')) {
        return;
    }

    const { total, current, isSuccess = true, duration = 3500, sticky = false } = options;
    if (currentNotification.element && currentNotification.id !== id) {
        if (currentNotification.timeoutId) clearTimeout(currentNotification.timeoutId);
        if (currentNotification.element.parentNode) {
            currentNotification.element.remove();
        }
        currentNotification.element = null;
        currentNotification.id = null;
    }

    let notificationElement = currentNotification.element;

    if (!notificationElement || currentNotification.id !== id || (currentNotification.type === 'progress' && type !== 'progress')) {
        if (notificationElement && notificationElement.parentNode) {
            notificationElement.remove();
        }
        notificationElement = document.createElement('div');
        notificationElement.className = 'card-helper-status-notification';
        document.body.appendChild(notificationElement);
        currentNotification.element = notificationElement;
        currentNotification.id = id;
    }

    currentNotification.type = type;
    let iconHtml = '';
    if (type === 'progress') {
        iconHtml = '<div class="card-helper-spinner"></div>';
        if (total !== undefined && current !== undefined) {
            let countText = total === 'неизвестно' ? `${current}` : `${current}/${total}`;
            let progressMessageSuffix = `Обработано ${countText}`;
            message = `${message} ${progressMessageSuffix}`;
        }
    } else if (type === 'completion' || type === 'temporary') {
        const iconClass = isSuccess ?
        'card-helper-checkmark' : 'card-helper-crossmark';
        const iconChar = isSuccess ? '✔' : '✖';
        iconHtml = `<span class="${iconClass}">${iconChar}</span>`;
    }

    notificationElement.innerHTML = `
        <div class="ch-status-icon-container">${iconHtml}</div>
        <span class="card-helper-status-text">${message}</span>
    `;
    requestAnimationFrame(() => {
        notificationElement.classList.add('show');
    });
    if (currentNotification.timeoutId) {
        clearTimeout(currentNotification.timeoutId);
        currentNotification.timeoutId = null;
    }

    if (!sticky && (type === 'completion' || type === 'temporary')) {
        currentNotification.timeoutId = setTimeout(() => {
            hideCurrentNotification(id);
        }, duration);
    }
}

function updateNotificationProgress(id, messagePrefix, current, total) {
    if (currentNotification.id === id && currentNotification.type === 'progress') {
        const textElement = currentNotification.element.querySelector('.card-helper-status-text');
        let countText = total === 'неизвестно' ? `${current}` : `${current}/${total}`;
        let progressMessageSuffix = `Обработано ${countText}`;
        const fullMessage = `${messagePrefix} ${progressMessageSuffix}`;

        if (textElement && textElement.textContent !== fullMessage) {
            textElement.textContent = fullMessage;
        }
    } else {
        displayNotification(id, messagePrefix, 'progress', { current, total, sticky: true });
    }
}

function completeProgressNotification(id, message, isSuccess = true, duration = 3500) {
    displayNotification(id, message, 'completion', { isSuccess, duration });
}

function showTemporaryMessage(id, message, isSuccess = true, duration = 3500) {
    displayNotification(id, message, 'temporary', { isSuccess, duration });
}

function hideCurrentNotification(idToHide) {
    if (currentNotification.element && currentNotification.id === idToHide) {
        const element = currentNotification.element;
        element.classList.remove('show');
        if (currentNotification.timeoutId) {
            clearTimeout(currentNotification.timeoutId);
            currentNotification.timeoutId = null;
        }
        setTimeout(() => {
            if (element.parentNode) {
                element.remove();
            }
            if (currentNotification.element === element) {
                currentNotification.element = null;
                currentNotification.id = null;
                currentNotification.type = null;
            }
        }, 400);
    }
}

function getCurrentDomain() {
    const hostname = window.location.hostname;
    const protocol = window.location.protocol;
    return `${protocol}//${hostname}`;
}

async function loadCard(cardId, maxRetries = 2, initialRetryDelay = 2500) {
    const cacheKey = 'cardId: ' + cardId;
    const cachedCard = await getCard(cacheKey);
    if (cachedCard) {
        return cachedCard;
    }

    const currentDomain = getCurrentDomain();
    let popularityCount = 0, needCount = 0, tradeCount = 0;

    for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
        if (attempt > 1) {
            const retryDelay = initialRetryDelay * Math.pow(1.5, attempt - 2);
            console.warn(`Карта ${cardId}: Попытка ${attempt}/${maxRetries + 1}. Ждем ${retryDelay / 1000}с перед повтором...`);
            await sleep(retryDelay);
        } else {
            await sleep(DELAY);
        }

        try {
            const mainCardPageResponse = await fetch(`${currentDomain}/cards/users/?id=${cardId}`);

            if (mainCardPageResponse.status === 403) {
                console.error(`Карта ${cardId}: Попытка ${attempt} - Ошибка 403 Forbidden.`);
                if (attempt === maxRetries + 1) return null;
                continue;
            }
            if (!mainCardPageResponse.ok) {
                console.error(`Карта ${cardId}: Попытка ${attempt} - Ошибка HTTP ${mainCardPageResponse.status}.`);
                if (attempt === maxRetries + 1) return null;
                continue;
            }

            const mainCardPageHtml = await mainCardPageResponse.text();
            const mainCardPageDoc = new DOMParser().parseFromString(mainCardPageHtml, 'text/html');

            popularityCount = parseInt(mainCardPageDoc.querySelector('#owners-count')?.textContent.trim(), 10) || 0;
            needCount = parseInt(mainCardPageDoc.querySelector('#owners-need')?.textContent.trim(), 10) || 0;
            tradeCount = parseInt(mainCardPageDoc.querySelector('#owners-trade')?.textContent.trim(), 10) || 0;

            if (popularityCount === 0 && needCount === 0 && tradeCount === 0) {
                if (attempt < maxRetries + 1) {
                    console.warn(`Карта ${cardId}: Попытка ${attempt} - все счетчики 0, повторяем.`);
                    continue;
                }
                console.warn(`Карта ${cardId}: Попытка ${attempt} (последняя) - принимаем нулевые счетчики.`);
            }

            const finalCardData = { popularityCount, needCount, tradeCount };
            await cacheCard(cacheKey, finalCardData);
            return finalCardData;

        } catch (error) {
            console.error(`Карта ${cardId}: Попытка ${attempt} - Исключение при загрузке:`, error);
            if (attempt === maxRetries + 1) return null;
        }
    }

    console.error(`Карта ${cardId}: Все ${maxRetries + 1} попытки загрузки не удались.`);
    return null;
}

function extractOwnerIdFromUrl(url) {
    const match = url.match(/\/user\/([^\/?#]+)/);
    return match && match[1] ? match[1] : null;
}

async function updateCardInfo(cardId, element) {
    if (!cardId || !element) return;

    try {
        const cardData = await loadCard(cardId);

        const oldStats = element.querySelector('.card-stats');
        if (oldStats) {
            oldStats.remove();
        }

        if (!cardData) {
            console.warn(`Не удалось загрузить данные для карты ${cardId}, информация не будет отображена.`);
            return;
        }

        const stats = document.createElement('div');
        stats.className = 'card-stats';

        const currentMode = getCardStatsMode();
        stats.classList.add(currentMode === 'full' ? 'card-stats--full' : 'card-stats--minimalistic');

        if (currentMode === 'full') {
            stats.innerHTML = `
                <div class="stat-line"><i class="fas fa-users"></i> Имеют ${cardData.popularityCount}</div>
                <div class="stat-line"><i class="fas fa-heart"></i> Хотят ${cardData.needCount}</div>
                <div class="stat-line"><i class="fas fa-sync-alt"></i> Обмен ${cardData.tradeCount}</div>
            `;
        } else {
            stats.innerHTML = `
                <span title="Владельцев"><i class="fas fa-users"></i> ${cardData.popularityCount}</span>
                <span title="Хотят получить"><i class="fas fa-heart"></i> ${cardData.needCount}</span>
                <span title="Готовы обменять"><i class="fas fa-sync-alt"></i> ${cardData.tradeCount}</span>
            `;
        }
        element.appendChild(stats);
    } catch (error) {
        console.error("Критическая ошибка в updateCardInfo для cardId " + cardId + ":", error);
    }
}

function clearMarkFromCards() { cleanByClass('div-marked'); }
function removeAllLinkIcons() { cleanByClass('link-icon'); }
function cleanByClass(className) { document.querySelectorAll('.' + className).forEach(item => item.remove()); }

function getCardsOnPage() {
    return Array.from(document.querySelectorAll(cardClasses)).filter(cardEl => cardEl.offsetParent !== null);
}

async function processCards() {
    if (isCardRemeltPage()) {
        const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
        if (Object.keys(storedData).length < 1) { await readyRemeltCards(); return; }
    }
    removeMatchingWatchlistItems(); removeAllLinkIcons(); clearMarkFromCards();
    const cardsOnPage = getCardsOnPage();
    let totalCardsToProcess = cardsOnPage.length;
    if (!totalCardsToProcess) return;

    const buttonId = 'processCards';
    startAnimation(buttonId);
    displayNotification(buttonId, 'Проверка спроса:', 'progress', { current: 0, total: totalCardsToProcess, sticky: true });
    let processedCardCount = 0;

    for (const cardElement of cardsOnPage) {
        if (cardElement.classList.contains('trade__inventory-item--lock') || cardElement.classList.contains('remelt__inventory-item--lock')) continue;
        cardElement.classList.add('processing-card');
        let cardId = await getCardId(cardElement);
        if (cardId) {
            await updateCardInfo(cardId, cardElement);
        }
        processedCardCount++;
        updateNotificationProgress(buttonId, 'Проверено карт:', processedCardCount, totalCardsToProcess);
        cardElement.classList.remove('processing-card');
        if (cardElement.classList.contains('lootbox__card')) cardElement.addEventListener('click', removeAllLinkIcons);
    }
    completeProgressNotification(buttonId, 'Проверка спроса завершена', true);
    stopAnimation(buttonId);
}

function getCardStatsMode() {
    return localStorage.getItem('cardStatsMode') || 'minimalistic';
}

function setCardStatsMode(mode) {
    localStorage.setItem('cardStatsMode', mode);
}

function removeMatchingWatchlistItems() {
    const watchlistItems = document.querySelectorAll('.watchlist__item');
    if (watchlistItems.length == 0) return;
    let initialCount = watchlistItems.length;
    watchlistItems.forEach(item => {
        const episodesText = item.querySelector('.watchlist__episodes')?.textContent.trim();
        if (episodesText) {
            const matches = episodesText.match(/[\d]+/g);
            if (matches) {
                const currentEpisode = parseInt(matches[0], 10);
                const totalEpisodes = parseInt(matches.length === 4 ? matches[3] : matches[1], 10);
                if (currentEpisode === totalEpisodes) item.remove();
            }
        }
    });
    let currentCount = document.querySelectorAll('.watchlist__item').length;
    if (initialCount > currentCount) {
        showTemporaryMessage('watchlistUpdate', `Из списка удалены просмотренные аниме. Осталось: ${currentCount}`, true);
    }
}

function startAnimation(id) {
    const buttonElement = document.getElementById(id);
    if (buttonElement) {
        buttonElement.classList.add('is-working');
        buttonElement.style.animationPlayState = 'paused';
        const iconElement = buttonElement.querySelector('span[class*="fa-"]');
        if (iconElement) {
            iconElement.style.animation = 'pulseIcon 1s ease-in-out infinite';
        }
    }
}

function stopAnimation(id) {
    const buttonElement = document.getElementById(id);
    if (buttonElement) {
        buttonElement.classList.remove('is-working');
        if (!buttonElement.matches(':hover')) {
             buttonElement.style.animationPlayState = 'running';
        }
        const iconElement = buttonElement.querySelector('span[class*="fa-"]');
        if (iconElement) {
            iconElement.style.animation = '';
        }
    }
}

function getButton(id, iconClassFASuffix, percent, tooltipText, clickFunction) {
    const wrapper = document.createElement('div');
    wrapper.style.position = 'fixed';
    wrapper.style.top = percent + '%';
    wrapper.style.right = '1%';
    wrapper.style.zIndex = '1000';

    const buttonElement = document.createElement('button');
    buttonElement.id = id;
    buttonElement.classList.add('anim-interactive-button');

    const icon = document.createElement('span');
    icon.className = 'fal fa-' + iconClassFASuffix;
    buttonElement.appendChild(icon);

    let tooltipTimeout;
    const tooltip = document.createElement('div');
    tooltip.className = 'anim-button-tooltip';
    tooltip.textContent = tooltipText;

    buttonElement.addEventListener('mouseenter', () => {
        tooltip.style.opacity = '1';
        tooltip.style.transform = 'translateY(-50%) translateX(0px)';
        buttonElement.style.animationPlayState = 'paused';
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
    });

    buttonElement.addEventListener('mouseleave', () => {
        tooltip.style.opacity = '0';
        tooltip.style.transform = 'translateY(-50%) translateX(10px)';
        if (!buttonElement.classList.contains('is-working')) {
            buttonElement.style.animationPlayState = 'running';
        }
    });

    buttonElement.addEventListener('click', (e) => {
        e.stopPropagation();
        clickFunction(e);
        if (window.innerWidth <= 768) {
            tooltip.style.opacity = '1';
            tooltip.style.transform = 'translateY(-50%) translateX(0px)';
            if (tooltipTimeout) clearTimeout(tooltipTimeout);
            tooltipTimeout = setTimeout(() => {
                tooltip.style.opacity = '0';
                tooltip.style.transform = 'translateY(-50%) translateX(10px)';
            }, 1500);
        }
    });

    wrapper.appendChild(buttonElement);
    wrapper.appendChild(tooltip);
    return wrapper;
}

function createToggleStatsModeButton(topPercent) {
    const wrapper = document.createElement('div');
    wrapper.style.position = 'fixed';
    wrapper.style.top = topPercent + '%';
    wrapper.style.right = '1%';
    wrapper.style.zIndex = '9998';

    const button = document.createElement('button');
    button.id = 'toggleStatsModeButton';
    button.className = 'anim-interactive-button anim-interactive-button--small-toggle';

    const icon = document.createElement('span');

    function updateButtonAppearance() {
        const currentMode = getCardStatsMode();
        if (currentMode === 'minimalistic') {
            icon.className = 'fal fa-ellipsis-h';
        } else {
            icon.className = 'fal fa-list-alt';
        }
    }

    button.appendChild(icon);
    updateButtonAppearance();

    const tooltip = document.createElement('div');
    tooltip.className = 'anim-button-tooltip';
    tooltip.textContent = "Перекл. режимы отображения спроса";

    let tooltipTimeout;

    button.addEventListener('click', (e) => {
        e.stopPropagation();
        const oldMode = getCardStatsMode();
        const newMode = oldMode === 'minimalistic' ? 'full' : 'minimalistic';
        setCardStatsMode(newMode);
        updateButtonAppearance();

        const modeName = newMode === 'full' ? 'Полный' : 'Мин';
        showTemporaryMessage('modeSwitched', `Режим статистики изменен на: ${modeName}.`, true, 4000);

        if (window.innerWidth <= 768) {
            tooltip.style.opacity = '1';
            tooltip.style.transform = 'translateY(-50%) translateX(0px)';
            if (tooltipTimeout) clearTimeout(tooltipTimeout);
            tooltipTimeout = setTimeout(() => {
                tooltip.style.opacity = '0';
                tooltip.style.transform = 'translateY(-50%) translateX(10px)';
            }, 1500);
        }
    });

    button.addEventListener('mouseenter', () => {
        tooltip.style.opacity = '1';
        tooltip.style.transform = 'translateY(-50%) translateX(0px)';
        button.style.animationPlayState = 'paused';
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
    });

    button.addEventListener('mouseleave', () => {
        tooltip.style.opacity = '0';
        tooltip.style.transform = 'translateY(-50%) translateX(10px)';
        if (!button.classList.contains('is-working')) {
           button.style.animationPlayState = 'running';
        }
    });

    wrapper.appendChild(button);
    wrapper.appendChild(tooltip);

    return wrapper;
}

function addUpdateButton() {
    if (window.location.pathname.includes('/pm/') || window.location.pathname.includes('emotions.php') || window.frameElement) return;
    if (!document.getElementById('processCards')) {
        document.body.appendChild(getButton('processCards', 'star', 42, 'Узнать спрос', processCards));
    }
    if (isMyCardPage() && !document.getElementById('readyToCharge')) {
        document.body.appendChild(getButton('readyToCharge', 'handshake', 50, 'Отметить всё как "Готов обменять"', readyToCharge));
    }
    if (isCardRemeltPage() && !document.getElementById('readyRemeltCards')) {
        document.body.appendChild(getButton('readyRemeltCards', 'yin-yang', 50, 'Кешировать карточки', readyRemeltCards));
    }
    if (!document.getElementById('clearCacheButton')) {
        document.body.appendChild(getButton('clearCacheButton', 'trash', 58, 'Очистить кеш карт', clearCardCache));
    }
    if (!document.getElementById('promoCodeLinkButton')) {
        document.body.appendChild(createPromoCodeButton());
    }
    if (!document.getElementById('toggleStatsModeButton')) {
        document.body.appendChild(createToggleStatsModeButton(66));
    }
    if (!document.getElementById('toggleAutoLootButton')) {
        document.body.appendChild(createToggleAutoLootButton(74));
    }
}

function isMyCardPage() {
    const pathname = window.location.pathname;
    const search = window.location.search;

    const oldPattern = /^\/user\/[^\/]+\/cards(\/page\/\d+\/)?$/;
    if (oldPattern.test(pathname)) {
        return true;
    }

    if (pathname === '/user/cards/' && search.startsWith('?name=')) {
        const params = new URLSearchParams(search);
        if (params.get('name') && params.get('name').length > 0) {
            return true;
        }
    }

    return false;
}

function isCardRemeltPage() { return (/^\/cards_remelt\//).test(window.location.pathname); }

async function readyRemeltCards() {
    const buttonId = 'readyRemeltCards';
    const notificationId = 'remeltCache';
    showTemporaryMessage(notificationId, 'Запрос на кеширование всех карт..', true, 2000);

    const userCardsLinkElement = document.querySelector('a.ncard__tabs-btn[href*="/user/"][href*="/cards/"]');
    const relativeHref = userCardsLinkElement ? userCardsLinkElement.href : null;

    if (!relativeHref) {
        showTemporaryMessage(notificationId, 'Не найдена ссылка на страницу "Мои карты" для начала кеширования.', false, 5000);
        return;
    }

    const absoluteHref = new URL(relativeHref, window.location.origin).href;

    removeMatchingWatchlistItems();
    removeAllLinkIcons();
    clearMarkFromCards();
    startAnimation(buttonId);

    try {
        await scrapeAllPages(absoluteHref);
    } catch (e) {
        showTemporaryMessage(notificationId, 'Произошла ошибка при кешировании.', false, 5000);
    } finally {
        stopAnimation(buttonId);
    }
}

function getCanonicalIdFromCacheByItemInstanceId(itemInstanceIdToFind) {
    const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
    if (!itemInstanceIdToFind) return null;

    for (const ownerKey in storedData) {
        if (Array.isArray(storedData[ownerKey])) {
            const foundCardData = storedData[ownerKey].find(card => card.itemInstanceId === itemInstanceIdToFind);
            if (foundCardData && foundCardData.canonicalCardId) {
                return foundCardData.canonicalCardId;
            }
        }
    }
    console.warn(`Канонический ID для экземпляра ${itemInstanceIdToFind} не найден в кеше animeCardsData.`);
    return null;
}

async function scrapeAllPages(firstPageHref) {
    const notificationId = 'scrapeAllPages';
    try {
        const response = await fetch(firstPageHref);
        if (!response.ok) throw new Error(`Ошибка HTTP: ${response.status}`);
        const firstPageHtml = await response.text();
        const firstPageDoc = new DOMParser().parseFromString(firstPageHtml, 'text/html');

        const pagination = firstPageDoc.querySelector('#pagination');
        let storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
        let totalCardsInProfile = -1;

        const titleElement = firstPageDoc.querySelector('h1.ncard__main-title.ncard__main-title-2.as-center.bolder');

        if (titleElement) {
            const match = titleElement.textContent.match(/\((\d+)\s*шт\.\)/);
            if (match && match[1]) {
                totalCardsInProfile = parseInt(match[1], 10);
            }
        }

        const countCurrentlyCachedCards = () => {
            let count = 0;
            for (const ownerKey in storedData) {
                if (Array.isArray(storedData[ownerKey])) {
                    count += storedData[ownerKey].length;
                }
            }
            return count;
        };

        if (totalCardsInProfile !== -1) {
            let currentTotalCached = countCurrentlyCachedCards();
            if (totalCardsInProfile === currentTotalCached && currentTotalCached > 0) {
                showTemporaryMessage(notificationId, 'Кеш карточек уже актуален.', true);
                await processCards();
                return;
            }
        }

        const calculatedTotalForDisplay = totalCardsInProfile > 0 ? totalCardsInProfile : 'неизвестно';
        displayNotification(notificationId, 'Кеширование страниц:', 'progress', { current: countCurrentlyCachedCards(), total: calculatedTotalForDisplay, sticky: true });

        const currentAccountOwnerIdForCacheKey = extractOwnerIdFromUrl(firstPageHref);

        async function processBatchInStorage(doc) {
            const cardsElements = doc.querySelectorAll('.anime-cards__item');

            let newCardsThisBatch = 0;
            for (let i = 0; i < cardsElements.length; i += 10) {
                const cardGroup = Array.from(cardsElements).slice(i, i + 10);
                for (const cardEl of cardGroup) {
                    const itemInstanceId = cardEl.getAttribute('data-owner-id');
                    const canonicalCardId = cardEl.getAttribute('data-id');

                    if (!currentAccountOwnerIdForCacheKey || !itemInstanceId || !canonicalCardId) {
                        console.warn("Пропуск карты в scrapeAllPages: отсутствует itemInstanceId, canonicalCardId или ID владельца коллекции.", cardEl, "AccountOwnerID:", currentAccountOwnerIdForCacheKey);
                        continue;
                    }

                    const ownerKey = 'o_' + currentAccountOwnerIdForCacheKey;
                    if (!storedData[ownerKey]) storedData[ownerKey] = [];

                    if (!storedData[ownerKey].find(c => c.itemInstanceId === itemInstanceId)) {
                        storedData[ownerKey].push({
                            itemInstanceId: itemInstanceId,
                            canonicalCardId: canonicalCardId,
                            name: cardEl.getAttribute('data-name'),
                            rank: cardEl.getAttribute('data-rank'),
                            animeLink: cardEl.getAttribute('data-anime-link'),
                            image: cardEl.querySelector('img')?.getAttribute('src') || cardEl.getAttribute('data-image'),
                            ownerId: currentAccountOwnerIdForCacheKey
                        });
                        newCardsThisBatch++;
                    }
                }
                await sleep(10);
            }
            updateNotificationProgress(notificationId, 'Кешировано карт:', countCurrentlyCachedCards(), calculatedTotalForDisplay);
        }

        await processBatchInStorage(firstPageDoc);

        if (pagination) {
            const pageLinks = Array.from(pagination.querySelectorAll('a[href*="page/"]'));
            let lastPageNumber = 1;
            if (pageLinks.length > 0) {
                const numbers = pageLinks.map(a => (a.getAttribute('href')?.match(/page\/(\d+)/) || [])[1]).map(n => parseInt(n, 10)).filter(n => n > 0);
                if (numbers.length > 0) {
                    lastPageNumber = Math.max(...numbers);
                } else {
                    const lastLinkElement = pagination.querySelector('a:last-of-type:not(.pagination__item--next)');
                    if(lastLinkElement) lastPageNumber = parseInt(lastLinkElement.textContent.trim(), 10) || 1;
                }
            }

            if (lastPageNumber > 1) {
                const parser = new DOMParser();
                let basePageUrl = firstPageHref.replace(/\/page\/\d+(\/)?$/, '').replace(/\/$/, '');
                for (let i = 2; i <= lastPageNumber; i++) {
                    const pageUrl = `${basePageUrl}/page/${i}/`;
                    const pageHTML = await (async (url) => {
                        try {
                            const r = await fetch(url);
                            return r.ok ? await r.text() : null;
                        } catch (e) {
                            console.error(`Ошибка при загрузке страницы ${url}:`, e);
                            return null;
                        }
                    })(pageUrl);
                    if (pageHTML) {
                         const nextPageDoc = parser.parseFromString(pageHTML, 'text/html');
                         await processBatchInStorage(nextPageDoc);
                    }
                    await sleep(1000 + Math.random() * 1500);
                    if (i % 5 === 0) {
                        localStorage.setItem('animeCardsData', JSON.stringify(storedData));
                    }
                }
            }
        }
        localStorage.setItem('animeCardsData', JSON.stringify(storedData));
        completeProgressNotification(notificationId, 'Кеширование завершено. Всего в кеше: ' + countCurrentlyCachedCards(), true);
        await processCards();
    } catch (error) {
        console.error("Ошибка в scrapeAllPages:", error);
        completeProgressNotification(notificationId, 'Ошибка кеширования страниц.', false);
    }
}

async function getCardId(cardElement) {
    let cardId = cardElement.getAttribute('data-card-id') || cardElement.getAttribute('card-id');

    if (!cardId && cardElement.tagName === 'A' && typeof cardElement.hasAttribute === 'function' && cardElement.hasAttribute('href')) {
        const href = cardElement.getAttribute('href');
        if (href) {
            let match = href.match(/\/cards\/users\/\?id=(\d+)/);
            if (match && match[1]) {
                cardId = match[1];
            } else {
                match = href.match(/\/cards\/(\d+)\/users\//);
                if (match && match[1]) {
                    cardId = match[1];
                }
            }
        }
    }

    if (!cardId && typeof cardElement.matches === 'function') {
        if (cardElement.matches('.anime-cards__item') || cardElement.matches('.lootbox__card')) {
            cardId = cardElement.getAttribute('data-id');
        } else if (cardElement.matches('.remelt__inventory-item')) {
            const instanceIdFromRemelt = cardElement.getAttribute('data-id');
            if (instanceIdFromRemelt) {
                const canonicalIdFromCache = getCanonicalIdFromCacheByItemInstanceId(instanceIdFromRemelt);
                if (canonicalIdFromCache) {
                    cardId = canonicalIdFromCache;
                } else {
                     console.warn(`Не найден канонический ID в кеше для remelt item с instanceId ${instanceIdFromRemelt}.`);
                }
            }
        }
    }

    if (!cardId && cardElement.tagName !== 'A') {
        const linkElement = cardElement.querySelector('a[href*="/cards/users/?id="], a[href*="/cards/"][href*="/users/"]');
        if (linkElement) {
            const href = linkElement.getAttribute('href');
            let match = href.match(/\/cards\/users\/\?id=(\d+)/);
            if (match && match[1]) {
                cardId = match[1];
            } else {
                match = href.match(/\/cards\/(\d+)\/users\//);
                if (match && match[1]) {
                    cardId = match[1];
                }
            }
        }
    }

    return cardId;
}

async function getFirstCardByOwner(ownerId) {
    const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
    const key = 'o_' + ownerId;
    return storedData[key]?.[0] || null;
}

async function readyToCharge() {
    const buttonId = 'readyToCharge';
    displayNotification(buttonId, 'Подготовка к отметке карт...', 'progress', {sticky: true});
    let cardsOnPage = getCardsOnPage();
    if (!cardsOnPage || cardsOnPage.length === 0) { completeProgressNotification(buttonId, 'Карты на странице не найдены.', false); return; }
    const cardsToProcess = cardsOnPage.filter(cardEl => !cardEl.classList.contains('trade__inventory-item--lock'));
    const totalCardsToProcess = cardsToProcess.length;
    if (totalCardsToProcess === 0) { completeProgressNotification(buttonId, 'Нет карт для отметки.', false); return; }
    updateNotificationProgress(buttonId, 'Отмечаем карт:', 0, totalCardsToProcess);
    startAnimation(buttonId); clearMarkFromCards(); cardCounter = 0;
    let successfullyProcessedCount = 0, attemptedToProcessCount = 0;
    for (const cardElement of cardsToProcess) {
        cardElement.classList.add('charging-card');
        let idToSend = cardElement.getAttribute('data-owner-id') || await getCardId(cardElement);
        attemptedToProcessCount++;
        if (idToSend) {
            await sleep(1000 + Math.random() * 500);
            try { if (await readyToChargeCard(idToSend)) successfullyProcessedCount++; }
            catch (error) { console.error("Ошибка при отметке карты " + idToSend + ":", error); }
        }
        updateNotificationProgress(buttonId, 'Обработано карт:', attemptedToProcessCount, totalCardsToProcess);
        cardElement.classList.remove('charging-card');
    }
    completeProgressNotification(buttonId, `Отправлено на обмен ${cardCounter} из ${successfullyProcessedCount} (${attemptedToProcessCount} попыток).`, true, 5000);
    stopAnimation(buttonId);
}

async function readyToChargeCard(card_id_to_send) {
    try {
        await sleep(DELAY * 2 + Math.random() * DELAY);
        const data = await $.ajax({ url: "/engine/ajax/controller.php?mod=trade_ajax", type: "post", data: { action: "propose_add", type: 1, card_id: card_id_to_send, user_hash: dle_login_hash }, dataType: "json", cache: false });
        if (data?.error) {
            if (data.error === 'Слишком часто, подождите пару секунд и повторите действие') {
                await sleep(2500 + Math.random() * 1000); return await readyToChargeCard(card_id_to_send);
            }
            console.warn(`Ошибка от сервера (карта ${card_id_to_send}): ${data.error}`); return false;
        }
        if (data?.status == "added") { cardCounter++; return true; }
        if (data?.status == "deleted") { await sleep(1000); return await readyToChargeCard(card_id_to_send); }
        console.warn(`Неожиданный ответ от сервера для карты ${card_id_to_send}:`, data); return false;
    } catch (e) {
        console.error(`readyToChargeCard AJAX/исключение (ID ${card_id_to_send}):`, e.statusText || e.message || e);
        return false;
    }
}

function createPromoCodeButton() {
    const domain = getCurrentDomain();
    const promoUrl = domain + "/promo_codes";

    const buttonLink = document.createElement('a');
    buttonLink.id = 'promoCodeLinkButton';
    buttonLink.href = promoUrl;
    buttonLink.className = 'anim-interactive-button promo-code-button-custom';

    const icon = document.createElement('span');
    icon.className = 'fal fa-gift';

    const text = document.createElement('span');
    text.textContent = 'Промокоды';

    buttonLink.appendChild(icon);
    buttonLink.appendChild(text);

    return buttonLink;
}

function manageAutoLootInterval() {
    if (isAutoLootEnabled) {
        if (autoLootIntervalId === null) {
            autoLootIntervalId = setInterval(checkNewCard, AUTO_LOOT_INTERVAL_DURATION);
        }
    } else {
        if (autoLootIntervalId !== null) {
            clearInterval(autoLootIntervalId);
            autoLootIntervalId = null;
        }
    }
}

function createToggleAutoLootButton(topPercent) {
    const wrapper = document.createElement('div');
    wrapper.style.position = 'fixed';
    wrapper.style.top = topPercent + '%';
    wrapper.style.right = '1%';
    wrapper.style.zIndex = '9997';

    const button = document.createElement('button');
    button.id = 'toggleAutoLootButton';
    button.className = 'anim-interactive-button anim-interactive-button--small-toggle';

    const icon = document.createElement('span');
    button.appendChild(icon);

    const tooltip = document.createElement('div');
    tooltip.className = 'anim-button-tooltip';
    wrapper.appendChild(button);
    wrapper.appendChild(tooltip);

    function updateAppearance() {
        if (isAutoLootEnabled) {
            icon.className = 'fal fa-robot';
            tooltip.textContent = 'Авто-лут ВКЛ';
        } else {
            icon.className = 'fal fa-power-off';
            tooltip.textContent = 'Авто-лут ВЫКЛ';
        }
    }

    updateAppearance();

    let tooltipTimeout;

    button.addEventListener('click', (e) => {
        e.stopPropagation();
        isAutoLootEnabled = !isAutoLootEnabled;
        localStorage.setItem('isAutoLootEnabled', isAutoLootEnabled);
        manageAutoLootInterval();
        updateAppearance();

        showTemporaryMessage('autoLootToggle', `Авто-лут карт теперь ${isAutoLootEnabled ? 'ВКЛЮЧЕН' : 'ВЫКЛЮЧЕН'}.`, true, 3000);

        if (window.innerWidth <= 768) {
            tooltip.style.opacity = '1';
            tooltip.style.transform = 'translateY(-50%) translateX(0px)';
            if (tooltipTimeout) clearTimeout(tooltipTimeout);
            tooltipTimeout = setTimeout(() => {
                tooltip.style.opacity = '0';
                tooltip.style.transform = 'translateY(-50%) translateX(10px)';
            }, 1500);
        }
    });

    button.addEventListener('mouseenter', () => {
        tooltip.style.opacity = '1';
        tooltip.style.transform = 'translateY(-50%) translateX(0px)';
        button.style.animationPlayState = 'paused';
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
    });

    button.addEventListener('mouseleave', () => {
        tooltip.style.opacity = '0';
        tooltip.style.transform = 'translateY(-50%) translateX(10px)';
        if (!button.classList.contains('is-working')) {
            button.style.animationPlayState = 'running';
        }
    });

    return wrapper;
}

const style = document.createElement('style');
style.textContent = `
@keyframes glowEffect {
    0% { box-shadow: 0 0 5px #6c5ce7; }
    50% { box-shadow: 0 0 20px #6c5ce7; }
    100% { box-shadow: 0 0 5px #6c5ce7; }
}

@keyframes glowChargeEffect {
    0% { box-shadow: 0 0 7px #4CAF50; }
    50% { box-shadow: 0 0 25px #4CAF50; }
    100% { box-shadow: 0 0 7px #4CAF50; }
}

@keyframes fadeInUp {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes breatheShadowInteractive {
    0% { box-shadow: 0 0 6px rgba(108, 92, 231, 0.2); transform: scale(1); }
    50% { box-shadow: 0 0 12px rgba(108, 92, 231, 0.5); transform: scale(1.02); }
    100% { box-shadow: 0 0 6px rgba(108, 92, 231, 0.2); transform: scale(1); }
}

@keyframes pulseWorkingBorderInteractive {
    0% { box-shadow: 0 0 0 0px rgba(86, 200, 239, 0.7), 0 3px 8px rgba(0,0,0,0.25); }
    70% { box-shadow: 0 0 0 10px rgba(86, 200, 239, 0), 0 5px 12px rgba(0,0,0,0.3); }
    100% { box-shadow: 0 0 0 0px rgba(86, 200, 239, 0), 0 3px 8px rgba(0,0,0,0.25); }
}

@keyframes pulseIcon {
    0% { transform: scale(1) rotate(0deg); opacity: 1; }
    50% { transform: scale(1.2) rotate(0deg); opacity: 0.7; }
    100% { transform: scale(1) rotate(0deg); opacity: 1; }
}

@keyframes cardHelperSpin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.processing-card {
    position: relative;
}
.processing-card img {
    position: relative;
    z-index: 2;
}
.processing-card::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: 8px;
    z-index: 1;
    animation: glowEffect 1.5s infinite;
    pointer-events: none;
}

.charging-card {
    position: relative;
}
.charging-card img {
    position: relative;
    z-index: 2;
}
.charging-card::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: 8px;
    z-index: 1;
    animation: glowChargeEffect 1.5s infinite;
    pointer-events: none;
}

.card-stats {
    position: relative;
    background: linear-gradient(34deg, #4e2264 0%, #943aca 55%);
    padding: 8px;
    color: white;
    font-size: 12px;
    margin-top: 5px;
    border-radius: 5px;
    display: flex;
    text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
    animation: fadeInUp 0.3s ease;
    z-index: 0 !important;
    box-shadow: 0px 0px 8px 0px #a367dc;
    border: 1px dashed #ffffff !important;
}

.card-stats--minimalistic {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.card-stats--full {
    flex-direction: column;
    align-items: flex-start;
    padding: 10px;
}
.card-stats--full .stat-line {
    display: flex;
    align-items: center;
    margin-bottom: 4px;
}
.card-stats--full .stat-line:last-child {
    margin-bottom: 0;
}
.card-stats--full .stat-line i.fas {
    margin-right: 6px;
    font-size: 13px;
    width: 16px;
    text-align: center;
}
.history__inner {
    max-width: 1200px !important;
    margin: 0 auto !important;
    padding: 15px !important;
}

.history__body {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 15px !important;
    padding: 15px !important;
    border-radius: 8px !important;
}

@media screen and (min-width: 769px) {
    .history__body-item {
        width: 120px !important;
        height: auto !important;
        transition: transform 0.2s !important;
    }
    .history__body-item img {
        width: 120px !important;
        height: auto !important;
        border-radius: 8px !important;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
    }
}

.history__body-item:hover {
    transform: scale(1.05) !important;
    z-index: 2 !important;
}
.card-stats span {
    display: flex;
    align-items: center;
    gap: 4px;
}
.lootbox__card {
    position: relative !important;
}
.card-stats span i {
    font-size: 14px;
}

@media screen and (max-width: 768px) {
    .history__body-item,
    .history__body-item img {
        width: 100px !important;
    }
    .processing-card::before,
    .charging-card::before {
        top: -1px !important;
        left: -1px !important;
        right: -1px !important;
        bottom: -1px !important;
        opacity: 0.5 !important;
    }
    div[style*="position: fixed"][style*="right: 1%"] {
        transform: scale(0.9);
        transform-origin: bottom right;
    }
    .anim-interactive-button {
        width: 40px !important;
        height: 40px !important;
    }
    .anim-interactive-button span[class*="fa-"] {
        font-size: 18px !important;
    }
    #promoCodeLinkButton.anim-interactive-button.promo-code-button-custom {
        padding: 0 !important;
    }
    #promoCodeLinkButton.anim-interactive-button.promo-code-button-custom span:not(.fal) {
        display: none !important;
    }
    #promoCodeLinkButton.anim-interactive-button.promo-code-button-custom .fal {
        margin-right: 0 !important;
    }
    #promoCodeLinkButton {
        right: 1%;
    }
    #promoCodeLinkButton,
    #toggleStatsModeButton {
        transform: scale(0.9);
        transform-origin: bottom left;
    }
    .anim-button-tooltip {
        font-size: 11px !important;
        padding: 5px 8px !important;
    }
    .card-stats {
        padding: 4px;
        font-size: 10px;
    }
    .card-stats span i {
        font-size: 12px !important;
    }
    .remelt__inventory-list {
        grid-template-columns: repeat(2, 1fr) !important;
        gap: 10px !important;
    }
    .remelt__inventory-item {
        width: 100% !important;
        margin: 0 !important;
    }
    .remelt__inventory-item img {
        width: 100% !important;
        height: auto !important;
    }
    .remelt__inventory-item .card-stats {
        width: 100% !important;
        margin-top: 4px !important;
    }
}

.anim-interactive-button {
    background-color: #6c5ce7;
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 45px;
    height: 45px;
    padding: 0;
    cursor: pointer;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
    transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    display: flex;
    justify-content: center;
    align-items: center;
    animation: breatheShadowInteractive 2.5s infinite ease-in-out;
    outline: none;
    position: relative;
    text-decoration: none;
}
.anim-interactive-button span[class*="fa-"] {
    display: inline-block;
    font-size: 20px;
    transition: transform 0.25s ease-out;
}
.anim-interactive-button:hover {
    background-color: #5f51e3;
    transform: scale(1.12) translateY(-3px);
    box-shadow: 0 7px 18px rgba(0, 0, 0, 0.25);
}
.anim-interactive-button:hover span[class*="fa-"] {
    transform: rotate(18deg);
}
.anim-interactive-button:active {
    background-color: #5245c9;
    transform: scale(0.93) translateY(0px);
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
    transition-duration: 0.08s;
}
.anim-interactive-button:active span[class*="fa-"] {
    transform: rotate(-8deg) scale(0.88);
}
.anim-interactive-button.is-working {
    animation: pulseWorkingBorderInteractive 1s infinite ease-in-out, breatheShadowInteractive 2.5s infinite ease-in-out paused !important;
}
.anim-interactive-button.is-working:hover {
    transform: scale(1.05) translateY(-1px);
}
.anim-button-tooltip {
    position: absolute;
    right: calc(100% + 10px);
    top: 50%;
    transform: translateY(-50%) translateX(10px);
    background-color: #2d3436;
    color: #fff;
    padding: 8px 12px;
    border-radius: 4px;
    font-size: 14px;
    opacity: 0;
    transition: opacity 0.25s ease, transform 0.25s ease;
    white-space: nowrap;
    z-index: 1001;
    pointer-events: none;
}
.card-helper-status-notification {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background-color: #3e444c;
    color: #f0f0f0;
    padding: 10px 18px;
    border-radius: 6px;
    font-size: 14px;
    font-family: Arial, sans-serif;
    z-index: 2147483647;
    display: flex;
    align-items: center;
    box-shadow: 0 2px 6px rgba(0,0,0,0.25);
    opacity: 0;
    transition: opacity 0.4s ease, bottom 0.4s ease;
    max-width: 380px;
    min-width: 280px;
    box-sizing: border-box;
}
.card-helper-status-notification.show {
    opacity: 1;
    bottom: 30px;
}
.ch-status-icon-container {
    margin-right: 10px;
    display: flex;
    align-items: center;
    height: 18px;
}
.card-helper-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid #666;
    border-top: 2px solid #ddd;
    border-radius: 50%;
    animation: cardHelperSpin 0.8s linear infinite;
}
.card-helper-checkmark,
.card-helper-crossmark {
    font-size: 18px;
    line-height: 1;
}
.card-helper-checkmark {
    color: #76c779;
}
.card-helper-crossmark {
    color: #e57373;
}
.card-helper-status-text {
    white-space: normal;
    text-align: left;
    line-height: 1.3;
}
.promo-code-button-custom {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 9999;
    width: auto;
    height: auto;
    padding: 10px 18px;
    border-radius: 25px;
    text-decoration: none;
    font-size: 14px;
    line-height: 1.2;
}
.promo-code-button-custom .fal {
    margin-right: 8px;
    font-size: 16px;
}

.toggle-stats-button {
    position: fixed;
    bottom: 70px;
    left: 20px;
    z-index: 9998;
    width: auto;
    height: auto;
    padding: 8px 15px;
    border-radius: 20px;
    text-decoration: none;
    font-size: 13px;
    line-height: 1.2;
}
.toggle-stats-button .fal {
    margin-right: 6px;
    font-size: 14px;
}
#scrolltop {
    display: none !important;
}
`;
document.head.appendChild(style);

function clearIcons() {
    $('.card-notification:first')?.click();
}

function autoRepeatCheck() {
    clearIcons();
    checkGiftCard(document);
    const volumeButton = document.querySelector('.adv_volume.volume_on');
    if (volumeButton) {
        volumeButton.click();
    }
}

async function checkGiftCard(doc) {
    const button = doc.querySelector('#gift-icon');
    if (!button) return;
    const giftCode = button.getAttribute('data-code');
    if (!giftCode) return;
    try {
        const response = await fetch('/engine/ajax/controller.php?mod=gift_code_game', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({ code: giftCode, user_hash: dle_login_hash })
        });
        const data = await response.json();
        if (data.status === 'ok') {
            showTemporaryMessage('giftStatus', data.text, true);
            button.remove();
        } else if (data.text) {
            showTemporaryMessage('giftStatus', data.text, false);
        }
    } catch (error) {
        console.error("Error checking gift card:", error);
        showTemporaryMessage('giftError', "Ошибка проверки гифт карты.", false);
    }
}

async function checkNewCard() {
    if (!isAutoLootEnabled) {
        return;
    }

    const userHash = window.dle_login_hash;
    if (!userHash) {
        setTimeout(() => {
            if (window.dle_login_hash) {
                checkNewCard();
            }
        }, 2000);
        return;
    }

    const currentDateTime = new Date();
    const localStorageKey = 'checkCardStopped' + userHash;
    const currentHourMarker = currentDateTime.toISOString().slice(0, 13);

    if (localStorage.getItem(localStorageKey) === currentHourMarker && !isAutoLootEnabled) {
        return;
    }
     if (localStorage.getItem(localStorageKey) === currentHourMarker) {
        return;
    }

    try {
        await sleep(DELAY * 2);

        const cardForWatchPayload = {
            user_hash: userHash
        };

        const responseText = await $.ajax({
            url: "/ajax/card_for_watch/",
            type: "post",
            data: cardForWatchPayload,
            cache: false
        });

        if (typeof responseText === 'string') {
            let jsonData;
            if (responseText.startsWith("cards{") && responseText.endsWith("}")) {
                try {
                    const jsonString = responseText.substring(5);
                    jsonData = JSON.parse(jsonString);
                } catch (e) {
                }
            }

            if (jsonData && jsonData.if_reward && jsonData.if_reward.toLowerCase() === "yes") {
                if (jsonData.reward_limit !== undefined && parseInt(jsonData.reward_limit, 10) === 0) {
                    localStorage.setItem(localStorageKey, currentHourMarker);
                }
            }
        }
    } catch (e) {
        let errorMsg = "Ошибка автосбора: ";
        if (e.status !== undefined) errorMsg += `HTTP ${e.status} `;
        if (e.statusText) errorMsg += `${e.statusText} `;
    }
}

async function setCache(key, data, baseTtlInSeconds = 86400) {
    const jitterPercent = 0.10;
    const jitter = Math.round(baseTtlInSeconds * jitterPercent * (Math.random() * 2 - 1));
    const finalTtlInSeconds = baseTtlInSeconds + jitter;
    const expires = Date.now() + finalTtlInSeconds * 1000;
    const cacheData = { data, expires };
    try {
        localStorage.setItem(key, JSON.stringify(cacheData));
    } catch (e) {
        console.error("Ошибка при записи в localStorage (возможно, переполнен):", e);
        showTemporaryMessage('localStorageError', 'Ошибка записи в localStorage.', false);
    }
}

async function getCache(key) {
    const cacheDataJSON = localStorage.getItem(key);
    if (!cacheDataJSON) return null;
    try {
        const cacheData = JSON.parse(cacheDataJSON);
        if (!cacheData || typeof cacheData !== 'object' || !cacheData.expires || !('data' in cacheData) || Date.now() > cacheData.expires) {
             localStorage.removeItem(key); return null;
        }
        return cacheData.data;
    } catch (e) {
        localStorage.removeItem(key); return null;
    }
}

async function cacheCard(key, data) { await setCache(key, data); }
async function getCard(key) { return await getCache(key); }

function clearCardCache() {
    let clearedCount = 0, animeCardsDataCleared = false;
    Object.keys(localStorage).forEach(key => {
        if (key.startsWith('cardId: ')) {
            try { const parsed = JSON.parse(localStorage.getItem(key)); if (parsed?.data && parsed.expires) { localStorage.removeItem(key); clearedCount++; } } catch (e) {}
        } else if (key === 'animeCardsData') { localStorage.removeItem(key); animeCardsDataCleared = true; }
    });
    showTemporaryMessage('cacheCleared', `Очищено ${clearedCount} карт. ${animeCardsDataCleared ? "Общий кеш очищен." : ""}`, true);
}

(function() {
    'use strict';
    function initializeScript() {
        if (typeof $ === 'undefined') { console.error("jQuery не найден."); }
        if (typeof dle_login_hash === 'undefined') console.warn("dle_login_hash не определена.");
        addUpdateButton();
        manageAutoLootInterval();
        setInterval(autoRepeatCheck, 2000);
        $('#tg-banner')?.remove(); try { localStorage.setItem('notify18', 'closed'); localStorage.setItem('hideTelegramAs', 'true'); } catch (e) {}
    }
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initializeScript);
    else initializeScript();
})();