diff --git a/src/bedrock.ts b/src/bedrock.ts index e3c3a64f..8a3dbe76 100644 --- a/src/bedrock.ts +++ b/src/bedrock.ts @@ -1,39 +1,47 @@ import path from "node:path"; import fsOld from "node:fs"; import fs from "node:fs/promises"; -import * as Proprieties from "./lib/Proprieties"; import * as globalPlatfroms from "./globalPlatfroms"; import { platformManeger } from "@the-bds-maneger/server_versions"; import { pathControl, bdsPlatformOptions } from "./platformPathManeger"; import { commendExists } from "./lib/childPromisses"; import * as httpRequest from "./lib/httpRequest"; -import { exists, readdirrecursive } from "./lib/extendsFs"; +import extendsFs, { exists, readdirrecursive } from "./lib/extendsFs"; import { randomPort } from "./lib/randomPort"; +import { manegerConfigProprieties } from "./configManipulate"; + +// RegExp +export const saveFileFolder = /^(worlds|server\.properties|config|((permissions|allowlist|valid_known_packs)\.json)|(development_.*_packs))$/; +export const portListen = /\[.*\]\s+(IPv[46])\s+supported,\s+port:\s+([0-9]+)/; +export const started = /\[.*\]\s+Server\s+started\./; +export const player = /\[.*\]\s+Player\s+((dis|)connected):\s+(.*),\s+xuid:\s+([0-9]+)/; export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) { const { serverPath, serverRoot, platformIDs, id } = await pathControl("bedrock", platformOptions); const bedrockData = await platformManeger.bedrock.find(version); - const url = bedrockData?.url[process.platform]; + let platform = process.platform; + if (platform === "android") platform = "linux"; + const url = bedrockData?.url[platform]; + if (!url) throw new Error("No url to current os platform"); // Remover files - await fs.readdir(serverPath).then(files => files.filter(file => !saveFileFolder.test(file))).then(files => Promise.all(files.map(file => fs.rm(path.join(serverPath, file), {recursive: true, force: true})))); + await fs.readdir(serverPath).then(files => Promise.all(files.filter(file => !saveFileFolder.test(file)).map(file => fs.rm(path.join(serverPath, file), {recursive: true, force: true})))); - const serverConfig = (await fs.readFile(path.join(serverPath, "server.properties"), "utf8").catch(() => "")).trim(); + const serverConfigProperties = (await fs.readFile(path.join(serverPath, "server.properties"), "utf8").catch(() => "")).trim(); await httpRequest.extractZip({url, folderTarget: serverPath}); - if (serverConfig) await fs.writeFile(path.join(serverPath, "server.properties"), serverConfig); + if (serverConfigProperties) await fs.writeFile(path.join(serverPath, "server.properties"), serverConfigProperties); await fs.writeFile(path.join(serverRoot, "version_installed.json"), JSON.stringify({version: bedrockData.version, date: bedrockData.date, installDate: new Date()})); if (platformIDs.length > 1) { let v4: number, v6: number; - const platformPorts = (await Promise.all(platformIDs.map(id => getConfig({id})))).map(config => ({v4: config.serverPort, v6: config.serverPortv6})); + const platformPorts = (await Promise.all(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 updateConfig("serverPort", v4, {id}); - await updateConfig("serverPortv6", v6, {id}); + await (await serverConfig({id})).editConfig({name: "serverPort", data: v4}).editConfig({name: "serverPortv6", data: v6}).save() } return { id, url, @@ -42,12 +50,6 @@ export async function installServer(version: string|boolean, platformOptions: bd }; } -// RegExp -export const saveFileFolder = /^(worlds|server\.properties|config|((permissions|allowlist|valid_known_packs)\.json)|(development_.*_packs))$/; -export const portListen = /\[.*\]\s+(IPv[46])\s+supported,\s+port:\s+([0-9]+)/; -export const started = /\[.*\]\s+Server\s+started\./; -export const player = /\[.*\]\s+Player\s+((dis|)connected):\s+(.*),\s+xuid:\s+([0-9]+)/; - 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"); @@ -101,94 +103,33 @@ export async function startServer(platformOptions: bdsPlatformOptions = {id: "de }); } -// Update file config -export type keyConfig = "serverName"|"gamemode"|"forceGamemode"|"difficulty"|"allowCheats"|"maxPlayers"|"onlineMode"|"allowList"|"serverPort"|"serverPortv6"|"viewDistance"|"tickDistance"|"playerIdleTimeout"|"maxThreads"|"levelName"|"levelSeed"|"defaultPlayerPermissionLevel"|"texturepackRequired"|"chatRestriction"|"mojangTelemetry"; -export async function updateConfig(key: "serverName", value: string, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "gamemode", value: "survival"|"creative"|"adventure", platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "forceGamemode", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "difficulty", value: "peaceful"|"easy"|"normal"|"hard", platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "allowCheats", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "maxPlayers", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "onlineMode", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "allowList", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "serverPort", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "serverPortv6", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "viewDistance", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "tickDistance", value: "4"|"6"|"8"|"10"|"12", platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "playerIdleTimeout", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "maxThreads", value: number, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "levelName", value: string, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "levelSeed", value?: string, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "defaultPlayerPermissionLevel", value: "visitor"|"member"|"operator", platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "texturepackRequired", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "chatRestriction", value: "None"|"Dropped"|"Disabled", platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: "mojangTelemetry", value: boolean, platformOptions?: bdsPlatformOptions): Promise; -export async function updateConfig(key: keyConfig, value: string|number|boolean, platformOptions: bdsPlatformOptions = {id: "default"}): Promise { - const { serverPath } = await pathControl("bedrock", platformOptions); - const fileProperties = path.join(serverPath, "server.properties"); - if (!fsOld.existsSync(fileProperties)) throw new Error("Install server fist!"); - let fileConfig = await fs.readFile(fileProperties, "utf8"); - if (key === "serverName") fileConfig = fileConfig.replace(/server-name=.*/, `server-name=${value}`); - else if (key === "gamemode") fileConfig = fileConfig.replace(/gamemode=(survival|creative|adventure)/, `gamemode=${value}`); - else if (key === "forceGamemode") fileConfig = fileConfig.replace(/force-gamemode=(true|false)/, `force-gamemode=${value}`); - else if (key === "difficulty") fileConfig = fileConfig.replace(/difficulty=(peaceful|easy|normal|hard)/, `difficulty=${value}`); - else if (key === "allowCheats") fileConfig = fileConfig.replace(/allow-cheats=(false|true)/, `allow-cheats=${value}`); - else if (key === "maxPlayers") fileConfig = fileConfig.replace(/max-players=[0-9]+/, `max-players=${value}`); - else if (key === "onlineMode") fileConfig = fileConfig.replace(/online-mode=(true|false)/, `online-mode=${value}`); - else if (key === "allowList") fileConfig = fileConfig.replace(/allow-list=(false|true)/, `allow-list=${value}`); - else if (key === "serverPort"||key === "serverPortv6") { - if (key === "serverPort") fileConfig = fileConfig.replace(/server-port=[0-9]+/, `server-port=${value}`); - else fileConfig = fileConfig.replace(/server-portv6=[0-9]+/, `server-portv6=${value}`); - } - else if (key === "viewDistance") { - if (value > 4) fileConfig = fileConfig.replace(/view-distance=[0-9]+/, `view-distance=${value}`); - else throw new Error("integer equal to 5 or greater"); - } else if (key === "tickDistance") fileConfig = fileConfig.replace(/tick-distance=(4|6|8|10|12)/, `tick-distance=${value}`); - else if (key === "playerIdleTimeout") fileConfig = fileConfig.replace(/player-idle-timeout=[0-9]+/, `player-idle-timeout=${value}`); - else if (key === "maxThreads") fileConfig = fileConfig.replace(/max-threads=[0-9]+/, `max-threads=${value}`); - else if (key === "levelName") fileConfig = fileConfig.replace(/level-name=.*/, `level-name=${value}`); - else if (key === "levelSeed") fileConfig = fileConfig.replace(/level-seed=.*/, `level-seed=${!value?"":value}`); - else if (key === "defaultPlayerPermissionLevel") fileConfig = fileConfig.replace(/default-player-permission-level=(visitor|member|operator)/, `default-player-permission-level=${value}`); - else if (key === "texturepackRequired") fileConfig = fileConfig.replace(/texturepack-required=(false|true)/, `texturepack-required=${value}`); - else if (key === "chatRestriction") fileConfig = fileConfig.replace(/chat-restriction=(None|Dropped|Disabled)/, `chat-restriction=${value}`); - else if (key === "mojangTelemetry") { - if (!fileConfig.includes("emit-server-telemetry")) fileConfig = fileConfig.trim()+`\nemit-server-telemetry=false\n`; - fileConfig = fileConfig.replace(/chat-restriction=(true|false)/, `nemit-server-telemetry=${value}`); - } - else throw new Error("Invalid key"); - - await fs.writeFile(fileProperties, fileConfig); - return fileConfig; -} +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: "defaultPlayerPermissionLevel", data: "visitor"|"member"|"operator"}| +{name: "texturepackRequired", data: boolean}| +{name: "chatRestriction", data: "None"|"Dropped"|"Disabled"}| +{name: "mojangTelemetry", data: boolean}; export type bedrockConfig = { - "serverName"?: string, - "gamemode"?: "survival"|"creative"|"adventure", - "forceGamemode"?: boolean, - "difficulty"?: "peaceful"|"easy"|"normal"|"hard", - "allowCheats"?: boolean, - "maxPlayers"?: number, - "onlineMode"?: boolean, - "allowList"?: boolean, - "serverPort"?: number, - "serverPortv6"?: number, - "viewDistance"?: number, - "tickDistance"?: "4"|"6"|"8"|"10"|"12", - "playerIdleTimeout"?: number, - "maxThreads"?: number, - "levelName"?: string, - "levelSeed"?: string, - "defaultPlayerPermissionLevel"?: "visitor"|"member"|"operator", - "texturepackRequired"?: boolean, - "chatRestriction"?: "None"|"Dropped"|"Disabled", - "mojangTelemetry"?: boolean -}; - -type rawConfig = { "server-name": string, - gamemode: string, + "gamemode": "survival"|"creative"|"adventure", "force-gamemode": boolean, - difficulty: string, + "difficulty": "peaceful"|"easy"|"normal"|"hard", "allow-cheats": boolean, "max-players": number, "online-mode": true, @@ -196,12 +137,12 @@ type rawConfig = { "server-port": number, "server-portv6": number, "view-distance": number, - "tick-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": string, + "default-player-permission-level": "visitor"|"member"|"operator", "texturepack-required": boolean, "content-log-file-enabled": boolean, "compression-threshold": number, @@ -212,28 +153,103 @@ type rawConfig = { "player-movement-duration-threshold-in-ms": number, "correct-player-movement": boolean, "server-authoritative-block-breaking": boolean, - "chat-restriction": string, + "chat-restriction": "None"|"Dropped"|"Disabled", "disable-player-interaction": boolean, "emit-server-telemetry"?: boolean } -export async function getConfig(platformOptions: bdsPlatformOptions = {id: "default"}): Promise { +export async function serverConfig(platformOptions: bdsPlatformOptions = {id: "default"}) { const { serverPath } = await pathControl("bedrock", platformOptions); const fileProperties = path.join(serverPath, "server.properties"); - if (!fsOld.existsSync(fileProperties)) throw new Error("Install server fist"); - const config = Proprieties.parse(await fs.readFile(fileProperties, "utf8")); - const configBase: bedrockConfig = {}; - const ignore = [ - "content-log-file-enabled", "compression-threshold", "server-authoritative-movement", "player-movement-score-threshold", "player-movement-action-direction-threshold", - "player-movement-distance-threshold", "player-movement-duration-threshold-in-ms", "correct-player-movement", "server-authoritative-block-breaking", "disable-player-interaction" - ] - for (const configKey of Object.keys(config)) { - if (ignore.includes(configKey)) continue; - const key = configKey.replace(/-(.)/g, (_, _1) => _1.toUpperCase()); - if (key === "levelSeed" && config[configKey] === null) configBase[key] = ""; - else configBase[key] = config[configKey]; - } - return configBase; + if (!await extendsFs.exists(fileProperties)) await fs.cp(path.join(__dirname, "../configs/java/server.properties"), fileProperties); + return manegerConfigProprieties({ + 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" + }, + 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 = { @@ -260,14 +276,14 @@ export type resourceManifest = { export async function addResourcePacksToWorld(resourceId: string, platformOptions: bdsPlatformOptions = {id: "default"}) { const { serverPath } = await pathControl("bedrock", platformOptions); - const serverConfig = await getConfig(platformOptions); - if (!await exists(path.join(serverPath, "worlds", serverConfig.levelName, "world_resource_packs.json"))) await fs.writeFile(path.join(serverPath, "worlds", serverConfig.levelName, "world_resource_packs.json"), "[]"); - const resourcesData: resourcePacks[] = JSON.parse(await fs.readFile(path.join(serverPath, "worlds", serverConfig.levelName, "world_resource_packs.json"), "utf8")); - const manifests: resourceManifest[] = await Promise.all((await readdirrecursive([path.join(serverPath, "resource_packs"), path.join(serverPath, "worlds", serverConfig.levelName, "resource_packs")])).filter((file: string) => file.endsWith("manifest.json")).map(async (file: string) => JSON.parse(await fs.readFile(file, "utf8")))); + const serverConfigObject = (await serverConfig(platformOptions)).getConfig(); + if (!await 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 readdirrecursive([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", serverConfig.levelName, "world_resource_packs.json"), JSON.stringify(resourcesData, null, 2)); + await fs.writeFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), JSON.stringify(resourcesData, null, 2)); return resourcesData; } \ No newline at end of file diff --git a/src/configManipulate.ts b/src/configManipulate.ts index c9636185..fb9cd4ee 100644 --- a/src/configManipulate.ts +++ b/src/configManipulate.ts @@ -2,21 +2,9 @@ import fs from "node:fs/promises"; import utils from "node:util"; import Proprieties, { properitiesBase } from "./lib/Proprieties"; -export type Manipulate = ((config: string, value: any) => string)|{ - validate?: (value: any) => boolean, - regexReplace: RegExp, - valueFormat: string, -}; -export type configOptions = { - configPath: string, - configManipulate: { - [keyName: string]: Manipulate[]|Manipulate - } -}; - -export type configEdit = {name: string, data: any}; -export async function manegerConfigProprieties(config: configOptions) { - let configFile: string = await fs.readFile(config.configPath, "utf8"); +export type configEdit = {name: string, data?: string|number|boolean}; +export async function manegerConfigProprieties(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(configFile)}; async function save() { await fs.writeFile(config.configPath, configFile); @@ -25,12 +13,15 @@ export async function manegerConfigProprieties