animestars Auto Helper

хелпер который помогает определить популярность карты на сайте astars.club

当前为 2025-05-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name animestars Auto Helper
  3. // @namespace animestars.org
  4. // @version 3.30
  5. // @description хелпер который помогает определить популярность карты на сайте astars.club
  6. // @author astars lover
  7. // @match https://astars.club/*
  8. // @match https://asstars1.astars.club/*
  9. // @match https://animestars.org/*
  10. // @match https://asstars.tv/*
  11. // @match https://asstars.astars.club/*
  12. // @match https://as1.astars.club/*
  13. // @match https://as2.astars.club/*
  14. // @match https://as3.astars.club/*
  15. // @match https://as4.astars.club/*
  16. // @match https://as5.astars.club/*
  17. // @match https://as6.astars.club/*
  18. // @match https://as7.astars.club/*
  19. // @match https://as8.astars.club/*
  20. // @match https://as9.astars.club/*
  21. // @match https://as10.astars.club/*
  22. // @match https://as11.astars.club/*
  23. // @match https://as12.astars.club/*
  24. // @match https://as13.astars.club/*
  25. // @match https://as14.astars.club/*
  26. // @match https://as15.astars.club/*
  27. // @match https://as16.astars.club/*
  28. // @match https://as17.astars.club/*
  29. // @match https://as18.astars.club/*
  30. // @match https://as18.astars.club/*
  31. // @match https://as20.astars.club/*
  32. // @match https://as21.astars.club/*
  33. // @match https://as22.astars.club/*
  34. // @match https://as23.astars.club/*
  35. // @match https://as24.astars.club/*
  36. // @match https://as25.astars.club/*
  37. // @match https://as26.astars.club/*
  38. // @match https://as27.astars.club/*
  39. // @match https://as28.astars.club/*
  40. // @match https://as29.astars.club/*
  41. // @match https://as30.astars.club/*
  42. // @match https://as31.astars.club/*
  43. // @match https://as32.astars.club/*
  44. // @match https://as33.astars.club/*
  45. // @match https://as34.astars.club/*
  46. // @match https://as35.astars.club/*
  47. // @match https://as36.astars.club/*
  48.  
  49. // @license MIT
  50. // @grant none
  51.  
  52. // ==/UserScript==
  53.  
  54. const DELAY = 50; // Задержка между запросами в миллисекундах (по умолчанию 0,5 секунды) не менять чтоб не делать избыточную нагрузку на сайт
  55.  
  56. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  57.  
  58. let cardCounter = 0;
  59.  
  60. 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-show__placeholder';
  61.  
  62. async function getCount(cardId, type) {
  63.  
  64. // Определяем текущий домен
  65. const currentDomain = window.location.origin;
  66. let name = '';
  67.  
  68. let count = 0;
  69. let needResponse = await fetch(`${currentDomain}/cards/${cardId}/users/${type}/`);
  70. if (needResponse.status === 502) {
  71. console.error("Ошибка 502: Остановка выполнения скриптов.");
  72. throw new Error("502 Bad Gateway");
  73. }
  74. let needHtml = '';
  75. let needDoc = '';
  76. if (needResponse.ok) {
  77. needHtml = await needResponse.text();
  78. needDoc = new DOMParser().parseFromString(needHtml, 'text/html');
  79. count = needDoc.querySelectorAll('.profile__friends-item').length;
  80. } else {
  81. return {count: count, name: name};
  82. }
  83.  
  84. let countOnPage = 38;
  85.  
  86. const pagination = needDoc.querySelector('.pagination__pages');
  87. if (pagination && count >= countOnPage) {
  88. const lastPageNum = pagination.querySelector('a:last-of-type');
  89. const totalPages = lastPageNum ? parseInt(lastPageNum.innerText, 10) : 1;
  90. if (totalPages > 1) {
  91. count = (totalPages - 1) * countOnPage;
  92. }
  93. needResponse = await fetch(`${currentDomain}/cards/${cardId}/users/${type}/page/${totalPages}`);
  94. if (needResponse.status === 502) {
  95. console.error("Ошибка 502: Остановка выполнения скриптов.");
  96. throw new Error("502 Bad Gateway");
  97. }
  98. if (needResponse.ok) {
  99. needHtml = await needResponse.text();
  100. needDoc = new DOMParser().parseFromString(needHtml, 'text/html');
  101. count += needDoc.querySelectorAll('.profile__friends-item').length;
  102. }
  103. }
  104.  
  105. if (type == 'trade') {
  106. name = needDoc.querySelector('.ncard__main-title a').innerText;
  107. }
  108.  
  109. return {count: count, name: name};
  110. }
  111.  
  112. function addCheckMark(element) {
  113. if (!element) return;
  114. const checkMark = document.createElement('i');
  115. checkMark.classList.add('fas', 'fa-check', 'div-marked');
  116. checkMark.style.position = 'absolute';
  117. checkMark.style.bottom = '5px';
  118. checkMark.style.left = '5px';
  119. checkMark.style.background = 'green';
  120. checkMark.style.color = 'white';
  121. checkMark.style.borderRadius = '50%';
  122. checkMark.style.padding = '5px';
  123. checkMark.style.fontSize = '16px';
  124. checkMark.style.width = '24px';
  125. checkMark.style.height = '24px';
  126. checkMark.style.display = 'flex';
  127. checkMark.style.alignItems = 'center';
  128. checkMark.style.justifyContent = 'center';
  129. element.classList.add('div-checked');
  130. if (window.getComputedStyle(element).position === 'static') {
  131. element.style.position = 'relative';
  132. }
  133. element.appendChild(checkMark);
  134. }
  135.  
  136. function addInCardMark(element, count) {
  137. if (!element) return;
  138. const checkMark = document.createElement('span');
  139. checkMark.classList.add('dupl-count');
  140. element.classList.add('div-checked');
  141. checkMark.title = 'Карт в корзине';
  142. if (window.getComputedStyle(element).position === 'static') {
  143. element.style.position = 'relative';
  144. }
  145. checkMark.innerText = count;
  146. element.appendChild(checkMark);
  147. }
  148.  
  149. async function iNeedCard(cardId) {
  150. await sleep(DELAY * 2);
  151. const url = '/engine/ajax/controller.php?mod=trade_ajax';
  152. const data = {
  153. action: 'propose_add',
  154. type: 0,
  155. card_id: cardId,
  156. user_hash: dle_login_hash
  157. };
  158.  
  159. try {
  160. const response = await fetch(url, {
  161. method: 'POST',
  162. headers: {
  163. 'Content-Type': 'application/x-www-form-urlencoded',
  164. },
  165. credentials: 'same-origin',
  166. body: new URLSearchParams(data).toString()
  167. });
  168. if (response.status === 502) {
  169. console.error("Ошибка 502: Остановка выполнения скриптов.");
  170. throw new Error("502 Bad Gateway");
  171. }
  172. if (response.ok) {
  173. const data = await response.json();
  174. if (data.error) {
  175. if (data.error == 'Слишком часто, подождите пару секунд и повторите действие') {
  176. await readyToChargeCard(cardId);
  177. return;
  178. } else {
  179. DLEPush?.info(data.error);
  180. }
  181. }
  182. if ( data.status == 'added' ) {
  183. cardCounter++;
  184. return;
  185. }
  186. if ( data.status == 'deleted' ) {
  187. await sleep(DELAY * 2);
  188. await iNeedCard(cardId);
  189. return;
  190. }
  191. cardCounter++;
  192. } else {
  193. console.error('Ошибка запроса:', response.status);
  194. }
  195. } catch (error) {
  196. console.error('Ошибка выполнения POST-запроса:', error);
  197. }
  198. }
  199.  
  200. async function loadCard(cardId) {
  201. const cacheKey = 'card_id: ' + cardId;
  202. let card = await getCard(cacheKey) ?? {};
  203. if (Object.keys(card).length) {
  204. // return card;
  205. }
  206.  
  207. // console.log(`Обработка карточки с ID: ${cardId}`);
  208. const currentDomain = window.location.origin;
  209. await sleep(DELAY);
  210. let rankText = '';
  211. const popularityResponse = await fetch(`${currentDomain}/cards/${cardId}/users/`);
  212. if (popularityResponse.status === 502) {
  213. console.error("Ошибка 502: Остановка выполнения скриптов.");
  214. throw new Error("502 Bad Gateway");
  215. }
  216. let likes = 0;
  217. let dislikes = 0;
  218. let popularityCount = 0;
  219. let needСount = 0;
  220. let tradeСount = 0;
  221. let tradeName = '';
  222.  
  223. console.log('popularityResponse.ok', popularityResponse.ok);
  224. if (popularityResponse.ok) {
  225. const popularityHtml = await popularityResponse.text();
  226. const popularityDoc = new DOMParser().parseFromString(popularityHtml, 'text/html');
  227.  
  228. needСount = popularityDoc.querySelectorAll('.ncard__tabs-btns a span')[0].innerText;
  229. tradeСount = popularityDoc.querySelectorAll('.ncard__tabs-btns a span')[1].innerText;
  230. tradeName = popularityDoc.querySelector('meta[name="description"]').getAttribute('content').replace('Все обладатели карточки ','');
  231. rankText = popularityDoc.querySelector('.ncard__rank').innerText.replace('Редкость\n','').trim();
  232.  
  233. await checkGiftCard(popularityDoc); // ищем небесный камень заодно
  234. const animeUrl = popularityDoc.querySelector('.card-show__placeholder')?.href;
  235. if (animeUrl) {
  236. try {
  237. const response = await fetch(animeUrl);
  238. if (!response.ok) {
  239. throw new Error(`Ошибка HTTP: ${response.status}`);
  240. }
  241. const htmlText = await response.text();
  242. const parser = new DOMParser();
  243. const doc = parser.parseFromString(htmlText, 'text/html');
  244.  
  245. likes = parseInt(doc.querySelector('[data-likes-id]')?.textContent?.trim(), 10);
  246. dislikes = parseInt(doc.querySelector('[data-dislikes-id]')?.textContent?.trim(), 10);
  247. checkGiftCard(doc); // ищем небесный камень заодно
  248. } catch (error) {
  249. console.error('Ошибка при загрузке страницы:', error);
  250. }
  251. }
  252. popularityCount = popularityDoc.querySelectorAll('.card-show__owner').length;
  253. const pagination = popularityDoc.querySelector('.pagination__pages');
  254. if (pagination) {
  255. const lastPageNum = pagination.querySelector('a:last-of-type');
  256. const totalPages = lastPageNum ? parseInt(lastPageNum.innerText, 10) : 1;
  257.  
  258. if (totalPages > 1 && popularityCount >= 27) {
  259. popularityCount = (totalPages - 1) * 27;
  260. const needResponse = await fetch(`${currentDomain}/cards/${cardId}/users/page/${totalPages}`);
  261. if (needResponse.status === 502) {
  262. console.error("Ошибка 502: Остановка выполнения скриптов.");
  263. throw new Error("502 Bad Gateway");
  264. }
  265. if (needResponse.ok) {
  266. const lastPageDoc = new DOMParser().parseFromString(await needResponse.text(), 'text/html');
  267. await checkGiftCard(lastPageDoc); // ищем небесный камень заодно
  268. popularityCount += lastPageDoc.querySelectorAll('.card-show__owner').length;
  269. }
  270. }
  271. }
  272. }
  273.  
  274. card = {likes: likes, dislikes: dislikes, rankText: rankText, popularityCount: popularityCount, needCount: needСount, tradeCount: tradeСount, name: tradeName};
  275.  
  276. console.log('card', card);
  277.  
  278. if (card.likes || card.dislikes) {
  279. await cacheCard(cacheKey, card)
  280. }
  281.  
  282. return card;
  283. }
  284.  
  285. async function updateCardInfo(cardId, element) {
  286. if (!cardId || !element) {
  287. console.log(cardId, 'updateCardInfo error');
  288. return;
  289. }
  290. try {
  291. const card = await loadCard(cardId);
  292. console.log(card);
  293.  
  294. element.querySelector('.link-icon')?.remove();
  295. const icon = document.createElement('div');
  296. icon.className = 'link-icon';
  297. icon.style.position = 'absolute';
  298. icon.style.top = '10px';
  299. icon.style.right = '10px';
  300. icon.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
  301. icon.style.color = '#05ed5b';
  302. icon.style.padding = '5px';
  303. icon.style.borderRadius = '5px';
  304. icon.style.fontSize = '8px';
  305. let anime = card.likes && card.dislikes ? `<br>аниме: +${card.likes} / -${card.dislikes}` : '';
  306. if (isCardsPackPage() || isTradesPage() || isTradePage())
  307. {
  308. let num = await getCardsCountInCart(card.name, cardId);
  309. anime += '<br>у меня: ' + num + ' шт';
  310. }
  311. icon.innerHTML = `Ранг: ${card.rankText}<br>имеют: ${card.popularityCount}<br>хотят: ${card.needCount}<br>отдают: ${card.tradeCount}` + anime;
  312. element.style.position = 'relative';
  313. element.appendChild(icon);
  314. } catch (error) {
  315. console.error(`Ошибка обработки карты ${cardId}:`, error);
  316. throw error;
  317. }
  318. }
  319.  
  320. function clearMarkFromCards() {
  321. cleanByClass('div-marked');
  322. }
  323.  
  324. function removeAllLinkIcons() {
  325. cleanByClass('link-icon');
  326. }
  327.  
  328. function cleanByClass(className) {
  329. const list = document.querySelectorAll('.' + className);
  330. list.forEach(item => item.remove());
  331. }
  332.  
  333. function getCardsOnPage() {
  334. return Array.from(
  335. document.querySelectorAll(cardClasses)
  336. ).filter(card => card.offsetParent !== null);
  337. }
  338.  
  339. async function processCards() {
  340.  
  341. if (isCardRemeltPage()) {
  342. const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
  343. if (Object.keys(storedData).length < 1) {
  344. await readyRemeltCards();
  345. return;
  346. }
  347. }
  348.  
  349. removeMatchingWatchlistItems();
  350. removeAllLinkIcons();
  351. clearMarkFromCards();
  352.  
  353. const cards = getCardsOnPage();
  354. let counter = cards.length;
  355.  
  356. if (!counter) {
  357. return;
  358. }
  359.  
  360. let buttonId = 'processCards';
  361. startAnimation(buttonId);
  362. updateButtonCounter(buttonId, counter);
  363. for (const card of cards) {
  364.  
  365. if (card.classList.contains('trade__inventory-item--lock') || card.classList.contains('remelt__inventory-item--lock')) {
  366. continue;
  367. }
  368. let cardId = await getCardId(card);
  369. if (cardId) {
  370. await updateCardInfo(cardId, card).catch(error => {
  371. console.error("Остановка из-за критической ошибки:", error.message);
  372. return;
  373. });
  374. addCheckMark(card);
  375. counter--;
  376. updateButtonCounter(buttonId, counter);
  377. } else {
  378. console.log(cardId, 'cardId not found');
  379. }
  380.  
  381. if (card.classList.contains('lootbox__card')) {
  382. card.addEventListener('click', removeAllLinkIcons);
  383. }
  384. }
  385. stopAnimation(buttonId);
  386. }
  387.  
  388. function removeMatchingWatchlistItems() {
  389. const watchlistItems = document.querySelectorAll('.watchlist__item');
  390. if (watchlistItems.length == 0) {
  391. return;
  392. }
  393. watchlistItems.forEach(item => {
  394. const episodesText = item.querySelector('.watchlist__episodes')?.textContent.trim();
  395. if (episodesText) {
  396. const matches = episodesText.match(/[\d]+/g);
  397. if (matches) {
  398. const currentEpisode = parseInt(matches[0], 10);
  399. const totalEpisodes = parseInt(matches.length === 4 ? matches[3] : matches[1], 10);
  400. if (currentEpisode === totalEpisodes) {
  401. item.remove();
  402. //console.log(`Удалён блок: ${item}`);
  403. }
  404. }
  405. }
  406. });
  407.  
  408. if (watchlistItems.length) {
  409. DLEPush?.info('Из списка удалены просмотренные аниме. В списке осталось ' + document.querySelectorAll('.watchlist__item').length + ' записей.');
  410. }
  411. }
  412.  
  413. function startAnimation(id) {
  414. $('#' + id + ' span:first').css('animation', 'rotateIcon 2s linear infinite');
  415. }
  416.  
  417. function stopAnimation(id) {
  418. $('#' + id + ' span:first').css('animation', '');
  419. }
  420.  
  421. function getButton(id, className, percent, text, clickFunction) {
  422. const button = document.createElement('button');
  423. button.id = id;
  424. button.title = text;
  425. button.style.position = 'fixed';
  426. button.style.top = percent + '%';
  427. button.style.right = '1%';
  428. button.style.zIndex = '1000';
  429. button.style.backgroundColor = '#007bff';
  430. button.style.color = '#fff';
  431. button.style.border = 'none';
  432. button.style.borderRadius = '5px';
  433. button.style.padding = '10px 15px';
  434. button.style.cursor = 'pointer';
  435. button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
  436. const icon = document.createElement('span');
  437. icon.className = 'fal fa-' + className;
  438. icon.style.display = 'inline-block';
  439. button.appendChild(icon);
  440. const info = document.createElement('span');
  441. info.id = id + '_counter';
  442. info.className = 'guest__notification';
  443. info.style.display = 'none';
  444. button.appendChild(info);
  445. button.addEventListener('click', clickFunction);
  446. return button;
  447. }
  448.  
  449. function updateButtonCounter(id, counter) {
  450. let c = $('#' + id + '_counter');
  451. c.css('display', counter > 0 ? 'flex' : 'none');
  452. c.text(counter);
  453. }
  454.  
  455. function addUpdateButton() {
  456. if (!document.querySelector('#fetchLinksButton')) {
  457. let cards = getCardsOnPage();
  458.  
  459. document.body.appendChild(getButton('processCards', 'rocket', 37, 'Сравнить карточки', processCards));
  460.  
  461. if (!cards.length) {
  462. return
  463. }
  464.  
  465. let myCardPage = isMyCardPage();
  466. if (myCardPage) {
  467. document.body.appendChild(getButton('readyToCharge', 'circle-check', 50, '"Готов поменять" на все карточки', readyToCharge));
  468. }
  469.  
  470. let animePage = isAnimePage();
  471. if (animePage) {
  472. document.body.appendChild(getButton('iNeedAllThisCards', 'search', 50, '"Хочу карту" на все карточки', iNeedAllThisCards));
  473. }
  474.  
  475. let cardRemeltPage = isCardRemeltPage();
  476. if (cardRemeltPage) {
  477. document.body.appendChild(getButton('readyRemeltCards', 'yin-yang', 50, 'закешировать карточки', readyRemeltCards));
  478. const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
  479. updateButtonCounter('readyRemeltCards', Object.keys(storedData).length);
  480. }
  481.  
  482. if (document.querySelectorAll(`[data-id][data-name]`).length) {
  483. let percent = myCardPage || cardRemeltPage || cardRemeltPage ? 63 : 50;
  484. document.body.appendChild(getButton('checkCardsCountInCart', 'suitcase', percent, 'проверить наличие в корзине', checkCardsCountInCart));
  485. }
  486. }
  487. }
  488.  
  489. async function checkCardsCountInCart() {
  490. let cardsCountInCart = document.querySelectorAll(`[data-id][data-name]`);
  491. if (cardsCountInCart.length < 1) {
  492. return;
  493. }
  494.  
  495. let buttonId = 'checkCardsCountInCart';
  496. startAnimation(buttonId);
  497. let counter = cardsCountInCart.length;
  498. updateButtonCounter(buttonId, counter);
  499.  
  500. for (const card of cardsCountInCart) {
  501. let name = card?.getAttribute("data-name");
  502. let id = card?.getAttribute("data-id");
  503. if (!name || !id) {
  504. continue;
  505. }
  506. let num = await getCardsCountInCart(name, id);
  507. addInCardMark(card, num);
  508. counter--;
  509. updateButtonCounter(buttonId, counter);
  510. }
  511. stopAnimation(buttonId);
  512. }
  513.  
  514. function isMyCardPage() {
  515. return (/^\/user\/(.*)\/cards(\/page\/\d+\/)?/).test(window.location.pathname)
  516. }
  517.  
  518. function isCardRemeltPage() {
  519. return (/^\/cards_remelt\//).test(window.location.pathname)
  520. }
  521.  
  522. function isCardsPackPage() {
  523. return (/^\/cards\/pack\//).test(window.location.pathname)
  524. }
  525.  
  526. function isTradesPage() {
  527. return (/^\/trades\//).test(window.location.pathname)
  528. }
  529.  
  530. function isTradePage() {
  531. return (/^\/cards\/.*\/trade\//).test(window.location.pathname)
  532. }
  533.  
  534. function isAnimePage() {
  535. return document.getElementById('anime-data') !== null;
  536. }
  537.  
  538. async function readyRemeltCards() {
  539. DLEPush.info('Кеширую все карты так как иначе на этой странице не получится их определить рейтинги');
  540. // получить все карты пользователя и запомнить ассоциации номеров карт в локальный кеш
  541. const linkElement = document.querySelector('a.button.button--left-icon.mr-3');
  542. const href = linkElement ? linkElement.href : null;
  543. if (!href) {
  544. return;
  545. }
  546. removeMatchingWatchlistItems();
  547. removeAllLinkIcons();
  548. clearMarkFromCards();
  549. const cards = getCardsOnPage();
  550. let counter = cards.length;
  551. if (!counter) {
  552. return;
  553. }
  554. let buttonId = 'readyRemeltCards';
  555. startAnimation(buttonId);
  556. updateButtonCounter(buttonId, 0);
  557. await scrapeAllPages(href, buttonId);
  558. stopAnimation(buttonId);
  559. }
  560.  
  561. async function scrapeAllPages(firstPageHref, buttonId) {
  562. const response = await fetch(firstPageHref);
  563. if (!response.ok) {
  564. throw new Error(`Ошибка HTTP: ${response.status}`);
  565. }
  566. const firstPageDoc = new DOMParser().parseFromString(await response.text(), 'text/html');
  567. const pagination = firstPageDoc.querySelector('#pagination');
  568. if (!pagination) {
  569. console.log('Пагинация не найдена');
  570. return;
  571. }
  572. let storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
  573. const titleElement = firstPageDoc.querySelector('h1.secondary-title.text-center');
  574. if (titleElement) {
  575. const match = titleElement.textContent.match(/\((\d+)\s*шт\.\)/);
  576. const cardsCount = match ? parseInt(match[1], 10) : -1;
  577. if (cardsCount == Object.keys(storedData).length) {
  578. DLEPush.info('На данный момент в кеше карточек ровно столько же сколько в профиле пользователя');
  579. return;
  580. }
  581. }
  582.  
  583. // Получаем ссылку на последнюю страницу
  584. const lastPageLink = pagination.querySelector('a:last-of-type');
  585. if (!lastPageLink) {
  586. console.log('Последняя страница не найдена');
  587. return;
  588. }
  589. const lastPageNumber = parseInt(lastPageLink.textContent.trim(), 10);
  590. if (isNaN(lastPageNumber)) {
  591. console.log('Не удалось определить номер последней страницы');
  592. return;
  593. }
  594. updateButtonCounter(buttonId, lastPageNumber);
  595. // console.log(`Обнаружено страниц: ${lastPageNumber}`);
  596. // clear data
  597. localStorage.removeItem('animeCardsData');
  598. storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
  599. // Функция для обработки карточек на странице
  600. async function processCardsToLocalstorage(doc, pageNum) {
  601. const cards = doc.querySelectorAll('.anime-cards__item');
  602. cards.forEach(card => {
  603. const cardId = card.getAttribute('data-id');
  604. const ownerId = card.getAttribute('data-owner-id');
  605. const name = card.getAttribute('data-name');
  606. const rank = card.getAttribute('data-rank');
  607. const animeLink = card.getAttribute('data-anime-link');
  608. const image = card.getAttribute('data-image');
  609. const ownerKey = 'o_' + ownerId;
  610. if (!ownerId || !cardId) return;
  611. if (!storedData[ownerKey]) {
  612. storedData[ownerKey] = []; // Если ключа нет, создаем пустой массив
  613. }
  614. storedData[ownerKey].push({ cardId, name, rank, animeLink, image, ownerId });
  615. });
  616. // console.log(`Обработано ${cards.length} карточек на странице: ` + pageNum + ', всего к сохранению: ' + Object.keys(storedData).length);
  617. }
  618.  
  619. async function fetchPage(url) {
  620. try {
  621. const response = await fetch(url);
  622. if (!response.ok) throw new Error(`Ошибка загрузки страницы ${url}`);
  623. return await response.text();
  624. } catch (error) {
  625. console.error(error);
  626. return null;
  627. }
  628. }
  629.  
  630. processCardsToLocalstorage(firstPageDoc, 1);
  631. updateButtonCounter(buttonId, lastPageNumber);
  632.  
  633. if (lastPageNumber > 1) {
  634. const parser = new DOMParser();
  635. for (let i = 2; i <= lastPageNumber; i++) {
  636. const pageUrl = lastPageLink.href.replace(/page\/\d+/, `page/${i}`);
  637. // console.log(`Загружаем страницу ${i}: ${pageUrl}`);
  638. const pageHTML = await fetchPage(pageUrl);
  639. if (pageHTML) {
  640. processCardsToLocalstorage(parser.parseFromString(pageHTML, 'text/html'), i);
  641. }
  642. await new Promise(resolve => setTimeout(resolve, 3000)); // Ждем 3 секунды между запросами
  643. updateButtonCounter(buttonId, lastPageNumber - i);
  644. }
  645. }
  646.  
  647. // console.log('Данные сохранены в localStorage');
  648. localStorage.setItem('animeCardsData', JSON.stringify(storedData));
  649. updateButtonCounter(buttonId, Object.keys(storedData).length);
  650.  
  651. document.body.appendChild(getButton('processCards', 'rocket', 37, 'Сравнить карточки', processCards));
  652. await processCards();
  653. }
  654.  
  655. async function iNeedAllThisCards() {
  656. let cards = getCardsOnPage();
  657. DLEPush.info('Отметить "Хочу карточку" на все ' + cards.length + ' карточек на странице');
  658.  
  659. let counter = cards.length;
  660. let buttonId = 'iNeedAllThisCards';
  661. startAnimation(buttonId);
  662. updateButtonCounter(buttonId, counter);
  663. clearMarkFromCards();
  664.  
  665. cardCounter = 0;
  666. for (const card of cards) {
  667. if (card.classList.contains('anime-cards__owned-by-user')) {
  668. counter--;
  669. updateButtonCounter(buttonId, counter);
  670. continue;
  671. }
  672. let cardId = await getCardId(card);
  673. if (cardId) {
  674. await iNeedCard(cardId).catch(error => {
  675. console.error("Остановка из-за критической ошибки:", error.message);
  676. return;
  677. });
  678. addCheckMark(card);
  679. counter--;
  680. updateButtonCounter(buttonId, counter);
  681. } else {
  682. console.log(cardId, 'cardId not found');
  683. }
  684. }
  685. stopAnimation(buttonId);
  686. }
  687.  
  688. async function getCardId(card) {
  689. let cardId = card.getAttribute('card-id') || card.getAttribute('data-card-id') || card.getAttribute('data-id');
  690. const href = card.getAttribute('href');
  691. if (href) {
  692. let cardIdMatch = href.match(/\/cards\/(\d+)\/users\//);
  693. if (cardIdMatch) {
  694. cardId = cardIdMatch[1];
  695. }
  696. }
  697. if (cardId) {
  698. // проверяем что в локально нет такого номера
  699. console.log('проверка в хранилище номера карты ' + cardId);
  700. const cardByOwner = await getFirstCardByOwner(cardId);
  701. // console.log('localStorage', cardByOwner);
  702. if (cardByOwner) {
  703. cardId = cardByOwner.cardId;
  704. }
  705. }
  706. return cardId;
  707. }
  708.  
  709. async function getCardOwnerId(card) {
  710. let cardId = card.getAttribute('data-owner-id');
  711. return cardId;
  712. }
  713.  
  714. async function getCardType(card) {
  715. return card.getAttribute('data-type') || null;
  716. }
  717.  
  718. async function getFirstCardByOwner(ownerId) {
  719. const storedData = JSON.parse(localStorage.getItem('animeCardsData')) || {};
  720. const key = 'o_' + ownerId;
  721.  
  722. return storedData[key] && storedData[key].length > 0 ? storedData[key][0] : null;
  723. }
  724.  
  725. async function readyToCharge() {
  726. DLEPush.info('Отмечаем все карты на странице как: "Готов обменять" кроме тех что на обмене и заблокированных');
  727. let cards = getCardsOnPage();
  728. // DLEPush.info('Карт на странице: ' + cards.length);
  729.  
  730. let counter = cards.length;
  731. let buttonId = 'readyToCharge';
  732. startAnimation(buttonId);
  733. updateButtonCounter(buttonId, counter);
  734. clearMarkFromCards();
  735.  
  736. cardCounter = 0;
  737. for (const card of cards) {
  738. if (card.classList.contains('trade__inventory-item--lock')) {
  739. continue;
  740. }
  741. let cardOwnerId = await getCardOwnerId(card);
  742. if (cardOwnerId) {
  743. await sleep(1000);
  744. //await readyToChargeCard(cardOwnerId);
  745. await cardProposeAdd(cardOwnerId)
  746. counter--;
  747. addCheckMark(card);
  748. updateButtonCounter(buttonId, counter);
  749. }
  750. }
  751. DLEPush.info('Отправили на обмен ' + cardCounter + ' карточек на странице');
  752. stopAnimation(buttonId);
  753. }
  754.  
  755. async function cardProposeAdd(card_id) {
  756. try {
  757. await sleep(DELAY * 3);
  758.  
  759. const data = await new Promise((resolve, reject) => {
  760. $.ajax({
  761. url: "/engine/ajax/controller.php?mod=trade_ajax",
  762. type: "post",
  763. data: {
  764. action: "propose_add",
  765. type: 1,
  766. card_id: card_id,
  767. user_hash: dle_login_hash
  768. },
  769. dataType: "json",
  770. cache: false,
  771. success: resolve,
  772. error: reject
  773. });
  774. });
  775.  
  776. if (data?.error) {
  777. if (data.error === 'Слишком часто, подождите пару секунд и повторите действие') {
  778. return await cardProposeAdd(card_id);
  779. }
  780. console.log('cardProposeAdd ' + card_id, data.error);
  781.  
  782. return false;
  783. }
  784.  
  785. if (data?.status == "added") {
  786. return true;
  787. }
  788.  
  789. if (data?.status == "deleted") {
  790. return await cardProposeAdd(card_id);
  791. }
  792.  
  793. return false;
  794.  
  795. } catch (e) {
  796. console.error("Ошибка запроса:", e);
  797. return false;
  798. }
  799. }
  800.  
  801. const readyToChargeCard = async (cardOwnerId) => {
  802. await sleep(DELAY * 2);
  803. const url = '/engine/ajax/controller.php?mod=trade_ajax';
  804. const data = {
  805. action: 'propose_add',
  806. type: 1,
  807. card_id: cardOwnerId,
  808. user_hash: dle_login_hash
  809. };
  810.  
  811. try {
  812. const response = await fetch(url, {
  813. method: 'POST',
  814. headers: {
  815. 'Content-Type': 'application/x-www-form-urlencoded',
  816. },
  817. credentials: 'same-origin',
  818. body: new URLSearchParams(data).toString()
  819. });
  820. if (response.status === 502) {
  821. console.error("Ошибка 502: Остановка выполнения скриптов.");
  822. throw new Error("502 Bad Gateway");
  823. }
  824. if (response.ok) {
  825. const data = await response.json();
  826. if (data.error) {
  827. if (data.error == 'Слишком часто, подождите пару секунд и повторите действие') {
  828. await readyToChargeCard(cardId);
  829. return;
  830. }
  831. }
  832. if ( data.status == 'added' ) {
  833. cardCounter++;
  834. return;
  835. }
  836. if ( data.status == 'deleted' ) {
  837. await readyToChargeCard(cardId);
  838. return;
  839. }
  840. cardCounter++;
  841. //console.log('Ответ сервера:', data);
  842. } else {
  843. console.error('Ошибка запроса:', response.status);
  844. }
  845. } catch (error) {
  846. console.error('Ошибка выполнения POST-запроса:', error);
  847. }
  848. };
  849.  
  850. // Анимация вращения в CSS
  851. const style = document.createElement('style');
  852. style.textContent = `
  853. @keyframes rotateIcon {
  854. 0% {
  855. transform: rotate(0deg);
  856. }
  857. 100% {
  858. transform: rotate(360deg);
  859. }
  860. }
  861. `;
  862. document.head.appendChild(style);
  863.  
  864. function clearIcons() {
  865. document.querySelector('.card-notification')?.click();
  866. }
  867.  
  868. function autoRepeatCheck() {
  869. clearIcons();
  870. checkGiftCard(document);
  871. document.querySelector('.adv_volume.volume_on')?.click();
  872.  
  873. Audio.prototype.play = function() {
  874. return new Promise(() => {});
  875. };
  876. }
  877.  
  878. async function checkGiftCard(doc) {
  879. const button = doc.querySelector('#gift-icon');
  880. if (!button) return;
  881.  
  882. const giftCode = button.getAttribute('data-code');
  883. if (!giftCode) return false;
  884.  
  885. try {
  886. const response = await fetch('/engine/ajax/controller.php?mod=gift_code_game', {
  887. method: 'POST',
  888. headers: {
  889. 'Content-Type': 'application/x-www-form-urlencoded'
  890. },
  891. credentials: 'same-origin',
  892. body: new URLSearchParams({
  893. code: giftCode,
  894. user_hash: dle_login_hash
  895. })
  896. });
  897. const data = await response.json();
  898. if (data.status === 'ok') {
  899. DLEPush.info(data.text);
  900. button.remove();
  901. }
  902. } catch (error) {
  903. console.error('Ошибка при проверке подарочной карты:', error);
  904. }
  905. }
  906.  
  907. async function startPing() {
  908. if (!dle_login_hash) {
  909. console.error("Переменная dle_login_hash не определена.");
  910. return;
  911. }
  912.  
  913. // Определяем текущий домен
  914. const currentDomain = window.location.origin;
  915.  
  916. try {
  917. await sleep(DELAY * 3);
  918. const user_count_timer_data = await new Promise((resolve, reject) => {
  919. $.ajax({
  920. url: "/engine/ajax/controller.php?mod=user_count_timer",
  921. type: "post",
  922. data: {
  923. user_hash: dle_login_hash
  924. },
  925. dataType: "json",
  926. cache: false,
  927. success: resolve,
  928. error: reject
  929. });
  930. });
  931. } catch (e) {
  932. console.error("Ошибка запроса:", e);
  933. return false;
  934. }
  935. }
  936.  
  937. async function checkNewCard() {
  938. const currentDateTime = new Date();
  939. // Получаем значение из глобальной переменной
  940. // const userHash = window.dle_login_hash;
  941.  
  942. if (!dle_login_hash) {
  943. console.error("Переменная dle_login_hash не определена.");
  944. return;
  945. }
  946.  
  947. const localStorageKey = 'checkCardStopped' + window.dle_login_hash; // Формат YYYY-MM-DDTHH
  948.  
  949. if (localStorage.getItem(localStorageKey) === currentDateTime.toISOString().slice(0, 13)) {
  950. console.log("Проверка карты уже остановлена на текущий час.");
  951. return;
  952. }
  953.  
  954. // Определяем текущий домен
  955. const currentDomain = window.location.origin;
  956.  
  957. try {
  958. await sleep(DELAY * 3);
  959.  
  960. const data = await new Promise((resolve, reject) => {
  961. $.ajax({
  962. url: "/engine/ajax/controller.php?mod=reward_card",
  963. type: "post",
  964. data: {
  965. action: "check_reward",
  966. user_hash: dle_login_hash
  967. },
  968. dataType: "json",
  969. cache: false,
  970. success: resolve,
  971. error: reject
  972. });
  973. });
  974.  
  975. if (data?.stop_reward === "yes") {
  976. console.log("Проверка карт остановлена на текущий час:", data.reason);
  977. localStorage.setItem(localStorageKey, currentDateTime.toISOString().slice(0, 13));
  978. return;
  979. }
  980.  
  981. if (!data.cards || !data.cards.owner_id) {
  982. return;
  983. }
  984.  
  985. if ( data.cards.name ) {
  986. DLEPush?.info('Получена новая карта "' + data.cards.name + '"');
  987. }
  988.  
  989. const ownerId = data.cards.owner_id;
  990. console.log("owner_id получен:", ownerId); // Выводим owner_id
  991.  
  992. $.ajax({
  993. url: "/engine/ajax/controller.php?mod=cards_ajax",
  994. type: "post",
  995. data: {
  996. action: "take_card",
  997. owner_id: ownerId
  998. },
  999. dataType: "json",
  1000. cache: false,
  1001. success: function () {},
  1002. error: function () {}
  1003. });
  1004.  
  1005. } catch (e) {
  1006. console.error("Ошибка запроса:", e);
  1007. return false;
  1008. }
  1009.  
  1010.  
  1011. }
  1012.  
  1013. async function setCache(key, data, ttlInSeconds) {
  1014. const expires = Date.now() + ttlInSeconds * 1000; // Устанавливаем срок хранения
  1015. const cacheData = { data, expires };
  1016. localStorage.setItem(key, JSON.stringify(cacheData));
  1017. }
  1018.  
  1019. async function getCardsCountInCart(name, id) {
  1020. if (!name || !id) return;
  1021.  
  1022. try {
  1023. await sleep(DELAY * 4);
  1024. const searchUrl = document.querySelector('.lgn__btn-profile').getAttribute('href') + `cards/?search=${encodeURIComponent(name)}`;
  1025. const response = await fetch(searchUrl);
  1026. const html = new DOMParser().parseFromString(await response.text(), 'text/html');
  1027. await checkGiftCard(html); // ищем небесный камень заодно
  1028. const foundCards = html.querySelectorAll(`[data-id="${id}"]`);
  1029.  
  1030. return foundCards.length;
  1031. } catch (err) {
  1032. console.error("Ошибка при запросе:", err);
  1033. return "❌";
  1034. }
  1035. }
  1036.  
  1037. async function getCache(key) {
  1038. const cacheData = JSON.parse(localStorage.getItem(key));
  1039. if (!cacheData) return null; // Если данных нет
  1040. if (Date.now() > cacheData.expires) {
  1041. localStorage.removeItem(key); // Если срок истёк, удаляем
  1042. return null;
  1043. }
  1044. return cacheData.data;
  1045. }
  1046.  
  1047. async function cacheCard(key, data) {
  1048. await setCache(key, data, 86400); // Записываем данные на 24 часа (86400 секунд)
  1049. }
  1050.  
  1051. async function getCard(key) {
  1052. return await getCache(key); // Записываем данные на 24 часа (86400 секунд)
  1053. }
  1054.  
  1055. function addClearButton() {
  1056. const filterControls = document.querySelector('.card-filter-form__controls');
  1057. if (!filterControls) {
  1058. return;
  1059. }
  1060. const inputField = filterControls.querySelector('.card-filter-form__search');
  1061. if (!inputField) {
  1062. return;
  1063. }
  1064. const searchButton = filterControls.querySelector('.tabs__search-btn');
  1065. if (!searchButton) {
  1066. return;
  1067. }
  1068. inputField.addEventListener('keydown', function (event) {
  1069. if (event.key === 'Enter') {
  1070. event.preventDefault();
  1071. searchButton.click();
  1072. }
  1073. });
  1074. const clearButton = document.createElement('button');
  1075. clearButton.innerHTML = '<i class="fas fa-times"></i>'; // Иконка Font Awesome
  1076. clearButton.classList.add('clear-search-btn'); // Добавляем класс для стилизации
  1077. clearButton.style.margin = '5px';
  1078. clearButton.style.position = 'absolute';
  1079. clearButton.style.padding = '10px';
  1080. clearButton.style.background = 'red';
  1081. clearButton.style.color = 'white';
  1082. clearButton.style.border = 'none';
  1083. clearButton.style.cursor = 'pointer';
  1084. clearButton.style.fontSize = '14px';
  1085. clearButton.style.borderRadius = '5px';
  1086. clearButton.addEventListener('click', function () {
  1087. inputField.value = '';
  1088. searchButton.click();
  1089. });
  1090. inputField.style.marginLeft = '30px';
  1091. inputField.parentNode.insertBefore(clearButton, inputField);
  1092. }
  1093.  
  1094. function addPromocodeBtn() {
  1095. const button = document.createElement('button');
  1096. button.innerText = 'Промокоды';
  1097. button.style.position = 'fixed';
  1098. button.style.bottom = '80px';
  1099. button.style.right = '0px';
  1100. button.style.zIndex = '1000';
  1101. button.style.background = 'rgb(0, 123, 255)';
  1102. button.style.fontSize = '8px';
  1103. button.style.cursor = 'pointer';
  1104. button.style.transform = 'rotateY(47deg);';
  1105. button.addEventListener('click', () => {
  1106. window.location.href = '/promo_codes';
  1107. });
  1108. document.body.appendChild(button);
  1109. }
  1110.  
  1111. (function() {
  1112. 'use strict';
  1113.  
  1114. setInterval(autoRepeatCheck, 2000);
  1115. setInterval(startPing, 31000);
  1116. setInterval(checkNewCard, 10000);
  1117.  
  1118. addUpdateButton();
  1119. addClearButton();
  1120. addPromocodeBtn();
  1121.  
  1122. document.getElementById('tg-banner')?.remove();
  1123. localStorage.setItem('notify18', 'closed');
  1124. localStorage.setItem('hideTelegramAs', 'true');
  1125.  
  1126. // авто открытие карт под аниме
  1127. // $('div .pmovie__related a.glav-s:first')?.click()?.remove();
  1128.  
  1129. // немного увеличиваем карты на списках чтоб читались надписи
  1130. document.querySelectorAll('.anime-cards__item-wrapper').forEach(el => {
  1131. el.style.setProperty('min-width', '20.28%');
  1132. });
  1133.  
  1134. if (isCardsPackPage()) {
  1135. window.doAnimLoot = () => {};
  1136. }
  1137.  
  1138. })();
  1139.  
  1140. document.addEventListener('click', function (e) {
  1141. const target = e.target;
  1142.  
  1143. if (target.classList.contains('anime-cards__name')) {
  1144. const name = target.textContent.trim();
  1145. const searchUrl = document.querySelector('.lgn__btn-profile').getAttribute('href') + `cards/?search=${encodeURIComponent(name)}`;
  1146. window.open(searchUrl, '_blank');
  1147. }
  1148. });
  1149.  
  1150. const styleGlobal = document.createElement('style');
  1151. style.textContent = `
  1152. .anime-cards__name {
  1153. cursor: pointer;
  1154. text-decoration: underline;
  1155. }
  1156. `;
  1157. document.head.appendChild(styleGlobal);