V6 initial #507

Merged
Sirherobrine23 merged 6 commits from V6_big_changes into main 2023-02-05 02:50:34 +00:00
22 changed files with 113 additions and 1918 deletions
Showing only changes of commit 3051b8e145 - Show all commits

View File

@@ -10,7 +10,7 @@
"**/node_modules/": true,
// Ignore generate tsc files
"**/dist/": true,
"**/src/**/*.js": true,
"**/src/**/*.d.ts": true,
"**/src/**/*.js": false,
"**/src/**/*.d.ts": false,
}
}

View File

@@ -1,15 +0,0 @@
import * as Bedrock from "./bedrock.js";
if (!(process.platform === "win32"||process.platform === "linux")) console.log("Bedrock disabled to %s, avaible only to Windows and Linux");
else {
describe("Bedrock", async function() {
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await Bedrock.installServer({version: "latest", platformOptions: {newId: true}})).id as string
const serverManeger = await Bedrock.startServer({id});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});
}

View File

@@ -1,14 +0,0 @@
import * as java from "./java.js";
describe("Java", function() {
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await java.installServer("latest", {newId: true})).id as string
const serverManeger = await java.startServer({
platformOptions: {id}
});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});

View File

@@ -1,12 +0,0 @@
import {installServer, startServer} from "./paper.js";
describe("PaperMC", function() {
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await installServer("latest", {newId: true})).id as string
const serverManeger = await startServer({platformOptions: {id}});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});

View File

@@ -1,12 +0,0 @@
import { installServer, startServer } from "./pocketmine.js";
describe("Pocketmine", function() {
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await installServer("latest", {newId: true})).id as string
const serverManeger = await startServer({id});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});

View File

@@ -1,12 +0,0 @@
import { installServer, startServer } from "./pwnuukit.js";
describe("Powernukkit", function() {
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await installServer("latest", {newId: true})).id as string
const serverManeger = await startServer({platformOptions: {id}});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});

View File

@@ -1,12 +0,0 @@
import * as spigot from "./spigot.js";
describe("Spigot", function (){
this.timeout(Infinity);
let id: string;
it("Install and Start", async () => {
id = (await spigot.installServer("latest", {newId: true})).id as string
const serverManeger = await spigot.startServer({platformOptions: {id}});
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
return serverManeger.waitExit();
});
});

View File

@@ -1,348 +0,0 @@
import { manegerConfigProprieties } from "../configManipulate.js";
import { randomPort } from "../lib/randomPort.js";
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import * as globalPlatfroms from "../globalPlatfroms.js";
import coreUtils from "@sirherobrine23/coreutils";
import path from "node:path";
import fsOld from "node:fs";
import fs from "node:fs/promises";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// RegExp
const portListen = /\[.*\]\s+(IPv[46])\s+supported,\s+port:\s+([0-9]+)/;
const started = /\[.*\]\s+Server\s+started\./;
const playerActionsV1 = /\[.*\]\s+Player\s+((dis|)connected):\s+(.*),\s+xuid:\s+([0-9]+)/;
const newPlayerActions = /\[.*INFO\]\s+Player\s+(Spawned|connected|disconnected):\s+([\s\S\w]+)\s+(xuid:\s+([0-9]+))?/;
const fileSave = /^(worlds|server\.properties|((permissions|allowlist)\.json))$/;
type bedrockVersionJSON = {
version: string,
date: Date,
release?: "stable"|"preview",
url: {
[platform in NodeJS.Platform]?: {
[arch in NodeJS.Architecture]?: string
}
}
};
export type installOptions = {
version: string|boolean,
release?: bedrockVersionJSON["release"],
platformOptions?: bdsPlatformOptions,
generateRandomPorts?: boolean
};
const emulaterSoftwares = [
"qemu-x86_64-static",
"qemu-x86_64",
"box64"
];
export async function installServer(installOptions: installOptions) {
if ((["android", "linux"]).includes(process.platform) && process.arch !== "x64") {
let emitThrow = true;
for (const emu of emulaterSoftwares) if (await coreUtils.childPromisses.commandExists(emu)) {emitThrow = false; break;}
if (emitThrow) throw new Error("Cannot emulate x64 architecture. Check the documentents in \"https://github.com/core/wiki/Server-Platforms#minecraft-bedrock-server-alpha\"");
}
const folderControl = await pathControl("bedrock", installOptions?.platformOptions||{id: "default"});
const allVersions = await coreUtils.httpRequest.getJSON<bedrockVersionJSON[]>("https://the-bds-maneger.github.io/BedrockFetch/all.json");
const bedrockData = ((typeof installOptions?.version === "boolean")||(installOptions?.version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => ((rel.release||"stable") !== (installOptions?.release||"stable")) && (rel.version === installOptions.version));
let platform = process.platform;
if (platform === "android") platform = "linux";
let url = bedrockData?.url[platform]?.[process.arch];
if (!url) throw new Error("No url to current os platform");
// Remover files
const files = await fs.readdir(folderControl.serverPath);
await Promise.all(files.filter(file => !fileSave.test(file)).map(file => fs.rm(path.join(folderControl.serverPath, file), {recursive: true, force: true})));
const backups = await Promise.all(files.filter(file => fileSave.test(file)).map(async file => fs.lstat(path.join(folderControl.serverPath, file)).then(res => res.isFile()?fs.readFile(path.join(folderControl.serverPath, file)).then(data => ({data, file: path.join(folderControl.serverPath, file)})).catch(() => null):null)))
// Extract file
await coreUtils.httpRequestLarge.extractZip({url, folderTarget: folderControl.serverPath});
await fs.writeFile(path.join(folderControl.serverRoot, "version_installed.json"), JSON.stringify({version: bedrockData.version, date: bedrockData.date, installDate: new Date()}));
// Restore files
if (backups.length > 0) await Promise.all(backups.filter(file => file !== null).map(({data, file}) => fs.writeFile(file, data).catch(() => null)));
if (installOptions?.generateRandomPorts||folderControl.platformIDs.length > 2) {
let v4: number, v6: number;
const platformPorts = (await Promise.all(folderControl.platformIDs.map(async id =>(await serverConfig({id})).getConfig()))).map(config => ({v4: config["server-port"], v6: config["server-portv6"]}));
while (!v4||!v6) {
const tmpNumber = await randomPort();
if (platformPorts.some(ports => ports.v4 === tmpNumber||ports.v6 == tmpNumber)) continue;
if (!v4) v4 = tmpNumber;
else v6 = tmpNumber;
};
await (await serverConfig({id: folderControl.id})).editConfig({name: "serverPort", data: v4}).editConfig({name: "serverPortv6", data: v6}).save()
}
return {
id: folderControl.id,
url: url,
version: bedrockData.version,
date: bedrockData.date
};
}
export async function startServer(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath, logsPath, id } = await pathControl("bedrock", platformOptions);
if (!fsOld.existsSync(path.join(serverPath, "bedrock_server"+(process.platform==="win32"?".exe":"")))) throw new Error("Install server fist");
const args: string[] = [];
let command = path.join(serverPath, "bedrock_server");
if ((["android", "linux"]).includes(process.platform) && process.arch !== "x64") {
args.push(command);
let emitThrow = true;
for (const emu of emulaterSoftwares) if (await coreUtils.childPromisses.commandExists(emu)) {
emitThrow = false;
command = emu;
break;
}
if (emitThrow) throw new Error("Cannot emulate x64 architecture. Check the documentents in \"https://github.com/core/wiki/Server-Platforms#minecraft-bedrock-server-alpha\"");
}
const backendStart = new Date(), logFileOut = path.join(logsPath, `${backendStart.getTime()}_${process.platform}_${process.arch}.log`);
const serverConfig: globalPlatfroms.actionsV2 = {
serverStarted(data, done) {
if (started.test(data)) done({
onAvaible: new Date(),
timePassed: Date.now() - backendStart.getTime()
});
},
portListening(data, done) {
if (!portListen.test(data)) return;
const [, protocol, port] = data.match(portListen);
done({
type: "UDP",
port: parseInt(port),
host: protocol?.trim() === "IPv4" ? "127.0.0.1" : protocol?.trim() === "IPv6" ? "[::]" : "Unknown",
protocol: protocol?.trim() === "IPv4" ? "IPv4" : protocol?.trim() === "IPv6" ? "IPv6" : "Unknown"
});
},
playerAction(data, Callbacks) {
const connectTime = new Date();
if (!(newPlayerActions.test(data)||playerActionsV1.test(data))) return;
let playerName: string, action: string, xuid: string;
if (newPlayerActions.test(data)) {
const [, actionV2,, playerNameV2,, xuidV2] = data.match(newPlayerActions);
playerName = playerNameV2;
action = actionV2;
xuid = xuidV2;
} else {
const [, actionV1,, playerNameV1, xuidV1] = data.match(newPlayerActions);
playerName = playerNameV1;
action = actionV1;
xuid = xuidV1;
}
const playerData: globalPlatfroms.playerBase = {connectTime, playerName, xuid};
if (action === "connect") Callbacks.connect(playerData);
else if (action === "disconnect") Callbacks.disconnect(playerData);
else if (action === "Spawned") Callbacks.spawn(playerData);
else Callbacks.unknown({...playerData, lineString: data});
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
playerTp(actions, playerName, x, y, z) {
if (!/".*"/.test(playerName) && playerName.includes(" ")) playerName = `"${playerName}"`;
actions.runCommand("tp", playerName, x, y, z);
},
};
return globalPlatfroms.actionV2({
id,
platform: "bedrock",
processConfig: {command, args, options: {cwd: serverPath, maxBuffer: Infinity, env: {LD_LIBRARY_PATH: process.platform === "win32"?undefined:serverPath}, logPath: {stdout: logFileOut}}},
hooks: serverConfig
});
}
export type editConfig =
{name: "serverName", data: string}|
{name: "gamemode", data: "survival"|"creative"|"adventure"}|
{name: "forceGamemode", data: boolean}|
{name: "difficulty", data: "peaceful"|"easy"|"normal"|"hard"}|
{name: "allowCheats", data: boolean}|
{name: "maxPlayers", data: number}|
{name: "onlineMode", data: boolean}|
{name: "allowList", data: boolean}|
{name: "serverPort", data: number}|
{name: "serverPortv6", data: number}|
{name: "viewDistance", data: number}|
{name: "tickDistance", data: "4"|"6"|"8"|"10"|"12"}|
{name: "playerIdleTimeout", data: number}|
{name: "maxThreads", data: number}|
{name: "levelName", data: string}|
{name: "levelSeed", data?: string}|
{name: "levelType", data?: "FLAT"|"LEGACY"|"DEFAULT"}|
{name: "defaultPlayerPermissionLevel", data: "visitor"|"member"|"operator"}|
{name: "texturepackRequired", data: boolean}|
{name: "chatRestriction", data: "None"|"Dropped"|"Disabled"}|
{name: "mojangTelemetry", data: boolean};
export type bedrockConfig = {
"server-name": string,
"gamemode": "survival"|"creative"|"adventure",
"force-gamemode": boolean,
"difficulty": "peaceful"|"easy"|"normal"|"hard",
"allow-cheats": boolean,
"max-players": number,
"online-mode": true,
"allow-list": boolean,
"server-port": number,
"server-portv6": number,
"view-distance": number,
"tick-distance": "4"|"6"|"8"|"10"|"12",
"player-idle-timeout": number,
"max-threads": number,
"level-name": string,
"level-seed": any,
"default-player-permission-level": "visitor"|"member"|"operator",
"texturepack-required": boolean,
"content-log-file-enabled": boolean,
"compression-threshold": number,
"server-authoritative-movement": string,
"player-movement-score-threshold": number,
"player-movement-action-direction-threshold": number,
"player-movement-distance-threshold": number,
"player-movement-duration-threshold-in-ms": number,
"correct-player-movement": boolean,
"server-authoritative-block-breaking": boolean,
"chat-restriction": "None"|"Dropped"|"Disabled",
"disable-player-interaction": boolean,
"emit-server-telemetry"?: boolean
}
export async function serverConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath } = await pathControl("bedrock", platformOptions);
const fileProperties = path.join(serverPath, "server.properties");
if (!await coreUtils.extendFs.exists(fileProperties)) await fs.cp(path.join(__dirname, "../configs/java/server.properties"), fileProperties);
return manegerConfigProprieties<editConfig, bedrockConfig>({
configPath: fileProperties,
configManipulate: {
serverName: {
regexReplace: /server-name=.*/,
valueFormat: "server-name=%s"
},
gamemode: {
regexReplace: /gamemode=(survival|creative|adventure)/,
valueFormat: "gamemode=%s"
},
forceGamemode: {
regexReplace: /force-gamemode=(true|false)/,
valueFormat: "force-gamemode=%s"
},
difficulty: {
regexReplace: /difficulty=(peaceful|easy|normal|hard)/,
valueFormat: "difficulty=%s"
},
allowCheats: {
regexReplace: /allow-cheats=(false|true)/,
valueFormat: "allow-cheats=%s"
},
maxPlayers: {
regexReplace: /max-players=[0-9]+/,
valueFormat: "max-players=%s"
},
onlineMode: {
regexReplace: /online-mode=(true|false)/,
valueFormat: "online-mode=%s"
},
allowList: {
regexReplace: /allow-list=(false|true)/,
valueFormat: "allow-list=%s"
},
tickDistance: {
regexReplace: /tick-distance=(4|6|8|10|12)/,
valueFormat: "tick-distance=%f",
validate(value: number) {return ([4,6,8,10,12]).includes(value);}
},
playerIdleTimeout: {
regexReplace: /player-idle-timeout=[0-9]+/,
valueFormat: "player-idle-timeout=%f"
},
maxThreads: {
regexReplace: /max-threads=[0-9]+/,
valueFormat: "max-threads=%f"
},
levelName: {
regexReplace: /^level-name=[\s\w\S]+/,
valueFormat: "level-name=%s"
},
levelSeed: {
regexReplace: /level-seed=[0-9]+/,
valueFormat: "level-seed=%f"
},
levelType: {
regexReplace: /level-type=(FLAT|LEGACY|DEFAULT)/,
valueFormat: "level-type=%s"
},
defaultPlayerPermissionLevel: {
regexReplace: /default-player-permission-level=(visitor|member|operator)/,
valueFormat: "default-player-permission-level=%s"
},
texturepackRequired: {
regexReplace: /texturepack-required=(false|true)/,
valueFormat: "texturepack-required=%s"
},
chatRestriction: {
regexReplace: /chat-restriction=(None|Dropped|Disabled)/,
valueFormat: "chat-restriction=%s"
},
viewDistance: {
validate(value: number) {return value > 4},
regexReplace: /view-distance=[0-9]+/,
valueFormat: "view-distance=%f"
},
mojangTelemetry: {
addIfNotExist: "chat-restriction=false",
regexReplace: /chat-restriction=(true|false)/,
valueFormat: "chat-restriction=%s"
},
serverPort: {
regexReplace: /server-port=[0-9]+/,
valueFormat: "server-port=%f"
},
serverPortv6: {
regexReplace: /server-portv6=[0-9]+/,
valueFormat: "server-portv6=%f"
},
}
})
}
export type resourcePacks = {
pack_id: string,
version?: number[]
};
export type resourceManifest = {
format_version?: 2,
header: {
uuid: string,
name?: string,
description?: string,
version?: number[],
min_engine_version?: number[]
},
modules?: {
uuid: string,
type: "resources",
description?: string,
version: number[]
}[]
};
export async function addResourcePacksToWorld(resourceId: string, platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath } = await pathControl("bedrock", platformOptions);
const serverConfigObject = (await serverConfig(platformOptions)).getConfig();
if (!await coreUtils.extendFs.exists(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"))) await fs.writeFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), "[]");
const resourcesData: resourcePacks[] = JSON.parse(await fs.readFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), "utf8"));
const manifests: resourceManifest[] = await Promise.all((await coreUtils.extendFs.readdir({folderPath: [path.join(serverPath, "resource_packs"), path.join(serverPath, "worlds", serverConfigObject["level-name"], "resource_packs")]})).filter((file: string) => file.endsWith("manifest.json")).map(async (file: string) => JSON.parse(await fs.readFile(file, "utf8"))));
const packInfo = manifests.find(pf => pf.header.uuid === resourceId);
if (!packInfo) throw new Error("UUID to texture not installed in the server");
if (resourcesData.includes({pack_id: resourceId})) throw new Error("Textura alredy installed in the World");
resourcesData.push({pack_id: packInfo.header.uuid, version: packInfo.header.version});
await fs.writeFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), JSON.stringify(resourcesData, null, 2));
return resourcesData;
}

