Training School Tools

Faster navigation on training pages

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           Training School Tools
// @namespace      neopets
// @version        2021.01.17
// @description    Faster navigation on training pages
// @author         wtmeow
// @match          http://www.neopets.com/pirates/academy.phtml?type=status*
// @match          http://www.neopets.com/island/training.phtml?type=status*
// @match          http://www.neopets.com/island/fight_training.phtml?type=status*
// @require        https://code.jquery.com/jquery-3.5.1.min.js
// ==/UserScript==

const PIN = "0"; // set to "0" if you don't have PIN enabled for SDB

const url = location.href;
const itemID = {
    "One Dubloon Coin" : "12755",
    "Two Dubloon Coin" : "12756",
    "Five Dubloon Coin" : "12757",
    "Mau Codestone" : "7458",
    "Tai-Kai Codestone" : "7459",
    "Lu Codestone" : "7460",
    "Vo Codestone" : "7461",
    "Eo Codestone" : "7462",
    "Main Codestone" : "7463",
    "Zei Codestone" : "7464",
    "Orn Codestone" : "7465",
    "Har Codestone" : "7466",
    "Bri Codestone" : "7467",
    "Mag Codestone" : "22208",
    "Vux Codestone" : "22209",
    "Cui Codestone" : "22210",
    "Kew Codestone" : "22211",
    "Sho Codestone" : "22212",
    "Zed Codestone" : "22213"
};

const process_url = location.pathname.replace(/\/(?!.+\/)/g, "/process_");

const getTime = () => $("#nst").html().replace(/ NST/g, "");

// Get list of all pets and stats
let stats = {};
$("b").filter(function () {
    return this.innerHTML.includes(" (Level ")
}).each(function (index, element) {
    const petName = $(element).text().split(" (Level")[0];
    stats[petName] = {};
    $(element).parent().parent().next().find("b").each(function (index2, element2) {
        const stat = $(element2).text();
        switch (index2) {
            case 0: // Lvl
                stats[petName]["Lvl"] = parseInt(stat);
                break;
            case 1: // Str
                stats[petName]["Str"] = parseInt(stat);
                break;
            case 2: // Def
                stats[petName]["Def"] = parseInt(stat);
                break;
            case 3: // Mov
                stats[petName]["Mov"] = parseInt(stat);
                break;
            case 4: // Hp
                stats[petName]["Hp"] = parseInt(stat.split("/")[1]);
                break;
            case 5:
                return false;
            default:
                return false;
        }
    });
});

// Add floating div
$(`<div id="quick-links-show" class="floating-box" style="display: none; width: auto;"><div style="color: #ffff00; text-align:right;"><span style="cursor: pointer;" id="floating-show">Show</span></div></div><div id="quick-links" class="floating-box"><div style="color: #ffff00; text-align:right;"><span style="cursor: pointer;" id="floating-hide">Hide</span></div><div style="font-size: 150%; text-align: center;">Quick links</div><br><br><button id="completeAll-button" class="quickButton">Complete all courses</button><br><button id="getAll-button" class="quickButton">Get all items</button><br><button id="payAll-button" class="quickButton">Pay all courses</button><div id="floating-results" style="letter-spacing: 0.03em;"></div></div>`).appendTo("body");
$("#floating-hide").on("click", function () {
    $("#quick-links").hide(300);
    $("#quick-links-show").show(300);
});
$("#floating-show").on("click", function () {
    $("#quick-links-show").hide(300);
    $("#quick-links").show(300);
});

