animestars Auto Helper

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

目前为 2025-04-14 提交的版本。查看 最新版本

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