View File

@@ -1,194 +0,0 @@
import coreUtils from "@sirherobrine23/coreutils";
import { actionV2, actionsV2 } from "../globalPlatfroms.js";
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import { manegerConfigProprieties } from "../configManipulate.js";
import { randomPort } from "../lib/randomPort.js";
import fsOld from "node:fs";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { compareVersions } from "compare-versions";
import {fileURLToPath} from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
type javaRemoteVersion = {
version: string,
date: string,
latest: boolean,
url: string
};
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath, serverRoot, platformIDs, id } = await pathControl("java", platformOptions);
const allVersions = (await coreUtils.httpRequest.getJSON<javaRemoteVersion[]>("https://mcpeversion-static.sirherobrine23.org/java/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
const javaDownload = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
if (!javaDownload) throw new Error("Cannot find version!");
await coreUtils.httpRequestLarge.saveFile({url: javaDownload.url, filePath: path.join(serverPath, "server.jar")});
await fs.writeFile(path.join(serverRoot, "version_installed.json"), JSON.stringify({version: javaDownload.version, date: javaDownload.date, installDate: new Date()}));
if (platformIDs.length > 1) {
const platformPorts = (await Promise.all(platformIDs.map(id => serverConfig({id})))).map(config => config.getConfig()["server-port"]);
let port: number;
while (!port) {
const tmpNumber = await randomPort();
if (platformPorts.some(ports => ports === tmpNumber)) continue;
port = tmpNumber;
};
await (await serverConfig({id})).editConfig({name: "serverPort", data: port}).save();
}
return {
id,
version: javaDownload.version,
date: javaDownload.date,
url: javaDownload.url
};
}
export const started = /\[.*\].*\s+Done\s+\([0-9\.]+s\)\!.*/;
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
const javaHooks: actionsV2 = {
serverStarted(data, done) {
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
if (started.test(data)) done(new Date());
},
portListening(data, done) {
const portParse = data.match(portListen);
if (!portParse) return;
let [,, host, port] = portParse;
if (host === "*"||!host) host = "127.0.0.1";
done({
port: parseInt(port),
type: "TCP",
host: host,
protocol: "IPV4/IPv6"
});
},
playerAction(data, Callbacks) {
if (playerAction.test(data)) {
const [, playerName, action] = data.match(data)||[];
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
else Callbacks.unknown({playerName, connectTime: new Date()});
}
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
};
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
const { serverPath, logsPath, id } = await pathControl("java", Config?.platformOptions||{id: "default"});
const jarPath = path.join(serverPath, "server.jar");
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
const command = "java";
const args = [
"-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled",
"-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
"-XX:G1NewSizePercent=30",
"-XX:G1MaxNewSizePercent=40",
"-XX:G1HeapRegionSize=8M",
"-XX:G1ReservePercent=20",
"-XX:G1HeapWastePercent=5",
"-XX:G1MixedGCCountTarget=4",
"-XX:InitiatingHeapOccupancyPercent=15",
"-XX:G1MixedGCLiveThresholdPercent=90",
"-XX:G1RSetUpdatingPauseTimePercent=5",
"-XX:SurvivorRatio=32",
"-XX:+PerfDisableSharedMem",
"-XX:MaxTenuringThreshold=1",
"-Dusing.aikars.flags=https://mcflags.emc.gs",
"-Daikars.new.flags=true",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:-UseAESCTRIntrinsics"
];
if (Config) {
if (Config.maxFreeMemory) {
const safeFree = Math.floor(os.freemem()/1e6);
if (safeFree > 1000) Config.maxMemory = safeFree;
else console.warn("There is little ram available!")
}
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
}
args.push("-jar", jarPath, "nogui");
const eula = path.join(serverPath, "eula.txt");
await fs.writeFile(eula, (await fs.readFile(eula, "utf8").catch(() => "eula=false")).replace("eula=false", "eula=true"));
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
return actionV2({
id, platform: "java",
processConfig: {command, args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
hooks: javaHooks
});
}
export type Gamemode = {name: "Gamemode", data: "survival"|"creative"|"hardcore"};
export type Difficulty = {name: "Difficulty", data: "peaceful"|"easy"|"normal"|"hard"};
export type serverPort = {name: "serverPort", data: number};
export type maxPlayers = {name: "maxPlayers", data: number};
export type allowList = {name: "allowList", data: boolean};
export type serverDescription = {name: "serverDescription", data: string};
export type worldName = {name: "worldName", data: string};
export type editConfig = Gamemode|Difficulty|serverPort|maxPlayers|allowList|serverDescription|worldName;
/**
* Update java server config
* @param platformOptions
* @returns
*/
export async function serverConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath } = await pathControl("java", platformOptions);
const fileProperties = path.join(serverPath, "server.properties");
if (!await coreUtils.extendFs.exists(fileProperties)) await fs.cp(path.join(__dirname, "../configs/java/server.properties"), fileProperties);
return manegerConfigProprieties<editConfig>({
configPath: fileProperties,
configManipulate: {
Gamemode: (fileConfig, value: string) => {
if (!(["survival", "creative", "hardcore"]).includes(value)) throw new Error("Invalid gameode");
if (value === "hardcore") fileConfig = fileConfig.replace(/gamemode=(survival|creative)/, `gamemode=survial`).replace(/hardcore=(false|true)/, `hardcore=true`);
else {
if (!(value === "survival"||value === "creative")) throw new Error("Invalid gamemode");
fileConfig = fileConfig.replace(/gamemode=(survival|creative)/, `gamemode=${value}`).replace(/hardcore=(false|true)/, `hardcore=false`);
}
return fileConfig;
},
Difficulty: {
validate: (value: string) => (["peaceful", "easy", "normal", "hard"]).includes(value),
regexReplace: /difficulty=(peaceful|easy|normal|hard)/,
valueFormat: "difficulty=%s"
},
serverPort: {
validate: (value: number) => value > 1000,
regexReplace: /server-port=([0-9]+)/,
valueFormat: "server-port=%f"
},
maxPlayers: {
validate: (value: number) => value > 1,
regexReplace: /max-players=([0-9]+)/,
valueFormat: "max-players=%f"
},
serverDescription: {
validate: (value: string) => value.length < 50,
regexReplace: /motd=(.*)/,
valueFormat: "motd=%s"
},
worldName: {
validate: (value: string) => value.length < 50,
regexReplace: /level-name=(.*)/,
valueFormat: "level-name=%s"
},
allowList: {
validate: (value: boolean) => value === true||value === false,
regexReplace: /white-list=(true|false)/,
valueFormat: "white-list=%o"
}
}
})
}

