kol.js 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +10 -5
  2. package/src/Client.test.ts +245 -64
  3. package/src/Client.ts +201 -191
  4. package/src/LoathingDate.test.ts +202 -0
  5. package/src/LoathingDate.ts +390 -0
  6. package/src/Player.test.ts +83 -82
  7. package/src/Player.ts +112 -181
  8. package/src/domains/AutomatedFuture.test.ts +19 -0
  9. package/src/domains/AutomatedFuture.ts +46 -0
  10. package/src/domains/Bookmobile.test.ts +20 -0
  11. package/src/domains/Bookmobile.ts +66 -0
  12. package/src/domains/ClanDungeon.ts +230 -0
  13. package/src/domains/Dreadsylvania.test.ts +424 -0
  14. package/src/domains/Dreadsylvania.ts +550 -0
  15. package/src/domains/Familiar.ts +82 -0
  16. package/src/domains/FloralMercantileExchange.test.ts +20 -0
  17. package/src/domains/FloralMercantileExchange.ts +51 -0
  18. package/src/{utils/leaderboard.test.ts → domains/Leaderboard.test.ts} +24 -4
  19. package/src/domains/Leaderboard.ts +173 -0
  20. package/src/domains/Players.test.ts +141 -0
  21. package/src/domains/Players.ts +108 -0
  22. package/src/domains/Raffle.test.ts +65 -0
  23. package/src/domains/Raffle.ts +60 -0
  24. package/src/domains/SkeletonOfCrimboPast.test.ts +55 -0
  25. package/src/domains/SkeletonOfCrimboPast.ts +38 -0
  26. package/src/domains/WardrobeOMatic.test.ts +141 -0
  27. package/src/domains/WardrobeOMatic.ts +650 -0
  28. package/src/domains/__fixtures__/automated_future.html +1 -0
  29. package/src/domains/__fixtures__/bookmobile_spooky.html +6 -0
  30. package/src/domains/__fixtures__/dread/cdr1-current.html +25 -0
  31. package/src/domains/__fixtures__/dread/cdr1-oldlogs-page0.html +25 -0
  32. package/src/domains/__fixtures__/dread/cdr2-current.html +25 -0
  33. package/src/domains/__fixtures__/dread/cdr2-oldlogs-page0.html +25 -0
  34. package/src/domains/__fixtures__/dread/raid-213013.html +24 -0
  35. package/src/domains/__fixtures__/dread/raid-217988.html +24 -0
  36. package/src/domains/__fixtures__/dread/raid-218029.html +24 -0
  37. package/src/domains/__fixtures__/dread/raid-218205.html +24 -0
  38. package/src/domains/__fixtures__/dread/raid-218286.html +24 -0
  39. package/src/domains/__fixtures__/dread/raid-218518.html +24 -0
  40. package/src/domains/__fixtures__/dread/raid-218519.html +24 -0
  41. package/src/domains/__fixtures__/flowers.html +229 -0
  42. package/src/domains/__fixtures__/raidlog.html +1 -0
  43. package/src/domains/__fixtures__/socp.html +1 -0
  44. package/src/index.ts +10 -4
  45. package/src/stats.ts +31 -0
  46. package/src/utils/kmail.ts +3 -3
  47. package/src/utils/utils.ts +43 -0
  48. package/src/Cache.ts +0 -33
  49. package/src/utils/leaderboard.ts +0 -78
  50. /package/src/{utils → domains}/__fixtures__/leaderboard_wotsf.html +0 -0
  51. /package/src/{__fixtures__ → domains/__fixtures__}/raffle.html +0 -0
package/src/Client.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { Mutex } from "async-mutex";
2
- import { EventEmitter } from "node:events";
3
- import TypedEventEmitter, { type EventMap } from "typed-emitter";
2
+ import Emittery from "emittery";
3
+ import makeFetchCookie from "fetch-cookie";
4
+ import { ofetch } from "ofetch";
5
+ import { CookieJar } from "tough-cookie";
4
6
 
5
- import { sanitiseBlueText, wait } from "./utils/utils.js";
6
7
  import { Player } from "./Player.js";
