animestars Auto Helper

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

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