View File

@@ -1,119 +0,0 @@
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import { spigotProprieties } from "./spigot.js";
import * as globalPlatfroms from "../globalPlatfroms.js";
import * as coreUtils from "@sirherobrine23/coreutils";
import Proprieties from "../lib/Proprieties.js";
import fsOld from "node:fs";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { compareVersions } from "compare-versions";
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath, id } = await pathControl("paper", platformOptions);
const allVersions = (await coreUtils.httpRequest.getJSON("https://mcpeversion-static.sirherobrine23.org/paper/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
const release = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
if (!release) throw new Error("Cannot find version!");
await coreUtils.httpRequestLarge.saveFile({url: release.url, filePath: path.join(serverPath, "paper.jar")});
return {
id,
version: release.version,
url: release.url,
date: release.date
};
}
export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
export const paperHook: globalPlatfroms.actionsV2 = {
serverStarted(data, done) {
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
if (started.test(data)) done(new Date());
},
playerAction(data, Callbacks) {
if (!playerAction.test(data)) return;
const [, playerName, action] = data.match(data)||[];
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
else Callbacks.unknown({playerName, connectTime: new Date()});
},
portListening(data, done) {
const portParse = data.match(portListen);
if (!portParse) return;
let [,, host, port] = portParse;
if (host === "*"||!host) host = "127.0.0.1";
done({
port: parseInt(port),
type: "TCP",
host: host,
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6"
});
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
};
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
const { serverPath, logsPath, id } = await pathControl("paper", Config?.platformOptions||{id: "default"});
if (!fsOld.existsSync(path.join(serverPath, "paper.jar"))) throw new Error("Install server fist.");
const args = [
"-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled",
"-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
"-XX:G1NewSizePercent=30",
"-XX:G1MaxNewSizePercent=40",
"-XX:G1HeapRegionSize=8M",
"-XX:G1ReservePercent=20",
"-XX:G1HeapWastePercent=5",
"-XX:G1MixedGCCountTarget=4",
"-XX:InitiatingHeapOccupancyPercent=15",
"-XX:G1MixedGCLiveThresholdPercent=90",
"-XX:G1RSetUpdatingPauseTimePercent=5",
"-XX:SurvivorRatio=32",
"-XX:+PerfDisableSharedMem",
"-XX:MaxTenuringThreshold=1",
"-Dusing.aikars.flags=https://mcflags.emc.gs",
"-Daikars.new.flags=true",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:-UseAESCTRIntrinsics"
];
if (Config) {
if (Config.maxFreeMemory) {
const safeFree = Math.floor(os.freemem()/1e6);
if (safeFree > 1000) Config.maxMemory = safeFree;
else console.warn("There is little ram available!")
}
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
}
args.push("-jar", path.join(serverPath, "paper.jar"), "nogui");
const eula = path.join(serverPath, "eula.txt");
await fs.writeFile(eula, (await fs.readFile(eula, "utf8").catch(() => "eula=false")).replace("eula=false", "eula=true"));
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
return globalPlatfroms.actionV2({
id, platform: "paper",
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
hooks: paperHook
});
}
export async function getConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath } = await pathControl("paper", platformOptions);
return Proprieties.parse<spigotProprieties>(await fs.readFile(path.join(serverPath, "server.properties"), "utf8"));
}
// This is a fast and dirty way to implement a new feature, but i'm too exhausted to implement the same type as bedrock
export async function updateConfig(config: {key: string, value: any}, platformOptions: bdsPlatformOptions = {id: "default"}) {
const currentConfig = await getConfig(platformOptions);
const { serverPath } = await pathControl("paper", platformOptions);
currentConfig[config.key] = config.value;
return fs.writeFile(path.join(serverPath, "server.properties"), Proprieties.stringify(currentConfig));
}

View File

