// ==UserScript==
// @name Neopets: Codestone Tracker
// @namespace https://greasyfork.org/en/users/1510049-saah7
// @version 1.0.0
// @description Moves your battle pet(s) to the top in the status page of training schools
// @author saahphire
// @homepageURL https://www.neopets.com/userlookup.phtml?user=saahphire
// @match *://*.neopets.com/island/training.phtml?type=status
// @match *://*.neopets.com/island/fight_training.phtml?type=status
// @match *://*.neopets.com/safetydeposit.phtml?*category=2&*
// @match *://*.neopets.com/safetydeposit.phtml?*category=2
// @match *://*.neopets.com/quickstock.phtml*
// @icon https://www.google.com/s2/favicons?sz=64&domain=neopets.com
// @grant GM_setValue
// @grant GM_getValue
// @license The Unlicense
// ==/UserScript==
/*
•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•:•:•:•:•:•:•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•.•:•:•:•:•:•:•:•:•.•:•:•.•:•.••:•.•
..................................................................................................................
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆
This script does the following:
0. Every feature SHOULD work for both the Training School and the Ninja Training School, but I can't test
the Ninja Training School so I'm doing it blindly.
1. Remembers which codestones you have in your SDB
- To do that, you must go to your SDB's Codestones page:
https://www.neopets.com/island/training.phtml?type=status
- If the script gets out of sync, go back to the Codestones page to update it.
2. Adds a link to your SDB's Codestones page to the top links (Courses, Status, etc)
3. Highlights any pet waiting for their course to be paid if you don't own all needed codestones
4. Adds a link to the Shop Wizard search page to each codestone you don't own
5. Adds a link to remove one codestone from your Safety Deposit Box if you own it
- To do this, you'll either have to add your PIN to the pin const, fill your PIN in the PIN box at the top
of the page, or not have a pin at all. I couldn't get PIN autofillers to work with this script, sorry.
6. Remembers whenever you add codestones to your SDB by using the quick stock page
- I didn't do the same with the inventory page because I don't know if anyone uses it
✦ ⌇ saahphire
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆
..................................................................................................................
•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•:•:•:•:•:•:•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•.•:•:•:•:•:•:•:•:•.•:•:•.•:•.••:•.•
*/
// Change true to false if, instead of taking codestones to your inventory with a click, you want to search for them in your SDB
const iWantToRemoveCodestonesFromMySDBWithAClick = true;
// If you don't have a pin in your SDB, change this line to: const pin = '';
const pin = '0000';
// Set your locale (language Neopets is in) here. Lowercase only.
// Portuguese and Spanish: If codestones in the quick stock are p(i)edras m�sticas, use portugues/espanol. If they're p(i)edras místicas (with the Í), use portugues_ok/espanol_ok
// Chinese (simplified/traditional), Japanese, Korean: As far as I can tell, quick stock and the SDB are completely broken for you guys? I'm so sorry
const locale = 'english';
// Set this to true to have your storage printed on the console any time you refresh a page this script is active in
const isDebug = false;
const localize = {
english: {
codestones: ["Mau Codestone", "Tai-Kai Codestone", "Lu Codestone", "Vo Codestone", "Eo Codestone", "Main Codestone", "Zei Codestone", "Orn Codestone", "Har Codestone", "Bri Codestone", "Mag Codestone", "Vux Codestone", "Cui Codestone", "Kew Codestone", "Sho Codestone", "Zed Codestone"],
special: "special"
},
nederlands: {
codestones: ["Mau Codesteen", "Tai-Kai Codesteen", "Lu Codesteen", "Vo Codesteen", "Eo Codesteen", "Main Codesteen", "Zei Codesteen", "Orn Codesteen", "Har Codesteen", "Bri Codesteen", "Vux Codesteen", "Cui Codesteen", "Kew Codesteen", "Sho Codesteen", "Zed Codesteen"],
special: "speciaal"
},
portugues: {
codestones: ["Pedra M�stica de Mau", "Pedra M�stica de Tai-Kai", "Pedra M�stica de Lu", "Pedra M�stica de Vo", "Pedra M�stica de Eo", "Pedra M�stica Principal", "Pedra M�stica de Zei", "Pedra M�stica de Orn", "Pedra M�stica de Har", "Pedra M�stica de Bri", "Pedra M�stica Vux", "Pedra M�stica Cui", "Pedra M�stica Kew", "Pedra M�stica Sho", "Pedra M�stica Zed"],
special: "especial"
},
portugues_ok: {
codestones: ["Pedra Mística de Mau", "Pedra Mística de Tai-Kai", "Pedra Mística de Lu", "Pedra Mística de Vo", "Pedra Mística de Eo", "Pedra Mística Principal", "Pedra Mística de Zei", "Pedra Mística de Orn", "Pedra Mística de Har", "Pedra Mística de Bri", "Pedra Mística Vux", "Pedra Mística Cui", "Pedra Mística Kew", "Pedra Mística Sho", "Pedra Mística Zed"],
special: "especial"
},
deutsch: {
codestones: ["Mau-Codestein", "Tai-Kai-Codestein", "Lu-Codestein", "Vo-Codestein", "Eo-Codestein", "Main-Codestein", "Zei-Codestein", "Orn-Codestein", "Har-Codestein", "Bri-Codestein", ],
special: "Speziell"
},
francais: {
codestones: ["Codestone Mau", "Codestone Tai-Kai", "Codestone Lu", "Codestone Vo", "Codestone Eo", "Codestone Principale", "Codestone Zei", "Codestone Orn", "Codestone Har", "Codestone Bri", "Codestone Vux", "Codestone Cui", "Codestone Kew", "Codestone Sho", "Codestone Zed"],
special: "spécial"
},
italiano: {
codestones: ["Sassocodice Mau", "Sassocodice Tai-Kai", "Sassocodice Lu", "Sassocodice Vo", "Sassocodice Eo", "Sassocodice Principale", "Sassocodice Zei", "Sassocodice Orn", "Sassocodice Har", "Sassocodice Bri", "Sassocodice Vux", "Sassocodice Cui", "Sassocodice Kew", "Sassocodice Sho", "Sassocodice Zed"],
special: "speciale"
},
espanol: {
codestones: ["Piedra m�stica de Mau", "Piedra m�stica de Tai-Kai", "Piedra m�stica de Lu", "Piedra m�stica de Vo", "Piedra m�stica de Eo", "Piedra m�stica principal", "Piedra m�stica de Zei", "Piedra m�stica de Orn", "Piedra m�stica de Har", "Piedra m�stica de Bri", "Piedra m�stica de Mag", "Piedra m�stica de Vux", "Piedra m�stica de Cui", "Piedra m�stica de Kew", "Piedra m�stica de Sho", "Piedra m�stica de Zed"],
special: "especial"
},
espanol_ok: {
codestones: ["Piedra mística de Mau", "Piedra mística de Tai-Kai", "Piedra mística de Lu", "Piedra mística de Vo", "Piedra mística de Eo", "Piedra mística principal", "Piedra mística de Zei", "Piedra mística de Orn", "Piedra mística de Har", "Piedra mística de Bri", "Piedra mística de Mag", "Piedra mística de Vux", "Piedra mística de Cui", "Piedra mística de Kew", "Piedra mística de Sho", "Piedra mística de Zed"],
special: "especial"
}
};
const localizedNames = localize[locale];
const getPin = () => {
if (!pin) return '';
return document.getElementById("pin_field")?.value ?? (pin.match(/^\d{4}$/) ? pin : undefined);
}
const addPinField = () => {
const div = document.createElement("div");
div.classList.add("saahphire-pin");
div.style = "display: flex;place-content:center;place-items:center;gap:0.5em;";
div.innerHTML = `<p>Enter your <a href="/pin_prefs.phtml">PIN</a>:</p>
<input type="password" name="pin" id="pin_field" size="4" maxlength="4" style="width:10ex;height:15px;">
<img src="https://images.neopets.com/pin/bank_pin_mgr_35.jpg" border="1" width="35" height="35" align="left">`;
document.querySelector(".content > div:nth-child(2) center").insertAdjacentElement("afterEnd", div);
}
const getStoredCodestones = () => {
return JSON.parse(GM_getValue("saahphire-codestone-tracker", "{}"));
}
const setStoredCodestones = (codestones) => {
GM_setValue("saahphire-codestone-tracker", JSON.stringify(codestones));
}
const removeOneFromStorage = (itemId) => {
const storage = getStoredCodestones();
storage[itemId].qty--;
setStoredCodestones(storage);
return storage[itemId].qty;
}
const removeCodestone = (itemId, itemName, pin, link) => {
const url = `https://www.neopets.com/process_safetydeposit.phtml?offset=0&remove_one_object=${itemId}&obj_name=&category=2&pin=${pin}`;
fetch(url, {
method: 'GET'
}).then(response => {
removeOneFromStorage(itemName);
link.innerText = `✔️ ${link.innerText}`;
link.style.textDecoration = "line-through";
link.removeAttribute("href");
}).catch(error => {
console.error("Something went wrong! " + error);
link.innerText = `✖️ ${link.innerText}`;
});
}
const makeLink = (codestoneElement, isSDB, itemId) => {
const a = document.createElement("a");
codestoneElement.insertAdjacentElement("beforeBegin", a);
a.appendChild(codestoneElement);
const foundPin = getPin();
if(iWantToRemoveCodestonesFromMySDBWithAClick && isSDB && foundPin) {
a.onclick = () => removeCodestone(itemId, codestoneElement.innerText, foundPin, a);
a.href = "#";
}
else {
a.href = `https://www.neopets.com/${isSDB ? 'safetydeposit.phtml?obj_name' : 'shops/wizard.phtml?string'}=${codestoneElement.innerText.replace(" ", "+")}`;
a.target = "_blank";
}
}
const onTrainingPage = () => {
const ownedCodestones = getStoredCodestones();
const codestones = document.querySelectorAll("table[width='500'] tr:nth-child(2n) td:last-child img ~ b");
const usage = {};
codestones.forEach(codestone => {
const available = ownedCodestones[codestone.innerText]?.qty ?? 0;
const used = usage[codestone.innerText] ?? 0;
if (used < available) {
usage[codestone.innerText] = used + 1;
makeLink(codestone, true, ownedCodestones[codestone.innerText].id);
}
else {
makeLink(codestone, false);
codestone.style.color = "red";
codestone.parentElement.parentElement.style.backgroundColor = "wheat";
}
});
}
const onSDBPage = () => {
const ownedCodestones = {};
document.querySelectorAll("#boxform ~ tr:not(:last-child)").forEach(row => {
const name = row.children[1].children[0].childNodes[0].data;
const quantity = row.children[4].innerText;
const id = row.querySelector("td:nth-child(6) a").href.match(/\(0,(\d+),/)[1];
ownedCodestones[name] = {id: id, qty: quantity};
});
setStoredCodestones(ownedCodestones);
}
const onQuickStock = () => {
const storage = getStoredCodestones();
document.querySelector("input[type='submit']").addEventListener("click", () => {
const count = 0;
document.querySelectorAll("input[name='buyitem'] ~ table tr:has(input[type='hidden'])").forEach(row => {
if(count >= 70) return;
count++;
const name = row.children[0].innerText;
if(localizedNames.includes(name) && row.children[3].children[0].checked) {
if(!storage[name]) storage[name] = {qty: 0};
storage[name].qty++;
}
});
});
}
const addSDBLink = () => document.getElementsByTagName("center")[0].insertAdjacentHTML("beforeEnd", " | <a href='https://www.neopets.com/safetydeposit.phtml?obj_name=&category=2'>SDB</a>");
const isUrl = (url) => window.location.href.includes(url);
const isParam = (parameter, value, nullIsTrue = false) => window.location.href.match(new RegExp(`${parameter}=${value}(&|$)`)) ?? (nullIsTrue && !window.location.href.match(parameter + '='));
(function() {
'use strict';
if(isParam('type', 'status')) {
addPinField();
addSDBLink();
onTrainingPage();
}
else if(isUrl('safetydeposit') && isParam('offset', 0, true) && isParam('obj_name', '', true) && isParam('category', 2)) onSDBPage();
else if(isUrl('quickstock')) onQuickStock();
if(isDebug) {
console.info("Codestone Tracker storage:");
console.info(getStoredCodestones());
}
})();