Show NPC Time Til Next Level at Loader

Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data. Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false.

目前为 2024-12-05 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Show NPC Time Til Next Level at Loader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data. Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false.
  6. // @author Hesper [2924630]
  7. // @include https://www.torn.com/loader.php?sid=attack&user2ID=*
  8. // @grant GM.xmlHttpRequest
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Displays time remaining until the next level in the tab title.
  16. // Set to false to disable
  17. const IF_UPDATE_TAB_TITLE = true;
  18.  
  19. // Can optionally insert a public API key to help with the seconds offset in the time displayed due to the difference in the time between the server and your device.
  20. const PUBLIC_API_KEY = '';
  21.  
  22. const validIDs = [10, 20, 21, 4, 19, 15, 17];
  23.  
  24. // Function to fetch JSON data
  25. function fetchData(url) {
  26. return new Promise((resolve, reject) => {
  27. GM.xmlHttpRequest({
  28. method: 'GET',
  29. url: url,
  30. onload: function(response) {
  31. if (response.status >= 200 && response.status < 300) {
  32. try {
  33. const data = JSON.parse(response.responseText);
  34. resolve(data);
  35. } catch (e) {
  36. reject(e);
  37. }
  38. } else {
  39. reject(new Error(`HTTP error! status: ${response.status}`));
  40. }
  41. },
  42. onerror: function(err) {
  43. reject(err);
  44. }
  45. });
  46. });
  47. }
  48.  
  49. function calculateTimeRemaining(currentTime, hospOutTime) {
  50. const levelTimes = [0, 30, 90, 210, 450]; // Minutes after hosp_out for each level
  51. let nextLevel = 1;
  52. let timeRemaining = 0;
  53.  
  54. if (currentTime < hospOutTime) {
  55. timeRemaining = hospOutTime - currentTime;
  56. } else {
  57. for (let level = 2; level <= 5; level++) {
  58. const targetTime = hospOutTime + levelTimes[level - 1] * 60; // Convert minutes to seconds
  59. timeRemaining = targetTime - currentTime;
  60. // If the time remaining is positive, the current level is the previous level
  61. if (timeRemaining > 0) {
  62. nextLevel = level;
  63. break;
  64. }
  65. }
  66. }
  67.  
  68. return { nextLevel, timeRemaining: timeRemaining > 0 ? timeRemaining : 0 };
  69. }
  70.  
  71. function formatTime(seconds) {
  72. const minutes = Math.floor(seconds / 60);
  73. const hours = Math.floor(minutes / 60);
  74. const remainingMinutes = minutes % 60;
  75. const remainingSeconds = seconds % 60;
  76. if (hours === 0) {
  77. if (remainingMinutes === 0) {
  78. return `${remainingSeconds}s`;
  79. }
  80. return `${remainingMinutes}m ${remainingSeconds}s`;
  81. }
  82. return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
  83. }
  84.  
  85. function updateDiv(npc, currentTime) {
  86.  
  87. const hospOutTime = npc.hosp_out;
  88. const { nextLevel, timeRemaining } = calculateTimeRemaining(currentTime, hospOutTime);
  89. let level = nextLevel - 1;
  90.  
  91. const targetDiv = document.querySelector('.titleContainer___QrlWP');
  92. if (targetDiv) {
  93. let newDiv = document.querySelector('#npc-level-info');
  94. if (!newDiv) {
  95. newDiv = document.createElement('div');
  96. newDiv.id = 'npc-level-info';
  97. newDiv.style.marginTop = '15px';
  98. newDiv.style.marginRight = '10px';
  99. newDiv.style.textAlign = 'right';
  100. newDiv.style.fontSize = '1em';
  101. newDiv.style.lineHeight = '1.2em';
  102.  
  103. targetDiv.parentNode.insertBefore(newDiv, targetDiv.nextSibling);
  104. }
  105. if (level < 5) {
  106. newDiv.innerHTML = `Currently <b>Level ${level}</b><br />Time til Level ${level + 1}: <b>${formatTime(timeRemaining)}</b>`;
  107. if (IF_UPDATE_TAB_TITLE) document.title = `${formatTime(timeRemaining)} til Level ${level + 1} - ${npc.name}`;
  108. } else {
  109. newDiv.innerHTML = `Currently <b>Level ${level}</b>`;
  110. if (IF_UPDATE_TAB_TITLE) document.title = `Level ${level} - ${npc.name}`;
  111. }
  112. }
  113. }
  114.  
  115. // async function startUpdating(user2ID) {
  116. // // Check if the user2ID is valid
  117. // const npcData = await fetchData();
  118. // const npc = npcData.npcs[user2ID];
  119. // if (!npc) return;
  120.  
  121. // // Start updating the timer every second
  122. // setInterval(() => {
  123. // const currentTime = Math.floor(Date.now() / 1000);
  124. // updateDiv(npc, currentTime);
  125. // }, 1000);
  126. // }
  127.  
  128. async function startUpdating() {
  129. try {
  130. let timeOffset = 0;
  131. if (!PUBLIC_API_KEY !== '') {
  132. // Fetch the timestamp from Torn API
  133. const tornData = await fetchData(`https://api.torn.com/torn/?selections=timestamp&key=${PUBLIC_API_KEY}`);
  134. const tornTimestamp = tornData.timestamp;
  135. // console.log(`[NPCTimerAtLoader]: Torn timestamp: ${tornTimestamp}`);
  136. const localTimestamp = Math.floor(Date.now() / 1000);
  137. // console.log(`[NPCTimerAtLoader]: Local timestamp: ${localTimestamp}`);
  138. timeOffset = localTimestamp - tornTimestamp;
  139. console.log(`[NPCTimerAtLoader]: Time offset: ${timeOffset}`);
  140. }
  141. // Fetch the NPC data
  142. const npcData = await fetchData('https://api.lzpt.io/loot');
  143. setInterval(() => {
  144. const currentTime = Math.floor(Date.now() / 1000) - timeOffset;
  145. updateDiv(npcData, currentTime);
  146. }, 1000);
  147. } catch (error) {
  148. console.error('Failed to fetch NPC data:', error);
  149. }
  150. }
  151.  
  152. window.addEventListener('load', () => {
  153. // check if the user2ID is valid
  154. const user2ID = parseInt(new URLSearchParams(window.location.search).get('user2ID'), 10);
  155. if (validIDs.includes(user2ID)) {
  156. startUpdating(user2ID);
  157. }
  158. });
  159. })();