mirror of
https://github.com/minescope/mineping.git
synced 2025-07-18 18:06:36 +03:00
Compare commits
10 Commits
a1b999ca4e
...
master
Author | SHA1 | Date | |
---|---|---|---|
65d375cd86
|
|||
ed802661a4
|
|||
c533bfe40d
|
|||
7222eeae3c
|
|||
0bf30140b7
|
|||
371fb9daa7
|
|||
1e6b5b3973
|
|||
23299a9a07
|
|||
2bd5d9c9bf
|
|||
7248a0096c
|
155
README.md
155
README.md
@ -1,8 +1,19 @@
|
||||
# mineping
|
||||
|
||||
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.
|
||||
[](https://www.npmjs.com/package/@minescope/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)
|
||||
A simple and efficient JavaScript library for pinging Minecraft servers. It supports both Java and Bedrock editions through a async/await-friendly API.
|
||||
|
||||
`@minescope/mineping` automatically resolves SRV records for Java servers and parses rich status data, including MOTD, player counts, version info, and server icons. Comes with full TypeScript support.
|
||||
|
||||
*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)*
|
||||
|
||||
## Features
|
||||
|
||||
- **Dual Protocol Support:** Ping both Java and Bedrock servers with a consistent API.
|
||||
- **SRV Record Resolution:** Automatically resolves SRV records for Java Edition servers, so you don't have to worry about custom ports.
|
||||
- **Rich Data:** Parses the full response from servers, including player samples, favicons, gamemodes, and more.
|
||||
- **Lightweight:** Has only **one** runtime dependency — [debug](https://www.npmjs.com/package/debug) (used for tracing).
|
||||
|
||||
## Requirements
|
||||
|
||||
@ -12,22 +23,56 @@ Mirror on my [<img src="https://git.zeldon.ru/assets/img/logo.svg" align="center
|
||||
|
||||
To install `mineping`, simply run the following command:
|
||||
|
||||
```
|
||||
```bash
|
||||
npm i @minescope/mineping
|
||||
```
|
||||
|
||||
> To install _beta_ version (if available), run: `npm i @minescope/mineping@next`
|
||||
## API & Usage Examples
|
||||
|
||||
The library exports two main functions: `pingJava` and `pingBedrock`. Both are asynchronous and return a `Promise`.
|
||||
|
||||
### 1. Basic Server Ping
|
||||
|
||||
Java:
|
||||
```js
|
||||
import { pingJava } from "@minescope/mineping";
|
||||
|
||||
const data = await pingJava("0.0.0.0");
|
||||
console.log(data)
|
||||
```
|
||||
```js
|
||||
{
|
||||
version: { name: '1.21.5', protocol: 770 },
|
||||
enforcesSecureChat: true,
|
||||
description: '§1Welcome to §2My Minecraft Server!',
|
||||
players: { max: 20, online: 0 }
|
||||
}
|
||||
```
|
||||
|
||||
Bedrock:
|
||||
```js
|
||||
import { pingBedrock } from "@minescope/mineping";
|
||||
|
||||
const data = await pingBedrock("0.0.0.0");
|
||||
console.log(data)
|
||||
```
|
||||
```js
|
||||
{
|
||||
edition: 'MCPE',
|
||||
name: 'Dedicated Server',
|
||||
levelName: 'Bedrock level',
|
||||
gamemode: 'Survival',
|
||||
version: { protocol: 800, minecraft: '1.21.84' },
|
||||
players: { online: 0, max: 10 },
|
||||
port: { v4: 19132, v6: 19133 },
|
||||
guid: 12143264093420916401n,
|
||||
isNintendoLimited: false,
|
||||
isEditorModeEnabled: false
|
||||
}
|
||||
```
|
||||
|
||||
## 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";
|
||||
```
|
||||
|
||||
### CommonJS
|
||||
|
||||
`mineping` is an ESM-only module — you are not able to import it with `require()`.
|
||||
@ -40,34 +85,86 @@ const pingBedrock = (...args) =>
|
||||
import("@minescope/mineping").then((module) => module.pingBedrock(...args));
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Debugging
|
||||
|
||||
Ping a Java server with default options:
|
||||
`mineping` uses the [`debug`](https://www.npmjs.com/package/debug) library to provide detailed tracing information, which can be useful for diagnosing connection issues or understanding the library's internal workings.
|
||||
|
||||
```js
|
||||
import { pingJava } from "@minescope/mineping";
|
||||
To enable debug logs, set the `DEBUG` environment variable when running your script. The library uses two namespaces:
|
||||
|
||||
const data = await pingJava("mc.hypixel.net");
|
||||
console.log(data);
|
||||
- `mineping:java` for the Java Edition pinger.
|
||||
- `mineping:bedrock` for the Bedrock Edition pinger.
|
||||
|
||||
### Examples
|
||||
|
||||
**Enable all `mineping` debug logs:**
|
||||
|
||||
You can use a wildcard (`*`) to enable all logs from this library.
|
||||
|
||||
```bash
|
||||
DEBUG=mineping:* node your-script.js
|
||||
```
|
||||
|
||||
Ping a Bedrock server with custom options:
|
||||
<details>
|
||||
<summary>Click to see output for <code>DEBUG="mineping:*" node examples/01-basic-ping.js</code></summary>
|
||||
|
||||
```js
|
||||
import { pingBedrock } from "@minescope/mineping";
|
||||
```bash
|
||||
DEBUG="mineping:*" node examples/01-basic-ping.js
|
||||
mineping:java pinging Java server hypixel.net with options: {} +0ms
|
||||
mineping:java attempting SRV lookup for _minecraft._tcp.hypixel.net with 5000ms timeout +2ms
|
||||
mineping:java SRV lookup successful, new target: mc.hypixel.net:25565 +2ms
|
||||
mineping:java creating TCP connection to mc.hypixel.net:25565 +0ms
|
||||
mineping:java socket connected to mc.hypixel.net:25565, sending packets... +182ms
|
||||
mineping:java received 1440 bytes of data, total buffer size is now 1440 bytes +130ms
|
||||
mineping:java packet incomplete, waiting for more data +0ms
|
||||
mineping:java received 12960 bytes of data, total buffer size is now 14400 bytes +1ms
|
||||
mineping:java packet incomplete, waiting for more data +0ms
|
||||
mineping:java received 1601 bytes of data, total buffer size is now 16001 bytes +129ms
|
||||
mineping:java received raw JSON response +0ms
|
||||
mineping:java successfully parsed full response +0ms
|
||||
mineping:java cleaning up resources for mc.hypixel.net:25565 +0ms
|
||||
--- Java Server ---
|
||||
{
|
||||
version: { name: 'Requires MC 1.8 / 1.21', protocol: 47 },
|
||||
players: { max: 200000, online: 28654, sample: [] },
|
||||
description: ' §aHypixel Network §c[1.8-1.21]\n' +
|
||||
' §6§lSB 0.23 §2§lFORAGING §8§l- §e§lSUMMER EVENT',
|
||||
favicon: 'data:image/png;base64,iVBORw0K'... 5738 more characters
|
||||
}
|
||||
|
||||
const data = await pingBedrock("mco.mineplex.com", {
|
||||
port: 19132,
|
||||
timeout: 500,
|
||||
});
|
||||
console.log(data);
|
||||
====================
|
||||
|
||||
mineping:bedrock pinging Bedrock server geo.hivebedrock.network:19132 with 5000ms timeout +0ms
|
||||
mineping:bedrock sending Unconnected Ping packet to geo.hivebedrock.network:19132 +1ms
|
||||
mineping:bedrock packet: <Buffer 01 c0 01 00 00 00 00 00 00 00 ff ff 00 fe fe fe fe fd fd fd fd 12 34 56 78 19 20 9f 00 e6 ed ef 96> +0ms
|
||||
mineping:bedrock received 124 bytes from geo.hivebedrock.network:19132 +104ms
|
||||
mineping:bedrock received raw MOTD string: MCPE;BEDWARS + BUILD BATTLE;121;1.0;13074;100001;-4669279440237021648;Hive Games;Survival +0ms
|
||||
mineping:bedrock cleaning up resources for geo.hivebedrock.network:19132 +0ms
|
||||
--- Bedrock Server ---
|
||||
{
|
||||
edition: 'MCPE',
|
||||
name: 'BEDWARS + BUILD BATTLE',
|
||||
levelName: 'Hive Games',
|
||||
gamemode: 'Survival',
|
||||
version: { protocol: 121, minecraft: '1.0' },
|
||||
players: { online: 13074, max: 100001 },
|
||||
port: { v4: undefined, v6: undefined },
|
||||
guid: -4669279440237021648n,
|
||||
isNintendoLimited: undefined,
|
||||
isEditorModeEnabled: undefined
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
> More complex example can be found in the `example` folder!
|
||||
**_PowerShell_ uses different syntax to set environment variables:**
|
||||
|
||||
```powershell
|
||||
$env:DEBUG="mineping:*";node your-script.js
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Special thanks to the following projects:
|
||||
Special thanks to the following projects for inspiration and protocol details:
|
||||
|
||||
- [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 querying Minecraft Java Edition servers
|
||||
- The amazing community at [minecraft.wiki](https://minecraft.wiki/) for documenting the protocols.
|
||||
|
@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { pingBedrock } from "../index.js";
|
||||
|
||||
const host = "0.0.0.0";
|
||||
const motd = await pingBedrock(host);
|
||||
console.log(motd);
|
19
examples/01-basic-ping.js
Normal file
19
examples/01-basic-ping.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { pingJava, pingBedrock } from "../index.js";
|
||||
|
||||
try {
|
||||
const javaData = await pingJava("hypixel.net");
|
||||
console.log("--- Java Server ---");
|
||||
console.log(javaData);
|
||||
} catch (error) {
|
||||
console.error("Could not ping Java server:", error);
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(20) + "\n");
|
||||
|
||||
try {
|
||||
const motd = await pingBedrock("geo.hivebedrock.network");
|
||||
console.log("--- Bedrock Server ---");
|
||||
console.log(motd);
|
||||
} catch (error) {
|
||||
console.error("Could not ping Bedrock server:", error);
|
||||
}
|
14
examples/02-error-handling.js
Normal file
14
examples/02-error-handling.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { pingJava } from "../index.js";
|
||||
|
||||
const offlineServer = "this.server.does.not.exist";
|
||||
const port = 12345;
|
||||
|
||||
console.log(`Pinging an offline server: ${offlineServer}:${port}`);
|
||||
|
||||
try {
|
||||
// We set a short timeout to fail faster.
|
||||
const data = await pingJava(offlineServer, { port, timeout: 500 });
|
||||
console.log("Success!?", data);
|
||||
} catch (error) {
|
||||
console.error("Caught expected error:", error.message);
|
||||
}
|
47
examples/03-server-dashboard.js
Normal file
47
examples/03-server-dashboard.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { pingJava, pingBedrock } from "../index.js";
|
||||
|
||||
const servers = [
|
||||
{ type: "Java", host: "mc.hypixel.net" },
|
||||
{ type: "Java", host: "play.cubecraft.net" },
|
||||
{ type: "Java", host: "an-offline-java-server.com" },
|
||||
{ type: "Bedrock", host: "geo.hivebedrock.network" },
|
||||
{ type: "Bedrock", host: "buzz.insanitycraft.net" },
|
||||
{ type: "Bedrock", host: "an.offline.bedrock.server" },
|
||||
];
|
||||
|
||||
console.log("Pinging all servers...");
|
||||
|
||||
// Create an array of ping promises
|
||||
const pingPromises = servers.map((server) => {
|
||||
if (server.type === "Java") {
|
||||
return pingJava(server.host, { timeout: 3000 });
|
||||
} else {
|
||||
return pingBedrock(server.host, { timeout: 3000 });
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for all pings to complete (or fail)
|
||||
const results = await Promise.allSettled(pingPromises);
|
||||
|
||||
// Process and display results
|
||||
const displayData = results.map((result, index) => {
|
||||
const server = servers[index];
|
||||
if (result.status === "fulfilled") {
|
||||
const data = result.value;
|
||||
return {
|
||||
Server: `${server.type} - ${server.host}`,
|
||||
Status: "✅ Online",
|
||||
Players: `${data.players.online} / ${data.players.max}`,
|
||||
Version: data.version.name ?? data.version.minecraft,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
Server: `${server.type} - ${server.host}`,
|
||||
Status: "❌ Offline",
|
||||
Players: "N/A",
|
||||
Version: `Error: ${result.reason.message.slice(0, 30)}...`,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
console.table(displayData);
|
@ -86,31 +86,31 @@ function shouldShowHelp(args) {
|
||||
|
||||
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
|
||||
-h, --help Show this help message
|
||||
|
||||
--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!`);
|
||||
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
|
||||
-h, --help Show this help message
|
||||
|
||||
--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!`);
|
||||
}
|
||||
|
||||
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
|
||||
colons that warp up and down rapidly
|
||||
- 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)`);
|
||||
- so far they seem to exclusively use legacy color codes
|
||||
- the random style has a special impl for periods, they turn into animated
|
||||
colons that warp up and down rapidly
|
||||
- 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)`);
|
||||
}
|
||||
|
||||
function getDefaultPort(args) {
|
||||
@ -120,7 +120,7 @@ function getDefaultPort(args) {
|
||||
async function pingJavaServer(host, port, timeout) {
|
||||
const data = await pingJava(host, { port, timeout });
|
||||
console.log(`Host: ${host}
|
||||
Version: ${data.version?.name} (protocol: ${data.version?.protocol})
|
||||
Version: ${data.version.name} (protocol: ${data.version.protocol})
|
||||
Players: ${data.players?.online}/${data.players?.max}
|
||||
Description: ${
|
||||
typeof data.description === "string"
|
||||
@ -133,8 +133,8 @@ 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})
|
||||
Version: ${data.version.minecraft} (protocol: ${data.version.protocol})
|
||||
Players: ${data.players.online}/${data.players.max}
|
||||
Name: ${data.name}
|
||||
Gamemode: ${data.gameMode}`);
|
||||
Gamemode: ${data.gamemode}`);
|
||||
}
|
@ -200,7 +200,7 @@ const parseUnconnectedPong = (pongPacket) => {
|
||||
* @param {BedrockPingOptions} [options={}] - Optional configuration.
|
||||
* @returns {Promise<BedrockPingResponse>} A promise that resolves with the server's parsed MOTD.
|
||||
*/
|
||||
export const pingBedrock = (host, options = {}) => {
|
||||
export async function pingBedrock(host, options = {}) {
|
||||
if (!host) {
|
||||
throw new Error("Host argument is required.");
|
||||
}
|
||||
@ -258,4 +258,4 @@ export const pingBedrock = (host, options = {}) => {
|
||||
socket.emit("error", err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
76
lib/java.js
76
lib/java.js
@ -5,8 +5,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
import net from "node:net";
|
||||
import dns from "node:dns/promises";
|
||||
import { createConnection, isIP } from "node:net";
|
||||
import { Resolver } from "node:dns/promises";
|
||||
import createDebug from "debug";
|
||||
import * as varint from "./varint.js";
|
||||
|
||||
@ -147,34 +147,60 @@ export async function pingJava(host, options = {}) {
|
||||
let targetHost = host;
|
||||
let targetPort = fallbackPort;
|
||||
|
||||
try {
|
||||
debug("attempting SRV lookup for _minecraft._tcp.%s", host);
|
||||
const srvRecords = await dns.resolveSrv(`_minecraft._tcp.${host}`);
|
||||
if (srvRecords.length > 0) {
|
||||
targetHost = srvRecords[0].name;
|
||||
targetPort = srvRecords[0].port;
|
||||
debug("SRV lookup successful, new target: %s:%d", targetHost, targetPort);
|
||||
}
|
||||
} catch (err) {
|
||||
// Common errors like ENODATA or ENOTFOUND are expected when a server
|
||||
// does not have an SRV record, so we ignore them and proceed
|
||||
if (
|
||||
err instanceof Error &&
|
||||
"code" in err &&
|
||||
(err.code === "ENODATA" || err.code === "ENOTFOUND")
|
||||
) {
|
||||
// Non fatal DNS error, log it and continue
|
||||
debug("SRV lookup for %s failed (%s), using fallback.", host, err.code);
|
||||
} else {
|
||||
// Re-throw anything else to fail the operation
|
||||
debug("SRV lookup for %s failed unexpectedly, re-throwing.", host, err);
|
||||
throw err;
|
||||
// A list of hostnames that should never have an SRV lookup.
|
||||
const nonSrvLookableHostnames = ["localhost"];
|
||||
|
||||
// Check if the host is a valid IP address (v4 or v6).
|
||||
// net.isIP() returns 0 for invalid IPs, 4 for IPv4, and 6 for IPv6.
|
||||
const isDirectIp = isIP(host) !== 0;
|
||||
const isNonLookableHostname = nonSrvLookableHostnames.includes(
|
||||
host.toLowerCase()
|
||||
);
|
||||
|
||||
if (isDirectIp || isNonLookableHostname) {
|
||||
debug(
|
||||
"host '%s' is a direct IP or a non-lookable hostname, skipping SRV lookup.",
|
||||
host
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
debug(
|
||||
"attempting SRV lookup for _minecraft._tcp.%s with %dms timeout",
|
||||
host,
|
||||
timeout
|
||||
);
|
||||
const resolver = new Resolver({ timeout, tries: 3 });
|
||||
const srvRecords = await resolver.resolveSrv(`_minecraft._tcp.${host}`);
|
||||
if (srvRecords.length > 0) {
|
||||
targetHost = srvRecords[0].name;
|
||||
targetPort = srvRecords[0].port;
|
||||
debug(
|
||||
"SRV lookup successful, new target: %s:%d",
|
||||
targetHost,
|
||||
targetPort
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// Common errors like ENODATA, ENOTFOUND, or a DNS timeout (ETIMEOUT) are expected
|
||||
// when a server does not have an SRV record, so we ignore them and proceed.
|
||||
const nonFatalDnsCodes = ["ENODATA", "ENOTFOUND", "ETIMEOUT"];
|
||||
if (
|
||||
err instanceof Error &&
|
||||
"code" in err &&
|
||||
nonFatalDnsCodes.includes(err.code)
|
||||
) {
|
||||
debug("SRV lookup for %s failed (%s), using fallback.", host, err.code);
|
||||
} else {
|
||||
// Re-throw anything else to fail the operation
|
||||
debug("SRV lookup for %s failed unexpectedly, re-throwing.", host, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
debug("creating TCP connection to %s:%d", targetHost, targetPort);
|
||||
const socket = net.createConnection({ host: targetHost, port: targetPort });
|
||||
const socket = createConnection({ host: targetHost, port: targetPort });
|
||||
|
||||
// Prevent cleanup tasks from running more than once
|
||||
// in case of multiple error callbacks
|
||||
|
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@minescope/mineping",
|
||||
"version": "1.7.0-beta.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Ping both Minecraft Bedrock and Java servers.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@ -8,14 +8,23 @@
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"types:check": "tsc --noEmit",
|
||||
"types:build": "tsc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/minescope/mineping.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [],
|
||||
"keywords": [
|
||||
"minecraft",
|
||||
"raknet",
|
||||
"node",
|
||||
"mcpe",
|
||||
"mcbe",
|
||||
"ping",
|
||||
"bedrock"
|
||||
],
|
||||
"author": {
|
||||
"name": "Timofey Gelazoniya",
|
||||
"email": "timofey@z4n.me",
|
||||
|
@ -125,8 +125,10 @@ describe("bedrock.js", () => {
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
it("should throw an error if host is not provided", () => {
|
||||
expect(() => pingBedrock(null)).toThrow("Host argument is required");
|
||||
it("should throw an error if host is not provided", async () => {
|
||||
await expect(pingBedrock(null)).rejects.toThrow(
|
||||
"Host argument is required"
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject on socket timeout", async () => {
|
||||
@ -167,7 +169,7 @@ function createMockPongPacket(motd) {
|
||||
const packet = Buffer.alloc(35 + motdBuffer.length);
|
||||
packet.writeUInt8(0x1c, 0);
|
||||
packet.writeBigInt64LE(BigInt(Date.now()), 1);
|
||||
Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(packet, 17);
|
||||
Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(packet, 9);
|
||||
packet.writeUInt16BE(motdBuffer.length, 33);
|
||||
motdBuffer.copy(packet, 35);
|
||||
return packet;
|
||||
|
@ -1,18 +1,25 @@
|
||||
import net from "node:net";
|
||||
import dns from "node:dns/promises";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { pingJava } from "../lib/java.js";
|
||||
import * as varint from "../lib/varint.js";
|
||||
|
||||
const mockResolveSrv = vi.fn();
|
||||
|
||||
vi.mock("node:net");
|
||||
vi.mock("node:dns/promises");
|
||||
vi.mock("node:dns/promises", () => ({
|
||||
Resolver: vi.fn().mockImplementation(() => ({
|
||||
resolveSrv: mockResolveSrv,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe("pingJava", () => {
|
||||
let mockSocket;
|
||||
|
||||
beforeEach(() => {
|
||||
// Simulate no SRV record found.
|
||||
dns.resolveSrv.mockResolvedValue([]);
|
||||
// Reset mocks before each test.
|
||||
mockResolveSrv.mockClear();
|
||||
// Simulate no SRV record found by default.
|
||||
mockResolveSrv.mockResolvedValue([]);
|
||||
|
||||
const mockHandlers = {};
|
||||
mockSocket = {
|
||||
@ -44,7 +51,11 @@ describe("pingJava", () => {
|
||||
// Allow the async SRV lookup to complete
|
||||
await vi.runAllTicks();
|
||||
|
||||
expect(dns.resolveSrv).toHaveBeenCalledWith(`_minecraft._tcp.${host}`);
|
||||
expect(net.createConnection).toHaveBeenCalledWith({
|
||||
host: host,
|
||||
port: options.port,
|
||||
});
|
||||
|
||||
expect(net.createConnection).toHaveBeenCalledWith({
|
||||
host,
|
||||
port: options.port,
|
||||
|
@ -1,14 +1,10 @@
|
||||
{
|
||||
"include": ["lib/**/*.js", "index.js"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["lib", "index.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "types",
|
||||
"removeComments": false
|
||||
}
|
||||
}
|
2
types/index.d.ts
vendored
Normal file
2
types/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { pingJava } from "./lib/java.js";
|
||||
export { pingBedrock } from "./lib/bedrock.js";
|
131
types/lib/bedrock.d.ts
vendored
Normal file
131
types/lib/bedrock.d.ts
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Asynchronously pings a Minecraft Bedrock server.
|
||||
* @param {string} host - The IP address or hostname of the server.
|
||||
* @param {BedrockPingOptions} [options={}] - Optional configuration.
|
||||
* @returns {Promise<BedrockPingResponse>} A promise that resolves with the server's parsed MOTD.
|
||||
*/
|
||||
export function pingBedrock(host: string, options?: BedrockPingOptions): Promise<BedrockPingResponse>;
|
||||
/**
|
||||
* Representation of raw, semicolon-delimited MOTD string.
|
||||
* This struct directly mirrors the fields and order from the server response.
|
||||
* See [`Unconnected Pong Documentation`](https://minecraft.wiki/w/RakNet#Unconnected_Pong) for more details.
|
||||
*/
|
||||
export type BedrockMotd = {
|
||||
/**
|
||||
* - The edition of the server (MCPE or MCEE).
|
||||
*/
|
||||
edition: string;
|
||||
/**
|
||||
* - The primary name of the server (first line of MOTD).
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* - The protocol version.
|
||||
*/
|
||||
protocol: number;
|
||||
/**
|
||||
* - The game version (e.g., "1.21.2").
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* - The current number of players online.
|
||||
*/
|
||||
playerCount: number;
|
||||
/**
|
||||
* - The maximum number of players allowed.
|
||||
*/
|
||||
playerMax: number;
|
||||
/**
|
||||
* - The server's GUID.
|
||||
*/
|
||||
serverGuid: bigint;
|
||||
/**
|
||||
* - The secondary name of the server (second line of MOTD).
|
||||
*/
|
||||
subName: string;
|
||||
/**
|
||||
* - The default gamemode (e.g., "Survival").
|
||||
*/
|
||||
gamemode: string;
|
||||
/**
|
||||
* - Whether the server is Nintendo limited.
|
||||
*/
|
||||
nintendoLimited?: boolean;
|
||||
/**
|
||||
* - The server's IPv4 port, if provided.
|
||||
*/
|
||||
port?: number;
|
||||
/**
|
||||
* - The server's IPv6 port, if provided.
|
||||
*/
|
||||
ipv6Port?: number;
|
||||
/**
|
||||
* - Whether the server is in editor mode, if provided. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details.
|
||||
*/
|
||||
editorMode?: boolean;
|
||||
};
|
||||
/**
|
||||
* Represents the structured and user-friendly response from a server ping.
|
||||
* This is the public-facing object that users of the library will receive.
|
||||
*/
|
||||
export type BedrockPingResponse = {
|
||||
/**
|
||||
* - The edition of the server (MCPE or MCEE).
|
||||
*/
|
||||
edition: string;
|
||||
/**
|
||||
* - The primary name of the server (first line of MOTD).
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* - The name of the world or level being hosted.
|
||||
*/
|
||||
levelName: string;
|
||||
/**
|
||||
* - The default gamemode of the server.
|
||||
*/
|
||||
gamemode: string;
|
||||
/**
|
||||
* - Game and protocol versions.
|
||||
*/
|
||||
version: {
|
||||
protocol: number;
|
||||
minecraft: string;
|
||||
};
|
||||
/**
|
||||
* - Current and maximum player counts.
|
||||
*/
|
||||
players: {
|
||||
online: number;
|
||||
max: number;
|
||||
};
|
||||
/**
|
||||
* - Announced IPv4 and IPv6 ports.
|
||||
*/
|
||||
port: {
|
||||
v4?: number;
|
||||
v6?: number;
|
||||
};
|
||||
/**
|
||||
* - The server's unique 64-bit GUID.
|
||||
*/
|
||||
guid: bigint;
|
||||
/**
|
||||
* - True if the server restricts Nintendo Switch players.
|
||||
*/
|
||||
isNintendoLimited?: boolean;
|
||||
/**
|
||||
* - True if the server is in editor mode. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details.
|
||||
*/
|
||||
isEditorModeEnabled?: boolean;
|
||||
};
|
||||
export type BedrockPingOptions = {
|
||||
/**
|
||||
* - The server port to ping.
|
||||
*/
|
||||
port?: number;
|
||||
/**
|
||||
* - The timeout in milliseconds for the request.
|
||||
*/
|
||||
timeout?: number;
|
||||
};
|
63
types/lib/java.d.ts
vendored
Normal file
63
types/lib/java.d.ts
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Asynchronously Pings a Minecraft Java Edition server.
|
||||
* This function performs an SRV lookup and then attempts to connect and retrieve the server status.
|
||||
* @param {string} host - The server address to ping.
|
||||
* @param {JavaPingOptions} [options={}] - Optional configuration.
|
||||
* @returns {Promise<JavaPingResponse>} A promise that resolves with the server's status.
|
||||
*/
|
||||
export function pingJava(host: string, options?: JavaPingOptions): Promise<JavaPingResponse>;
|
||||
/**
|
||||
* Represents the structured and user-friendly response from a server ping.
|
||||
* The fields and their optionality are based on the protocol documentation.
|
||||
* See [Status Response Documentation](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response) for more details.
|
||||
*/
|
||||
export type JavaPingResponse = {
|
||||
/**
|
||||
* - Contains the server's version name and protocol number.
|
||||
*/
|
||||
version: {
|
||||
name: string;
|
||||
protocol: number;
|
||||
};
|
||||
/**
|
||||
* - Player count and a sample of online players.
|
||||
*/
|
||||
players?: {
|
||||
max: number;
|
||||
online: number;
|
||||
sample?: Array<{
|
||||
name: string;
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* - The server's Message of the Day (MOTD).
|
||||
*/
|
||||
description?: object | string;
|
||||
/**
|
||||
* - A Base64-encoded 64x64 PNG image data URI.
|
||||
*/
|
||||
favicon?: string;
|
||||
/**
|
||||
* - True if the server requires clients to have a Mojang-signed public key.
|
||||
*/
|
||||
enforcesSecureChat?: boolean;
|
||||
/**
|
||||
* - True if a mod is installed to disable chat reporting.
|
||||
*/
|
||||
preventsChatReports?: boolean;
|
||||
};
|
||||
export type JavaPingOptions = {
|
||||
/**
|
||||
* - The fallback port if an SRV record is not found.
|
||||
*/
|
||||
port?: number;
|
||||
/**
|
||||
* - The connection timeout in milliseconds.
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* - The protocol version to use in the handshake. `-1` is for auto-detection.
|
||||
*/
|
||||
protocolVersion?: number;
|
||||
};
|
49
types/lib/varint.d.ts
vendored
Normal file
49
types/lib/varint.d.ts
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Encodes an integer into a VarInt buffer.
|
||||
* VarInts are never longer than 5 bytes for the Minecraft protocol.
|
||||
* @param {number} value The integer to encode
|
||||
* @returns {Buffer} The encoded VarInt as a buffer
|
||||
* @throws {VarIntError} if the value is too large to be encoded
|
||||
*/
|
||||
export function encodeVarInt(value: number): Buffer;
|
||||
/**
|
||||
* Encodes a string into a UTF-8 buffer.
|
||||
* @param {string} value The string to encode
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
export function encodeString(value: string): Buffer;
|
||||
/**
|
||||
* Encodes an unsigned short (16-bit big-endian) into a 2-byte buffer.
|
||||
* @param {number} value The number to encode
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
export function encodeUShort(value: number): Buffer;
|
||||
/**
|
||||
* Creates a Minecraft-style packet by concatenating chunks and prefixing the total length as a VarInt.
|
||||
* @param {Buffer[]} chunks An array of buffers to include in the packet payload
|
||||
* @returns {Buffer} The complete packet with its length prefix
|
||||
*/
|
||||
export function concatPackets(chunks: Buffer[]): Buffer;
|
||||
/**
|
||||
* Decodes a VarInt from a buffer.
|
||||
* Returns the decoded value and the number of bytes it consumed.
|
||||
* @param {Buffer} buffer The buffer to read from
|
||||
* @param {number} [offset=0] The starting offset in the buffer
|
||||
* @returns {{ value: number, bytesRead: number }}
|
||||
* @throws {VarIntError} if the buffer is too short or the VarInt is malformed
|
||||
*/
|
||||
export function decodeVarInt(buffer: Buffer, offset?: number): {
|
||||
value: number;
|
||||
bytesRead: number;
|
||||
};
|
||||
export const ERR_VARINT_BUFFER_UNDERFLOW: "VARINT_BUFFER_UNDERFLOW";
|
||||
export const ERR_VARINT_MALFORMED: "VARINT_MALFORMED";
|
||||
export const ERR_VARINT_ENCODE_TOO_LARGE: "VARINT_ENCODE_TOO_LARGE";
|
||||
export class VarIntError extends Error {
|
||||
/**
|
||||
* @param {string} message The error message.
|
||||
* @param {string} code The error code.
|
||||
*/
|
||||
constructor(message: string, code: string);
|
||||
code: string;
|
||||
}
|
Reference in New Issue
Block a user