@hytaleone/query 1.0.1 → 1.1.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/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # @hytaleone/query
2
2
 
3
- Query Hytale servers using the UDP query protocol.
3
+ [![npm version](https://img.shields.io/npm/v/@hytaleone/query.svg)](https://www.npmjs.com/package/@hytaleone/query)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Query Hytale servers using the UDP query protocol. Get server status, player count, player list, and plugin information.
7
+
8
+ ## Features
9
+
10
+ - **Server Status** - Get server name, MOTD, version, player count
11
+ - **Player List** - Retrieve online players with names and UUIDs
12
+ - **Plugin List** - See installed plugins with versions
13
+ - **V2 Protocol** - Enhanced protocol with challenge-based authentication and pagination
14
+ - **Zero dependencies** - Uses only Node.js built-in modules
15
+ - **TypeScript** - Full type definitions included
16
+ - **Dual format** - Works with both ESM and CommonJS
4
17
 
5
18
  ## Installation
6
19
 
@@ -10,6 +23,8 @@ npm install @hytaleone/query
10
23
 
11
24
  ## Usage
12
25
 
26
+ ### V1 Protocol
27
+
13
28
  ```typescript
14
29
  import { query } from '@hytaleone/query';
15
30
 
@@ -17,17 +32,46 @@ import { query } from '@hytaleone/query';
17
32
  const info = await query('play.example.com', 5520);
18
33
  console.log(`${info.serverName}: ${info.currentPlayers}/${info.maxPlayers}`);
19
34
 
35
+ // Check V2 support
36
+ if (info.supportsV2) {
37
+ console.log('Server supports V2 protocol');
38
+ }
39
+
20
40
  // Full query - includes players and plugins
21
41
  const full = await query('play.example.com', 5520, { full: true });
22
42
  console.log('Players:', full.players.map(p => p.name).join(', '));
23
43
  console.log('Plugins:', full.plugins.map(p => p.id).join(', '));
24
44
  ```
25
45
 
46
+ ### V2 Protocol
47
+
48
+ ```typescript
49
+ import { query, queryV2 } from '@hytaleone/query';
50
+
51
+ // Check if server supports V2
52
+ const info = await query('play.example.com', 5520);
53
+
54
+ if (info.supportsV2) {
55
+ // Basic V2 query
56
+ const v2Info = await queryV2('play.example.com', 5520);
57
+ console.log(`${v2Info.serverName}: ${v2Info.currentPlayers}/${v2Info.maxPlayers}`);
58
+
59
+ // V2 query with players (single page)
60
+ const withPlayers = await queryV2('play.example.com', 5520, { players: true });
61
+ console.log('Players:', withPlayers.players.map(p => p.name).join(', '));
62
+ console.log('Has more:', withPlayers.hasMore);
63
+
64
+ // V2 query with all players (auto-pagination)
65
+ const allPlayers = await queryV2('play.example.com', 5520, { players: 'all' });
66
+ console.log(`All ${allPlayers.totalPlayers} players fetched`);
67
+ }
68
+ ```
69
+
26
70
  ## API
27
71
 
28
72
  ### query(host, port?, options?)
29
73
 
30
- Query a server for information.
74
+ Query a server using the V1 protocol.
31
75
 
32
76
  ```typescript
33
77
  const info = await query('localhost', 5520, {
@@ -49,11 +93,71 @@ const info = await query('localhost', 5520, {
49
93
  - `version` - Server version
50
94
  - `protocolVersion` - Protocol version number
51
95
  - `protocolHash` - Protocol hash
96
+ - `supportsV2` - Whether server supports V2 protocol
97
+ - `isNetworkMode` - Whether server is in network aggregation mode
98
+ - `v2Version` - V2 protocol version (0 if not supported)
52
99
 
53
100
  **With `full: true`, also returns:**
54
101
  - `players` - Array of `{ name, uuid }`
55
102
  - `plugins` - Array of `{ id, version, enabled }`
56
103
 
104
+ ### queryV2(host, port?, options?)
105
+
106
+ Query a server using the V2 protocol with challenge-based authentication.
107
+
108
+ ```typescript
109
+ const info = await queryV2('localhost', 5520, {
110
+ timeout: 5000,
111
+ players: true,
112
+ playerOffset: 0
113
+ });
114
+ ```
115
+
116
+ **Options:**
117
+ - `timeout` - Query timeout in milliseconds (default: 5000)
118
+ - `players` - Request player list: `false` (default), `true` (single page), or `'all'` (auto-paginate)
119
+ - `playerOffset` - Starting offset for player pagination (default: 0)
120
+ - `authToken` - Optional authentication token for private servers
121
+
122
+ **Returns `ServerInfoV2`:**
123
+ - `serverName` - Server display name
124
+ - `motd` - Message of the day
125
+ - `currentPlayers` - Current player count
126
+ - `maxPlayers` - Maximum player capacity
127
+ - `version` - Server version
128
+ - `protocolVersion` - Protocol version number
129
+ - `protocolHash` - Protocol hash
130
+ - `isNetwork` - Whether response contains aggregated network data
131
+ - `host` - Server host (if provided by server)
132
+ - `hostPort` - Server port (if provided by server)
133
+
134
+ **With `players: true` or `players: 'all'`, also returns:**
135
+ - `players` - Array of `{ name, uuid }`
136
+ - `totalPlayers` - Total player count on server
137
+ - `offset` - Offset used for this response
138
+ - `hasMore` - Whether more players are available
139
+
140
+ ### clearChallengeCache()
141
+
142
+ Clear the V2 challenge token cache. Useful for testing or forcing fresh challenges.
143
+
144
+ ```typescript
145
+ import { clearChallengeCache } from '@hytaleone/query';
146
+
147
+ clearChallengeCache();
148
+ ```
149
+
150
+ ## V1 vs V2 Protocol
151
+
152
+ | Feature | V1 | V2 |
153
+ |---------|----|----|
154
+ | Authentication | None | Challenge-based |
155
+ | Player pagination | No | Yes |
156
+ | Network aggregation | No | Yes |
157
+ | Plugin list | Yes | No |
158
+
159
+ Use V1 for simple queries or when you need plugin information. Use V2 for servers with many players or when you need pagination support.
160
+
57
161
  ## Requirements
58
162
 
59
163
  - Node.js >= 18
@@ -63,6 +167,10 @@ const info = await query('localhost', 5520, {
63
167
 
64
168
  MIT
65
169
 
170
+ ## Related
171
+
172
+ - [@hytaleone/votifier](https://www.npmjs.com/package/@hytaleone/votifier) - Send votes to Hytale and Minecraft servers
173
+
66
174
  ---
67
175
 
68
176
  **[hytale.one](https://hytale.one/)** - Discover Hytale Servers
package/dist/index.cjs CHANGED
@@ -30,7 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- query: () => query
33
+ V2QueryType: () => V2QueryType,
34
+ V2ResponseFlag: () => V2ResponseFlag,
35
+ V2TLVType: () => V2TLVType,
36
+ clearChallengeCache: () => clearChallengeCache,
37
+ query: () => query,
38
+ queryV2: () => queryV2
34
39
  });
35
40
  module.exports = __toCommonJS(index_exports);
36
41
 
@@ -42,6 +47,8 @@ var REQUEST_MAGIC = Buffer.from("HYQUERY\0", "ascii");
42
47
  var RESPONSE_MAGIC = Buffer.from("HYREPLY\0", "ascii");
43
48
  var TYPE_BASIC = 0;
44
49
  var TYPE_FULL = 1;
50
+ var CAP_V2_PROTOCOL = 1;
51
+ var CAP_NETWORK_MODE = 2;
45
52
  function buildRequest(type) {
46
53
  const buf = Buffer.alloc(REQUEST_MAGIC.length + 1);
47
54
  REQUEST_MAGIC.copy(buf, 0);
@@ -53,31 +60,58 @@ var BufferReader = class {
53
60
  this.buf = buf;
54
61
  }
55
62
  offset = 0;
63
+ checkBounds(needed) {
64
+ if (this.offset + needed > this.buf.length) {
65
+ throw new RangeError(
66
+ `Buffer underflow: need ${needed} bytes at offset ${this.offset}, but buffer is ${this.buf.length} bytes`
67
+ );
68
+ }
69
+ }
56
70
  readBytes(length) {
71
+ this.checkBounds(length);
57
72
  const slice = this.buf.subarray(this.offset, this.offset + length);
58
73
  this.offset += length;
59
74
  return slice;
60
75
  }
76
+ readUInt8() {
77
+ this.checkBounds(1);
78
+ return this.buf[this.offset++];
79
+ }
61
80
  readUInt16LE() {
81
+ this.checkBounds(2);
62
82
  const value = this.buf.readUInt16LE(this.offset);
63
83
  this.offset += 2;
64
84
  return value;
65
85
  }
86
+ readUInt32LE() {
87
+ this.checkBounds(4);
88
+ const value = this.buf.readUInt32LE(this.offset);
89
+ this.offset += 4;
90
+ return value;
91
+ }
66
92
  readInt32LE() {
93
+ this.checkBounds(4);
67
94
  const value = this.buf.readInt32LE(this.offset);
68
95
  this.offset += 4;
69
96
  return value;
70
97
  }
71
98
  readBigInt64BE() {
99
+ this.checkBounds(8);
72
100
  const value = this.buf.readBigInt64BE(this.offset);
73
101
  this.offset += 8;
74
102
  return value;
75
103
  }
76
104
  readBoolean() {
105
+ this.checkBounds(1);
77
106
  return this.buf[this.offset++] !== 0;
78
107
  }
79
108
  readString() {
80
109
  const length = this.readUInt16LE();
110
+ if (length > this.remaining) {
111
+ throw new RangeError(
112
+ `Invalid string length ${length} at offset ${this.offset - 2}, only ${this.remaining} bytes remaining`
113
+ );
114
+ }
81
115
  const bytes = this.readBytes(length);
82
116
  return bytes.toString("utf8");
83
117
  }
@@ -86,9 +120,23 @@ var BufferReader = class {
86
120
  const lsb = this.readBigInt64BE();
87
121
  return formatUUID(msb, lsb);
88
122
  }
123
+ readBigInt64LE() {
124
+ this.checkBounds(8);
125
+ const value = this.buf.readBigInt64LE(this.offset);
126
+ this.offset += 8;
127
+ return value;
128
+ }
129
+ readUUIDLE() {
130
+ const msb = this.readBigInt64LE();
131
+ const lsb = this.readBigInt64LE();
132
+ return formatUUID(msb, lsb);
133
+ }
89
134
  get remaining() {
90
135
  return this.buf.length - this.offset;
91
136
  }
137
+ get position() {
138
+ return this.offset;
139
+ }
92
140
  };
93
141
  function formatUUID(msb, lsb) {
94
142
  const toHex = (n) => {
@@ -106,6 +154,18 @@ function validateResponse(buf) {
106
154
  }
107
155
  return buf.subarray(0, RESPONSE_MAGIC.length).equals(RESPONSE_MAGIC);
108
156
  }
157
+ function parseCapabilities(reader) {
158
+ if (reader.remaining >= 3) {
159
+ const caps = reader.readUInt16LE();
160
+ const v2Version = reader.readUInt8();
161
+ return {
162
+ supportsV2: (caps & CAP_V2_PROTOCOL) !== 0,
163
+ isNetworkMode: (caps & CAP_NETWORK_MODE) !== 0,
164
+ v2Version
165
+ };
166
+ }
167
+ return { supportsV2: false, isNetworkMode: false, v2Version: 0 };
168
+ }
109
169
  function parseBasicResponse(buf) {
110
170
  if (!validateResponse(buf)) {
111
171
  throw new Error("Invalid response: magic mismatch");
@@ -113,15 +173,25 @@ function parseBasicResponse(buf) {
113
173
  const reader = new BufferReader(buf);
114
174
  reader.readBytes(RESPONSE_MAGIC.length);
115
175
  reader.readBytes(1);
176
+ const serverName = reader.readString();
177
+ const motd = reader.readString();
178
+ const currentPlayers = reader.readInt32LE();
179
+ const maxPlayers = reader.readInt32LE();
180
+ const hostPort = reader.readUInt16LE();
181
+ const version = reader.readString();
182
+ const protocolVersion = reader.readInt32LE();
183
+ const protocolHash = reader.readString();
184
+ const capabilities = parseCapabilities(reader);
116
185
  return {
117
- serverName: reader.readString(),
118
- motd: reader.readString(),
119
- currentPlayers: reader.readInt32LE(),
120
- maxPlayers: reader.readInt32LE(),
121
- hostPort: reader.readUInt16LE(),
122
- version: reader.readString(),
123
- protocolVersion: reader.readInt32LE(),
124
- protocolHash: reader.readString()
186
+ serverName,
187
+ motd,
188
+ currentPlayers,
189
+ maxPlayers,
190
+ hostPort,
191
+ version,
192
+ protocolVersion,
193
+ protocolHash,
194
+ ...capabilities
125
195
  };
126
196
  }
127
197
  function parseFullResponse(buf) {
@@ -156,6 +226,7 @@ function parseFullResponse(buf) {
156
226
  enabled: reader.readBoolean()
157
227
  });
158
228
  }
229
+ const capabilities = parseCapabilities(reader);
159
230
  return {
160
231
  serverName,
161
232
  motd,
@@ -166,7 +237,8 @@ function parseFullResponse(buf) {
166
237
  protocolVersion,
167
238
  protocolHash,
168
239
  players,
169
- plugins
240
+ plugins,
241
+ ...capabilities
170
242
  };
171
243
  }
172
244
 
@@ -216,8 +288,327 @@ async function query(host, port = 5520, options = {}) {
216
288
  }
217
289
  return parseBasicResponse(response);
218
290
  }
291
+
292
+ // src/query-v2.ts
293
+ var import_node_dgram2 = __toESM(require("dgram"), 1);
294
+
295
+ // src/types-v2.ts
296
+ var V2QueryType = {
297
+ CHALLENGE: 0,
298
+ BASIC: 1,
299
+ PLAYERS: 2
300
+ };
301
+ var V2ResponseFlag = {
302
+ HAS_MORE_PLAYERS: 1,
303
+ AUTH_REQUIRED: 2,
304
+ IS_NETWORK: 16,
305
+ HAS_ADDRESS: 32
306
+ };
307
+ var V2TLVType = {
308
+ SERVER_INFO: 1,
309
+ PLAYER_LIST: 2
310
+ };
311
+
312
+ // src/protocol-v2.ts
313
+ var V2_REQUEST_MAGIC = Buffer.from("ONEQUERY", "ascii");
314
+ var V2_RESPONSE_MAGIC = Buffer.from("ONEREPLY", "ascii");
315
+ var CHALLENGE_TOKEN_SIZE = 32;
316
+ function buildChallengeRequest() {
317
+ const buf = Buffer.alloc(V2_REQUEST_MAGIC.length + 1);
318
+ V2_REQUEST_MAGIC.copy(buf, 0);
319
+ buf[V2_REQUEST_MAGIC.length] = V2QueryType.CHALLENGE;
320
+ return buf;
321
+ }
322
+ function buildV2Request(type, token, requestId, flags, offset, authToken) {
323
+ const authBuf = authToken ? Buffer.from(authToken, "utf8") : null;
324
+ const authLength = authBuf ? 2 + authBuf.length : 0;
325
+ const totalLength = V2_REQUEST_MAGIC.length + 1 + 32 + 4 + 2 + 4 + authLength;
326
+ const buf = Buffer.alloc(totalLength);
327
+ let pos = 0;
328
+ V2_REQUEST_MAGIC.copy(buf, pos);
329
+ pos += V2_REQUEST_MAGIC.length;
330
+ buf[pos++] = type;
331
+ token.copy(buf, pos);
332
+ pos += 32;
333
+ buf.writeUInt32LE(requestId, pos);
334
+ pos += 4;
335
+ buf.writeUInt16LE(flags, pos);
336
+ pos += 2;
337
+ buf.writeUInt32LE(offset, pos);
338
+ pos += 4;
339
+ if (authBuf) {
340
+ buf.writeUInt16LE(authBuf.length, pos);
341
+ pos += 2;
342
+ authBuf.copy(buf, pos);
343
+ }
344
+ return buf;
345
+ }
346
+ function validateV2Response(buf) {
347
+ if (buf.length < V2_RESPONSE_MAGIC.length) {
348
+ return false;
349
+ }
350
+ return buf.subarray(0, V2_RESPONSE_MAGIC.length).equals(V2_RESPONSE_MAGIC);
351
+ }
352
+ function parseChallengeResponse(buf) {
353
+ if (!validateV2Response(buf)) {
354
+ throw new Error("Invalid V2 response: magic mismatch");
355
+ }
356
+ const tokenOffset = V2_RESPONSE_MAGIC.length + 1;
357
+ if (buf.length < tokenOffset + CHALLENGE_TOKEN_SIZE) {
358
+ throw new Error("Invalid challenge response: too short");
359
+ }
360
+ return Buffer.from(buf.subarray(tokenOffset, tokenOffset + CHALLENGE_TOKEN_SIZE));
361
+ }
362
+ function parseV2ResponseHeader(buf) {
363
+ if (!validateV2Response(buf)) {
364
+ throw new Error("Invalid V2 response: magic mismatch");
365
+ }
366
+ const reader = new BufferReader(buf);
367
+ reader.readBytes(V2_RESPONSE_MAGIC.length);
368
+ const protocolVersion = reader.readUInt8();
369
+ const flags = reader.readUInt16LE();
370
+ const requestId = reader.readUInt32LE();
371
+ const payloadLength = reader.readUInt16LE();
372
+ return { protocolVersion, flags, requestId, payloadLength };
373
+ }
374
+ function parseTLVEntries(payload) {
375
+ const entries = [];
376
+ const reader = new BufferReader(payload);
377
+ while (reader.remaining >= 4) {
378
+ const type = reader.readUInt16LE();
379
+ const length = reader.readUInt16LE();
380
+ if (reader.remaining < length) {
381
+ throw new Error("Invalid TLV: truncated value");
382
+ }
383
+ const value = reader.readBytes(length);
384
+ entries.push({ type, value });
385
+ }
386
+ return entries;
387
+ }
388
+ function parseV2Response(buf) {
389
+ const header = parseV2ResponseHeader(buf);
390
+ const headerSize = V2_RESPONSE_MAGIC.length + 1 + 2 + 4 + 2;
391
+ const payload = buf.subarray(headerSize, headerSize + header.payloadLength);
392
+ const entries = parseTLVEntries(payload);
393
+ return { header, entries };
394
+ }
395
+ function parseTLVServerInfo(entry, hasAddress, isNetwork) {
396
+ if (entry.type !== V2TLVType.SERVER_INFO) {
397
+ throw new Error(`Expected SERVER_INFO TLV (${V2TLVType.SERVER_INFO}), got ${entry.type}`);
398
+ }
399
+ const reader = new BufferReader(entry.value);
400
+ const serverName = reader.readString();
401
+ const motd = reader.readString();
402
+ const currentPlayers = reader.readInt32LE();
403
+ const maxPlayers = reader.readInt32LE();
404
+ const version = reader.readString();
405
+ const protocolVersion = reader.readInt32LE();
406
+ const protocolHash = reader.readString();
407
+ const result = {
408
+ serverName,
409
+ motd,
410
+ currentPlayers,
411
+ maxPlayers,
412
+ version,
413
+ protocolVersion,
414
+ protocolHash,
415
+ isNetwork
416
+ };
417
+ if (hasAddress && reader.remaining >= 2) {
418
+ result.host = reader.readString();
419
+ if (reader.remaining >= 2) {
420
+ result.hostPort = reader.readUInt16LE();
421
+ }
422
+ }
423
+ return result;
424
+ }
425
+ function parseTLVPlayerList(entry) {
426
+ if (entry.type !== V2TLVType.PLAYER_LIST) {
427
+ throw new Error(`Expected PLAYER_LIST TLV (${V2TLVType.PLAYER_LIST}), got ${entry.type}`);
428
+ }
429
+ const reader = new BufferReader(entry.value);
430
+ const totalPlayers = reader.readInt32LE();
431
+ const playersInResponse = reader.readInt32LE();
432
+ const offset = reader.readInt32LE();
433
+ const players = [];
434
+ for (let i = 0; i < playersInResponse; i++) {
435
+ const name = reader.readString();
436
+ const uuid = reader.readUUIDLE();
437
+ players.push({ name, uuid });
438
+ }
439
+ return { players, totalPlayers, offset };
440
+ }
441
+ function findTLVEntry(entries, type) {
442
+ return entries.find((e) => e.type === type);
443
+ }
444
+
445
+ // src/query-v2.ts
446
+ var DEFAULT_TIMEOUT2 = 5e3;
447
+ var TOKEN_TTL_MS = 3e4;
448
+ var TOKEN_REFRESH_BUFFER_MS = 5e3;
449
+ var CLEANUP_INTERVAL_MS = 6e4;
450
+ var challengeCache = /* @__PURE__ */ new Map();
451
+ var cleanupScheduled = false;
452
+ function scheduleCleanup() {
453
+ if (cleanupScheduled) return;
454
+ cleanupScheduled = true;
455
+ setTimeout(() => {
456
+ const now = Date.now();
457
+ for (const [key, entry] of challengeCache) {
458
+ if (now >= entry.expiresAt) {
459
+ challengeCache.delete(key);
460
+ }
461
+ }
462
+ cleanupScheduled = false;
463
+ if (challengeCache.size > 0) {
464
+ scheduleCleanup();
465
+ }
466
+ }, CLEANUP_INTERVAL_MS).unref();
467
+ }
468
+ function clearChallengeCache() {
469
+ challengeCache.clear();
470
+ }
471
+ function sendUDP(host, port, data, timeout) {
472
+ return new Promise((resolve, reject) => {
473
+ const socket = import_node_dgram2.default.createSocket("udp4");
474
+ let timeoutHandle;
475
+ let closed = false;
476
+ const cleanup = () => {
477
+ if (closed) return;
478
+ closed = true;
479
+ clearTimeout(timeoutHandle);
480
+ try {
481
+ socket.close();
482
+ } catch {
483
+ }
484
+ };
485
+ socket.on("message", (msg) => {
486
+ cleanup();
487
+ resolve(msg);
488
+ });
489
+ socket.on("error", (err) => {
490
+ cleanup();
491
+ reject(err);
492
+ });
493
+ timeoutHandle = setTimeout(() => {
494
+ cleanup();
495
+ reject(new Error(`Query timeout after ${timeout}ms`));
496
+ }, timeout);
497
+ socket.send(data, port, host, (err) => {
498
+ if (err) {
499
+ cleanup();
500
+ reject(err);
501
+ }
502
+ });
503
+ });
504
+ }
505
+ async function fetchChallenge(host, port, timeout) {
506
+ const request = buildChallengeRequest();
507
+ const response = await sendUDP(host, port, request, timeout);
508
+ return parseChallengeResponse(response);
509
+ }
510
+ async function ensureChallenge(host, port, timeout) {
511
+ const key = `${host}:${port}`;
512
+ const cached = challengeCache.get(key);
513
+ const now = Date.now();
514
+ if (cached && now < cached.expiresAt - TOKEN_REFRESH_BUFFER_MS) {
515
+ cached.requestId++;
516
+ return cached;
517
+ }
518
+ const token = await fetchChallenge(host, port, timeout);
519
+ const entry = {
520
+ token,
521
+ expiresAt: now + TOKEN_TTL_MS,
522
+ requestId: 1
523
+ };
524
+ challengeCache.set(key, entry);
525
+ scheduleCleanup();
526
+ return entry;
527
+ }
528
+ async function performV2Query(host, port, type, offset, timeout, authToken) {
529
+ const challenge = await ensureChallenge(host, port, timeout);
530
+ const request = buildV2Request(type, challenge.token, challenge.requestId, 0, offset, authToken);
531
+ const response = await sendUDP(host, port, request, timeout);
532
+ return parseV2Response(response);
533
+ }
534
+ async function queryV2(host, port = 5520, options = {}) {
535
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT2;
536
+ const wantPlayers = options.players === true || options.players === "all";
537
+ const offset = options.playerOffset ?? 0;
538
+ const { header: basicHeader, entries: basicEntries } = await performV2Query(
539
+ host,
540
+ port,
541
+ V2QueryType.BASIC,
542
+ 0,
543
+ timeout,
544
+ options.authToken
545
+ );
546
+ const hasAddress = (basicHeader.flags & V2ResponseFlag.HAS_ADDRESS) !== 0;
547
+ const isNetwork = (basicHeader.flags & V2ResponseFlag.IS_NETWORK) !== 0;
548
+ const serverInfoEntry = findTLVEntry(basicEntries, V2TLVType.SERVER_INFO);
549
+ if (!serverInfoEntry) {
550
+ throw new Error("Response missing SERVER_INFO TLV");
551
+ }
552
+ const serverInfo = parseTLVServerInfo(serverInfoEntry, hasAddress, isNetwork);
553
+ if (!wantPlayers) {
554
+ return serverInfo;
555
+ }
556
+ const { header: playersHeader, entries: playersEntries } = await performV2Query(
557
+ host,
558
+ port,
559
+ V2QueryType.PLAYERS,
560
+ offset,
561
+ timeout,
562
+ options.authToken
563
+ );
564
+ const hasMorePlayers = (playersHeader.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;
565
+ const playerListEntry = findTLVEntry(playersEntries, V2TLVType.PLAYER_LIST);
566
+ if (!playerListEntry) {
567
+ throw new Error("Response missing PLAYER_LIST TLV");
568
+ }
569
+ const playerList = parseTLVPlayerList(playerListEntry);
570
+ let players = playerList.players;
571
+ let currentOffset = playerList.offset + players.length;
572
+ let hasMore = hasMorePlayers;
573
+ if (options.players === "all" && hasMore) {
574
+ const allPlayers = [...players];
575
+ while (hasMore) {
576
+ const nextResult = await performV2Query(
577
+ host,
578
+ port,
579
+ V2QueryType.PLAYERS,
580
+ currentOffset,
581
+ timeout,
582
+ options.authToken
583
+ );
584
+ const nextHasMore = (nextResult.header.flags & V2ResponseFlag.HAS_MORE_PLAYERS) !== 0;
585
+ const nextPlayerEntry = findTLVEntry(nextResult.entries, V2TLVType.PLAYER_LIST);
586
+ if (!nextPlayerEntry) {
587
+ break;
588
+ }
589
+ const nextPlayerList = parseTLVPlayerList(nextPlayerEntry);
590
+ allPlayers.push(...nextPlayerList.players);
591
+ currentOffset = nextPlayerList.offset + nextPlayerList.players.length;
592
+ hasMore = nextHasMore;
593
+ }
594
+ players = allPlayers;
595
+ hasMore = false;
596
+ }
597
+ return {
598
+ ...serverInfo,
599
+ players,
600
+ totalPlayers: playerList.totalPlayers,
601
+ offset: playerList.offset,
602
+ hasMore
603
+ };
604
+ }
219
605
  // Annotate the CommonJS export names for ESM import in node:
220
606
  0 && (module.exports = {
221
- query
607
+ V2QueryType,
608
+ V2ResponseFlag,
609
+ V2TLVType,
610
+ clearChallengeCache,
611
+ query,
612
+ queryV2
222
613
  });
223
614
  //# sourceMappingURL=index.cjs.map