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.
- package/package.json +14 -9
- package/src/Client.test.ts +245 -64
- package/src/Client.ts +204 -193
- 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 +11 -5
- package/src/stats.ts +31 -0
- package/src/utils/kmail.test.ts +110 -0
- package/src/utils/kmail.ts +98 -3
- package/src/utils/utils.ts +45 -2
- 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,
|
|
@@ -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
|
|
17
|
-
import {
|
|
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:
|
|
36
|
-
whisper:
|
|
37
|
-
system:
|
|
38
|
-
public:
|
|
39
|
-
rollover:
|
|
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
|
|
87
|
+
export class Client extends Emittery<Events> {
|
|
60
88
|
actionMutex = new Mutex();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
162
|
-
|
|
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
|
|
208
|
+
throw new RolloverError();
|
|
166
209
|
}
|
|
167
210
|
|
|
168
211
|
if (!(await this.checkLoggedIn())) return false;
|
|
169
212
|
|
|
170
|
-
if (this
|
|
171
|
-
this
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
|
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
|
-
|
|
250
|
+
try {
|
|
251
|
+
const html = await this.session("login.php", { responseType: "text" });
|
|
252
|
+
const isRollover = Client.#rolloverPattern.test(html);
|
|
207
253
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
254
|
+
if (this.#isRollover && !isRollover) {
|
|
255
|
+
this.#postRolloverLatch = true;
|
|
256
|
+
}
|
|
212
257
|
|
|
213
|
-
|
|
258
|
+
this.#isRollover = isRollover;
|
|
259
|
+
} catch {
|
|
260
|
+
// Can't reach server — don't change rollover state
|
|
261
|
+
}
|
|
214
262
|
|
|
215
|
-
if (this.#isRollover) {
|
|
216
|
-
|
|
217
|
-
setTimeout(() =>
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
349
|
+
query: {
|
|
283
350
|
what: "kmail",
|
|
284
351
|
for: `${this.#username} bot`,
|
|
285
352
|
},
|
|
286
353
|
});
|
|
287
354
|
|
|
288
|
-
if (
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
620
|
+
query: { date: formattedDate },
|
|
610
621
|
});
|
|
611
622
|
}
|
|
612
623
|
}
|