@deitylamb/mcping 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/.prettierrc +7 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/cli.cjs +107 -0
- package/dist/cli.mjs +108 -0
- package/dist/index.cjs +3 -0
- package/dist/index.mjs +2 -0
- package/dist/src-Baubd7mm.cjs +395 -0
- package/dist/src-HHWxQmnZ.mjs +364 -0
- package/package.json +44 -0
- package/src/ansi.ts +50 -0
- package/src/bedrock-ping.ts +88 -0
- package/src/cache.ts +42 -0
- package/src/chat.ts +45 -0
- package/src/cli.ts +113 -0
- package/src/index.ts +143 -0
- package/src/java-ping.ts +114 -0
- package/src/target.ts +65 -0
- package/src/varint.ts +53 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let node_dns_promises = require("node:dns/promises");
|
|
24
|
+
node_dns_promises = __toESM(node_dns_promises);
|
|
25
|
+
let node_net = require("node:net");
|
|
26
|
+
node_net = __toESM(node_net);
|
|
27
|
+
let node_dgram = require("node:dgram");
|
|
28
|
+
node_dgram = __toESM(node_dgram);
|
|
29
|
+
let node_crypto = require("node:crypto");
|
|
30
|
+
node_crypto = __toESM(node_crypto);
|
|
31
|
+
//#region src/target.ts
|
|
32
|
+
/**
|
|
33
|
+
* Resolves host and port, handling SRV records and lookups.
|
|
34
|
+
*/
|
|
35
|
+
async function resolveTarget(targetStr, type) {
|
|
36
|
+
let host = targetStr;
|
|
37
|
+
let port = type === "bedrock" ? 19132 : 25565;
|
|
38
|
+
const parts = targetStr.split(":");
|
|
39
|
+
if (parts.length === 2 && !isNaN(parseInt(parts[1]))) {
|
|
40
|
+
host = parts[0];
|
|
41
|
+
port = parseInt(parts[1]);
|
|
42
|
+
const ip = await resolveIp(host);
|
|
43
|
+
return {
|
|
44
|
+
host,
|
|
45
|
+
port,
|
|
46
|
+
protocol: type === null ? "java" : type,
|
|
47
|
+
ip
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (type !== "bedrock") try {
|
|
51
|
+
const srv = await node_dns_promises.resolveSrv(`_minecraft._tcp.${host}`);
|
|
52
|
+
if (srv && srv.length > 0) {
|
|
53
|
+
const srvHost = srv[0].name;
|
|
54
|
+
return {
|
|
55
|
+
host: srvHost,
|
|
56
|
+
port: srv[0].port,
|
|
57
|
+
protocol: "java",
|
|
58
|
+
ip: await resolveIp(srvHost)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {}
|
|
62
|
+
if (type === "bedrock" && parts.length === 1) try {
|
|
63
|
+
const srv = await node_dns_promises.resolveSrv(`_minecraft._udp.${host}`);
|
|
64
|
+
if (srv && srv.length > 0) {
|
|
65
|
+
const srvHost = srv[0].name;
|
|
66
|
+
return {
|
|
67
|
+
host: srvHost,
|
|
68
|
+
port: srv[0].port,
|
|
69
|
+
protocol: "bedrock",
|
|
70
|
+
ip: await resolveIp(srvHost)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {}
|
|
74
|
+
const ip = await resolveIp(host);
|
|
75
|
+
return {
|
|
76
|
+
host,
|
|
77
|
+
port,
|
|
78
|
+
protocol: type || "java",
|
|
79
|
+
ip
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function resolveIp(host) {
|
|
83
|
+
try {
|
|
84
|
+
return (await node_dns_promises.lookup(host)).address;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return host;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/varint.ts
|
|
91
|
+
/**
|
|
92
|
+
* VarInt encoding and decoding for Minecraft protocol.
|
|
93
|
+
* Based on the specification at https://wiki.vg/Protocol#VarInt_and_VarLong
|
|
94
|
+
*/
|
|
95
|
+
function encodeVarInt(value) {
|
|
96
|
+
const bytes = [];
|
|
97
|
+
let temp = value >>> 0;
|
|
98
|
+
while (temp >= 128) {
|
|
99
|
+
bytes.push(temp & 127 | 128);
|
|
100
|
+
temp >>>= 7;
|
|
101
|
+
}
|
|
102
|
+
bytes.push(temp);
|
|
103
|
+
return Buffer.from(bytes);
|
|
104
|
+
}
|
|
105
|
+
function decodeVarInt(buffer, offset = 0) {
|
|
106
|
+
let value = 0;
|
|
107
|
+
let length = 0;
|
|
108
|
+
let currentByte;
|
|
109
|
+
while (true) {
|
|
110
|
+
currentByte = buffer.readUInt8(offset + length);
|
|
111
|
+
value |= (currentByte & 127) << length * 7;
|
|
112
|
+
length++;
|
|
113
|
+
if (length > 5) throw new Error("VarInt is too big");
|
|
114
|
+
if ((currentByte & 128) !== 128) break;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
value: value | 0,
|
|
118
|
+
length
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function encodeString(str) {
|
|
122
|
+
const content = Buffer.from(str, "utf8");
|
|
123
|
+
return Buffer.concat([encodeVarInt(content.length), content]);
|
|
124
|
+
}
|
|
125
|
+
function decodeString(buffer, offset = 0) {
|
|
126
|
+
const { value: strLen, length: varIntLen } = decodeVarInt(buffer, offset);
|
|
127
|
+
return {
|
|
128
|
+
value: buffer.toString("utf8", offset + varIntLen, offset + varIntLen + strLen),
|
|
129
|
+
length: varIntLen + strLen
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/java-ping.ts
|
|
134
|
+
async function pingJava(host, port, timeout) {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const client = new node_net.Socket();
|
|
137
|
+
let startTime;
|
|
138
|
+
let response;
|
|
139
|
+
const timeoutHandler = setTimeout(() => {
|
|
140
|
+
client.destroy();
|
|
141
|
+
reject(/* @__PURE__ */ new Error(`Connection timed out after ${timeout}ms`));
|
|
142
|
+
}, timeout);
|
|
143
|
+
client.connect(port, host, () => {
|
|
144
|
+
const protocolVersion = encodeVarInt(763);
|
|
145
|
+
const serverAddress = encodeString(host);
|
|
146
|
+
const serverPort = Buffer.alloc(2);
|
|
147
|
+
serverPort.writeUInt16BE(port);
|
|
148
|
+
const nextState = encodeVarInt(1);
|
|
149
|
+
const handshakePayload = Buffer.concat([
|
|
150
|
+
protocolVersion,
|
|
151
|
+
serverAddress,
|
|
152
|
+
serverPort,
|
|
153
|
+
nextState
|
|
154
|
+
]);
|
|
155
|
+
const packetIdBuffer = encodeVarInt(0);
|
|
156
|
+
const handshake = Buffer.concat([
|
|
157
|
+
encodeVarInt(handshakePayload.length + packetIdBuffer.length),
|
|
158
|
+
packetIdBuffer,
|
|
159
|
+
handshakePayload
|
|
160
|
+
]);
|
|
161
|
+
client.write(handshake);
|
|
162
|
+
const statusRequest = Buffer.concat([encodeVarInt(1), encodeVarInt(0)]);
|
|
163
|
+
client.write(statusRequest);
|
|
164
|
+
startTime = Date.now();
|
|
165
|
+
});
|
|
166
|
+
let data = Buffer.alloc(0);
|
|
167
|
+
client.on("data", (chunk) => {
|
|
168
|
+
data = Buffer.concat([data, chunk]);
|
|
169
|
+
while (data.length > 0) try {
|
|
170
|
+
const { value: packetLen, length: varIntLen } = decodeVarInt(data, 0);
|
|
171
|
+
if (data.length < packetLen + varIntLen) return;
|
|
172
|
+
const packet = data.subarray(varIntLen, varIntLen + packetLen);
|
|
173
|
+
data = data.subarray(varIntLen + packetLen);
|
|
174
|
+
const { value: packetId, length: idLen } = decodeVarInt(packet, 0);
|
|
175
|
+
if (packetId === 0) {
|
|
176
|
+
const { value: jsonStr } = decodeString(packet, idLen);
|
|
177
|
+
response = JSON.parse(jsonStr);
|
|
178
|
+
const pingPayload = Buffer.alloc(8);
|
|
179
|
+
pingPayload.writeBigInt64BE(BigInt(startTime));
|
|
180
|
+
const pingPacket = Buffer.concat([
|
|
181
|
+
encodeVarInt(9),
|
|
182
|
+
encodeVarInt(1),
|
|
183
|
+
pingPayload
|
|
184
|
+
]);
|
|
185
|
+
client.write(pingPacket);
|
|
186
|
+
} else if (packetId === 1) {
|
|
187
|
+
const latency = Date.now() - startTime;
|
|
188
|
+
clearTimeout(timeoutHandler);
|
|
189
|
+
client.destroy();
|
|
190
|
+
resolve({
|
|
191
|
+
response,
|
|
192
|
+
latency
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
client.on("error", (err) => {
|
|
201
|
+
clearTimeout(timeoutHandler);
|
|
202
|
+
reject(err);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/bedrock-ping.ts
|
|
208
|
+
const RAKNET_MAGIC = Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex");
|
|
209
|
+
async function pingBedrock(host, port, timeout) {
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
const client = node_dgram.createSocket("udp4");
|
|
212
|
+
const timeoutHandler = setTimeout(() => {
|
|
213
|
+
client.close();
|
|
214
|
+
reject(/* @__PURE__ */ new Error(`Bedrock ping timed out after ${timeout}ms`));
|
|
215
|
+
}, timeout);
|
|
216
|
+
const startTime = BigInt(Date.now());
|
|
217
|
+
const clientGUID = node_crypto.randomBytes(8);
|
|
218
|
+
const packet = Buffer.alloc(33);
|
|
219
|
+
packet.writeUInt8(1, 0);
|
|
220
|
+
packet.writeBigInt64BE(startTime, 1);
|
|
221
|
+
RAKNET_MAGIC.copy(packet, 9);
|
|
222
|
+
clientGUID.copy(packet, 25);
|
|
223
|
+
client.send(packet, port, host);
|
|
224
|
+
client.on("message", (msg) => {
|
|
225
|
+
if (msg.readUInt8(0) === 28) {
|
|
226
|
+
clearTimeout(timeoutHandler);
|
|
227
|
+
client.close();
|
|
228
|
+
msg.readBigInt64BE(1);
|
|
229
|
+
msg.readBigInt64BE(9);
|
|
230
|
+
if (!msg.slice(17, 33).equals(RAKNET_MAGIC)) {
|
|
231
|
+
reject(/* @__PURE__ */ new Error("Invalid RakNet magic"));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const stringLength = msg.readUInt16BE(33);
|
|
235
|
+
const parts = msg.toString("utf8", 35, 35 + stringLength).split(";");
|
|
236
|
+
resolve({
|
|
237
|
+
edition: parts[0] || "MCPE",
|
|
238
|
+
motdLine1: parts[1] || "",
|
|
239
|
+
protocolVersion: parseInt(parts[2]) || 0,
|
|
240
|
+
versionName: parts[3] || "",
|
|
241
|
+
playerCount: parseInt(parts[4]) || 0,
|
|
242
|
+
maxPlayerCount: parseInt(parts[5]) || 0,
|
|
243
|
+
serverUniqueId: parts[6] || "",
|
|
244
|
+
motdLine2: parts[7] || "",
|
|
245
|
+
gameMode: parts[8] || "",
|
|
246
|
+
nintendoLimited: parseInt(parts[9]) || 0,
|
|
247
|
+
ipv4Port: parts[10] ? parseInt(parts[10]) : null,
|
|
248
|
+
ipv6Port: parts[11] ? parseInt(parts[11]) : null
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
client.on("error", (err) => {
|
|
253
|
+
clearTimeout(timeoutHandler);
|
|
254
|
+
client.close();
|
|
255
|
+
reject(err);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/chat.ts
|
|
261
|
+
function parseChat(chat) {
|
|
262
|
+
if (typeof chat === "string") return chat;
|
|
263
|
+
if (!chat || typeof chat !== "object") return "";
|
|
264
|
+
let text = "";
|
|
265
|
+
const colorMap = {
|
|
266
|
+
black: "0",
|
|
267
|
+
dark_blue: "1",
|
|
268
|
+
dark_green: "2",
|
|
269
|
+
dark_aqua: "3",
|
|
270
|
+
dark_red: "4",
|
|
271
|
+
dark_purple: "5",
|
|
272
|
+
gold: "6",
|
|
273
|
+
gray: "7",
|
|
274
|
+
dark_gray: "8",
|
|
275
|
+
blue: "9",
|
|
276
|
+
green: "a",
|
|
277
|
+
aqua: "b",
|
|
278
|
+
red: "c",
|
|
279
|
+
light_purple: "d",
|
|
280
|
+
yellow: "e",
|
|
281
|
+
white: "f"
|
|
282
|
+
};
|
|
283
|
+
if (chat.color && colorMap[chat.color]) text += `§${colorMap[chat.color]}`;
|
|
284
|
+
if (chat.bold) text += "§l";
|
|
285
|
+
if (chat.italic) text += "§o";
|
|
286
|
+
if (chat.underlined) text += "§n";
|
|
287
|
+
if (chat.strikethrough) text += "§m";
|
|
288
|
+
if (chat.obfuscated) text += "§k";
|
|
289
|
+
text += chat.text || "";
|
|
290
|
+
if (chat.extra && Array.isArray(chat.extra)) for (const part of chat.extra) text += parseChat(part);
|
|
291
|
+
return text;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/cache.ts
|
|
295
|
+
const cache = /* @__PURE__ */ new Map();
|
|
296
|
+
function getCache(target, options) {
|
|
297
|
+
const entry = cache.get(target);
|
|
298
|
+
if (!entry) return null;
|
|
299
|
+
if (!(Date.now() - entry.timestamp > options.ttl) || options.strategy === "swr") return {
|
|
300
|
+
...entry.response,
|
|
301
|
+
cached: true
|
|
302
|
+
};
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
function setCache(target, response) {
|
|
306
|
+
if (response.cached) return;
|
|
307
|
+
cache.set(target, {
|
|
308
|
+
response,
|
|
309
|
+
timestamp: Date.now()
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function isExpired(target, options) {
|
|
313
|
+
const entry = cache.get(target);
|
|
314
|
+
if (!entry) return true;
|
|
315
|
+
return Date.now() - entry.timestamp > options.ttl;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/index.ts
|
|
319
|
+
async function ping(target, options) {
|
|
320
|
+
const timeout = options?.timeout || 5e3;
|
|
321
|
+
const type = options?.type || null;
|
|
322
|
+
const cacheOptions = options?.cache;
|
|
323
|
+
if (cacheOptions) {
|
|
324
|
+
const cached = getCache(target, cacheOptions);
|
|
325
|
+
if (cached) {
|
|
326
|
+
if (cacheOptions.strategy === "swr" && isExpired(target, cacheOptions)) pingServer(target, timeout, type).then((refreshed) => setCache(target, refreshed)).catch(() => {});
|
|
327
|
+
return cached;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const result = await pingServer(target, timeout, type);
|
|
331
|
+
if (cacheOptions) setCache(target, result);
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
async function pingServer(target, timeout, type) {
|
|
335
|
+
const resolved = await resolveTarget(target, type);
|
|
336
|
+
let javaError;
|
|
337
|
+
if (type === "java" || type === null) try {
|
|
338
|
+
const { response, latency } = await pingJava(resolved.host, resolved.port, timeout);
|
|
339
|
+
return {
|
|
340
|
+
type: "java",
|
|
341
|
+
version: {
|
|
342
|
+
name: response.version.name,
|
|
343
|
+
protocol: response.version.protocol
|
|
344
|
+
},
|
|
345
|
+
players: {
|
|
346
|
+
online: response.players.online,
|
|
347
|
+
max: response.players.max
|
|
348
|
+
},
|
|
349
|
+
motd: parseChat(response.description),
|
|
350
|
+
latency,
|
|
351
|
+
target: {
|
|
352
|
+
host: resolved.host,
|
|
353
|
+
port: resolved.port,
|
|
354
|
+
ip: resolved.ip
|
|
355
|
+
},
|
|
356
|
+
raw: response
|
|
357
|
+
};
|
|
358
|
+
} catch (e) {
|
|
359
|
+
javaError = e;
|
|
360
|
+
if (type === "java") throw e;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const bedrockResolved = type === "bedrock" ? resolved : await resolveTarget(target, "bedrock");
|
|
364
|
+
const response = await pingBedrock(bedrockResolved.host, bedrockResolved.port, timeout);
|
|
365
|
+
return {
|
|
366
|
+
type: "bedrock",
|
|
367
|
+
version: {
|
|
368
|
+
name: response.versionName,
|
|
369
|
+
protocol: response.protocolVersion
|
|
370
|
+
},
|
|
371
|
+
players: {
|
|
372
|
+
online: response.playerCount,
|
|
373
|
+
max: response.maxPlayerCount
|
|
374
|
+
},
|
|
375
|
+
motd: response.motdLine2 ? `${response.motdLine1}\n${response.motdLine2}` : response.motdLine1,
|
|
376
|
+
latency: 0,
|
|
377
|
+
target: {
|
|
378
|
+
host: bedrockResolved.host,
|
|
379
|
+
port: bedrockResolved.port,
|
|
380
|
+
ip: bedrockResolved.ip
|
|
381
|
+
},
|
|
382
|
+
raw: response
|
|
383
|
+
};
|
|
384
|
+
} catch (bedrockErr) {
|
|
385
|
+
if (type === null && javaError) throw new Error(`Both Java and Bedrock pings failed. Java: ${javaError.message}. Bedrock: ${bedrockErr.message}`);
|
|
386
|
+
throw bedrockErr;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
Object.defineProperty(exports, "ping", {
|
|
391
|
+
enumerable: true,
|
|
392
|
+
get: function() {
|
|
393
|
+
return ping;
|
|
394
|
+
}
|
|
395
|
+
});
|