[HWM] GuildTimersOnMain

Таймеры для гильдии охотников, наёмников, воров и лидеров

  1. // ==UserScript==
  2. // @name [HWM] GuildTimersOnMain
  3. // @namespace [HWM] GuildTimersOnMain
  4. // @version 0.3.4
  5. // @description Таймеры для гильдии охотников, наёмников, воров и лидеров
  6. // @author Komdosh
  7. // @include http*://*.heroeswm.ru/home.php*
  8. // @include http*://*.heroeswm.ru/map.php*
  9. // @include http*://*.heroeswm.ru/mercenary_guild.php*
  10. // @include http*://*.heroeswm.ru/leader_guild.php*
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17. const GUILDS_INFO_MERCENARY = 'GUILDS_INFO_MERCENARY';
  18. const GUILDS_INFO_HUNTER = 'GUILDS_INFO_HUNTER';
  19. const GUILDS_INFO_LEADER = 'GUILDS_INFO_LEADER';
  20. const GUILDS_INFO_THIEF = 'GUILDS_INFO_THIEF';
  21.  
  22. if (/map.php/.test(location.href)) {
  23. const hunterInfo = getWithExpiry(GUILDS_INFO_HUNTER);
  24. if (hunterInfo != null && hunterInfo === '-1') {
  25. localStorage.removeItem(GUILDS_INFO_HUNTER);
  26. }
  27. const thiefInfo = getWithExpiry(GUILDS_INFO_THIEF);
  28. if (thiefInfo != null && thiefInfo === '-1') {
  29. localStorage.removeItem(GUILDS_INFO_THIEF);
  30. }
  31. return;
  32. } else if (/leader_guild.php/.test(location.href)) {
  33. const leaderInfo = getWithExpiry(GUILDS_INFO_LEADER);
  34. if (leaderInfo != null && leaderInfo === '-1') {
  35. localStorage.removeItem(GUILDS_INFO_LEADER);
  36. }
  37. return;
  38. }
  39.  
  40. localStorage.removeItem(GUILDS_INFO_MERCENARY);
  41.  
  42. const isThiefsAvailable = parseInt(Array.from(document.querySelectorAll('.home_guild_text'))
  43. .find(el => el.innerText === 'Воров').parentElement.querySelector('.home_text_exp').innerText) > 0;
  44.  
  45. const userInfo = getUserInfo();
  46.  
  47. const guildsInfoDiv = document.createElement('div');
  48. guildsInfoDiv.className += "home_container_block";
  49. guildsInfoDiv.style = "align-items: left;";
  50.  
  51. const guildsInfoHeader = document.createElement('div');
  52. guildsInfoHeader.className += "global_container_block_header global_a_hover";
  53. guildsInfoHeader.innerHTML = 'Таймеры';
  54. guildsInfoDiv.append(guildsInfoHeader);
  55.  
  56. const guildsInfoContentDiv = document.createElement('div');
  57. guildsInfoContentDiv.className += "home_inside_margins global_a_hover";
  58. guildsInfoDiv.append(guildsInfoContentDiv);
  59.  
  60. const workerGuild = document.querySelector(".home_work_block");
  61.  
  62. workerGuild.before(guildsInfoDiv);
  63.  
  64. const loading = document.createElement('span');
  65. loading.innerText = 'Данные обновляются...';
  66. guildsInfoContentDiv.append(loading);
  67.  
  68. requestHunterInfo();
  69. requestMercenaryInfo();
  70. if (isThiefsAvailable) {
  71. requestThiefInfo(userInfo.id);
  72. }
  73. requestLeadersInfo();
  74.  
  75. //***************************************************************************
  76. function requestLeadersInfo() {
  77. request('leader', 'Новое задание через ', "/leader_guild.php",
  78. GUILDS_INFO_LEADER,
  79. 'ГЛ',
  80. '<a href="leader_guild.php" style="text-decoration:underline">Все цели обнаружены</a>',
  81. 'Требуется лидер',
  82. (respDoc, timerDiv) => {
  83. const nextTime = respDoc.querySelector('#next_lg');
  84. if(nextTime == null){
  85. return 0;
  86. }
  87. const script = nextTime.parentElement.getElementsByTagName('script')[0];
  88.  
  89. const scriptDeltaText = script.text.match(/Delta2 = (\d+)/);
  90.  
  91. if (scriptDeltaText == null) {
  92. return 0;
  93. } else {
  94. return parseInt(scriptDeltaText[1]);
  95. }
  96. });
  97. }
  98.  
  99. //***************************************************************************
  100. function requestHunterInfo() {
  101. request('hunter', 'Охота будет доступна через ', "/map.php",
  102. GUILDS_INFO_HUNTER,
  103. 'ГО',
  104. '<a href="map.php" style="text-decoration:underline">Новая охота</a>',
  105. 'Охотники зовут на помощь',
  106. (respDoc, timerDiv) => {
  107. const script = document.querySelector('#map_right_block_inside').getElementsByTagName('script')[0];
  108.  
  109. const scriptDeltaText = script.text.match(/MapHunterDelta = (\d+)/);
  110.  
  111. if (scriptDeltaText == null) {
  112. return 0;
  113. } else {
  114. return parseInt(scriptDeltaText[1]);
  115. }
  116. });
  117. }
  118.  
  119. //***************************************************************************
  120. function requestMercenaryInfo() {
  121. request('mercenary', 'Новое задание через ', "/mercenary_guild.php", GUILDS_INFO_MERCENARY,
  122. 'ГН',
  123. '<a href="mercenary_guild.php" style="text-decoration:underline">Новое задание</a>',
  124. 'Наёмникам требуется герой!',
  125. (respDoc, timerDiv) => {
  126. const taskMessageDiv = respDoc.querySelector('table:not([align="center"])').querySelector('table:not([align="center"], [class="wbwhite"])');
  127.  
  128. if (taskMessageDiv == null) {
  129. return 0;
  130. } else {
  131. const taskMessage = taskMessageDiv.querySelector('td').innerText;
  132. return (parseInt(taskMessage.split('\u0447\u0435\u0440\u0435\u0437 ')[1].split(' ')[0])+1) * 60 ;
  133. }
  134. });
  135. }
  136.  
  137. //***************************************************************************
  138. function requestThiefInfo(userId) {
  139. request('thief', 'Новое задание через ', `/pl_warlog.php?id=${userId}`, GUILDS_INFO_THIEF,
  140. 'ГВ',
  141. '<a href="map.php" style="text-decoration:underline">Новая засада</a>',
  142. 'Воры замышляют новое нападение!',
  143. (respDoc, timerDiv) => {
  144. const battles = respDoc.querySelector('div[class="global_a_hover"]').innerHTML.split('br');
  145.  
  146. let nextTime = 1000 * 60 * 60;
  147. if (isPremium()) {
  148. nextTime *= 0.7;
  149. }
  150. for (const battle of battles) {
  151. if (/<b>\u041A\u0430\u0440\u0430\u0432\u0430\u043D/.test(battle)) { // Караван выигран
  152. let battleDate = battle.split('>')[1].split('<')[0];
  153. const battleDateSplit = battleDate.split('-');
  154. battleDate = battleDateSplit[1] + '-' + battleDateSplit[0] + '-' + battleDateSplit[2];
  155. const thiefDateTime = new Date(battleDate);
  156.  
  157. return Math.floor(((thiefDateTime.getTime() + nextTime) - Date.now()) / 1000);
  158. } else {
  159. return 0;
  160. }
  161. }
  162.  
  163. return 0;
  164. });
  165. }
  166.  
  167. function request(domId, taskHtml, link, localStorageName, notifyMeLinkName, newTaskInstantlyDom, notifyText, processor) {
  168. const timerDiv = document.createElement('div');
  169. timerDiv.id = domId;
  170. guildsInfoContentDiv.append(timerDiv);
  171.  
  172. const expiration = getWithExpiry(localStorageName);
  173.  
  174. const notifyMeLink = document.createElement('a');
  175. notifyMeLink.style = 'cursor: pointer';
  176.  
  177. let isNotify = localStorage.getItem(`${localStorageName}_SETTINGS`) === 'true';
  178.  
  179. notifyMeLink.innerHTML = isNotify ? `<b>${notifyMeLinkName}</b>: ` : `${notifyMeLinkName}: `;
  180.  
  181. notifyMeLink.onclick = () => {
  182. isNotify = !isNotify;
  183.  
  184. toggleNotification(isNotify,
  185. notifyMeLink,
  186. notifyMeLinkName);
  187.  
  188. localStorage.setItem(`${localStorageName}_SETTINGS`, isNotify);
  189. };
  190.  
  191. const wrapper = document.createElement('span');
  192. wrapper.append(notifyMeLink);
  193.  
  194. if (expiration != null) {
  195. if (loading != null) {
  196. loading.remove();
  197. }
  198.  
  199. if (expiration === '-1') {
  200. timerDiv.append(createElementFromTextAndAppend(wrapper, newTaskInstantlyDom));
  201. return;
  202. }
  203.  
  204. toggleNotification(isNotify,
  205. notifyMeLink,
  206. notifyMeLinkName);
  207.  
  208. const delay = Math.floor((expiration - Date.now()) / 1000);
  209. initTimer(delay, timerDiv, createElementFromTextAndAppend(wrapper, taskHtml), () => {
  210. timerFinished(isNotify, notifyText, timerDiv, newTaskInstantlyDom);
  211. });
  212. return;
  213. }
  214. const xhr = new XMLHttpRequest();
  215. xhr.open('GET', encodeURI(link));
  216. xhr.overrideMimeType('text/xml; charset=windows-1251');
  217. xhr.onload = function () {
  218. if (xhr.status === 200) {
  219. const div = document.createElement('div');
  220. div.id = 'kom-' + domId;
  221. div.style.display = 'none';
  222. div.innerHTML = xhr.responseText;
  223. document.getElementsByTagName('body')[0].appendChild(div);
  224. const respDoc = document.getElementById('kom-' + domId);
  225. if (loading != null) {
  226. loading.remove();
  227. }
  228. const delta = processor(respDoc, timerDiv);
  229.  
  230. if (delta > 0) {
  231. const expiration = Date.now() + delta * 1000;
  232.  
  233. setWithExpiry(localStorageName, expiration, expiration);
  234.  
  235. toggleNotification(isNotify,
  236. notifyMeLink,
  237. notifyMeLinkName);
  238.  
  239. initTimer(delta, timerDiv, createElementFromTextAndAppend(wrapper, taskHtml), () => {
  240. timerFinished(isNotify, notifyText, timerDiv, newTaskInstantlyDom);
  241. });
  242. } else {
  243. setWithExpiry(localStorageName, '-1', Date.now() + 60 * 60 * 1000);
  244. timerDiv.append(createElementFromTextAndAppend(wrapper, newTaskInstantlyDom));
  245. }
  246.  
  247. respDoc.remove();
  248. } else {
  249. console.log('Request failed. Returned status of ' + xhr.status);
  250. }
  251. };
  252. xhr.send();
  253. }
  254.  
  255. //***************************************************************************
  256. function setWithExpiry(key, value, expirationTime) {
  257. const item = {
  258. value: value,
  259. expiry: expirationTime,
  260. }
  261. localStorage.setItem(key, JSON.stringify(item))
  262. }
  263.  
  264. //***************************************************************************
  265. function getWithExpiry(key) {
  266. const itemStr = localStorage.getItem(key)
  267. // if the item doesn't exist, return null
  268. if (!itemStr) {
  269. return null;
  270. }
  271. const item = JSON.parse(itemStr)
  272. const now = new Date()
  273. // compare the expiry time of the item with the current time
  274. if (now.getTime() > item.expiry) {
  275. // If the item is expired, delete the item from storage
  276. // and return null
  277. localStorage.removeItem(key)
  278. return null
  279. }
  280. return item.value
  281. }
  282.  
  283. //***************************************************************************
  284. function initTimer(Delta, timerDiv, html, onTimeEnd) {
  285. const timeSpan = document.createElement('span');
  286. html.append(timeSpan);
  287.  
  288. function print_time(t) {
  289. if (t < 0) t = 0;
  290.  
  291. const min = Math.floor(t / 60);
  292. const sec = t % 60;
  293. let s = '';
  294.  
  295. if (min) s = min + ' ' + 'мин. ';
  296. s = s + sec + ' ' + 'с. ';
  297.  
  298. timeSpan.innerText = ' ' + s;
  299. if (timerDiv.firstChild != null) {
  300. timerDiv.replaceChild(html, timerDiv.firstChild);
  301. } else {
  302. timerDiv.append(html);
  303. }
  304. }
  305.  
  306. const Refresh = () => {
  307. if (Timer >= 0) clearTimeout(Timer);
  308. Delta = Delta - 1;
  309. print_time(Delta);
  310. if (Delta >= 0) {
  311. Timer = setTimeout(Refresh, 1000);
  312. } else {
  313. onTimeEnd();
  314. }
  315. }
  316.  
  317. let Timer = setTimeout(Refresh, 1000);
  318. print_time(Delta);
  319. }
  320.  
  321. //*******************
  322. function getUserInfo() {
  323. const infoLink = document.querySelector('center>a[href^=pl_info]');
  324. const infoLinkValues = infoLink.href.split("id=");
  325.  
  326. return {id: infoLinkValues[infoLinkValues.length - 1], name: infoLink.innerText};
  327. }
  328.  
  329. //*******************
  330. function isPremium() {
  331. return document.querySelector('a[href="shop.php?cat=potions#vip"]') != null;
  332. }
  333.  
  334. //***************************************************************************
  335. function toggleNotification(isNotify, notifyMeLink, notifyMeLinkName) {
  336. if (isNotify) {
  337. notifyMeLink.innerHTML = `<b>${notifyMeLinkName}</b>: `;
  338. notifyMeLink.title = 'Уведомление: включено';
  339. } else {
  340. notifyMeLink.innerHTML = `${notifyMeLinkName}: `;
  341. notifyMeLink.title = 'Уведомление: выключено';
  342. }
  343. }
  344.  
  345. //***************************************************************************
  346. function createElementFromTextAndAppend(wrapper, html) {
  347. const span = document.createElement('span');
  348. span.innerHTML = html.trim();
  349. wrapper.append(span);
  350. return wrapper;
  351. }
  352.  
  353. //***************************************************************************
  354. function timerFinished(isNotify, notifyText, timerDiv, finishedHtml) {
  355. if (isNotify) {
  356. notifyAfter(notifyText);
  357. }
  358.  
  359.  
  360. const span = document.createElement('span');
  361. span.innerHTML = finishedHtml.trim();
  362.  
  363. timerDiv = timerDiv.lastChild;
  364. let child = timerDiv.lastChild;
  365. while (timerDiv.children.length > 1) {
  366. timerDiv.removeChild(child);
  367. child = timerDiv.lastChild;
  368. }
  369.  
  370. timerDiv.append(span);
  371. }
  372.  
  373. //***************************************************************************
  374. function notifyAfter(text) {
  375. var notify = alert;
  376.  
  377. const cancellation = setTimeout(() => {
  378. notify(text);
  379. }, 1000);
  380.  
  381. Notification.requestPermission().then((permission) => {
  382. if (permission === 'granted') {
  383. notify = (t) => new Notification(t);
  384. }
  385. });
  386.  
  387. return cancellation;
  388. }
  389. })();