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