HeroesWM Clan Masters Info

Собирает информацию о мастерах и кузницах клана и выводит в виде таблиц во всплывающем окне

  1. // ==UserScript==
  2. // @name HeroesWM Clan Masters Info
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.37
  5. // @description Собирает информацию о мастерах и кузницах клана и выводит в виде таблиц во всплывающем окне
  6. // @author o3-mini-ChatGPT
  7. // @match https://www.heroeswm.ru/clan_info.php?id=*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. /**
  15. * @typedef {Object} PlayerResult
  16. * @property {string} id - Идентификатор игрока
  17. * @property {string} name - Имя игрока
  18. * @property {string} link - HTML-ссылка на страницу игрока
  19. * @property {number} guildKuznetsov - Значение "Гильдия Кузнецов"
  20. * @property {number} guildOrujejnikov - Значение "Гильдия Оружейников"
  21. * @property {number} masterOrujie - Значение "Мастер оружия"
  22. * @property {number} masterBronya - Значение "Мастер доспехов"
  23. * @property {number} jeweler - Значение "Ювелир"
  24. */
  25. // Создаём кнопку в левом верхнем углу для открытия анализа
  26. const button = document.createElement('button');
  27. button.innerText = 'Анализ мастеров';
  28. button.style.position = 'fixed';
  29. button.style.top = '10px';
  30. button.style.left = '10px';
  31. button.style.zIndex = '1000';
  32. button.style.padding = '5px';
  33. button.style.cursor = 'pointer';
  34. document.body.appendChild(button);
  35. // Функция для создания модального окна с содержимым
  36. function createModal(content) {
  37. const modal = document.createElement('div');
  38. modal.style.position = 'fixed';
  39. modal.style.top = '50%';
  40. modal.style.left = '50%';
  41. modal.style.transform = 'translate(-50%, -50%)';
  42. modal.style.backgroundColor = '#fff';
  43. modal.style.padding = '20px';
  44. modal.style.border = '1px solid #000';
  45. modal.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
  46. modal.style.zIndex = '2000';
  47. modal.style.maxHeight = '80vh';
  48. modal.style.overflowY = 'auto';
  49. modal.innerHTML = content;
  50. const closeButton = document.createElement('button');
  51. closeButton.innerText = 'Закрыть';
  52. closeButton.style.marginTop = '10px';
  53. closeButton.onclick = () => {
  54. document.body.removeChild(modal);
  55. };
  56. modal.appendChild(closeButton);
  57. return modal;
  58. }
  59. // Обработчик нажатия на кнопку анализа
  60. button.onclick = async function() {
  61. // Создаем модальное окно с индикатором прогресса
  62. const modalDiv = document.createElement('div');
  63. modalDiv.style.position = 'fixed';
  64. modalDiv.style.top = '50%';
  65. modalDiv.style.left = '50%';
  66. modalDiv.style.transform = 'translate(-50%, -50%)';
  67. modalDiv.style.backgroundColor = '#fff';
  68. modalDiv.style.padding = '20px';
  69. modalDiv.style.border = '1px solid #000';
  70. modalDiv.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
  71. modalDiv.style.zIndex = '2000';
  72. modalDiv.style.minWidth = '600px';
  73. modalDiv.innerHTML = '<h2>Сбор информации...</h2><div id="progress">0%</div>';
  74. document.body.appendChild(modalDiv);
  75. // Поиск игроков только в таблице с id "table-content"
  76. const tableContent = document.getElementById('table-content');
  77. if (!tableContent) {
  78. modalDiv.innerHTML = '<h2>Ошибка</h2><p>Не найден блок с игроками (table-content).</p>';
  79. return;
  80. }
  81. const playerLinkElements = tableContent.querySelectorAll('a.pi[href^="pl_info.php?id="]');
  82. if (playerLinkElements.length === 0) {
  83. modalDiv.innerHTML = '<h2>Ошибка</h2><p>Игроки не найдены в блоке table-content.</p>';
  84. return;
  85. }
  86. /** @type {PlayerResult[]} */
  87. const results = [];
  88. const playerElements = Array.from(playerLinkElements);
  89. console.log(`Найдено ${playerElements.length} игроков`);
  90. // Обрабатываем каждого игрока по очереди
  91. for (let i = 0; i < playerElements.length; i++) {
  92. const linkElem = playerElements[i];
  93. const href = linkElem.getAttribute('href');
  94. const id = href.split('=')[1];
  95. const name = linkElem.textContent.trim();
  96. const playerLink = linkElem.outerHTML;
  97. // Обновляем индикатор прогресса
  98. const progressElem = document.getElementById('progress');
  99. if (progressElem) {
  100. progressElem.textContent = `${Math.round(((i + 1) / playerElements.length) * 100)}% (${i + 1}/${playerElements.length})`;
  101. }
  102. try {
  103. // Загружаем страницу игрока через скрытый iframe для эмуляции поведения реального пользователя
  104. const iframe = document.createElement('iframe');
  105. iframe.style.display = 'none';
  106. document.body.appendChild(iframe);
  107. iframe.src = `https://www.heroeswm.ru/pl_info.php?id=${id}`;
  108. await new Promise(resolve => iframe.onload = resolve);
  109. const doc = iframe.contentDocument || iframe.contentWindow.document;
  110. document.body.removeChild(iframe);
  111. // Используем innerText для видимого текста и нормализуем его, заменяя неразрывные пробелы
  112. const normalizedBody = doc.body.innerText.replace(/\u00A0/g, ' ');
  113. // Извлекаем значение Гильдия Кузнецов из нормализованного текста страницы
  114. const guildKuznetsovMatch = normalizedBody.match(/Гильдия Кузнецов\s*:\s*(\d+)/i);
  115. const guildKuznetsov = guildKuznetsovMatch ? parseInt(guildKuznetsovMatch[1], 10) : 0;
  116. // Для Гильдии оружейников сначала пробуем получить данные из элемента с id "home_2"
  117. let guildOrujejnikov = 0;
  118. const home2 = doc.getElementById("home_2");
  119. if (home2) {
  120. const normalizedHome2 = home2.innerText.replace(/\u00A0/g, ' ');
  121. const match = normalizedHome2.match(/Гильдия Оружейников\s*:\s*(\d+)/i);
  122. guildOrujejnikov = match ? parseInt(match[1], 10) : 0;
  123. }
  124. // Если не найдено через home2, пробуем по всему тексту
  125. if (guildOrujejnikov === 0) {
  126. const extraMatch = normalizedBody.match(/Гильдия Оружейников\s*:\s*(\d+)/i);
  127. if (extraMatch) {
  128. guildOrujejnikov = parseInt(extraMatch[1], 10);
  129. }
  130. }
  131. // Извлекаем значения отделения гильдии оружейников (навыки) из нормализованного текста
  132. const masterOrujieMatch = normalizedBody.match(/Мастер оружия\s*:\s*(\d+)/i);
  133. const masterOrujie = masterOrujieMatch ? parseInt(masterOrujieMatch[1], 10) : 0;
  134. const masterBronyaMatch = normalizedBody.match(/Мастер доспехов\s*:\s*(\d+)/i);
  135. const masterBronya = masterBronyaMatch ? parseInt(masterBronyaMatch[1], 10) : 0;
  136. const jewelerMatch = normalizedBody.match(/Ювелир\s*:\s*(\d+)/i);
  137. const jeweler = jewelerMatch ? parseInt(jewelerMatch[1], 10) : 0;
  138. console.log(`Игрок ${name}: Кузница=${guildKuznetsov}, Оружейники=${guildOrujejnikov}, Мастер оружия=${masterOrujie}, Мастер доспехов=${masterBronya}, Ювелир=${jeweler}`);
  139. // Если у игрока не прокачан ни один стата в оружейных, пропускаем его
  140. if (masterOrujie === 0 && masterBronya === 0 && jeweler === 0 && guildKuznetsov === 0) {
  141. console.log(`Игрок ${name} пропущен, т.к. отсутствует прокачка статов в оружейных и кузницы.`);
  142. continue;
  143. }
  144. results.push({
  145. id,
  146. name,
  147. link: playerLink,
  148. guildKuznetsov,
  149. guildOrujejnikov,
  150. masterOrujie,
  151. masterBronya,
  152. jeweler
  153. });
  154. // Пауза между запросами
  155. await new Promise(resolve => setTimeout(resolve, 300));
  156. } catch (error) {
  157. console.error(`Ошибка при обработке игрока ${name}:`, error);
  158. }
  159. }
  160. // Если результатов нет, выводим сообщение
  161. if (results.length === 0) {
  162. document.body.removeChild(modalDiv);
  163. const noDataModal = createModal('<h2>Результаты</h2><p>Нет игроков с прокачанными статами в оружейных.</p>');
  164. document.body.appendChild(noDataModal);
  165. return;
  166. }
  167. // Формирование текстовой таблицы мастеров для удобного копирования
  168. let masterTableText = '|| Оружие || Броня || Ювелир || Персонаж\n\n';
  169. results.forEach(player => {
  170. if (!player.guildOrujejnikov) return; // пропускаем игроков с 0 гильдий
  171. const weaponValue = (player.guildOrujejnikov && player.masterOrujie) ? (Math.min(player.guildOrujejnikov + 1, 5) + '*' + String(Math.min(player.masterOrujie + 1, 12)).padStart(2, '0') + '%') : '- - - - -';
  172. const armorValue = (player.guildOrujejnikov && player.masterBronya) ? (Math.min(player.guildOrujejnikov + 1, 5) + '*' + String(Math.min(player.masterBronya + 1, 12)).padStart(2, '0') + '%') : '- - - - -';
  173. const jewelerValue = (player.guildOrujejnikov && player.jeweler) ? (Math.min(player.guildOrujejnikov + 1, 5) + '*' + String(Math.min(player.jeweler + 1, 12)).padStart(2, '0') + '%') : '- - - - -';
  174. masterTableText += `|| ${weaponValue} || ${armorValue} || ${jewelerValue} || ${player.name}\n`;
  175. });
  176. let smithTableText = '|| Кузница || Персонаж\n\n';
  177. results.forEach(player => {
  178. if (!(player.guildKuznetsov > 0)) return;
  179. const smithValue = (Math.min((player.guildKuznetsov + 1) * 10, 90)) + '%';
  180. smithTableText += `|| ${smithValue} || ${player.name}\n`;
  181. });
  182.  
  183. const finalHTML = '<h2>Информация о мастерах клана</h2><textarea style="width:100%;height:200px;" readonly>' + masterTableText + '</textarea>' +
  184. '<h2>Информация о кузницах клана</h2><textarea style="width:100%;height:200px;" readonly>' + smithTableText + '</textarea>';
  185. document.body.removeChild(modalDiv);
  186. const resultModal = createModal(finalHTML);
  187. document.body.appendChild(resultModal);
  188. };
  189. })();