GC Helper

Adds helpful timers and links to the GC sidebar.

  1. // ==UserScript==
  2. // @name GC Helper
  3. // @description Adds helpful timers and links to the GC sidebar.
  4. // @version 0.3
  5. // @author ben (mushroom), dani
  6. // @match https://grundos.cafe/*
  7. // @match https://www.grundos.cafe/*
  8. // @icon https://www.google.com/s2/favicons?domain=grundos.cafe
  9. // @namespace https://greasyfork.org/users/727556
  10. // ==/UserScript==
  11.  
  12. var storage;
  13. var fishingStorage;
  14. localStorage.getItem("trainingtrackerGC==") != null ? storage = JSON.parse(localStorage.getItem("trainingtrackerGC==")) : storage = {display: true, students: {}};
  15. localStorage.getItem("fishingtrackerGC==") != null ? fishingStorage = JSON.parse(localStorage.getItem("fishingtrackerGC==")) : fishingStorage = {display: true, pets: {}};
  16.  
  17. var currentPage = window.location.href;
  18. var pageHTML = document.body.innerHTML;
  19. var documentText = document.body.innerText;
  20. var content = document.getElementsByClassName("content")[0];
  21. var currentDate = new Date();
  22. var usingDefaultTheme = document.body.innerHTML.indexOf('04/top_bar_anim') < 0 ? true : false;
  23.  
  24.  
  25. function trainingList() {
  26. // Loop through students in storage
  27. var newDate = new Date();
  28. for (var student in storage.students)
  29. {
  30. // get name, parse date, update content of
  31. let studentName = student;
  32. var endDate = new Date(storage.students[student].timeToCompletion);
  33. var timeString;
  34.  
  35. if (!isNaN(Date.parse(endDate))) // check if valid date
  36. {
  37. var hoursLeft = Math.floor((endDate - newDate)/(1000*60*60));
  38. var minutesLeft = Math.floor(((endDate - newDate) % (1000*60*60)) / (1000*60));
  39. var secondsLeft = Math.floor(((endDate - newDate) % (1000*60)) / (1000));
  40. if (secondsLeft >= 0)
  41. {
  42. timeString = hoursLeft + "h " + minutesLeft + "m";
  43. }
  44. else
  45. {
  46. timeString = "Course Finished!";
  47. }
  48. }
  49. else
  50. {
  51. timeString = storage.students[student].timeToCompletion;
  52. }
  53.  
  54. document.getElementById(studentName + "_ttc").innerText = timeString;
  55. }
  56.  
  57. for (var fishingPet in fishingStorage.pets)
  58. {
  59. var endDateFish = new Date(fishingStorage.pets[fishingPet][0])
  60. var timeStringFish;
  61.  
  62. if (!isNaN(Date.parse(endDateFish))) // check if valid date
  63. {
  64. var hoursLeftFish = Math.floor((endDateFish - newDate)/(1000*60*60));
  65. var minutesLeftFish = Math.floor(((endDateFish - newDate) % (1000*60*60)) / (1000*60));
  66. var secondsLeftFish = Math.floor(((endDateFish - newDate) % (1000*60)) / (1000));
  67.  
  68.  
  69. if (secondsLeftFish >= 0)
  70. {
  71. timeStringFish = hoursLeftFish + "h " + minutesLeftFish + "m";
  72. }
  73.  
  74.  
  75. else
  76. {
  77. timeStringFish = "🐟";
  78. }
  79. }
  80.  
  81. document.getElementById(fishingPet + "_ttf").innerText = timeStringFish;
  82. }
  83. }
  84.  
  85. (function() {
  86. 'use strict';
  87.  
  88. // When on training status page, parse HTML & store in local storage
  89. if (currentPage == "https://www.grundos.cafe/island/training/?type=status"){
  90.  
  91. var petTables = content.getElementsByTagName("table")[0].getElementsByTagName("tbody")[0].getElementsByTagName("tr");
  92. var numOfPets = petTables.length / 2;
  93. var petNames = [];
  94.  
  95. // Loop through pets
  96. for (var i = 0; i < numOfPets; i++)
  97. {
  98. var courseInfo = petTables[2*i].getElementsByTagName("td")[0].getElementsByTagName("b")[0].innerText.split(" ");
  99. petNames.push(courseInfo[0]);
  100.  
  101. // if last element of courseInfo == course, remove petname from students
  102. if (courseInfo[courseInfo.length - 1] == "course")
  103. {
  104. delete storage.students[courseInfo[0]];
  105. }
  106.  
  107. // else, add student record (pet name, course, time to completion)
  108. else
  109. {
  110. var timeToCompletion = petTables[2*i + 1].getElementsByTagName("td")[1].getElementsByTagName("b")[0].innerText;
  111. if (timeToCompletion.indexOf("Codestone") > 0)
  112. {
  113. storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: timeToCompletion + " required"};
  114. }
  115. else if (timeToCompletion == "Course Finished!")
  116. {
  117. storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: timeToCompletion};
  118. }
  119. else
  120. {
  121. var re = /\d+/g;
  122. var timeParts = [...timeToCompletion.matchAll(re)];
  123. var endTime = new Date(currentDate.getTime() + timeParts[0]*60*60*1000 + timeParts[1]*60*1000 + timeParts[2]*1000)
  124. storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: endTime};
  125. }
  126. }
  127. }
  128.  
  129. for (var s in storage.students)
  130. {
  131. var matchExists = 0;
  132. for (var j = 0; j < petNames.length; j++)
  133. {
  134. if (petNames[j] == s)
  135. {
  136. matchExists = 1;
  137. }
  138. }
  139. if (matchExists == 0)
  140. {
  141. delete storage.students[s];
  142. }
  143. }
  144.  
  145. localStorage.setItem("trainingtrackerGC==", JSON.stringify(storage));
  146. }
  147.  
  148. // When on fishing success page, parse HTML & store in local storage
  149. if (currentPage == "https://www.grundos.cafe/water/fishing/" && document.body.innerText.lastIndexOf('might be able to') > 0)
  150. {
  151. var activePet = usingDefaultTheme ? documentText.substring(documentText.lastIndexOf(' | Pet : ') + 9, documentText.lastIndexOf(' | NP : ')) : documentText.substring(documentText.lastIndexOf('\npet : ') + 7, documentText.lastIndexOf('\nNP : '))
  152. var hoursUntilNext = documentText.substring(documentText.lastIndexOf('might be able to cast again in about ') + 37, documentText.lastIndexOf('might be able to cast again in about ') + 38)
  153. var currentLevel = activePet in fishingStorage.pets ? fishingStorage.pets[activePet][1] : "TBD"
  154. if (documentText.lastIndexOf("fishing level increases to") > 0){
  155. const levRegex = /fishing level increases to (.*?)!/g;
  156. var levResult = levRegex.exec(documentText);
  157.  
  158. currentLevel = levResult[1];
  159. }
  160.  
  161. fishingStorage.pets[activePet] = [ new Date(currentDate.getTime() + 60*60*1000*parseInt(hoursUntilNext)) , currentLevel ]
  162. localStorage.setItem("fishingtrackerGC==", JSON.stringify(fishingStorage));
  163. }
  164.  
  165. // When on any page with Neopets sidebar, add training module
  166. if (document.getElementsByName("a").length > 0)
  167. {
  168. var trainingModule = `<div class="tt" style="position:absolute; left:950px; top:240px; width: 140px;"><a href="https://www.grundos.cafe/island/training/?type=status" style='color:#FF8CA1'>Training:</a><br>`
  169. var studentCount = 0;
  170.  
  171. // Loop through students in storage
  172. for (var student in storage.students)
  173. {
  174. let studentName = student;
  175. let currentCourse = storage.students[student].currentCourse;
  176. var endDate = new Date(storage.students[student].timeToCompletion);
  177. var timeString;
  178.  
  179. if (!isNaN(Date.parse(endDate))) // check if valid date
  180. {
  181. if (Date.parse(endDate) > currentDate)
  182. {
  183. var hoursLeft = Math.floor((endDate - currentDate)/(1000*60*60));
  184. var minutesLeft = Math.floor(((endDate - currentDate) % (1000*60*60)) / (1000*60));
  185. var secondsLeft = Math.floor(((endDate - currentDate) % (1000*60)) / (1000));
  186. timeString = hoursLeft + "h " + minutesLeft + "m";
  187. }
  188. else
  189. {
  190. timeString = "Course Finished!";
  191. }
  192. }
  193. else
  194. {
  195. timeString = storage.students[student].timeToCompletion;
  196. }
  197.  
  198. var trainingModulePart = `<li>${student} (<i>${currentCourse}</i>): <span id="${student}_ttc">${timeString}</span></li>`
  199.  
  200. trainingModule = trainingModule + trainingModulePart;
  201. studentCount++;
  202. }
  203.  
  204. if (studentCount == 0)
  205. {
  206. trainingModule = trainingModule + `<br>No pets in courses - go get started!`;
  207. }
  208.  
  209. // Adding fishing
  210. var fishingDisplay = fishingStorage.display ? "inline" : "none";
  211. var fishingDisplayArrow = fishingStorage.display ? "↑" : "↓";
  212. trainingModule = trainingModule + `<br><a href="/water/fishing/" style='color:#FF8CA1'>Fishing:</a> <span style="font-size: 16px; cursor: pointer;" id="TBD">` + fishingDisplayArrow + `</span><div style="display:` + fishingDisplay + `;" id="fishingModulePets">`
  213.  
  214. var fishingPetsArray = [];
  215. for (var pet in fishingStorage.pets)
  216. {
  217. if (!Array.isArray(fishingStorage.pets[pet])){
  218. var ph = fishingStorage.pets[pet]
  219. fishingStorage.pets[pet] = [ ph, "TBD" ]
  220. }
  221. fishingPetsArray.push([ pet, fishingStorage.pets[pet][0] ]);
  222. }
  223.  
  224. fishingPetsArray.sort((a, b) => new Date(b[1]) - new Date(a[1]));
  225.  
  226. for (var p = 0; p < fishingPetsArray.length; p++)
  227. {
  228. let petName = fishingPetsArray[p][0];
  229. let petLevel = fishingStorage.pets[petName][1];
  230. let nextFishingTime = new Date(fishingStorage.pets[petName][0]);
  231. var timeStringF;
  232.  
  233. if (!isNaN(Date.parse(nextFishingTime))) // check if valid date
  234. {
  235. if (Date.parse(nextFishingTime) > currentDate)
  236. {
  237. var hoursLeftF = Math.floor((nextFishingTime - currentDate)/(1000*60*60));
  238. var minutesLeftF = Math.floor(((nextFishingTime - currentDate) % (1000*60*60)) / (1000*60));
  239. var secondsLeftF = Math.floor(((nextFishingTime - currentDate) % (1000*60)) / (1000));
  240. timeStringF = hoursLeftF + "h " + minutesLeftF + "m";
  241. }
  242. else
  243. {
  244. timeStringF = "🐟";
  245. }
  246. }
  247.  
  248. var fishingModulePart = `<li><a style="font-size: 10px; color:#FF8CA1" href="/setactivepet/?pet_name=${petName}">${petName}</a> (${petLevel}): <span id="${petName}_ttf">${timeStringF}</span></li>`
  249.  
  250. trainingModule = trainingModule + fishingModulePart;
  251. }
  252. trainingModule = trainingModule + `</div>`;
  253.  
  254.  
  255.  
  256.  
  257. document.getElementsByTagName("body")[0].insertAdjacentHTML("afterbegin", trainingModule)
  258.  
  259. // Add on-click event
  260. document.getElementById("TBD").onclick = function() {
  261. fishingStorage.display = !fishingStorage.display;
  262. if (fishingStorage.display){
  263. console.log("hi")
  264. document.getElementById("TBD").innerText = "↑";
  265. document.getElementById("fishingModulePets").style.display = "inline";
  266. }
  267. else{
  268. console.log("bye")
  269. document.getElementById("TBD").innerText = "↓";
  270. document.getElementById("fishingModulePets").style.display = "none";
  271. }
  272. localStorage.setItem("fishingtrackerGC==", JSON.stringify(fishingStorage));
  273. };
  274.  
  275.  
  276. setInterval(trainingList, 1000);
  277. }
  278.  
  279. // When on quickref, remove abandoned fishers
  280. const getPetNames = () => Array.from(document.querySelectorAll(`.quickref_content a[href*="/setactivepet/?pet_name="]`))
  281. .map(e => e.nextElementSibling.textContent)
  282.  
  283. function pruneFishers() {
  284. const key = "fishingtrackerGC=="
  285. const actualPets = getPetNames()
  286.  
  287.  
  288. // Load saved fishing data
  289. let data = localStorage.getItem(key)
  290. if (!data) return // never visited the fishing module
  291. data = JSON.parse(data)
  292.  
  293. // Remove pets that are no longer on the account
  294. const currentEntries = Object.entries(data.pets)
  295. const prunedEntries = currentEntries.filter(([fisher, _]) => actualPets.includes(fisher))
  296.  
  297. // Quit if nothing changed
  298. const changed = prunedEntries.length < currentEntries.length
  299. if (!changed) return
  300.  
  301. // Update and save
  302. data.pets = Object.fromEntries(prunedEntries)
  303. localStorage.setItem(key, JSON.stringify(data))
  304. }
  305.  
  306.  
  307. if (currentPage == "https://www.grundos.cafe/quickref/") {
  308. pruneFishers()
  309. }
  310.  
  311. })();