32 Commits

Author SHA1 Message Date
0b5c5e2938 chore: bump version to 1.6.0 2025-02-07 08:02:26 +03:00
73a2fffe8b feat: add virtualHost option (#6) 2025-02-07 08:01:12 +03:00
9469564736 docs: update references to wiki.vg
wiki.vg has been shut down and is in the process of being merged into minecraft.wiki
2024-12-28 22:47:11 +03:00
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
9dace3748b docs(README.md): improve clarity and grammar in the project description 2023-10-22 22:49:53 +03:00
0aa73655b1 fix(parallel.js): add break statement to stop iterating over results if a promise is rejected 2023-10-22 21:38:34 +03:00
78ca03b004 chore(package.json): update version from 1.1.0 to 1.1.1 2023-10-22 21:32:33 +03:00
910184bf5f refactoring and comments
fix(cli.js): refactor help and error handling logic for better readability and maintainability
feat(cli.js): add support for custom port and timeout options
fix(parallel.js): update list of hosts to ping
fix(bedrock.js): add comments and improve error handling in ping function
fix(java.js): add comments and improve error handling in ping function
fix(varint.js): add comments to functions and improve readability
fix(index.d.ts): export all functions from java.js and bedrock.js
fix(lib/bedrock.d.ts): update source link
fix(lib/java.d.ts): update source link
2023-10-22 21:32:00 +03:00
915edbec9c update version from 1.0.4 to 1.1.0 2023-10-22 02:29:54 +03:00
0fe675385f bedrock.js refactoring, remove bytebuffer dependency
add parallel.js file to demonstrate parallel pinging of multiple Bedrock servers
2023-10-22 02:29:20 +03:00
c3158ac925 Merge pull request #2 from sya-ri/update-java-ping-response-type
Update outdated JavaPingResponse type
2023-10-22 00:39:14 +03:00
31c58ee637 Update outdated JavaPingResponse type 2023-10-22 06:04:58 +09:00
7cd3444a4f Merge pull request #1 from romanalexander/fixes/bedrock-ping
Fix ping never firing an error when timeout occurs.
2023-10-09 02:21:00 +03:00
af78a184db Fix ping never firing an error when timeout occurs. 2023-10-08 16:22:18 -04:00
12 changed files with 491 additions and 370 deletions

View File

@ -1,6 +1,6 @@
# mineping
`mineping` is a Javasript library thar provides Minecraft server ping protocol implementation. It can be used to collect information about the server, such as MODT, current online, server icon (java edition only) and etc.
This JavaScript library provides an implementation of the Minecraft server ping protocol. **It allows you to gather information about a Minecraft server**, such as the MOTD, current online players, server icon (Java Edition only), and more.
Mirror on my [<img src="https://git.zeldon.ru/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.zeldon.ru/zeldon/mineping)
@ -10,6 +10,8 @@ Mirror on my [<img src="https://git.zeldon.ru/assets/img/logo.svg" align="center
## Install
To install `mineping`, simply run the following command:
```
npm i @minescope/mineping
```
@ -17,6 +19,7 @@ npm i @minescope/mineping
## Loading and configuration the module
### ES Modules (ESM)
If you are using ES Modules, you can import the library like this:
```js
import { pingJava, pingBedrock } from '@minescope/mineping';
@ -59,5 +62,7 @@ console.log(data);
## Acknowledgements
Special thanks to the following projects:
- [mcping](https://github.com/Scetch/mcping) crate for Rust
- [mcping-js](https://github.com/Cryptkeeper/mcping-js) library for quering Minecraft Java Edition servers

View File

@ -1,19 +1,15 @@
/**
* Usage examples:
* - Java (with custom timeout): node cli.js -j --host="mc.hypixel.net" --timeout 1000
* - Bedrock: node cli.js -b --host="play.timecrack.net"
*/
import { pingBedrock, pingJava } from '../index.js';
const args = getArgs();
if (args.help || args.h || Object.keys(args).length === 0) {
console.log(`node cli.js [..]
A simple to use, efficient, and full-featured Minecraft server info parser!
USAGE:
node cli.js [OPTIONS] --host <HOST> --port <PORT> --timeout <TIMEOUT>
OPTIONS:
-j Use for Minecraft Java Edition
-b Use for Minecraft Bedrock Edition
P.S. Don't use them at the same time!`);
if (shouldShowHelp(args)) {
printHelp();
process.exit(1);
}
@ -24,6 +20,42 @@ if (!args.host) {
// easter egg <3
if (args.j && args.b) {
printInterestingFacts();
process.exit(0);
}
const port = args.port || getDefaultPort(args);
const timeout = args.timeout || 500;
if (args.j) {
await pingJavaServer(args.host, port, timeout)
.catch(err => console.error(`ERROR: ${err.message}`));
} else if (args.b) {
await pingBedrockServer(args.host, port, timeout)
.catch(err => console.error(`ERROR: ${err.message}`));
} else {
console.error('ERROR: Unsupported flag passed. Use -h or --help.');
}
function shouldShowHelp(args) {
return args.help || args.h || Object.keys(args).length === 0;
}
function printHelp() {
console.log(`node cli.js [..]
A simple to use, efficient, and full-featured Minecraft server info parser!
USAGE:
node cli.js [OPTIONS] --host <HOST> --port <PORT> --timeout <TIMEOUT>
OPTIONS:
-j Use for Minecraft Java Edition
-b Use for Minecraft Bedrock Edition
P.S. Don't use them at the same time!`);
}
function printInterestingFacts() {
console.log(`Some interesting facts about MOTDs on bedrock:
- so far they seem to exclusively use legacy color codes
- the random style has a special impl for periods, they turn into animated
@ -31,25 +63,20 @@ if (args.j && args.b) {
- motd_2 is ignored? client displays "motd_1 - v{version}", where the
appended version text is considered part of motd_1 for color code processing
- motd_2 seems to mainly be used to return the server software in use (e.g. PocketMine-MP)`);
process.exit(0);
}
if (args.j) {
const data = await pingJava(args.host, {
port: args.port || 25565,
timeout: args.timeout || 500
});
function getDefaultPort(args) {
return args.j ? 25565 : 19132;
}
console.log(`host: ${args.host}\nprotocol: ${data.version.protocol}\nonline: ${data.players.online}`);
} else if (args.b) {
const data = await pingBedrock(args.host, {
port: args.port || 19132,
timeout: args.timeout || 500
});
async function pingJavaServer(host, port, timeout) {
const data = await pingJava(host, { port, timeout });
console.log(`host: ${host}\nprotocol: ${data.version?.protocol}\nonline: ${data.players?.online}`);
}
console.log(`host: ${args.host}\nprotocol: ${data.version.protocol}\nonline: ${data.players.online}`);
} else {
console.error('ERROR: Unsupported flag passed. Use -h or --help.');
async function pingBedrockServer(host, port, timeout) {
const data = await pingBedrock(host, { port, timeout });
console.log(`host: ${host}\nprotocol: ${data.version.protocol}\nonline: ${data.players.online}`);
}
// parsing command line arguments

20
example/parallel.js Normal file
View File

@ -0,0 +1,20 @@
import { pingBedrock } from '../index.js';
const hosts = [
'play.timecrack.net',
'geo.hivebedrock.network',
'oasys-pe.com',
'play.galaxite.net',
];
const pingPromises = hosts.map(host => pingBedrock(host));
const results = await Promise.allSettled(pingPromises);
for (let result of results) {
if (result.status === 'rejected') {
console.error(result.reason);
break;
}
console.log(result.value);
}

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,168 +1,154 @@
/**
* Implementation of the RakNet ping/pong protocol.
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping
*
* Data types:
* @see https://wiki.vg/Raknet_Protocol#Data_types
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol
*/
'use strict';
"use strict";
import dgram from 'dgram';
import ByteBuffer from 'bytebuffer';
import dgram from "node:dgram";
import crypto from "node:crypto";
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = new Date().getTime();
/**
* Decode Unconnected Ping
* Creates an Unconnected Ping packet.
* @param {number} pingId
* @returns {import('bytebuffer')}
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping
* @returns {Buffer}
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Ping}
*/
const UNCONNECTED_PING = (pingId) => {
// 0x01
const bb = new ByteBuffer();
bb.buffer[0] = 0x01;
bb.offset = 1;
return bb.writeLong(pingId).append('00ffff00fefefefefdfdfdfd12345678', 'hex').writeLong(0).flip().compact();
const createUnconnectedPingFrame = (timestamp) => {
const buffer = Buffer.alloc(33);
buffer.writeUInt8(0x01, 0); // Packet ID
buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic)
Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID
return buffer;
};
/**
* Decode Unconnected Pong
* @param {import('bytebuffer')} buffer
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Pong
* Extract Modt from Unconnected Pong Packet and convert to an object
* @param {Buffer} unconnectedPongPacket
* @returns {Object}
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Pong}
*/
const UNCONNECTED_PONG = (buffer) => {
// 0x1c
buffer.offset = 1;
const pingId = buffer.readLong();
const serverId = buffer.readLong();
const offset = buffer.offset += 16;
const nameLength = buffer.readShort();
let advertiseStr;
const extractModt = (unconnectedPongPacket) => {
// Skip everything to Modt
const offset = 33;
const length = unconnectedPongPacket.readUInt16BE(offset);
let modt = unconnectedPongPacket.toString(
"utf-8",
offset + 2,
offset + 2 + length
);
try {
advertiseStr = buffer.readUTF8String(nameLength);
} catch (err) {
advertiseStr = buffer.readUTF8String(parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)));
}
const components = modt.split(";");
const parsedComponents = {
edition: components[0],
name: components[1],
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],
};
advertiseStr = advertiseStr.split(/;/g);
const gameId = advertiseStr[0];
const description = advertiseStr[1];
const protocolVersion = advertiseStr[2];
const gameVersion = advertiseStr[3];
const currentPlayers = advertiseStr[4];
const maxPlayers = advertiseStr[5];
const name = advertiseStr[7];
const mode = advertiseStr[8];
return {
pingId,
advertiseStr,
serverId,
offset,
gameId,
description,
protocolVersion,
gameVersion,
currentPlayers,
maxPlayers,
name,
mode
};
return parsedComponents;
};
function ping(host, port = 19132, cb, timeout = 5000) {
const socket = dgram.createSocket('udp4');
/**
* Sends a ping request to the specified host and port.
* @param {string} host - The IP address or hostname of the server.
* @param {number} [port=19132] - The port number.
* @param {function} cb - The callback function to handle the response.
* @param {number} [timeout=5000] - The timeout duration in milliseconds.
*/
const ping = (host, port = 19132, cb, timeout = 5000) => {
const socket = dgram.createSocket("udp4");
// 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);
// 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);
};
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;
// 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;
const handleError = (err) => {
closeSocket();
/**
* 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);
}
};
if (!didFireError) {
didFireError = true;
cb(null, err);
}
};
try {
const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME);
socket.send(ping.buffer, 0, ping.buffer.length, port, host);
} catch (err) {
handleError(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', (msg) => {
const buffer = new ByteBuffer().append(msg, 'hex').flip();
const id = buffer.buffer[0];
socket.on("message", (pongPacket) => {
const id = pongPacket[0];
switch (id) {
// https://wiki.vg/Raknet_Protocol#Unconnected_Ping
case 0x1c: {
const pong = UNCONNECTED_PONG(buffer);
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
};
switch (id) {
case 0x1c: {
const modtObject = extractModt(pongPacket);
closeSocket();
cb(modtObject, null);
break;
}
// Close the socket and clear the timeout task
// This is a general cleanup for success conditions
closeSocket();
cb(null, clientData);
break;
}
default: {
handleError(new Error("Received unexpected packet"));
break;
}
}
});
default: {
handleError(new Error('Received unexpected packet'));
break;
}
}
});
socket.on('error', (err) => handleError(err));
}
socket.on("error", handleError);
};
/**
* Asynchronously ping 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 Bedrock server address.
* @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Bedrock server.
* @returns {Promise<import('../types/lib/bedrock.js').BedrockPingResponse>}
* @returns {Promise<import('../types/index.js').BedrockPingResponse>}
*/
export function pingBedrock(host, options = {}) {
if (!host) throw new Error('Host argument is not provided');
export const pingBedrock = (host, options = {}) => {
if (!host) throw new Error("Host argument is not provided");
const { port = 19132, timeout = 5000 } = options;
const { port = 19132, timeout = 5000 } = options;
return new Promise((resilve, reject) => {
ping(host, port, (err, res) => {
err ? reject(err) : resilve(res);
}, timeout);
});
}
return new Promise((resolve, reject) => {
ping(
host,
port,
(res, err) => {
err ? reject(err) : resolve(res);
},
timeout
);
});
};

View File

@ -1,127 +1,149 @@
/**
* Implementation of the Java Minecraft ping protocol.
* @see https://wiki.vg/Server_List_Ping
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Server_List_Ping
*/
'use strict';
"use strict";
import net from 'net';
import varint from './varint.js';
import net from "node:net";
import varint from "./varint.js";
const PROTOCOL_VERSION = 0;
/**
* Ping a Minecraft Java server.
* @param {string} host The host of the Java server.
* @param {string} virtualHost The host sent in handshake.
* @param {number} [port=25565] The port of the Java server.
* @param {function} cb The callback function to handle the ping response.
* @param {number} [timeout=5000] The timeout duration in milliseconds.
* @param {number} [protocolVersion=-1] The protocol version of the Java client.
*/
function ping(host, virtualHost, port = 25565, cb, timeout = 5000, protocolVersion = -1) {
const socket = net.createConnection({ host, port });
function ping(host, port = 25565, cb, timeout = 5000) {
const socket = net.createConnection(({ host, port }));
// 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);
// 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.destroy();
clearTimeout(timeoutTask);
};
const closeSocket = () => {
socket.destroy();
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;
// 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();
const handleError = (err) => {
closeSocket();
if (!didFireError) {
didFireError = true;
cb(null, err);
}
};
if (!didFireError) {
didFireError = true;
cb(null, err);
}
};
// #setNoDelay instantly flushes data during read/writes
// This prevents the runtime from delaying the write at all
socket.setNoDelay(true);
// #setNoDelay instantly flushes data during read/writes
// This prevents the runtime from delaying the write at all
socket.setNoDelay(true);
socket.on("connect", () => {
const handshake = varint.concat([
varint.encodeInt(0),
varint.encodeInt(protocolVersion),
varint.encodeInt(virtualHost.length),
varint.encodeString(virtualHost),
varint.encodeUShort(port),
varint.encodeInt(1),
]);
socket.on('connect', () => {
const handshake = varint.concat([
varint.encodeInt(0),
varint.encodeInt(PROTOCOL_VERSION),
varint.encodeInt(host.length),
varint.encodeString(host),
varint.encodeUShort(port),
varint.encodeInt(1)
]);
socket.write(handshake);
socket.write(handshake);
const request = varint.concat([varint.encodeInt(0)]);
const request = varint.concat([
varint.encodeInt(0)
]);
socket.write(request);
});
socket.write(request);
});
let incomingBuffer = Buffer.alloc(0);
let incomingBuffer = Buffer.alloc(0);
socket.on("data", (data) => {
incomingBuffer = Buffer.concat([incomingBuffer, data]);
socket.on('data', (data) => {
incomingBuffer = Buffer.concat([incomingBuffer, data]);
// Wait until incomingBuffer is at least 5 bytes long to ensure it has captured the first VarInt value
// This value is used to determine the full read length of the response
// "VarInts are never longer than 5 bytes"
// https://wiki.vg/Data_types#VarInt_and_VarLong
if (incomingBuffer.length < 5) {
return;
}
// Wait until incomingBuffer is at least 5 bytes long to ensure it has captured the first VarInt value
// This value is used to determine the full read length of the response
// "VarInts are never longer than 5 bytes"
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Data_types#VarInt_and_VarLong
if (incomingBuffer.length < 5) {
return;
}
let offset = 0;
const packetLength = varint.decodeInt(incomingBuffer, offset);
let offset = 0;
const packetLength = varint.decodeInt(incomingBuffer, offset);
// Ensure incomingBuffer contains the full response
if (incomingBuffer.length - offset < packetLength) {
return;
}
// Ensure incomingBuffer contains the full response
if (incomingBuffer.length - offset < packetLength) {
return;
}
const packetId = varint.decodeInt(incomingBuffer, varint.decodeLength(packetLength));
const packetId = varint.decodeInt(
incomingBuffer,
varint.decodeLength(packetLength)
);
if (packetId === 0) {
const data = incomingBuffer.slice(varint.decodeLength(packetLength) + varint.decodeLength(packetId));
const responseLength = varint.decodeInt(data, 0);
const response = data.slice(varint.decodeLength(responseLength), varint.decodeLength(responseLength) + responseLength);
if (packetId === 0) {
const data = incomingBuffer.subarray(
varint.decodeLength(packetLength) + varint.decodeLength(packetId)
);
const responseLength = varint.decodeInt(data, 0);
const response = data.subarray(
varint.decodeLength(responseLength),
varint.decodeLength(responseLength) + responseLength
);
try {
const message = JSON.parse(response);
cb(null, message);
try {
const message = JSON.parse(response);
// Close the socket and clear the timeout task
closeSocket();
} catch (err) {
handleError(err);
}
} else {
handleError(new Error('Received unexpected packet'));
}
});
closeSocket();
cb(message, null);
} catch (err) {
handleError(err);
}
} else {
handleError(new Error("Received unexpected packet"));
}
});
socket.on('error', handleError);
socket.on("error", handleError);
}
/**
* 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 {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Java server.
* @returns {Promise<import('../types/lib/java.js').JavaPingResponse>}
* @returns {Promise<import('../types/index.js').JavaPingResponse>}
*/
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, virtualHost = null } = options;
return new Promise((resolve, reject) => {
ping(host, port, (err, res) => {
err ? reject(err) : resolve(res);
}, timeout);
});
}
return new Promise((resolve, reject) => {
ping(
host,
virtualHost || host,
port,
(res, err) => {
err ? reject(err) : resolve(res);
},
timeout,
protocolVersion
);
});
}

View File

@ -1,90 +1,131 @@
// https://wiki.vg/Data_types
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Data_types
/**
* A utility object for encoding and decoding varints.
*/
const varint = {
encodeInt: (val) => {
// "constInts are never longer than 5 bytes"
// https://wiki.vg/Data_types#constInt_and_constLong
const buf = Buffer.alloc(5);
let written = 0;
/**
* Encodes an integer value into a varint byte buffer.
* @param {number} val - The integer value to encode.
* @returns {Buffer}
*/
encodeInt: (val) => {
// "VarInts are never longer than 5 bytes"
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Data_types#VarInt_and_VarLong
const buf = Buffer.alloc(5);
let written = 0;
while (true) {
if ((val & 0xFFFFFF80) === 0) {
buf.writeUInt8(val, written++);
break;
} else {
buf.writeUInt8(val & 0x7F | 0x80, written++);
val >>>= 7;
}
}
while (true) {
const byte = val & 0x7f;
val >>>= 7;
return buf.slice(0, written);
},
if (val === 0) {
buf.writeUInt8(byte, written++);
break;
}
encodeString: (val) => {
return Buffer.from(val, 'utf-8');
},
buf.writeUInt8(byte | 0x80, written++);
}
encodeUShort: (val) => {
return Buffer.from([val >> 8, val & 0xFF]);
},
return buf.slice(0, written);
},
concat: (chunks) => {
let length = 0;
/**
* Encodes a string value into a UTF-8 byte buffer.
* @param {string} val - The string value to encode.
* @returns {Buffer}
*/
encodeString: (val) => {
return Buffer.from(val, "utf-8");
},
for (const chunk of chunks) {
length += chunk.length;
}
/**
* Encodes an unsigned short value into a byte buffer.
* @param {number} val - The unsigned short value to encode.
* @returns {Buffer}
*/
encodeUShort: (val) => {
return Buffer.from([val >> 8, val & 0xff]);
},
const buffer = [
varint.encodeInt(length),
...chunks
];
/**
* Concatenates multiple byte buffers into a single byte buffer.
* @param {Buffer[]} chunks - An array of byte buffers to concatenate.
* @returns {Buffer}
*/
concat: (chunks) => {
let length = 0;
return Buffer.concat(buffer);
},
for (const chunk of chunks) {
length += chunk.length;
}
decodeInt: (buffer, offset) => {
let val = 0;
let count = 0;
const buffer = [varint.encodeInt(length), ...chunks];
while (true) {
const b = buffer.readUInt8(offset++);
return Buffer.concat(buffer);
},
val |= (b & 0x7F) << count++ * 7;
/**
* Decodes a varint integer value from a byte buffer.
* @param {Buffer} buffer - The byte buffer to decode from.
* @param {number} offset - The offset in the buffer to start decoding from.
* @returns {number}
*/
decodeInt: (buffer, offset) => {
let val = 0;
let count = 0;
if ((b & 0x80) != 128) {
break;
}
}
while (true) {
const byte = buffer.readUInt8(offset++);
return val;
},
val |= (byte & 0x7f) << (count++ * 7);
// The number of bytes that the last .decodeInt() call had to use to decode.
decodeLength: (val) => {
const N1 = Math.pow(2, 7);
const N2 = Math.pow(2, 14);
const N3 = Math.pow(2, 21);
const N4 = Math.pow(2, 28);
const N5 = Math.pow(2, 35);
const N6 = Math.pow(2, 42);
const N7 = Math.pow(2, 49);
const N8 = Math.pow(2, 56);
const N9 = Math.pow(2, 63);
if ((byte & 0x80) !== 0x80) {
break;
}
}
return (
val < N1 ? 1
: val < N2 ? 2
: val < N3 ? 3
: val < N4 ? 4
: val < N5 ? 5
: val < N6 ? 6
: val < N7 ? 7
: val < N8 ? 8
: val < N9 ? 9
: 10
);
}
return val;
},
/**
* Calculates the number of bytes required to decode a varint integer value.
* @param {number} val - The varint integer value.
* @returns {5 | 7 | 8 | 1 | 2 | 3 | 4 | 6 | 9 | 10}
*/
decodeLength: (val) => {
// Constants representing the powers of 2 used for comparison
const N1 = Math.pow(2, 7);
const N2 = Math.pow(2, 14);
const N3 = Math.pow(2, 21);
const N4 = Math.pow(2, 28);
const N5 = Math.pow(2, 35);
const N6 = Math.pow(2, 42);
const N7 = Math.pow(2, 49);
const N8 = Math.pow(2, 56);
const N9 = Math.pow(2, 63);
// Return the number of bytes required based on the value
return val < N1
? 1
: val < N2
? 2
: val < N3
? 3
: val < N4
? 4
: val < N5
? 5
: val < N6
? 6
: val < N7
? 7
: val < N8
? 8
: val < N9
? 9
: 10;
},
};
export default varint;

View File

@ -1,6 +1,6 @@
{
"name": "@minescope/mineping",
"version": "1.0.4",
"version": "1.6.0",
"description": "Ping both Minecraft Bedrock and Java servers.",
"main": "index.js",
"types": "types/index.d.ts",
@ -18,8 +18,5 @@
"engines": {
"node": ">=14"
},
"license": "MIT",
"dependencies": {
"bytebuffer": "^5.0.1"
}
"license": "MIT"
}

4
types/index.d.ts vendored
View File

@ -1,2 +1,2 @@
export { pingJava } from "./lib/java.js";
export { pingBedrock } from "./lib/bedrock.js";
export * from "./lib/java.js";
export * from "./lib/bedrock.js";

View File

@ -2,22 +2,25 @@
* @param port The server port.
* @param timeout The read/write socket timeout.
*/
export type PingOptions = {
port: number,
timeout: number;
export type BedrockPingOptions = {
port?: number;
timeout?: number;
};
export type BedrockPingResponse = {
edition: string;
name: string;
version: {
name: string;
protocol: string;
protocolVersion: number;
minecraftVersion: string;
};
players: {
max: string;
online: string;
online: number;
max: number;
};
description: string;
gamemode: string;
serverId: 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.
*
* @param host The Bedrock server address.
* @param options The configuration for pinging Minecraft Bedrock server.
*
* ```js
* import { pingBedrock } from '@minescope/mineping';
@ -43,7 +47,7 @@ export type BedrockPingResponse = {
* gamemode: 'Survival'
* }
* ```
* @see [source](https://github.com/minescope/mineping/blob/24a48802300f988d3ae520edbeb4f3e12820dcc9/lib/java.js#L117)
* @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>;

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

@ -1,26 +1,53 @@
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;
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;
name: string;
id: string;
};
/**
* `JSON Response` field of Response packet.
* @see https://wiki.vg/Server_List_Ping#Response
* @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;
favicon: string;
version: {
name: string;
protocol: number;
};
players: {
max: number;
online: number;
sample?: SampleProp[];
};
description: string | ChatComponent;
favicon?: string;
enforcesSecureChat?: boolean;
previewsChat?: boolean;
};
/**
@ -48,7 +75,9 @@ export type JavaPingResponse = {
* favicon: 'data:image/png;base64,iVBORw0KGg...
}
* ```
* @see [source](https://github.com/minescope/mineping/blob/8c84925ef7f5c420a7ef52740cba027491e82934/lib/bedrock.js#L158)
* @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>;

View File

@ -1,15 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
bytebuffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=
dependencies:
long "~3"
long@~3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=