mirror of
https://github.com/minescope/mineping.git
synced 2025-07-16 01:14:36 +03:00
Compare commits
36 Commits
72041aa3f9
...
v1.6.1
Author | SHA1 | Date | |
---|---|---|---|
d8d4a9a467
|
|||
d90a916fa5
|
|||
0959403b1b
|
|||
c71236f223
|
|||
0b5c5e2938
|
|||
73a2fffe8b | |||
9469564736
|
|||
838ffc497a
|
|||
502029869a
|
|||
e3e7e293ed
|
|||
88ad92e59d
|
|||
009f542c55
|
|||
0b0bed4e71
|
|||
fa4c34d896
|
|||
296294ca96
|
|||
9d25aaf4ea
|
|||
c735604c38
|
|||
afdaa9eb3e
|
|||
435899309f | |||
13e6b8c6ff | |||
d7256eabe7
|
|||
afa2c3025f
|
|||
6c297d0b8c
|
|||
283e9b32c6 | |||
354fa212a6 | |||
d9bf4cfb3f | |||
9dace3748b
|
|||
0aa73655b1
|
|||
78ca03b004
|
|||
910184bf5f
|
|||
915edbec9c
|
|||
0fe675385f
|
|||
c3158ac925 | |||
31c58ee637 | |||
7cd3444a4f | |||
af78a184db |
@ -1,6 +1,6 @@
|
|||||||
# mineping
|
# 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)
|
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
|
## Install
|
||||||
|
|
||||||
|
To install `mineping`, simply run the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i @minescope/mineping
|
npm i @minescope/mineping
|
||||||
```
|
```
|
||||||
@ -17,6 +19,7 @@ npm i @minescope/mineping
|
|||||||
## 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:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { pingJava, pingBedrock } from '@minescope/mineping';
|
import { pingJava, pingBedrock } from '@minescope/mineping';
|
||||||
@ -59,5 +62,7 @@ console.log(data);
|
|||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
Special thanks to the following projects:
|
||||||
|
|
||||||
- [mcping](https://github.com/Scetch/mcping) crate for Rust
|
- [mcping](https://github.com/Scetch/mcping) crate for Rust
|
||||||
- [mcping-js](https://github.com/Cryptkeeper/mcping-js) library for quering Minecraft Java Edition servers
|
- [mcping-js](https://github.com/Cryptkeeper/mcping-js) library for quering Minecraft Java Edition servers
|
||||||
|
171
example/cli.js
171
example/cli.js
@ -1,9 +1,91 @@
|
|||||||
import { pingBedrock, pingJava } from '../index.js';
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const args = getArgs();
|
/**
|
||||||
|
* 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"
|
||||||
|
*/
|
||||||
|
|
||||||
if (args.help || args.h || Object.keys(args).length === 0) {
|
import { pingBedrock, pingJava } from "../index.js";
|
||||||
console.log(`node cli.js [..]
|
|
||||||
|
const DEFAULT_TIMEOUT = 5000;
|
||||||
|
const JAVA_DEFAULT_PORT = 25565;
|
||||||
|
const BEDROCK_DEFAULT_PORT = 19132;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const args = parseArgs(process.argv.slice(2));
|
||||||
|
|
||||||
|
if (shouldShowHelp(args)) {
|
||||||
|
printHelp();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateArgs(args);
|
||||||
|
|
||||||
|
const port = Number(args.port) || getDefaultPort(args);
|
||||||
|
const timeout = Number(args.timeout) || DEFAULT_TIMEOUT;
|
||||||
|
|
||||||
|
if (args.j) {
|
||||||
|
await pingJavaServer(args.host, port, timeout);
|
||||||
|
} else if (args.b) {
|
||||||
|
await pingBedrockServer(args.host, port, timeout);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`ERROR: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(rawArgs) {
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < rawArgs.length; i++) {
|
||||||
|
const arg = rawArgs[i];
|
||||||
|
|
||||||
|
if (arg.startsWith("--")) {
|
||||||
|
// Handle --key=value and --key value formats
|
||||||
|
const [key, value] = arg.slice(2).split("=");
|
||||||
|
args[key] = value ?? rawArgs[++i] ?? true;
|
||||||
|
} else if (arg.startsWith("-")) {
|
||||||
|
// Handle short flags (-j, -b, -h)
|
||||||
|
const flags = arg.slice(1).split("");
|
||||||
|
flags.forEach((flag) => {
|
||||||
|
args[flag] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateArgs(args) {
|
||||||
|
if (args.j && args.b) {
|
||||||
|
printInterestingFacts();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.host) {
|
||||||
|
throw new Error("The host argument not found! Use -h or --help.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.j && !args.b) {
|
||||||
|
throw new Error("Must specify either -j or -b flag. Use -h or --help.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.port && (isNaN(args.port) || args.port < 1 || args.port > 65535)) {
|
||||||
|
throw new Error("Port must be a number between 1 and 65535");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.timeout && (isNaN(args.timeout) || args.timeout < 0)) {
|
||||||
|
throw new Error("Timeout must be a positive number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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!
|
A simple to use, efficient, and full-featured Minecraft server info parser!
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
@ -11,65 +93,48 @@ if (args.help || args.h || Object.keys(args).length === 0) {
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-j Use for Minecraft Java Edition
|
-j Use for Minecraft Java Edition
|
||||||
-b Use for Minecraft Bedrock Edition
|
-b Use for Minecraft Bedrock Edition
|
||||||
P.S. Don't use them at the same time!`);
|
-h, --help Show this help message
|
||||||
|
|
||||||
process.exit(1);
|
--host The server address (required)
|
||||||
|
--port The server port (default: ${JAVA_DEFAULT_PORT} for Java, ${BEDROCK_DEFAULT_PORT} for Bedrock)
|
||||||
|
--timeout The socket timeout in milliseconds (default: ${DEFAULT_TIMEOUT})
|
||||||
|
|
||||||
|
P.S. Don't use -j and -b at the same time!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.host) {
|
function printInterestingFacts() {
|
||||||
console.error('ERROR: The host argument not found! Use -h or --help.');
|
console.log(`Some interesting facts about MOTDs on bedrock:
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// easter egg <3
|
|
||||||
if (args.j && args.b) {
|
|
||||||
console.log(`Some interesting facts about MOTDs on bedrock:
|
|
||||||
- so far they seem to exclusively use legacy color codes
|
- so far they seem to exclusively use legacy color codes
|
||||||
- the random style has a special impl for periods, they turn into animated
|
- the random style has a special impl for periods, they turn into animated
|
||||||
colons that warp up and down rapidly
|
colons that warp up and down rapidly
|
||||||
- motd_2 is ignored? client displays "motd_1 - v{version}", where the
|
- 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
|
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)`);
|
- motd_2 seems to mainly be used to return the server software in use (e.g. PocketMine-MP)`);
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.j) {
|
function getDefaultPort(args) {
|
||||||
const data = await pingJava(args.host, {
|
return args.j ? JAVA_DEFAULT_PORT : BEDROCK_DEFAULT_PORT;
|
||||||
port: args.port || 25565,
|
|
||||||
timeout: args.timeout || 500
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`host: ${args.host}\nprotocol: ${data.version.protocol}\nonline: ${data.players.online}`);
|
|
||||||
} else {
|
|
||||||
console.error('ERROR: Unsupported flag passed. Use -h or --help.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsing command line arguments
|
async function pingJavaServer(host, port, timeout) {
|
||||||
function getArgs() {
|
const data = await pingJava(host, { port, timeout });
|
||||||
const args = {};
|
console.log(`Host: ${host}
|
||||||
process.argv.slice(2).forEach(arg => {
|
Version: ${data.version?.name} (protocol: ${data.version?.protocol})
|
||||||
// long arg
|
Players: ${data.players?.online}/${data.players?.max}
|
||||||
if (arg.slice(0, 2) === '--') {
|
Description: ${
|
||||||
const longArg = arg.split('=');
|
typeof data.description === "string"
|
||||||
const longArgFlag = longArg[0].slice(2, longArg[0].length);
|
? data.description
|
||||||
const longArgValue = longArg.length > 1 ? longArg[1] : true;
|
: data.description?.text
|
||||||
args[longArgFlag] = longArgValue;
|
}`);
|
||||||
// flags
|
}
|
||||||
} else if (arg[0] === '-') {
|
|
||||||
const flags = arg.slice(1, arg.length).split('');
|
|
||||||
flags.forEach(flag => {
|
|
||||||
args[flag] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return args;
|
async function pingBedrockServer(host, port, timeout) {
|
||||||
}
|
const data = await pingBedrock(host, { port, timeout });
|
||||||
|
console.log(`Host: ${host}
|
||||||
|
Edition: ${data.edition}
|
||||||
|
Version: ${data.version.minecraftVersion} (protocol: ${data.version.protocolVersion})
|
||||||
|
Players: ${data.players.online}/${data.players.max}
|
||||||
|
Name: ${data.name}
|
||||||
|
Gamemode: ${data.gameMode}`);
|
||||||
|
}
|
||||||
|
20
example/parallel.js
Normal file
20
example/parallel.js
Normal 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
5
example/single.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { pingBedrock } from "../index.js";
|
||||||
|
|
||||||
|
const host = "mc.nevertime.su";
|
||||||
|
const ping = await pingBedrock(host);
|
||||||
|
console.log(ping);
|
267
lib/bedrock.js
267
lib/bedrock.js
@ -1,168 +1,177 @@
|
|||||||
/**
|
/**
|
||||||
* Implementation of the RakNet ping/pong protocol.
|
* Implementation of the RakNet ping/pong protocol.
|
||||||
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping
|
* @see https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/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 ByteBuffer from 'bytebuffer';
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
|
||||||
const START_TIME = new Date().getTime();
|
const START_TIME = new Date().getTime();
|
||||||
|
const UNCONNECTED_PONG = 0x1c;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode Unconnected Ping
|
* Creates an Unconnected Ping packet.
|
||||||
* @param {number} pingId
|
* @param {number} pingId
|
||||||
* @returns {import('bytebuffer')}
|
* @returns {Buffer}
|
||||||
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping
|
* @see {@link https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Raknet_Protocol#Unconnected_Ping}
|
||||||
*/
|
*/
|
||||||
const UNCONNECTED_PING = (pingId) => {
|
const createUnconnectedPingFrame = (timestamp) => {
|
||||||
// 0x01
|
const buffer = Buffer.alloc(33);
|
||||||
const bb = new ByteBuffer();
|
buffer.writeUInt8(0x01, 0); // Packet ID
|
||||||
bb.buffer[0] = 0x01;
|
buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
|
||||||
bb.offset = 1;
|
Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic)
|
||||||
return bb.writeLong(pingId).append('00ffff00fefefefefdfdfdfd12345678', 'hex').writeLong(0).flip().compact();
|
Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID
|
||||||
|
return buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode Unconnected Pong
|
* Extract Modt from Unconnected Pong Packet and convert to an object
|
||||||
* @param {import('bytebuffer')} buffer
|
* @param {Buffer} unconnectedPongPacket
|
||||||
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Pong
|
* @returns {Object}
|
||||||
|
* @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 UNCONNECTED_PONG = (buffer) => {
|
const extractModt = (unconnectedPongPacket) => {
|
||||||
// 0x1c
|
if (
|
||||||
buffer.offset = 1;
|
!Buffer.isBuffer(unconnectedPongPacket) ||
|
||||||
const pingId = buffer.readLong();
|
unconnectedPongPacket.length < 35
|
||||||
const serverId = buffer.readLong();
|
) {
|
||||||
const offset = buffer.offset += 16;
|
throw new Error("Invalid pong packet");
|
||||||
const nameLength = buffer.readShort();
|
}
|
||||||
let advertiseStr;
|
|
||||||
|
|
||||||
try {
|
const offset = 33;
|
||||||
advertiseStr = buffer.readUTF8String(nameLength);
|
const length = unconnectedPongPacket.readUInt16BE(offset);
|
||||||
} catch (err) {
|
|
||||||
advertiseStr = buffer.readUTF8String(parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)));
|
|
||||||
}
|
|
||||||
|
|
||||||
advertiseStr = advertiseStr.split(/;/g);
|
// Check for buffer bounds
|
||||||
const gameId = advertiseStr[0];
|
if (offset + 2 + length > unconnectedPongPacket.length) {
|
||||||
const description = advertiseStr[1];
|
throw new Error("Malformed pong packet");
|
||||||
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 {
|
let modt = unconnectedPongPacket.toString(
|
||||||
pingId,
|
"utf-8",
|
||||||
advertiseStr,
|
offset + 2,
|
||||||
serverId,
|
offset + 2 + length
|
||||||
offset,
|
);
|
||||||
gameId,
|
|
||||||
description,
|
const components = modt.split(";");
|
||||||
protocolVersion,
|
|
||||||
gameVersion,
|
// Validate required components
|
||||||
currentPlayers,
|
if (components.length < 9) {
|
||||||
maxPlayers,
|
throw new Error("Invalid MODT format");
|
||||||
name,
|
}
|
||||||
mode
|
|
||||||
};
|
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],
|
||||||
|
};
|
||||||
|
|
||||||
|
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.
|
// Set manual timeout interval.
|
||||||
// This ensures the connection will NEVER hang regardless of internal state
|
// This ensures the connection will NEVER hang regardless of internal state
|
||||||
const timeoutTask = setTimeout(() => {
|
const timeoutTask = setTimeout(() => {
|
||||||
socket.emit('error', new Error('Socket timeout'));
|
socket.emit("error", new Error("Socket timeout"));
|
||||||
}, timeout);
|
}, timeout);
|
||||||
|
|
||||||
const closeSocket = () => {
|
const closeSocket = () => {
|
||||||
socket.close();
|
socket.close();
|
||||||
clearTimeout(timeoutTask);
|
clearTimeout(timeoutTask);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic error handler
|
// Generic error handler
|
||||||
// This protects multiple error callbacks given the complex socket state
|
// This protects multiple error callbacks given the complex socket state
|
||||||
// This is mostly dangerous since it can swallow errors
|
// This is mostly dangerous since it can swallow errors
|
||||||
let didFireError = false;
|
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) {
|
if (!didFireError) {
|
||||||
didFireError = true;
|
didFireError = true;
|
||||||
cb(null, err);
|
cb(null, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME);
|
const ping = createUnconnectedPingFrame(new Date().getTime() - START_TIME);
|
||||||
socket.send(ping.buffer, 0, ping.buffer.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 buffer = new ByteBuffer().append(msg, 'hex').flip();
|
if (!Buffer.isBuffer(pongPacket) || pongPacket.length === 0) {
|
||||||
const id = buffer.buffer[0];
|
handleError(new Error("Invalid packet received"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (id) {
|
const id = pongPacket[0];
|
||||||
// https://wiki.vg/Raknet_Protocol#Unconnected_Ping
|
if (id !== UNCONNECTED_PONG) {
|
||||||
case 0x1c: {
|
handleError(new Error(`Unexpected packet ID: 0x${id.toString(16)}`));
|
||||||
const pong = UNCONNECTED_PONG(buffer);
|
return;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
// Close the socket and clear the timeout task
|
try {
|
||||||
// This is a general cleanup for success conditions
|
const modtObject = extractModt(pongPacket);
|
||||||
closeSocket();
|
closeSocket();
|
||||||
cb(null, clientData);
|
cb(modtObject, null);
|
||||||
break;
|
} catch (err) {
|
||||||
}
|
handleError(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
default: {
|
socket.on("error", handleError);
|
||||||
handleError(new Error('Received unexpected packet'));
|
};
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('error', (err) => handleError(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously ping Minecraft Bedrock server.
|
* 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.
|
* 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 {string} host The Bedrock server address.
|
||||||
* @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Bedrock server.
|
* @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 = {}) {
|
export const pingBedrock = (host, options = {}) => {
|
||||||
if (!host) throw new Error('Host argument is not provided');
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
ping(host, port, (err, res) => {
|
ping(
|
||||||
err ? reject(err) : resilve(res);
|
host,
|
||||||
}, timeout);
|
port,
|
||||||
});
|
(res, err) => {
|
||||||
}
|
err ? reject(err) : resolve(res);
|
||||||
|
},
|
||||||
|
timeout
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
204
lib/java.js
204
lib/java.js
@ -1,127 +1,149 @@
|
|||||||
/**
|
/**
|
||||||
* Implementation of the Java Minecraft ping protocol.
|
* 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 net from "node:net";
|
||||||
import varint from './varint.js';
|
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) {
|
// Set manual timeout interval.
|
||||||
const socket = net.createConnection(({ host, port }));
|
// 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.
|
const closeSocket = () => {
|
||||||
// This ensures the connection will NEVER hang regardless of internal state
|
socket.destroy();
|
||||||
const timeoutTask = setTimeout(() => {
|
clearTimeout(timeoutTask);
|
||||||
socket.emit('error', new Error('Socket timeout'));
|
};
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
const closeSocket = () => {
|
// Generic error handler
|
||||||
socket.destroy();
|
// This protects multiple error callbacks given the complex socket state
|
||||||
clearTimeout(timeoutTask);
|
// 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
|
* Handle any error that occurs during the ping process.
|
||||||
// This is mostly dangerous since it can swallow errors
|
* @param {Error} err The error that occurred.
|
||||||
let didFireError = false;
|
*/
|
||||||
|
const handleError = (err) => {
|
||||||
|
closeSocket();
|
||||||
|
|
||||||
const handleError = (err) => {
|
if (!didFireError) {
|
||||||
closeSocket();
|
didFireError = true;
|
||||||
|
cb(null, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!didFireError) {
|
// #setNoDelay instantly flushes data during read/writes
|
||||||
didFireError = true;
|
// This prevents the runtime from delaying the write at all
|
||||||
cb(null, err);
|
socket.setNoDelay(true);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// #setNoDelay instantly flushes data during read/writes
|
socket.on("connect", () => {
|
||||||
// This prevents the runtime from delaying the write at all
|
const handshake = varint.concat([
|
||||||
socket.setNoDelay(true);
|
varint.encodeInt(0),
|
||||||
|
varint.encodeInt(protocolVersion),
|
||||||
|
varint.encodeInt(virtualHost.length),
|
||||||
|
varint.encodeString(virtualHost),
|
||||||
|
varint.encodeUShort(port),
|
||||||
|
varint.encodeInt(1),
|
||||||
|
]);
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.write(handshake);
|
||||||
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);
|
const request = varint.concat([varint.encodeInt(0)]);
|
||||||
|
|
||||||
const request = varint.concat([
|
socket.write(request);
|
||||||
varint.encodeInt(0)
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
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) => {
|
// Wait until incomingBuffer is at least 5 bytes long to ensure it has captured the first VarInt value
|
||||||
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
// This value is used to determine the full read length of the response
|
||||||
// Wait until incomingBuffer is at least 5 bytes long to ensure it has captured the first VarInt value
|
// "VarInts are never longer than 5 bytes"
|
||||||
// This value is used to determine the full read length of the response
|
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Data_types#VarInt_and_VarLong
|
||||||
// "VarInts are never longer than 5 bytes"
|
if (incomingBuffer.length < 5) {
|
||||||
// https://wiki.vg/Data_types#VarInt_and_VarLong
|
return;
|
||||||
if (incomingBuffer.length < 5) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
const packetLength = varint.decodeInt(incomingBuffer, offset);
|
const packetLength = varint.decodeInt(incomingBuffer, offset);
|
||||||
|
|
||||||
// Ensure incomingBuffer contains the full response
|
// Ensure incomingBuffer contains the full response
|
||||||
if (incomingBuffer.length - offset < packetLength) {
|
if (incomingBuffer.length - offset < packetLength) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packetId = varint.decodeInt(incomingBuffer, varint.decodeLength(packetLength));
|
const packetId = varint.decodeInt(
|
||||||
|
incomingBuffer,
|
||||||
|
varint.decodeLength(packetLength)
|
||||||
|
);
|
||||||
|
|
||||||
if (packetId === 0) {
|
if (packetId === 0) {
|
||||||
const data = incomingBuffer.slice(varint.decodeLength(packetLength) + varint.decodeLength(packetId));
|
const data = incomingBuffer.subarray(
|
||||||
const responseLength = varint.decodeInt(data, 0);
|
varint.decodeLength(packetLength) + varint.decodeLength(packetId)
|
||||||
const response = data.slice(varint.decodeLength(responseLength), varint.decodeLength(responseLength) + responseLength);
|
);
|
||||||
|
const responseLength = varint.decodeInt(data, 0);
|
||||||
|
const response = data.subarray(
|
||||||
|
varint.decodeLength(responseLength),
|
||||||
|
varint.decodeLength(responseLength) + responseLength
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const message = JSON.parse(response);
|
const message = JSON.parse(response);
|
||||||
cb(null, message);
|
|
||||||
|
|
||||||
// Close the socket and clear the timeout task
|
closeSocket();
|
||||||
closeSocket();
|
cb(message, null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleError(new Error('Received unexpected packet'));
|
handleError(new Error("Received unexpected packet"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('error', handleError);
|
socket.on("error", handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously ping Minecraft Java server.
|
* Asynchronously ping Minecraft Java server.
|
||||||
*
|
* 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.
|
||||||
* The optional `options` argument can be an object with a `ping` (default is `25565`) or/and `timeout` (default is `5000`) 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/lib/java.js').JavaPingResponse>}
|
* @returns {Promise<import('../types/index.js').JavaPingResponse>}
|
||||||
*/
|
*/
|
||||||
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, virtualHost = null } = options;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ping(host, port, (err, res) => {
|
ping(
|
||||||
err ? reject(err) : resolve(res);
|
host,
|
||||||
}, timeout);
|
virtualHost || host,
|
||||||
});
|
port,
|
||||||
}
|
(res, err) => {
|
||||||
|
err ? reject(err) : resolve(res);
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
protocolVersion
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
173
lib/varint.js
173
lib/varint.js
@ -1,90 +1,123 @@
|
|||||||
// 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 = {
|
const varint = {
|
||||||
encodeInt: (val) => {
|
/**
|
||||||
// "constInts are never longer than 5 bytes"
|
* Encodes an integer value into a varint byte buffer.
|
||||||
// https://wiki.vg/Data_types#constInt_and_constLong
|
* @param {number} val - The integer value to encode.
|
||||||
const buf = Buffer.alloc(5);
|
* @returns {Buffer}
|
||||||
let written = 0;
|
*/
|
||||||
|
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) {
|
while (true) {
|
||||||
if ((val & 0xFFFFFF80) === 0) {
|
const byte = val & 0x7f;
|
||||||
buf.writeUInt8(val, written++);
|
val >>>= 7;
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
buf.writeUInt8(val & 0x7F | 0x80, written++);
|
|
||||||
val >>>= 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.slice(0, written);
|
if (val === 0) {
|
||||||
},
|
buf.writeUInt8(byte, written++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
encodeString: (val) => {
|
buf.writeUInt8(byte | 0x80, written++);
|
||||||
return Buffer.from(val, 'utf-8');
|
}
|
||||||
},
|
|
||||||
|
|
||||||
encodeUShort: (val) => {
|
return buf.subarray(0, written);
|
||||||
return Buffer.from([val >> 8, val & 0xFF]);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
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),
|
* Concatenates multiple byte buffers into a single byte buffer.
|
||||||
...chunks
|
* @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) => {
|
const buffer = [varint.encodeInt(length), ...chunks];
|
||||||
let val = 0;
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
while (true) {
|
return Buffer.concat(buffer);
|
||||||
const b = buffer.readUInt8(offset++);
|
},
|
||||||
|
|
||||||
val |= (b & 0x7F) << count++ * 7;
|
/**
|
||||||
|
* Decodes a varint integer value from a 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) => {
|
||||||
|
// Fast path for single-byte varints
|
||||||
|
const firstByte = buffer.readUInt8(offset);
|
||||||
|
if (firstByte < 0x80) {
|
||||||
|
return firstByte;
|
||||||
|
}
|
||||||
|
|
||||||
if ((b & 0x80) != 128) {
|
let val = firstByte & 0x7f;
|
||||||
break;
|
let position = 7;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
while (position < 32) {
|
||||||
},
|
const byte = buffer.readUInt8(++offset);
|
||||||
|
val |= (byte & 0x7f) << position;
|
||||||
|
|
||||||
// The number of bytes that the last .decodeInt() call had to use to decode.
|
if ((byte & 0x80) === 0) {
|
||||||
decodeLength: (val) => {
|
return 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);
|
|
||||||
|
|
||||||
return (
|
position += 7;
|
||||||
val < N1 ? 1
|
}
|
||||||
: val < N2 ? 2
|
|
||||||
: val < N3 ? 3
|
throw new Error("VarInt is too big");
|
||||||
: val < N4 ? 4
|
},
|
||||||
: val < N5 ? 5
|
|
||||||
: val < N6 ? 6
|
/**
|
||||||
: val < N7 ? 7
|
* Calculates how many bytes are needed to encode a number as a VarInt
|
||||||
: val < N8 ? 8
|
* VarInts use a variable number of bytes to efficiently encode integers
|
||||||
: val < N9 ? 9
|
* Each byte uses 7 bits for the value and 1 bit to indicate if more bytes follow
|
||||||
: 10
|
* VarInts are never longer than 5 bytes
|
||||||
);
|
*
|
||||||
}
|
* @param {number} val - The number to calculate the VarInt length for
|
||||||
|
* @returns {1|2|3|4|5} The number of bytes needed to encode the value
|
||||||
|
*/
|
||||||
|
decodeLength: (val) => {
|
||||||
|
// Using bit shifts to calculate power of 2 thresholds
|
||||||
|
// 1 << 7 = 2^7 = 128 - Numbers below this fit in 1 byte
|
||||||
|
// 1 << 14 = 2^14 = 16,384 - Numbers below this fit in 2 bytes
|
||||||
|
// 1 << 21 = 2^21 = 2,097,152 - Numbers below this fit in 3 bytes
|
||||||
|
// 1 << 28 = 2^28 = 268,435,456 - Numbers below this fit in 4 bytes
|
||||||
|
// Any larger number needs 5 bytes (maximum VarInt size)
|
||||||
|
|
||||||
|
if (val < 1 << 7) return 1;
|
||||||
|
if (val < 1 << 14) return 2;
|
||||||
|
if (val < 1 << 21) return 3;
|
||||||
|
if (val < 1 << 28) return 4;
|
||||||
|
return 5;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default varint;
|
export default varint;
|
||||||
|
45
package.json
45
package.json
@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@minescope/mineping",
|
"name": "@minescope/mineping",
|
||||||
"version": "1.0.4",
|
"version": "1.6.1",
|
||||||
"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",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Timofey (xzeldon)",
|
"name": "Timofey (xzeldon)",
|
||||||
"email": "contact@zeldon.ru",
|
"email": "contact@zeldon.ru",
|
||||||
"url": "https://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",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"dependencies": {
|
}
|
||||||
"bytebuffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
4
types/index.d.ts
vendored
4
types/index.d.ts
vendored
@ -1,2 +1,2 @@
|
|||||||
export { pingJava } from "./lib/java.js";
|
export * from "./lib/java.js";
|
||||||
export { pingBedrock } from "./lib/bedrock.js";
|
export * from "./lib/bedrock.js";
|
68
types/lib/bedrock.d.ts
vendored
68
types/lib/bedrock.d.ts
vendored
@ -1,49 +1,61 @@
|
|||||||
/**
|
/**
|
||||||
* @param port The server port.
|
* @param port The server port (1-65535).
|
||||||
* @param timeout The read/write socket timeout.
|
* @param timeout The read/write socket timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
export type PingOptions = {
|
export type BedrockPingOptions = {
|
||||||
port: number,
|
port?: number & { _brand: "Port" }; // 1-65535
|
||||||
timeout: number;
|
timeout?: number & { _brand: "Timeout" }; // > 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BedrockPingResponse = {
|
export type BedrockPingResponse = {
|
||||||
version: {
|
edition: string;
|
||||||
name: string;
|
name: string;
|
||||||
protocol: string;
|
version: {
|
||||||
};
|
protocolVersion: number;
|
||||||
players: {
|
minecraftVersion: string;
|
||||||
max: string;
|
};
|
||||||
online: string;
|
players: {
|
||||||
};
|
online: number;
|
||||||
description: string;
|
max: number;
|
||||||
gamemode: string;
|
};
|
||||||
|
serverId: string;
|
||||||
|
mapName: string;
|
||||||
|
gameMode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously ping Minecraft Bedrock server.
|
* 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 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';
|
||||||
*
|
*
|
||||||
* const data = await pingBedrock('mco.mineplex.com');
|
* const data = await pingBedrock('mco.mineplex.com');
|
||||||
* console.log(data);
|
* console.log(data);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The resulting output will resemble:
|
* The resulting output will resemble:
|
||||||
* ```console
|
* ```console
|
||||||
* {
|
* {
|
||||||
* version: { name: 'Mineplex', protocol: '475' },
|
* edition: "MCPE",
|
||||||
* players: { max: '5207', online: '5206' },
|
* name: "Mineplex",
|
||||||
* description: ' New Costumes',
|
* version: {
|
||||||
* gamemode: 'Survival'
|
* protocolVersion: 475,
|
||||||
|
* minecraftVersion: "1.18.0"
|
||||||
|
* },
|
||||||
|
* players: {
|
||||||
|
* online: 5206,
|
||||||
|
* max: 5207
|
||||||
|
* },
|
||||||
|
* serverId: "12345678",
|
||||||
|
* mapName: "Lobby",
|
||||||
|
* gameMode: "Survival"
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
* @see [source](https://github.com/minescope/mineping/blob/24a48802300f988d3ae520edbeb4f3e12820dcc9/lib/java.js#L117)
|
|
||||||
*/
|
*/
|
||||||
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
65
types/lib/java.d.ts
vendored
@ -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 = {
|
export type SampleProp = {
|
||||||
name: string,
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `JSON Response` field of Response packet.
|
* `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 = {
|
export type JavaPingResponse = {
|
||||||
version: {
|
version: {
|
||||||
name: string;
|
name: string;
|
||||||
protocol: number;
|
protocol: number;
|
||||||
};
|
};
|
||||||
players: {
|
players: {
|
||||||
max: number;
|
max: number;
|
||||||
online: number;
|
online: number;
|
||||||
sample: SampleProp[];
|
sample?: SampleProp[];
|
||||||
};
|
};
|
||||||
description: string;
|
description: string | ChatComponent;
|
||||||
favicon: string;
|
favicon?: string;
|
||||||
|
enforcesSecureChat?: boolean;
|
||||||
|
previewsChat?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +75,9 @@ export type JavaPingResponse = {
|
|||||||
* favicon: 'data:image/png;base64,iVBORw0KGg...
|
* 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>;
|
||||||
|
48
types/lib/varint.d.ts
vendored
48
types/lib/varint.d.ts
vendored
@ -1,10 +1,44 @@
|
|||||||
export default varint;
|
export default varint;
|
||||||
declare namespace varint {
|
declare namespace varint {
|
||||||
function encodeInt(val: number): Buffer;
|
/**
|
||||||
function encodeString(val: string): Buffer;
|
* Encodes an integer value into a varint byte buffer.
|
||||||
function encodeUShort(val: number): Buffer;
|
* @param val - The integer value to encode.
|
||||||
function concat(chunks: Buffer[]): Buffer;
|
*/
|
||||||
function decodeInt(buffer: Buffer, offset: number): number;
|
function encodeInt(val: number): Buffer;
|
||||||
function decodeString(val: Buffer, offset?: number): string;
|
|
||||||
function decodeLength(val: number): 5 | 7 | 8 | 1 | 2 | 3 | 4 | 6 | 9 | 10;
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
15
yarn.lock
15
yarn.lock
@ -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=
|
|
Reference in New Issue
Block a user