@@ -1,115 +0,0 @@
import { childPromisses, extendFs, httpRequest, httpRequestGithub, httpRequestLarge } from "@sirherobrine23/coreutils";
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import { existsSync as fsExistsSync, Stats } from "node:fs";
import * as globalPlatfroms from "../globalPlatfroms.js";
import path from "node:path";
import fs from "node:fs/promises";
import debug from "debug";
const pocketmineDebug = debug("bdscore:platform:pocketmine");
const phpStaticBucket = "https://objectstorage.sa-saopaulo-1.oraclecloud.com/p/0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W/n/grwodtg32n4d/b/bdsFiles/o/";
export async function listVersions() {
return (await httpRequestGithub.getRelease({owner: "pmmp", repository: "PocketMine-MP", all: true})).map(data => {
const pharFile = data.assets.find(data => data.name.endsWith((".phar")));
return {
version: data.tag_name,
publish: new Date(data.published_at),
pharFile: pharFile?.browser_download_url,
size: pharFile?.size
};
}).filter(rel => !!rel.pharFile);
}
async function findPhp(serverPath: string, extraPath?: string): Promise<string> {
if (!extraPath) extraPath = path.join(serverPath, "bin");
const files = await Promise.all((await fs.readdir(extraPath)).map(file => fs.lstat(path.join(extraPath, file)).then(stat => ({stat, file, fullPath: path.join(extraPath, file)})).catch(() => {})));
let folderFF = "";
for (const file of (files.filter(a=>!!a) as {file: string, fullPath: string, stat: Stats}[]).sort(a => a.stat.isDirectory() ? 1:-1)) {
if (file.stat.isDirectory()) {
folderFF = await findPhp(serverPath, file.fullPath).catch(() => "");
if (folderFF) return folderFF;
} else if (file.file === "php"||file.file === "php.exe") return file.fullPath;
}
if (folderFF) return folderFF;
throw new Error("Cannot find php");
}
async function installPhp(serverPath: string): Promise<void> {
let filePath = (await httpRequest.getJSON<{objects: {name: string}[]}>(phpStaticBucket)).objects.find(({name}) => name.includes(process.platform) && name.includes(process.arch))?.name;
if (!filePath) {
pocketmineDebug("Current OS: %s", process.platform);
pocketmineDebug("Current Arch: %s", process.arch);
throw new Error("Cannot find php release!");
}
const binFolder = path.resolve(serverPath, "bin");
if (await extendFs.exists(binFolder)) await fs.rm(path.resolve(serverPath, "bin"), {recursive: true});
filePath = phpStaticBucket+filePath;
if (filePath.endsWith(".zip")) await httpRequestLarge.extractZip({url: filePath, folderTarget: binFolder});
else if (filePath.endsWith(".tar.gz")||filePath.endsWith(".tgz")||filePath.endsWith(".tar")) await httpRequestLarge.tarExtract({url: filePath, folderPath: binFolder});
else throw new Error("Invalid file: "+filePath);
// test it's works php
const phpExec = await findPhp(serverPath);
if (!phpExec) throw new Error("Cannot find php exec file!");
await childPromisses.execFile(phpExec, ["--version"]).catch(err => {
pocketmineDebug("PHP bin error: %O", err);
pocketmineDebug("Current OS: %s", process.platform);
pocketmineDebug("Current Arch: %s", process.arch);
throw new Error("Corrupt PHP in host!");
});
}
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
platformOptions.withBuildFolder = true;
const serverFolders = await pathControl("pocketmine", platformOptions);
await installPhp(serverFolders.serverPath);
if (typeof version === "boolean") version = "latest";
if (version?.trim()?.toLowerCase() === "latest") version = (await listVersions()).at(0)?.version
const info = (await listVersions()).find(rel => rel.version === version);
await httpRequestLarge.saveFile({url: info?.pharFile, filePath: path.join(serverFolders.serverPath, "pocketmine.phar")});
return {
id: serverFolders.id,
version: info.version,
url: info.pharFile,
date: info.publish
};
}
const portListen = /([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|\[[a-zA-Z0-9:]+\]):([0-9]+)/;
// const player = /[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi;
export async function startServer(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath, logsPath, id } = await pathControl("pocketmine", platformOptions);
const serverPhar = path.join(serverPath, "pocketmine.phar");
if (!fsExistsSync(serverPhar)) throw new Error("Install server fist!");
const runStart = new Date();
const logFileOut = path.join(logsPath, `${runStart.getTime()}_${process.platform}_${process.arch}.stdout.log`);
const pocketmineHooks: globalPlatfroms.actionsV2 = {
serverStarted(data, done) {
if (!(data.includes("INFO") && data.includes("Done") && data.includes("help"))) return;
const doneStart = new Date();
done({
onAvaible: doneStart,
timePassed: runStart.getTime() - doneStart.getTime()
});
},
portListening(data, done) {
if (!portListen.test(data)) return;
const [,, host, port] = data.match(portListen);
done({
type: "UDP",
protocol: /\[[a-zA-Z0-9:]+\]/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6",
port: parseInt(port),
host: host?.trim()
});
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
};
return globalPlatfroms.actionV2({
id, platform: "pocketmine",
processConfig: {command: await findPhp(serverPath), args: [serverPhar, "--no-wizard"], options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
hooks: pocketmineHooks
});
}

View File

@@ -1,103 +0,0 @@
import coreUtils, { httpRequestLarge } from "@sirherobrine23/coreutils";
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import * as globalPlatfroms from "../globalPlatfroms.js";
import path from "node:path";
import fsOld from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import { compareVersions } from "compare-versions";
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath, id } = await pathControl("powernukkit", platformOptions);
const jarPath = path.join(serverPath, "pwnukkit.jar");
if (!fsOld.existsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
const allVersions = (await coreUtils.httpRequest.getJSON("https://mcpeversion-static.sirherobrine23.org/paper/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
const pwNukktiData = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
if (!pwNukktiData) throw new Error("Cannot find version!");
// const pwNukktiData = await platformManeger.powernukkit.find(version);
await httpRequestLarge.saveFile({url: pwNukktiData.url, filePath: jarPath})
return {
id,
version: pwNukktiData.version,
url: pwNukktiData.url,
date: pwNukktiData.date
};
}
export const playerAction = /^.*\[.*\]\s([\S\w]+|"[\S\w]+")\s+(left|joined)\s+the\s+game$/;
export const portListen = /Opening\s+server\s+on\s+(([A-Za-z0-9:\.]+):([0-9]+))/;
export const powernukkitHooks: globalPlatfroms.actionsV2 = {
serverStarted(data, done) {
// 16:57:15 [INFO ] Done (2.122s)! For help, type "help" or "?"
if (/^.*\[.*\]\s+Done\s+\([0-9\.]+s\)!/.test(data)) done(new Date());
},
portListening(data, done) {
if (portListen.test(data)) {
const [,, host, port] = data.match(portListen);
done({
port: parseInt(port),
type: "UDP",
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6",
host: host
})
}
},
playerAction(data, Callbacks) {
if (playerAction.test(data)) {
const [, playerName, action] = data.match(playerAction)||[];
if (action === "joined") Callbacks.connect({connectTime: new Date(), playerName});
else if (action === "left") Callbacks.disconnect({connectTime: new Date(), playerName});
else Callbacks.unknown({connectTime: new Date(), playerName});
}
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
};
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
const { serverPath, logsPath, id } = await pathControl("powernukkit", Config?.platformOptions||{id: "default"});
const jarPath = path.join(serverPath, "pwnukkit.jar");
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
const args = [
"-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled",
"-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
"-XX:G1NewSizePercent=30",
"-XX:G1MaxNewSizePercent=40",
"-XX:G1HeapRegionSize=8M",
"-XX:G1ReservePercent=20",
"-XX:G1HeapWastePercent=5",
"-XX:G1MixedGCCountTarget=4",
"-XX:InitiatingHeapOccupancyPercent=15",
"-XX:G1MixedGCLiveThresholdPercent=90",
"-XX:G1RSetUpdatingPauseTimePercent=5",
"-XX:SurvivorRatio=32",
"-XX:+PerfDisableSharedMem",
"-XX:MaxTenuringThreshold=1",
"-Dusing.aikars.flags=https://mcflags.emc.gs",
"-Daikars.new.flags=true",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:-UseAESCTRIntrinsics"
];
if (Config) {
if (Config.maxFreeMemory) {
const safeFree = Math.floor(os.freemem()/1e6);
if (safeFree > 1000) Config.maxMemory = safeFree;
else console.warn("There is little ram available!")
}
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
}
args.push("-jar", jarPath, "--language", "eng");
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
return globalPlatfroms.actionV2({
id, platform: "powernukkit",
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
hooks: powernukkitHooks
});
}

View File

@@ -1,198 +0,0 @@
import { httpRequestLarge, httpRequestGithub } from "@sirherobrine23/coreutils";
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
import * as globalPlatfroms from "../globalPlatfroms.js";
import Proprieties from "../lib/Proprieties.js"
import fsOld from "node:fs";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { compareVersions } from "compare-versions";
async function listVersions(): Promise<{version: string, date: Date, url: string}[]> {
const releases = (await httpRequestGithub.getRelease({owner: "The-Bds-Maneger", repository: "SpigotBuilds", all: true})).filter(rel => rel.tag_name !== "latest" && rel.assets.some(file => file.name.endsWith(".jar")));
return releases.map(rel => ({
version: rel.tag_name,
date: new Date(rel.published_at),
url: rel.assets.find(file => file.name.endsWith(".jar"))?.browser_download_url
})).sort((a, b) => compareVersions(a.version.replace(/\-[a-zA-Z\-]+/, ".0"), b.version.replace(/\-[a-zA-Z\-]+/, ".0")));
}
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
if (!(typeof version === "boolean"||typeof version === "string" && !!version?.trim())) throw TypeError("Version only if boolean and string");
const serverPath = await pathControl("spigot", platformOptions);
const relData = (await listVersions()).find(rel => ((typeof version === "boolean"||version.trim().toLowerCase() === "latest")||version === rel.version));
if (!relData) throw new Error("no version found!");
await httpRequestLarge.saveFile({
url: relData.url,
filePath: path.join(serverPath.serverPath, "server.jar")
});
return {
id: serverPath.id,
url: relData.url,
version: version,
date: relData.date
};
}
export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
export const serverConfig: globalPlatfroms.actionsV2 = {
serverStarted(data, done) {
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
if (started.test(data)) done(new Date());
},
portListening(data, done) {
const serverPort = data.match(portListen);
if (serverPort) {
let [,, host, port] = serverPort;
if (host === "*"||!host) host = "127.0.0.1";
return done({
port: parseInt(port),
type: "TCP",
host: host,
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6"
});
}
},
playerAction(data, Callbacks) {
if (playerAction.test(data)) {
const [, playerName, action] = data.match(data)||[];
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
else Callbacks.unknown({playerName, connectTime: new Date()});
}
},
stopServer(components) {
components.actions.runCommand("stop");
return components.actions.waitExit();
},
};
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
const { serverPath, logsPath, id } = await pathControl("spigot", Config?.platformOptions||{id: "default"});
const jarPath = path.join(serverPath, "server.jar");
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
const args = [
"-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled",
"-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
"-XX:G1NewSizePercent=30",
"-XX:G1MaxNewSizePercent=40",
"-XX:G1HeapRegionSize=8M",
"-XX:G1ReservePercent=20",
"-XX:G1HeapWastePercent=5",
"-XX:G1MixedGCCountTarget=4",
"-XX:InitiatingHeapOccupancyPercent=15",
"-XX:G1MixedGCLiveThresholdPercent=90",
"-XX:G1RSetUpdatingPauseTimePercent=5",
"-XX:SurvivorRatio=32",
"-XX:+PerfDisableSharedMem",
"-XX:MaxTenuringThreshold=1",
"-Dusing.aikars.flags=https://mcflags.emc.gs",
"-Daikars.new.flags=true",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:-UseAESCTRIntrinsics"
];
if (Config) {
if (Config.maxFreeMemory) {
const safeFree = Math.floor(os.freemem()/1e6);
if (safeFree > 1000) Config.maxMemory = safeFree;
else console.warn("There is little ram available!")
}
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
}
args.push("-jar", jarPath, "nogui");
const eula = path.join(serverPath, "eula.txt");
await fs.readFile(eula, "utf8").catch(() => "eula=false").then(eulaFile => fs.writeFile(eula, eulaFile.replace("eula=false", "eula=true")));
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
return globalPlatfroms.actionV2({
id, platform: "spigot",
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
hooks: serverConfig
});
}
export type baseConfig = {
"gamemode": string,
"level-name": string,
"level-seed": number,
"pvp": boolean,
"difficulty": "easy"|"normal"|"hard",
"hardcore": boolean,
"motd": string,
"server-port": number,
"enforce-secure-profile": boolean,
"require-resource-pack": boolean,
"max-players": number,
"online-mode": boolean,
"enable-status": boolean,
"allow-flight": boolean,
"view-distance": number,
"simulation-distance": number,
"player-idle-timeout": number,
"force-gamemode": boolean,
"white-list": boolean,
"spawn-animals": boolean,
"enforce-whitelist": boolean,
"enable-command-block": boolean,
};
export type ignoreBaseConfig = {
"query.port": number,
"enable-jmx-monitoring": boolean,
"rcon.port": number,
"enable-query": boolean,
"generator-settings": "{}"|string,
"generate-structures": boolean,
"max-chained-neighbor-updates": number,
"network-compression-threshold": number,
"max-tick-time": number,
"use-native-transport": boolean,
"broadcast-rcon-to-ops": boolean,
"server-ip": string,
"resource-pack-prompt": any,
"allow-nether": boolean,
"enable-rcon": boolean,
"sync-chunk-writes": boolean,
"op-permission-level": number,
"prevent-proxy-connections": boolean,
"hide-online-players": boolean,
"resource-pack": any,
"entity-broadcast-range-percentage": number,
"rcon.password": string,
"debug": boolean,
"rate-limit": number,
"broadcast-console-to-ops": boolean,
"spawn-npcs": boolean,
"previews-chat": boolean,
"function-permission-level": number,
"level-type": "minecraft\\:normal"|string,
"text-filtering-config": any,
"spawn-monsters": boolean,
"spawn-protection": number,
"resource-pack-sha1": any|string,
"max-world-size": number
}
export type spigotProprieties = baseConfig & ignoreBaseConfig;
export async function getConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
const { serverPath } = await pathControl("spigot", platformOptions);
return Proprieties.parse<spigotProprieties>(await fs.readFile(path.join(serverPath, "server.properties"), "utf8"));
}
// This is a fast and dirty way to implement a new feature, but i'm too exhausted to implement the same type as bedrock
export async function updateConfig(config: {key: string, value: any}, platformOptions: bdsPlatformOptions = {id: "default"}) {
const currentConfig = await getConfig(platformOptions);
const { serverPath } = await pathControl("spigot", platformOptions);
currentConfig[config.key] = config.value;
return fs.writeFile(path.join(serverPath, "server.properties"), Proprieties.stringify(currentConfig));
}

