19 Commits

Author SHA1 Message Date
838ffc497a chore: bump version to 1.5.0 2024-10-09 22:00:11 +03:00
502029869a style: use "node:" prefix for imports 2024-10-09 21:59:16 +03:00
e3e7e293ed fix: use vanilla ping format 2024-10-09 21:57:37 +03:00
88ad92e59d chore: add example for single server ping 2024-10-09 21:55:01 +03:00
009f542c55 chore: bump version to 1.4.1 2024-03-31 02:02:08 +03:00
0b0bed4e71 docs: some clarifications in createUnconnectedPingFrame 2024-03-31 02:01:46 +03:00
fa4c34d896 chore: bump version to 1.4.0 2024-03-31 01:56:43 +03:00
296294ca96 refactor!: changes in bedrock protocol code
BREAKING CHANGE: new bedrock ping response format
2024-03-31 01:56:03 +03:00
9d25aaf4ea chore: bump version to 1.3.0 2024-03-30 16:42:44 +03:00
c735604c38 fix: #5 add gameVersion field for Bedrock
Add gameVersion field in BedrockPingResponse

Closes: #5
2024-03-30 16:41:42 +03:00
afdaa9eb3e chore(package.json): bump version to 1.2.2 2023-12-12 00:44:41 +03:00
435899309f Merge pull request #4 from sya-ri/fix-types/optional
fix(types): Change options to optional
2023-12-12 00:44:20 +03:00
13e6b8c6ff fix(types): Change options to optional 2023-12-09 22:38:37 +09:00
d7256eabe7 chore(package.json): bump version to 1.2.1 2023-12-09 12:58:53 +03:00
afa2c3025f fix(bedrock.js): resolve UNCONNECTED_PING formation issue
- Simplify UNCONNECTED_PING function
- Address an issue where certain servers, particularly those based on Pocketmine, were unresponsive to Unconnected Ping requests
2023-12-09 12:57:43 +03:00
6c297d0b8c chore(package.json): bump version to 1.2.0 2023-12-08 15:22:53 +03:00
283e9b32c6 Merge pull request #3 from inotflying/patch-1
feat(pingJava): The ability to set a specific protocol version in the parameters. According to https://wiki.vg/Server_List_Ping#:~:text=0x00-,Protocol%20Version,-VarInt
2023-12-08 15:22:07 +03:00
354fa212a6 fix(javaPing): default protocolVersion value 2023-12-08 16:16:30 +04:00
d9bf4cfb3f feat(pingJava): add protocolVersion
feat(types): `PingOptions` for Java

fix(types): type names

fix(types): type names
2023-12-08 16:04:35 +04:00
6 changed files with 82 additions and 141 deletions

5
example/single.js Normal file
View File

@ -0,0 +1,5 @@
import { pingBedrock } from "../index.js";
const host = "mc.nevertime.su";
const ping = await pingBedrock(host);
console.log(ping);

View File

