kol.js 0.2.0 → 0.3.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 +12 -11
- package/src/Client.ts +14 -10
- package/src/index.ts +2 -2
- package/src/utils/avatar.test.ts +0 -1
- package/src/utils/kmail.test.ts +110 -0
- package/src/utils/kmail.ts +95 -0
- package/src/utils/utils.ts +2 -2
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kol.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"/src"
|
|
8
8
|
],
|
|
9
|
+
"repository": "https://github.com/loathers/oaf.git",
|
|
9
10
|
"license": "MIT",
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsc",
|
|
@@ -13,23 +14,23 @@
|
|
|
13
14
|
"format": "prettier --write ."
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
|
-
"prettier": "^3.
|
|
17
|
-
"typescript": "^5.
|
|
18
|
-
"vitest": "^
|
|
17
|
+
"prettier": "^3.8.1",
|
|
18
|
+
"typescript": "^5.9.3",
|
|
19
|
+
"vitest": "^4.0.18"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"async-mutex": "^0.5.0",
|
|
22
|
-
"css-select": "^
|
|
23
|
+
"css-select": "^6.0.0",
|
|
23
24
|
"date-fns": "^4.1.0",
|
|
24
25
|
"domhandler": "^5.0.3",
|
|
25
26
|
"domutils": "^3.2.2",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"htmlparser2": "^10.
|
|
29
|
-
"image-size": "^2.0.
|
|
27
|
+
"entities": "^7.0.1",
|
|
28
|
+
"got": "^14.6.6",
|
|
29
|
+
"htmlparser2": "^10.1.0",
|
|
30
|
+
"image-size": "^2.0.2",
|
|
30
31
|
"querystring": "^0.2.1",
|
|
31
|
-
"tough-cookie": "^
|
|
32
|
+
"tough-cookie": "^6.0.0",
|
|
32
33
|
"ts-dedent": "^2.2.0",
|
|
33
34
|
"typed-emitter": "^2.1.0"
|
|
34
35
|
}
|
|
35
|
-
}
|
|
36
|
+
}
|
package/src/Client.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { Mutex } from "async-mutex";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import TypedEventEmitter, { EventMap } from "typed-emitter";
|
|
3
|
+
import TypedEventEmitter, { type EventMap } from "typed-emitter";
|
|
4
4
|
|
|
5
5
|
import { sanitiseBlueText, wait } from "./utils/utils.js";
|
|
6
6
|
import { Player } from "./Player.js";
|
|
7
7
|
import { parseLeaderboard } from "./utils/leaderboard.js";
|
|
8
8
|
import {
|
|
9
|
-
ChatMessage,
|
|
10
|
-
KmailMessage,
|
|
11
|
-
KoLChatMessage,
|
|
12
|
-
KoLKmail,
|
|
13
|
-
KoLMessage,
|
|
9
|
+
type ChatMessage,
|
|
10
|
+
type KmailMessage,
|
|
11
|
+
type KoLChatMessage,
|
|
12
|
+
type KoLKmail,
|
|
13
|
+
type KoLMessage,
|
|
14
14
|
isValidMessage,
|
|
15
|
+
parseKmailMessage,
|
|
15
16
|
} from "./utils/kmail.js";
|
|
16
17
|
import { PlayerCache } from "./Cache.js";
|
|
17
18
|
import { CookieJar } from "tough-cookie";
|
|
18
|
-
import got, {
|
|
19
|
+
import got, {
|
|
20
|
+
type OptionsOfJSONResponseBody,
|
|
21
|
+
type OptionsOfTextResponseBody,
|
|
22
|
+
} from "got";
|
|
19
23
|
|
|
20
24
|
type TypedEmitter<T extends EventMap> = TypedEventEmitter.default<T>;
|
|
21
25
|
|
|
22
|
-
type MallPrice = {
|
|
26
|
+
export type MallPrice = {
|
|
23
27
|
formattedMallPrice: string;
|
|
24
28
|
formattedLimitedMallPrice: string;
|
|
25
29
|
formattedMinPrice: string;
|
|
@@ -29,7 +33,7 @@ type MallPrice = {
|
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
type Events = {
|
|
32
|
-
kmail: (message:
|
|
36
|
+
kmail: (message: KmailMessage) => void;
|
|
33
37
|
whisper: (message: KoLMessage) => void;
|
|
34
38
|
system: (message: KoLMessage) => void;
|
|
35
39
|
public: (message: KoLMessage) => void;
|
|
@@ -288,8 +292,8 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter<E
|
|
|
288
292
|
id: Number(msg.id),
|
|
289
293
|
type: "kmail" as const,
|
|
290
294
|
who: new Player(this, Number(msg.fromid), msg.fromname),
|
|
291
|
-
msg: msg.message,
|
|
292
295
|
time: new Date(Number(msg.azunixtime) * 1000),
|
|
296
|
+
...parseKmailMessage(msg.message, msg.type),
|
|
293
297
|
}));
|
|
294
298
|
}
|
|
295
299
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Player } from "./Player.js";
|
|
2
|
-
export { Client } from "./Client.js";
|
|
2
|
+
export { Client, type MallPrice } from "./Client.js";
|
|
3
3
|
|
|
4
4
|
export { resolveKoLImage } from "./utils/utils.js";
|
|
5
|
-
export type { KoLMessage } from "./utils/kmail.js";
|
|
5
|
+
export type { KmailItem, KoLMessage } from "./utils/kmail.js";
|
|
6
6
|
export type { SubboardInfo } from "./utils/leaderboard.js";
|
package/src/utils/avatar.test.ts
CHANGED
|
@@ -13,7 +13,6 @@ describe("Avatars", () => {
|
|
|
13
13
|
it("can parse an avatar with a broken url", async () => {
|
|
14
14
|
const page = await loadFixture(__dirname, "showplayer_broken_avatar.html");
|
|
15
15
|
const svg = await generateAvatarSvg(page);
|
|
16
|
-
console.log(svg);
|
|
17
16
|
expect(svg).toContain(`title="/adventureimages/nopic.gif"`);
|
|
18
17
|
});
|
|
19
18
|
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { parseKmailMessage } from "./kmail.js";
|
|
3
|
+
|
|
4
|
+
describe("Kmail parsing", () => {
|
|
5
|
+
it("strips a valentine", () => {
|
|
6
|
+
const result = parseKmailMessage(
|
|
7
|
+
"<center><table><tr><td><img src=\"https://d2uyhvukfffg5a.cloudfront.net/adventureimages/smiley.gif\" width=100 height=100></td><td valign=center>You zerg rush'd my heart<br>Your love gets me high<br>Come give me a kiss<br>You're oh em gee KAWAIIIIIIII!!11!!11!!!?!!?!</td></tr></table></center>",
|
|
8
|
+
"normal",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
expect(result.msg).toBe("");
|
|
12
|
+
expect(result.valentine).toBe("smiley");
|
|
13
|
+
expect(result.items).toEqual([]);
|
|
14
|
+
expect(result.meat).toBe(0);
|
|
15
|
+
expect(result.insideNote).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("parses a kmail with items and a message", () => {
|
|
19
|
+
const result = parseKmailMessage(
|
|
20
|
+
'Enjoy!<center><table class="item" style="float: none" rel="id=641&s=14&q=0&d=1&g=0&t=1&n=1&m=0&p=0&u=e"><tr><td><img src="https://d2uyhvukfffg5a.cloudfront.net/itemimages/toast.gif" alt="toast" title="toast" class=hand onClick=\'descitem(931984879)\' ></td><td valign=center class=effect>You acquire an item: <b>toast</b></td></tr></table></center>',
|
|
21
|
+
"normal",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(result.msg).toBe("Enjoy!");
|
|
25
|
+
expect(result.valentine).toBeNull();
|
|
26
|
+
expect(result.items).toEqual([
|
|
27
|
+
{ id: 641, name: "toast", quantity: 1, descid: "931984879" },
|
|
28
|
+
]);
|
|
29
|
+
expect(result.meat).toBe(0);
|
|
30
|
+
expect(result.insideNote).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("parses a kmail with items but no message", () => {
|
|
34
|
+
const result = parseKmailMessage(
|
|
35
|
+
'<center><table class="item" style="float: none" rel="id=6863&s=1&q=0&d=1&g=0&t=1&n=2&m=0&p=0&u=e"><tr><td><img src="https://d2uyhvukfffg5a.cloudfront.net/itemimages/butterpat.gif" alt="pat of butter" title="pat of butter" class=hand onClick=\'descitem(310457727)\' ></td><td valign=center class=effect>You acquire <b>2 pats of butter</b></td></tr></table></center>',
|
|
36
|
+
"normal",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result.msg).toBe("");
|
|
40
|
+
expect(result.valentine).toBeNull();
|
|
41
|
+
expect(result.items).toEqual([
|
|
42
|
+
{
|
|
43
|
+
id: 6863,
|
|
44
|
+
name: "pat of butter",
|
|
45
|
+
quantity: 2,
|
|
46
|
+
descid: "310457727",
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
expect(result.meat).toBe(0);
|
|
50
|
+
expect(result.insideNote).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("parses a kmail with meat", () => {
|
|
54
|
+
const result = parseKmailMessage(
|
|
55
|
+
"<center>You acquire <b>5,000</b> Meat.<br>You gain 5,000 Meat</center>",
|
|
56
|
+
"normal",
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result.msg).toBe("");
|
|
60
|
+
expect(result.items).toEqual([]);
|
|
61
|
+
expect(result.meat).toBe(5000);
|
|
62
|
+
expect(result.insideNote).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("parses a kmail with items and meat", () => {
|
|
66
|
+
const result = parseKmailMessage(
|
|
67
|
+
'Here you go<center><table class="item" style="float: none" rel="id=641&s=14&q=0&d=1&g=0&t=1&n=1&m=0&p=0&u=e"><tr><td><img src="https://d2uyhvukfffg5a.cloudfront.net/itemimages/toast.gif" alt="toast" title="toast" class=hand onClick=\'descitem(931984879)\' ></td><td valign=center class=effect>You acquire an item: <b>toast</b></td></tr></table></center><center>You gain 1,000 Meat</center>',
|
|
68
|
+
"normal",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(result.msg).toBe("Here you go");
|
|
72
|
+
expect(result.items).toEqual([
|
|
73
|
+
{ id: 641, name: "toast", quantity: 1, descid: "931984879" },
|
|
74
|
+
]);
|
|
75
|
+
expect(result.meat).toBe(1000);
|
|
76
|
+
expect(result.insideNote).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("parses a plain text kmail", () => {
|
|
80
|
+
const result = parseKmailMessage("Hello there!", "normal");
|
|
81
|
+
|
|
82
|
+
expect(result.msg).toBe("Hello there!");
|
|
83
|
+
expect(result.valentine).toBeNull();
|
|
84
|
+
expect(result.items).toEqual([]);
|
|
85
|
+
expect(result.meat).toBe(0);
|
|
86
|
+
expect(result.insideNote).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("parses a gift shop kmail", () => {
|
|
90
|
+
const result = parseKmailMessage(
|
|
91
|
+
"Thanks for everything!<p>Inside Note:<p>Hope you enjoy this!",
|
|
92
|
+
"giftshop",
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(result.msg).toBe("Thanks for everything!");
|
|
96
|
+
expect(result.kmailType).toBe("giftshop");
|
|
97
|
+
expect(result.insideNote).toBe("Hope you enjoy this!");
|
|
98
|
+
expect(result.items).toEqual([]);
|
|
99
|
+
expect(result.meat).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("decodes HTML entities", () => {
|
|
103
|
+
const result = parseKmailMessage(
|
|
104
|
+
"That's a "great" idea & I love it",
|
|
105
|
+
"normal",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result.msg).toBe(`That's a "great" idea & I love it`);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/utils/kmail.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
import { selectAll, selectOne } from "css-select";
|
|
2
|
+
import { type AnyNode, Element } from "domhandler";
|
|
3
|
+
import { decodeHTML } from "entities";
|
|
4
|
+
import { parseDocument } from "htmlparser2";
|
|
5
|
+
|
|
1
6
|
import { Player } from "../Player.js";
|
|
2
7
|
|
|
8
|
+
export type KmailItem = {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
quantity: number;
|
|
12
|
+
descid: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
3
15
|
export type KoLChatMessage = {
|
|
4
16
|
who?: Player<false>;
|
|
5
17
|
type?: string;
|
|
@@ -39,6 +51,89 @@ export type BaseKoLMessage = {
|
|
|
39
51
|
export interface KmailMessage extends BaseKoLMessage {
|
|
40
52
|
type: "kmail";
|
|
41
53
|
id: number;
|
|
54
|
+
kmailType: "normal" | "giftshop";
|
|
55
|
+
valentine: string | null;
|
|
56
|
+
items: KmailItem[];
|
|
57
|
+
meat: number;
|
|
58
|
+
insideNote: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extractItemsFromHtml(html: string): KmailItem[] {
|
|
62
|
+
const doc = parseDocument(html);
|
|
63
|
+
const tables = selectAll<AnyNode, Element>("table.item", doc);
|
|
64
|
+
|
|
65
|
+
return tables.map((table) => {
|
|
66
|
+
const rel = table.attribs.rel ?? "";
|
|
67
|
+
const params = new URLSearchParams(rel);
|
|
68
|
+
|
|
69
|
+
const img = selectOne<AnyNode, Element>("img", table);
|
|
70
|
+
const name = img?.attribs.title ?? "";
|
|
71
|
+
const onClick = img?.attribs.onClick ?? img?.attribs.onclick ?? "";
|
|
72
|
+
const descMatch = onClick.match(/descitem\((\d+)\)/);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
id: Number(params.get("id") ?? 0),
|
|
76
|
+
name,
|
|
77
|
+
quantity: Number(params.get("n") ?? 1),
|
|
78
|
+
descid: descMatch?.[1] ?? "",
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function extractMeatFromHtml(html: string): number {
|
|
84
|
+
const match = html.match(/You (?:gain|acquire) ([\d,]+) Meat/);
|
|
85
|
+
if (!match) return 0;
|
|
86
|
+
return Number(match[1].replace(/,/g, ""));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseKmailMessage(
|
|
90
|
+
rawMessage: string,
|
|
91
|
+
type: string,
|
|
92
|
+
): {
|
|
93
|
+
msg: string;
|
|
94
|
+
kmailType: "normal" | "giftshop";
|
|
95
|
+
valentine: string | null;
|
|
96
|
+
items: KmailItem[];
|
|
97
|
+
meat: number;
|
|
98
|
+
insideNote: string | null;
|
|
99
|
+
} {
|
|
100
|
+
let text = rawMessage;
|
|
101
|
+
let insideText: string | undefined;
|
|
102
|
+
let valentine: string | null = null;
|
|
103
|
+
const kmailType = type as "normal" | "giftshop";
|
|
104
|
+
|
|
105
|
+
if (type === "normal") {
|
|
106
|
+
if (text.startsWith("<center><table>")) {
|
|
107
|
+
const endIdx = text.indexOf("</center>");
|
|
108
|
+
const header = text.slice(0, endIdx);
|
|
109
|
+
const imgMatch = header.match(/adventureimages\/(\w+)\.\w+/);
|
|
110
|
+
valentine = imgMatch?.[1] ?? "unknown";
|
|
111
|
+
text = text.slice(endIdx + 9);
|
|
112
|
+
}
|
|
113
|
+
} else if (type === "giftshop") {
|
|
114
|
+
[text, insideText] = text.split("<p>Inside Note:<p>");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const split = (s: string): [string, string | null] => {
|
|
118
|
+
const idx = s.indexOf("<");
|
|
119
|
+
if (idx === -1) return [s, null];
|
|
120
|
+
return [s.slice(0, idx), s.slice(idx)];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const [outsideNote, outsideAttachments] = split(text);
|
|
124
|
+
const [insideNote, insideAttachments] =
|
|
125
|
+
insideText !== undefined ? split(insideText) : [null, null];
|
|
126
|
+
|
|
127
|
+
const allAttachments = `${outsideAttachments ?? ""}${insideAttachments ?? ""}`;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
msg: decodeHTML(outsideNote),
|
|
131
|
+
kmailType,
|
|
132
|
+
valentine,
|
|
133
|
+
items: allAttachments ? extractItemsFromHtml(allAttachments) : [],
|
|
134
|
+
meat: allAttachments ? extractMeatFromHtml(allAttachments) : 0,
|
|
135
|
+
insideNote: insideNote !== null ? decodeHTML(insideNote) : null,
|
|
136
|
+
};
|
|
42
137
|
}
|
|
43
138
|
|
|
44
139
|
export interface ChatMessage extends BaseKoLMessage {
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parse as parseDate } from "date-fns";
|
|
2
|
-
import {
|
|
2
|
+
import { decodeHTML } from "entities";
|
|
3
3
|
|
|
4
4
|
export function parsePlayerDate(input?: string) {
|
|
5
5
|
if (!input) return new Date();
|
|
@@ -8,7 +8,7 @@ export function parsePlayerDate(input?: string) {
|
|
|
8
8
|
|
|
9
9
|
export function sanitiseBlueText(blueText: string | undefined): string {
|
|
10
10
|
if (!blueText) return "";
|
|
11
|
-
return
|
|
11
|
+
return decodeHTML(
|
|
12
12
|
blueText
|
|
13
13
|
.replace(/\r/g, "")
|
|
14
14
|
.replace(/\r/g, "")
|