View File

@@ -1,30 +0,0 @@
import fs from "node:fs/promises";
import utils from "node:util";
import Proprieties, { properitiesBase } from "./lib/Proprieties.js";
export type configEdit = {name: string, data?: any};
export async function manegerConfigProprieties<updateConfig extends configEdit, configJson extends properitiesBase = any>(config: {configPath: string, configManipulate: {[Properties in updateConfig["name"]]: ((config: string, value: updateConfig["data"]) => string)|{validate?: (value: updateConfig["data"]) => boolean, regexReplace: RegExp, valueFormat: string, addIfNotExist?: string}}}) {
let configFile = await fs.readFile(config.configPath, "utf8");
const mani = {save, editConfig, getConfig: () => Proprieties.parse<configJson>(configFile)};
async function save() {
await fs.writeFile(config.configPath, configFile);
return configFile;
}
function editConfig(serverConfig: updateConfig) {
if (!config.configManipulate[serverConfig.name]) throw new Error("Key name not exist");
const manipulation = config.configManipulate[serverConfig.name as updateConfig["name"]];
if (typeof manipulation === "function") configFile = manipulation(configFile, serverConfig.data);
else {
if (typeof manipulation.validate === "function") if (!manipulation.validate(serverConfig.data)) throw new Error("Invaid value");
if (!manipulation.regexReplace.test(configFile)) {
if (manipulation.addIfNotExist) configFile += ("\n"+manipulation.addIfNotExist);
else throw new Error("Not config exist!");
}
configFile = configFile.replace(manipulation.regexReplace, utils.format(manipulation.valueFormat, serverConfig.data));
}
return mani;
}
return mani;
}

View File

@@ -1,155 +0,0 @@
import * as coreUtils from "@sirherobrine23/coreutils";
import net from "node:net";
import crypto from "node:crypto";
import fs from "node:fs/promises";
import tar from "tar";
import { bdsRoot } from "./platformPathManeger.js";
export type payload = {
httpVersion?: string,
request?: {method: string, path: string},
response?: {code: number, text: string},
header?: {[key: string]: string|boolean|number},
second?: payload,
raw?: string,
socket?: net.Socket
body?: any,
};
export class payloadError extends Error {
public payload: payload;
constructor(errMessage: string, payload: payload) {
super(errMessage);
this.payload = payload;
}
}
const payloadRequest = /^(GET|POST|CONNECT|HEAD|PUT|DELETE)\s+(.*)\s+HTTP\/([0-9\.]+)/;
const payloadResponse = /HTTP\/([0-9\.]+)\s+([0-9]+)\s+([\w\S\s]+)/;
const parseHeard = /^([0-9A-Za-z\._-\s@]+):([\w\S\s]+)/;
async function parsePayload(input: string|Buffer|net.Socket): Promise<payload> {
const payloadBody: payload = {header: {}};
let data = "";
if (typeof input === "string") data = input; else {
if (Buffer.isBuffer(input)) data = input.toString("utf8");
else {
payloadBody.socket = input;
data = await new Promise<string>(done => input.once("data", dataInput => done(dataInput.toString("utf8"))));
}
}
payloadBody.raw = data;
if (/^{.*}$/.test(data.replace(/\r?\n/, ""))) {
payloadBody.body = JSON.parse(data);
return payloadBody;
}
for (const line of data.trim().split(/\r?\n/g)) {
data = data.replace(line, "").trim();
if (payloadResponse.test(line) && !payloadBody.request) {
const [, httpVersion, CodeNumber, responseText] = line.match(payloadResponse);
payloadBody.httpVersion = httpVersion;
payloadBody.response = {
code: parseInt(CodeNumber),
text: responseText
};
} else if (payloadRequest.test(line) && !payloadBody.response) {
const [, method, reqPath, httpVersion] = line.match(payloadRequest);
if (!payloadBody.request) {
payloadBody.httpVersion = httpVersion;
payloadBody.request = {
method: method,
path: reqPath
};
continue;
} else payloadBody.second = await parsePayload(`${line}\r\n${data}`);
break;
} else if (parseHeard.test(line.trim())) {
const [, key, value] = line.trim().match(parseHeard);
if (value.trim() === "false"||value.trim() === "true") payloadBody.header[key.trim()] = Boolean(value.trim());
else if (/^[0-9]+$/.test(value.trim())) payloadBody.header[key.trim()] = parseFloat(value.trim());
else payloadBody.header[key.trim()] = value.trim();
continue;
}
payloadBody.body = data.trim();
if (line.trim() === "") break;
};
if (payloadBody.body) {
const backupBody = payloadBody.body;
try {
payloadBody.body = JSON.parse(backupBody);
} catch (_) {
payloadBody.body = backupBody;
}
}
if (payloadBody.response) {
if (!/[12][0-9][0-9]/.test(payloadBody.response.code.toFixed())) throw new payloadError(`Response code ${payloadBody.response.code}, text: ${payloadBody.response.text}`, payloadBody);
}
return payloadBody;
}
function stringifyPayload(socket: net.Socket, response: payload) {
let message = "";
if (response.request) message += `${response.request.method.toUpperCase()} ${response.request.path} HTTP/${response.httpVersion||"1.0"}\r\n`;
else message += `HTTP/${response.httpVersion||"1.0"} ${response.response.code} ${response.response.text}\r\n`;
if (response.header) message += (Object.keys(response.header).map(key => `${key}: ${response.header[key]}`).join("\r\n"))+"\r\n";
message += "\r\n";
if (response.body !== undefined) {
if (Array.isArray(response.body)||typeof response.body === "object") message += JSON.stringify(response.body);
else if (typeof response.body === "string") message += response.body;
else if (typeof response.body === "bigint") message += response.body.toString();
else message += String(response.body);
}
socket.write(message);
return socket;
}
export class exportBds {
public acceptConnection = true;
public authToken = crypto.randomBytes(16).toString("base64");
#server = net.createServer(async (socket): Promise<any> => {
if (!this.acceptConnection) return stringifyPayload(socket, {response: {code: 400, text: "Server locked"}, body: {erro: "Server locked"}}).end();
const payload = await parsePayload(await new Promise<string>(done => socket.once("data", res => done(res.toString("utf8")))));
if (payload.header.Authorization !== this.authToken) return stringifyPayload(socket, {response: {code: 401, text: "Not allowed"}, body: {error: "Invalid token"}}).end();
else stringifyPayload(socket, {response: {code: 200, text: "Success"}, header: {Date: (new Date()).toISOString(), "Content-Type": "bdsStream/tar"}});
this.acceptConnection = false;
console.log("Sending to %s", socket.localAddress+":"+socket.localPort);
// Compact bds root
const tarCompress = tar.create({gzip: true, cwd: bdsRoot}, await fs.readdir(bdsRoot));
tarCompress.pipe(socket);
tarCompress.on("end", () => this.#server.close());
socket.on("close", tarCompress.destroy);
});
public async listen(port = 0) {
return new Promise<number>(done => {
this.#server.listen(port, async () => {
const externalIP = await coreUtils.httpRequestClient.getExternalIP();
let address = this.#server.address()["address"], port = this.#server.address()["port"];
if (/::/.test(address)) address = `[${address}]`;
console.log("auth token '%s'", this.authToken);
console.log("Port listen on http://%s:%s/", address, port);
console.log("Port listen on http://%s:%s/", externalIP.ipv4, port);
if (externalIP.ipv6) console.log("Port listen on http://[%s]:%s/", externalIP.ipv6, port);
return done(port);
});
});
}
public async waitClose() {
return new Promise<void>((done, reject) => {
this.#server.on("error", reject);
this.#server.once("close", done);
});
}
}
export async function importBds(option: {host: string, port: number, authToken: string}) {
if (await coreUtils.extendFs.exists(bdsRoot)) await fs.rename(bdsRoot, bdsRoot+"_backup_"+Date.now());
const {socket} = await parsePayload(stringifyPayload(net.createConnection({host: option.host, port: option.port}), {request: {method: "GET", path: "/"}, header: {Authorization: option.authToken}}));
console.info("Connection sucess!");
const tar_extract = tar.extract({cwd: bdsRoot, noChmod: false, noMtime: false, preserveOwner: true});
socket.pipe(tar_extract);
return new Promise<void>((done, reject) => {
socket.on("error", reject);
tar_extract.on("error", reject);
socket.once("close", () => tar_extract.on("finish", done));
});
}

