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