// ==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();
})();