GazelleGames pet leveling info

Adds pet leveling info to your own profile page

  1. // ==UserScript==
  2. // @name GazelleGames pet leveling info
  3. // @namespace v3rrrr82xk1c96vvo1c6
  4. // @match https://gazellegames.net/user.php?id=*
  5. // @grant GM.getValue
  6. // @grant GM.setValue
  7. // @grant GM.deleteValue
  8. // @version 1.3.4
  9. // @description Adds pet leveling info to your own profile page
  10. // @author lunboks
  11. // @run-at document-start
  12. // @inject-into content
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (async function () {
  17. "use strict";
  18.  
  19.  
  20. const theirUserID = new URLSearchParams(location.search).get("id");
  21. const ownUserID = await GM.getValue("you").then((yourID) => {
  22. // Own ID is cached
  23. if (yourID) {
  24. return yourID;
  25. }
  26.  
  27. // Not cached, get it from page once it's loaded
  28. return new Promise((resolve) => {
  29. window.addEventListener("DOMContentLoaded", () => {
  30. yourID = new URLSearchParams(document.body.querySelector("#nav_userinfo a.username").search).get("id");
  31. GM.setValue("you", yourID);
  32. resolve(yourID);
  33. });
  34. });
  35. });
  36.  
  37.  
  38. // Only runs on your own user page
  39. if (theirUserID !== ownUserID) {
  40. return;
  41. }
  42.  
  43.  
  44. let apiKey = await GM.getValue("apiKey");
  45.  
  46. if (!apiKey) {
  47. if (!(apiKey = prompt("Please enter an API key with the 'Items' permission to use this script.")?.trim())) {
  48. return;
  49. }
  50. GM.setValue("apiKey", apiKey);
  51. }
  52.  
  53.  
  54. const endpoint = "https://gazellegames.net/api.php?request=items&type=users_equipped&include_info=true";
  55. const options = {
  56. method: "GET",
  57. mode: "same-origin",
  58. credentials: "omit",
  59. redirect: "error",
  60. referrerPolicy: "no-referrer",
  61. headers: {
  62. "X-API-Key": apiKey
  63. }
  64. };
  65.  
  66.  
  67. const equipment = await (await fetch(endpoint, options)).json();
  68.  
  69. if (equipment.status !== "success") {
  70. if (equipment.status === 401) {
  71. GM.deleteValue("apiKey");
  72. }
  73. return;
  74. }
  75.  
  76.  
  77. function toInt(value) {
  78. return (typeof value === "number") ? value : parseInt(value, 10);
  79. }
  80.  
  81.  
  82. // Hardcoded IDs of XP-gaining pets.
  83. // We display these even if they have 0 XP, which we otherwise
  84. // cannot distinguish from non-leveling pets.
  85. const levelingPetIDs = new Set(["2509","2510","2511","2512","2513","2514","2515","2521","2522","2523","2524","2525","2529","2583","2927","2928","2929","2933","3215","3216","3237","3322","3323","3324","3369","3370","3371","3373"]);
  86.  
  87. const pets = [];
  88.  
  89. for (const equip of equipment.response) {
  90. const type = equip.item.equipType;
  91.  
  92. if (type && String(type) === "18" && (levelingPetIDs.has(equip.itemid) || equip.experience > 0)) {
  93. pets.push({
  94. name: equip.item.name,
  95. xp: toInt(equip.experience),
  96. lv: toInt(equip.level),
  97. id: String(equip.itemid),
  98. slot: toInt(equip.slotid)
  99. });
  100. }
  101. }
  102.  
  103. if (!pets.length) return;
  104.  
  105.  
  106. // Sort by slot ID for consistent ordering.
  107. pets.sort((first, second) => first.slot - second.slot);
  108.  
  109.  
  110. // Build our HTML structure.
  111. // CSS classes copied from the "Personal" box
  112. const box = document.createElement("div");
  113. const innerBox = document.createElement("div");
  114. const list = document.createElement("ul");
  115. const heading = document.createElement("div");
  116.  
  117. box.className = "box_personal_history";
  118. innerBox.className = "box";
  119. heading.className = "head colhead_dark";
  120. list.className = "stats nobullet";
  121. list.style.lineHeight = "1.5";
  122.  
  123. heading.append("Pet Leveling");
  124. innerBox.append(heading, list);
  125. box.append(innerBox);
  126.  
  127.  
  128. function totalXP(lv) {
  129. return Math.ceil((lv * lv * 625) / 9);
  130. }
  131.  
  132.  
  133. function xpToTimeString(xp) {
  134. const days = Math.floor(xp / 24);
  135. const hours = xp % 24;
  136. let timeString = "";
  137.  
  138. if (days) {
  139. const s = (days === 1) ? "" : "s";
  140. timeString = `${days} day${s}`;
  141. }
  142. if (hours) {
  143. if (timeString) {
  144. timeString += " ";
  145. }
  146. const s = (hours === 1) ? "" : "s";
  147. timeString += `${hours} hour${s}`;
  148. } else if (!timeString) {
  149. timeString = "0 hours"; // ???
  150. }
  151.  
  152. return timeString;
  153. }
  154.  
  155.  
  156. const listItems = [];
  157.  
  158. pets.forEach((pet, index) => {
  159. const liItem = document.createElement("li");
  160. const liLevelInput = document.createElement("li");
  161. const liTimeOutput = document.createElement("li");
  162. const shopLink = document.createElement("a");
  163.  
  164. // Spacing between pets
  165. if (index > 0) {
  166. liItem.style.marginTop = "0.6em";
  167. }
  168.  
  169. // Indent the level/time lines
  170. liLevelInput.style.paddingLeft = "10px";
  171. liTimeOutput.style.paddingLeft = "10px";
  172.  
  173.  
  174. shopLink.style.fontWeight = "bold";
  175. shopLink.href = `/shop.php?ItemID=${pet.id}`;
  176. shopLink.referrerPolicy = "no-referrer";
  177. shopLink.title = "Shop for this pet";
  178.  
  179.  
  180. const nextLevel = pet.lv + 1;
  181.  
  182. const targetLevelInput = document.createElement("input");
  183. targetLevelInput.type = "number";
  184. targetLevelInput.required = true;
  185. targetLevelInput.inputmode = "numeric";
  186. targetLevelInput.style.width = "3em";
  187. targetLevelInput.min = nextLevel;
  188. targetLevelInput.max = Math.max(999, nextLevel);
  189. targetLevelInput.value = nextLevel;
  190.  
  191. const displayTimeDifference = (toLevel) => {
  192. const missingXP = totalXP(toLevel) - pet.xp;
  193. liTimeOutput.textContent = xpToTimeString(missingXP);
  194. };
  195.  
  196.  
  197. // Display initial info
  198. displayTimeDifference(nextLevel);
  199.  
  200. // When the user types anything, update if it's valid.
  201. targetLevelInput.addEventListener("input", function () {
  202. if (this.checkValidity()) {
  203. displayTimeDifference(parseInt(this.value, 10));
  204. }
  205. });
  206.  
  207. // When the user commits a new value, complain if it's invalid.
  208. // No idea why setTimeout is needed tbh
  209. targetLevelInput.addEventListener("change", function () {
  210. setTimeout(() => {
  211. if (!this.reportValidity()) {
  212. liTimeOutput.textContent = "";
  213. }
  214. });
  215. });
  216.  
  217.  
  218. shopLink.append(pet.name);
  219. liItem.append(shopLink);
  220. liLevelInput.append(`Level ${pet.lv} `, targetLevelInput);
  221.  
  222.  
  223. listItems.push(liItem, liLevelInput, liTimeOutput);
  224. });
  225.  
  226.  
  227. list.append(...listItems);
  228.  
  229.  
  230. // Try to insert our UI
  231. function insert() {
  232. document.getElementsByName("user_info")[0]?.after(box);
  233. return box.isConnected;
  234.  
  235. }
  236.  
  237. if (!insert()) {
  238. window.addEventListener("DOMContentLoaded", insert);
  239. }
  240. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址