Greasy Fork 支持简体中文。

animestars Auto Helper

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

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