Update Platforms #390
@ -1,3 +0,0 @@
|
||||
node_modules/
|
||||
.git/
|
||||
dist/
|
19
.vscode/extensions.json
vendored
19
.vscode/extensions.json
vendored
@ -1,7 +1,22 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"github.copilot",
|
||||
"formulahendry.code-runner",
|
||||
"chrmarti.regex"
|
||||
"chrmarti.regex",
|
||||
"benshabatnoam.google-translate-ext",
|
||||
"eamodio.gitlens",
|
||||
"github.vscode-pull-request-github",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml",
|
||||
"ms-vscode-remote.remote-containers",
|
||||
"wix.vscode-import-cost",
|
||||
"eg2.vscode-npm-script",
|
||||
"christian-kohler.npm-intellisense",
|
||||
"christian-kohler.path-intellisense",
|
||||
"aaron-bond.better-comments",
|
||||
"vscode-icons-team.vscode-icons",
|
||||
"me-dutour-mathieu.vscode-github-actions",
|
||||
"cschleiden.vscode-github-actions",
|
||||
"oderwat.indent-rainbow",
|
||||
"ms-azuretools.vscode-docker"
|
||||
]
|
||||
}
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Bds Test",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "node",
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
||||
"args": ["testProject.ts"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
}
|
||||
]
|
||||
}
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,6 +1,11 @@
|
||||
{
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"files.encoding": "utf8",
|
||||
"files.defaultLanguage": "typescript",
|
||||
"editor.minimap.enabled": false,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.exclude": {
|
||||
"**/dist/": true,
|
||||
"**/node_modules/": true
|
||||
|
@ -16,8 +16,7 @@
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "run-s build:*",
|
||||
"test": "node testProject.js",
|
||||
"test:docker": "docker build . -f test.Dockerfile",
|
||||
"test": "ts-node testProject.ts",
|
||||
"build:cjs": "tsc --outDir dist/cjs --module commonjs",
|
||||
"build:esm": "tsc --outDir dist/esm --module es6 && node -e 'const fs = require(\"fs\");const path = require(\"path\");const read = (pathRe) => {for (const fileFolde of fs.readdirSync(pathRe)) {const filePath = path.join(pathRe, fileFolde);if (fs.statSync(filePath).isDirectory()) read(filePath);else {console.log(filePath, \"-->\", filePath.replace(/\\.js$/, \".mjs\"));fs.renameSync(filePath, filePath.replace(/\\.js$/, \".mjs\"));}}};read(\"dist/esm\");'"
|
||||
},
|
||||
|
@ -1,23 +0,0 @@
|
||||
import downloadServer from "./download_server";
|
||||
export const name = "installServer";
|
||||
|
||||
export async function pocketmine() {
|
||||
console.log("Installing pocketmine server");
|
||||
const data = await downloadServer("pocketmine", "latest");
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function java() {
|
||||
console.log("Installing java server");
|
||||
const data = await downloadServer("java", "latest");
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function spigot() {
|
||||
console.log("Installing spigot server");
|
||||
const data = await downloadServer("spigot", "latest");
|
||||
console.log(data);
|
||||
return data;
|
||||
}
|
@ -4,6 +4,7 @@ import { gitBackupOption } from "./backup/git";
|
||||
export type Platform = "bedrock"|"java"|"pocketmine"|"spigot";
|
||||
export const PlatformArray = ["bedrock", "java", "pocketmine", "spigot"];
|
||||
|
||||
// Bds Session on declaretion function types
|
||||
export type bdsSessionCommands = {
|
||||
/** Exec any commands in server */
|
||||
execCommand: (...command: Array<string|number>) => bdsSessionCommands;
|
||||
@ -16,46 +17,55 @@ export type bdsSessionCommands = {
|
||||
/** Stop Server */
|
||||
stop: () => Promise<number|null>;
|
||||
};
|
||||
|
||||
export type startServerOptions = {
|
||||
/** Save only worlds/maps without server software - (Beta) */
|
||||
storageOnlyWorlds?: boolean;
|
||||
gitBackup?: gitBackupOption;
|
||||
};
|
||||
export type playerAction1 = {player: string, Date: Date; xuid?: string|undefined}
|
||||
export type playerAction2 = playerAction1 & {action: "connect"|"disconnect"|"unknown"}
|
||||
|
||||
// Server events
|
||||
export interface serverOn {
|
||||
(act: "started", fn: (data: Date) => void);
|
||||
(act: "err", fn: (data: Error|number) => void);
|
||||
(act: "closed", fn: (data: number) => void);
|
||||
(act: "player_ban", fn: (data: playerAction1) => void);
|
||||
(act: "player", fn: (data: playerAction2) => void);
|
||||
(act: "player_connect", fn: (data: playerAction1) => void);
|
||||
(act: "player_disconnect", fn: (data: playerAction1) => void);
|
||||
(act: "player_unknown", fn: (data: playerAction1) => void);
|
||||
(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void);
|
||||
(act: "log", fn: (data: string) => void);
|
||||
(act: "log_stdout", fn: (data: string) => void);
|
||||
(act: "log_stderr", fn: (data: string) => void);
|
||||
}
|
||||
export type serverListen = {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"};
|
||||
export type playerObject = {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
|
||||
|
||||
export type BdsSession = {
|
||||
/** Server Session ID */
|
||||
id: string;
|
||||
/** Server Started date */
|
||||
startDate: Date;
|
||||
logFile?: string;
|
||||
/** register cron job to create backups */
|
||||
creteBackup: (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip", pathStorage?: string}) => CronJob;
|
||||
/** Get server players historic connections */
|
||||
Player: playerObject;
|
||||
/** Get Server ports. listening. */
|
||||
ports: Array<serverListen>;
|
||||
/** if exists server map get world seed, fist map not get seed */
|
||||
seed?: string|number;
|
||||
/** Server Started */
|
||||
started: boolean;
|
||||
/** Some platforms may have a plugin manager. */
|
||||
addonManeger?: any;
|
||||
/** register cron job to create backups */
|
||||
creteBackup: (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip"}) => CronJob;
|
||||
/** callback to log event */
|
||||
log: {
|
||||
on: (eventName: "all"|"err"|"out", listener: (data: string) => void) => void;
|
||||
once: (eventName: "all"|"err"|"out", listener: (data: string) => void) => void;
|
||||
};
|
||||
/** If the server crashes or crashes, the callbacks will be called. */
|
||||
onExit: (callback: (code: number) => void) => void;
|
||||
/** Server actions, example on avaible to connect or banned¹ */
|
||||
server: {
|
||||
/** Server actions, example on avaible to connect or banned¹ */
|
||||
on: (act: "started"|"ban", call: (...any) => void) => void;
|
||||
on: serverOn;
|
||||
/** Server actions, example on avaible to connect or banned¹ */
|
||||
once: (act: "started"|"ban", call: (...any) => void) => void;
|
||||
once: serverOn;
|
||||
/** Server Started date */
|
||||
startDate: Date;
|
||||
/** Server Started */
|
||||
started: boolean;
|
||||
};
|
||||
/** Get server players historic connections */
|
||||
getPlayer: () => {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
|
||||
/** This is a callback that call a function, for some player functions */
|
||||
onPlayer: (callback: (data: {player: string; action?: "connect"|"disconnect"|"unknown"; date: Date;}) => string) => void;
|
||||
/** Get Server ports. listening. */
|
||||
ports: () => Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}>;
|
||||
/** Basic server functions. */
|
||||
commands: bdsSessionCommands;
|
||||
};
|
@ -1,4 +1,11 @@
|
||||
export default parse;
|
||||
|
||||
/**
|
||||
* Parse Proprieties files and return a map of properties.
|
||||
*
|
||||
* @param Proper - String with the properties or similar files
|
||||
* @returns
|
||||
*/
|
||||
export function parse(Proper: string): {[key: string]: string|number|true|false|null} {
|
||||
const ProPri = {};
|
||||
const ProperSplit = Proper.replace(/\r\n/g, "\n").replace(/\\\n/gi, "").split("\n").map(Line => Line.trim()).filter(line => /.*(\s+)?\=(\s+)?.*/.test(line) && !/^#/.test(line));
|
||||
@ -15,6 +22,12 @@ export function parse(Proper: string): {[key: string]: string|number|true|false|
|
||||
return ProPri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert json to properities files.
|
||||
*
|
||||
* @param ProPri - String with properties file
|
||||
* @returns
|
||||
*/
|
||||
export function stringify(ProPri: {[key: string]: any}): string {
|
||||
const Proper = [];
|
||||
for (const key in Object.keys(ProPri)) {
|
||||
|
50
src/lib/customEvents.ts
Normal file
50
src/lib/customEvents.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import events from "node:events";
|
||||
import { playerAction1, playerAction2 } from '../globalType';
|
||||
|
||||
declare interface bdsServerEvent {
|
||||
// Emit
|
||||
emit(act: "started", data: Date): boolean;
|
||||
emit(act: "err", data: Error|number): boolean;
|
||||
emit(act: "closed", data: number): boolean;
|
||||
emit(act: "port_listen", data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}): boolean;
|
||||
emit(act: "log", data: string): boolean;
|
||||
emit(act: "log_stdout", data: string): boolean;
|
||||
emit(act: "log_stderr", data: string): boolean;
|
||||
emit(act: "player", data: playerAction2): boolean;
|
||||
emit(act: "player_ban", data: playerAction1): boolean;
|
||||
emit(act: "player_connect", data: playerAction1): boolean;
|
||||
emit(act: "player_disconnect", data: playerAction1): boolean;
|
||||
emit(act: "player_unknown", data: playerAction1): boolean;
|
||||
|
||||
// on
|
||||
on(act: "started", fn: (data: Date) => void): this;
|
||||
on(act: "err", fn: (data: Error|number) => void): this;
|
||||
on(act: "closed", fn: (data: number) => void): this;
|
||||
on(act: "player_ban", fn: (data: playerAction1) => void): this;
|
||||
on(act: "player", fn: (data: playerAction2) => void): this;
|
||||
on(act: "player_connect", fn: (data: playerAction1) => void): this;
|
||||
on(act: "player_disconnect", fn: (data: playerAction1) => void): this;
|
||||
on(act: "player_unknown", fn: (data: playerAction1) => void): this;
|
||||
on(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
|
||||
on(act: "log", fn: (data: string) => void): this;
|
||||
on(act: "log_stdout", fn: (data: string) => void): this;
|
||||
on(act: "log_stderr", fn: (data: string) => void): this;
|
||||
|
||||
// Once
|
||||
once(act: "started", fn: (data: Date) => void): this;
|
||||
once(act: "err", fn: (data: Error|number) => void): this;
|
||||
once(act: "closed", fn: (data: number) => void): this;
|
||||
once(act: "player_ban", fn: (data: playerAction1) => void): this;
|
||||
once(act: "player", fn: (data: playerAction2) => void): this;
|
||||
once(act: "player_connect", fn: (data: playerAction1) => void): this;
|
||||
once(act: "player_disconnect", fn: (data: playerAction1) => void): this;
|
||||
once(act: "player_unknown", fn: (data: playerAction1) => void): this;
|
||||
once(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
|
||||
once(act: "log", fn: (data: string) => void): this;
|
||||
once(act: "log_stdout", fn: (data: string) => void): this;
|
||||
once(act: "log_stderr", fn: (data: string) => void): this;
|
||||
}
|
||||
|
||||
class bdsServerEvent extends events {}
|
||||
export default bdsServerEvent;
|
||||
export {bdsServerEvent};
|
13
src/lib/portIsAllocated.ts
Normal file
13
src/lib/portIsAllocated.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as net from "node:net";
|
||||
|
||||
export default async function portIsAllocated(port: number): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const tester = net.createServer()
|
||||
tester.once("error", () => () => resolve(true));
|
||||
tester.once("listening", () => {
|
||||
tester.once("close", () => resolve(false));
|
||||
tester.close();
|
||||
});
|
||||
tester.listen(port);
|
||||
});
|
||||
}
|
30
src/platform/bedrock/bedrock.test.ts
Normal file
30
src/platform/bedrock/bedrock.test.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import * as bedrock from "./index";
|
||||
|
||||
export default async function bedrockTest() {
|
||||
// Download Server
|
||||
const download = await bedrock.DownloadServer(true);
|
||||
console.log("Version: %s, Date: %o, url: %s", download.version, download.publishDate, download.url);
|
||||
|
||||
// Config
|
||||
const currentConfig = await bedrock.config.getConfig();
|
||||
console.log("Current config: %o", currentConfig);
|
||||
currentConfig.gamemode = Math.floor(Math.random() * 2) > 3 ? "survival" : "creative";
|
||||
currentConfig.worldName = "test"+Math.floor(Math.random()*100);
|
||||
const newConfig = await bedrock.config.CreateServerConfig(currentConfig);
|
||||
console.log("New config: %o", newConfig);
|
||||
|
||||
// Run Server
|
||||
const server = await bedrock.server.startServer();
|
||||
server.server.once("started", () => console.log("Server started"));
|
||||
await new Promise((resolve, reject) => {
|
||||
server.server.once("started", resolve);
|
||||
server.server.once("closed", resolve);
|
||||
server.server.once("err", reject);
|
||||
});
|
||||
await server.commands.stop();
|
||||
|
||||
// Backup
|
||||
const zipFile = await bedrock.backup.CreateBackup();
|
||||
console.log("Backup created: %o", zipFile.length);
|
||||
return;
|
||||
}
|
@ -48,7 +48,7 @@ export type bedrockConfig = {
|
||||
texturepackRequired?: true|false
|
||||
};
|
||||
|
||||
export async function CreateServerConfig(config: bedrockConfig): Promise<bedrockConfig&{configArray: string}> {
|
||||
export async function CreateServerConfig(config: bedrockConfig): Promise<bedrockConfig> {
|
||||
if (!!config.difficulty) {
|
||||
if (typeof config.difficulty === "number") {
|
||||
if (config.difficulty === 1) config.difficulty = "peaceful";
|
||||
@ -203,8 +203,7 @@ export async function CreateServerConfig(config: bedrockConfig): Promise<bedrock
|
||||
tickDistance,
|
||||
playerIdleTimeout,
|
||||
maxCpuThreads,
|
||||
texturepackRequired,
|
||||
configArray: configFileArray.join("\n")
|
||||
texturepackRequired
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +220,10 @@ type bedrockParsedConfig = {
|
||||
difficulty: "peaceful"|"easy"|"normal"|"hard",
|
||||
/** The seed to be used for randomizing the world (`If left empty a seed will be chosen at random`). */
|
||||
worldSeed: string|number,
|
||||
port: {
|
||||
v4: number,
|
||||
v6: number
|
||||
},
|
||||
/** World NBT */
|
||||
nbtParsed: {parsed: NBT, type: NBTFormat, metadata: nbtData}
|
||||
};
|
||||
@ -232,6 +235,10 @@ export async function getConfig(): Promise<bedrockParsedConfig> {
|
||||
difficulty: "normal",
|
||||
maxPlayers: 0,
|
||||
worldSeed: "",
|
||||
port: {
|
||||
v4: 19132,
|
||||
v6: 19133
|
||||
},
|
||||
nbtParsed: undefined
|
||||
};
|
||||
if (fs.existsSync(path.join(serverPath, "server.properties"))) {
|
||||
@ -241,6 +248,21 @@ export async function getConfig(): Promise<bedrockParsedConfig> {
|
||||
if (ProPri["gamemode"] !== undefined) config.gamemode = String(ProPri["gamemode"]) as "survival"|"creative"|"adventure";
|
||||
if (ProPri["max-players"] !== undefined) config.maxPlayers = Number(ProPri["max-players"]);
|
||||
if (ProPri["difficulty"] !== undefined) config.difficulty = String(ProPri["difficulty"]) as "peaceful"|"easy"|"normal"|"hard";
|
||||
if (ProPri["server-port"] !== undefined) config.port.v4 = Number(ProPri["server-port"]);
|
||||
if (ProPri["server-portv6"] !== undefined) config.port.v6 = Number(ProPri["server-portv6"]);
|
||||
if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);
|
||||
// if (ProPri["allow-cheats"] !== undefined) config.allowCheats = Boolean(ProPri["allow-cheats"]);
|
||||
// if (ProPri["allow-list"] !== undefined) config.allowList = Boolean(ProPri["allow-list"]);
|
||||
// if (ProPri["texturepack-required"] !== undefined) config.texturepackRequired = Boolean(ProPri["texturepack-required"]);
|
||||
// if (ProPri["view-distance"] !== undefined) config.viewDistance = Number(ProPri["view-distance"]);
|
||||
// if (ProPri["tick-distance"] !== undefined) config.tickDistance = Number(ProPri["tick-distance"]);
|
||||
// if (ProPri["player-idle-timeout"] !== undefined) config.playerIdleTimeout = Number(ProPri["player-idle-timeout"]);
|
||||
// if (ProPri["max-threads"] !== undefined) config.maxCpuThreads = Number(ProPri["max-threads"]);
|
||||
// if (ProPri["default-player-permission-level"] !== undefined) config.PlayerDefaultPermissionLevel = String(ProPri["default-player-permission-level"]);
|
||||
// if (ProPri["emit-server-telemetry"] !== undefined) config.emitServerTelemetry = Boolean(ProPri["emit-server-telemetry"]);
|
||||
// if (ProPri["content-log-file-enabled"] !== undefined) config.contentLogFileEnabled = Boolean(ProPri["content-log-file-enabled"]);
|
||||
// if (ProPri["compression-threshold"] !== undefined) config.compressionThreshold = Number(ProPri["compression-threshold"]);
|
||||
// if (ProPri["server-authoritative-movement"] !== undefined) config.
|
||||
const worldDatePath = path.join(serverPath, "worlds", config.worldName, "level.dat");
|
||||
if (fs.existsSync(worldDatePath)) config.nbtParsed = await nbtParse(await fsPromise.readFile(worldDatePath));
|
||||
if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { DownloadServer, config, server } from "./index";
|
||||
|
||||
export async function testBedrock() {
|
||||
const downlaod = await DownloadServer("latest");
|
||||
console.log(downlaod);
|
||||
const currentConfig = await config.getConfig();
|
||||
console.log(currentConfig);
|
||||
currentConfig.worldName = "test";
|
||||
const newConfig = await config.CreateServerConfig(currentConfig);
|
||||
console.log(newConfig);
|
||||
console.log("Starting bedrock server...");
|
||||
const session = await server.startServer();
|
||||
session.log.on("all", console.log);
|
||||
await new Promise(res => {
|
||||
session.server.once("started", (date: Date) => {
|
||||
console.log("bedrock started at", date);
|
||||
console.log("Stoping bedrock");
|
||||
res("");
|
||||
});
|
||||
});
|
||||
console.log("Stoping bedrock");
|
||||
return session.commands.stop();
|
||||
}
|
@ -1,26 +1,27 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import node_cron from "cron";
|
||||
import * as child_process from "../../childProcess";
|
||||
import { backupRoot, serverRoot } from "../../pathControl";
|
||||
import { BdsSession, bdsSessionCommands } from "../../globalType";
|
||||
import { BdsSession, bdsSessionCommands, playerAction2 } from '../../globalType';
|
||||
import { getConfig } from "./config";
|
||||
import { gitBackup, gitBackupOption } from "../../backup/git";
|
||||
import { createZipBackup } from "../../backup/zip";
|
||||
import events from "../../lib/customEvents";
|
||||
import portislisten from "../../lib/portIsAllocated";
|
||||
|
||||
const bedrockSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return bedrockSesions;}
|
||||
|
||||
const ServerPath = path.join(serverRoot, "bedrock");
|
||||
export async function startServer(): Promise<BdsSession> {
|
||||
if (!(fs.existsSync(ServerPath))) throw new Error("Install server first");
|
||||
const SessionID = crypto.randomUUID();
|
||||
const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {
|
||||
command: "",
|
||||
args: [],
|
||||
env: {...process.env}
|
||||
};
|
||||
const serverConfig = await getConfig();
|
||||
if (await portislisten(serverConfig.port.v4)) throw new Error("Port is already in use");
|
||||
if (await portislisten(serverConfig.port.v6)) throw new Error("Port is already in use");
|
||||
const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {command: "", args: [], env: {...process.env}};
|
||||
if (process.platform === "darwin") throw new Error("Run Docker image");
|
||||
Process.command = path.resolve(ServerPath, "bedrock_server"+(process.platform === "win32"?".exe":""));
|
||||
if (process.platform !== "win32") {
|
||||
@ -37,68 +38,45 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
|
||||
// Start Server
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const serverEvents = new events({captureRejections: false});
|
||||
serverEvents.setMaxListeners(0);
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
// Log Server redirect to callbacks events and exit
|
||||
ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
|
||||
ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
|
||||
ServerProcess.on("all", data => serverEvents.emit("log", data));
|
||||
ServerProcess.Exec.on("exit", code => {
|
||||
serverEvents.emit("closed", code);
|
||||
if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
|
||||
});
|
||||
|
||||
const playersConnections: {
|
||||
[player: string]: {
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date;
|
||||
history: Array<{
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date
|
||||
}>
|
||||
}
|
||||
} = {};
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
// on start
|
||||
serverEvents.on("log", lineData => {
|
||||
// [2022-05-19 22:35:09:315 INFO] Server started.
|
||||
if (/\[.*\]\s+Server\s+started\./.test(lineData)) serverEvents.emit("started", new Date());
|
||||
});
|
||||
|
||||
// Port
|
||||
onLog.on("all", data => {
|
||||
serverEvents.on("log", data => {
|
||||
const portParse = data.match(/(IPv[46])\s+supported,\s+port:\s+(.*)/);
|
||||
if (!!portParse) {
|
||||
ports.push({
|
||||
port: parseInt(portParse[2]),
|
||||
version: portParse[1] as "IPv4"|"IPv6",
|
||||
protocol: "UDP"
|
||||
});
|
||||
}
|
||||
if (!!portParse) serverEvents.emit("port_listen", {port: parseInt(portParse[2]), protocol: "UDP", version: portParse[1] as "IPv4"|"IPv6"});
|
||||
});
|
||||
|
||||
// Player
|
||||
onLog.on("all", data => {
|
||||
serverEvents.on("log", data => {
|
||||
if (/r\s+.*\:\s+.*\,\s+xuid\:\s+.*/gi.test(data)) {
|
||||
const actionDate = new Date();
|
||||
const [action, player, xuid] = (data.match(/r\s+(.*)\:\s+(.*)\,\s+xuid\:\s+(.*)/)||[]).slice(1, 4);
|
||||
const __PlayerAction: {player: string, xuid: string|undefined, action: "connect"|"disconnect"|"unknown"} = {
|
||||
player: player,
|
||||
xuid: xuid,
|
||||
action: "unknown"
|
||||
};
|
||||
if (action === "connected") __PlayerAction.action = "connect"; else if (action === "disconnected") __PlayerAction.action = "disconnect";
|
||||
if (!playersConnections[__PlayerAction.player]) playersConnections[__PlayerAction.player] = {
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate,
|
||||
history: [{
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
}]
|
||||
}; else {
|
||||
playersConnections[__PlayerAction.player].action = __PlayerAction.action;
|
||||
playersConnections[__PlayerAction.player].date = actionDate;
|
||||
playersConnections[__PlayerAction.player].history.push({
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
});
|
||||
}
|
||||
const __PlayerAction: playerAction2 = {player: player, xuid: xuid, action: "unknown", Date: actionDate};
|
||||
if (action === "connected") __PlayerAction.action = "connect";
|
||||
else if (action === "disconnected") __PlayerAction.action = "disconnect";
|
||||
|
||||
// Server player event
|
||||
serverEvents.emit("player", __PlayerAction);
|
||||
delete __PlayerAction.action;
|
||||
if (action === "connect") serverEvents.emit("player_connect", __PlayerAction);
|
||||
else if (action === "disconnect") serverEvents.emit("player_disconnect", __PlayerAction);
|
||||
else serverEvents.emit("player_unknown", __PlayerAction);
|
||||
}
|
||||
});
|
||||
|
||||
@ -164,7 +142,7 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
serverEvents.on("closed", () => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
@ -172,43 +150,50 @@ export async function startServer(): Promise<BdsSession> {
|
||||
const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
|
||||
if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
|
||||
const logStream = fs.createWriteStream(logFile, {flags: "w+"});
|
||||
logStream.write(`[${StartDate.toString()}] Server started\n\n`);
|
||||
logStream.write(`[${(new Date()).toString()}] Server started\n\n`);
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
logFile: logFile,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
seed: (await getConfig()).worldSeed,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
seed: serverConfig.worldSeed,
|
||||
ports: [],
|
||||
Player: {},
|
||||
commands: serverCommands,
|
||||
server: {
|
||||
on: (act, fn) => serverEvents.on(act, fn),
|
||||
once: (act, fn) => serverEvents.once(act, fn),
|
||||
started: false,
|
||||
startDate: new Date(),
|
||||
}
|
||||
};
|
||||
|
||||
onLog.on("all", lineData => {
|
||||
// [2022-05-19 22:35:09:315 INFO] Server started.
|
||||
if (/\[.*\]\s+Server\s+started\./.test(lineData)) {
|
||||
Seesion.started = true;
|
||||
serverEvents.emit("started", new Date());
|
||||
serverEvents.on("port_listen", Seesion.ports.push);
|
||||
serverEvents.on("started", date => {Seesion.server.started = true; Seesion.server.startDate = date;});
|
||||
serverEvents.on("player", __PlayerAction => {
|
||||
// Add to object
|
||||
if (!Seesion.Player[__PlayerAction.player]) Seesion.Player[__PlayerAction.player] = {
|
||||
action: __PlayerAction.action,
|
||||
date: __PlayerAction.Date,
|
||||
history: [{
|
||||
action: __PlayerAction.action,
|
||||
date: __PlayerAction.Date
|
||||
}]
|
||||
}; else {
|
||||
Seesion.Player[__PlayerAction.player].action = __PlayerAction.action;
|
||||
Seesion.Player[__PlayerAction.player].date = __PlayerAction.Date;
|
||||
Seesion.Player[__PlayerAction.player].history.push({
|
||||
action: __PlayerAction.action,
|
||||
date: __PlayerAction.Date
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Return Session
|
||||
bedrockSesions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete bedrockSesions[SessionID]);
|
||||
serverEvents.on("closed", () => delete bedrockSesions[SessionID]);
|
||||
return Seesion;
|
||||
}
|
@ -2,29 +2,29 @@ import * as bedrock from "./bedrock/index";
|
||||
import * as pocketmine from "./pocketmine/index";
|
||||
import * as java from "./java/index";
|
||||
import * as spigot from "./spigot/index";
|
||||
import { BdsSession } from "../globalType";
|
||||
// import { BdsSession } from "../globalType";
|
||||
//
|
||||
// type globalPlatform = {
|
||||
// [platform: string]: {
|
||||
// /**
|
||||
// * Download Server (and ¹auto install).
|
||||
// *
|
||||
// * 1: **In java server required java installation (if not installed, it will install it)**
|
||||
// */
|
||||
// DownloadServer: (version: string|boolean) => Promise<{version: string, publishDate: Date, url: string}>,
|
||||
// server: {
|
||||
// /** get server session */
|
||||
// startServer: () => Promise<BdsSession>,
|
||||
// /** Get all Server Sessions */
|
||||
// getSessions: () => {[sessionId: string]: BdsSession}
|
||||
// },
|
||||
// backup: {
|
||||
// /** Create Platform Backup, and return Buffer zip File */
|
||||
// CreateBackup: () => Promise<Buffer>,
|
||||
// /** Restore Zip Backup, this option replace local files */
|
||||
// RestoreBackup: (buffer: Buffer) => Promise<void>,
|
||||
// },
|
||||
// }
|
||||
// };
|
||||
|
||||
type globalPlatform = {
|
||||
[platform: string]: {
|
||||
/**
|
||||
* Download Server (and ¹auto install).
|
||||
*
|
||||
* 1: **In java server required java installation (if not installed, it will install it)**
|
||||
*/
|
||||
DownloadServer: (version: string|boolean) => Promise<{version: string, publishDate: Date, url: string}>,
|
||||
server: {
|
||||
/** get server session */
|
||||
startServer: () => Promise<BdsSession>,
|
||||
/** Get all Server Sessions */
|
||||
getSessions: () => {[sessionId: string]: BdsSession}
|
||||
},
|
||||
backup: {
|
||||
/** Create Platform Backup, and return Buffer zip File */
|
||||
CreateBackup: () => Promise<Buffer>,
|
||||
/** Restore Zip Backup, this option replace local files */
|
||||
RestoreBackup: (buffer: Buffer) => Promise<void>,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default {bedrock, java, pocketmine, spigot} as globalPlatform;
|
||||
export default {bedrock, java, pocketmine, spigot}
|
23
src/platform/java/java.test.ts
Normal file
23
src/platform/java/java.test.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import * as java from "./index";
|
||||
|
||||
export default async function javaTest() {
|
||||
// Download and install server
|
||||
const download = await java.DownloadServer(true);
|
||||
console.log("Version: %s, Date: %o, url: %s", download.version, download.publishDate, download.url);
|
||||
|
||||
// Start Server
|
||||
const server = await java.server.startServer();
|
||||
server.server.on("log", console.log);
|
||||
server.server.once("started", () => console.log("Server started"));
|
||||
server.server.on("port_listen", port => console.log("Server listening on port: %o with protocol: %s", port.port, port.protocol));
|
||||
await new Promise((resolve, reject) => {
|
||||
server.server.once("started", resolve);
|
||||
server.server.once("closed", code => code === null ? resolve(code) : reject(code));
|
||||
});
|
||||
await server.commands.stop();
|
||||
|
||||
// Backup
|
||||
const zipFile = await java.backup.CreateBackup();
|
||||
console.log("Backup created: %o", zipFile.length);
|
||||
return;
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import node_cron from "cron";
|
||||
import * as child_process from "../../childProcess";
|
||||
import { backupRoot, serverRoot } from "../../pathControl";
|
||||
import { BdsSession, bdsSessionCommands } from "../../globalType";
|
||||
import { BdsSession, bdsSessionCommands, serverListen } from '../../globalType';
|
||||
import { gitBackup, gitBackupOption } from "../../backup/git";
|
||||
import { createZipBackup } from "../../backup/zip";
|
||||
import events from "../../lib/customEvents";
|
||||
|
||||
const javaSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return javaSesions;}
|
||||
@ -19,27 +19,13 @@ export async function startServer(): Promise<BdsSession> {
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
const playersConnections: {[player: string]: {action: "connect"|"disconnect"|"unknown", date: Date, history: Array<{action: "connect"|"disconnect"|"unknown", date: Date}>}} = {};
|
||||
|
||||
// Parse ports
|
||||
onLog.on("all", data => {
|
||||
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
|
||||
if (!!portParse) {
|
||||
ports.push({
|
||||
port: parseInt(portParse[2]),
|
||||
version: "IPv4/IPv6"
|
||||
});
|
||||
}
|
||||
// Log Server redirect to callbacks events and exit
|
||||
ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
|
||||
ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
|
||||
ServerProcess.on("all", data => serverEvents.emit("log", data));
|
||||
ServerProcess.Exec.on("exit", code => {
|
||||
serverEvents.emit("closed", code);
|
||||
if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
|
||||
});
|
||||
|
||||
// Run Command
|
||||
@ -104,7 +90,7 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
serverEvents.on("closed", () => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
@ -116,39 +102,49 @@ export async function startServer(): Promise<BdsSession> {
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
ports: [],
|
||||
Player: {},
|
||||
seed: undefined,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
commands: serverCommands,
|
||||
server: {
|
||||
on: (act, fn) => serverEvents.on(act, fn),
|
||||
once: (act, fn) => serverEvents.once(act, fn),
|
||||
started: false,
|
||||
startDate: StartDate
|
||||
}
|
||||
};
|
||||
|
||||
onLog.on("all", lineData => {
|
||||
// Detect server start
|
||||
serverEvents.on("log", lineData => {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
|
||||
Seesion.started = true;
|
||||
serverEvents.emit("started", new Date());
|
||||
const StartDate = new Date();
|
||||
Seesion.server.startDate = StartDate;
|
||||
serverEvents.emit("started", StartDate);
|
||||
Seesion.server.started = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Parse ports
|
||||
serverEvents.on("log", data => {
|
||||
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
|
||||
if (!!portParse) {
|
||||
const portObject: serverListen = {
|
||||
port: parseInt(portParse[2]),
|
||||
version: "IPv4/IPv6"
|
||||
};
|
||||
serverEvents.emit("port_listen", portObject);
|
||||
Seesion.ports.push(portObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Return Session
|
||||
javaSesions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete javaSesions[SessionID]);
|
||||
serverEvents.on("closed", () => delete javaSesions[SessionID]);
|
||||
return Seesion;
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import node_cron from "cron";
|
||||
import * as child_process from "../../childProcess";
|
||||
import { backupRoot, serverRoot } from "../../pathControl";
|
||||
import { BdsSession, bdsSessionCommands } from "../../globalType";
|
||||
import { BdsSession, bdsSessionCommands, serverListen, playerAction2 } from '../../globalType';
|
||||
import { gitBackup, gitBackupOption } from "../../backup/git";
|
||||
import { createZipBackup } from "../../backup/zip";
|
||||
import events from "../../lib/customEvents";
|
||||
|
||||
const pocketmineSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return pocketmineSesions;}
|
||||
@ -31,79 +31,12 @@ export async function startServer(): Promise<BdsSession> {
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
|
||||
const playersConnections: {
|
||||
[player: string]: {
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date;
|
||||
history: Array<{
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date
|
||||
}>
|
||||
}
|
||||
} = {};
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
|
||||
// Port listen
|
||||
onLog.on("all", data => {
|
||||
// [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
|
||||
// [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
|
||||
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
|
||||
const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
|
||||
if (!!matchString) {
|
||||
const portParse = matchString[1];
|
||||
const isIpv6 = /\[.*\]/.test(portParse);
|
||||
if (isIpv6) {
|
||||
ports.push({
|
||||
port: parseInt(portParse.replace(/\[.*\]:/, "").trim()),
|
||||
version: "IPv6"
|
||||
});
|
||||
} else {
|
||||
ports.push({
|
||||
port: parseInt(portParse.split(":")[1]),
|
||||
version: "IPv4"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Player Actions
|
||||
onLog.on("all", data => {
|
||||
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
|
||||
const actionDate = new Date();
|
||||
const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
|
||||
const __PlayerAction: {player: string, action: "connect"|"disconnect"|"unknown"} = {
|
||||
player: player,
|
||||
action: "unknown"
|
||||
};
|
||||
if (action === "joined") __PlayerAction.action = "connect";
|
||||
else if (action === "left") __PlayerAction.action = "disconnect";
|
||||
if (!playersConnections[__PlayerAction.player]) playersConnections[__PlayerAction.player] = {
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate,
|
||||
history: [{
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
}]
|
||||
}; else {
|
||||
playersConnections[__PlayerAction.player].action = __PlayerAction.action;
|
||||
playersConnections[__PlayerAction.player].date = actionDate;
|
||||
playersConnections[__PlayerAction.player].history.push({
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
const { onExit, on: execOn } = ServerProcess;
|
||||
// Log Server redirect to callbacks events and exit
|
||||
execOn("out", data => serverEvents.emit("log_stdout", data));
|
||||
execOn("err", data => serverEvents.emit("log_stderr", data));
|
||||
execOn("all", data => serverEvents.emit("log", data));
|
||||
onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
|
||||
|
||||
// Run Command
|
||||
const serverCommands: bdsSessionCommands = {
|
||||
@ -167,7 +100,7 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
serverEvents.on("closed", () => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
@ -179,39 +112,89 @@ export async function startServer(): Promise<BdsSession> {
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
ports: [],
|
||||
Player: {},
|
||||
seed: undefined,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
commands: serverCommands,
|
||||
server: {
|
||||
on: (act, fn) => serverEvents.on(act, fn),
|
||||
once: (act, fn) => serverEvents.once(act, fn),
|
||||
startDate: StartDate,
|
||||
started: false
|
||||
}
|
||||
};
|
||||
|
||||
onLog.on("all", lineData => {
|
||||
// On server started
|
||||
serverEvents.on("log", lineData => {
|
||||
// [22:52:05.580] [Server thread/INFO]: Done (0.583s)! For help, type "help" or "?"
|
||||
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
|
||||
Seesion.started = true;
|
||||
serverEvents.emit("started", new Date());
|
||||
const StartDate = new Date();
|
||||
Seesion.server.startDate = StartDate;
|
||||
serverEvents.emit("started", StartDate);
|
||||
Seesion.server.started = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Port listen
|
||||
serverEvents.on("log", data => {
|
||||
// [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
|
||||
// [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
|
||||
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
|
||||
const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
|
||||
if (!!matchString) {
|
||||
const portParse = matchString[1];
|
||||
const isIpv6 = /\[.*\]:/.test(portParse);
|
||||
const portObject: serverListen = {port: 0, version: "IPv4"};
|
||||
if (!isIpv6) portObject.port = parseInt(portParse.split(":")[1]);
|
||||
else {
|
||||
portObject.port = parseInt(portParse.replace(/\[.*\]:/, "").trim())
|
||||
portObject.version = "IPv6";
|
||||
}
|
||||
serverEvents.emit("port_listen", portObject);
|
||||
Seesion.ports.push(portObject);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Player Actions
|
||||
serverEvents.on("log", data => {
|
||||
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
|
||||
const actionDate = new Date();
|
||||
const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
|
||||
const __PlayerAction: playerAction2 = {player: player, action: "unknown", Date: actionDate};
|
||||
if (action === "joined") __PlayerAction.action = "connect";
|
||||
else if (action === "left") __PlayerAction.action = "disconnect";
|
||||
if (!Seesion.Player[__PlayerAction.player]) Seesion.Player[__PlayerAction.player] = {
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate,
|
||||
history: [{
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
}]
|
||||
}; else {
|
||||
Seesion.Player[__PlayerAction.player].action = __PlayerAction.action;
|
||||
Seesion.Player[__PlayerAction.player].date = actionDate;
|
||||
Seesion.Player[__PlayerAction.player].history.push({
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
});
|
||||
}
|
||||
|
||||
// Server player event
|
||||
serverEvents.emit("player", __PlayerAction);
|
||||
delete __PlayerAction.action;
|
||||
if (action === "connect") serverEvents.emit("player_connect", __PlayerAction);
|
||||
else if (action === "disconnect") serverEvents.emit("player_disconnect", __PlayerAction);
|
||||
else serverEvents.emit("player_unknown", __PlayerAction);
|
||||
}
|
||||
});
|
||||
|
||||
// Return Session
|
||||
pocketmineSesions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete pocketmineSesions[SessionID]);
|
||||
serverEvents.on("closed", () => delete pocketmineSesions[SessionID]);
|
||||
return Seesion;
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import crypto from "node:crypto";
|
||||
import node_cron from "cron";
|
||||
import * as child_process from "../../childProcess";
|
||||
import { backupRoot, serverRoot } from "../../pathControl";
|
||||
import { BdsSession, bdsSessionCommands } from "../../globalType";
|
||||
import { BdsSession, bdsSessionCommands, serverListen } from '../../globalType';
|
||||
import { gitBackup, gitBackupOption } from "../../backup/git";
|
||||
import { createZipBackup } from "../../backup/zip";
|
||||
import events from "../../lib/customEvents";
|
||||
|
||||
const javaSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return javaSesions;}
|
||||
@ -19,28 +19,12 @@ export async function startServer(): Promise<BdsSession> {
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
const playersConnections: {[player: string]: {action: "connect"|"disconnect"|"unknown", date: Date, history: Array<{action: "connect"|"disconnect"|"unknown", date: Date}>}} = {};
|
||||
|
||||
// Parse ports
|
||||
onLog.on("all", data => {
|
||||
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
|
||||
if (!!portParse) {
|
||||
ports.push({
|
||||
port: parseInt(portParse[2]),
|
||||
version: "IPv4/IPv6"
|
||||
});
|
||||
}
|
||||
});
|
||||
const { onExit, on: execOn } = ServerProcess;
|
||||
// Log Server redirect to callbacks events and exit
|
||||
execOn("out", data => serverEvents.emit("log_stdout", data));
|
||||
execOn("err", data => serverEvents.emit("log_stderr", data));
|
||||
execOn("all", data => serverEvents.emit("log", data));
|
||||
onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
|
||||
|
||||
// Run Command
|
||||
const serverCommands: bdsSessionCommands = {
|
||||
@ -104,7 +88,7 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
serverEvents.on("closed", () => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
@ -116,39 +100,44 @@ export async function startServer(): Promise<BdsSession> {
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
ports: [],
|
||||
Player: {},
|
||||
seed: undefined,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
commands: serverCommands,
|
||||
server: {
|
||||
on: (act, fn) => serverEvents.on(act, fn),
|
||||
once: (act, fn) => serverEvents.once(act, fn),
|
||||
started: false,
|
||||
startDate: StartDate
|
||||
}
|
||||
};
|
||||
|
||||
onLog.on("all", lineData => {
|
||||
serverEvents.on("log", lineData => {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
|
||||
Seesion.started = true;
|
||||
serverEvents.emit("started", new Date());
|
||||
const StartDate = new Date();
|
||||
Seesion.server.startDate = StartDate;
|
||||
serverEvents.emit("started", StartDate);
|
||||
Seesion.server.started = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Parse ports
|
||||
serverEvents.on("log", data => {
|
||||
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
|
||||
if (!!portParse) {
|
||||
const portObject: serverListen = {port: parseInt(portParse[2]), version: "IPv4/IPv6"};
|
||||
serverEvents.emit("port_listen", portObject);
|
||||
Seesion.ports.push(portObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Return Session
|
||||
javaSesions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete javaSesions[SessionID]);
|
||||
serverEvents.on("closed", () => delete javaSesions[SessionID]);
|
||||
return Seesion;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import * as server from "./server";
|
||||
export const name = "startServer";
|
||||
export const depends = "installServer";
|
||||
|
||||
export async function java() {
|
||||
console.log("Starting java server...");
|
||||
const session = await server.Start("java");
|
||||
session.log.on("all", console.log);
|
||||
await new Promise(res => {
|
||||
session.server.once("started", (date: Date) => {
|
||||
console.log("java started at", date);
|
||||
console.log("Stoping java");
|
||||
res("");
|
||||
});
|
||||
});
|
||||
return session.commands.stop();
|
||||
}
|
||||
|
||||
export async function pocketmine() {
|
||||
console.log("Starting pocketmine server...");
|
||||
const session = await server.Start("pocketmine");
|
||||
session.log.on("all", console.log);
|
||||
session.log.on("all", data => {
|
||||
// [*] PocketMine-MP set-up wizard
|
||||
if (/set-up\s+wizard/.test(data)) {
|
||||
console.log("Auto setup wizard");
|
||||
session.commands.execCommand("eng").execCommand("y").execCommand("y");
|
||||
}
|
||||
});
|
||||
await new Promise(res => {
|
||||
session.server.once("started", (date: Date) => {
|
||||
console.log("pocketmine started at", date);
|
||||
console.log("Stoping pocketmine");
|
||||
res("")
|
||||
});
|
||||
});
|
||||
return session.commands.stop();
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
FROM ghcr.io/the-bds-maneger/container:latest
|
||||
WORKDIR /testApp
|
||||
COPY ./package*.json ./
|
||||
RUN npm ci
|
||||
COPY ./ ./
|
||||
RUN npm run test:host -- --exit-on-error --show-return
|
@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const testDir = path.join(__dirname, ".testDir");
|
||||
if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
|
||||
fs.mkdirSync(testDir);
|
||||
|
||||
/**
|
||||
* Read dir recursively
|
||||
*
|
||||
* @param {string} dir
|
||||
* @param {Array<RegExp>} filterFile
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
async function readdirRecursive(dir, filterFile) {
|
||||
if (!fs.existsSync(dir)) throw new Error(`Directory not found: ${dir}`);
|
||||
if (!(fs.statSync(dir).isDirectory())) return [dir];
|
||||
if (!filterFile) filterFile = [];
|
||||
/** @type {Array<string>} */
|
||||
const files = [];
|
||||
/** @type {Array<string>} */
|
||||
const dirs = fs.readdirSync(dir);
|
||||
for (const d of dirs) {
|
||||
const dirPath = path.resolve(dir, d);
|
||||
if (fs.statSync(dirPath).isDirectory()) files.push(...(await readdirRecursive(dirPath, filterFile)));
|
||||
else {
|
||||
if (filterFile.length <= 0) files.push(dirPath);
|
||||
else {
|
||||
if (filterFile.some(f => f.test(d))) files.push(dirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(__dirname, "dist"))) fs.rmSync(path.join(__dirname, "dist"), {recursive: true, force: true});
|
||||
console.log("Building project");
|
||||
execSync("npm run build:cjs", { stdio: "inherit" });
|
||||
|
||||
(async function() {
|
||||
/** @type {Array<{filePath: string, funcs: {name?: string, depends?: string}}>} */
|
||||
const Files = await readdirRecursive(path.join(__dirname, "dist/cjs"), [/\.test\.js$/]).then(res => res.map(x => ({filePath: x, funcs: require(x)})));
|
||||
const Sort = [];
|
||||
while (Files.length > 0) {
|
||||
const File = Files.shift();
|
||||
if (!File.funcs.depends||!File.funcs.name) Sort.push(File);
|
||||
else {
|
||||
const depends = File.funcs.depends;
|
||||
if (Sort.some(f => f.funcs.name === depends)) Sort.push(File);
|
||||
else Files.push(File);
|
||||
}
|
||||
}
|
||||
// return console.log(Sort);
|
||||
for (const {filePath: file, funcs: main} of Sort) {
|
||||
const logFile = path.join(testDir, file.replace(path.join(__dirname, "dist/cjs"), "").replace(/\/|\\/gi, "_").replace(/\.test\.[tj]s$/, "").replace(/^_/, ""), "");
|
||||
const log = [];
|
||||
for (const func of Object.keys(main).filter(key => typeof main[key] === "function")) {
|
||||
const Data = await main[func]();
|
||||
console.log(`${file}#${func}: %o`, Data);
|
||||
}
|
||||
fs.writeFileSync(logFile+".json", JSON.stringify(log, null, 2));
|
||||
}
|
||||
})();
|
49
testProject.ts
Normal file
49
testProject.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as fsOld from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
async function readDirAndFilter(dir: string, test: Array<RegExp> = [/.*/]) {
|
||||
if (!(fsOld.existsSync(dir))) throw new Error(`${dir} does not exist`);
|
||||
const files = await fs.readdir(dir);
|
||||
const parseFiles: Array<string> = []
|
||||
await Promise.all(files.map(async (fd) => {
|
||||
const stat = await fs.stat(path.join(dir, fd));
|
||||
if (stat.isDirectory()) return readDirAndFilter(path.join(dir, fd), test).then(res => parseFiles.push(...res)).catch(err => console.error(err));
|
||||
else if (stat.isFile()) {
|
||||
const match = test.some(reg => reg.test(fd));
|
||||
if (match) parseFiles.push(path.join(dir, fd));
|
||||
}
|
||||
}));
|
||||
return parseFiles;
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
const mainFind = path.join(process.cwd(), "src");
|
||||
const testsFiles = await readDirAndFilter(mainFind, [/.*\.test\.ts$/]);
|
||||
for (const file of testsFiles) {
|
||||
console.log("************** Start Script: %s **************", file);
|
||||
const testScript = await import(file) as {[key: string]: () => Promise<void>};
|
||||
if (!!testScript.default) {
|
||||
console.log("************** Start Test: %s **************", file);
|
||||
await testScript.default();
|
||||
}
|
||||
for (const key in testScript) {
|
||||
if (key === "default") continue;
|
||||
console.log("************** Start Test: %s **************", key);
|
||||
await testScript[key]();
|
||||
}
|
||||
console.log("************** End Script: %s **************", file);
|
||||
}
|
||||
}
|
||||
|
||||
runTest().then(() => {
|
||||
console.log("Test passed");
|
||||
process.exitCode = 0;
|
||||
}).catch((err: Error) => {
|
||||
console.error("Test failed");
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
}).then(() => {
|
||||
console.log("Exit with code: %d", process.exitCode);
|
||||
return process.exit();
|
||||
});
|
Reference in New Issue
Block a user