Skip to content

Instantly share code, notes, and snippets.

@zRains
Created September 17, 2022 17:12
Show Gist options
  • Save zRains/905457b550ef86cc4f7ecd4f99228e9b to your computer and use it in GitHub Desktop.
Save zRains/905457b550ef86cc4f7ecd4f99228e9b to your computer and use it in GitHub Desktop.
Get Minecraft server info by LSP using nodejs.
const { Socket } = require('net')
class MineStatus {
/** @type {Socket} */
socket
/** @type {string} */
host
/** @type {number} */
port
/** @type {number} */
packageDataLen
/** @type {Buffer} */
responseBuffers = Buffer.from('')
/** @type {{host: string, ip: string, port: string}} */
debugInfo
/**
* @param {string} host
* @param {number} port
* @param {boolean} [immediately]
*/
constructor(host, port) {
this.host = host
this.port = port
}
onConnect() {
const hostUnit8Arr = Uint8Array.from(Buffer.from(this.host))
const dataBody = MineStatus.concatData([
Uint8Array.from([0x00, 0x00]),
MineStatus.packVarint(hostUnit8Arr.length),
hostUnit8Arr,
MineStatus.concatData([
Uint8Array.from(Buffer.from(Uint16Array.from([this.port]).buffer)),
Uint8Array.from([0x01]),
]),
])
this.socket.write(MineStatus.concatData([MineStatus.packVarint(dataBody.length), dataBody]))
this.socket.write(Uint8Array.from([0x01, 0x00]))
// Set debug info here
this.debugInfo = {
host: this.host,
ip: this.socket.remoteAddress || this.host,
port: this.socket.remotePort || this.port,
}
}
/** @return {Promise<Buffer>} */
getResponseData() {
if (this.responseBuffers.length >= this.packageDataLen) return Promise.resolve(this.responseBuffers)
this.socket = new Socket().connect({ host: this.host, port: this.port })
this.socket.on('connect', this.onConnect.bind(this))
return new Promise((resolve) => {
this.socket.on('readable', () => {
if (this.socket.readableLength >= 5) {
if (this.packageDataLen === undefined) {
const packageLen = MineStatus.unPackVarint(this.socket)
const packageId = MineStatus.unPackVarint(this.socket)
const packageDataLen = MineStatus.unPackVarint(this.socket)
this.packageDataLen = packageDataLen
}
this.responseBuffers = Buffer.concat([this.responseBuffers, this.socket.read()])
if (this.responseBuffers.length >= this.packageDataLen) {
this.socket.destroy()
resolve(this.responseBuffers)
}
}
})
})
}
async getStatus() {
const responseData = await this.getResponseData()
try {
return JSON.parse(responseData.toString())
} catch (error) {
console.log(responseData.toString())
return {}
}
}
/** @return {Promise<{host: string, ip: string, port: string}>} */
getDebugInfo() {
if (this.debugInfo) return Promise.resolve(this.debugInfo)
this.socket = new Socket().connect({ host: this.host, port: this.port })
return new Promise((resolve) => {
this.socket.on('connect', () => {
this.debugInfo = {
host: this.host,
ip: this.socket.remoteAddress || this.host,
port: this.socket.remotePort || this.port,
}
this.socket.destroy()
resolve(this.debugInfo)
})
})
}
/** @param {Uint8Array[]} arrays */
static concatData(arrays) {
const totalLength = arrays.reduce((acc, value) => acc + value.length, 0)
let result = new Uint8Array(totalLength)
arrays.reduce((offset, arr) => {
result.set(arr, offset)
return offset + arr.length
}, 0)
return result
}
/** @param {number} num */
static packVarint(num) {
const buffers = []
while (true) {
if (num > 0x7f) {
buffers.push(0x80 | (num & 0x7f))
num >>= 7
} else {
buffers.push(num)
break
}
}
return Uint8Array.from(buffers)
}
/** @param {Socket} socket */
static unPackVarint(socket) {
let result = 0
for (let idx = 0; idx < 5; idx++) {
let bufferVal = socket.read(1)[0]
result |= (bufferVal & 0x7f) << (idx * 7)
if ((bufferVal & 0x80) === 0) return result
}
return result
}
}
@zRains
Copy link
Author

zRains commented Sep 18, 2022

async function start() {
  const server = new MineStatus('mc.hypixel.net', 25565)
  console.log(await server.getStatus())
}

start()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment