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.
- package/package.json +10 -5
- package/src/Client.test.ts +245 -64
- package/src/Client.ts +201 -191
- package/src/LoathingDate.test.ts +202 -0
- package/src/LoathingDate.ts +390 -0
- package/src/Player.test.ts +83 -82
- package/src/Player.ts +112 -181
- package/src/domains/AutomatedFuture.test.ts +19 -0
- package/src/domains/AutomatedFuture.ts +46 -0
- package/src/domains/Bookmobile.test.ts +20 -0
- package/src/domains/Bookmobile.ts +66 -0
- package/src/domains/ClanDungeon.ts +230 -0
- package/src/domains/Dreadsylvania.test.ts +424 -0
- package/src/domains/Dreadsylvania.ts +550 -0
- package/src/domains/Familiar.ts +82 -0
- package/src/domains/FloralMercantileExchange.test.ts +20 -0
- package/src/domains/FloralMercantileExchange.ts +51 -0
- package/src/{utils/leaderboard.test.ts → domains/Leaderboard.test.ts} +24 -4
- package/src/domains/Leaderboard.ts +173 -0
- package/src/domains/Players.test.ts +141 -0
- package/src/domains/Players.ts +108 -0
- package/src/domains/Raffle.test.ts +65 -0
- package/src/domains/Raffle.ts +60 -0
- package/src/domains/SkeletonOfCrimboPast.test.ts +55 -0
- package/src/domains/SkeletonOfCrimboPast.ts +38 -0
- package/src/domains/WardrobeOMatic.test.ts +141 -0
- package/src/domains/WardrobeOMatic.ts +650 -0
- package/src/domains/__fixtures__/automated_future.html +1 -0
- package/src/domains/__fixtures__/bookmobile_spooky.html +6 -0
- package/src/domains/__fixtures__/dread/cdr1-current.html +25 -0
- package/src/domains/__fixtures__/dread/cdr1-oldlogs-page0.html +25 -0
- package/src/domains/__fixtures__/dread/cdr2-current.html +25 -0
- package/src/domains/__fixtures__/dread/cdr2-oldlogs-page0.html +25 -0
- package/src/domains/__fixtures__/dread/raid-213013.html +24 -0
- package/src/domains/__fixtures__/dread/raid-217988.html +24 -0
- package/src/domains/__fixtures__/dread/raid-218029.html +24 -0
- package/src/domains/__fixtures__/dread/raid-218205.html +24 -0
- package/src/domains/__fixtures__/dread/raid-218286.html +24 -0
- package/src/domains/__fixtures__/dread/raid-218518.html +24 -0
- package/src/domains/__fixtures__/dread/raid-218519.html +24 -0
- package/src/domains/__fixtures__/flowers.html +229 -0
- package/src/domains/__fixtures__/raidlog.html +1 -0
- package/src/domains/__fixtures__/socp.html +1 -0
- package/src/index.ts +10 -4
- package/src/stats.ts +31 -0
- package/src/utils/kmail.ts +3 -3
- package/src/utils/utils.ts +43 -0
- package/src/Cache.ts +0 -33
- package/src/utils/leaderboard.ts +0 -78
- /package/src/{utils → domains}/__fixtures__/leaderboard_wotsf.html +0 -0
- /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
|
|
3
|
-
import
|
|
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 {
|
|
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
|
|
18
|
-
import {
|
|
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:
|
|
37
|
-
whisper:
|
|
38
|
-
system:
|
|
39
|
-
public:
|
|
40
|
-
rollover:
|
|
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
|
|
87
|
+
export class Client extends Emittery<Events> {
|
|
61
88
|
actionMutex = new Mutex();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
})
|
|
163
|
-
|
|
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
|
|
208
|
+
throw new RolloverError();
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
if (!(await this.checkLoggedIn())) return false;
|
|
170
212
|
|
|
171
|
-
if (this
|
|
172
|
-
this
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
|
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
|
-
|
|
250
|
+
try {
|
|
251
|
+
const html = await this.session("login.php", { responseType: "text" });
|
|
252
|
+
const isRollover = Client.#rolloverPattern.test(html);
|
|
208
253
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
254
|
+
if (this.#isRollover && !isRollover) {
|
|
255
|
+
this.#postRolloverLatch = true;
|
|
256
|
+
}
|
|
213
257
|
|
|
214
|
-
|
|
258
|
+
this.#isRollover = isRollover;
|
|
259
|
+
} catch {
|
|
260
|
+
// Can't reach server — don't change rollover state
|
|
261
|
+
}
|
|
215
262
|
|
|
216
|
-
if (this.#isRollover) {
|
|
217
|
-
|
|
218
|
-
setTimeout(() =>
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
349
|
+
query: {
|
|
284
350
|
what: "kmail",
|
|
285
351
|
for: `${this.#username} bot`,
|
|
286
352
|
},
|
|
287
353
|
});
|
|
288
354
|
|
|
289
|
-
if (
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
620
|
+
query: { date: formattedDate },
|
|
611
621
|
});
|
|
612
622
|
}
|
|
613
623
|
}
|