forked from saahphire/NeopetsUserscripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodestoneTracker.js
More file actions
235 lines (214 loc) · 10.4 KB
/
Copy pathcodestoneTracker.js
File metadata and controls
235 lines (214 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// ==UserScript==
// @name Neopets: Codestone Tracker
// @namespace https://github.com/saahphire/NeopetsUserscripts
// @version 3.0.1
// @description Tracks your codestones, removes them with a click on the training page, adds a link to SW for the ones you don't have yet
// @author saahphire
// @homepageURL https://github.com/saahphire/NeopetsUserscripts
// @homepage https://github.com/saahphire/NeopetsUserscripts
// @downloadURL https://github.com/saahphire/NeopetsUserscripts/blob/main/codestoneTracker.js
// @updateURL https://github.com/saahphire/NeopetsUserscripts/blob/main/codestoneTracker.js
// @match *://*.neopets.com/island/training.phtml?type=status
// @match *://*.neopets.com/island/fight_training.phtml?type=status
// @icon https://www.google.com/s2/favicons?sz=64&domain=neopets.com
// @license The Unlicense
// @require https://update.greasyfork.org/scripts/567035/1759043/Neopets%3A%20Shop%20Wizard%20Anchor%20Creator.js
// ==/UserScript==
/*
•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•:•:•:•:•:•:•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•.•:•:•:•:•:•:•:•:•.•:•:•.
........................................................................................................
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆
Now works for the Ninja Training School, thanks androidturret!
This script does the following:
1. Accesses your SDB to count how many codestones of each type you have
2. Forwards any random events to the page you're in (in theory. It's hard to test)
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 (or SSW, if you have Premium) 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.
✦ ⌇ saahphire
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠂⠄⠄⠂☆
........................................................................................................
•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•:•:•:•:•:•:•:•.•:•.•:•:•:•:•:•:•:••:•.•:•.•:•.•:•:•:•:•:•:•:•:•.•:•:•.
*/
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
Configuration
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Whether codestones on your SDB should have a link to retrieve it with a click (if true) or to your SDB's search function (if false)
* @constant {boolean}
* @default
*/
const iWantToRemoveCodestonesFromMySDBWithAClick = true;
/**
* Your PIN, or, if you don't have a PIN set on your SDB, an empty string ''
* @constant {string}
*/
const pin = '0000';
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
PIN
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Tries to retrieve your PIN from either the input field or your pin const
* @returns {string|undefined}
*/
const getPin = () => {
const pinField = document.getElementById("pin_field")?.value;
if(pinField && pinField !== '') return pinField;
if(pin === '' || pin.match(/^\d{4}$/)) return pin;
}
/**
* Adds an input field for your PIN in the Training School's page
*/
const addPinField = () => document.querySelector(".content > div:nth-child(2) center")?.insertAdjacentHTML('afterEnd', `<div class="saahphire-pin" style="display:flex;place-content:center;place-items:center;gap:0.5em">
<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">
</div>`);
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
Utility
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Takes an image's URL and retrieves the codestone's ID from within
* @returns {number}
*/
const getIndexFromImageUrl = (imageUrl) => parseInt(imageUrl.match(/codestone(\d+)/)[1]);
/**
* Takes a codestone's index (from 1 to 16) and returns its item id.
* @returns {number}
*/
const getIdFromIndex = (index) => index < 11 ? index + 7457 : index + 22197;
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
Links
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Edits a given anchor to communicate a successful action
* @param anchor {Element} The anchor element that should be edited to reflect success
*/
const communicateSuccess = (anchor) => {
anchor.textContent = anchor.textContent.replace("⌛", "✔️");
anchor.style.textDecoration = "line-through";
anchor.removeAttribute("href");
}
/**
* Edits a given anchor to communicate a failed action and logs its error in the console
* @param anchor {Element} The anchor element that should be edited to reflect failure
* @param error {string} The error that should be logged
*/
const communicateFailure = (anchor, error) => {
console.error("Something went wrong! " + error);
anchor.textContent = anchor.textContent.replace("⌛", "✖️");
}
/**
* Retrieves a given codestone from your SDB
* @param itemIndex {number} The codestone's index within image numbers and our storage
* @param itemName {string} The codestone's name
* @param pin {string} Your pin
* @param anchor {Element} The anchor element that should be edited to reflect the request's result
*/
const removeCodestone = async (itemIndex, pin, anchor) => {
anchor.textContent = "⌛ " + anchor.textContent;
const itemId = getIdFromIndex(itemIndex);
const url = `https://www.neopets.com/process_safetydeposit.phtml?offset=0&remove_one_object=${itemId}&obj_name=&category=2&pin=${pin}`;
try {
await fetch(url);
communicateSuccess(anchor);
} catch (e) {
communicateFailure(anchor, e)
}
}
/**
* Adds a link to the appropriate place (or an event listener) to each codestone name
* @param codestoneElement {Element} The bold element containing the codestone's name
* @param isSDB {boolean} Whether this should link to an SDB action (on true) or the Shop Wizard (on false)
* @param itemIndex {number} The codestone's index within image numbers and our storage
*/
const makeLink = (codestoneElement, isSDB, itemIndex) => {
if(isSDB) {
const foundPin = getPin();
if(!iWantToRemoveCodestonesFromMySDBWithAClick || typeof foundPin !== 'string') return;
const a = document.createElement("a");
codestoneElement.insertAdjacentElement("beforeBegin", a);
a.appendChild(codestoneElement);
a.onclick = (e) => {
e.preventDefault();
removeCodestone(itemIndex, foundPin, a)
};
a.href = "#";
}
else codestoneElement.previousElementSibling.insertAdjacentElement('afterend', createSWLink(codestoneElement.textContent, codestoneElement));
}
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
Page-Specific
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Fetches the codestone SDB page and retrieves each codestone's quantity
* @returns {Object.<number, number>} A dictionary with the item's id as key and quantity as value
*/
const getStoredCodestones = async () => {
const fetched = await fetch('https://www.neopets.com/safetydeposit.phtml?obj_name=&category=2');
const text = await fetched.text();
const randomEvent = text.match(/<link [^>]+randomevents.+>[\s\S]+<div class="copy">\s*.+\s*<\/div>/);
if(randomEvent) document.querySelector('td[class="content"], .container').insertAdjacentHTML('afterbegin', randomEvent);
const matches = text.matchAll(/center"><b>(\d+)<\/b>[\s\S]+?back_to_inv\[(\d+)\]/g);
const result = {};
matches.forEach(match => result[match[2]] = parseInt(match[1]));
return result;
}
/**
* Adds links to each codestone shown in the Training School status page
*/
const onTrainingPage = async () => {
const codestones = document.querySelectorAll("table[width='500'] tr:nth-child(2n) td:last-child b + img");
if(!codestones.length) return;
const ownedCodestones = await getStoredCodestones();
const usage = new Array(17);
codestones.forEach(codestone => {
const textElement = codestone.previousElementSibling;
const itemIndex = getIndexFromImageUrl(codestone.src);
const itemId = getIdFromIndex(itemIndex);
const available = ownedCodestones[itemId] ?? 0;
const used = usage[itemIndex] ?? 0;
if (used < available) {
usage[itemIndex] = used + 1;
makeLink(textElement, true, itemIndex);
}
else {
makeLink(textElement, false);
textElement.style.color = "red";
codestone.parentElement.parentElement.style.backgroundColor = "wheat";
}
});
}
/*
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
URL Checkers
☆ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂✦ ⠂⠄⠄⠂⠁⠁⠂⠄⠄⠂☆
*/
/**
* Checks whether the current page's url contains a parameter and its value is the desired value.
* @param parameter {string} The parameter to check for
* @param value {string} The value the parameter should have
* @param nullIsTrue {boolean} Whether the ausence of the parameter should be counted as a positive or negative result
* @returns {boolean} Whether the parameter has the appropriate value
*/
const isParam = (parameter, value, nullIsTrue = true) => window.location.href.match(new RegExp(`${parameter}=${value}(&|$)`)) ?? (nullIsTrue && !window.location.href.match(parameter + '='));
(async function() {
'use strict';
if(isParam('type', 'status', false)) {
addPinField();
onTrainingPage();
}
})();