// Add table
$("b:contains('Current Course Status')").after(`
<style>
    .courses {
        margin-left: auto;
        margin-right: auto;
        table-layout: fixed;
        width: 60%;
        border: 2px solid black;
        border-collapse: collapse;
        text-align: center;
    }

    .courses td, .courses th {
        border: 1px solid black;
        padding: 3px;
    }

    .courses tbody tr:nth-child(even) {
        background-color: #e3e3e3
    }

    .courses input[type="radio"] {
        width: 20px;
        height: 20px;
        cursor: pointer;
    }

    .floating-box {
        text-align: left;
        position: fixed;
        padding: 10px;
        opacity: 95%;
        width: 230px;
        right: 5px;
        top: 200px;
        background-color: #3c3f41;
        color: #FFFFFF;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 14px;
        border-radius: 10px;
    }

    .quickButton {
        width: 100%;
        padding: 5px;
        border-radius: 10px;
        font-family: Verdana, Arial, Helvetica, sans-serif;
        font-size: 12px;
    }
</style>
<table id="courseTable" class="courses" style="display:none;">
    <colgroup>
        <col span="1" style="width: 20%;">
        <col span="5" style="width: 7%;">
    </colgroup>
    <tbody>
    <tr id="tableHeader" style="background-color: #72bd80">
        <th><b>Pet</b></th>
        <th><b>Lvl</b></th>
        <th><b>Str</b></th>
        <th><b>Def</b></th>
        <th><b>Mov</b></th>
        <th><b>Hp</b></th>
    </tr>
    <tr id="checkall">
        <td><b>Check All</b></td>
        <td><input name="checkall" type="radio" class="checkall-Level" id="lvl-all"></td>
        <td><input name="checkall" type="radio" class="checkall-Strength" id="str-all"></td>
        <td><input name="checkall" type="radio" class="checkall-Defence" id="def-all"></td>
        <td><input name="checkall" type="radio" class="checkall-Agility" id="mov-all"></td>
        <td><input name="checkall" type="radio" class="checkall-Endurance" id="hp-all"></td>
    </tr>
    </tbody>
    <tfoot>
    <tr>
        <td colspan="6" style="text-align:center;">
            <input type="button" id="train-all" value="Train Pets"> &nbsp;&nbsp;
            <input type="button" id="reset-all" value="Clear Form">
        </td>
    </tr>
    </tfoot>
</table>
`);

// Create quick course table
let tableRows = ``;
for (let pet in stats) {
    const thisPet = stats[pet];
    const withinLvlRange =
        url.includes("island/fight_training") || // Only pets >= Lv250 will appear on the page anyway
        url.includes("island/training") && thisPet["Lvl"] <= 250 ||
        url.includes("pirates/academy") && thisPet["Lvl"] <= 40;
    if ($(`b:contains(${pet})`).text().includes("is not on a course") &&
        withinLvlRange === true) {
        tableRows += `
            <!--${pet}-->
            <tr>
                <td>
                    <input type="hidden" value="${pet}">
                    <span>${pet}</span>
                </td>
                <td>
                    <input name="course-${pet}" type="radio" value="Level">
                    <br>
                    <b id="${pet}-stat-Lvl" style="color: green">${thisPet["Lvl"]}</b>
                </td>
                <td>
                    <input name="course-${pet}" type="radio" value="Strength">
                    <br>
                    <b id="${pet}-stat-Str">${thisPet["Str"]}</b>
                </td>
                <td>
                    <input name="course-${pet}" type="radio" value="Defence">
                    <br>
                    <b id="${pet}-stat-Def">${thisPet["Def"]}</b></td>
                <td>
                    <input name="course-${pet}" type="radio" value="Agility">
                    <br>
                    <b id="${pet}-stat-Mov">${thisPet["Mov"]}</b></td>
                <td>
                    <input name="course-${pet}" type="radio" value="Endurance">
                    <br>
                    <b id="${pet}-stat-Hp">${thisPet["Hp"]}</b>
                </td>
            </tr>`;
    }
}
if (tableRows !== ``) {
    $("#courseTable").show();
    $("#tableHeader").after(tableRows);
    $("#checkall input").each(function (index, element) {
        const $this = $(element);
        const stat = $this.attr("class").split("-")[1];
        $this.on("change", function () {
            if ($this.prop("checked")) {
                $(":radio").filter(function () {
                    return this.value === stat;
                }).each(function () {
                    $(this).prop("checked", true);
                });
            }
        });
    });
    $("#reset-all").on("click", function () {
        $(".courses :radio").each(function (index, element) {
            $(element).prop("checked", false);
        });
    });
    $("#train-all").on("click", function () {
        $(".courses input").each(function (index, element) {
            $(element).prop("disabled", true); // disable all radio buttons
        });
        let training = [];
        $(".courses tbody").find("input:checked:not([name='checkall'])").each(function (index, element) {
            const pet = $(element).attr("name").split("-")[1];
            const stat = $(element).val();
            training.push([pet, stat]);
        });
        (async () => {
            print(`[1 / ${training.length}]<br><br>Processing...`)
            for (let i = 0; i < training.length; i++) {
                const pet = training[i][0];
                const stat = training[i][1];
                const result = await startCourse(pet, stat);
                console.log(result);
                print(`[${i + 1} / ${training.length}]<br><br>${result["Pet"]} (${result["Course"]}) : <br><br>${result["Status"]}`);
            }
            location.replace(url);
        })();
    });
}