View File

@@ -1,259 +0,0 @@
import fs from "node:fs";
import readline from "node:readline";
import child_process from "node:child_process";
import { EventEmitter } from "node:events";
import { bdsPlatform } from "./platformPathManeger.js";
import debug from "debug";
export const internalSessions: {[sessionID: string]: serverActionV2} = {};
process.once("exit", () => Object.keys(internalSessions).forEach(id => internalSessions[id].stopServer()));
const actionsDebug = debug("bdscore:global_platform");
export type actionCommandOption = {command: string, args?: string[], options?: fs.ObjectEncodingOptions & child_process.ExecFileOptions & {logPath?: {stdout: string, stderr?: string}}};
export type playerHistoric = {[player: string]: {action: "connect"|"disconnect"|"unknown"|"spawn"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"|"spawn"; date: Date}>}};
export type playerBase = {
playerName: string,
connectTime: Date,
xuid?: string,
action?: string,
lineString?: string
};
export type portListen = {port: number, host?: string, type: "TCP"|"UDP"|"TCP/UDP", protocol: "IPv4"|"IPv6"|"IPV4/IPv6"|"Unknown", proxyOrigin?: number, plugin?: string};
export type serverStarted = Date|{
onAvaible: Date,
timePassed: number
};
export declare interface serverActionsEvent {
on(act: "error", fn: (data: any) => void): this;
once(act: "error", fn: (data: any) => void): this;
emit(act: "error", data: any): boolean;
on(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", fn: (data: playerBase) => void): this;
once(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", fn: (data: playerBase) => void): this;
emit(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", data: playerBase): boolean;
on(act: "portListening", fn: (data: portListen) => void): this;
once(act: "portListening", fn: (data: portListen) => void): this;
emit(act: "portListening", data: portListen): boolean;
on(act: "serverStarted", fn: (data: serverStarted) => void): this;
once(act: "serverStarted", fn: (data: serverStarted) => void): this;
emit(act: "serverStarted", data: serverStarted): boolean;
on(act: "log_stderr", fn: (data: string) => void): this;
once(act: "log_stderr", fn: (data: string) => void): this;
emit(act: "log_stderr", data: string): boolean;
on(act: "log_stdout", fn: (data: string) => void): this;
once(act: "log_stdout", fn: (data: string) => void): this;
emit(act: "log_stdout", data: string): boolean;
on(act: "log", fn: (data: string) => void): this;
once(act: "log", fn: (data: string) => void): this;
emit(act: "log", data: string): boolean;
on(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
once(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
emit(act: "exit", data: {code: number, signal: NodeJS.Signals}): boolean;
}
export class serverActionsEvent extends EventEmitter {};
export type playerCallback = {
[T in playerHistoric[number]["action"]]: (player: playerBase) => void
};
export type actionsV2 = {
serverStarted?: (data: string, done: (startedDate: serverStarted) => void) => void,
portListening?: (data: string, done: (portInfo: portListen) => void) => void,
playerAction?: (data: string, Callbacks: playerCallback) => void,
stopServer?: (components: {child: child_process.ChildProcess, actions: serverActionV2}) => void|ReturnType<serverActionV2["stopServer"]>,
playerTp?: (actions: serverActionV2, playerName: string, x: number|string, y: number|string, z: number|string) => void,
};
export type serverActionV2 = {
version: 2,
id: string,
platform: bdsPlatform,
events: serverActionsEvent,
serverCommand?: actionCommandOption,
serverStarted?: serverStarted,
killProcess: (signal?: number|NodeJS.Signals) => boolean,
waitExit: () => Promise<number|NodeJS.Signals>
stopServer: () => ReturnType<serverActionV2["waitExit"]>,
runCommand?: (...command: Array<string|number|boolean>) => serverActionV2,
tp?: (playerName: string, x: number|string, y: number|string, z: number|string) => serverActionV2,
portListening: {[port: number]: portListen},
playerActions: playerHistoric,
};
/**
* A second version for actions, this version fixes several issues that occur with the old class-based version.
*/
export async function actionV2(options: {id: string, platform: bdsPlatform, processConfig: actionCommandOption, hooks: actionsV2}) {
if (internalSessions[options.id]) throw new Error("The platform with that id is already running!");
// Add exec options if not exists
const {processConfig} = options;
if (!processConfig.args) processConfig.args = [];
if (!processConfig.options) processConfig.options = {};
// Disable log buffer limit
processConfig.options.maxBuffer = Infinity;
processConfig.options.env = {...process.env, ...(processConfig.options.env||{})}
// Run commands
actionsDebug("Stating %s", options.id);
const childProcess = child_process.execFile(processConfig.command, processConfig.args, processConfig.options);
const serverObject: serverActionV2 = {
version: 2,
id: options.id,
platform: options.platform,
events: new serverActionsEvent({captureRejections: false}),
playerActions: {},
portListening: {},
serverCommand: processConfig,
runCommand(...command: Array<string|number|boolean>) {
const commandMaped = command.map(a => String(a)).join(" ")
actionsDebug("%s run '%s'", options.id, commandMaped);
childProcess.stdin.write(commandMaped+"\n");
return serverObject;
},
killProcess(signal?: number|NodeJS.Signals) {
actionsDebug("%s call kill with %s", options.id, signal);
if(childProcess?.killed) return childProcess?.killed;
return childProcess.kill(signal);
},
stopServer() {
actionsDebug("%s call stop server", options.id);
if (options.hooks.stopServer === undefined) childProcess.kill("SIGKILL");
const data = options.hooks.stopServer({child: childProcess, actions: serverObject});
if (!data) return serverObject.waitExit();
return data;
},
async waitExit(): Promise<number|NodeJS.Signals> {
if (childProcess.exitCode||childProcess.signalCode) return childProcess.exitCode||childProcess.signalCode;
return new Promise<number>((done, reject) => {
childProcess.once("error", err => reject(err));
childProcess.once("close", code => done(code));
});
},
tp(playerName: string, x: number|string = 0, y: number|string = 0, z: number|string = 0) {
const tpfunction = options.hooks.playerTp;
if (tpfunction === undefined) throw new Error("tp is disabled, tpfunction not defined to platform!");
if (!(playerName?.startsWith("@")||!!this.playerActions[playerName])) throw new Error("Player or target not exist");
tpfunction(serverObject, playerName, x, y, z);
return serverObject;
}
};
// Add to internal sessions
internalSessions[options.id] = serverObject;
serverObject.events.on("exit", () => delete internalSessions[options.id]);
// Break lines with readline
const readlineStdout = readline.createInterface(childProcess.stdout);
readlineStdout.on("line", data => {
serverObject.events.emit("log", data);
serverObject.events.emit("log_stdout", data)
});
const readlineStderr = readline.createInterface(childProcess.stderr);
readlineStderr.on("line", data => {
serverObject.events.emit("log", data);
serverObject.events.emit("log_stderr", data);
});
// Register hooks
// Server avaible to player
if (options.hooks.serverStarted) {
actionsDebug("Register server started function to %s", options.id);
function started(data: string) {
return options.hooks.serverStarted(data, onAvaible => {
actionsDebug("Call server started function to %s", options.id);
if (serverObject.serverStarted) return;
serverObject.serverStarted = onAvaible;
serverObject.events.emit("serverStarted", onAvaible);
});
}
readlineStderr.on("line", started);
readlineStdout.on("line", started);
}
// Server Player actions
if (options.hooks.playerAction) {
function updateHistoric(action: playerHistoric[0]["action"], data: playerBase) {
if (!serverObject.playerActions[data.playerName]) {
serverObject.playerActions[data.playerName] = {
action,
date: data.connectTime,
history: [
{
action,
date: data.connectTime
}
]
};
return;
}
serverObject.playerActions[data.playerName].action = action;
serverObject.playerActions[data.playerName].date = data.connectTime;
serverObject.playerActions[data.playerName].history.push({
action,
date: data.connectTime
});
}
actionsDebug("Register player actions to %s", options.id);
const playerConnect = (data: playerBase) => {
actionsDebug("Player actions to %s, call connect", options.id);
serverObject.events.emit("playerConnect", data);
updateHistoric("connect", data);
}
const playerSpawn = (data: playerBase) => {
actionsDebug("Player actions to %s, call spawn", options.id);
serverObject.events.emit("playerSpawn", data);
updateHistoric("spawn", data);
}
const playerDisonnect = (data: playerBase) => {
actionsDebug("Player actions to %s, call disconnect", options.id);
serverObject.events.emit("playerDisconnect", data);
updateHistoric("disconnect", data);
}
const playerUnknown = (data: playerBase) => {
actionsDebug("Player actions to %s, call unknown", options.id);
serverObject.events.emit("playerUnknown", data);
updateHistoric("unknown", data);
}
const mainFunc = (data: string) => options.hooks.playerAction(data, {
connect: playerConnect,
disconnect: playerDisonnect,
unknown: playerUnknown,
spawn: playerSpawn
});
readlineStdout.on("line", mainFunc);
readlineStderr.on("line", mainFunc);
}
// Port listen
if (options.hooks.portListening) {
actionsDebug("Register port listening to %s", options.id);
const mainFunc = (data: string) => options.hooks.portListening(data, (portData) => {
actionsDebug("Port listen to %s", options.id);
serverObject.events.emit("portListening", portData);
serverObject.portListening[portData.port] = portData;
})
readlineStdout.on("line", mainFunc);
readlineStderr.on("line", mainFunc);
}
// Pipe logs
if (processConfig.options.logPath) {
actionsDebug("Pipe log to file in %s", options.id);
childProcess.stdout.pipe(fs.createWriteStream(processConfig.options.logPath.stdout));
if (processConfig.options.logPath.stderr) childProcess.stderr.pipe(fs.createWriteStream(processConfig.options.logPath.stderr));
}
// Add listeners to actions events
childProcess.on("error", data => serverObject.events.emit("error", data));
childProcess.on("exit", (code, signal) => serverObject.events.emit("exit", {code, signal}));
childProcess.on("exit", () => serverObject.events.removeAllListeners());
// Return session maneger
return serverObject;
}

View File

@@ -1,33 +0,0 @@
// Utils
import * as platformPathManeger from "./platformPathManeger.js"
import * as globalPlatfroms from "./globalPlatfroms.js";
import * as pluginManeger from "./plugin/plugin.js";
import * as export_import from "./export_import.js";
import * as pluginHooks from "./plugin/hook.js";
import * as proxy from "./lib/proxy.js";
// Platforms
import * as Bedrock from "./Platforms/bedrock.js";
import * as Java from "./Platforms/java.js";
import * as PocketmineMP from "./Platforms/pocketmine.js";
import * as Spigot from "./Platforms/spigot.js";
import * as Powernukkit from "./Platforms/pwnuukit.js";
import * as PaperMC from "./Platforms/paper.js";
export {platformPathManeger, globalPlatfroms, pluginManeger, export_import, PocketmineMP, pluginHooks, Powernukkit, PaperMC, Bedrock, Spigot, proxy, Java};
export default {
Bedrock,
Java,
PocketmineMP,
Powernukkit,
PaperMC,
Spigot,
utils: {
platformPathManeger,
globalPlatfroms,
pluginManeger,
pluginHooks,
export_import,
proxy
}
};

13
src/platform/Bedrock.ts Normal file
View File

@@ -0,0 +1,13 @@
import { serverManeger, pathOptions } from "../serverManeger.js";
export default async function Bedrock(options?: pathOptions & {variant?: string}) {
options = {...options};
return serverManeger({
exec: {
exec: "java",
args: []
},
actions: {}
})
}

View File

@@ -1,142 +0,0 @@
import { extendFs } from "@sirherobrine23/coreutils";
import crypto from "node:crypto";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
export let bdsRoot = process.env.BDS_HOME?(process.env.BDS_HOME.startsWith("~")?process.env.BDS_HOME.replace("~", os.homedir()):process.env.BDS_HOME):path.join(os.homedir(), ".bdsManeger");
export type bdsPlatform = "bedrock"|"java"|"pocketmine"|"spigot"|"powernukkit"|"paper";
export type bdsPlatformOptions = {
newId?: boolean,
id?: "default"|string,
withBuildFolder?: boolean
};
// This array to valida platform imput!
const platformArray: bdsPlatform[] = [
"bedrock",
"java",
"pocketmine",
"spigot",
"powernukkit",
"paper"
];
// Test ID
const idRegex = /^[A-Za-z0-9]*$/;
/**
* Register or get folder to Servers, this is to create and maneger folders
* @param platform - Select platform to maneger folders
* @param options - ?
*/
export async function pathControl(platform: bdsPlatform, options?: bdsPlatformOptions) {
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
const platformRoot = path.join(bdsRoot, platform);
if (!await extendFs.exists(platformRoot)) await fs.mkdir(platformRoot, {recursive: true});
if (!options) options = {};
// Create if not exists
const foldersAndLink = await fs.readdir(platformRoot);
if (foldersAndLink.length === 0) options.newId = true;
if (options.newId) {
options.id = crypto.randomBytes(16).toString("hex");
fs.mkdir(path.join(platformRoot, options.id), {recursive: true});
if (await extendFs.exists(path.join(platformRoot, "default"))) await fs.unlink(path.join(platformRoot, "default"));
await fs.symlink(path.join(platformRoot, options.id), path.join(platformRoot, "default"));
} else if (!await extendFs.exists(path.join(platformRoot, options.id))) throw new Error("Folder ID not created!");
// Get real id
if (!idRegex.test(options.id)) throw new Error("Invalid Platform ID");
if (options?.id === "default") options.id = path.basename(await fs.realpath(path.join(platformRoot, options.id)).catch(async () => (await fs.readdir(platformRoot)).sort().at(0)));
// Mount Paths
const serverRoot = path.join(platformRoot, options.id);
const serverPath = path.join(serverRoot, "server");
const hooksPath = path.join(serverRoot, "hooks");
const backupPath = path.join(serverRoot, "backup");
const logsPath = path.join(serverRoot, "logs");
let buildFolder: string;
if (options?.withBuildFolder) buildFolder = path.join(serverRoot, "build");
// Create folder if not exists
if (!(await extendFs.exists(serverRoot))) await fs.mkdir(serverRoot, {recursive: true});
if (!(await extendFs.exists(serverPath))) await fs.mkdir(serverPath, {recursive: true});
if (!(await extendFs.exists(hooksPath))) await fs.mkdir(hooksPath, {recursive: true});
if (!(await extendFs.exists(backupPath))) await fs.mkdir(backupPath, {recursive: true});
if (!(await extendFs.exists(logsPath))) await fs.mkdir(logsPath, {recursive: true});
if (buildFolder && !(await extendFs.exists(buildFolder))) await fs.mkdir(buildFolder, {recursive: true});
return {
id: options?.id,
serverRoot,
serverPath,
hooksPath,
backupPath,
logsPath,
buildFolder,
platformIDs: foldersAndLink
};
}
/**
* Change default folder to Platform
* @param platform
* @param id
* @returns
*/
export async function changeDefault(platform: bdsPlatform, id: bdsPlatformOptions["id"]) {
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
const serverPlatform = path.join(bdsRoot, platform);
if (!await extendFs.exists(serverPlatform)) throw new Error("Install server fist!");
const ids = (await fs.readdir(serverPlatform)).filter(folder => folder.toLowerCase() !== "default");
if (!ids.includes(id)) throw new Error("Id not exists to Platform");
const oldPath = fs.realpath(path.join(bdsRoot, platform, "default"));
if (await extendFs.exists(path.join(bdsRoot, platform, "default"))) await fs.unlink(path.join(bdsRoot, platform, "default"));
await fs.symlink(path.join(bdsRoot, platform, id), path.join(bdsRoot, platform, "default"));
return {
oldPath,
newPath: path.join(bdsRoot, platform, id)
};
}
export type platformIds = {
[platform in bdsPlatform]?: {
realID?: string,
id: string,
}[]
};
/**
* Get all ids to all platforms installed
* @returns
*/
export async function getIds(): Promise<platformIds>;
/**
* Get all ids to platform
* @param platform - Set platform to get ids
* @returns
*/
export async function getIds(platform: bdsPlatform): Promise<string[]>;
export async function getIds(platform?: bdsPlatform): Promise<platformIds|string[]> {
if (!platform) {
const platformIds: platformIds = {};
if (!await extendFs.exists(bdsRoot)) return platformIds;
const Platforms = (await fs.readdir(bdsRoot)).filter(folder => platformArray.includes(folder as bdsPlatform));
await Promise.all(Platforms.map(async Platform => {
for (const id of await fs.readdir(path.join(bdsRoot, Platform))) {
if (!platformIds[Platform]) platformIds[Platform] = []
const idPlatform = path.join(bdsRoot, Platform, id);
const realPath = await fs.realpath(idPlatform);
if (idPlatform !== realPath) platformIds[Platform].push({id, realID: path.basename(realPath)});
else platformIds[Platform].push({id});
}
}));
return platformIds;
}
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
const serverPlatform = path.join(bdsRoot, platform);
if (!await extendFs.exists(serverPlatform)) throw new Error("Install server fist!");
return (await fs.readdir(serverPlatform)).filter(folder => folder.toLowerCase() !== "default");
}

View File

@@ -1,82 +0,0 @@
import { httpRequestLarge, childPromisses } from "@sirherobrine23/coreutils";
import { actionsV2 } from "../globalPlatfroms.js";
import path from "node:path";
import fs from "node:fs/promises";
export type hooksPlatform = "bedrock"|"java"|"pocketmine"|"spigot"|"powernukkit"|"paper";
export type hooksPlatformGeneric = hooksPlatform|"generic";
export type hooksRegister = {
scriptName: string,
platforms: hooksPlatformGeneric[],
register: (actions: actionsV2) => void,
};
async function exists(path: string) {
return fs.access(path).then(() => true).catch(() => false);
}
const gitUrlDetect = /(^http:\/\/.*\.git$|^http:\/\/.*.git$|^git:\/\/)/;
export class script_hook {
#serverActions: actionsV2;
#currentPlatform: hooksPlatformGeneric;
#localFolder: string;
async #registerScript(filePath: string) {
const lo_script = (await(eval(`import("${filePath}")`))) as hooksRegister|{default: hooksRegister};
let script: hooksRegister;
if (lo_script["default"]) script = lo_script["default"];
else script = lo_script as hooksRegister;
console.log(script);
if (!script.scriptName) throw new Error("Scriptname in null");
if (!script.register) throw new Error("Register not is function");
if (!(script.platforms.includes("generic")||script.platforms.includes(this.#currentPlatform))) throw new Error(`Cannot get valid platform (${this.#currentPlatform}) (${script.platforms?.join(", ")})`);
const { scriptName, register } = script;
console.log("Loading hook %s (External)", scriptName);
return (Promise.resolve().then(() => register(this.#serverActions))).then(() => console.log("hook %s loaded (External)", scriptName));
};
async #loadLocalScript() {
if (!this.#serverActions) throw new Error("Server actions (globalPlatform) is undefined");
if (!await exists(this.#localFolder)) await fs.mkdir(this.#localFolder, {recursive: true});
const localFiles = (await fs.readdir(this.#localFolder)).map(file => path.join(this.#localFolder, file));
for (const localFile of localFiles) {
await fs.lstat(localFile).then(async stat => {
if (stat.isFile()) return this.#registerScript(localFile);
if (await exists(path.join(localFile, "package.json"))) {
const externalHookPackage = JSON.parse(await fs.readFile(path.join(localFile, "package.json"), "utf8"));
let indexFile: string;
if (externalHookPackage.main) indexFile = path.join(localFile, externalHookPackage.main);
else {
const files = (await fs.readdir(localFile)).filter(file => /index\.(js|cjs|mjs)$/.test(file));
if (files.length === 0) throw new Error("no index files");
indexFile = path.join(localFile, files[0]);
}
if (!indexFile) throw new Error("No file to init");
return this.#registerScript(indexFile);
}
throw new Error("Know know, boom!");
}).catch(err => console.error(String(err)));
}
}
public async installHook(urlHost: string, fileName?: string) {
if (!await exists(this.#localFolder)) await fs.mkdir(this.#localFolder, {recursive: true});
if (!fileName) fileName = path.basename(urlHost);
const onSave = path.join(this.#localFolder, fileName);
// Git
if (gitUrlDetect.test(urlHost)) {
await childPromisses.execFile("git", ["clone", urlHost, "--depth", "1", onSave], {cwd: this.#localFolder});
if (await exists(path.join(onSave, "package.json"))) await childPromisses.execFile("npm", ["install", "--no-save"], {cwd: onSave});
} else await httpRequestLarge.saveFile({url: urlHost, filePath: onSave});
if (!!this.#serverActions) await this.#registerScript(onSave);
return;
}
constructor(hookFolder: string, targetPlatform: hooksPlatform, platformActions?: actionsV2) {
this.#serverActions = platformActions;
this.#currentPlatform = targetPlatform;
this.#localFolder = hookFolder;
if (platformActions) this.#loadLocalScript();
}
}

View File

@@ -1,61 +0,0 @@
import path from "node:path";
import type { bdsPlatform } from "../platformPathManeger.js";
import { httpRequest, httpRequestLarge } from "@sirherobrine23/coreutils";
export type pluginConfig = {
version?: 1,
name: string,
url: string,
fileName?: string,
platforms: bdsPlatform[],
dependecies?: string[],
/** @deprecated */
scripts?: string[],
};
export type pluginV2 = {
version: 2,
name: string,
pluginVersion?: string,
platform: {
[platform: string]: {
url: string,
fileName?: string,
dependencies?: string[]
}
}
};
export class pluginManeger {
#pluginFolder: string;
#platform: bdsPlatform;
constructor(pluginFolder: string, options?: {
bdsPlatfrom: bdsPlatform
}) {
this.#pluginFolder = pluginFolder;
this.#platform = options?.bdsPlatfrom;
}
async installPlugin(plugin: string) {
const urlandbds = /http[s]:\/\/|bdsplugin:\/\//;
if (/bdsplugin:\/\//.test(plugin)) plugin = `https://raw.githubusercontent.com/The-Bds-Maneger/plugin_list/main/plugins/${plugin.replace(urlandbds, "").replace(".json", "")}.json`;
else if (!/http[s]:\/\/\//.test(plugin)) plugin = "bdsplugin://"+plugin;
const info = await httpRequest.getJSON<pluginConfig|pluginV2>(plugin);
if (info.version === 2) {
const platformData = info.platform[this.#platform];
if (!platformData) throw new Error("Platform not supported to Plugin!");
await httpRequestLarge.saveFile({
url: platformData.url,
filePath: path.join(this.#pluginFolder, platformData.fileName||path.basename(platformData.url))
});
if (platformData.dependencies) await Promise.all(platformData.dependencies.map(dep => this.installPlugin(dep)));
return;
}
if (!info.platforms.includes(this.#platform)) throw new Error("Platform not supported to Plugin!");
await httpRequestLarge.saveFile({
url: info.url,
filePath: path.join(this.#pluginFolder, info.fileName||path.basename(info.url))
});
if (info.dependecies) await Promise.all(info.dependecies.map(dep => this.installPlugin(dep)));
}
};

98
src/serverManeger.ts Normal file
View File

@@ -0,0 +1,98 @@
import { createInterface as readline } from "node:readline";
import { promises as fs } from "node:fs";
import child_process, { ChildProcess } from "node:child_process";
import { extendFs } from "@sirherobrine23/coreutils";
import crypto from "node:crypto";
import path from "node:path";
import os from "node:os";
export type pathOptions = {
id?: "default"|string,
newId?: boolean,
withBuildFolder?: boolean,
};
export let bdsRoot = process.env.BDS_HOME?(process.env.BDS_HOME.startsWith("~")?process.env.BDS_HOME.replace("~", os.homedir()):process.env.BDS_HOME):path.join(os.homedir(), ".bdsManeger");
export async function platformPathID(platform: "bedrock"|"java", options?: pathOptions) {
if (!(["bedrock", "java"].includes(platform))) throw new Error("Invalid platform target");
options = {id: "default", ...options};
const platformRoot = path.join(bdsRoot, platform);
if (!await extendFs.exists(platformRoot)) await fs.mkdir(platformRoot, {recursive: true});
if (!options) options = {};
// Create if not exists
const foldersAndLink = await fs.readdir(platformRoot);
if (foldersAndLink.length === 0) options.newId = true;
if (options.newId) {
options.id = crypto.randomBytes(16).toString("hex");
fs.mkdir(path.join(platformRoot, options.id), {recursive: true});
if (await extendFs.exists(path.join(platformRoot, "default"))) await fs.unlink(path.join(platformRoot, "default"));
await fs.symlink(path.join(platformRoot, options.id), path.join(platformRoot, "default"));
} else if (!await extendFs.exists(path.join(platformRoot, options.id))) throw new Error("Folder ID not created!");
// Get real id
if (!(/^[A-Za-z0-9]*$/).test(options.id)) throw new Error("Invalid Platform ID");
if (options?.id === "default") options.id = path.basename(await fs.realpath(path.join(platformRoot, options.id)).catch(async () => (await fs.readdir(platformRoot)).sort().at(0)));
// Mount Paths
const serverRoot = path.join(platformRoot, options.id);
const serverPath = path.join(serverRoot, "server");
const hooksPath = path.join(serverRoot, "hooks");
const backupPath = path.join(serverRoot, "backup");
const logsPath = path.join(serverRoot, "logs");
let buildFolder: string;
if (options?.withBuildFolder) buildFolder = path.join(serverRoot, "build");
// Create folder if not exists
if (!(await extendFs.exists(serverRoot))) await fs.mkdir(serverRoot, {recursive: true});
if (!(await extendFs.exists(serverPath))) await fs.mkdir(serverPath, {recursive: true});
if (!(await extendFs.exists(hooksPath))) await fs.mkdir(hooksPath, {recursive: true});
if (!(await extendFs.exists(backupPath))) await fs.mkdir(backupPath, {recursive: true});
if (!(await extendFs.exists(logsPath))) await fs.mkdir(logsPath, {recursive: true});
if (buildFolder && !(await extendFs.exists(buildFolder))) await fs.mkdir(buildFolder, {recursive: true});
return {
id: options?.id,
serverRoot,
serverPath,
hooksPath,
backupPath,
logsPath,
buildFolder,
platformIDs: foldersAndLink
};
}
export type serverConfig = {
exec: {
exec: string,
args?: string[],
cwd?: string,
env?: NodeJS.ProcessEnv & {[key: string]: string},
},
actions: {
stopServer?: (child_process: ChildProcess) => void,
onStart?: (lineData: string, fnRegister: (data: {serverAvaible: Date, bootUp?: number}) => void) => void,
playerActions?: (lineData: string, fnRegister: (data: {player: string, action: "join" | "leave"}) => void) => void,
}
};
export async function serverManeger(serverOptions: serverConfig) {
const serverExec = child_process.execFile(serverOptions.exec.exec, serverOptions.exec.args ?? [], {
cwd: serverOptions.exec.cwd,
env: {
...process.env,
...serverOptions.exec.env
},
windowsHide: true,
maxBuffer: Infinity
});
const stdoutReadline = readline({input: serverExec.stdout});
const stderrReadline = readline({input: serverExec.stderr});
return {
serverExec,
stdoutReadline,
stderrReadline
};
}