GC Helper

Adds helpful timers and links to the GC sidebar.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();