mirror of
https://github.com/minescope/mineping.git
synced 2025-07-18 17:26:37 +03:00
Compare commits
6 Commits
d8d4a9a467
...
51b4771305
Author | SHA1 | Date | |
---|---|---|---|
51b4771305
|
|||
3c2c049c19
|
|||
cbaa1a3e3e
|
|||
435e59739c
|
|||
ef2bebe755
|
|||
27011d4091
|
23
README.md
23
README.md
@ -16,13 +16,16 @@ To install `mineping`, simply run the following command:
|
|||||||
npm i @minescope/mineping
|
npm i @minescope/mineping
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> To install _beta_ version (if available), run: `npm i @minescope/mineping@next`
|
||||||
|
|
||||||
## Loading and configuration the module
|
## Loading and configuration the module
|
||||||
|
|
||||||
### ES Modules (ESM)
|
### ES Modules (ESM)
|
||||||
|
|
||||||
If you are using ES Modules, you can import the library like this:
|
If you are using ES Modules, you can import the library like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { pingJava, pingBedrock } from '@minescope/mineping';
|
import { pingJava, pingBedrock } from "@minescope/mineping";
|
||||||
```
|
```
|
||||||
|
|
||||||
### CommonJS
|
### CommonJS
|
||||||
@ -31,8 +34,10 @@ import { pingJava, pingBedrock } from '@minescope/mineping';
|
|||||||
If you cannot switch to ESM, you can use the async `import()` function from CommonJS to load `mineping` asynchronously:
|
If you cannot switch to ESM, you can use the async `import()` function from CommonJS to load `mineping` asynchronously:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const pingJava = (...args) => import('@minescope/mineping').then(module => module.pingJava(...args));
|
const pingJava = (...args) =>
|
||||||
const pingBedrock = (...args) => import('@minescope/mineping').then(module => module.pingBedrock(...args));
|
import("@minescope/mineping").then((module) => module.pingJava(...args));
|
||||||
|
const pingBedrock = (...args) =>
|
||||||
|
import("@minescope/mineping").then((module) => module.pingBedrock(...args));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -40,20 +45,20 @@ const pingBedrock = (...args) => import('@minescope/mineping').then(module => mo
|
|||||||
Ping a Java server with default options:
|
Ping a Java server with default options:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { pingJava } from '@minescope/mineping';
|
import { pingJava } from "@minescope/mineping";
|
||||||
|
|
||||||
const data = await pingJava('mc.hypixel.net');
|
const data = await pingJava("mc.hypixel.net");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
```
|
```
|
||||||
|
|
||||||
Ping a Bedrock server with custom options:
|
Ping a Bedrock server with custom options:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { pingBedrock } from '@minescope/mineping';
|
import { pingBedrock } from "@minescope/mineping";
|
||||||
|
|
||||||
const data = await pingBedrock('mco.mineplex.com', {
|
const data = await pingBedrock("mco.mineplex.com", {
|
||||||
port: 19132,
|
port: 19132,
|
||||||
timeout: 500
|
timeout: 500,
|
||||||
});
|
});
|
||||||
console.log(data);
|
console.log(data);
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Reporting a Vulnerability
|
# Reporting a Vulnerability
|
||||||
|
|
||||||
If you believe you have found a security vulnerability, let us know by sending email to contact@zeldon.ru We will investigate that and do our best to quickly fix the problem.
|
If you believe you have found a security vulnerability, let me know by sending email to timofey@z4n.me I will investigate that and do my best to quickly fix the problem.
|
||||||
|
|
||||||
Please don't open an issue to or discuss this security vulnerability in a public place. Thanks for understanding!
|
Please don't open an issue to or discuss this security vulnerability in a public place. Thanks for understanding!
|
@ -1,5 +1,5 @@
|
|||||||
import { pingBedrock } from "../index.js";
|
import { pingBedrock } from "../index.js";
|
||||||
|
|
||||||
const host = "mc.nevertime.su";
|
const host = "0.0.0.0";
|
||||||
const ping = await pingBedrock(host);
|
const motd = await pingBedrock(host);
|
||||||
console.log(ping);
|
console.log(motd);
|
||||||
|
309
lib/bedrock.js
309
lib/bedrock.js
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Implementation of the RakNet ping/pong protocol.
|
* Implementation of the RakNet ping/pong protocol.
|
||||||
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol
|
* @see https://minecraft.wiki/w/RakNet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -9,169 +9,238 @@ import dgram from "node:dgram";
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
|
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
|
||||||
const START_TIME = new Date().getTime();
|
const START_TIME = Date.now();
|
||||||
const UNCONNECTED_PONG = 0x1c;
|
const UNCONNECTED_PONG = 0x1c;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of raw, semicolon-delimited MOTD string.
|
||||||
|
* This struct directly mirrors the fields and order from the server response.
|
||||||
|
* @see {@link https://minecraft.wiki/w/RakNet#Unconnected_Pong}
|
||||||
|
* @typedef {object} BedrockMotd
|
||||||
|
* @property {string} edition - The edition of the server (MCPE or MCEE)
|
||||||
|
* @property {string} name - The primary name of the server (first line of MOTD)
|
||||||
|
* @property {number} protocol - The protocol version
|
||||||
|
* @property {string} version - The game version (e.g., "1.21.2")
|
||||||
|
* @property {number} playerCount - The current number of players online
|
||||||
|
* @property {number} playerMax - The maximum number of players allowed
|
||||||
|
* @property {bigint} serverGuid - The server's GUID
|
||||||
|
* @property {string} subName - The secondary name of the server (second line of MOTD)
|
||||||
|
* @property {string} gamemode - The default gamemode (e.g., "Survival")
|
||||||
|
* @property {boolean | undefined} nintendoLimited - Whether the server is Nintendo limited
|
||||||
|
* @property {string | undefined} port - The server's IPv4 port, if provided
|
||||||
|
* @property {string | undefined} ipv6Port - The server's IPv6 port, if provided
|
||||||
|
* @property {string | undefined} editorMode - Whether the server is in editor mode, if provided. See: https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the structured and user-friendly response from a server ping.
|
||||||
|
* This is the public-facing object that users of the library will receive.
|
||||||
|
* @typedef {object} BedrockPingResponse
|
||||||
|
* @property {string} edition
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} levelName
|
||||||
|
* @property {string} gamemode
|
||||||
|
* @property {{ protocol: number, minecraft: string }} version
|
||||||
|
* @property {{ online: number, max: number }} players
|
||||||
|
* @property {{ v4: number | undefined, v6: number | undefined }} port
|
||||||
|
* @property {bigint} guid
|
||||||
|
* @property {boolean | undefined} isNintendoLimited
|
||||||
|
* @property {string | undefined} isEditorModeEnabled
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Unconnected Ping packet.
|
* Creates an Unconnected Ping packet.
|
||||||
* @param {number} pingId
|
* @param {number} timestamp - The current time delta since the script started
|
||||||
* @returns {Buffer}
|
* @returns {Buffer}
|
||||||
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Ping}
|
* @see {@link https://minecraft.wiki/w/RakNet#Unconnected_Ping}
|
||||||
*/
|
*/
|
||||||
const createUnconnectedPingFrame = (timestamp) => {
|
const createUnconnectedPingFrame = (timestamp) => {
|
||||||
const buffer = Buffer.alloc(33);
|
const buffer = Buffer.alloc(33);
|
||||||
buffer.writeUInt8(0x01, 0); // Packet ID
|
buffer.writeUInt8(0x01, 0); // Packet ID
|
||||||
buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
|
buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
|
||||||
Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic)
|
Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic bytes)
|
||||||
Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID
|
Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID
|
||||||
return buffer;
|
return buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract Modt from Unconnected Pong Packet and convert to an object
|
* Parses the semicolon-delimited MOTD string into a structured object.
|
||||||
* @param {Buffer} unconnectedPongPacket
|
* @param {string} motdString - The raw MOTD string from the server
|
||||||
* @returns {Object}
|
* @throws {Error} If the MOTD string is missing required fields
|
||||||
* @throws {Error} If packet is malformed or invalid
|
|
||||||
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Pong}
|
|
||||||
*/
|
*/
|
||||||
const extractModt = (unconnectedPongPacket) => {
|
const parseMotd = (motdString) => {
|
||||||
if (
|
const parts = motdString.split(";");
|
||||||
!Buffer.isBuffer(unconnectedPongPacket) ||
|
|
||||||
unconnectedPongPacket.length < 35
|
if (parts.length < 5) {
|
||||||
) {
|
throw new Error(
|
||||||
throw new Error("Invalid pong packet");
|
`Invalid MOTD format: Expected at least 5 fields, but got ${parts.length}.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = 33;
|
const [
|
||||||
const length = unconnectedPongPacket.readUInt16BE(offset);
|
edition,
|
||||||
|
name,
|
||||||
|
protocolStr,
|
||||||
|
version,
|
||||||
|
playerCountStr,
|
||||||
|
playerMaxStr,
|
||||||
|
serverGuidStr,
|
||||||
|
subName,
|
||||||
|
gamemode,
|
||||||
|
nintendoLimitedStr,
|
||||||
|
port,
|
||||||
|
ipv6Port,
|
||||||
|
editorModeStr,
|
||||||
|
] = parts;
|
||||||
|
|
||||||
// Check for buffer bounds
|
let nintendoLimited;
|
||||||
if (offset + 2 + length > unconnectedPongPacket.length) {
|
if (nintendoLimitedStr === "0") {
|
||||||
throw new Error("Malformed pong packet");
|
nintendoLimited = true;
|
||||||
|
} else if (nintendoLimitedStr === "1") {
|
||||||
|
nintendoLimited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let modt = unconnectedPongPacket.toString(
|
return {
|
||||||
"utf-8",
|
edition,
|
||||||
offset + 2,
|
name,
|
||||||
offset + 2 + length
|
protocol: Number(protocolStr),
|
||||||
);
|
version,
|
||||||
|
playerCount: Number(playerCountStr),
|
||||||
|
playerMax: Number(playerMaxStr),
|
||||||
|
serverGuid: BigInt(serverGuidStr),
|
||||||
|
subName,
|
||||||
|
gamemode,
|
||||||
|
nintendoLimited,
|
||||||
|
port: port ? Number(port) : undefined,
|
||||||
|
ipv6Port: ipv6Port ? Number(ipv6Port) : undefined,
|
||||||
|
editorMode: editorModeStr ? Boolean(Number(editorModeStr)) : undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const components = modt.split(";");
|
/**
|
||||||
|
* Transforms the raw MOTD object into a user-friendly, nested structure.
|
||||||
// Validate required components
|
* @param {BedrockMotd} motd - The parsed MOTD object
|
||||||
if (components.length < 9) {
|
* @returns {BedrockPingResponse}
|
||||||
throw new Error("Invalid MODT format");
|
*/
|
||||||
}
|
const transformMotd = (motd) => {
|
||||||
|
return {
|
||||||
const parsedComponents = {
|
edition: motd.edition,
|
||||||
edition: components[0],
|
name: motd.name,
|
||||||
name: components[1],
|
levelName: motd.subName,
|
||||||
|
gamemode: motd.gamemode,
|
||||||
version: {
|
version: {
|
||||||
protocolVersion: Number(components[2]),
|
protocol: motd.protocol,
|
||||||
minecraftVersion: components[3],
|
minecraft: motd.version,
|
||||||
},
|
},
|
||||||
players: {
|
players: {
|
||||||
online: Number(components[4]),
|
online: motd.playerCount,
|
||||||
max: Number(components[5]),
|
max: motd.playerMax,
|
||||||
},
|
},
|
||||||
serverId: components[6],
|
port: {
|
||||||
mapName: components[7],
|
v4: motd.port,
|
||||||
gameMode: components[8],
|
v6: motd.ipv6Port,
|
||||||
|
},
|
||||||
|
guid: motd.serverGuid,
|
||||||
|
isNintendoLimited: motd.nintendoLimited,
|
||||||
|
isEditorModeEnabled: motd.editorMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
return parsedComponents;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a ping request to the specified host and port.
|
* Extracts the MOTD string from an Unconnected Pong packet and parses it.
|
||||||
* @param {string} host - The IP address or hostname of the server.
|
* @param {Buffer} pongPacket - The raw pong packet from the server
|
||||||
* @param {number} [port=19132] - The port number.
|
* @returns {BedrockPingResponse}
|
||||||
* @param {function} cb - The callback function to handle the response.
|
* @throws {Error} If the packet is malformed
|
||||||
* @param {number} [timeout=5000] - The timeout duration in milliseconds.
|
|
||||||
*/
|
*/
|
||||||
const ping = (host, port = 19132, cb, timeout = 5000) => {
|
const parseUnconnectedPong = (pongPacket) => {
|
||||||
const socket = dgram.createSocket("udp4");
|
if (!Buffer.isBuffer(pongPacket) || pongPacket.length < 35) {
|
||||||
|
throw new Error("Invalid pong packet: buffer is too small.");
|
||||||
// Set manual timeout interval.
|
|
||||||
// This ensures the connection will NEVER hang regardless of internal state
|
|
||||||
const timeoutTask = setTimeout(() => {
|
|
||||||
socket.emit("error", new Error("Socket timeout"));
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
const closeSocket = () => {
|
|
||||||
socket.close();
|
|
||||||
clearTimeout(timeoutTask);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generic error handler
|
|
||||||
// This protects multiple error callbacks given the complex socket state
|
|
||||||
// This is mostly dangerous since it can swallow errors
|
|
||||||
let didFireError = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle any error that occurs during the ping process.
|
|
||||||
* @param {Error} err The error that occurred.
|
|
||||||
*/
|
|
||||||
const handleError = (err) => {
|
|
||||||
closeSocket();
|
|
||||||
|
|
||||||
if (!didFireError) {
|
|
||||||
didFireError = true;
|
|
||||||
cb(null, err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ping = createUnconnectedPingFrame(new Date().getTime() - START_TIME);
|
|
||||||
socket.send(ping, 0, ping.length, port, host);
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on("message", (pongPacket) => {
|
const packetId = pongPacket.readUInt8(0);
|
||||||
if (!Buffer.isBuffer(pongPacket) || pongPacket.length === 0) {
|
if (packetId !== UNCONNECTED_PONG) {
|
||||||
handleError(new Error("Invalid packet received"));
|
throw new Error(
|
||||||
return;
|
`Unexpected packet ID: 0x${packetId.toString(16)}. Expected 0x1c.`
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const id = pongPacket[0];
|
// The MOTD string is prefixed with its length as a 16-bit big-endian integer
|
||||||
if (id !== UNCONNECTED_PONG) {
|
const motdLength = pongPacket.readUInt16BE(33);
|
||||||
handleError(new Error(`Unexpected packet ID: 0x${id.toString(16)}`));
|
const motdOffset = 35;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (motdOffset + motdLength > pongPacket.length) {
|
||||||
const modtObject = extractModt(pongPacket);
|
throw new Error("Malformed pong packet: MOTD length exceeds buffer size.");
|
||||||
closeSocket();
|
}
|
||||||
cb(modtObject, null);
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("error", handleError);
|
const motdString = pongPacket.toString(
|
||||||
|
"utf-8",
|
||||||
|
motdOffset,
|
||||||
|
motdOffset + motdLength
|
||||||
|
);
|
||||||
|
|
||||||
|
const rawMotd = parseMotd(motdString);
|
||||||
|
const motd = transformMotd(rawMotd);
|
||||||
|
return motd;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously ping Minecraft Bedrock server.
|
* Asynchronously pings a Minecraft Bedrock server.
|
||||||
* The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property.
|
* @param {string} host - The IP address or hostname of the server
|
||||||
* @param {string} host The Bedrock server address.
|
* @param {object} [options] - Optional configuration
|
||||||
* @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Bedrock server.
|
* @param {number} [options.port=19132] - The server port
|
||||||
* @returns {Promise<import('../types/index.js').BedrockPingResponse>}
|
* @param {number} [options.timeout=5000] - The request timeout in milliseconds
|
||||||
|
* @returns {Promise<BedrockPingResponse>} A promise that resolves with the server's parsed MOTD
|
||||||
*/
|
*/
|
||||||
export const pingBedrock = (host, options = {}) => {
|
export const pingBedrock = (host, options = {}) => {
|
||||||
if (!host) throw new Error("Host argument is not provided");
|
if (!host) {
|
||||||
|
throw new Error("Host argument is required.");
|
||||||
|
}
|
||||||
|
|
||||||
const { port = 19132, timeout = 5000 } = options;
|
const { port = 19132, timeout = 5000 } = options;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ping(
|
const socket = dgram.createSocket("udp4");
|
||||||
host,
|
|
||||||
port,
|
// Prevent cleanup tasks from running more than once
|
||||||
(res, err) => {
|
// in case of multiple error callbacks
|
||||||
err ? reject(err) : resolve(res);
|
let isCleanupCompleted = false;
|
||||||
},
|
|
||||||
timeout
|
// Set a manual timeout interval to ensure
|
||||||
);
|
// the connection will NEVER hang regardless of internal state
|
||||||
|
const timeoutTask = setTimeout(() => {
|
||||||
|
socket.emit("error", new Error("Socket timeout"));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
// Idempotent function to handle cleanup tasks, we can safely call it multiple times without side effects
|
||||||
|
const cleanup = () => {
|
||||||
|
if (isCleanupCompleted) return;
|
||||||
|
isCleanupCompleted = true;
|
||||||
|
clearTimeout(timeoutTask);
|
||||||
|
socket.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic error handler
|
||||||
|
socket.on("error", (err) => {
|
||||||
|
cleanup();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message", (pongPacket) => {
|
||||||
|
try {
|
||||||
|
const motd = parseUnconnectedPong(pongPacket);
|
||||||
|
cleanup();
|
||||||
|
resolve(motd);
|
||||||
|
} catch (err) {
|
||||||
|
socket.emit("error", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pingPacket = createUnconnectedPingFrame(Date.now() - START_TIME);
|
||||||
|
socket.send(pingPacket, 0, pingPacket.length, port, host);
|
||||||
|
} catch (err) {
|
||||||
|
// Handle any immediate, synchronous errors that might occur when sending the ping packet
|
||||||
|
socket.emit("error", err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
19
lib/java.js
19
lib/java.js
@ -17,7 +17,14 @@ import varint from "./varint.js";
|
|||||||
* @param {number} [timeout=5000] The timeout duration in milliseconds.
|
* @param {number} [timeout=5000] The timeout duration in milliseconds.
|
||||||
* @param {number} [protocolVersion=-1] The protocol version of the Java client.
|
* @param {number} [protocolVersion=-1] The protocol version of the Java client.
|
||||||
*/
|
*/
|
||||||
function ping(host, virtualHost, port = 25565, cb, timeout = 5000, protocolVersion = -1) {
|
function ping(
|
||||||
|
host,
|
||||||
|
virtualHost,
|
||||||
|
port = 25565,
|
||||||
|
cb,
|
||||||
|
timeout = 5000,
|
||||||
|
protocolVersion = -1
|
||||||
|
) {
|
||||||
const socket = net.createConnection({ host, port });
|
const socket = net.createConnection({ host, port });
|
||||||
|
|
||||||
// Set manual timeout interval.
|
// Set manual timeout interval.
|
||||||
@ -41,10 +48,9 @@ function ping(host, virtualHost, port = 25565, cb, timeout = 5000, protocolVersi
|
|||||||
* @param {Error} err The error that occurred.
|
* @param {Error} err The error that occurred.
|
||||||
*/
|
*/
|
||||||
const handleError = (err) => {
|
const handleError = (err) => {
|
||||||
closeSocket();
|
|
||||||
|
|
||||||
if (!didFireError) {
|
if (!didFireError) {
|
||||||
didFireError = true;
|
didFireError = true;
|
||||||
|
closeSocket();
|
||||||
cb(null, err);
|
cb(null, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -132,7 +138,12 @@ function ping(host, virtualHost, port = 25565, cb, timeout = 5000, protocolVersi
|
|||||||
export function pingJava(host, options = {}) {
|
export function pingJava(host, options = {}) {
|
||||||
if (!host) throw new Error("Host argument is not provided");
|
if (!host) throw new Error("Host argument is not provided");
|
||||||
|
|
||||||
const { port = 25565, timeout = 5000, protocolVersion = -1, virtualHost = null } = options;
|
const {
|
||||||
|
port = 25565,
|
||||||
|
timeout = 5000,
|
||||||
|
protocolVersion = -1,
|
||||||
|
virtualHost = null,
|
||||||
|
} = options;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ping(
|
ping(
|
||||||
|
1499
package-lock.json
generated
Normal file
1499
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@ -1,22 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "@minescope/mineping",
|
"name": "@minescope/mineping",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0-beta.0",
|
||||||
"description": "Ping both Minecraft Bedrock and Java servers.",
|
"description": "Ping both Minecraft Bedrock and Java servers.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
"keywords": [],
|
"scripts": {
|
||||||
"author": {
|
"test": "vitest run",
|
||||||
"name": "Timofey (xzeldon)",
|
"test:watch": "vitest"
|
||||||
"email": "contact@zeldon.ru",
|
|
||||||
"url": "https://zeldon.ru"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/minescope/mineping.git"
|
"url": "git://github.com/minescope/mineping.git"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"license": "MIT",
|
||||||
|
"keywords": [],
|
||||||
|
"author": {
|
||||||
|
"name": "Timofey Gelazoniya",
|
||||||
|
"email": "timofey@z4n.me",
|
||||||
|
"url": "https://zeldon.ru"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"devDependencies": {
|
||||||
|
"vitest": "^3.2.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
174
test/bedrock.test.js
Normal file
174
test/bedrock.test.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import dgram from "node:dgram";
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { pingBedrock } from "../lib/bedrock.js";
|
||||||
|
|
||||||
|
vi.mock("node:dgram");
|
||||||
|
|
||||||
|
describe("bedrock.js", () => {
|
||||||
|
let mockSocket;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// A store for event handlers, closed over by the mockSocket.
|
||||||
|
const handlers = {};
|
||||||
|
|
||||||
|
// Create a stateful mock socket to simulate EventEmitter.
|
||||||
|
mockSocket = {
|
||||||
|
send: vi.fn(),
|
||||||
|
close: vi.fn(),
|
||||||
|
on: vi.fn((event, handler) => {
|
||||||
|
handlers[event] = handler;
|
||||||
|
}),
|
||||||
|
emit: vi.fn((event, ...args) => {
|
||||||
|
if (handlers[event]) {
|
||||||
|
handlers[event](...args);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
dgram.createSocket = vi.fn().mockReturnValue(mockSocket);
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ping a 3rd party server and parse MOTD", async () => {
|
||||||
|
const host = "play.example.com";
|
||||||
|
const options = { port: 25565, timeout: 10000 };
|
||||||
|
const pingPromise = pingBedrock(host, options);
|
||||||
|
|
||||||
|
const motd =
|
||||||
|
"MCPE;§l§bOasys§fPE §eГриф§7, §cДуэли§7, §aКейсы;0;1337;1070;1999;-138584171542148188;oasys-pe.ru;Adventure;1";
|
||||||
|
const mockPongPacket = createMockPongPacket(motd);
|
||||||
|
|
||||||
|
mockSocket.emit("message", mockPongPacket);
|
||||||
|
|
||||||
|
const result = await pingPromise;
|
||||||
|
|
||||||
|
expect(dgram.createSocket).toHaveBeenCalledWith("udp4");
|
||||||
|
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||||
|
expect.any(Buffer),
|
||||||
|
0,
|
||||||
|
33,
|
||||||
|
options.port,
|
||||||
|
host
|
||||||
|
);
|
||||||
|
expect(mockSocket.close).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual({
|
||||||
|
edition: "MCPE",
|
||||||
|
name: "§l§bOasys§fPE §eГриф§7, §cДуэли§7, §aКейсы",
|
||||||
|
levelName: "oasys-pe.ru",
|
||||||
|
gamemode: "Adventure",
|
||||||
|
version: {
|
||||||
|
protocol: 0,
|
||||||
|
minecraft: "1337",
|
||||||
|
},
|
||||||
|
players: {
|
||||||
|
online: 1070,
|
||||||
|
max: 1999,
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
v4: undefined,
|
||||||
|
v6: undefined,
|
||||||
|
},
|
||||||
|
guid: -138584171542148188n,
|
||||||
|
isNintendoLimited: false,
|
||||||
|
isEditorModeEnabled: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ping a BDS server with default `server.properties` and parse MOTD", async () => {
|
||||||
|
const host = "play.example.com";
|
||||||
|
const options = { port: 25565, timeout: 10000 };
|
||||||
|
const pingPromise = pingBedrock(host, options);
|
||||||
|
|
||||||
|
const motd =
|
||||||
|
"MCPE;Dedicated Server;800;1.21.84;0;10;11546321190880321782;Bedrock level;Survival;1;19132;19133;0;";
|
||||||
|
const mockPongPacket = createMockPongPacket(motd);
|
||||||
|
|
||||||
|
mockSocket.emit("message", mockPongPacket);
|
||||||
|
|
||||||
|
const result = await pingPromise;
|
||||||
|
|
||||||
|
expect(dgram.createSocket).toHaveBeenCalledWith("udp4");
|
||||||
|
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||||
|
expect.any(Buffer),
|
||||||
|
0,
|
||||||
|
33,
|
||||||
|
options.port,
|
||||||
|
host
|
||||||
|
);
|
||||||
|
expect(mockSocket.close).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual({
|
||||||
|
edition: "MCPE",
|
||||||
|
name: "Dedicated Server",
|
||||||
|
levelName: "Bedrock level",
|
||||||
|
gamemode: "Survival",
|
||||||
|
version: {
|
||||||
|
protocol: 800,
|
||||||
|
minecraft: "1.21.84",
|
||||||
|
},
|
||||||
|
players: {
|
||||||
|
online: 0,
|
||||||
|
max: 10,
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
v4: 19132,
|
||||||
|
v6: 19133,
|
||||||
|
},
|
||||||
|
guid: 11546321190880321782n,
|
||||||
|
isNintendoLimited: false,
|
||||||
|
isEditorModeEnabled: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
it("should throw an error if host is not provided", () => {
|
||||||
|
expect(() => pingBedrock(null)).toThrow("Host argument is required");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject on socket timeout", async () => {
|
||||||
|
const pingPromise = pingBedrock("play.example.com", { timeout: 1000 });
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("Socket timeout");
|
||||||
|
expect(mockSocket.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject on a generic socket error", async () => {
|
||||||
|
const pingPromise = pingBedrock("play.example.com");
|
||||||
|
|
||||||
|
// Simulate a DNS or network error by emitting it.
|
||||||
|
mockSocket.emit("error", new Error("EHOSTUNREACH"));
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("EHOSTUNREACH");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only reject once, even if multiple errors occur", async () => {
|
||||||
|
const pingPromise = pingBedrock("play.example.com");
|
||||||
|
|
||||||
|
// Fire a socket error first.
|
||||||
|
mockSocket.emit("error", new Error("First error"));
|
||||||
|
|
||||||
|
// Then, try to trigger another error by sending a bad message.
|
||||||
|
mockSocket.emit("message", Buffer.alloc(0));
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("First error");
|
||||||
|
expect(mockSocket.close).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockPongPacket(motd) {
|
||||||
|
const motdBuffer = Buffer.from(motd, "utf-8");
|
||||||
|
const packet = Buffer.alloc(35 + motdBuffer.length);
|
||||||
|
packet.writeUInt8(0x1c, 0);
|
||||||
|
packet.writeBigInt64LE(BigInt(Date.now()), 1);
|
||||||
|
Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(packet, 17);
|
||||||
|
packet.writeUInt16BE(motdBuffer.length, 33);
|
||||||
|
motdBuffer.copy(packet, 35);
|
||||||
|
return packet;
|
||||||
|
}
|
112
test/java.test.js
Normal file
112
test/java.test.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import net from "node:net";
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { pingJava } from "../lib/java.js";
|
||||||
|
import varint from "../lib/varint.js";
|
||||||
|
|
||||||
|
vi.mock("node:net");
|
||||||
|
|
||||||
|
describe("pingJava", () => {
|
||||||
|
let mockSocket;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockHandlers = {};
|
||||||
|
mockSocket = {
|
||||||
|
write: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
setNoDelay: vi.fn(),
|
||||||
|
on: vi.fn((event, handler) => (mockHandlers[event] = handler)),
|
||||||
|
emit: vi.fn((event, ...args) => mockHandlers[event]?.(...args)),
|
||||||
|
};
|
||||||
|
net.createConnection = vi.fn().mockReturnValue(mockSocket);
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ping a server and handle a chunked response", async () => {
|
||||||
|
const host = "mc.hypixel.net";
|
||||||
|
const options = {
|
||||||
|
port: 25565,
|
||||||
|
timeout: 5000,
|
||||||
|
protocolVersion: 765,
|
||||||
|
virtualHost: "mc.hypixel.net",
|
||||||
|
};
|
||||||
|
|
||||||
|
const pingPromise = pingJava(host, options);
|
||||||
|
|
||||||
|
mockSocket.emit("connect");
|
||||||
|
|
||||||
|
expect(net.createConnection).toHaveBeenCalledWith({
|
||||||
|
host,
|
||||||
|
port: options.port,
|
||||||
|
});
|
||||||
|
expect(mockSocket.setNoDelay).toHaveBeenCalledWith(true);
|
||||||
|
expect(mockSocket.write).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
version: { name: "1.21", protocol: 765 },
|
||||||
|
players: { max: 20, online: 5, sample: [] },
|
||||||
|
description: "A Minecraft Server",
|
||||||
|
favicon: "data:image/png;base64,iVBORw0KGgo...",
|
||||||
|
};
|
||||||
|
const fullPacket = createMockJavaResponse(mockResponse);
|
||||||
|
const chunk1 = fullPacket.subarray(0, 10);
|
||||||
|
const chunk2 = fullPacket.subarray(10);
|
||||||
|
|
||||||
|
mockSocket.emit("data", chunk1);
|
||||||
|
mockSocket.emit("data", chunk2);
|
||||||
|
|
||||||
|
const result = await pingPromise;
|
||||||
|
expect(result).toEqual(mockResponse);
|
||||||
|
expect(mockSocket.destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
it("should throw an error if host is not provided", () => {
|
||||||
|
expect(() => pingJava(null)).toThrow("Host argument is not provided");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject on socket timeout before data is received", async () => {
|
||||||
|
const pingPromise = pingJava("localhost", { timeout: 1000 });
|
||||||
|
mockSocket.emit("connect");
|
||||||
|
|
||||||
|
// Advance time to trigger the timeout
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("Socket timeout");
|
||||||
|
expect(mockSocket.destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject on connection error", async () => {
|
||||||
|
const pingPromise = pingJava("localhost");
|
||||||
|
|
||||||
|
// Simulate a connection refusal
|
||||||
|
mockSocket.emit("error", new Error("ECONNREFUSED"));
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("ECONNREFUSED");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only reject once, even if multiple errors occur", async () => {
|
||||||
|
const pingPromise = pingJava("localhost");
|
||||||
|
|
||||||
|
// Fire two errors back-to-back
|
||||||
|
mockSocket.emit("error", new Error("First error"));
|
||||||
|
mockSocket.emit("error", new Error("Second error"));
|
||||||
|
|
||||||
|
await expect(pingPromise).rejects.toThrow("First error");
|
||||||
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockJavaResponse(response) {
|
||||||
|
const jsonString = JSON.stringify(response);
|
||||||
|
const jsonBuffer = Buffer.from(jsonString, "utf8");
|
||||||
|
const responseLength = varint.encodeInt(jsonBuffer.length);
|
||||||
|
const packetId = varint.encodeInt(0);
|
||||||
|
const packetData = Buffer.concat([packetId, responseLength, jsonBuffer]);
|
||||||
|
const packetLength = varint.encodeInt(packetData.length);
|
||||||
|
return Buffer.concat([packetLength, packetData]);
|
||||||
|
}
|
76
test/varint.test.js
Normal file
76
test/varint.test.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import varint from "../lib/varint.js";
|
||||||
|
|
||||||
|
describe("varint.js", () => {
|
||||||
|
it("should encode and decode integers symmetrically (round-trip)", () => {
|
||||||
|
const testValues = [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
127, // Max 1-byte
|
||||||
|
128, // Min 2-byte
|
||||||
|
255,
|
||||||
|
16383, // Max 2-byte
|
||||||
|
16384, // Min 3-byte
|
||||||
|
2147483647, // Max signed 32-bit int
|
||||||
|
-1, // Critical edge case (encodes as max unsigned int)
|
||||||
|
];
|
||||||
|
|
||||||
|
testValues.forEach((value) => {
|
||||||
|
const encoded = varint.encodeInt(value);
|
||||||
|
const decoded = varint.decodeInt(encoded, 0);
|
||||||
|
expect(decoded, `Value ${value} failed round-trip`).toBe(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should decode an integer from a non-zero offset", () => {
|
||||||
|
// [255 (invalid varint), 128 (valid varint), 127 (valid varint)]
|
||||||
|
const buffer = Buffer.from([0xff, 0x80, 0x01, 0x7f]);
|
||||||
|
expect(varint.decodeInt(buffer, 1)).toBe(128);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error for a malformed varint that is too long", () => {
|
||||||
|
const invalidBuffer = Buffer.from([0x80, 0x80, 0x80, 0x80, 0x80, 0x80]);
|
||||||
|
expect(() => varint.decodeInt(invalidBuffer, 0)).toThrow(
|
||||||
|
"VarInt is too big"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly predict the encoded length of a varint", () => {
|
||||||
|
const boundaries = [0, 127, 128, 16383, 16384, 2097151, 2097152];
|
||||||
|
boundaries.forEach((value) => {
|
||||||
|
const predictedLength = varint.decodeLength(value);
|
||||||
|
const actualLength = varint.encodeInt(value).length;
|
||||||
|
expect(predictedLength).toBe(actualLength);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encode 16-bit unsigned shorts in big-endian format", () => {
|
||||||
|
expect(varint.encodeUShort(0)).toEqual(Buffer.from([0x00, 0x00]));
|
||||||
|
expect(varint.encodeUShort(256)).toEqual(Buffer.from([0x01, 0x00]));
|
||||||
|
expect(varint.encodeUShort(65535)).toEqual(Buffer.from([0xff, 0xff]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly assemble and parse a Minecraft handshake packet", () => {
|
||||||
|
const protocolVersion = -1;
|
||||||
|
const virtualHost = "mc.example.com";
|
||||||
|
const port = 25565;
|
||||||
|
|
||||||
|
const payload = Buffer.concat([
|
||||||
|
varint.encodeInt(0),
|
||||||
|
varint.encodeInt(protocolVersion),
|
||||||
|
varint.encodeInt(virtualHost.length),
|
||||||
|
varint.encodeString(virtualHost),
|
||||||
|
varint.encodeUShort(port),
|
||||||
|
varint.encodeInt(1),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const finalPacket = varint.concat([payload]);
|
||||||
|
|
||||||
|
const decodedPacketLength = varint.decodeInt(finalPacket, 0);
|
||||||
|
expect(decodedPacketLength).toBe(payload.length);
|
||||||
|
|
||||||
|
const lengthOfPacketLength = varint.decodeLength(decodedPacketLength);
|
||||||
|
const decodedPayload = finalPacket.subarray(lengthOfPacketLength);
|
||||||
|
expect(decodedPayload).toEqual(payload);
|
||||||
|
});
|
||||||
|
});
|
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
export * from "./lib/java.js";
|
|
||||||
export * from "./lib/bedrock.js";
|
|
61
types/lib/bedrock.d.ts
vendored
61
types/lib/bedrock.d.ts
vendored
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* @param port The server port (1-65535).
|
|
||||||
* @param timeout The read/write socket timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
export type BedrockPingOptions = {
|
|
||||||
port?: number & { _brand: "Port" }; // 1-65535
|
|
||||||
timeout?: number & { _brand: "Timeout" }; // > 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BedrockPingResponse = {
|
|
||||||
edition: string;
|
|
||||||
name: string;
|
|
||||||
version: {
|
|
||||||
protocolVersion: number;
|
|
||||||
minecraftVersion: string;
|
|
||||||
};
|
|
||||||
players: {
|
|
||||||
online: number;
|
|
||||||
max: number;
|
|
||||||
};
|
|
||||||
serverId: string;
|
|
||||||
mapName: string;
|
|
||||||
gameMode: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously ping Minecraft Bedrock server.
|
|
||||||
*
|
|
||||||
* @param host The Bedrock server address.
|
|
||||||
* @param options The configuration for pinging Minecraft Bedrock server.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* import { pingBedrock } from '@minescope/mineping';
|
|
||||||
*
|
|
||||||
* const data = await pingBedrock('mco.mineplex.com');
|
|
||||||
* console.log(data);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The resulting output will resemble:
|
|
||||||
* ```console
|
|
||||||
* {
|
|
||||||
* edition: "MCPE",
|
|
||||||
* name: "Mineplex",
|
|
||||||
* version: {
|
|
||||||
* protocolVersion: 475,
|
|
||||||
* minecraftVersion: "1.18.0"
|
|
||||||
* },
|
|
||||||
* players: {
|
|
||||||
* online: 5206,
|
|
||||||
* max: 5207
|
|
||||||
* },
|
|
||||||
* serverId: "12345678",
|
|
||||||
* mapName: "Lobby",
|
|
||||||
* gameMode: "Survival"
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function pingBedrock(
|
|
||||||
host: string,
|
|
||||||
options?: BedrockPingOptions
|
|
||||||
): Promise<BedrockPingResponse>;
|
|
83
types/lib/java.d.ts
vendored
83
types/lib/java.d.ts
vendored
@ -1,83 +0,0 @@
|
|||||||
/**
|
|
||||||
* @param port The server port.
|
|
||||||
* @param timeout The read/write socket timeout.
|
|
||||||
* @param protocolVersion The protocol version.
|
|
||||||
*/
|
|
||||||
export type JavaPingOptions = {
|
|
||||||
port?: number | undefined;
|
|
||||||
timeout?: number | undefined;
|
|
||||||
protocolVersion?: number | undefined;
|
|
||||||
virtualHost?: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON format chat component used for description field.
|
|
||||||
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Chat
|
|
||||||
*/
|
|
||||||
export type ChatComponent = {
|
|
||||||
text: string;
|
|
||||||
bold?: boolean;
|
|
||||||
italic?: boolean;
|
|
||||||
underlined?: boolean;
|
|
||||||
strikethrough?: boolean;
|
|
||||||
obfuscated?: boolean;
|
|
||||||
color?: string;
|
|
||||||
extra?: ChatComponent[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SampleProp = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `JSON Response` field of Response packet.
|
|
||||||
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Server_List_Ping#Status_Response
|
|
||||||
*/
|
|
||||||
export type JavaPingResponse = {
|
|
||||||
version: {
|
|
||||||
name: string;
|
|
||||||
protocol: number;
|
|
||||||
};
|
|
||||||
players: {
|
|
||||||
max: number;
|
|
||||||
online: number;
|
|
||||||
sample?: SampleProp[];
|
|
||||||
};
|
|
||||||
description: string | ChatComponent;
|
|
||||||
favicon?: string;
|
|
||||||
enforcesSecureChat?: boolean;
|
|
||||||
previewsChat?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously ping Minecraft Java server.
|
|
||||||
*
|
|
||||||
* The optional `options` argument can be an object with a `ping` (default is `25565`) or/and `timeout` (default is `5000`) property.
|
|
||||||
*
|
|
||||||
* @param host The Java server address.
|
|
||||||
* @param options The configuration for pinging Minecraft Java server.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* import { pingJava } from '@minescope/mineping';
|
|
||||||
*
|
|
||||||
* const data = await pingJava('mc.hypixel.net');
|
|
||||||
* console.log(data);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The resulting output will resemble:
|
|
||||||
* ```console
|
|
||||||
* {
|
|
||||||
* version: { name: 'Requires MC 1.8 / 1.18', protocol: 47 },
|
|
||||||
* players: { max: 200000, online: 67336, sample: [] },
|
|
||||||
* description: ' §f☃ §aHypixel Network §eTRIPLE COINS & EXP §f☃\n' +
|
|
||||||
* ' §6✰ §f§lHOLIDAY SALE §c§lUP TO 85% OFF §6✰',
|
|
||||||
* favicon: 'data:image/png;base64,iVBORw0KGg...
|
|
||||||
}
|
|
||||||
* ```
|
|
||||||
* @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/java.js#L117)
|
|
||||||
*/
|
|
||||||
export function pingJava(
|
|
||||||
host: string,
|
|
||||||
options?: JavaPingOptions
|
|
||||||
): Promise<JavaPingResponse>;
|
|
44
types/lib/varint.d.ts
vendored
44
types/lib/varint.d.ts
vendored
@ -1,44 +0,0 @@
|
|||||||
export default varint;
|
|
||||||
declare namespace varint {
|
|
||||||
/**
|
|
||||||
* Encodes an integer value into a varint byte buffer.
|
|
||||||
* @param val - The integer value to encode.
|
|
||||||
*/
|
|
||||||
function encodeInt(val: number): Buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a string value into a UTF-8 byte buffer.
|
|
||||||
* @param val - The string value to encode.
|
|
||||||
*/
|
|
||||||
function encodeString(val: string): Buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes an unsigned short value into a byte buffer.
|
|
||||||
* @param val - The unsigned short value to encode.
|
|
||||||
*/
|
|
||||||
function encodeUShort(val: number): Buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concatenates multiple byte buffers into a single byte buffer.
|
|
||||||
* @param chunks - An array of byte buffers to concatenate.
|
|
||||||
*/
|
|
||||||
function concat(chunks: Buffer[]): Buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a varint integer value from a buffer.
|
|
||||||
* @param buffer - The byte buffer to decode from.
|
|
||||||
* @param offset - The offset in the buffer to start decoding from.
|
|
||||||
*/
|
|
||||||
function decodeInt(buffer: Buffer, offset: number): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates how many bytes are needed to encode a number as a VarInt.
|
|
||||||
* VarInts use a variable number of bytes to efficiently encode integers.
|
|
||||||
* Each byte uses 7 bits for the value and 1 bit to indicate if more bytes follow.
|
|
||||||
* VarInts are never longer than 5 bytes.
|
|
||||||
*
|
|
||||||
* @param val - The number to calculate the VarInt length for.
|
|
||||||
* @returns The number of bytes needed to encode the value (1-5).
|
|
||||||
*/
|
|
||||||
function decodeLength(val: number): 1 | 2 | 3 | 4 | 5;
|
|
||||||
}
|
|
Reference in New Issue
Block a user