Card Helper AStars | AnimeStars | ASStars

card helper

// ==UserScript==
// @name         Card Helper AStars | AnimeStars | ASStars
// @namespace    animestars.org
// @version      6.7
// @description  card helper
// @author       bmr
// @match        https://astars.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 = 40;

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';

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

async function getCount(cardId, type) {
    const currentDomain = getCurrentDomain();
    let count = 0;
    let needResponse = await fetch(`${currentDomain}/cards/${cardId}/users/${type}/`);
    if (needResponse.status === 502) {
        throw new Error("502 Bad Gateway");
    }
    let needHtml = '';
    let needDoc = '';
    if (needResponse.ok) {
        needHtml = await needResponse.text();
        needDoc = new DOMParser().parseFromString(needHtml, 'text/html');
        count = needDoc.querySelectorAll('.profile__friends-item').length;
    } else {
        return count;
    }

    const pagination = needDoc.querySelector('.pagination__pages');
    if (pagination && count >= 50) {
        const lastPageNum = pagination.querySelector('a:last-of-type');
        const totalPages = lastPageNum ? parseInt(lastPageNum.innerText, 10) : 1;
        if (totalPages > 1) {
            count = (totalPages - 1) * 50;
        }
        needResponse = await fetch(`${currentDomain}/cards/${cardId}/users/${type}/page/${totalPages}`);
        if (needResponse.status === 502) {
            throw new Error("502 Bad Gateway");
        }
        if (needResponse.ok) {
            needHtml = await needResponse.text();
            needDoc = new DOMParser().parseFromString(needHtml, 'text/html');
            count += needDoc.querySelectorAll('.profile__friends-item').length;
        }
    }

    return count;
}

