Card Helper AStars | AnimeStars | ASStars

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

// ==UserScript==
// @name         Card Helper AStars | AnimeStars | ASStars
// @namespace    animestars.org
// @version      7.13
// @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/*
// @license      MIT
// @grant        none
// ==/UserScript==

const DELAY = 300;

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/${cardId}/users/`);

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

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

            const newPopularityCount = parseInt(mainCardPageDoc.querySelector('#owners-count')?.textContent.trim(), 10) || 0;
            const newNeedCount = parseInt(mainCardPageDoc.querySelector('#owners-need')?.textContent.trim(), 10) || 0;
            const newTradeCount = parseInt(mainCardPageDoc.querySelector('#owners-trade')?.textContent.trim(), 10) || 0;

            popularityCount = newPopularityCount;
            needCount = newNeedCount;
            tradeCount = newTradeCount;

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

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

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

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();
        const stats = document.createElement('div');
        stats.className = 'card-stats';
        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("Error in updateCardInfo for 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 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 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', 37, 'Узнать спрос', 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', 63, 'Очистить кеш карт', clearCardCache));
    }
}

function isMyCardPage() { return (/^\/user\/(.*)\/cards(\/page\/\d+\/)?/).test(window.location.pathname); }
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);
    }
}

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 countCurrentlyCachedCards = () => Object.values(storedData).reduce((sum, userCards) => sum + (Array.isArray(userCards) ? userCards.length : 0), 0);
        let processedCardsCount = 0;
        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) totalCardsInProfile = parseInt(match[1], 10); }
        if (totalCardsInProfile !== -1) {
            let currentTotalCached = countCurrentlyCachedCards();
            if (totalCardsInProfile === currentTotalCached && currentTotalCached > 0) {
                showTemporaryMessage(notificationId, 'Кеш карточек уже актуален.', true);
                await processCards(); return;
            }
        }
        displayNotification(notificationId, 'Кеширование страниц:', 'progress', { current: countCurrentlyCachedCards(), total: totalCardsInProfile > 0 ? totalCardsInProfile : 'неизвестно', sticky: true });
        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 cardId = cardEl.getAttribute('data-id'), ownerId = cardEl.getAttribute('data-owner-id');
                    if (!ownerId || !cardId) continue;
                    const ownerKey = 'o_' + ownerId;
                    if (!storedData[ownerKey]) storedData[ownerKey] = [];
                    if (!storedData[ownerKey].find(c => c.cardId === cardId && c.ownerId === ownerId)) {
                        storedData[ownerKey].push({ cardId, name: cardEl.getAttribute('data-name'), rank: cardEl.getAttribute('data-rank'), animeLink: cardEl.getAttribute('data-anime-link'), image: cardEl.getAttribute('data-image'), ownerId });
                        newCardsThisBatch++;
                    }
                }
                await sleep(10);
            }
            if (newCardsThisBatch > 0)  processedCardsCount += newCardsThisBatch;
            updateNotificationProgress(notificationId, 'Кешировано карт:', countCurrentlyCachedCards(), totalCardsInProfile > 0 ? totalCardsInProfile : 'неизвестно');
        }
        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 lastLinkText = pagination.querySelector('a:last-of-type:not(.pagination__item--next)'); if(lastLinkText) lastPageNumber = parseInt(lastLinkText.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(`Workspace error ${url}:`, e); return null; } })(pageUrl);
                    if (pageHTML) await processBatchInStorage(parser.parseFromString(pageHTML, 'text/html'));
                    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('card-id') || cardElement.getAttribute('data-card-id') || cardElement.getAttribute('data-id');
    const href = cardElement.getAttribute('href');
    if (href) { const cardIdMatch = href.match(/\/cards\/(\d+)\/users\//); if (cardIdMatch) cardId = cardIdMatch[1]; }
    if (cardId) { const cardByOwner = await getFirstCardByOwner(cardId); if (cardByOwner?.cardId) cardId = cardByOwner.cardId; }
    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;
    }
}

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 2px 6px rgba(0, 0, 0, 0.15); transform: scale(1); } 50% { box-shadow: 0 5px 15px rgba(108, 92, 231, 0.4); transform: scale(1.02); } 100% { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); 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%; max-height: calc(100% - 30px); 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%; max-height: calc(100% - 30px); border-radius: 8px; z-index: 1; animation: glowChargeEffect 1.5s infinite; pointer-events: none; }

.card-stats {position: relative; background: linear-gradient(45deg, #6c5ce7, #a367dc); padding: 8px; color: white; font-size: 12px; margin-top: 5px; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; text-shadow: 1px 1px 2px rgba(0,0,0,0.3); animation: fadeInUp 0.3s ease; z-index: 0 !important;}
.history__inner {max-width: 1200px !important; margin: 0 auto !important; padding: 15px !important;}
.history__item {background: rgba(108, 92, 231, 0.05) !important; border-radius: 10px !important; padding: 20px !important; margin-bottom: 20px !important;}
.history__body {display: flex !important; flex-wrap: wrap !important; gap: 15px !important; padding: 15px !important; border-radius: 8px !important;}
.history__body--gained {background: rgba(46, 213, 115, 0.1) !important; margin-bottom: 10px !important;}
.history__body--lost {background: rgba(255, 71, 87, 0.1) !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;}
.card-stats span i {font-size: 14px;}
.lootbox__card {position: relative !important; transform: scale(0.85) !important; margin-top: -15px !important; margin-bottom: 35px !important;}
.lootbox__card .card-stats {position: absolute !important; bottom: -35px !important; left: 0 !important; right: 0 !important; margin: 0; padding: 8px !important; border-radius: 5px; z-index: 9999 !important; background: linear-gradient(45deg, #6c5ce7, #a367dc) !important; font-size: 16px !important; width: 100% !important; transform: none !important; text-rendering: optimizeLegibility !important; -webkit-font-smoothing: antialiased !important;}
.lootbox__card .card-stats span {color: white !important; text-shadow: 1px 1px 2px rgba(0,0,0,0.3) !important; padding: 0 8px !important; flex: 1; text-align: center; font-weight: 500 !important;}
.lootbox__card .card-stats i {color: white !important; font-size: 16px !important; margin-right: 4px;}
.lootbox__list {gap: 25px !important; padding-bottom: 20px !important;}

@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; }
    .anim-button-tooltip { font-size: 11px !important; padding: 5px 8px !important; }
    .card-stats {font-size: 10px !important; padding: 4px !important;} .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;}
    .lootbox__card {transform: scale(0.8) !important; margin-top: -20px !important; margin-bottom: 30px !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; }
.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; }
`;
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 startPing() {
    const userHash = window.dle_login_hash;
    if (!userHash) return;
    try {
        await sleep(DELAY * 3);
        await $.ajax({
            url: "/engine/ajax/controller.php?mod=user_count_timer",
            type: "post", data: { user_hash: userHash }, dataType: "json", cache: false
        });
    } catch (e) {
        console.error("Error in startPing:", e.statusText || e.message || e);
    }
}

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

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

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

    try {
        await sleep(DELAY * 3);

        const data = await new Promise((resolve, reject) => {
            $.ajax({
                url: "/engine/ajax/controller.php?mod=reward_card",
                type: "post",
                data: {
                    action: "check_reward",
                    user_hash: userHash
                },
                dataType: "json",
                cache: false,
                success: resolve,
                error: reject
            });
        });

        if (data && data.stop_reward === "yes") {
            localStorage.setItem(localStorageKey, currentHourMarker);
            return;
        }

        if (!data || !data.cards || !data.cards.owner_id) {
            return;
        }

        if (data.cards.name) {
            showTemporaryMessage('newCardReceived', 'Получена новая карта: ' + data.cards.name, true, 5000);
        }

        const ownerId = data.cards.owner_id;

        await $.ajax({
            url: "/engine/ajax/controller.php?mod=cards_ajax",
            type: "post",
            data: {
                action: "take_card",
                owner_id: ownerId,
                user_hash: userHash
            },
            dataType: "json",
            cache: false
        });

    } catch (e) {
        let statusText = "Error";
        let message = "";
        let responseText = "";

        if (e && e.statusText) {
            statusText = e.statusText;
            message = e.message || "";
            responseText = e.responseText || "";
        } else if (Array.isArray(e) && e.length > 0) {
            if (e[0] && e[0].statusText) statusText = e[0].statusText;
            if (e[1]) message = e[1];
            if (e[2]) message += (message ? ": " : "") + e[2];
            if (e[0] && e[0].responseText) responseText = e[0].responseText;
        } else if (e instanceof Error) {
            message = e.message;
        } else if (typeof e === 'string') {
            message = e;
        }

        if (responseText) {
        }
    }
}

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 не найден."); showTemporaryMessage('jQueryError', 'jQuery не найден!', false, 10000); }
        if (typeof dle_login_hash === 'undefined') console.warn("dle_login_hash не определена.");
        addUpdateButton();
        setInterval(autoRepeatCheck, 2000); setInterval(startPing, 31000); setInterval(checkNewCard, 10000);
        $('#tg-banner')?.remove(); try { localStorage.setItem('notify18', 'closed'); localStorage.setItem('hideTelegramAs', 'true'); } catch (e) {}
    }
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initializeScript);
    else initializeScript();
})();