kol.js 0.1.3 → 0.1.5
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/Client.test.ts +41 -0
- package/src/Client.ts +51 -4
- package/src/Player.test.ts +28 -9
- package/src/Player.ts +9 -3
- package/src/__fixtures__/raffle.html +41 -0
- package/src/__fixtures__/searchplayer_beldur.html +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kol.js",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -13,22 +13,23 @@
|
|
|
13
13
|
"format": "prettier --write ."
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"typescript": "^5.4.5",
|
|
16
|
+
"prettier": "^3.3.2",
|
|
17
|
+
"typescript": "^5.5.3",
|
|
19
18
|
"vitest": "^1.6.0"
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
21
|
"@xmldom/xmldom": "^0.8.10",
|
|
23
22
|
"async-mutex": "^0.5.0",
|
|
24
23
|
"date-fns": "^3.6.0",
|
|
25
|
-
"got": "^14.
|
|
24
|
+
"got": "^14.4.2",
|
|
26
25
|
"html-entities": "^2.5.2",
|
|
27
26
|
"image-size": "^1.1.1",
|
|
28
27
|
"node-html-parser": "^6.1.13",
|
|
29
28
|
"querystring": "^0.2.1",
|
|
30
|
-
"tough-cookie": "^
|
|
29
|
+
"tough-cookie": "^5.0.0",
|
|
30
|
+
"ts-dedent": "^2.2.0",
|
|
31
31
|
"typed-emitter": "^2.1.0",
|
|
32
|
+
"typescript-memoize": "^1.1.1",
|
|
32
33
|
"xpath": "^0.0.34"
|
|
33
34
|
}
|
|
34
35
|
}
|
package/src/Client.test.ts
CHANGED
|
@@ -204,3 +204,44 @@ describe("Familiars", () => {
|
|
|
204
204
|
expect(familiars).toHaveLength(206);
|
|
205
205
|
});
|
|
206
206
|
});
|
|
207
|
+
|
|
208
|
+
describe("Raffle", () => {
|
|
209
|
+
it("can fetch the current raffle", async () => {
|
|
210
|
+
text.mockResolvedValueOnce(await loadFixture(__dirname, "raffle.html"));
|
|
211
|
+
text.mockResolvedValueOnce("<!-- itemid: 1 -->");
|
|
212
|
+
text.mockResolvedValueOnce("<!-- itemid: 2 -->");
|
|
213
|
+
text.mockResolvedValueOnce("<!-- itemid: 3 -->");
|
|
214
|
+
text.mockResolvedValueOnce("<!-- itemid: 4 -->");
|
|
215
|
+
|
|
216
|
+
const raffle = await client.getRaffle();
|
|
217
|
+
|
|
218
|
+
expect(raffle).toMatchObject({
|
|
219
|
+
today: {
|
|
220
|
+
first: 1,
|
|
221
|
+
second: 2,
|
|
222
|
+
},
|
|
223
|
+
yesterday: [
|
|
224
|
+
{
|
|
225
|
+
player: { id: 809337, name: "Collective Consciousness" },
|
|
226
|
+
item: 3,
|
|
227
|
+
tickets: 3333,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
player: { id: 852958, name: "Ryo_Sangnoir" },
|
|
231
|
+
item: 4,
|
|
232
|
+
tickets: 1011,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
player: { id: 1765063, name: "SSpectre_Karasu" },
|
|
236
|
+
item: 4,
|
|
237
|
+
tickets: 1000,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
player: { id: 1652370, name: "yueli7" },
|
|
241
|
+
item: 4,
|
|
242
|
+
tickets: 2040,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
package/src/Client.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { PlayerCache } from "./Cache.js";
|
|
17
17
|
import { CookieJar } from "tough-cookie";
|
|
18
18
|
import got, { OptionsOfJSONResponseBody, OptionsOfTextResponseBody } from "got";
|
|
19
|
+
import { Memoize } from "typescript-memoize";
|
|
19
20
|
|
|
20
21
|
type TypedEmitter<T extends EventMap> = TypedEventEmitter.default<T>;
|
|
21
22
|
|
|
@@ -42,7 +43,7 @@ type Familiar = {
|
|
|
42
43
|
image: string;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
export class Client extends (EventEmitter as new () => TypedEmitter<Events>) {
|
|
46
|
+
export class Client extends (EventEmitter as unknown as new () => TypedEmitter<Events>) {
|
|
46
47
|
actionMutex = new Mutex();
|
|
47
48
|
session = got.extend({
|
|
48
49
|
cookieJar: new CookieJar(),
|
|
@@ -55,7 +56,8 @@ export class Client extends (EventEmitter as new () => TypedEmitter<Events>) {
|
|
|
55
56
|
|
|
56
57
|
if (options.searchParams) {
|
|
57
58
|
const searchParams = options.searchParams as URLSearchParams;
|
|
58
|
-
if (searchParams.get("pwd") !== "false")
|
|
59
|
+
if (searchParams.get("pwd") !== "false")
|
|
60
|
+
searchParams.set("pwd", this.#pwd);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
return next(options);
|
|
@@ -269,14 +271,20 @@ export class Client extends (EventEmitter as new () => TypedEmitter<Events>) {
|
|
|
269
271
|
}
|
|
270
272
|
|
|
271
273
|
async deleteKmails(ids: number[]) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
if (ids.length === 0) return true;
|
|
275
|
+
|
|
276
|
+
const response = await this.fetchText("messages.php", {
|
|
277
|
+
form: {
|
|
274
278
|
the_action: "delete",
|
|
275
279
|
box: "Inbox",
|
|
276
280
|
...Object.fromEntries(ids.map((id) => [`sel${id}`, "on"])),
|
|
277
281
|
pwd: true,
|
|
278
282
|
},
|
|
279
283
|
});
|
|
284
|
+
|
|
285
|
+
return response.includes(
|
|
286
|
+
`<td>${ids.length} message${ids.length === 1 ? "" : "s"} deleted.</td>`,
|
|
287
|
+
);
|
|
280
288
|
}
|
|
281
289
|
|
|
282
290
|
async checkKmails() {
|
|
@@ -503,4 +511,43 @@ export class Client extends (EventEmitter as new () => TypedEmitter<Events>) {
|
|
|
503
511
|
|
|
504
512
|
return familiars;
|
|
505
513
|
}
|
|
514
|
+
|
|
515
|
+
@Memoize()
|
|
516
|
+
async descIdToId(descId: number): Promise<number> {
|
|
517
|
+
const page = await this.fetchText("desc_item.php", {
|
|
518
|
+
searchParams: {
|
|
519
|
+
whichitem: descId,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
return Number(page.match(/<!-- itemid: (\d+) -->/)?.[1] ?? -1);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async getRaffle() {
|
|
526
|
+
const page = await this.fetchText("raffle.php");
|
|
527
|
+
const today = page.matchAll(
|
|
528
|
+
/<tr><td align=right>(?:First|Second) Prize:<\/td>.*?descitem\((\d+)\)/g,
|
|
529
|
+
);
|
|
530
|
+
const [first, second] = await Promise.all(
|
|
531
|
+
today
|
|
532
|
+
? [...today].map(async (p) => await this.descIdToId(Number(p[1])))
|
|
533
|
+
: [null, null],
|
|
534
|
+
);
|
|
535
|
+
const winners = page.matchAll(
|
|
536
|
+
/<tr><td class=small><a href='showplayer\.php\?who=\d+'>(.*?) \(#(\d+)\).*?descitem\((\d+)\).*?([\d,]+)<\/td><\/tr>/g,
|
|
537
|
+
);
|
|
538
|
+
const yesterday = await Promise.all(
|
|
539
|
+
winners
|
|
540
|
+
? [...winners].map(async (w) => ({
|
|
541
|
+
player: new Player(this, Number(w[2]), w[1]),
|
|
542
|
+
item: await this.descIdToId(Number(w[3])),
|
|
543
|
+
tickets: Number(w[4].replace(",", "")),
|
|
544
|
+
}))
|
|
545
|
+
: [],
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
today: { first, second },
|
|
550
|
+
yesterday,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
506
553
|
}
|
package/src/Player.test.ts
CHANGED
|
@@ -17,18 +17,37 @@ vi.mock("./Client.js", async (importOriginal) => {
|
|
|
17
17
|
|
|
18
18
|
const client = new Client("", "");
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
describe("Player searching", () => {
|
|
21
|
+
test("Can search for a player by name", async () => {
|
|
22
|
+
vi.mocked(text).mockResolvedValueOnce(
|
|
23
|
+
await loadFixture(__dirname, "searchplayer_mad_carew.html"),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const player = await Player.fromName(client, "mad carew");
|
|
27
|
+
|
|
28
|
+
expectNotNull(player);
|
|
29
|
+
|
|
30
|
+
expect(player.id).toBe(263717);
|
|
31
|
+
// Learns correct capitalisation
|
|
32
|
+
expect(player.name).toBe("Mad Carew");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("Can search for a player in Valhalla by name ", async () => {
|
|
36
|
+
vi.mocked(text).mockResolvedValueOnce(
|
|
37
|
+
await loadFixture(__dirname, "searchplayer_beldur.html"),
|
|
38
|
+
);
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
const player = await Player.fromName(client, "Beldur");
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
expectNotNull(player);
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
expect(player.id).toBe(1046951);
|
|
45
|
+
// Learns correct capitalisation
|
|
46
|
+
expect(player.name).toBe("Beldur");
|
|
47
|
+
expect(player.level).toBe(0);
|
|
48
|
+
// We display Astral Spirit even though the search page says Seal Clubber.
|
|
49
|
+
expect(player.kolClass).toBe("Astral Spirit");
|
|
50
|
+
});
|
|
32
51
|
});
|
|
33
52
|
|
|
34
53
|
describe("Profile parsing", () => {
|
package/src/Player.ts
CHANGED
|
@@ -82,7 +82,7 @@ export class Player<IsFull extends boolean = boolean> {
|
|
|
82
82
|
): Promise<Player<false> | null> {
|
|
83
83
|
try {
|
|
84
84
|
const matcher =
|
|
85
|
-
|
|
85
|
+
/<tr><td class=small><b><a target=mainpane href="showplayer\.php\?who=(?<playerId>\d+)">(?<playerName>[^<]+)<\/a><\/b>.*?<\/td><td valign=top class=small>\d*<\/td><td valign=top class=small>(?:<img src=".*?">|(?<level>\d+))<\/td><td class=small valign=top>(?<class>[^<]+)<\/td><\/tr>/i;
|
|
86
86
|
const search = await client.fetchText("searchplayer.php", {
|
|
87
87
|
searchParams: {
|
|
88
88
|
searchstring: name.replace(/_/g, "\\_"),
|
|
@@ -98,12 +98,14 @@ export class Player<IsFull extends boolean = boolean> {
|
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const clazz = match.level ? match.class : "Astral Spirit";
|
|
102
|
+
|
|
101
103
|
return new Player(
|
|
102
104
|
client,
|
|
103
105
|
Number(match.playerId),
|
|
104
106
|
match.playerName,
|
|
105
|
-
parseInt(match.level),
|
|
106
|
-
|
|
107
|
+
parseInt(match.level) || 0,
|
|
108
|
+
clazz,
|
|
107
109
|
);
|
|
108
110
|
} catch (error) {
|
|
109
111
|
return null;
|
|
@@ -206,4 +208,8 @@ export class Player<IsFull extends boolean = boolean> {
|
|
|
206
208
|
response?.output.includes("This player is currently online") ?? false
|
|
207
209
|
);
|
|
208
210
|
}
|
|
211
|
+
|
|
212
|
+
toString() {
|
|
213
|
+
return `${this.name} (#${this.id})`;
|
|
214
|
+
}
|
|
209
215
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<html><head>
|
|
2
|
+
<script language=Javascript>
|
|
3
|
+
<!--
|
|
4
|
+
if (parent.frames.length == 0) location.href="game.php";
|
|
5
|
+
var actions = { "sendmessage.php" : { "action" : 1, "title" : "Send Message", "arg" : "toid" } }
|
|
6
|
+
var notchat = true;//-->
|
|
7
|
+
</script>
|
|
8
|
+
<script language=Javascript src="https://d2uyhvukfffg5a.cloudfront.net/scripts/keybinds.min.2.js"></script>
|
|
9
|
+
<script language=Javascript src="https://d2uyhvukfffg5a.cloudfront.net/scripts/window.20111231.js"></script>
|
|
10
|
+
<script language="javascript">function chatFocus(){if(top.chatpane.document.chatform.graf) top.chatpane.document.chatform.graf.focus();}
|
|
11
|
+
if (typeof defaultBind != 'undefined') { defaultBind(47, 2, chatFocus); defaultBind(190, 2, chatFocus);defaultBind(191, 2, chatFocus); defaultBind(47, 8, chatFocus);defaultBind(190, 8, chatFocus); defaultBind(191, 8, chatFocus); }</script><script language=Javascript>
|
|
12
|
+
var confirm_ = 0;
|
|
13
|
+
function conf()
|
|
14
|
+
{
|
|
15
|
+
if (!confirm_)
|
|
16
|
+
return true;
|
|
17
|
+
|
|
18
|
+
var select = document.f.where;
|
|
19
|
+
var where = select.options[select.selectedIndex].value;
|
|
20
|
+
if (where == 1)
|
|
21
|
+
return true;
|
|
22
|
+
else
|
|
23
|
+
return confirm("You are currently in ronin/hardcore. Are you sure you want to buy these tickets using your on-hand Meat?");
|
|
24
|
+
}
|
|
25
|
+
</script><script language=Javascript src="https://d2uyhvukfffg5a.cloudfront.net/scripts/jquery-1.3.1.min.js"></script>
|
|
26
|
+
<script language=Javascript src='https://d2uyhvukfffg5a.cloudfront.net/scripts/rcm.20160406.js'></script> <link rel="stylesheet" type="text/css" href="https://d2uyhvukfffg5a.cloudfront.net/styles.20230117d.css">
|
|
27
|
+
<style type='text/css'>
|
|
28
|
+
.faded {
|
|
29
|
+
zoom: 1;
|
|
30
|
+
filter: alpha(opacity=35);
|
|
31
|
+
opacity: 0.35;
|
|
32
|
+
-khtml-opacity: 0.35;
|
|
33
|
+
-moz-opacity: 0.35;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
|
|
37
|
+
</head>
|
|
38
|
+
|
|
39
|
+
<body>
|
|
40
|
+
<div id='menu' class=rcm></div><center><table width=95% cellspacing=0 cellpadding=0><tr><td style="color: white;" align=center bgcolor=blue><b>Raffle House</b></td></tr><tr><td style="padding: 5px; border: 1px solid blue;"><center><table><tr><td><table><tr><td valign=center><img src='https://d2uyhvukfffg5a.cloudfront.net/otherimages/town/shoppeng.gif'></td><td>Greetings, Adventurer, and welcome to our Raffle House, which is completely and totally legitimate and fully-licensed by the Council of Loathing.<p> Here's how it works: you buy some raffle tickets, and every day (at rollover) we'll draw one to see who wins our Fabulous Prize, which will be delivered to you in one of our special packages. Tickets are non-transferrable, but we will accept tickets from Hagnk's if you should ascend before the drawing.<p> All proceeds will be donated to an unspecified but very worthy charity of our choosing. So, how many tickets do you want?</td></tr></table><center><b>Today's Raffle Prize:</b></center><center><table><tr><td align=right>First Prize:</td><td><img class=hand onclick='descitem(352170676);' alt='maypole' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/maypole.gif'> <b>miniature gravy-covered maypole</b></td></tr></table><center><table><tr><td align=right>Second Prize:</td><td><img class=hand onclick='descitem(992809861);' alt='vcase' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/vcase.gif'> <b>Doll Moll violin case</b></td></tr></table><table><tr><td>1 first prize lot and 3 second prize lots are available. One prize lot per winner. All ticket sales are final. Void where prohibited by law. Part of a complete breakfast.<p></td></tr></table><p>You haven't bought any tickets for today's raffle.<p><b>Winners of Yesterday's Raffle:</b><br><div style='max-height: 300px; overflow: auto; border: 1px dashed black; margin-left: 5%; margin-right: 5%'><table><tr><td align=center><b>Name</b></td><td align=center><b>Prize</b></td><td align=center><b>Tickets<br>Purchased</b></td></tr><tr><td class=small><a href='showplayer.php?who=809337'>Collective Consciousness (#809337)</a></td><td><img class=hand onclick='descitem(828455718);' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/wintercatalog.gif'> <b>Discontent™ Winter Garden Catalog</b></td><td class=small> 3,333</td></tr><tr><td class=small><a href='showplayer.php?who=852958'>Ryo_Sangnoir (#852958)</a></td><td><img class=hand onclick='descitem(211748296);' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/wine.gif'> <b>Colera Peste Nebbiolo</b></td><td class=small> 1,011</td></tr><tr><td class=small><a href='showplayer.php?who=1765063'>SSpectre_Karasu (#1765063)</a></td><td><img class=hand onclick='descitem(211748296);' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/wine.gif'> <b>Colera Peste Nebbiolo</b></td><td class=small> 1,000</td></tr><tr><td class=small><a href='showplayer.php?who=1652370'>yueli7 (#1652370)</a></td><td><img class=hand onclick='descitem(211748296);' style='vertical-align: middle' src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/wine.gif'> <b>Colera Peste Nebbiolo</b></td><td class=small> 2,040</td></tr></table></div><form name=f style='display: inline' action=raffle.php method=post onsubmit='return conf();'><input type=hidden name=action value=buy><input type=hidden name=pwd value=4cf70f166096b3abab3d98431af7094a><table><tr><td colspan=3 align=center><b>Buy Raffle Tickets:</b></td></tr><tr><td align=right><img style='vertical-align: middle' class=hand src='https://d2uyhvukfffg5a.cloudfront.net/itemimages/raffle.gif' onclick='descitem(488779797)'></td><td><b>raffle ticket</b></td><td>10,000 Meat</td></tr><tr><td colspan=3><center><input type=hidden name=where value=0><input type=submit class=button value='Buy Tickets'> <input type=text class=text name=quantity value=1 size=3></center></td></tr></table></form></center><center><p><a href='bordertown.php'>Go back to Bordertown</a></center></td></tr></table></center></td></tr><tr><td height=4></td></tr></table></center></body></html>
|
|
41
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<html><head><title>Search for a player</title>
|
|
2
|
+
<link rel="stylesheet" type="text/css" href="/images/styles.20230117d.css">
|
|
3
|
+
<script>
|
|
4
|
+
var actions = { "sendmessage.php" : { "action" : 1, "title" : "Send Message", "arg" : "toid" }, "town_sendgift.php" : { "action" : 1, "title" : "Send Gift", "arg" : "towho" }, "makeoffer.php" : { "action" : 1, "title" : "Propose Trade", "arg" : "towho" }, "mallstore.php" : { "action" : 1, "title" : "Mall Store", "arg" : "whichstore" }, "/./curse.php" : { "action" : 1, "title" : "Throw TP", "arg" : "whichitem=1923&targetplayer" }, "/msg" : { "action" : 3, "useid" : true, "query" : "Enter message to send to %:" } }
|
|
5
|
+
var notchat = true;
|
|
6
|
+
</script>
|
|
7
|
+
<script language=Javascript src='/images/scripts/rcm.3.js'></script>
|
|
8
|
+
<script language="Javascript" src="/basics.js"></script><link rel="stylesheet" href="/basics.1.css" /></head>
|
|
9
|
+
<style type="text/css">
|
|
10
|
+
<!--
|
|
11
|
+
BODY, TD, UL {
|
|
12
|
+
size : 1;
|
|
13
|
+
}
|
|
14
|
+
a {
|
|
15
|
+
text-decoration: none;
|
|
16
|
+
}
|
|
17
|
+
.small { font-size: .8em; }
|
|
18
|
+
-->
|
|
19
|
+
</style>
|
|
20
|
+
|
|
21
|
+
<script src="/onfocus.1.js"></script></html>
|
|
22
|
+
<body bgcolor=white text=black link=black alink=black vlink=black>
|
|
23
|
+
<div id='menu' class=rcm></div>
|
|
24
|
+
|
|
25
|
+
<center><b>Search Results:<br><table align=center><tr><td class=small><b><u>Name</u></b></td><td class=small><b><u>PlayerID</u></b></td><td class=small><b><u>Level</u></b></td><td class=small><b><u>Class</u></b></td></tr><tr><td class=small><b><a target=mainpane href="showplayer.php?who=1046951">Beldur</a></b> </td><td valign=top class=small>1046951</td><td valign=top class=small><img src="https://d2uyhvukfffg5a.cloudfront.net/otherimages/inf_small.gif"></td><td class=small valign=top>Seal Clubber</td></tr><tr><td class=small><b><a target=mainpane href="showplayer.php?who=3498868">Beldur2</a></b> </td><td valign=top class=small>3498868</td><td valign=top class=small>1</td><td class=small valign=top>Sauceror</td></tr></table></center>
|
|
26
|
+
<form name="search" action="searchplayer.php" method="post">
|
|
27
|
+
<input type="hidden" name="searching" value="Yep.">
|
|
28
|
+
<input type="hidden" name="for" value="">
|
|
29
|
+
<center>
|
|
30
|
+
<b>Search for names:</b>
|
|
31
|
+
<input class="text" type="text" name="searchstring" size="30" value="Beldur"><br />
|
|
32
|
+
<div style="font-weight: normal">
|
|
33
|
+
starting with <input type="radio" name="startswith" value="1" checked="checked">
|
|
34
|
+
containing<input type="radio" name="startswith" value="2">
|
|
35
|
+
ending with <input type="radio" name="startswith" value="3">
|
|
36
|
+
</div>
|
|
37
|
+
<table>
|
|
38
|
+
<tr><td valign="center" align="center">
|
|
39
|
+
Level: <input class="text" type="text" size=3 name=searchlevel>
|
|
40
|
+
Fame: <input class="text" type="text" size=3 name=searchranking>
|
|
41
|
+
</td></tr>
|
|
42
|
+
<tr><td valign="center"><input type="checkbox" name="pvponly" >PvP players only</td>
|
|
43
|
+
<td><input type="radio" name="hardcoreonly" value="1">Hardcore only<br>
|
|
44
|
+
<input type="radio" name="hardcoreonly" value="2">Not in Hardcore<br>
|
|
45
|
+
<input type="radio" name="hardcoreonly" value="0" checked>Don't Care</td>
|
|
46
|
+
</tr>
|
|
47
|
+
</table>
|
|
48
|
+
<input class="button" type="submit" value="Search">
|
|
49
|
+
</form>
|
|
50
|
+
|
|
51
|
+
</font>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
|