kol.js 0.0.2 → 0.1.1
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 +7 -6
- package/src/Cache.ts +33 -0
- package/src/Client.test.ts +206 -0
- package/src/Client.ts +506 -0
- package/src/Player.test.ts +101 -0
- package/src/Player.ts +209 -0
- package/src/__fixtures__/backoffice_prices_alien_meat.html +46 -0
- package/src/__fixtures__/backoffice_prices_lov_elephant.html +30 -0
- package/src/__fixtures__/backoffice_prices_magical_mystery_juice.html +30 -0
- package/src/__fixtures__/backoffice_prices_tofurkey_leg.html +48 -0
- package/src/__fixtures__/backoffice_prices_turtle_wax_greaves.html +42 -0
- package/src/__fixtures__/backoffice_prices_turtle_wax_helmet.html +42 -0
- package/src/__fixtures__/backoffice_prices_turtle_wax_shield.html +47 -0
- package/src/__fixtures__/desc_effect_pasta_oneness.html +31 -0
- package/src/__fixtures__/desc_effect_the_visible_adventurer.html +31 -0
- package/src/__fixtures__/desc_item_alien_meat.html +38 -0
- package/src/__fixtures__/desc_item_hypodermic_needle.html +39 -0
- package/src/__fixtures__/desc_item_lov_elephant.html +39 -0
- package/src/__fixtures__/desc_item_magical_mystery_juice.html +39 -0
- package/src/__fixtures__/desc_item_mosquito_larva.html +39 -0
- package/src/__fixtures__/desc_item_tofurkey_leg.html +38 -0
- package/src/__fixtures__/desc_item_turtle_wax_shield.html +79 -0
- package/src/__fixtures__/desc_skill_impetuous_sauciness.html +32 -0
- package/src/__fixtures__/desc_skill_overload_discarded_refridgerator.html +32 -0
- package/src/__fixtures__/familiar_in_standard_run.html +184 -0
- package/src/__fixtures__/searchplayer_mad_carew.html +53 -0
- package/src/__fixtures__/showplayer_dependence_day.html +47 -0
- package/src/__fixtures__/showplayer_golden_gun.html +28 -0
- package/src/__fixtures__/showplayer_regular.html +56 -0
- package/{dist/index.d.ts → src/index.ts} +1 -0
- package/src/testUtils.ts +13 -0
- package/src/utils/avatar.ts +90 -0
- package/src/utils/kmail.ts +48 -0
- package/src/utils/leaderboard.ts +84 -0
- package/src/utils/utils.ts +33 -0
- package/dist/Cache.d.ts +0 -11
- package/dist/Cache.js +0 -26
- package/dist/Client.d.ts +0 -84
- package/dist/Client.js +0 -343
- package/dist/Client.test.d.ts +0 -1
- package/dist/Client.test.js +0 -162
- package/dist/Player.d.ts +0 -28
- package/dist/Player.js +0 -136
- package/dist/Player.test.d.ts +0 -1
- package/dist/Player.test.js +0 -48
- package/dist/index.js +0 -3
- package/dist/testUtils.d.ts +0 -2
- package/dist/testUtils.js +0 -10
- package/dist/utils/avatar.d.ts +0 -1
- package/dist/utils/avatar.js +0 -70
- package/dist/utils/kmail.d.ts +0 -32
- package/dist/utils/kmail.js +0 -1
- package/dist/utils/leaderboard.d.ts +0 -16
- package/dist/utils/leaderboard.js +0 -56
- package/dist/utils/utils.d.ts +0 -4
- package/dist/utils/utils.js +0 -27
- package/dist/utils/visit.d.ts +0 -3
- package/dist/utils/visit.js +0 -10
package/dist/Client.test.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { Client } from "./Client.js";
|
|
3
|
-
import { loadFixture } from "./testUtils.js";
|
|
4
|
-
const { json, text } = vi.hoisted(() => ({ json: vi.fn(), text: vi.fn() }));
|
|
5
|
-
vi.mock("./Client.js", async (importOriginal) => {
|
|
6
|
-
const client = await importOriginal();
|
|
7
|
-
client.Client.prototype.login = async () => true;
|
|
8
|
-
client.Client.prototype.checkLoggedIn = async () => true;
|
|
9
|
-
client.Client.prototype.fetchText = text;
|
|
10
|
-
client.Client.prototype.fetchJson = json;
|
|
11
|
-
return client;
|
|
12
|
-
});
|
|
13
|
-
const client = new Client("", "");
|
|
14
|
-
describe("LoathingChat", () => {
|
|
15
|
-
test("Can parse a regular message", async () => {
|
|
16
|
-
json.mockResolvedValue({});
|
|
17
|
-
json.mockResolvedValueOnce({
|
|
18
|
-
msgs: [
|
|
19
|
-
{
|
|
20
|
-
msg: "testing",
|
|
21
|
-
type: "public",
|
|
22
|
-
mid: "1538072797",
|
|
23
|
-
who: { name: "gAUSIE", id: "1197090", color: "black" },
|
|
24
|
-
format: "0",
|
|
25
|
-
channel: "talkie",
|
|
26
|
-
channelcolor: "green",
|
|
27
|
-
time: "1698787642",
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
const messageSpy = vi.fn();
|
|
32
|
-
client.on("public", messageSpy);
|
|
33
|
-
await client.checkMessages();
|
|
34
|
-
expect(messageSpy).toHaveBeenCalledOnce();
|
|
35
|
-
const [message] = messageSpy.mock.calls[0];
|
|
36
|
-
expect(message).toMatchObject({
|
|
37
|
-
type: "public",
|
|
38
|
-
msg: "testing",
|
|
39
|
-
time: new Date(1698787642000),
|
|
40
|
-
who: { id: 1197090, name: "gAUSIE" },
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
test("Can parse a system message for rollover in 5 minutes", async () => {
|
|
44
|
-
json.mockResolvedValueOnce({
|
|
45
|
-
msgs: [
|
|
46
|
-
{
|
|
47
|
-
msg: "The system will go down for nightly maintenance in 5 minutes.",
|
|
48
|
-
type: "system",
|
|
49
|
-
mid: "1538084998",
|
|
50
|
-
who: { name: "System Message", id: "-1", color: "" },
|
|
51
|
-
format: "2",
|
|
52
|
-
channelcolor: "green",
|
|
53
|
-
time: "1698809101",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
});
|
|
57
|
-
const messageSpy = vi.fn();
|
|
58
|
-
client.on("system", messageSpy);
|
|
59
|
-
await client.checkMessages();
|
|
60
|
-
expect(messageSpy).toHaveBeenCalledOnce();
|
|
61
|
-
const [message] = messageSpy.mock.calls[0];
|
|
62
|
-
expect(message).toMatchObject({
|
|
63
|
-
type: "system",
|
|
64
|
-
who: { id: -1, name: "System Message" },
|
|
65
|
-
msg: "The system will go down for nightly maintenance in 5 minutes.",
|
|
66
|
-
time: new Date(1698809101000),
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
test("Can parse a system message for rollover in one minute", async () => {
|
|
70
|
-
json.mockResolvedValueOnce({
|
|
71
|
-
msgs: [
|
|
72
|
-
{
|
|
73
|
-
msg: "The system will go down for nightly maintenance in 1 minute.",
|
|
74
|
-
type: "system",
|
|
75
|
-
mid: "1538084998",
|
|
76
|
-
who: { name: "System Message", id: "-1", color: "" },
|
|
77
|
-
format: "2",
|
|
78
|
-
channelcolor: "green",
|
|
79
|
-
time: "1698809101",
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
});
|
|
83
|
-
const messageSpy = vi.fn();
|
|
84
|
-
client.on("system", messageSpy);
|
|
85
|
-
await client.checkMessages();
|
|
86
|
-
expect(messageSpy).toHaveBeenCalledOnce();
|
|
87
|
-
const [message] = messageSpy.mock.calls[0];
|
|
88
|
-
expect(message).toMatchObject({
|
|
89
|
-
type: "system",
|
|
90
|
-
who: { id: -1, name: "System Message" },
|
|
91
|
-
msg: "The system will go down for nightly maintenance in 1 minute.",
|
|
92
|
-
time: new Date(1698809101000),
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
test("Can parse a system message for rollover complete", async () => {
|
|
96
|
-
json.mockResolvedValueOnce({
|
|
97
|
-
msgs: [
|
|
98
|
-
{
|
|
99
|
-
msg: "Rollover is over.",
|
|
100
|
-
type: "system",
|
|
101
|
-
mid: "1538085619",
|
|
102
|
-
who: { name: "System Message", id: "-1", color: "" },
|
|
103
|
-
format: "2",
|
|
104
|
-
channelcolor: "green",
|
|
105
|
-
time: "1698809633",
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
});
|
|
109
|
-
const messageSpy = vi.fn();
|
|
110
|
-
client.on("system", messageSpy);
|
|
111
|
-
await client.checkMessages();
|
|
112
|
-
expect(messageSpy).toHaveBeenCalledOnce();
|
|
113
|
-
const [message] = messageSpy.mock.calls[0];
|
|
114
|
-
expect(message).toMatchObject({
|
|
115
|
-
type: "system",
|
|
116
|
-
who: { id: -1, name: "System Message" },
|
|
117
|
-
msg: "Rollover is over.",
|
|
118
|
-
time: new Date(1698809633000),
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe("Skill descriptions", () => {
|
|
123
|
-
test("can describe a Skill with no bluetext", async () => {
|
|
124
|
-
text.mockResolvedValueOnce(await loadFixture(__dirname, "desc_skill_overload_discarded_refridgerator.html"));
|
|
125
|
-
const description = await client.getSkillDescription(7017);
|
|
126
|
-
expect(description).toStrictEqual({
|
|
127
|
-
blueText: "",
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
test("can describe a Skill with bluetext", async () => {
|
|
131
|
-
text.mockResolvedValueOnce(await loadFixture(__dirname, "desc_skill_impetuous_sauciness.html"));
|
|
132
|
-
const description = await client.getSkillDescription(4015);
|
|
133
|
-
expect(description).toStrictEqual({
|
|
134
|
-
blueText: "Makes Sauce Potions last longer",
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
describe("Familiars", () => {
|
|
139
|
-
test("can fetch all familiars", async () => {
|
|
140
|
-
text.mockResolvedValueOnce(await loadFixture(__dirname, "familiar_in_standard_run.html"));
|
|
141
|
-
const familiars = await client.getFamiliars();
|
|
142
|
-
// Current
|
|
143
|
-
expect(familiars).toContainEqual({
|
|
144
|
-
id: 294,
|
|
145
|
-
name: "Jill-of-All-Trades",
|
|
146
|
-
image: "itemimages/darkjill2f.gif",
|
|
147
|
-
});
|
|
148
|
-
// Problematic
|
|
149
|
-
expect(familiars).toContainEqual({
|
|
150
|
-
id: 278,
|
|
151
|
-
name: "Left-Hand Man",
|
|
152
|
-
image: "otherimages/righthandbody.png",
|
|
153
|
-
});
|
|
154
|
-
expect(familiars).toContainEqual({
|
|
155
|
-
id: 279,
|
|
156
|
-
name: "Melodramedary",
|
|
157
|
-
image: "otherimages/camelfam_left.gif",
|
|
158
|
-
});
|
|
159
|
-
// Just to be sure
|
|
160
|
-
expect(familiars).toHaveLength(206);
|
|
161
|
-
});
|
|
162
|
-
});
|
package/dist/Player.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Client } from "./Client.js";
|
|
2
|
-
export type If<Value extends boolean, TrueResult, FalseResult = null> = Value extends true ? TrueResult : Value extends false ? FalseResult : TrueResult | FalseResult;
|
|
3
|
-
export declare class Player<IsFull extends boolean = boolean> {
|
|
4
|
-
#private;
|
|
5
|
-
id: number;
|
|
6
|
-
name: string;
|
|
7
|
-
level: If<IsFull, number>;
|
|
8
|
-
kolClass: If<IsFull, string>;
|
|
9
|
-
avatar: If<IsFull, string>;
|
|
10
|
-
ascensions: If<IsFull, number>;
|
|
11
|
-
trophies: If<IsFull, number>;
|
|
12
|
-
tattoos: If<IsFull, number>;
|
|
13
|
-
favoriteFood: If<IsFull, string | null>;
|
|
14
|
-
favoriteBooze: If<IsFull, string | null>;
|
|
15
|
-
createdDate: If<IsFull, Date>;
|
|
16
|
-
lastLogin: If<IsFull, Date>;
|
|
17
|
-
hasDisplayCase: If<IsFull, boolean>;
|
|
18
|
-
constructor(client: Client, id: number, name: string, level?: number | null, kolClass?: string | null);
|
|
19
|
-
isFull(): this is Player<true>;
|
|
20
|
-
isPartial(): this is Player<false>;
|
|
21
|
-
static getNameFromId(client: Client, id: number): Promise<string | null>;
|
|
22
|
-
static fromName(client: Client, name: string): Promise<Player<false> | null>;
|
|
23
|
-
static fromId(client: Client, id: number): Promise<Player<false> | null>;
|
|
24
|
-
static from(client: Client, identifier: string | number): Promise<Player<false> | null>;
|
|
25
|
-
matchesIdentifier(identifier: string | number): boolean;
|
|
26
|
-
full(): Promise<Player<true> | null>;
|
|
27
|
-
isOnline(): Promise<boolean>;
|
|
28
|
-
}
|
package/dist/Player.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { generateAvatarSvg } from "./utils/avatar.js";
|
|
2
|
-
import { parsePlayerDate } from "./utils/utils.js";
|
|
3
|
-
export class Player {
|
|
4
|
-
#client;
|
|
5
|
-
id;
|
|
6
|
-
name;
|
|
7
|
-
level;
|
|
8
|
-
kolClass;
|
|
9
|
-
avatar;
|
|
10
|
-
ascensions;
|
|
11
|
-
trophies;
|
|
12
|
-
tattoos;
|
|
13
|
-
favoriteFood;
|
|
14
|
-
favoriteBooze;
|
|
15
|
-
createdDate;
|
|
16
|
-
lastLogin;
|
|
17
|
-
hasDisplayCase;
|
|
18
|
-
constructor(client, id, name, level = null, kolClass = null) {
|
|
19
|
-
this.#client = client;
|
|
20
|
-
this.id = id;
|
|
21
|
-
this.name = name;
|
|
22
|
-
this.level = level;
|
|
23
|
-
this.kolClass = kolClass;
|
|
24
|
-
this.avatar = null;
|
|
25
|
-
this.ascensions = null;
|
|
26
|
-
this.trophies = null;
|
|
27
|
-
this.tattoos = null;
|
|
28
|
-
this.favoriteFood = null;
|
|
29
|
-
this.favoriteBooze = null;
|
|
30
|
-
this.createdDate = null;
|
|
31
|
-
this.lastLogin = null;
|
|
32
|
-
this.hasDisplayCase = null;
|
|
33
|
-
}
|
|
34
|
-
isFull() {
|
|
35
|
-
return this.createdDate !== null;
|
|
36
|
-
}
|
|
37
|
-
isPartial() {
|
|
38
|
-
return this.createdDate === null;
|
|
39
|
-
}
|
|
40
|
-
static async getNameFromId(client, id) {
|
|
41
|
-
try {
|
|
42
|
-
const profile = await client.fetchText("showplayer.php", {
|
|
43
|
-
params: { who: id },
|
|
44
|
-
});
|
|
45
|
-
const name = profile.match(/<b>([^>]*?)<\/b> \(#(\d+)\)<br>/)?.[1];
|
|
46
|
-
return name || null;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
static async fromName(client, name) {
|
|
53
|
-
try {
|
|
54
|
-
const matcher = /href="showplayer.php\?who=(?<playerId>\d+)">(?<playerName>.*?)<\/a>\D+(clan=\d+[^<]+\D+)?\d+\D*(?<level>(\d+)|(inf_large\.gif))\D+valign=top>(?<class>[^<]*)<\/td>/i;
|
|
55
|
-
const search = await client.fetchText("searchplayer.php", {
|
|
56
|
-
params: {
|
|
57
|
-
searchstring: name.replace(/_/g, "\\_"),
|
|
58
|
-
searching: "Yep.",
|
|
59
|
-
for: "",
|
|
60
|
-
startswith: 1,
|
|
61
|
-
hardcoreonly: 0,
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
const match = matcher.exec(search)?.groups;
|
|
65
|
-
if (!match) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
return new Player(client, Number(match.playerId), match.playerName, parseInt(match.level), match.class);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
static async fromId(client, id) {
|
|
75
|
-
const name = await Player.getNameFromId(client, id);
|
|
76
|
-
if (!name)
|
|
77
|
-
return null;
|
|
78
|
-
return await Player.fromName(client, name);
|
|
79
|
-
}
|
|
80
|
-
static async from(client, identifier) {
|
|
81
|
-
const id = Number(identifier);
|
|
82
|
-
if (!Number.isNaN(id) || typeof identifier === "number") {
|
|
83
|
-
return await Player.fromId(client, id);
|
|
84
|
-
}
|
|
85
|
-
return await Player.fromName(client, identifier);
|
|
86
|
-
}
|
|
87
|
-
matchesIdentifier(identifier) {
|
|
88
|
-
const id = Number(identifier);
|
|
89
|
-
if (!Number.isNaN(id) || typeof identifier === "number") {
|
|
90
|
-
return this.id === identifier;
|
|
91
|
-
}
|
|
92
|
-
return this.name.toLowerCase() === identifier.toLowerCase();
|
|
93
|
-
}
|
|
94
|
-
async full() {
|
|
95
|
-
const t = this;
|
|
96
|
-
if (this.isFull())
|
|
97
|
-
return this;
|
|
98
|
-
try {
|
|
99
|
-
const profile = await this.#client.fetchText("showplayer.php", {
|
|
100
|
-
params: {
|
|
101
|
-
who: this.id,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
const header = profile.match(/<center><table><tr><td><center>.*?<img.*?src="(.*?)".*?<b>([^>]*?)<\/b> \(#(\d+)\)<br>/);
|
|
105
|
-
if (!header)
|
|
106
|
-
return null;
|
|
107
|
-
t.avatar = (await generateAvatarSvg(profile)) || header[1];
|
|
108
|
-
t.ascensions =
|
|
109
|
-
Number(profile
|
|
110
|
-
.match(/>Ascensions<\/a>:<\/b><\/td><td>(.*?)<\/td>/)?.[1]
|
|
111
|
-
?.replace(/,/g, "")) || 0;
|
|
112
|
-
t.trophies = Number(profile.match(/>Trophies Collected:<\/b><\/td><td>(.*?)<\/td>/)?.[1] ??
|
|
113
|
-
0);
|
|
114
|
-
t.tattoos = Number(profile.match(/>Tattoos Collected:<\/b><\/td><td>(.*?)<\/td>/)?.[1] ??
|
|
115
|
-
0);
|
|
116
|
-
t.favoriteFood =
|
|
117
|
-
profile.match(/>Favorite Food:<\/b><\/td><td>(.*?)<\/td>/)?.[1] ?? null;
|
|
118
|
-
t.favoriteBooze =
|
|
119
|
-
profile.match(/>Favorite Booze:<\/b><\/td><td>(.*?)<\/td>/)?.[1] ??
|
|
120
|
-
null;
|
|
121
|
-
t.createdDate = parsePlayerDate(profile.match(/>Account Created:<\/b><\/td><td>(.*?)<\/td>/)?.[1]);
|
|
122
|
-
t.lastLogin = parsePlayerDate(profile.match(/>Last Login:<\/b><\/td><td>(.*?)<\/td>/)?.[1]);
|
|
123
|
-
t.hasDisplayCase =
|
|
124
|
-
profile.match(/Display Case<\/b><\/a> in the Museum<\/td>/) !== null;
|
|
125
|
-
return t;
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
console.error(error);
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async isOnline() {
|
|
133
|
-
const response = await this.#client.useChatMacro(`/whois ${this.name}`);
|
|
134
|
-
return (response?.output.includes("This player is currently online") ?? false);
|
|
135
|
-
}
|
|
136
|
-
}
|
package/dist/Player.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/Player.test.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { resolveKoLImage } from "./utils/utils.js";
|
|
3
|
-
import { Player } from "./Player.js";
|
|
4
|
-
import { expectNotNull, loadFixture } from "./testUtils.js";
|
|
5
|
-
import { Client } from "./Client.js";
|
|
6
|
-
const { json, text } = vi.hoisted(() => ({ json: vi.fn(), text: vi.fn() }));
|
|
7
|
-
vi.mock("./Client.js", async (importOriginal) => {
|
|
8
|
-
const client = await importOriginal();
|
|
9
|
-
client.Client.prototype.login = async () => true;
|
|
10
|
-
client.Client.prototype.checkLoggedIn = async () => true;
|
|
11
|
-
client.Client.prototype.fetchText = text;
|
|
12
|
-
client.Client.prototype.fetchJson = json;
|
|
13
|
-
return client;
|
|
14
|
-
});
|
|
15
|
-
const client = new Client("", "");
|
|
16
|
-
test("Can search for a player by name", async () => {
|
|
17
|
-
vi.mocked(text).mockResolvedValueOnce(await loadFixture(__dirname, "searchplayer_mad_carew.html"));
|
|
18
|
-
const player = await Player.fromName(client, "mad carew");
|
|
19
|
-
expectNotNull(player);
|
|
20
|
-
expect(player.id).toBe(263717);
|
|
21
|
-
// Learns correct capitalisation
|
|
22
|
-
expect(player.name).toBe("Mad Carew");
|
|
23
|
-
});
|
|
24
|
-
describe("Profile parsing", () => {
|
|
25
|
-
test("Can parse a profile picture", async () => {
|
|
26
|
-
vi.mocked(text).mockResolvedValueOnce(await loadFixture(__dirname, "showplayer_regular.html"));
|
|
27
|
-
const player = await new Player(client, 2264486, "SSBBHax", 1, "Sauceror").full();
|
|
28
|
-
expectNotNull(player);
|
|
29
|
-
expect(player.avatar).toContain("<svg");
|
|
30
|
-
});
|
|
31
|
-
test("Can parse a profile picture on dependence day", async () => {
|
|
32
|
-
vi.mocked(text).mockResolvedValueOnce(await loadFixture(__dirname, "showplayer_dependence_day.html"));
|
|
33
|
-
const player = await new Player(client, 3019702, "Name Guy Man", 1, "Sauceror").full();
|
|
34
|
-
expectNotNull(player);
|
|
35
|
-
expect(player.avatar).toContain("<svg");
|
|
36
|
-
});
|
|
37
|
-
test("Can parse an avatar when the player has been painted gold", async () => {
|
|
38
|
-
vi.mocked(text).mockResolvedValueOnce(await loadFixture(__dirname, "showplayer_golden_gun.html"));
|
|
39
|
-
const player = await new Player(client, 1197090, "gAUSIE", 15, "Sauceror").full();
|
|
40
|
-
expectNotNull(player);
|
|
41
|
-
expect(player.avatar).toContain("<svg");
|
|
42
|
-
});
|
|
43
|
-
test("Can resolve KoL images", () => {
|
|
44
|
-
expect(resolveKoLImage("/iii/otherimages/classav31_f.gif")).toBe("https://s3.amazonaws.com/images.kingdomofloathing.com/otherimages/classav31_f.gif");
|
|
45
|
-
expect(resolveKoLImage("/itemimages/oaf.gif")).toBe("https://s3.amazonaws.com/images.kingdomofloathing.com/itemimages/oaf.gif");
|
|
46
|
-
expect(resolveKoLImage("https://s3.amazonaws.com/images.kingdomofloathing.com/itemimages/oaf.gif")).toBe("https://s3.amazonaws.com/images.kingdomofloathing.com/itemimages/oaf.gif");
|
|
47
|
-
});
|
|
48
|
-
});
|
package/dist/index.js
DELETED
package/dist/testUtils.d.ts
DELETED
package/dist/testUtils.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { expect } from "vitest";
|
|
4
|
-
export async function loadFixture(dirname, name) {
|
|
5
|
-
const file = path.join(dirname, `__fixtures__/${name}`);
|
|
6
|
-
return await fs.readFile(file, { encoding: "utf-8" });
|
|
7
|
-
}
|
|
8
|
-
export function expectNotNull(value) {
|
|
9
|
-
expect(value).not.toBeNull();
|
|
10
|
-
}
|
package/dist/utils/avatar.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function generateAvatarSvg(profile: string): Promise<string | null>;
|
package/dist/utils/avatar.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { parse } from "node-html-parser";
|
|
2
|
-
import { resolveKoLImage } from "./utils.js";
|
|
3
|
-
import { imageSize } from "image-size";
|
|
4
|
-
import { dedent } from "ts-dedent";
|
|
5
|
-
export async function generateAvatarSvg(profile) {
|
|
6
|
-
const header = profile.match(/<center><table><tr><td><center>.*?(<div.*?>.*?<\/div>).*?<b>([^>]*?)<\/b> \(#(\d+)\)<br>/);
|
|
7
|
-
const blockHtml = header?.[1];
|
|
8
|
-
if (!blockHtml)
|
|
9
|
-
return null;
|
|
10
|
-
const block = parse(blockHtml).querySelector("div");
|
|
11
|
-
if (!block)
|
|
12
|
-
return null;
|
|
13
|
-
const ocrsColour = ["gold", "red"].find((k) => block.classList.contains(k)) ?? "black";
|
|
14
|
-
const images = [];
|
|
15
|
-
for (const imgElement of block.querySelectorAll("img")) {
|
|
16
|
-
const src = imgElement.getAttribute("src");
|
|
17
|
-
if (!src)
|
|
18
|
-
continue;
|
|
19
|
-
const result = await fetch(resolveKoLImage(src));
|
|
20
|
-
const buffer = Buffer.from(await result.arrayBuffer());
|
|
21
|
-
const { width = 0, height = 0 } = imageSize(buffer);
|
|
22
|
-
const href = `data:image/png;base64,${buffer.toString("base64")}`;
|
|
23
|
-
const style = imgElement.getAttribute("style");
|
|
24
|
-
const top = Number(style?.match(/top: ?(-?\d+)px/i)?.[1] || "0");
|
|
25
|
-
const left = Number(style?.match(/left: ?(-?\d+)px/i)?.[1] || "0");
|
|
26
|
-
const rotate = Number(style?.match(/rotate\((-?\d+)deg\)/)?.[1] || "0");
|
|
27
|
-
images.push({
|
|
28
|
-
href,
|
|
29
|
-
top,
|
|
30
|
-
left,
|
|
31
|
-
rotate,
|
|
32
|
-
width,
|
|
33
|
-
height,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
const width = Math.max(...images.map((i) => i.left + i.width));
|
|
37
|
-
return dedent `
|
|
38
|
-
<svg width="${width}" height="100" xmlns="http://www.w3.org/2000/svg">
|
|
39
|
-
<defs>
|
|
40
|
-
<filter id="colorMask">
|
|
41
|
-
<feComponentTransfer in="SourceGraphic" out="f1">
|
|
42
|
-
<feFuncR type="discrete" tableValues="1 0"/>
|
|
43
|
-
<feFuncG type="discrete" tableValues="1 0"/>
|
|
44
|
-
<feFuncB type="discrete" tableValues="1 0"/>
|
|
45
|
-
</feComponentTransfer>
|
|
46
|
-
<feColorMatrix type="matrix" values="1 0 0 0 0
|
|
47
|
-
0 1 0 0 0
|
|
48
|
-
0 0 1 0 0
|
|
49
|
-
1 1 1 1 -3" result="selectedColor"/>
|
|
50
|
-
<feFlood flood-color="${ocrsColour}"/>
|
|
51
|
-
<feComposite operator="in" in2="selectedColor"/>
|
|
52
|
-
<feComposite operator="over" in2="SourceGraphic"/>
|
|
53
|
-
</filter>
|
|
54
|
-
</defs>
|
|
55
|
-
${images
|
|
56
|
-
.map((i) => dedent `
|
|
57
|
-
<image
|
|
58
|
-
filter="url(#colorMask)"
|
|
59
|
-
href="${i.href}"
|
|
60
|
-
width="${i.width}"
|
|
61
|
-
height="${i.height}"
|
|
62
|
-
x="${i.left}"
|
|
63
|
-
y="${i.top}"
|
|
64
|
-
transform="rotate(${i.rotate},${i.width / 2 + i.left},${i.height / 2 + i.top})"
|
|
65
|
-
/>
|
|
66
|
-
`)
|
|
67
|
-
.join("\n")}
|
|
68
|
-
</svg>
|
|
69
|
-
`;
|
|
70
|
-
}
|
package/dist/utils/kmail.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Player } from "../Player.js";
|
|
2
|
-
export type KoLChatMessage = {
|
|
3
|
-
who?: Player<false>;
|
|
4
|
-
type?: string;
|
|
5
|
-
msg?: string;
|
|
6
|
-
link?: string;
|
|
7
|
-
channel?: string;
|
|
8
|
-
time: string;
|
|
9
|
-
};
|
|
10
|
-
type KoLMessageType = "private" | "system" | "public" | "kmail";
|
|
11
|
-
export declare const isValidMessage: (msg: KoLChatMessage) => msg is KoLChatMessage & {
|
|
12
|
-
type: KoLMessageType;
|
|
13
|
-
who: Player<false>;
|
|
14
|
-
msg: string;
|
|
15
|
-
};
|
|
16
|
-
export type KoLKmail = {
|
|
17
|
-
id: string;
|
|
18
|
-
type: string;
|
|
19
|
-
fromid: string;
|
|
20
|
-
fromname: string;
|
|
21
|
-
azunixtime: string;
|
|
22
|
-
message: string;
|
|
23
|
-
localtime: string;
|
|
24
|
-
};
|
|
25
|
-
export type KoLMessage = {
|
|
26
|
-
type: KoLMessageType;
|
|
27
|
-
who: Player<false>;
|
|
28
|
-
msg: string;
|
|
29
|
-
time: Date;
|
|
30
|
-
channel?: string;
|
|
31
|
-
};
|
|
32
|
-
export {};
|
package/dist/utils/kmail.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const isValidMessage = (msg) => msg.who !== undefined && msg.msg !== undefined;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type LeaderboardInfo = {
|
|
2
|
-
name: string;
|
|
3
|
-
boards: SubboardInfo[];
|
|
4
|
-
};
|
|
5
|
-
export type SubboardInfo = {
|
|
6
|
-
name: string;
|
|
7
|
-
runs: RunInfo[];
|
|
8
|
-
updated: Date | null;
|
|
9
|
-
};
|
|
10
|
-
type RunInfo = {
|
|
11
|
-
player: string;
|
|
12
|
-
days: string;
|
|
13
|
-
turns: string;
|
|
14
|
-
};
|
|
15
|
-
export declare function parseLeaderboard(page: string): LeaderboardInfo;
|
|
16
|
-
export {};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import xpath, { select } from "xpath";
|
|
2
|
-
import { DOMParser } from "@xmldom/xmldom";
|
|
3
|
-
const parser = new DOMParser({
|
|
4
|
-
locator: {},
|
|
5
|
-
errorHandler: {
|
|
6
|
-
warning: () => { },
|
|
7
|
-
error: () => { },
|
|
8
|
-
fatalError: console.error,
|
|
9
|
-
},
|
|
10
|
-
});
|
|
11
|
-
const selectMulti = (expression, node) => {
|
|
12
|
-
const selection = select(expression, node);
|
|
13
|
-
if (Array.isArray(selection))
|
|
14
|
-
return selection;
|
|
15
|
-
return selection instanceof Node ? [selection] : [];
|
|
16
|
-
};
|
|
17
|
-
export function parseLeaderboard(page) {
|
|
18
|
-
const document = parser.parseFromString(page);
|
|
19
|
-
const [board, ...boards] = selectMulti("//table", document);
|
|
20
|
-
return {
|
|
21
|
-
name: selectMulti(".//text()", board.firstChild)
|
|
22
|
-
.map((node) => node.nodeValue)
|
|
23
|
-
.join("")
|
|
24
|
-
.replace(/\s+/g, " ")
|
|
25
|
-
.trim(),
|
|
26
|
-
boards: boards
|
|
27
|
-
.slice(1)
|
|
28
|
-
.filter((board) => selectMulti("./tr//text()", board)[0]?.nodeValue?.match(/^((Fast|Funn|B)est|Most (Goo|Elf))/) && selectMulti("./tr", board).length > 1)
|
|
29
|
-
.map((subboard) => {
|
|
30
|
-
const rows = selectMulti("./tr", subboard);
|
|
31
|
-
return {
|
|
32
|
-
name: (selectMulti(".//text()", rows[0])[0]?.nodeValue || "").trim(),
|
|
33
|
-
runs: selectMulti("./td//tr", rows[1])
|
|
34
|
-
.slice(2)
|
|
35
|
-
.map((node) => {
|
|
36
|
-
const rowText = selectMulti(".//text()", node).map((text) => text.toString().replace(/&nbsp;/g, ""));
|
|
37
|
-
const hasTwoNumbers = !!parseInt(rowText[rowText.length - 2]);
|
|
38
|
-
return {
|
|
39
|
-
player: rowText
|
|
40
|
-
.slice(0, rowText.length - (hasTwoNumbers ? 2 : 1))
|
|
41
|
-
.join("")
|
|
42
|
-
.trim()
|
|
43
|
-
.toString(),
|
|
44
|
-
days: hasTwoNumbers
|
|
45
|
-
? rowText[rowText.length - 2].toString() || "0"
|
|
46
|
-
: "",
|
|
47
|
-
turns: rowText[rowText.length - 1].toString() || "0",
|
|
48
|
-
};
|
|
49
|
-
}),
|
|
50
|
-
updated: xpath.isComment(subboard.nextSibling)
|
|
51
|
-
? new Date(subboard.nextSibling.data.slice(9, -1))
|
|
52
|
-
: null,
|
|
53
|
-
};
|
|
54
|
-
}),
|
|
55
|
-
};
|
|
56
|
-
}
|
package/dist/utils/utils.d.ts
DELETED
package/dist/utils/utils.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { parse as parseDate } from "date-fns";
|
|
2
|
-
import { decode } from "html-entities";
|
|
3
|
-
export function parsePlayerDate(input) {
|
|
4
|
-
if (!input)
|
|
5
|
-
return new Date();
|
|
6
|
-
return parseDate(input, "MMMM dd, yyyy", new Date());
|
|
7
|
-
}
|
|
8
|
-
export function sanitiseBlueText(blueText) {
|
|
9
|
-
if (!blueText)
|
|
10
|
-
return "";
|
|
11
|
-
return decode(blueText
|
|
12
|
-
.replace(/\r/g, "")
|
|
13
|
-
.replace(/\r/g, "")
|
|
14
|
-
.replace(/(<p><\/p>)|(<br>)|(<Br>)|(<br \/>)|(<Br \/>)/g, "\n")
|
|
15
|
-
.replace(/<[^<>]+>/g, "")
|
|
16
|
-
.replace(/(\n+)/g, "\n")
|
|
17
|
-
.replace(/(\n)+$/, "")).trim();
|
|
18
|
-
}
|
|
19
|
-
export function resolveKoLImage(path) {
|
|
20
|
-
if (!/^https?:\/\//i.test(path))
|
|
21
|
-
return ("https://s3.amazonaws.com/images.kingdomofloathing.com" +
|
|
22
|
-
path.replace(/^\/(iii|images)/, ""));
|
|
23
|
-
return path;
|
|
24
|
-
}
|
|
25
|
-
export function wait(ms) {
|
|
26
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
27
|
-
}
|
package/dist/utils/visit.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export declare function createBody(data: Record<string, string | number>): URLSearchParams;
|
|
2
|
-
export declare function formatQuerystring(params: Record<string, string | number>): URLSearchParams;
|
|
3
|
-
export declare const createSession: () => import("fetch-cookie").FetchCookieImpl<string | URL | Request, RequestInit, Response>;
|
package/dist/utils/visit.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import makeFetchCookie from "fetch-cookie";
|
|
2
|
-
export function createBody(data) {
|
|
3
|
-
const formData = new FormData();
|
|
4
|
-
Object.entries(data).forEach(([key, value]) => formData.append(key, String(value)));
|
|
5
|
-
return new URLSearchParams(formData);
|
|
6
|
-
}
|
|
7
|
-
export function formatQuerystring(params) {
|
|
8
|
-
return new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])));
|
|
9
|
-
}
|
|
10
|
-
export const createSession = () => makeFetchCookie(fetch);
|