@rlqd/minecraft-server-util
Version:
A Node.js library for Minecraft servers that can retrieve status, perform queries, and RCON into servers.
68 lines (50 loc) • 1.84 kB
text/typescript
import assert from 'assert';
import dgram from 'dgram';
import { TextDecoder } from 'util';
export interface ScanLANOptions {
scanTime?: number
}
export interface ScannedServer {
host: string,
port: number,
motd: string
}
const decoder = new TextDecoder('utf8');
const pattern = /\[MOTD\](.*)\[\/MOTD\]\[AD\](\d{1,5})\[\/AD\]/;
export function scanLAN(options?: ScanLANOptions): Promise<ScannedServer[]> {
assert(typeof options === 'object' || typeof options === 'undefined', `Expected 'options' to be an 'object' or 'undefined', got '${typeof options}'`);
if (typeof options === 'object') {
assert(typeof options.scanTime === 'number' || typeof options.scanTime === 'undefined', `Expected 'options.scanTime' to be a 'number' or 'undefined', got '${typeof options.scanTime}'`);
if (typeof options.scanTime === 'number') {
assert(options.scanTime > 0, `Expected 'options.scanTime' to be greater than or equal to 0, got '${options.scanTime}'`);
}
}
const servers: ScannedServer[] = [];
const socket = dgram.createSocket('udp4');
socket.on('message', (message, info) => {
const match = decoder.decode(message).match(pattern);
if (!match || match.length < 3) return;
let port = parseInt(match[2]);
if (isNaN(port)) port = 25565;
if (servers.some((server) => server.host === info.address && server.port === port)) return;
servers.push({
host: info.address,
port,
motd: match[1]
});
});
socket.bind(4445, () => {
socket.addMembership('224.0.2.60');
});
return new Promise((resolve, reject) => {
const timeout = setTimeout(async () => {
await new Promise<void>((resolve) => socket.close(resolve));
resolve(servers);
}, options?.scanTime ?? 1000 * 5);
socket.on('error', (error: Error) => {
socket.close();
clearTimeout(timeout);
reject(error);
});
});
}