7
- import { parseLeaderboard } from "./utils/leaderboard.js";
8
+ import { Players } from "./domains/Players.js";
8
9
  import {
9
10
  type ChatMessage,
10
11
  type KmailMessage,
@@ -14,14 +15,8 @@ import {
14
15
  isValidMessage,
15
16
  parseKmailMessage,
16
17
  } from "./utils/kmail.js";
17
- import { PlayerCache } from "./Cache.js";
18
- import { CookieJar } from "tough-cookie";
19
- import got, {
20
- type OptionsOfJSONResponseBody,
21
- type OptionsOfTextResponseBody,
22
- } from "got";
23
-
24
- type TypedEmitter<T extends EventMap> = TypedEventEmitter.default<T>;
18
+ import pkg from "../package.json" with { type: "json" };
19
+ import { sanitiseBlueText, wait } from "./utils/utils.js";
25
20
 
26
21
  export type MallPrice = {
27
22
  formattedMallPrice: string;
@@ -32,12 +27,42 @@ export type MallPrice = {
32
27
  minPrice: number | null;
33
28
  };
34
29
 
30
+ class LoginRedirectError extends Error {}
31
+
32
+ export class RolloverError extends Error {
33
+ constructor() {
34
+ super("Kingdom of Loathing is currently down for rollover");
35
+ this.name = "RolloverError";
36
+ }
37
+ }
38
+
39
+ export class AuthError extends Error {
40
+ constructor() {
41
+ super("Unable to log in to Kingdom of Loathing");
42
+ this.name = "AuthError";
43
+ }
44
+ }
45
+
46
+ type FormData = Record<string, string | number | boolean>;
47
+
48
+ type RequestOptions = {
49
+ method?: string;
50
+ query?: Record<string, unknown>;
51
+ form?: FormData;
52
+ };
53
+
54
+ function formToBody(form: FormData): URLSearchParams {
55
+ return new URLSearchParams(
56
+ Object.entries(form).map(([k, v]) => [k, String(v)]),
57
+ );
58
+ }
59
+
35
60
  type Events = {
36
- kmail: (message: KmailMessage) => void;
37
- whisper: (message: KoLMessage) => void;
38
- system: (message: KoLMessage) => void;
39
- public: (message: KoLMessage) => void;
40
- rollover: () => void;
61
+ kmail: KmailMessage;
62
+ whisper: KoLMessage;
63
+ system: KoLMessage;
64
+ public: KoLMessage;
65
+ rollover: Date;
41
66
  };
42
67
 
43
68
  type Familiar = {
@@ -53,41 +78,52 @@ type ApiStatus = {
53
78
  turnsplayed: string;
54
79
  /** kol game day number */
55
80
  daynumber: string;
81
+ /** player level */
82
+ level: string;
56
83
  /** session password */
57
84
  pwd: string;
58
85
  };
59
86
 
60
- export class Client extends (EventEmitter as unknown as new () => TypedEmitter<Events>) {
87
+ export class Client extends Emittery<Events> {
61
88
  actionMutex = new Mutex();
62
- session = got.extend({
63
- cookieJar: new CookieJar(),
64
- prefixUrl: "https://www.kingdomofloathing.com",
65
- handlers: [
66
- (options, next) => {
67
- if (options.form) {
68
- if (options.form.pwd !== false) options.form.pwd = this.#pwd;
89
+ #cookieJar = new CookieJar();
90
+ session = ofetch.create(
91
+ {
92
+ baseURL: "https://www.kingdomofloathing.com",
93
+ retry: 0,
94
+ headers: { "user-agent": `kol.js/${pkg.version}` },
95
+ onRequest: ({ options }) => {
96
+ if (options.query) {
97
+ const { pwd: _, ...rest } = options.query;
98
+ options.query = { ...rest, pwd: this.#pwd };
69
99
  }
70
-
71
- if (options.searchParams) {
72
- const searchParams = options.searchParams as URLSearchParams;
73
- if (searchParams.get("pwd") !== "false")
74
- searchParams.set("pwd", this.#pwd);
100
+ if (options.body instanceof URLSearchParams) {
101
+ options.body.set("pwd", this.#pwd);
75
102
  }
76
-
77
- return next(options);
78
103
  },
79
- ],
80
- });
81
- players = new PlayerCache(this);
104
+ onResponse: ({ request, response }) => {
105
+ const requestUrl = typeof request === "string" ? request : request.url;
106
+ if (
107
+ !requestUrl.includes("login.php") &&
108
+ response.url.includes("/login.php")
109
+ ) {
110
+ throw new LoginRedirectError();
111
+ }
112
+ },
113
+ },
114
+ { fetch: makeFetchCookie(fetch, this.#cookieJar) },
115
+ );
116
+ players = new Players(this);
82
117
 
83
118
  #username: string;
84
119
  #password: string;
85
120
  #isRollover = false;
121
+ #rolloverCheckScheduled = false;
86
122
  #chatBotStarted = false;
87
123
  #pwd = "";
88
124
 
89
125
  private lastFetchedMessages = "0";
90
- private postRolloverLatch = false;
126
+ #postRolloverLatch = false;
91
127
 
92
128
  constructor(username: string, password: string) {
93
129
  super();
@@ -99,84 +135,91 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
99
135
  return this.#username;
100
136
  }
101
137
 
102
- async fetchText(
103
- path: string,
104
- options: OptionsOfTextResponseBody = {},
105
- fallback?: string,
106
- ): Promise<string> {
107
- // With no pwd, try to log in
108
- if (!this.#pwd && !(await this.login())) return fallback ?? "";
109
-
110
- // Make the request
111
- const response = await this.session(path, {
112
- method: "POST",
113
- ...options,
114
- responseType: "text",
115
- });
138
+ async #withReauth<T>(fn: () => Promise<T>): Promise<T> {
139
+ if (this.#isRollover) throw new RolloverError();
140
+ if (!this.#pwd && !(await this.login())) {
141
+ if (this.#isRollover) throw new RolloverError();
142
+ throw new AuthError();
143
+ }
116
144
 
117
- // If we've been redirected to the login page, clear the pwd and try again
118
- if (response.url.includes("/login.php")) {
119
- this.#pwd = "";
120
- return this.fetchText(path, options);
145
+ for (let attempt = 0; attempt < 2; attempt++) {
146
+ try {
147
+ return await fn();
148
+ } catch (error) {
149
+ if (error instanceof RolloverError) throw error;
150
+ if (error instanceof LoginRedirectError && attempt === 0) {
151
+ this.#pwd = "";
152
+ if (!(await this.login())) {
153
+ if (this.#isRollover) throw new RolloverError();
154
+ throw new AuthError();
155
+ }
156
+ continue;
157
+ }
158
+ throw error;
159
+ }
121
160
  }
122
161
 
123
- return response.body;
162
+ throw new AuthError();
163
+ }
164
+
165
+ async fetchText(path: string, options: RequestOptions = {}): Promise<string> {
166
+ const { form, ...rest } = options;
167
+ return this.#withReauth(() =>
168
+ this.session(path, {
169
+ method: "POST",
170
+ ...rest,
171
+ body: form ? formToBody(form) : undefined,
172
+ responseType: "text",
173
+ }),
174
+ );
124
175
  }
125
176
 
126
177
  async fetchJson<Result>(
127
178
  path: string,
128
- options: OptionsOfJSONResponseBody = {},
129
- fallback?: Result,
130
- ): Promise<Result | null> {
131
- if (!(await this.login())) return fallback ?? null;
132
-
133
- let failed = false;
134
-
135
- // Make the request
136
- try {
137
- const response = await this.session(path, {
138
- ...options,
179
+ options: RequestOptions = {},
180
+ ): Promise<Result> {
181
+ const { form, ...rest } = options;
182
+ return this.#withReauth(() =>
183
+ this.session<Result>(path, {
184
+ ...rest,
185
+ body: form ? formToBody(form) : undefined,
139
186
  responseType: "json",
140
- });
141
- if (!response.url.includes("/login.php")) return response.body as Result;
142
- } catch (error) {}
143
-
144
- // If we've not been successful, clear the pwd and try again
145
- this.#pwd = "";
146
- return this.fetchJson(path, options);
187
+ }),
188
+ );
147
189
  }
148
190
 
149
191
  async login(): Promise<boolean> {
150
192
  if (await this.checkLoggedIn()) return true;
151
193
  if (this.#isRollover) return false;
152
194
  try {
153
- const result = await this.session
154
- .post("login.php", {
155
- form: {
156
- loggingin: "Yup.",
157
- loginname: this.#username,
158
- password: this.#password,
159
- secure: "0",
160
- submitbutton: "Log In",
161
- },
162
- })
163
- .text();
195
+ const result = await this.session("login.php", {
196
+ method: "POST",
197
+ responseType: "text",
198
+ body: formToBody({
199
+ loggingin: "Yup.",
200
+ loginname: this.#username,
201
+ password: this.#password,
202
+ secure: "0",
203
+ submitbutton: "Log In",
204
+ }),
205
+ });
164
206
 
165
207
  if (Client.#rolloverPattern.test(result)) {
166
- throw new Error("It's rollover!");
208
+ throw new RolloverError();
167
209
  }
168
210
 
169
211
  if (!(await this.checkLoggedIn())) return false;
170
212
 
171
- if (this.postRolloverLatch) {
172
- this.postRolloverLatch = false;
173
- this.emit("rollover");
213
+ if (this.#postRolloverLatch) {
214
+ this.#postRolloverLatch = false;
215
+ this.emit("rollover", new Date());
174
216
  }
175
217
 
176
218
  return true;
177
219
  } catch (error) {
178
- console.log("error", error);
179
- // Login failed, let's check if it is due to rollover
220
+ if (!(error instanceof RolloverError)) {
221
+ console.error("Login failed:", error);
222
+ }
180
223
  await this.#checkForRollover();
181
224
  return false;
182
225
  }
@@ -188,14 +231,13 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
188
231
 
189
232
  async checkLoggedIn(): Promise<boolean> {
190
233
  try {
191
- const api = await this.session
192
- .get("api.php", {
193
- searchParams: { what: "status", for: `${this.#username} bot` },
194
- })
195
- .json<{ pwd: string }>();
234
+ const api = await this.session<{ pwd: string }>("api.php", {
235
+ query: { what: "status", for: `${this.#username} bot` },
236
+ });
237
+ if (!api || typeof api !== "object" || !api.pwd) return false;
196
238
  this.#pwd = api.pwd;
197
239
  return true;
198
- } catch (error) {
240
+ } catch {
199
241
  return false;
200
242
  }
201
243
  }
@@ -203,33 +245,62 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
203
245
  static #rolloverPattern =
204
246
  /The system is currently down for nightly maintenance/;
205
247
 
248
+ // Uses this.session directly to avoid recursion through fetchText → login
206
249
  async #checkForRollover() {
207
- const isRollover = Client.#rolloverPattern.test(await this.fetchText(""));
250
+ try {
251
+ const html = await this.session("login.php", { responseType: "text" });
252
+ const isRollover = Client.#rolloverPattern.test(html);
208
253
 
209
- if (this.#isRollover && !isRollover) {
210
- // Set the post-rollover latch so the bot can react on next log in.
211
- this.postRolloverLatch = true;
212
- }
254
+ if (this.#isRollover && !isRollover) {
255
+ this.#postRolloverLatch = true;
256
+ }
213
257
 
214
- this.#isRollover = isRollover;
258
+ this.#isRollover = isRollover;
259
+ } catch {
260
+ // Can't reach server — don't change rollover state
261
+ }
215
262
 
216
- if (this.#isRollover) {
217
- // Rollover appears to be in progress. Check again in one minute.
218
- setTimeout(() => this.#checkForRollover(), 60_000);
263
+ if (this.#isRollover && !this.#rolloverCheckScheduled) {
264
+ this.#rolloverCheckScheduled = true;
265
+ setTimeout(() => {
266
+ this.#rolloverCheckScheduled = false;
267
+ void this.#checkForRollover();
268
+ }, 60_000);
219
269
  }
220
270
  }
221
271
 
222
272
  async startChatBot() {
223
273
  if (this.#chatBotStarted) return;
224
- await this.useChatMacro("/join talkie");
225
- this.loopChatBot();
226
274
  this.#chatBotStarted = true;
275
+ this.on("rollover", () => void this.#joinChat());
276
+ await this.#joinChat();
277
+ this.loopChatBot().catch((error) => {
278
+ console.error("Chat bot stopped:", error);
279
+ });
280
+ }
281
+
282
+ async #joinChat() {
283
+ try {
284
+ await this.useChatMacro("/join talkie");
285
+ } catch (error) {
286
+ if (!(error instanceof RolloverError)) {
287
+ console.error("Failed to join chat:", error);
288
+ }
289
+ }
227
290
  }
228
291
 
229
292
  private async loopChatBot() {
230
- await Promise.all([this.checkMessages(), this.checkKmails()]);
231
- await wait(3000);
232
- await this.loopChatBot();
293
+ while (true) {
294
+ try {
295
+ await Promise.all([this.checkMessages(), this.checkKmails()]);
296
+ } catch (error) {
297
+ if (error instanceof AuthError) throw error;
298
+ if (!(error instanceof RolloverError)) {
299
+ console.error("Chat bot loop error:", error);
300
+ }
301
+ }
302
+ await wait(this.#isRollover ? 60_000 : 3000);
303
+ }
233
304
  }
234
305
 
235
306
  async checkMessages() {
@@ -237,15 +308,12 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
237
308
  last: string;
238
309
  msgs: KoLChatMessage[];
239
310
  }>("newchatmessages.php", {
240
- searchParams: {
311
+ query: {
241
312
  j: 1,
242
313
  lasttime: this.lastFetchedMessages,
243
314
  },
244
315
  });
245
316
 
246
- if (!newChatMessagesResponse || typeof newChatMessagesResponse !== "object")
247
- return;
248
-
249
317
  this.lastFetchedMessages = newChatMessagesResponse["last"];
250
318
 
251
319
  newChatMessagesResponse["msgs"]
@@ -270,23 +338,21 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
270
338
  });
271
339
  }
272
340
 
273
- async fetchStatus(): Promise<ApiStatus | null> {
274
- const api = await this.fetchJson<ApiStatus>("api.php", {
275
- searchParams: { what: "status", for: `${this.#username} bot` },
341
+ async fetchStatus(): Promise<ApiStatus> {
342
+ return this.fetchJson<ApiStatus>("api.php", {
343
+ query: { what: "status", for: `${this.#username} bot` },
276
344
  });
277
-
278
- return api;
279
345
  }
280
346
 
281
347
  async fetchKmails(): Promise<KmailMessage[]> {
282
348
  const kmails = await this.fetchJson<KoLKmail[]>("api.php", {
283
- searchParams: {
349
+ query: {
284
350
  what: "kmail",
285
351
  for: `${this.#username} bot`,
286
352
  },
287
353
  });
288
354
 
289
- if (!Array.isArray(kmails) || kmails.length === 0) return [];
355
+ if (kmails.length === 0) return [];
290
356
 
291
357
  return kmails.map((msg: KoLKmail) => ({
292
358
  id: Number(msg.id),
@@ -305,7 +371,6 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
305
371
  the_action: "delete",
306
372
  box: "Inbox",
307
373
  ...Object.fromEntries(ids.map((id) => [`sel${id}`, "on"])),
308
- pwd: true,
309
374
  },
310
375
  });
311
376
 
@@ -324,7 +389,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
324
389
  return await this.fetchJson<{ output: string; msgs: string[] }>(
325
390
  "submitnewchat.php",
326
391
  {
327
- searchParams: {
392
+ query: {
328
393
  graf: message,
329
394
  j: 1,
330
395
  },
@@ -335,9 +400,9 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
335
400
  async getUpdates() {
336
401
  const result = await this.sendChat("/updates");
337
402
  return [
338
- ...(result?.output.matchAll(
403
+ ...result.output.matchAll(
339
404
  /<p><b>[A-za-z]+ \d+<\/b> - (.*?)(?=<p>(?:<b>|<hr>))/g,
340
- ) ?? []),
405
+ ),
341
406
  ].map((m) => m[1]);
342
407
  }
343
408
 
@@ -351,7 +416,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
351
416
 
352
417
  async kmail(recipientId: number, message: string) {
353
418
  await this.fetchText("sendmessage.php", {
354
- searchParams: {
419
+ query: {
355
420
  action: "send",
356
421
  j: 1,
357
422
  towho: recipientId,
@@ -366,9 +431,8 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
366
431
 
367
432
  async getMallPrice(itemId: number): Promise<MallPrice> {
368
433
  const prices = await this.fetchText("backoffice.php", {
369
- searchParams: {
434
+ query: {
370
435
  action: "prices",
371
- pwd: true,
372
436
  ajax: 1,
373
437
  iid: itemId,
374
438
  },
@@ -417,9 +481,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
417
481
  };
418
482
  }> {
419
483
  const description = await this.fetchText("desc_item.php", {
420
- searchParams: {
421
- whichitem: descId,
422
- },
484
+ query: { whichitem: descId },
423
485
  });
424
486
  const blueText = description.match(
425
487
  /<center>\s*<b>\s*<font color="?[\w]+"?>(?<description>[\s\S]+)<\/center>/i,
@@ -440,7 +502,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
440
502
  blueText: sanitiseBlueText(blueText?.groups?.description),
441
503
  effect: effect?.groups
442
504
  ? {
443
- name: effect.groups?.name,
505
+ name: effect.groups?.effect,
444
506
  duration: Number(effect.groups?.duration) || 0,
445
507
  descid: effect.groups?.descid,
446
508
  }
@@ -450,9 +512,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
450
512
 
451
513
  async getEffectDescription(descId: string): Promise<{ blueText: string }> {
452
514
  const description = await this.fetchText("desc_effect.php", {
453
- searchParams: {
454
- whicheffect: descId,
455
- },
515
+ query: { whicheffect: descId },
456
516
  });
457
517
  const blueText = description.match(
458
518
  /<center><font color="?[\w]+"?>(?<description>[\s\S]+)<\/div>/m,
@@ -462,9 +522,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
462
522
 
463
523
  async getSkillDescription(id: number): Promise<{ blueText: string }> {
464
524
  const description = await this.fetchText("desc_skill.php", {
465
- searchParams: {
466
- whichskill: String(id),
467
- },
525
+ query: { whichskill: String(id) },
468
526
  });
469
527
 
470
528
  const blueText = description.match(
@@ -475,11 +533,10 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
475
533
 
476
534
  async joinClan(id: number): Promise<boolean> {
477
535
  const result = await this.fetchText("showclan.php", {
478
- searchParams: {
536
+ query: {
479
537
  whichclan: id,
480
538
  action: "joinclan",
481
539
  confirm: "on",
482
- pwd: true,
483
540
  },
484
541
  });
485
542
  return (
@@ -492,7 +549,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
492
549
  return await this.actionMutex.runExclusive(async () => {
493
550
  if (!(await this.joinClan(clanId))) return false;
494
551
  await this.fetchText("clan_whitelist.php", {
495
- searchParams: {
552
+ query: {
496
553
  addwho: playerId,
497
554
  level: 2,
498
555
  title: "",
@@ -503,21 +560,9 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
503
560
  });
504
561
  }
505
562
 
506
- async getLeaderboard(leaderboardId: number) {
507
- const page = await this.fetchText("museum.php", {
508
- searchParams: {
509
- floor: 1,
510
- place: "leaderboards",
511
- whichboard: leaderboardId,
512
- },
513
- });
514
-
515
- return parseLeaderboard(page);
516
- }
517
-
518
563
  async useFamiliar(familiarId: number): Promise<boolean> {
519
564
  const result = await this.fetchText("familiar.php", {
520
- searchParams: {
565
+ query: {
521
566
  action: "newfam",
522
567
  newfam: familiarId.toFixed(0),
523
568
  },
@@ -554,48 +599,13 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
554
599
  if (Client.#descIdToIdCache.has(descId))
555
600
  return Client.#descIdToIdCache.get(descId)!;
556
601
  const page = await this.fetchText("desc_item.php", {
557
- searchParams: {
558
- whichitem: descId,
559
- },
602
+ query: { whichitem: descId },
560
603
  });
561
604
  const id = Number(page.match(/<!-- itemid: (\d+) -->/)?.[1] ?? -1);
562
605
  Client.#descIdToIdCache.set(descId, id);
563
606
  return id;
564
607
  }
565
608
 
566
- async getRaffle() {
567
- const page = await this.fetchText("raffle.php");
568
- const today = page.matchAll(
569
- /<tr><td align=right>(?:First|Second) Prize:<\/td>.*?descitem\((\d+)\)/g,
570
- );
571
- const [first, second] = await Promise.all(
572
- today
573
- ? [...today].map(async (p) => await this.descIdToId(Number(p[1])))
574
- : [null, null],
575
- );
576
- const winners = page.matchAll(
577
- /<tr><td class=small><a href='showplayer\.php\?who=\d+'>(.*?) \(#(\d+)\).*?descitem\((\d+)\).*?([\d,]+)<\/td><\/tr>/g,
578
- );
579
- const yesterday = await Promise.all(
580
- winners
581
- ? [...winners].map(async (w, i) => ({
582
- player: new Player(this, Number(w[2]), w[1]),
583
- item: await this.descIdToId(Number(w[3])),
584
- tickets: Number(w[4].replace(",", "")),
585
- place: Math.min(i + 1, 2),
586
- }))
587
- : [],
588
- );
589
-
590
- const { daynumber } = (await this.fetchStatus()) ?? { daynumber: "0" };
591
-
592
- return {
593
- today: { first, second },
594
- yesterday,
595
- gameday: Number(daynumber),
596
- };
597
- }
598
-
599
609
  async getStandard(date?: Date) {
600
610
  if (!date) {
601
611
  date = new Date();
@@ -607,7 +617,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
607
617
  const formattedDate = date.toISOString().split("T")[0];
608
618
 
609
619
  return await this.fetchText("standard.php", {
610
- searchParams: { date: formattedDate },
620
+ query: { date: formattedDate },
611
621
  });
612
622
  }
613
623
  }