// Replace complete course button
const forms = $("form[action*='process_']"); // get all available "Complete course!" forms
for (let i = 0; i < forms.length; i++) {
    let petName = forms.eq(i).find("input[name='pet_name']").val();
    let completeButton = forms.eq(i).find(":submit[value='Complete Course!']");
    completeButton.replaceWith(`<button id="complete-${petName}" class="completeButton" type="button">Complete!</button>`);
    $(`#complete-${petName}`).on("click", function () {
        completeCourse(this, petName);
    });
}

// Add button to get dubloons/codestones from SDB
// codestone
$("a[href*='_training.phtml?type=pay&pet_name=']").each(function (index, element) {
//let petName = $(element).attr("href").split("&pet_name=")[1];
    let $p = $(element).next("p");
    $p.before('<br><button class="getItems" type="button">Get items from SDB</button>');
});
// dubloon
$("b:contains(' Dubloon Coin')").each(function (index, element) {
    $(element).parent().parent().before('<tr><td style="text-align: center" colspan="2"><button class="getItems" type="button">Get from SDB</button></td></tr>');
});
// Handler
$(".getItems").each(function (index, element) {
    $(element).on("click", function () {
        $(this).prop("disabled", true);
        let items = [];
        if (url.includes("/island/")) {
            $(element).next("p").find("b:contains('Codestone')").each(function (index, element) {
                const codestone = $(element).text();
                items.push(codestone);
            });
        }
        if (url.includes("/pirates/")) {
            const dubloon = $(element).parentsUntil("table").eq(2).find("b").text();
            items.push(dubloon);
        }
        (async () => $(this).html(await getItemsFromSDB(items)))();
    });
});

/********************************
 *
 * All quick links
 *
 ********************************/

// Get all items
let getAllItems = [];
$(".getItems").each(function (index, element) {
    // Codestones
    if (url.includes("/island/")) {
        $(element).next("p").find("b:contains('Codestone')").each(function (index2, element2) {
            const codestone = $(element2).text();
            getAllItems.push(codestone);
        });
    }
    // Dubloons
    if (url.includes("/pirates/")) {
        const dubloon = $(element).parent().parent().parent().find("b:contains('Dubloon Coin')").html();
        getAllItems.push(dubloon);
    }
});