@ -1,124 +1,61 @@
/** /**
* Implementation of the RakNet ping/pong protocol. * Implementation of the RakNet ping/pong protocol.
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping * @see https://wiki.vg/Raknet_Protocol
*
* Data types:
* @see https://wiki.vg/Raknet_Protocol#Data_types
*/ */
'use strict'; 'use strict';
import dgram from 'dgram'; import dgram from 'node:dgram';
import crypto from 'node:crypto'
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = new Date().getTime(); const START_TIME = new Date().getTime();
/** /**
* Creates a buffer with the specified length. * Creates an Unconnected Ping packet.
* @param {number} length - The length of the buffer. * @param {number} pingId
* @returns {Buffer} - The created buffer. * @returns {Buffer}
*/
const createBuffer = (length) => {
const buffer = Buffer.alloc(length);
buffer[0] = 0x01;
return buffer;
};
/**
* Writes a BigInt value to the buffer at the specified offset using big-endian byte order.
* @param {Buffer} buffer - The buffer to write to.
* @param {number} value - The BigInt value to write.
* @param {number} offset - The offset in the buffer to write the value.
*/
const writeBigInt64BE = (buffer, value, offset) => {
buffer.writeBigInt64BE(BigInt(value), offset);
};
/**
* Copies the specified hex value to the buffer at the specified offset.
* @param {Buffer} buffer - The buffer to copy to.
* @param {string} hex - The hex value to copy.
* @param {number} offset - The offset in the buffer to copy the value.
*/
const copyHexToBuffer = (buffer, hex, offset) => {
Buffer.from(hex, 'hex').copy(buffer, offset);
};
/**
* Reads a BigInt value from the buffer at the specified offset using big-endian byte order.
* @param {Buffer} buffer - The buffer to read from.
* @param {number} offset - The offset in the buffer to read the value.
* @returns {BigInt} - The read BigInt value.
*/
const readBigInt64BE = (buffer, offset) => {
return buffer.readBigInt64BE(offset);
};
/**
* Reads a string from the buffer at the specified offset.
* @param {Buffer} buffer - The buffer to read from.
* @param {number} offset - The offset in the buffer to read the string.
* @returns {string} - The read string.
*/
const readStringFromBuffer = (buffer, offset) => {
const length = buffer.readUInt16BE(offset);
return buffer.toString('utf8', offset + 2, offset + 2 + length);
};
/**
* Parses the advertise string into an object with properties.
* @param {string} advertiseStr - The advertise string to parse.
* @returns {Object} - The parsed object with properties.
*/
const parseAdvertiseString = (advertiseStr) => {
const parts = advertiseStr.split(';');
return {
gameId: parts[0],
description: parts[1],
protocolVersion: parts[2],
gameVersion: parts[3],
currentPlayers: parts[4],
maxPlayers: parts[5],
name: parts[7],
mode: parts[8]
};
};
/**
* Creates an Unconnected Ping buffer.
* @param {number} pingId - The ping ID.
* @returns {Buffer} - The Unconnected Ping buffer.
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping}
*/ */
const UNCONNECTED_PING = (pingId) => { const createUnconnectedPingFrame = (timestamp) => {
const buffer = createBuffer(35); const buffer = Buffer.alloc(33);
writeBigInt64BE(buffer, pingId, 1); buffer.writeUInt8(0x01, 0); // Packet ID
copyHexToBuffer(buffer, '00ffff00fefefefefdfdfdfd12345678', 9); buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
writeBigInt64BE(buffer, 0, 25); Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic)
Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID
return buffer; return buffer;
}; };
/** /**
* Decodes an Unconnected Pong buffer and returns the parsed data. * Extract Modt from Unconnected Pong Packet and convert to an object
* @param {Buffer} buffer - The Unconnected Pong buffer. * @param {Buffer} unconnectedPongPacket
* @returns {Object} - The parsed Unconnected Pong data. * @returns {Object}
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong}
*/ */
const UNCONNECTED_PONG = (buffer) => { const extractModt = (unconnectedPongPacket) => {
const pingId = readBigInt64BE(buffer, 1); // Skip everything to Modt
const serverId = readBigInt64BE(buffer, 9); const offset = 33;
let offset = 25; const length = unconnectedPongPacket.readUInt16BE(offset);
let advertiseStr; let modt = unconnectedPongPacket.toString("utf-8", offset + 2, offset + 2 + length);
try { const components = modt.split(';');
advertiseStr = readStringFromBuffer(buffer, offset); const parsedComponents = {
} catch (err) { edition: components[0],
const length = parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)); name: components[1],
advertiseStr = buffer.toString('utf8', offset, offset + length); version: {
} protocolVersion: Number(components[2]),
minecraftVersion: components[3],
},
players: {
online: Number(components[4]),
max: Number(components[5])
},
serverId: components[6],
mapName: components[7],
gameMode: components[8]
};
const parsedAdvertiseStr = parseAdvertiseString(advertiseStr); return parsedComponents;
return { pingId, advertiseStr, serverId, offset, ...parsedAdvertiseStr };
}; };
/** /**
@ -161,33 +98,20 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
}; };
try { try {
const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME); const ping = createUnconnectedPingFrame(new Date().getTime() - START_TIME);
socket.send(ping, 0, ping.length, port, host); socket.send(ping, 0, ping.length, port, host);
} catch (err) { } catch (err) {
handleError(err); handleError(err);
} }
socket.on('message', (msg) => { socket.on('message', (pongPacket) => {
const id = msg[0]; const id = pongPacket[0];
switch (id) { switch (id) {
case 0x1c: { case 0x1c: {
const pong = UNCONNECTED_PONG(msg); const modtObject = extractModt(pongPacket);
const clientData = {
version: {
name: pong.name,
protocol: pong.protocolVersion
},
players: {
max: pong.maxPlayers,
online: pong.currentPlayers
},
description: pong.description.replace(/\xA7[0-9A-FK-OR]/ig, ''),
gamemode: pong.mode
};
closeSocket(); closeSocket();
cb(clientData, null); cb(modtObject, null);
break; break;
} }

View File

@ -5,19 +5,18 @@
'use strict'; 'use strict';
import net from 'net'; import net from 'node:net';
import varint from './varint.js'; import varint from './varint.js';
const PROTOCOL_VERSION = 0;
/** /**
* Ping a Minecraft Java server. * Ping a Minecraft Java server.
* @param {string} host The host of the Java server. * @param {string} host The host of the Java server.
* @param {number} [port=25565] The port of the Java server. * @param {number} [port=25565] The port of the Java server.
* @param {function} cb The callback function to handle the ping response. * @param {function} cb The callback function to handle the ping response.
* @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.
*/ */
function ping(host, port = 25565, cb, timeout = 5000) { function ping(host, 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.
@ -56,7 +55,7 @@ function ping(host, port = 25565, cb, timeout = 5000) {
socket.on('connect', () => { socket.on('connect', () => {
const handshake = varint.concat([ const handshake = varint.concat([
varint.encodeInt(0), varint.encodeInt(0),
varint.encodeInt(PROTOCOL_VERSION), varint.encodeInt(protocolVersion),
varint.encodeInt(host.length), varint.encodeInt(host.length),
varint.encodeString(host), varint.encodeString(host),
varint.encodeUShort(port), varint.encodeUShort(port),
@ -118,7 +117,7 @@ function ping(host, port = 25565, cb, timeout = 5000) {
/** /**
* Asynchronously ping Minecraft Java server. * 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. * The optional `options` argument can be an object with a `port` (default is `25565`) or/and `timeout` (default is `5000`) or/and `protocolVersion` (default is `-1`) property.
* @param {string} host The Java server address. * @param {string} host The Java server address.
* @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Java server. * @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Java server.
* @returns {Promise<import('../types/index.js').JavaPingResponse>} * @returns {Promise<import('../types/index.js').JavaPingResponse>}
@ -126,11 +125,11 @@ function ping(host, port = 25565, cb, timeout = 5000) {
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 } = options; const { port = 25565, timeout = 5000, protocolVersion = -1 } = options;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ping(host, port, (res, err) => { ping(host, port, (res, err) => {
err ? reject(err) : resolve(res); err ? reject(err) : resolve(res);
}, timeout); }, timeout, protocolVersion);
}); });
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@minescope/mineping", "name": "@minescope/mineping",
"version": "1.1.1", "version": "1.5.0",
"description": "Ping both Minecraft Bedrock and Java servers.", "description": "Ping both Minecraft Bedrock and Java servers.",
"main": "index.js", "main": "index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",

View File

@ -2,22 +2,25 @@
* @param port The server port. * @param port The server port.
* @param timeout The read/write socket timeout. * @param timeout The read/write socket timeout.
*/ */
export type PingOptions = { export type BedrockPingOptions = {
port: number, port?: number;
timeout: number; timeout?: number;
}; };
export type BedrockPingResponse = { export type BedrockPingResponse = {
version: { edition: string;
name: string; name: string;
protocol: string; version: {
protocolVersion: number;
minecraftVersion: string;
}; };
players: { players: {
max: string; online: number;
online: string; max: number;
}; };
description: string; serverId: string;
gamemode: string; mapName: string;
gameMode: string;
}; };
/** /**
@ -26,6 +29,7 @@ export type BedrockPingResponse = {
* The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property. * The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property.
* *
* @param host The Bedrock server address. * @param host The Bedrock server address.
* @param options The configuration for pinging Minecraft Bedrock server.
* *
* ```js * ```js
* import { pingBedrock } from '@minescope/mineping'; * import { pingBedrock } from '@minescope/mineping';
@ -45,5 +49,5 @@ export type BedrockPingResponse = {
* ``` * ```
* @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/bedrock.js#L204) * @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/bedrock.js#L204)
*/ */
export function pingBedrock(host: string, options?: PingOptions): Promise<BedrockPingResponse>; export function pingBedrock(host: string, options?: BedrockPingOptions): Promise<BedrockPingResponse>;

13
types/lib/java.d.ts vendored
View File

@ -1,4 +1,13 @@
import { PingOptions } from "./bedrock"; /**
* @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;
};
/** /**
* JSON format chat component used for description field. * JSON format chat component used for description field.
@ -67,5 +76,5 @@ export type JavaPingResponse = {
* ``` * ```
* @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/java.js#L117) * @see [source](https://github.com/minescope/mineping/blob/915edbec9c9ad811459458600af3531ec0836911/lib/java.js#L117)
*/ */
export function pingJava(host: string, options?: PingOptions): Promise<JavaPingResponse>; export function pingJava(host: string, options?: JavaPingOptions): Promise<JavaPingResponse>;