GC Helper

Adds helpful timers and links to the GC sidebar.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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()
    }

})();