$("#getAll-button").on("click", function () {
    if (!getAllItems.length) {
        window.alert("No items to retrieve!");
        return false;
    }
    $(this).prop("disabled", true);
    let printItems = {};
    for (let i = 0; i < getAllItems.length; i++) {
        if (!printItems[getAllItems[i]]) {
            printItems[getAllItems[i]] = 0;
        }
        printItems[getAllItems[i]]++;
    }
    const itemList = JSON.stringify(printItems).replace(/,/g, "<br>").replace(/[{}"']/g, "").replace(/:(\d)/g, " (x$1)");
    (async () => {
        $(this).html(await getItemsFromSDB(getAllItems));
        print(`<span style="color:#00ffff">[${getTime()}]</span><br><br>Retrieved items:<br><br>${itemList}<br><br><span style="color: #ffff00; font-size: 80%">If some ${url.includes("/island/") ? "codestones" : "dubloons"} were not retrieved, you may not have enough in your Safety Deposit Box.</span>`);
    })();
});

// Complete all courses
$("#completeAll-button").on("click", function () {
    const numButtons = $(".completeButton").length;
    if (!numButtons) {
        window.alert("No courses to complete!");
        return false;
    }
    $(this).prop("disabled", true);
    print(`<span style="color:#00ffff">[${getTime()}]</span><br><br>Processing...`);
    (async () => {
        let printText = `<span style="color:#00ffff">[${getTime()}]</span><br><br>`;
        for (let i = 0; i < numButtons; i++) {
            const thisButton = $(".completeButton")[i];
            const petName = $(thisButton).attr("id").split("-")[1];
            const {pet, stat, bonus} = await completeCourse(thisButton, petName);
            printText += `${pet} : <span style="color: #00ff00">+${bonus}</span> ${stat}<br>`;
        }
        print(printText);
    })();
});

// Pay all courses
$("#payAll-button").on("click", function () {
    let payees = [];
    if (url.includes("/island/training.phtml")) {
        $("a[href*='process_training.phtml']").each(function (index, element) {
            const pet = $(element).attr("href").split("name=")[1];
            payees.push(pet);
        });
    } else if (url.includes("/pirates/academy.phtml")) {
        $(":submit[value='Pay']").each(function (index, element) {
            const pet = $(element).prev().prev().val();
            payees.push(pet);
        });
    } else if (url.includes("/island/fight_training.phtml")) {
        $("a[href*='process_fight_training.phtml?type=pay']").each(function (index, element) {
            const pet = $(element).attr("href").split("name=")[1];
            payees.push(pet);
        });
    }
    if (payees.length) {
        $(this).prop("disabled", true);
        (async () => {
            for (let i = 0; i < payees.length; i++) {
                const pet = payees[i];
                // print(`[${i + 1} / ${payees.length}]<br><br>${pet} (Paying...)`);
                const status = await payCourse(pet);
                print(`[${i + 1} / ${payees.length}]<br><br>${pet} (${status})`);
            }
            location.replace(url);
        })();
    } else {
        window.alert("No courses to pay!");
    }
});

/********************************
 *
 * Functions
 *
 *******************************/

function print(data = ``) {
    $("#floating-results").html(`<br><hr><br>${data}`);
}

function payCourse(pet) {
    return new Promise(resolve => {
        $.ajax({
            type : "GET",
            url : `${process_url}?type=pay&pet_name=${pet}`,
            success : data => {
                resolve(`<span style="color: #00ff00">Success</span>`);
            },
            error : data => {
                resolve(`<span style="color: #ff0000">Error</span>`);
            }
        });
    })
}

function completeCourse(element, pet) {
    return new Promise(resolve => {
        const $this = $(element);
        $this.prop("disabled", true);
        let response = {};
        $.ajax({
            type : "POST",
            url : process_url,
            // async : false,
            data : {
                "type" : "complete",
                "pet_name" : pet
            },
            success : (data, status) => {
                let stat = data.match(/increased (\w+)/)[1] || "error";
                let bonus = data.includes("SUPER BONUS") ? parseInt(data.match(/SUPER BONUS - You went up (\d+) points/)[1]) : 1;
                response = {
                    "pet" : pet,
                    "stat" : stat,
                    "bonus" : bonus,
                    "status" : status
                };
                let result = stat === "error" ? "error" : `<b style="color:green">Course complete!</b><br><br><span>+${bonus} ${stat}</span><br><br><button id="repeat-${pet}" type="button">Repeat this course</button>`;
                $this.parent().parent().find("b:contains('Course Finished')").replaceWith(result);
                if (document.getElementById(`repeat-${pet}`)) {
                    $(`#repeat-${pet}`).on("click", function () {
                        $(this).prop("disabled", true);
                        (async () => {
                            const result = await startCourse(pet, stat);
                            $(this).html(result["Status"]);
                            setTimeout(function () {
                                location.replace(url);
                            }, 1000);
                        })();
                    });
                }
                resolve(response);
            }
        });
    })
}

function startCourse(pet, course) {
    return new Promise(resolve => {
        setTimeout(() => {
            $.ajax({
                type : "POST",
                url : process_url,
                // async : false,
                data : {
                    "type" : "start",
                    "course_type" : course,
                    "pet_name" : pet
                },
                success : data => {
                    let error = data.includes("Error:") ? `Error: ${data.split("<b>Error: </b>")[1].split("</div>")[0]}` : "Successful";
                    resolve({
                        "Pet" : pet,
                        "Course" : course,
                        "Status" : error
                    });
                }
            });
        }, 1500);
    })
}

function getItemsFromSDB(array) {
    return new Promise(resolve => {
        console.log(array);
        let postData = {};
        let itemCount = {};
        for (let i = 0; i < array.length; i++) {
            let id = itemID[array[i]];
            if (!itemCount[id]) {
                itemCount[id] = 0;
            }
            itemCount[id]++;
        }
        for (let item in itemCount) {
            if (itemCount[item] > 0) {
                postData[`back_to_inv[${item}]`] = itemCount[item];
            }
        }
        postData["category"] = "0";
        postData["offset"] = "0";
        if (PIN && PIN !== "0") {
            postData["pin"] = PIN.toString();
        }
        $.ajax({
            type : "POST",
            url : "/process_safetydeposit.phtml?checksub=scan",
            // async : false,
            data : postData,
            success : data => {
                let error = data.includes("Error:") ? `Error: ${data.split("<b>Error: </b>")[1].split("</div>")[0]}` : "Successful";
                resolve(error);
            }
        });
    })
}