Feat: ping minecraft server support

This commit is contained in:
Unitwk 2024-08-19 11:25:15 +08:00
parent 317af39265
commit c33a40837b
9 changed files with 115 additions and 70 deletions

View File

@ -3,35 +3,55 @@
import net from "net";
export interface MinecraftPingResponse {
host: string;
port: number;
online: boolean;
version: string;
motd: string;
current_players: number;
max_players: number;
latency: number;
}
export default class PingMinecraftServer {
public port: number;
public host: string;
public status: any;
public status: MinecraftPingResponse;
public client?: net.Socket;
constructor(port: number, host: string) {
this.port = port;
this.host = host;
this.status = {
online: null,
version: null,
motd: null,
current_players: null,
max_players: null,
latency: null
online: false,
host,
port,
version: "",
motd: "",
current_players: 0,
max_players: 0,
latency: 0
};
}
getStatus() {
return new Promise((resolve, reject) => {
return new Promise<MinecraftPingResponse>((resolve, reject) => {
var start_time = new Date().getTime();
this.client = net.connect(this.port, this.host, () => {
this.status.latency = Math.round(new Date().getTime() - start_time);
// 0xFE packet identifier for a server list ping
// 0x01 server list ping's payload (always 1)
let data = Buffer.from([0xfe, 0x01]);
this?.client?.write(data);
});
this.client = net.connect(
{
host: this.host,
port: this.port,
timeout: 1000 * 15
},
() => {
this.status.latency = Math.round(new Date().getTime() - start_time);
// 0xFE packet identifier for a server list ping
// 0x01 server list ping's payload (always 1)
let data = Buffer.from([0xfe, 0x01]);
this?.client?.write(data);
}
);
// The client can also receive data from the server by reading from its socket.
this?.client?.on("data", (response: any) => {
@ -39,9 +59,9 @@ export default class PingMinecraftServer {
var server_info = response.toString().split("\x00\x00");
this.status = {
online: true,
host: this.host,
port: this.port,
status: true,
version: server_info[2].replace(/\u0000/g, ""),
motd: server_info[3].replace(/\u0000/g, ""),
current_players: server_info[4].replace(/\u0000/g, ""),

View File

@ -16,6 +16,8 @@ import RconCommand from "./steam/rcon_command";
import DockerResizeCommand from "./docker/docker_pty_resize";
import PtyResizeCommand from "./pty/pty_resize";
import GeneralInstallCommand from "./general/general_install";
import PingJavaMinecraftServerCommand from "./minecraft/mc_ping";
import PingMinecraftServerTask from "./task/mc_players";
// If you add a new "Preset", Please add the definition here.
export type IPresetCommand =
@ -24,7 +26,7 @@ export type IPresetCommand =
| "restart"
| "kill"
| "update"
| "getPlayer"
| "refreshPlayers"
| "command"
| "resize"
| "install";
@ -51,7 +53,7 @@ export default class FunctionDispatcher extends InstanceCommand {
instance.setPreset("kill", new GeneralKillCommand());
instance.setPreset("restart", new GeneralRestartCommand());
instance.setPreset("update", new GeneralUpdateCommand());
instance.setPreset("getPlayer", new NullCommand());
instance.setPreset("refreshPlayers", new NullCommand());
instance.setPreset("install", new GeneralInstallCommand());
// Preset the basic operation mode according to the instance startup type
@ -74,18 +76,10 @@ export default class FunctionDispatcher extends InstanceCommand {
instance.setPreset("command", new RconCommand());
}
// Set different preset functions and functions according to different types
// No suitable implementation solution found, not supported for the time being.
// if (instance.config.type.includes(Instance.TYPE_UNIVERSAL)) {
// instance.setPreset("getPlayer", new NullCommand());
// }
// if (instance.config.type.includes(Instance.TYPE_MINECRAFT_JAVA)) {
// instance.setPreset("getPlayer", new MinecraftGetPlayersCommand());
// instance.lifeCycleTaskManager.registerLifeCycleTask(new RefreshPlayer());
// }
// if (instance.config.type.includes(Instance.TYPE_MINECRAFT_BEDROCK)) {
// instance.setPreset("getPlayer", new MinecraftBedrockGetPlayersCommand());
// instance.lifeCycleTaskManager.registerLifeCycleTask(new RefreshPlayer());
// }
// Minecraft Ping
if (instance.config.type.includes(Instance.TYPE_MINECRAFT_JAVA)) {
instance.setPreset("refreshPlayers", new PingJavaMinecraftServerCommand());
instance.lifeCycleTaskManager.registerLifeCycleTask(new PingMinecraftServerTask());
}
}
}

View File

@ -0,0 +1,30 @@
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import { MCServerStatus } from "common";
export default class PingJavaMinecraftServerCommand extends InstanceCommand {
constructor() {
super("PingJavaMinecraftServerCommand");
}
async exec(instance: Instance) {
const host = instance.config.pingConfig.ip || "localhost";
try {
if (instance.config.pingConfig.port) {
const result = await new MCServerStatus(instance.config.pingConfig.port, host).getStatus();
if (result.online) {
instance.info.mcPingOnline = true;
instance.info.currentPlayers = result.current_players;
instance.info.maxPlayers = result.max_players;
instance.info.version = result.version;
} else {
instance.resetPingInfo();
}
return result;
}
} catch (error) {
// ignore error
}
return null;
}
}

View File

@ -1,6 +1,6 @@
import dgram from "dgram";
import Instance from "../instance/instance";
import InstanceCommand from "../commands/base/command";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
// Get Minecraft Bedrock server MOTD information
// Author: https://github.com/Mcayear

View File

@ -0,0 +1,22 @@
import { ILifeCycleTask } from "../../instance/life_cycle";
import Instance from "../../instance/instance";
import { MCServerStatus } from "common";
// When the instance is running, continue to check the expiration time
export default class PingMinecraftServerTask implements ILifeCycleTask {
public status: number = 0;
public name: string = "TimeCheck";
private task?: NodeJS.Timeout;
async start(instance: Instance) {
this.task = setInterval(() => {
instance.execPreset("refreshPlayers");
}, 1000 * 60);
}
async stop(instance: Instance) {
instance.resetPingInfo();
clearInterval(this.task);
}
}

View File

@ -18,12 +18,14 @@ import { t } from "i18next";
// The instance does not need to store additional information persistently
interface IInstanceInfo {
mcPingOnline: boolean;
currentPlayers: number;
maxPlayers: number;
version: string;
fileLock: number;
playersChart: Array<{ value: string }>;
openFrpStatus: boolean;
latency: number;
}
interface IWatcherInfo {
@ -74,12 +76,14 @@ export default class Instance extends EventEmitter {
public config: InstanceConfig;
public info: IInstanceInfo = {
currentPlayers: -1,
maxPlayers: -1,
mcPingOnline: false,
currentPlayers: 0,
maxPlayers: 0,
version: "",
fileLock: 0,
playersChart: [],
openFrpStatus: false
openFrpStatus: false,
latency: 0
};
public watchers: Map<string, IWatcherInfo> = new Map();
@ -407,6 +411,14 @@ export default class Instance extends EventEmitter {
};
}
public resetPingInfo() {
this.info.mcPingOnline = false;
this.info.currentPlayers = 0;
this.info.maxPlayers = 0;
this.info.version = "";
this.info.latency = 0;
}
private pushOutput(data: string) {
if (data.length > LINE_MAX_SIZE * 100) {
this.outputStack.push(IGNORE_TEXT);

View File

@ -17,7 +17,7 @@ export class LifeCycleTaskManager {
this.lifeCycleTask.push(task);
}
execLifeCycleTask(type: number) {
execLifeCycleTask(type: 1 | 0) {
if (type == 1) {
this.lifeCycleTask.forEach((v) => {
if (v.status === 0) v.start(this.self);

View File

@ -1,20 +0,0 @@
import Instance from "../instance/instance";
import InstanceCommand from "../commands/base/command";
import { MCServerStatus } from "common";
export default class MinecraftGetPlayersCommand extends InstanceCommand {
constructor() {
super("MinecraftGetPlayersCommand");
}
async exec(instance: Instance) {
if (instance.config.pingConfig.ip && instance.config.pingConfig.port) {
const player = await new MCServerStatus(
instance.config.pingConfig.port,
instance.config.pingConfig.ip
).getStatus();
return player;
}
return null;
}
}

View File

@ -1,13 +0,0 @@
import { $t } from "../../i18n";
import Instance from "../instance/instance";
import InstanceCommand from "../commands/base/command";
export default class MinecraftUpdateCommand extends InstanceCommand {
constructor() {
super("MinecraftUpdateCommand");
}
async exec(instance: Instance) {
// Not supported yet
}
}