Adds helpful timers and links to the GC sidebar.
// ==UserScript==
// @name GC Helper
// @description Adds helpful timers and links to the GC sidebar.
// @version 0.3
// @author ben (mushroom), dani
// @match https://grundos.cafe/*
// @match https://www.grundos.cafe/*
// @icon https://www.google.com/s2/favicons?domain=grundos.cafe
// @namespace https://greasyfork.org/users/727556
// ==/UserScript==
var storage;
var fishingStorage;
localStorage.getItem("trainingtrackerGC==") != null ? storage = JSON.parse(localStorage.getItem("trainingtrackerGC==")) : storage = {display: true, students: {}};
localStorage.getItem("fishingtrackerGC==") != null ? fishingStorage = JSON.parse(localStorage.getItem("fishingtrackerGC==")) : fishingStorage = {display: true, pets: {}};
var currentPage = window.location.href;
var pageHTML = document.body.innerHTML;
var documentText = document.body.innerText;
var content = document.getElementsByClassName("content")[0];
var currentDate = new Date();
var usingDefaultTheme = document.body.innerHTML.indexOf('04/top_bar_anim') < 0 ? true : false;
function trainingList() {
// Loop through students in storage
var newDate = new Date();
for (var student in storage.students)
{
// get name, parse date, update content of
let studentName = student;
var endDate = new Date(storage.students[student].timeToCompletion);
var timeString;
if (!isNaN(Date.parse(endDate))) // check if valid date
{
var hoursLeft = Math.floor((endDate - newDate)/(1000*60*60));
var minutesLeft = Math.floor(((endDate - newDate) % (1000*60*60)) / (1000*60));
var secondsLeft = Math.floor(((endDate - newDate) % (1000*60)) / (1000));
if (secondsLeft >= 0)
{
timeString = hoursLeft + "h " + minutesLeft + "m";
}
else
{
timeString = "Course Finished!";
}
}
else
{
timeString = storage.students[student].timeToCompletion;
}
document.getElementById(studentName + "_ttc").innerText = timeString;
}
for (var fishingPet in fishingStorage.pets)
{
var endDateFish = new Date(fishingStorage.pets[fishingPet][0])
var timeStringFish;
if (!isNaN(Date.parse(endDateFish))) // check if valid date
{
var hoursLeftFish = Math.floor((endDateFish - newDate)/(1000*60*60));
var minutesLeftFish = Math.floor(((endDateFish - newDate) % (1000*60*60)) / (1000*60));
var secondsLeftFish = Math.floor(((endDateFish - newDate) % (1000*60)) / (1000));
if (secondsLeftFish >= 0)
{
timeStringFish = hoursLeftFish + "h " + minutesLeftFish + "m";
}
else
{
timeStringFish = "🐟";
}
}
document.getElementById(fishingPet + "_ttf").innerText = timeStringFish;
}
}
(function() {
'use strict';
// When on training status page, parse HTML & store in local storage
if (currentPage == "https://www.grundos.cafe/island/training/?type=status"){
var petTables = content.getElementsByTagName("table")[0].getElementsByTagName("tbody")[0].getElementsByTagName("tr");
var numOfPets = petTables.length / 2;
var petNames = [];
// Loop through pets
for (var i = 0; i < numOfPets; i++)
{
var courseInfo = petTables[2*i].getElementsByTagName("td")[0].getElementsByTagName("b")[0].innerText.split(" ");
petNames.push(courseInfo[0]);
// if last element of courseInfo == course, remove petname from students
if (courseInfo[courseInfo.length - 1] == "course")
{
delete storage.students[courseInfo[0]];
}
// else, add student record (pet name, course, time to completion)
else
{
var timeToCompletion = petTables[2*i + 1].getElementsByTagName("td")[1].getElementsByTagName("b")[0].innerText;
if (timeToCompletion.indexOf("Codestone") > 0)
{
storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: timeToCompletion + " required"};
}
else if (timeToCompletion == "Course Finished!")
{
storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: timeToCompletion};
}
else
{
var re = /\d+/g;
var timeParts = [...timeToCompletion.matchAll(re)];
var endTime = new Date(currentDate.getTime() + timeParts[0]*60*60*1000 + timeParts[1]*60*1000 + timeParts[2]*1000)
storage.students[courseInfo[0]] = {currentCourse: courseInfo[courseInfo.length - 1], timeToCompletion: endTime};
}
}
}
for (var s in storage.students)
{
var matchExists = 0;
for (var j = 0; j < petNames.length; j++)
{
if (petNames[j] == s)
{
matchExists = 1;
}
}
if (matchExists == 0)
{
delete storage.students[s];
}
}
localStorage.setItem("trainingtrackerGC==", JSON.stringify(storage));
}
// When on fishing success page, parse HTML & store in local storage
if (currentPage == "https://www.grundos.cafe/water/fishing/" && document.body.innerText.lastIndexOf('might be able to') > 0)
{
var activePet = usingDefaultTheme ? documentText.substring(documentText.lastIndexOf(' | Pet : ') + 9, documentText.lastIndexOf(' | NP : ')) : documentText.substring(documentText.lastIndexOf('\npet : ') + 7, documentText.lastIndexOf('\nNP : '))
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)
var currentLevel = activePet in fishingStorage.pets ? fishingStorage.pets[activePet][1] : "TBD"
if (documentText.lastIndexOf("fishing level increases to") > 0){
const levRegex = /fishing level increases to (.*?)!/g;
var levResult = levRegex.exec(documentText);
currentLevel = levResult[1];
}
fishingStorage.pets[activePet] = [ new Date(currentDate.getTime() + 60*60*1000*parseInt(hoursUntilNext)) , currentLevel ]
localStorage.setItem("fishingtrackerGC==", JSON.stringify(fishingStorage));
}
// When on any page with Neopets sidebar, add training module
if (document.getElementsByName("a").length > 0)
{
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>`
var studentCount = 0;
// Loop through students in storage
for (var student in storage.students)
{
let studentName = student;
let currentCourse = storage.students[student].currentCourse;
var endDate = new Date(storage.students[student].timeToCompletion);
var timeString;
if (!isNaN(Date.parse(endDate))) // check if valid date
{
if (Date.parse(endDate) > currentDate)
{
var hoursLeft = Math.floor((endDate - currentDate)/(1000*60*60));
var minutesLeft = Math.floor(((endDate - currentDate) % (1000*60*60)) / (1000*60));
var secondsLeft = Math.floor(((endDate - currentDate) % (1000*60)) / (1000));
timeString = hoursLeft + "h " + minutesLeft + "m";
}
else
{
timeString = "Course Finished!";
}
}
else
{
timeString = storage.students[student].timeToCompletion;
}
var trainingModulePart = `<li>${student} (<i>${currentCourse}</i>): <span id="${student}_ttc">${timeString}</span></li>`
trainingModule = trainingModule + trainingModulePart;
studentCount++;
}
if (studentCount == 0)
{
trainingModule = trainingModule + `<br>No pets in courses - go get started!`;
}
// Adding fishing
var fishingDisplay = fishingStorage.display ? "inline" : "none";
var fishingDisplayArrow = fishingStorage.display ? "↑" : "↓";
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">`
var fishingPetsArray = [];
for (var pet in fishingStorage.pets)
{
if (!Array.isArray(fishingStorage.pets[pet])){
var ph = fishingStorage.pets[pet]
fishingStorage.pets[pet] = [ ph, "TBD" ]
}
fishingPetsArray.push([ pet, fishingStorage.pets[pet][0] ]);
}
fishingPetsArray.sort((a, b) => new Date(b[1]) - new Date(a[1]));
for (var p = 0; p < fishingPetsArray.length; p++)
{
let petName = fishingPetsArray[p][0];
let petLevel = fishingStorage.pets[petName][1];
let nextFishingTime = new Date(fishingStorage.pets[petName][0]);
var timeStringF;
if (!isNaN(Date.parse(nextFishingTime))) // check if valid date
{
if (Date.parse(nextFishingTime) > currentDate)
{
var hoursLeftF = Math.floor((nextFishingTime - currentDate)/(1000*60*60));
var minutesLeftF = Math.floor(((nextFishingTime - currentDate) % (1000*60*60)) / (1000*60));
var secondsLeftF = Math.floor(((nextFishingTime - currentDate) % (1000*60)) / (1000));
timeStringF = hoursLeftF + "h " + minutesLeftF + "m";
}
else
{
timeStringF = "🐟";
}
}
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>`
trainingModule = trainingModule + fishingModulePart;
}
trainingModule = trainingModule + `</div>`;
document.getElementsByTagName("body")[0].insertAdjacentHTML("afterbegin", trainingModule)
// Add on-click event
document.getElementById("TBD").onclick = function() {
fishingStorage.display = !fishingStorage.display;
if (fishingStorage.display){
console.log("hi")
document.getElementById("TBD").innerText = "↑";
document.getElementById("fishingModulePets").style.display = "inline";
}
else{
console.log("bye")
document.getElementById("TBD").innerText = "↓";
document.getElementById("fishingModulePets").style.display = "none";
}
localStorage.setItem("fishingtrackerGC==", JSON.stringify(fishingStorage));
};
setInterval(trainingList, 1000);
}
// When on quickref, remove abandoned fishers
const getPetNames = () => Array.from(document.querySelectorAll(`.quickref_content a[href*="/setactivepet/?pet_name="]`))
.map(e => e.nextElementSibling.textContent)
function pruneFishers() {
const key = "fishingtrackerGC=="
const actualPets = getPetNames()
// Load saved fishing data
let data = localStorage.getItem(key)
if (!data) return // never visited the fishing module
data = JSON.parse(data)
// Remove pets that are no longer on the account
const currentEntries = Object.entries(data.pets)
const prunedEntries = currentEntries.filter(([fisher, _]) => actualPets.includes(fisher))
// Quit if nothing changed
const changed = prunedEntries.length < currentEntries.length
if (!changed) return
// Update and save
data.pets = Object.fromEntries(prunedEntries)
localStorage.setItem(key, JSON.stringify(data))
}
if (currentPage == "https://www.grundos.cafe/quickref/") {
pruneFishers()
}
})();