async function loadCard(cardId) {
    const cacheKey = 'cardId: ' + cardId;
    let card = await getCard(cacheKey) ?? {};

    if (Object.keys(card).length) {
        return card;
    }

    const currentDomain = getCurrentDomain();
    await sleep(DELAY);
    let needCount = await getCount(cardId, 'need');
    await sleep(DELAY);
    let tradeCount = await getCount(cardId, 'trade');
    await sleep(DELAY);

    let popularityCount = 0;
    const popularityResponse = await fetch(`${currentDomain}/cards/${cardId}/users/`);

    if (popularityResponse.ok) {
        const popularityHtml = await popularityResponse.text();
        const popularityDoc = new DOMParser().parseFromString(popularityHtml, 'text/html');

        const pagination = popularityDoc.querySelector('.pagination__pages');
        if (pagination) {
            const lastPageNum = pagination.querySelector('a:last-of-type');
            const totalPages = lastPageNum ? parseInt(lastPageNum.innerText, 10) : 1;

            if (totalPages > 1) {
                popularityCount = (totalPages - 1) * 40;
                await sleep(DELAY);
                const lastPageResponse = await fetch(`${currentDomain}/cards/${cardId}/users/page/${totalPages}`);
                if (lastPageResponse.ok) {
                    const lastPageHtml = await lastPageResponse.text();
                    const lastPageDoc = new DOMParser().parseFromString(lastPageHtml, 'text/html');
                    const lastPageCount = lastPageDoc.querySelectorAll('.card-show__owner').length;
                    popularityCount += lastPageCount;
                }
            } else {
                popularityCount = popularityDoc.querySelectorAll('.card-show__owner').length;
            }
        } else {
            popularityCount = popularityDoc.querySelectorAll('.card-show__owner').length;
        }
    }

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

async function updateCardInfo(cardId, element) {
    if (!cardId || !element) {
        return;
    }
    try {
        const card = await loadCard(cardId);

        let statsContainer = document.querySelector('.card-stats-container');
        if (!statsContainer) {
            statsContainer = document.createElement('div');
            statsContainer.className = 'card-stats-container';
            document.body.appendChild(statsContainer);
        }

        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>
                ${card.popularityCount}
            </span>
            <span title="Хотят получить">
                <i class="fas fa-heart"></i>
                ${card.needCount}
            </span>
            <span title="Готовы обменять">
                <i class="fas fa-sync-alt"></i>
                ${card.tradeCount}
            </span>
        `;

        element.appendChild(stats);

    } catch (error) {
        throw error;
    }
}

function clearMarkFromCards() {
    cleanByClass('div-marked');
}

function removeAllLinkIcons() {
    cleanByClass('link-icon');
}

function cleanByClass(className) {
    const list = document.querySelectorAll('.' + className);
    list.forEach(item => item.remove());
}

function getCardsOnPage() {
    return Array.from(
        document.querySelectorAll(cardClasses)
    ).filter(card => card.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 cards = getCardsOnPage();
    let counter = cards.length;

    if (!counter) {
        return;
    }

    let buttonId = 'processCards';
    startAnimation(buttonId);
    updateButtonCounter(buttonId, counter);

    showNotification('Проверяю спрос на ' + counter + ' карточек');

    for (const card of cards) {
        if (card.classList.contains('trade__inventory-item--lock') || card.classList.contains('remelt__inventory-item--lock')) {
            continue;
        }
        card.classList.add('processing-card');
        let cardId = await getCardId(card);
        if (cardId) {
            await updateCardInfo(cardId, card).catch(error => {
                return;
            });
            counter--;
            updateButtonCounter(buttonId, counter);
        }
        card.classList.remove('processing-card');

        if (card.classList.contains('lootbox__card')) {
            card.addEventListener('click', removeAllLinkIcons);
        }
    }

    showNotification('Проверка спроса завершена');
    stopAnimation(buttonId);
}

function removeMatchingWatchlistItems() {
    const watchlistItems = document.querySelectorAll('.watchlist__item');
    if (watchlistItems.length == 0) {
        return;
    }
    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();
                }
            }
        }
    });

    if (watchlistItems.length) {
        showNotification('Из списка удалены просмотренные аниме. В списке осталось ' + document.querySelectorAll('.watchlist__item').length + ' записей.');
    }
}

function startAnimation(id) {
    $('#' + id + ' span:first').css('animation', 'pulseIcon 1s ease-in-out infinite');
}

function stopAnimation(id) {
    $('#' + id + ' span:first').css('animation', '');
}

function getButton(id, className, percent, text, clickFunction) {
    const button = document.createElement('button');
    button.id = id;
    button.style.position = 'fixed';
    button.style.top = percent + '%';
    button.style.right = '1%';
    button.style.zIndex = '1000';
    button.style.backgroundColor = '#6c5ce7';
    button.style.color = '#fff';
    button.style.border = 'none';
    button.style.borderRadius = '50%';
    button.style.width = '45px';
    button.style.height = '45px';
    button.style.padding = '0';
    button.style.cursor = 'pointer';
    button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
    button.style.transition = 'all 0.3s ease';

    let tooltipTimeout;

    button.onmouseover = function() {
        this.style.backgroundColor = '#5f51e3';
        tooltip.style.opacity = '1';
        tooltip.style.transform = 'translateX(0)';
        if (tooltipTimeout) {
            clearTimeout(tooltipTimeout);
        }
    };
    button.onmouseout = function() {
        this.style.backgroundColor = '#6c5ce7';
        tooltip.style.opacity = '0';
        tooltip.style.transform = 'translateX(10px)';
    };

    const icon = document.createElement('span');
    icon.className = 'fal fa-' + className;
    icon.style.display = 'inline-block';
    icon.style.fontSize = '20px';
    button.appendChild(icon);

    const tooltip = document.createElement('div');
    tooltip.style.cssText = `
        position: fixed;
        right: calc(1% + 55px);
        background-color: #2d3436;
        color: #fff;
        padding: 8px 12px;
        border-radius: 4px;
        font-size: 14px;
        opacity: 0;
        transition: all 0.3s ease;
        white-space: nowrap;
        top: ${percent}%;
        transform: translateX(10px);
        z-index: 999;
        pointer-events: none;
    `;

    switch(id) {
        case 'processCards':
            tooltip.textContent = 'Узнать спрос';
            break;
        case 'readyToCharge':
            tooltip.textContent = 'Отметить всё как "Готов обменять"';
            break;
        case 'readyRemeltCards':
            tooltip.textContent = 'Кешировать карточки';
            break;
        default:
            tooltip.textContent = text;
    }

    button.addEventListener('click', function(e) {
        e.stopPropagation();
        clickFunction(e);

        if (window.innerWidth <= 768) {
            tooltip.style.opacity = '1';
            tooltip.style.transform = 'translateX(0)';

            if (tooltipTimeout) {
                clearTimeout(tooltipTimeout);
            }
            tooltipTimeout = setTimeout(() => {
                tooltip.style.opacity = '0';
                tooltip.style.transform = 'translateX(10px)';
            }, 1000);
        }
    });

    const container = document.createElement('div');
    container.appendChild(tooltip);
    container.appendChild(button);

    button.classList.add('action-button');

    return container;
}

function updateButtonCounter(id, counter) {
    return;
}

function addUpdateButton() {
    if (window.location.pathname.includes('/pm/') ||
        window.location.pathname.includes('emotions.php') ||
        window.frameElement) {
        return;
    }

    if (!document.querySelector('#fetchLinksButton')) {
        let cards = getCardsOnPage();

        document.body.appendChild(getButton('processCards', 'star', 37, 'Сравнить карточки', processCards));

        if (!cards.length) {
            return
        }

        if (isMyCardPage()) {
            document.body.appendChild(getButton('readyToCharge', 'handshake', 50, '"Готов поменять" на все карточки', readyToCharge));
        }

        if (isCardRemeltPage()) {
            document.body.appendChild(getButton('readyRemeltCards', 'yin-yang', 50, 'закешировать карточки', readyRemeltCards));
            const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
            updateButtonCounter('readyRemeltCards', Object.keys(storedData).length);
        }
    }
}

function isMyCardPage() {
    return (/^\/user\/(.*)\/cards(\/page\/\d+\/)?/).test(window.location.pathname)
}

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

async function readyRemeltCards() {
    showNotification('Кеширую все карты так как иначе на этой странице не получится их определить рейтинги');
    const linkElement = document.querySelector('a.button.button--left-icon.mr-3');
    const href = linkElement ? linkElement.href : null;
    if (!href) {
        return;
    }
    removeMatchingWatchlistItems();
    removeAllLinkIcons();
    clearMarkFromCards();
    const cards = getCardsOnPage();
    let counter = cards.length;
    if (!counter) {
        return;
    }
    let buttonId = 'readyRemeltCards';
    startAnimation(buttonId);
    await scrapeAllPages(href, buttonId);
    stopAnimation(buttonId);
}

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

    const progressBar = createProgressBar();
    let totalCards = 0;
    let processedCards = 0;

    let storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
    const titleElement = firstPageDoc.querySelector('h1.secondary-title.text-center');
    if (titleElement) {
        const match = titleElement.textContent.match(/\((\d+)\s*шт\.\)/);
        totalCards = match ? parseInt(match[1], 10) : -1;
        if (totalCards == Object.keys(storedData).length) {
            showNotification('На данный момент в кеше карточек ровно столько же сколько в профиле пользователя');
            return;
        }
    }

    async function processCardsToLocalstorage(doc) {
        const cards = doc.querySelectorAll('.anime-cards__item');
        for (let i = 0; i < cards.length; i += 5) {
            const cardGroup = Array.from(cards).slice(i, i + 5);
            for (const card of cardGroup) {
                const cardId = card.getAttribute('data-id');
                const ownerId = card.getAttribute('data-owner-id');
                const name = card.getAttribute('data-name');
                const rank = card.getAttribute('data-rank');
                const animeLink = card.getAttribute('data-anime-link');
                const image = card.getAttribute('data-image');
                const ownerKey = 'o_' + ownerId;
                if (!ownerId || !cardId) continue;
                if (!storedData[ownerKey]) {
                    storedData[ownerKey] = [];
                }
                storedData[ownerKey].push({ cardId, name, rank, animeLink, image, ownerId });
                processedCards++;
                if (totalCards > 0) {
                    progressBar.update(processedCards, totalCards);
                }
            }
            await sleep(10);
        }
    }

    async function fetchPage(url) {
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`Ошибка загрузки страницы ${url}`);
            return await response.text();
        } catch (error) {
            return null;
        }
    }

    await processCardsToLocalstorage(firstPageDoc);

    const lastPageLink = pagination.querySelector('a:last-of-type');
    if (lastPageLink) {
        const lastPageNumber = parseInt(lastPageLink.textContent.trim(), 10);
        if (!isNaN(lastPageNumber) && lastPageNumber > 1) {
            const parser = new DOMParser();
            for (let i = 2; i <= lastPageNumber; i++) {
                const pageUrl = lastPageLink.href.replace(/page\/\d+/, `page/${i}`);
                const pageHTML = await fetchPage(pageUrl);
                if (pageHTML) {
                    await processCardsToLocalstorage(parser.parseFromString(pageHTML, 'text/html'));
                }
                await sleep(1000);

                if (i % 3 === 0) {
                    localStorage.setItem('animeCardsData', JSON.stringify(storedData));
                }
            }
        }
    }

    localStorage.setItem('animeCardsData', JSON.stringify(storedData));

    setTimeout(() => {
        progressBar.remove();
    }, 1000);

    document.body.appendChild(getButton('processCards', 'star', 37, 'Сравнить карточки', processCards));
    await processCards();
}

async function getCardId(card) {
    let cardId = card.getAttribute('card-id') || card.getAttribute('data-card-id') || card.getAttribute('data-id');
    const href = card.getAttribute('href');
    if (href) {
        let cardIdMatch = href.match(/\/cards\/(\d+)\/users\//);
        if (cardIdMatch) {
            cardId = cardIdMatch[1];
        }
    }
    if (cardId) {
        const cardByOwner = await getFirstCardByOwner(cardId);
        if (cardByOwner) {
            cardId = cardByOwner.cardId;
        }
    }
    return cardId;
}

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

async function readyToCharge() {
    showNotification('Отмечаем все карты на странице как: "Готов обменять" кроме тех что на обмене и заблокированных');
    let cards = getCardsOnPage();

    let counter = cards.length;
    let buttonId = 'readyToCharge';
    startAnimation(buttonId);
    updateButtonCounter(buttonId, counter);
    clearMarkFromCards();

    const progressBar = createProgressBar();
    cardCounter = 0;

    const totalCards = cards.filter(card => !card.classList.contains('trade__inventory-item--lock')).length;
    let processedCards = 0;

    for (const card of cards) {
        if (card.classList.contains('trade__inventory-item--lock')) {
            continue;
        }

        let cardId = await getCardId(card);
        if (cardId) {
            await readyToChargeCard(cardId);
            processedCards++;
            progressBar.update(processedCards, totalCards);
            counter--;
            updateButtonCounter(buttonId, counter);
        }
    }

    setTimeout(() => {
        progressBar.remove();
    }, 1000);

    showNotification('Отправили на обмен ' + cardCounter + ' карточек на странице');
    stopAnimation(buttonId);
}

const readyToChargeCard = async (cardId) => {
    await sleep(DELAY * 2);
    const url = '/engine/ajax/controller.php?mod=trade_ajax';
    const data = {
        action: 'propose_add',
        type: 1,
        card_id: cardId,
        user_hash: dle_login_hash
    };

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams(data).toString()
        });
        if (response.status === 502) {
            throw new Error("502 Bad Gateway");
        }
        if (response.ok) {
            const data = await response.json();
            if (data.error) {
                if (data.error == 'Слишком часто, подождите пару секунд и повторите действие') {
                    await readyToChargeCard(cardId);
                    return;
                }
            }
            if ( data.status == 'added' ) {
                cardCounter++;
                return;
            }
            if ( data.status == 'deleted' ) {
                await readyToChargeCard(cardId);
                return;
            }
            cardCounter++;
        }
    } catch (error) {
    }
};

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 fadeInUp {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.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;
}

/* Общие стили для карт */
.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: 150px !important;
        height: auto !important;
        transition: transform 0.2s !important;
    }

    .history__body-item img {
        width: 150px !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;
}

/* Мобильная версия истории трейдов */
@media screen and (max-width: 768px) {
    .history__body-item,
    .history__body-item img {
        width: 120px !important;
    }

    .processing-card::before {
        top: -1px !important;
        left: -1px !important;
        right: -1px !important;
        bottom: -1px !important;
        opacity: 0.5 !important;
    }
}

.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 4px;
    background: #ddd;
    z-index: 10000;
}

.progress-bar__fill {
    width: 0%;
    height: 100%;
    background: linear-gradient(to right, #6c5ce7, #a367dc);
    transition: width 0.3s ease;
    position: relative;
}

.progress-bar__text {
    position: absolute;
    left: 50%;
    top: 5px;
    transform: translateX(-50%);
    color: #ffffff;
    font-size: 14px;
    font-weight: bold;
    background: #6c5ce7;
    padding: 2px 8px;
    border-radius: 10px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
}

.card-stats span {
    display: flex;
    align-items: center;
    gap: 4px;
}

.card-stats span i {
    font-size: 14px;
}


/* Мобильная версия */
@media screen and (max-width: 768px) {
    .action-button {
        transform: scale(0.8) !important;
    }

    .action-button button {
        width: 35px !important;
        height: 35px !important;
    }

    .action-button span {
        font-size: 16px !important;
    }

    .action-button div {
        font-size: 12px !important;
        padding: 6px 10px !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.75) !important;
        margin-top: -10px !important;
        margin-bottom: 25px !important;
    }

    .lootbox__card .card-stats {
        font-size: 14px !important;
        padding: 6px !important;
    }

    .lootbox__card .card-stats i {
        font-size: 14px !important;
    }

    .lootbox__list {
        gap: 15px !important;
        padding-bottom: 15px !important;
    }

    /* Уменьшение размера карт в истории */
    .history__body-item {
        width: 100px !important;
    }

    .history__body-item img {
        width: 100px !important;
    }

    /* Уменьшение размера прогресс-бара */
    .progress-bar {
        height: 3px !important;
    }

    .progress-bar__text {
        font-size: 12px !important;
        padding: 1px 6px !important;
    }
}

/* Стили для карт */
.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;
}

/* Специальные стили для карт в паках */
.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) {
    .lootbox__card {
        transform: scale(0.8) !important;
        margin-top: -20px !important;
        margin-bottom: 30px !important;
    }
}
`;
document.head.appendChild(style);

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

function autoRepeatCheck() {
    clearIcons();
    checkGiftCard(document);

    Audio.prototype.play = function() {
       return new Promise(() => {});
    };
}

async function checkGiftCard(doc) {
    const button = doc.querySelector('#gift-icon');
    if (!button) return;

    const giftCode = button.getAttribute('data-code');
    if (!giftCode) return false;

    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') {
            showNotification(data.text);
            button.remove();
        }
    } catch (error) {
    }
}

function startPing() {
    const userHash = window.dle_login_hash;
    if (!userHash) {
        return;
    }
    const currentDomain = getCurrentDomain();
    const url = `${currentDomain}/engine/ajax/controller.php?mod=user_count_timer&user_hash=${userHash}`;
    fetch(url)
        .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
    })
        .then(data => {
    })
        .catch(error => {
    });
}

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;
    if (localStorage.getItem(localStorageKey) === currentDateTime.toISOString().slice(0, 13)) {
        return;
    }

    const currentDomain = getCurrentDomain();
    const url = `${currentDomain}/engine/ajax/controller.php?mod=reward_card&action=check_reward&user_hash=${userHash}`;

    fetch(url)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            if (data.stop_reward === "yes") {
                localStorage.setItem(localStorageKey, currentDateTime.toISOString().slice(0, 13));
                return;
            }
            if (!data.cards || !data.cards.owner_id) {
                return;
            }
            const ownerId = data.cards.owner_id;
            if (data.cards.name) {
                showNotification('Получена новая карта "' + data.cards.name + '"');
            }
            const url = `${currentDomain}/engine/ajax/controller.php?mod=cards_ajax`;
            const postData = new URLSearchParams({
                action: "take_card",
                owner_id: ownerId
            });
            fetch(url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                },
                body: postData.toString()
            })
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error! Status: ${response.status}`);
                    }
                    return response.json();
                })
                .then(data => {
                })
                .catch(error => {
                });
        })
        .catch(error => {
        });
}

async function setCache(key, data, ttlInSeconds) {
    const expires = Date.now() + ttlInSeconds * 1000;
    const cacheData = { data, expires };
    localStorage.setItem(key, JSON.stringify(cacheData));
}

async function getCache(key) {
    const cacheData = JSON.parse(localStorage.getItem(key));
    if (!cacheData) return null;
    if (Date.now() > cacheData.expires) {
        localStorage.removeItem(key);
        return null;
    }
    return cacheData.data;
}

async function cacheCard(key, data) {
    await setCache(key, data, 3600);
}

async function getCard(key) {
    return await getCache(key);
}

function addClearButton() {
    const filterControls = document.querySelector('.card-filter-form__controls');
    if (!filterControls) {
        return;
    }
    const inputField = filterControls.querySelector('.card-filter-form__search');
    if (!inputField) {
        return;
    }
    const searchButton = filterControls.querySelector('.tabs__search-btn');
    if (!searchButton) {
        return;
    }
    inputField.addEventListener('keydown', function (event) {
        if (event.key === 'Enter') {
            event.preventDefault();
            searchButton.click();
        }
    });
    const clearButton = document.createElement('button');
    clearButton.innerHTML = '<i class="fas fa-times"></i>';
    clearButton.classList.add('clear-search-btn');
    clearButton.style.margin = '5px';
    clearButton.style.position = 'absolute';
    clearButton.style.padding = '10px';
    clearButton.style.background = 'red';
    clearButton.style.color = 'white';
    clearButton.style.border = 'none';
    clearButton.style.cursor = 'pointer';
    clearButton.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
    clearButton.style.fontSize = '14px';
    clearButton.style.borderRadius = '5px';
    clearButton.addEventListener('click', function () {
        inputField.value = '';
        searchButton.click();
    });
    inputField.style.marginLeft = '30px';
    inputField.parentNode.insertBefore(clearButton, inputField);
}

function showNotification(message) {
    const notification = document.createElement('div');
    notification.style.cssText = `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        background: linear-gradient(45deg, #6c5ce7, #a367dc);
        color: white;
        padding: 12px 24px;
        border-radius: 8px;
        box-shadow: 0 4px 15px rgba(0,0,0,0.2);
        z-index: 9999;
        animation: slideDown 0.5s ease, fadeOut 0.5s ease 2.5s forwards;
        font-size: 14px;
    `;
    notification.textContent = message;
    document.body.appendChild(notification);
    setTimeout(() => notification.remove(), 3000);
}

function createProgressBar() {
    const progressBar = document.createElement('div');
    progressBar.className = 'progress-bar';
    progressBar.innerHTML = `
        <div class="progress-bar__fill"></div>
        <div class="progress-bar__text">0%</div>
    `;

    const style = document.createElement('style');
    style.textContent = `
        .progress-bar {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 4px;
            background: #ddd;
            z-index: 10000;
        }
        .progress-bar__fill {
            width: 0%;
            height: 100%;
            background: linear-gradient(to right, #6c5ce7, #a367dc);
            transition: width 0.3s ease;
            position: relative;
        }
        .progress-bar__text {
            position: absolute;
            left: 50%;
            top: 5px;
            transform: translateX(-50%);
            color: #ffffff;
            font-size: 14px;
            font-weight: bold;
            background: #6c5ce7;
            padding: 2px 8px;
            border-radius: 10px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
        }
    `;
    document.head.appendChild(style);
    document.body.appendChild(progressBar);

    return {
        update: (current, total) => {
            const percentage = Math.round((current / total) * 100);
            progressBar.querySelector('.progress-bar__fill').style.width = percentage + '%';
            progressBar.querySelector('.progress-bar__text').textContent = percentage + '%';
        },
        remove: () => progressBar.remove()
    };
}

function clearCardCache() {
    const keys = Object.keys(localStorage);
    let clearedCount = 0;

    keys.forEach(key => {
        if (key.startsWith('cardId: ')) {
            localStorage.removeItem(key);
            clearedCount++;
        }
    });

    showNotification(`Очищено ${clearedCount} карточек из кеша`);
}

function addClearCacheButton() {
    if (window.location.pathname.includes('/pm/') ||
        window.location.pathname.includes('emotions.php') ||
        window.frameElement) {
        return;
    }

    if (!document.querySelector('#clearCacheButton')) {
        const button = getButton('clearCacheButton', 'trash', 63, 'Очистить кеш карт', clearCardCache);
        document.body.appendChild(button);
    }
}

(function() {
    'use strict';

    function checkTimeAndReset() {
        const now = new Date();
        const mskTime = new Date(now.getTime() + (now.getTimezoneOffset() + 180) * 60000);
        const hours = mskTime.getHours();
        const today = mskTime.toISOString().split('T')[0];

        const userHash = window.dle_login_hash;
        if (!userHash) return;

        const stopKey = 'checkCardStopped' + userHash;
        const stopValue = localStorage.getItem(stopKey);

        if (hours >= 0 && stopValue) {
            localStorage.removeItem(stopKey);
            console.log('Сброс ограничения сбора карт выполнен');

            checkNewCard();
            setTimeout(checkNewCard, 2000);
            setTimeout(checkNewCard, 5000);
        }
    }

    function initializeScript() {
        checkTimeAndReset();

        setInterval(autoRepeatCheck, 2000);
        setInterval(startPing, 31000);
        setInterval(checkNewCard, 10000);
        setInterval(checkTimeAndReset, 60000);

        setInterval(() => {
            if (!document.querySelector('#processCards')) {
                addUpdateButton();
                addClearButton();
                addClearCacheButton();
            }
        }, 500);

        $('#tg-banner').remove();
        localStorage.setItem('notify18', 'closed');
        localStorage.setItem('hideTelegramAs', 'true');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

    window.addEventListener('pageshow', initializeScript);
})();

(function() {
    'use strict';

    let lastActiveTime = localStorage.getItem('lastCrystalTime') || "00:00";
    let processedCrystals = new Set(JSON.parse(localStorage.getItem('processedCrystals') || '[]'));
    let workerBlob = null;
    let worker = null;

    function getCurrentTime() {
        const now = new Date();
        return now.getHours().toString().padStart(2, '0') + ":" + now.getMinutes().toString().padStart(2, '0');
    }

    function getCrystalId(msg) {
        const timeElement = msg.querySelector(".lc_chat_li_date");
        const author = msg.querySelector(".lc_chat_li_autor");
        if (timeElement && author) {
            return `${author.textContent.trim()}_${timeElement.textContent.trim()}`;
        }
        return null;
    }

    function initializeWorker() {
        if (worker) return;

        if (typeof Worker === 'undefined') {
            console.log('Web Workers не поддерживаются в этом браузере');
            setInterval(clickOnCrystal, 1000);
            setInterval(preventTimeout, 2000);
            setInterval(detectInactiveCrystal, 4000);
            setInterval(() => {
                const now = new Date();
                if (now.getHours() === 0 && now.getMinutes() === 0) {
                    resetTimerAtMidnight();
                }
            }, 60000);
            return;
        }

        try {
            const workerCode = `
                let intervalIds = [];

                self.onmessage = function(e) {
                    if (e.data.action === 'start') {
                        clearAllIntervals();
                        startIntervals();
                    } else if (e.data.action === 'stop') {
                        clearAllIntervals();
                    }
                };

                function clearAllIntervals() {
                    intervalIds.forEach(id => clearInterval(id));
                    intervalIds = [];
                }

                function startIntervals() {
                    intervalIds.push(setInterval(() => {
                        self.postMessage({ type: 'checkCrystal' });
                    }, 1000));

                    intervalIds.push(setInterval(() => {
                        self.postMessage({ type: 'checkTimeout' });
                    }, 2000));

                    intervalIds.push(setInterval(() => {
                        self.postMessage({ type: 'checkInactive' });
                    }, 4000));

                    intervalIds.push(setInterval(() => {
                        const now = new Date();
                        if (now.getHours() === 0 && now.getMinutes() === 0) {
                            self.postMessage({ type: 'midnight' });
                        }
                    }, 60000));
                }
            `;

            workerBlob = new Blob([workerCode], { type: 'application/javascript' });
            worker = new Worker(URL.createObjectURL(workerBlob));

            worker.onmessage = function(e) {
                switch(e.data.type) {
                    case 'checkCrystal':
                        clickOnCrystal();
                        break;
                    case 'checkTimeout':
                        preventTimeout();
                        break;
                    case 'checkInactive':
                        detectInactiveCrystal();
                        break;
                    case 'midnight':
                        resetTimerAtMidnight();
                        break;
                }
            };

            worker.onerror = function(error) {
                console.log('Ошибка воркера:', error);
                setInterval(clickOnCrystal, 1000);
                setInterval(preventTimeout, 2000);
                setInterval(detectInactiveCrystal, 4000);
                setInterval(() => {
                    const now = new Date();
                    if (now.getHours() === 0 && now.getMinutes() === 0) {
                        resetTimerAtMidnight();
                    }
                }, 60000);
            };
        } catch (error) {
            console.log('Ошибка при создании воркера:', error);
            setInterval(clickOnCrystal, 1000);
            setInterval(preventTimeout, 2000);
            setInterval(detectInactiveCrystal, 4000);
            setInterval(() => {
                const now = new Date();
                if (now.getHours() === 0 && now.getMinutes() === 0) {
                    resetTimerAtMidnight();
                }
            }, 60000);
        }
    }

    function clickOnCrystal() {
        try {
            const chatMessages = document.querySelectorAll(".lc_chat_li");
            chatMessages.forEach(msg => {
                const diamond = msg.querySelector("#diamonds-chat");
                const timeElement = msg.querySelector(".lc_chat_li_date");

                if (diamond && timeElement) {
                    const crystalId = getCrystalId(msg);
                    let messageTime = timeElement.textContent.trim();

                    if (messageTime >= lastActiveTime && !processedCrystals.has(crystalId)) {
                        diamond.click();
                        lastActiveTime = messageTime;
                        processedCrystals.add(crystalId);
                        try {
                            localStorage.setItem('lastCrystalTime', lastActiveTime);
                            localStorage.setItem('processedCrystals', JSON.stringify([...processedCrystals]));
                        } catch (e) {
                            console.log('Ошибка сохранения в localStorage:', e);
                        }
                    }
                }
            });
        } catch (error) {
            console.log('Ошибка в clickOnCrystal:', error);
        }
    }

    function preventTimeout() {
        const chatContainer = document.querySelector('.lc_chat');
        if (chatContainer) {
            try {
                const moveEvent = new MouseEvent('mousemove', {
                    bubbles: true,
                    cancelable: true,
                    view: window,
                    clientX: 1,
                    clientY: 1
                });
                chatContainer.dispatchEvent(moveEvent);
            } catch (e) {}
        }

        const timeoutButton = document.querySelector(".lc_chat_timeout_imback");
        if (timeoutButton) {
            timeoutButton.click();
        }
    }

    function detectInactiveCrystal() {
        const warning = document.querySelector(".DLEPush-notification.push-warning");
        if (warning) {
            const closeButton = warning.querySelector(".DLEPush-close");
            if (closeButton) {
                closeButton.click();
            }
            lastActiveTime = getCurrentTime();
            localStorage.setItem('lastCrystalTime', lastActiveTime);
        }
    }

    function resetTimerAtMidnight() {
        const now = new Date();
        if (now.getHours() === 0 && now.getMinutes() === 0) {
            lastActiveTime = "00:00";
            processedCrystals.clear();
            localStorage.setItem('lastCrystalTime', lastActiveTime);
            localStorage.setItem('processedCrystals', JSON.stringify([]));
        }
    }

    initializeWorker();
    worker.postMessage({ action: 'start' });

    window.addEventListener('beforeunload', function() {
        if (worker) {
            worker.terminate();
            URL.revokeObjectURL(workerBlob);
        }
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址