Core apis #528
@ -1,14 +0,0 @@
|
||||
# npm
|
||||
/*.tgz
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Typescript
|
||||
**/*.js
|
||||
**/*.d.ts
|
||||
**/tsconfig.tsbuildinfo
|
||||
|
||||
# PHP and Spigot Pre builds
|
||||
phpOutput/
|
||||
*.jar
|
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
# Build Core
|
||||
- name: Core Build
|
||||
if: matrix.package != '@the-bds-maneger/core'
|
||||
run: npm run -w "@the-bds-maneger/core" build
|
||||
run: npm run -w "@the-bds-maneger/core" prepack
|
||||
|
||||
- name: Build "${{ matrix.package }}"
|
||||
run: npm run --if-present -w "${{ matrix.package }}" prepack
|
@ -5,91 +5,10 @@ import yargs from "yargs";
|
||||
|
||||
// Init yargs
|
||||
yargs(process.argv.slice(2)).version(false).help(true).strictCommands().demandCommand().alias("h", "help")
|
||||
.command("install", "Install server", async yargs => {
|
||||
const options = yargs.option("platform", {
|
||||
type: "string",
|
||||
string: true,
|
||||
alias: "p",
|
||||
choices: ["bedrock", "java"],
|
||||
default: "bedrock",
|
||||
description: "Platform to install"
|
||||
})
|
||||
.option("altserver", {
|
||||
type: "string",
|
||||
string: true,
|
||||
alias: "a",
|
||||
choices: ["spigot", "paper", "purpur", "pocketmine", "powernukkit", "nukkit", "cloudbust"],
|
||||
})
|
||||
.option("id", {
|
||||
alias: "i",
|
||||
type: "string",
|
||||
describe: "ID to update server"
|
||||
})
|
||||
.option("version", {
|
||||
type: "string",
|
||||
alias: "V",
|
||||
describe: "Server version",
|
||||
default: "latest"
|
||||
})
|
||||
.option("beta", {
|
||||
type: "boolean",
|
||||
boolean: true,
|
||||
default: false,
|
||||
describe: "allow install beta or snapshort versions"
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
const serverPath = await bdsCore.serverManeger.serverManeger(options.platform === "java" ? "java" : "bedrock", {
|
||||
...(options.id ? {newID: false, ID: options.id} : {newID: true}),
|
||||
});
|
||||
|
||||
const installData = await (options.platform === "java" ? bdsCore.Java.installServer : bdsCore.Bedrock.installServer)(Object.assign({}, serverPath, {
|
||||
version: options.version,
|
||||
altServer: options.altserver as never,
|
||||
allowBeta: Boolean(options.beta)
|
||||
}));
|
||||
|
||||
console.log("ID: %O, Server Version: %O, Server Date: %O", installData.id, installData.version, installData.date);
|
||||
})
|
||||
.command("list", "list all versions", yargs => {
|
||||
const { platform, altserver } = yargs.option("platform", {
|
||||
type: "string",
|
||||
string: true,
|
||||
alias: "p",
|
||||
choices: ["bedrock", "java"],
|
||||
default: "bedrock",
|
||||
description: "Platform to install"
|
||||
}).option("altserver", {
|
||||
type: "string",
|
||||
string: true,
|
||||
alias: "a",
|
||||
choices: ["spigot", "paper", "purpur", "pocketmine", "powernukkit", "nukkit", "cloudbust"],
|
||||
}).parseSync();
|
||||
return (platform === "java" ? bdsCore.Java.listVersions : bdsCore.Bedrock.listVersions)(altserver as never).then(data => console.log(JSON.stringify(data, null, 2)));
|
||||
})
|
||||
.command("run", "Start server", async yargs => {
|
||||
const option = yargs.option("id", {
|
||||
type: "string",
|
||||
string: true,
|
||||
alias: "d",
|
||||
demandOption: true,
|
||||
}).parseSync();
|
||||
const idInfo = (await bdsCore.listIDs()).find(local => local.id === option.id);
|
||||
if (!idInfo) throw new Error("Invalid ID");
|
||||
const sserverPaths = await bdsCore.serverManeger.serverManeger(option.platform === "java" ? "java" : "bedrock", {ID: option.id, newID: false});
|
||||
const session = await (idInfo.platform === "java" ? bdsCore.Java.startServer : bdsCore.Bedrock.startServer)(sserverPaths);
|
||||
process.on("error", console.log);
|
||||
session.once("backup", filePath => console.log("Backup file path: %O", filePath));
|
||||
process.stdin.pipe(session.stdin);
|
||||
session.stdout.pipe(process.stdout);
|
||||
session.stderr.pipe(process.stderr);
|
||||
for (const ss of ([
|
||||
"SIGCONT",
|
||||
"SIGINT",
|
||||
"SIGTERM",
|
||||
] as NodeJS.Signals[])) process.on(ss, () => session.stopServer());
|
||||
return session;
|
||||
.command(["install", "i", "update"], "Install/update server platform", yargs => yargs, async options => {
|
||||
console.log(bdsCore);
|
||||
})
|
||||
.command(["start", "run", "$0"], "start server", yargs => yargs, async options => {})
|
||||
.parseAsync().catch(err => {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
|
@ -1,37 +1,3 @@
|
||||
# Bds Maneger Core
|
||||
|
||||
Basic core to install, update and manage several minecraft servers automatically, depending on a few dependencies, the basic being **Nodejs**.
|
||||
|
||||
## Servers supports and TODO
|
||||
|
||||
**Bedrock Mojang**:
|
||||
- [x] Install/Update.
|
||||
- [ ] Hot backup.
|
||||
- [x] Start.
|
||||
- [x] Port Listened.
|
||||
- [ ] Player connect/disconnect/spawn.
|
||||
- [ ] Player kick/ban.
|
||||
|
||||
**Pocketmine PMMP**:
|
||||
- [x] Install/Update.
|
||||
- [ ] Hot backup.
|
||||
- [x] Start.
|
||||
- [ ] Port listened.
|
||||
- [ ] Player connect/disconnect.
|
||||
- [ ] Player kick/ban.
|
||||
|
||||
**Powernukkit** and **Cloudbust**:
|
||||
- [x] Install/Update.
|
||||
- 🚫 Hot backup.
|
||||
- [x] Start.
|
||||
- [ ] Port listened.
|
||||
- [ ] Player connect/disconnect.
|
||||
- [ ] Player kick/ban.
|
||||
|
||||
**Java Mojang**, **Purpur**, **Paper** and **Spigot**:
|
||||
- [x] Install/Update.
|
||||
- 🚫 Hot Backup.
|
||||
- [x] Start.
|
||||
- [ ] Port listened.
|
||||
- [ ] Player connect/disconect action.
|
||||
- [ ] Player kick/ban.
|
||||
Um simples nucleo com exposição de APIs para integração com os Servidores de Minecraft para gerenciamento no Nodejs.
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@the-bds-maneger/core",
|
||||
"version": "6.0.4",
|
||||
"description": "",
|
||||
"description": "Core APIs to maneger Minecraft servers platforms",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"type": "module",
|
||||
@ -11,24 +11,22 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"prepack": "tsc --build --clean && tsc",
|
||||
"postpack": "tsc --build --clean",
|
||||
"build": "tsc --build --clean && tsc"
|
||||
"prepack": "tsc --build --clean && tsc --build",
|
||||
"postpack": "tsc --build --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sirherobrine23/cloud": "^3.6.11",
|
||||
"@sirherobrine23/extends": "^3.6.11",
|
||||
"@sirherobrine23/http": "^3.6.11",
|
||||
"adm-zip": "0.5.10",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.5.1",
|
||||
"tar": "^6.1.15",
|
||||
"unzip-stream": "^0.3.1",
|
||||
"unzipper": "^0.10.14"
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "0.5.0",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@types/tar": "^6.1.5",
|
||||
"@types/unzip-stream": "^0.3.1",
|
||||
"@types/unzipper": "^0.10.6"
|
||||
"@types/tar": "^6.1.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { oracleBucket } from "@sirherobrine23/cloud";
|
||||
/**
|
||||
* Bucket readonly
|
||||
*/
|
||||
export const oracleStorage = oracleBucket.oracleBucketPreAuth("sa-saopaulo-1", "grwodtg32n4d", "bdsFiles", "0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W");
|
5
packages/core/src/internalClouds.ts
Normal file
5
packages/core/src/internalClouds.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { oracleBucket } from "@sirherobrine23/cloud";
|
||||
/**
|
||||
* Bucket readonly
|
||||
*/
|
||||
export const bdsFilesBucket = oracleBucket.oracleBucketPreAuth("sa-saopaulo-1", "grwodtg32n4d", "bdsFiles", "0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W");
|
2
packages/core/src/platform/bedrock/index.ts
Normal file
2
packages/core/src/platform/bedrock/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * as listVersion from "./listVersion.js";
|
||||
export * from "./main.js";
|
184
packages/core/src/platform/bedrock/listVersion.ts
Normal file
184
packages/core/src/platform/bedrock/listVersion.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import { http, Github } from "@sirherobrine23/http";
|
||||
import { versionsStorages } from "../../serverRun.js";
|
||||
import util from "node:util";
|
||||
import xml from "xml-js";
|
||||
|
||||
export interface mojangInfo {
|
||||
date: Date,
|
||||
release?: "oficial" | "preview",
|
||||
url: {
|
||||
[platform in NodeJS.Platform]?: {
|
||||
[arch in NodeJS.Architecture]?: {
|
||||
[ext in "tgz" | "zip"]?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mojangCache = new versionsStorages<mojangInfo>();
|
||||
export async function listMojang() {
|
||||
const versions = await http.jsonRequestBody<({version: string} & mojangInfo)[]>("https://raw.githubusercontent.com/Sirherobrine23/BedrockFetch/main/versions/all.json");
|
||||
versions.filter(ver => !(mojangCache.has(ver.version))).forEach(rel => mojangCache.set(rel.version, {
|
||||
date: new Date(rel.date),
|
||||
release: rel.release,
|
||||
url: rel.url
|
||||
}));
|
||||
}
|
||||
|
||||
type powerNukkitRelease = {
|
||||
version: string;
|
||||
minecraftVersion: string;
|
||||
releaseTime: number;
|
||||
commitId: string;
|
||||
snapshotBuild?: number;
|
||||
artefacts: string[];
|
||||
}
|
||||
|
||||
export interface powernukkitDownload {
|
||||
version: string;
|
||||
releaseDate: Date;
|
||||
releaseType: "snapshot"|"oficial";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const powernukkitCache = new versionsStorages<powernukkitDownload>();
|
||||
export async function listPowernukkitProject() {
|
||||
const releases = await http.jsonRequestBody<{[k in "releases"|"snapshots"]: powerNukkitRelease[]}>("https://raw.githubusercontent.com/PowerNukkit/powernukkit-version-aggregator/master/powernukkit-versions.json");
|
||||
const releasesData = (Object.keys(releases) as (keyof typeof releases)[]).map(releaseType => releases[releaseType].map(data => ({...data, releaseType}))).flat(1).sort((b, a) => Math.min(1, Math.max(-1, a.releaseTime - b.releaseTime)));
|
||||
for (const data of releasesData) {
|
||||
if (powernukkitCache.has(data.minecraftVersion)) continue;
|
||||
const releateDate = new Date(data.releaseTime);
|
||||
const getArtefactExtension = (artefactId: string) => (artefactId.includes("REDUCED_JAR")) ? ".jar" : (artefactId.includes("REDUCED_SOURCES_JAR")) ? "-sources.jar" : (artefactId.includes("SHADED_JAR")) ? "-shaded.jar" : (artefactId.includes("SHADED_SOURCES_JAR")) ? "-shaded-sources.jar" : (artefactId.includes("JAVADOC_JAR")) ? "-javadoc.jar" : ".unknown";
|
||||
function buildArtefactUrl(data: any, artefactId?: string) {
|
||||
const buildReleaseArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : util.format("https://search.maven.org/remotecontent?filepath=org/powernukkit/powernukkit/%s/powernukkit-%s%s", data.version, data.version, getArtefactExtension(artefactId));
|
||||
const buildSnapshotArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : util.format("https://oss.sonatype.org/content/repositories/snapshots/org/powernukkit/powernukkit/%s-SNAPSHOT/powernukkit-%s-%s%s", data.version.substring(0, data.version.indexOf("-SNAPSHOT")), data.version.substring(0, data.version.indexOf("-SNAPSHOT")), releateDate.getUTCFullYear().toString().padStart(4, "0") + (releateDate.getUTCMonth() + 1).toString().padStart(2, "0") + releateDate.getUTCDate().toString().padStart(2, "0") + "." + releateDate.getUTCHours().toString().padStart(2, "0") + releateDate.getUTCMinutes().toString().padStart(2, "0") + releateDate.getUTCSeconds().toString().padStart(2, "0") + "-" + data.snapshotBuild, getArtefactExtension(artefactId));
|
||||
if (artefactId == "GIT_SOURCE") {
|
||||
if (data.commitId) return util.format("https://github.com/PowerNukkit/PowerNukkit/tree/%s", data.commitId);
|
||||
else if (data.snapshotBuild && data.artefacts.includes("SHADED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "SHADED_SOURCES_JAR");
|
||||
else if (data.snapshotBuild && data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "REDUCED_SOURCES_JAR");
|
||||
else if (data.artefacts.includes("SHADED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "SHADED_SOURCES_JAR");
|
||||
else if (data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "REDUCED_SOURCES_JAR");
|
||||
} else if (data.snapshotBuild) return buildSnapshotArtefactUrl(data, artefactId);
|
||||
return buildReleaseArtefactUrl(data, artefactId);
|
||||
}
|
||||
const artefacts = data.artefacts.reduce<{[key: string]: string}>((acc, artefactId) => {acc[artefactId] = buildArtefactUrl(data, artefactId); return acc;}, {});
|
||||
if (!(artefacts.SHADED_JAR || artefacts.REDUCED_JAR)) continue;
|
||||
powernukkitCache.set(data.minecraftVersion, {
|
||||
version: data.minecraftVersion,
|
||||
releaseDate: releateDate,
|
||||
releaseType: data.releaseType === "releases" ? "oficial" : "snapshot",
|
||||
url: artefacts.SHADED_JAR || artefacts.REDUCED_JAR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface cloudburstDownload {
|
||||
releaseDate: Date;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const nukkitCache = new versionsStorages<cloudburstDownload>();
|
||||
export const cloudburstCache = new versionsStorages<cloudburstDownload>();
|
||||
export async function listCloudburstProject() {
|
||||
const Projects = [ "Nukkit", "Server" ] as const;
|
||||
for (const Project of Projects) {
|
||||
const { body: { jobs } } = await http.jsonRequest<{jobs: {name: string, _class: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${Project}/api/json`);
|
||||
const buildFiles = await Promise.all(jobs.filter(b => b._class === "org.jenkinsci.plugins.workflow.job.WorkflowJob").map(b => b.name).map(async branch => {
|
||||
const { body: { builds } } = await http.jsonRequest<{builds: {_class: string, number: number, url: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${Project}/job/${branch}/api/json`);
|
||||
return Promise.all(builds.map(async build => {
|
||||
const { body: { artifacts, result, timestamp, actions } } = await http.jsonRequest<{result: "SUCCESS", actions: {_class: string, [k: string]: any}[], timestamp: number, artifacts: {displayPath: string, fileName: string, relativePath: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${Project}/job/${branch}/${build.number}/api/json`);
|
||||
if (result !== "SUCCESS") return [];
|
||||
const branchBuild = actions.find(r => typeof r["buildsByBranchName"] === "object");
|
||||
if (!branch) return [];
|
||||
const commitID = ((branchBuild?.buildsByBranchName[Object.keys(branchBuild.buildsByBranchName).at(0)]?.marked?.SHA1 || branchBuild.buildsByBranchName[Object.keys(branchBuild.buildsByBranchName).at(0)]?.revision?.SHA1) as string|undefined)
|
||||
let mcpeVersion: string;
|
||||
if (Project === "Server") {
|
||||
const json = xml.xml2js((await http.bufferRequestBody(`https://raw.githubusercontent.com/CloudburstMC/Server/${commitID}/pom.xml`)).toString("utf8"), {compact: true});
|
||||
const info = json["project"].dependencies.dependency.find(dep => dep.groupId._text === "com.nukkitx");
|
||||
mcpeVersion = info.version._text;
|
||||
} else {
|
||||
try {
|
||||
// https://raw.githubusercontent.com/CloudburstMC/Nukkit/master/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java
|
||||
const lines = (await http.bufferRequestBody(`https://raw.githubusercontent.com/CloudburstMC/Nukkit/${commitID}/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java`)).toString("utf8").split("\n");
|
||||
const versions = lines.filter(l => l.trim().startsWith("String") && l.toLowerCase().includes("version")).reduce<{[k: string]: string}>((acc, line) => {
|
||||
line = line.trim().slice(6).trim();
|
||||
const i = line.indexOf("=");
|
||||
const def = line.slice(0, i).trim();
|
||||
let version = line.slice(i+1).trim().slice(1, -1);
|
||||
if (version.endsWith("\"")) version = version.slice(0, -1).trim();
|
||||
if (version.startsWith("v")) version = version.slice(1);
|
||||
if (!(version.includes("+"))) acc[def] = version;
|
||||
return acc;
|
||||
}, {});
|
||||
mcpeVersion = versions.MINECRAFT_VERSION || versions.MINECRAFT_VERSION_NETWORK;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (!mcpeVersion) return [];
|
||||
return artifacts.filter(f => f.relativePath.endsWith(".jar")).map(target => ({
|
||||
buildNumber: build.number,
|
||||
branch,
|
||||
mcpeVersion,
|
||||
releaseDate: new Date(timestamp),
|
||||
url: `https://ci.opencollab.dev/job/NukkitX/job/${Project}/job/${branch}/${build.number}/artifact/${target.relativePath}`,
|
||||
}));
|
||||
}));
|
||||
})).then(r => r.flat(2));
|
||||
for (const build of buildFiles.sort((b, a) => a.releaseDate.getTime() - b.releaseDate.getTime())) {
|
||||
if (Project === "Server") {
|
||||
if (cloudburstCache.has(build.mcpeVersion)) continue;
|
||||
cloudburstCache.set(build.mcpeVersion, {
|
||||
releaseDate: build.releaseDate,
|
||||
url: build.url
|
||||
});
|
||||
} else {
|
||||
if (nukkitCache.has(build.mcpeVersion)) continue;
|
||||
nukkitCache.set(build.mcpeVersion, {
|
||||
releaseDate: build.releaseDate,
|
||||
url: build.url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface pocketmineDownload {
|
||||
releateDate: Date;
|
||||
releaseType: "preview" | "oficial";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const pocketmineCache = new versionsStorages<pocketmineDownload>();
|
||||
const pocketmineGithub = await Github.repositoryManeger("pmmp", "PocketMine-MP");
|
||||
export async function listPocketmineProject() {
|
||||
const pocketmineReleases = (await pocketmineGithub.release.getRelease()).filter(rel => (rel.assets.find(assert => assert.name.endsWith(".phar")) ?? {}).browser_download_url);
|
||||
for (const data of pocketmineReleases) {
|
||||
if (pocketmineCache.has(data.tag_name)) continue;
|
||||
const assest = data.assets.find(assert => assert.name.endsWith(".phar"));
|
||||
pocketmineCache.set(data.tag_name, {
|
||||
releaseType: data.prerelease ? "preview" : "oficial",
|
||||
releateDate: new Date(assest.created_at),
|
||||
url: assest.browser_download_url
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function getCacheVersions() {
|
||||
return {
|
||||
mojang: Array.from(mojangCache.keys()).reduce<{[k: string]: mojangInfo}>((acc, key) => {acc[key] = mojangCache.get(key); return acc;}, {}),
|
||||
pocketmine: Array.from(pocketmineCache.keys()).reduce<{[k: string]: pocketmineDownload}>((acc, key) => {acc[key] = pocketmineCache.get(key); return acc;}, {}),
|
||||
powernukkit: Array.from(powernukkitCache.keys()).reduce<{[k: string]: powernukkitDownload}>((acc, key) => {acc[key] = powernukkitCache.get(key); return acc;}, {}),
|
||||
nukkit: Array.from(nukkitCache.keys()).reduce<{[k: string]: cloudburstDownload}>((acc, key) => {acc[key] = nukkitCache.get(key); return acc;}, {}),
|
||||
cloudburst: Array.from(cloudburstCache.keys()).reduce<{[k: string]: cloudburstDownload}>((acc, key) => {acc[key] = cloudburstCache.get(key); return acc;}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function syncCaches() {
|
||||
await Promise.all([
|
||||
listMojang(),
|
||||
listCloudburstProject(),
|
||||
listPocketmineProject(),
|
||||
listPowernukkitProject()
|
||||
]);
|
||||
}
|
288
packages/core/src/platform/bedrock/main.ts
Normal file
288
packages/core/src/platform/bedrock/main.ts
Normal file
@ -0,0 +1,288 @@
|
||||
import { extendsFS } from "@sirherobrine23/extends";
|
||||
import { http } from "@sirherobrine23/http";
|
||||
import AdmZip from "adm-zip";
|
||||
import child_process from "node:child_process";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { finished } from "node:stream/promises";
|
||||
import tar from "tar";
|
||||
import { bdsFilesBucket } from "../../internalClouds.js";
|
||||
import { customEvent, defineEvents } from "../../serverRun.js";
|
||||
import * as bedrockVersions from "./listVersion.js";
|
||||
|
||||
export type platforms = "mojang" | "pocketmine" | "powernukkit" | "nukkit" | "cloudburst";
|
||||
|
||||
export interface bedrockPorts {
|
||||
port: number;
|
||||
localListen?: string;
|
||||
};
|
||||
|
||||
export interface playerInfo {
|
||||
connected: boolean;
|
||||
banned: boolean;
|
||||
xuid?: string;
|
||||
historic: {
|
||||
action: string;
|
||||
actionDate: Date;
|
||||
}[];
|
||||
};
|
||||
|
||||
class playerListen extends Map<string, playerInfo> {
|
||||
constructor(origem?: Record<string, playerInfo>) {
|
||||
super(Object.keys(origem || {}).map(key => ([key, origem[key]])));
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Array.from(this.keys()).reduce<{ [playerName: string]: playerInfo }>((acc, player) => {
|
||||
acc[player] = this.get(player);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
init(playerName: string): this {
|
||||
if (!(this.has(playerName))) {
|
||||
super.set(playerName, {
|
||||
banned: false,
|
||||
connected: false,
|
||||
historic: [],
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
updateState(playerName: string, state: playerInfo["historic"][number]["action"]) {
|
||||
const actionDate = new Date();
|
||||
const playerData = this.init(playerName).get(playerName);
|
||||
if (state === "disconnected") playerData.connected = false; else playerData.connected = true;
|
||||
playerData.historic.push({ action: state, actionDate });
|
||||
super.set(playerName, playerData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return boolean if Class input is Bedrock class server
|
||||
* @param event - Bedrock class
|
||||
* @returns
|
||||
*/
|
||||
export function isBedrock(event: Bedrock<any>): event is Bedrock<any> {
|
||||
return event instanceof Bedrock;
|
||||
}
|
||||
|
||||
export type bedrockEvents = defineEvents<{
|
||||
logLine(lineString: string): void;
|
||||
portListen(info: bedrockPorts): void;
|
||||
installedVersion(serverVersion: string): void;
|
||||
}>;
|
||||
|
||||
export class Bedrock<P extends platforms> extends customEvent<bedrockEvents> {
|
||||
readonly serverFolder: string;
|
||||
readonly rootServer: string;
|
||||
readonly platform: P;
|
||||
constructor(rootServer: string, platform: P) {
|
||||
super();
|
||||
if ((!(["mojang", "pocketmine", "powernukkit", "nukkit", "cloudburst"]).includes(platform))) throw new Error("Invalid platform");
|
||||
this.platform = platform;
|
||||
this.rootServer = rootServer;
|
||||
this.serverFolder = path.join(rootServer, "server");
|
||||
Object.defineProperty(this, "rootServer", { writable: false });
|
||||
Object.defineProperty(this, "serverFolder", { writable: false });
|
||||
Object.defineProperty(this, "platform", { writable: false });
|
||||
}
|
||||
|
||||
async installServer(version: string | number) {
|
||||
const { platform } = this;
|
||||
if (!(await extendsFS.exists(this.serverFolder))) await fs.mkdir(this.serverFolder, { recursive: true });
|
||||
if (platform === "mojang") {
|
||||
const release = bedrockVersions.mojangCache.get(version);
|
||||
if (!release) throw new Error("Not valid Release");
|
||||
const serverURL = release.url[process.platform]?.[process.arch]?.tgz;
|
||||
if (!serverURL) throw new Error("Current platform not support mojang server");
|
||||
let backupFiles = [{ file: "allowlist.json", data: "" }, { file: "permissions.json", data: "" }, { file: "server.properties", data: "" }];
|
||||
backupFiles = await Promise.all(backupFiles.map(async file => { file.data = await fs.readFile(path.join(this.serverFolder, file.file), "utf8").catch(() => ""); return file }));
|
||||
await finished((await http.streamRequest(serverURL)).pipe(tar.extract({ cwd: this.serverFolder, preserveOwner: true, keep: false })));
|
||||
await Promise.all(backupFiles.filter(file => !!(file.data.trim())).map(async file => fs.writeFile(path.join(this.serverFolder, file.file), file.data)));
|
||||
this.emit("installedVersion", bedrockVersions.mojangCache.prettyVersion(version));
|
||||
} else if (platform === "pocketmine") {
|
||||
const release = bedrockVersions.pocketmineCache.get(version);
|
||||
if (!release) throw new Error("Not valid Release");
|
||||
await finished((await http.streamRequest(release.url)).pipe(createWriteStream(path.join(this.serverFolder, "server.phar"))));
|
||||
let phpFiles = (await bdsFilesBucket.listFiles("php_bin/")).map(file => ({ name: file.name.slice(8).toLowerCase(), data: file })).filter(file => file.name.startsWith(`${process.platform}_${process.arch}`));
|
||||
if (!phpFiles.length) throw new Error("Cannot get php binary to current platform");
|
||||
const phpFile = phpFiles.sort((b, a) => a.data.Dates.Modified.getTime() - b.data.Dates.Modified.getTime()).at(0);
|
||||
const binPath = path.join(this.serverFolder, "bin");
|
||||
if (await extendsFS.exists(binPath)) await fs.rm(binPath, { recursive: true, force: true });
|
||||
if (phpFile.name.endsWith(".tar.gz") || phpFile.name.endsWith(".tgz")) await finished((await phpFile.data.getFile()).pipe(tar.extract({ cwd: binPath })));
|
||||
else if (phpFile.name.endsWith(".zip")) {
|
||||
const tmpFile = path.join(tmpdir(), Date.now() + "_" + phpFile.name);
|
||||
await finished((await phpFile.data.getFile()).pipe(createWriteStream(tmpFile)));
|
||||
await new Promise<void>((done, reject) => (new AdmZip(tmpFile)).extractAllToAsync(binPath, true, true, err => err ? reject(err) : done()));
|
||||
await fs.rm(tmpFile, { force: true });
|
||||
}
|
||||
this.emit("installedVersion", bedrockVersions.pocketmineCache.prettyVersion(version));
|
||||
} else if (platform === "powernukkit") {
|
||||
const release = bedrockVersions.powernukkitCache.get(version);
|
||||
if (!release) throw new Error("Not valid Release");
|
||||
await finished(await http.streamRequest(release.url), createWriteStream(path.join(this.serverFolder, "server.jar")));
|
||||
this.emit("installedVersion", bedrockVersions.powernukkitCache.prettyVersion(version));
|
||||
} else if (platform === "cloudburst" || platform === "nukkit") {
|
||||
const platformVersions = platform === "cloudburst" ? bedrockVersions.cloudburstCache : bedrockVersions.nukkitCache;
|
||||
const release = platformVersions.get(version);
|
||||
if (!release) throw new Error("Not valid Release");
|
||||
await finished(await http.streamRequest(release.url), createWriteStream(path.join(this.serverFolder, "server.jar")));
|
||||
this.emit("installedVersion", platformVersions.prettyVersion(version));
|
||||
}
|
||||
}
|
||||
|
||||
ports: bedrockPorts[] = [];
|
||||
readonly players = new playerListen();
|
||||
|
||||
serverProcess?: child_process.ChildProcess;
|
||||
async runServer() {
|
||||
const { platform } = this;
|
||||
const logLine: (bedrockEvents["logLine"])[] = [];
|
||||
|
||||
if (platform === "mojang") {
|
||||
const fileExec = path.join(this.serverFolder, (await fs.readdir(this.serverFolder)).find(file => file.startsWith("bedrock_server")));
|
||||
if (process.platform !== "win32") await fs.chmod(fileExec, 0o775);
|
||||
this.serverProcess = child_process.spawn(fileExec, {
|
||||
cwd: this.serverFolder,
|
||||
stdio: ["pipe", "pipe", "pipe"]
|
||||
});
|
||||
logLine.push(lineString => {
|
||||
// [INFO] IPv4 supported, port: 19132
|
||||
// [2023-03-08 13:01:57 INFO] Listening on IPv4 port: 19132
|
||||
const ipProtocol = lineString.slice(lineString.indexOf("IPv"), lineString.indexOf("IPv")+4);
|
||||
if (ipProtocol) {
|
||||
let port = lineString.slice(lineString.lastIndexOf("port:")+5).trim();
|
||||
if (port.indexOf(":") !== -1) port = port.slice(0, port.lastIndexOf(":"));
|
||||
const info: bedrockPorts = {
|
||||
port: Number(port),
|
||||
localListen: ipProtocol.toLowerCase() === "ipv4" ? "0.0.0.0" : "[::]",
|
||||
};
|
||||
this.ports.push(info);
|
||||
this.emit("portListen", info);
|
||||
}
|
||||
return null;
|
||||
}, lineString => {
|
||||
lineString = lineString.replace(/^(.*)?\[.*\]/, "").trim();
|
||||
if (lineString.startsWith("Player")) {
|
||||
lineString = lineString.replace("Player", "").trim();
|
||||
|
||||
// Spawned, disconnected, connected
|
||||
let action: string;
|
||||
if (lineString.startsWith("Spawned")) action = "Spawned";
|
||||
else if (lineString.startsWith("disconnected")) action = "disconnected";
|
||||
else if (lineString.startsWith("connected")) action = "connected";
|
||||
if (!action) return null;
|
||||
lineString = lineString.replace(action, "").trim();
|
||||
if (lineString.startsWith(":")) lineString = lineString.slice(1).trim();
|
||||
|
||||
let playerName = lineString.substring(0, lineString.indexOf("xuid:")-1).trim();
|
||||
if (!playerName) return null;
|
||||
if (playerName.endsWith(",")) playerName = playerName.slice(0, playerName.length - 1);
|
||||
|
||||
let xuid: string;
|
||||
if (lineString.indexOf("xuid:") !== -1) {
|
||||
xuid = lineString.slice(lineString.indexOf("xuid:")+5).trim();
|
||||
if (!xuid) xuid = null;
|
||||
}
|
||||
|
||||
if (!(this.players.has(playerName))) this.players.init(playerName);
|
||||
if (!!xuid) {
|
||||
if (!(this.players.get(playerName)).xuid) {
|
||||
const info = this.players.get(playerName);
|
||||
info.xuid = xuid;
|
||||
this.players.set(playerName, info);
|
||||
}
|
||||
}
|
||||
this.players.updateState(playerName, action);
|
||||
}
|
||||
});
|
||||
} else if (platform === "pocketmine") {
|
||||
const phpBin = (await extendsFS.readdirV2(this.serverFolder)).find(file => file.endsWith("php") || file.endsWith("php.exe"));
|
||||
if (!phpBin) throw new Error("Fist install php binary in server folder");
|
||||
this.serverProcess = child_process.spawn(phpBin, ["server.phar", "--no-wizard"], {
|
||||
cwd: this.serverFolder,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
} else if (platform === "nukkit" || platform === "powernukkit" || platform === "cloudburst") {
|
||||
this.serverProcess = child_process.spawn("java", [
|
||||
"-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",
|
||||
"-jar", "server.jar",
|
||||
], {
|
||||
cwd: this.serverFolder,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
}
|
||||
|
||||
// Break lines
|
||||
([
|
||||
readline.createInterface(this.serverProcess.stdout),
|
||||
readline.createInterface(this.serverProcess.stderr)
|
||||
]).map(inter => inter.on("error", err => this.emit("error", err)).on("line", data => {
|
||||
this.emit("logLine", data);
|
||||
logLine.forEach(fn => Promise.resolve().then(() => fn(data)).catch(err => this.emit("error", err)));
|
||||
}));
|
||||
|
||||
return this.serverProcess;
|
||||
}
|
||||
|
||||
writeLn(data: string | Buffer) {
|
||||
this.serverProcess.stdin.write(data);
|
||||
if (Buffer.isBuffer(data) && (String.fromCharCode(data.at(-1)) !== "\n")) this.serverProcess.stdin.write("\n");
|
||||
else if (typeof data === "string" && !(data.trim().endsWith("\n"))) this.serverProcess.stdin.write("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
async stopServer() {
|
||||
this.writeLn("stop");
|
||||
return new Promise<{ code: number, signal: NodeJS.Signals }>((done, reject) => this.serverProcess.once("error", reject).once("exit", (code, signal) => done({ code, signal })));
|
||||
}
|
||||
|
||||
async setPlayerPermission(playername: string, permission: P extends "mojang" ? "operator" | "member" | "visitor" : "admin" | "user") {
|
||||
if (this.platform === "mojang") {
|
||||
const permissions: { permission: "operator" | "member" | "visitor", xuid: string }[] = JSON.parse(await fs.readFile(path.join(this.serverFolder, "permissions.json"), "utf8"));
|
||||
permissions.push({
|
||||
permission: permission as any,
|
||||
xuid: playername
|
||||
});
|
||||
await fs.writeFile(path.join(this.serverFolder, "permissions.json"), JSON.stringify(permissions));
|
||||
if (this.serverProcess) this.writeLn("permission reload");
|
||||
}
|
||||
}
|
||||
|
||||
async allowList(playername: string, options?: { xuid?: string, ignoresPlayerLimit?: boolean }) {
|
||||
if (this.platform === "mojang") {
|
||||
const permissions: { ignoresPlayerLimit: boolean, name: string, xuid?: string }[] = JSON.parse(await fs.readFile(path.join(this.serverFolder, "allowlist.json"), "utf8"));
|
||||
await fs.writeFile(path.join(this.serverFolder, "allowlist.json"), JSON.stringify(permissions));
|
||||
permissions.push({
|
||||
name: playername,
|
||||
ignoresPlayerLimit: options?.ignoresPlayerLimit ?? false,
|
||||
xuid: options?.xuid
|
||||
});
|
||||
if (this.serverProcess) this.writeLn("allowlist reload");
|
||||
}
|
||||
}
|
||||
}
|
2
packages/core/src/platform/java/index.ts
Normal file
2
packages/core/src/platform/java/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * as listVersion from "./listVersion.js";
|
||||
export * from "./main.js";
|
175
packages/core/src/platform/java/listVersion.ts
Normal file
175
packages/core/src/platform/java/listVersion.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { Github, http } from "@sirherobrine23/http";
|
||||
import stream from "node:stream";
|
||||
import path from "path";
|
||||
import semver from "semver";
|
||||
import { bdsFilesBucket } from "../../internalClouds.js";
|
||||
import { versionsStorages } from "../../serverRun.js";
|
||||
|
||||
interface baseDownload {
|
||||
URL: string;
|
||||
releaseDate: Date;
|
||||
}
|
||||
|
||||
interface mojangInfo extends baseDownload {
|
||||
release: "oficial" | "snapshot" | "beta" | "alpha";
|
||||
}
|
||||
|
||||
async function PromiseSplit<T extends (any[])>(arrayInput: T, fn: (value: T[number]) => any): Promise<Awaited<ReturnType<typeof fn>>[]> {
|
||||
const backup = ([]).concat(...arrayInput);
|
||||
let i = arrayInput.length, b = 1; while (i > 2) { b++; i /= 2 };
|
||||
const arraySplit = Math.max(2, Math.floor(b));
|
||||
let result: Awaited<ReturnType<typeof fn>>[] = [];
|
||||
await Promise.all(Array(arraySplit).fill(async function call() {
|
||||
return Promise.resolve().then(() => backup.length > 0 ? fn(backup.shift()) : null).then(data => {
|
||||
result.push(data);
|
||||
if (backup.length > 0) return call();
|
||||
return null;
|
||||
});
|
||||
}).map(a => a()));
|
||||
return result.flat(1);
|
||||
}
|
||||
|
||||
export const mojangCache = new versionsStorages<mojangInfo>();
|
||||
export async function listMojang() {
|
||||
const versions = (await http.jsonRequestBody<{ versions: { id: string, releaseTime: string, url: string, type: "snapshot" | "release" | "old_beta" | "old_alpha" }[] }>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json")).versions;
|
||||
await PromiseSplit(versions, async version => {
|
||||
if (mojangCache.has(version.id)) return;
|
||||
const { downloads: { server } } = await http.jsonRequestBody<{ downloads: { server?: { url: string } } }>(version.url);
|
||||
if (!server) return;
|
||||
mojangCache.set(version.id, {
|
||||
release: version.type === "release" ? "oficial" : version.type === "snapshot" ? "snapshot" : version.type === "old_beta" ? "beta" : "alpha",
|
||||
releaseDate: new Date(version.releaseTime),
|
||||
URL: server.url,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface spigotDownload {
|
||||
getServer(): Promise<stream.Readable>;
|
||||
craftbukkit?(): Promise<stream.Readable>;
|
||||
}
|
||||
|
||||
export const spigotCache = new versionsStorages<spigotDownload>();
|
||||
export async function listSpigot() {
|
||||
const spigotFiles = await bdsFilesBucket.listFiles("SpigotBuild/");
|
||||
for (const file of spigotFiles.filter(file => file.name.slice(12).startsWith("1.")).sort((b, a) => {
|
||||
const valid = (c: typeof a) => semver.valid(semver.coerce(c.name.slice(12, -4), { loose: true }), { loose: true });
|
||||
return semver.compare(valid(a), valid(b));
|
||||
})) {
|
||||
const version = file.name.slice(12, -4), fixVersion = semver.valid(semver.coerce(version, { loose: true }), { loose: true });
|
||||
if (spigotCache.has(fixVersion)) continue;
|
||||
const craftbukkit = spigotFiles.find(file => file.name.slice(12).startsWith(`craftbukkit-${version}.jar`));
|
||||
spigotCache.set(fixVersion, {
|
||||
getServer: file.getFile,
|
||||
craftbukkit: craftbukkit ? craftbukkit.getFile : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const paperCache = new versionsStorages<baseDownload>();
|
||||
export const velocityCache = new versionsStorages<baseDownload>();
|
||||
export const foliaCache = new versionsStorages<baseDownload>();
|
||||
export async function listPaperProject() {
|
||||
const paperProjects = ["paper", "velocity", "folia"] as const;
|
||||
await Promise.all(paperProjects.map(async projectName => {
|
||||
const baseURL = new URL(path.posix.join("/v2/projects", projectName), "https://api.papermc.io");
|
||||
const projectInfo = await http.jsonRequestBody<{ versions: string[] }>(baseURL);
|
||||
for (const projectVersion of projectInfo.versions) {
|
||||
if (projectName === "paper" && paperCache.has(projectVersion)) continue;
|
||||
else if (projectName === "velocity" && velocityCache.has(projectVersion)) continue;
|
||||
else if (projectName === "folia" && foliaCache.has(projectVersion)) continue;
|
||||
else {
|
||||
const versionBase = new URL(path.posix.join(baseURL.pathname, "versions", projectVersion), baseURL);
|
||||
const builds = await http.jsonRequestBody<{ builds: number[] }>(versionBase);
|
||||
for (const build of builds.builds) {
|
||||
const buildURL = new URL(path.posix.join(versionBase.pathname, "builds", String(build)), versionBase);
|
||||
const downloadInfo = await http.jsonRequestBody<{ time: string; downloads: { application?: { name: string } } }>(buildURL);
|
||||
if (downloadInfo.downloads.application) {
|
||||
const downloadURL = new URL(path.posix.join(buildURL.pathname, "downloads", downloadInfo.downloads.application.name), buildURL);
|
||||
if (projectName === "paper") paperCache.set(projectVersion, { URL: downloadURL.toString(), releaseDate: new Date(downloadInfo.time) });
|
||||
else if (projectName === "velocity") velocityCache.set(projectVersion, { URL: downloadURL.toString(), releaseDate: new Date(downloadInfo.time) });
|
||||
else if (projectName === "folia") foliaCache.set(projectVersion, { URL: downloadURL.toString(), releaseDate: new Date(downloadInfo.time) });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
export const purpurCache = new versionsStorages<baseDownload>();
|
||||
export async function listPurpurProject() {
|
||||
const baseURL = new URL("https://api.purpurmc.org/v2/purpur");
|
||||
const { versions } = await http.jsonRequestBody<{ versions: string[] }>(baseURL);
|
||||
for (const version of versions) {
|
||||
if (purpurCache.has(version)) continue;
|
||||
const infoBase = new URL(path.posix.join(baseURL.pathname, version, "latest"), baseURL);
|
||||
const relInfo = await http.jsonRequestBody<{ timestamp: number }>(infoBase);
|
||||
purpurCache.set(version, {
|
||||
URL: (new URL(path.posix.join(infoBase.pathname, "download"), infoBase)).toString(),
|
||||
releaseDate: new Date(relInfo.timestamp)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const glowstoneCache = new versionsStorages<baseDownload>();
|
||||
export async function listGlowstoneProject() {
|
||||
const repo = await Github.repositoryManeger("GlowstoneMC", "Glowstone");
|
||||
const rels = (await repo.release.getRelease()).filter(rel => rel.assets.some(asset => asset.name.endsWith(".jar")));
|
||||
rels.forEach(rel => rel.assets.forEach(asset => glowstoneCache.has(rel.tag_name) ? null : glowstoneCache.set(rel.tag_name, {
|
||||
URL: asset.browser_download_url,
|
||||
releaseDate: new Date(asset.created_at)
|
||||
})));
|
||||
}
|
||||
|
||||
export const cuberiteCache = new versionsStorages<{ URL: string[] }>({
|
||||
"win32-x64": {
|
||||
URL: ["https://download.cuberite.org/windows-x86_64/Cuberite.zip"]
|
||||
},
|
||||
"win32-ia32": {
|
||||
URL: ["https://download.cuberite.org/windows-i386/Cuberite.zip"]
|
||||
}
|
||||
});
|
||||
export async function listCuberite() {
|
||||
const projects = ["android", "linux-aarch64", "linux-armhf", "linux-i386", "linux-x86_64", "darwin-x86_64"] as const;
|
||||
await Promise.all(projects.map(async project => {
|
||||
const { builds = [] } = await http.jsonRequestBody<{ builds: { number: number, _class: string }[] }>(`https://builds.cuberite.org/job/${project}/api/json`);
|
||||
for (const job of builds) {
|
||||
const { artifacts = [], result } = await http.jsonRequestBody<{ result: string, artifacts: { relativePath: string, fileName: string }[] }>(`https://builds.cuberite.org/job/${project}/${job.number}/api/json`);
|
||||
if (result !== "SUCCESS") continue;
|
||||
let map = artifacts.filter(file => !file.fileName.endsWith(".sha1")).map(file => `https://builds.cuberite.org/job/${project}/${job.number}/artifact/${file.relativePath}`);
|
||||
if (!map.length) continue;
|
||||
else if (project === "darwin-x86_64") cuberiteCache.set("darwin-x64", { URL: map });
|
||||
else if (project === "linux-x86_64") cuberiteCache.set("linux-x64", { URL: map });
|
||||
else if (project === "linux-aarch64") cuberiteCache.set("linux-arm64", { URL: map });
|
||||
else if (project === "linux-armhf") cuberiteCache.set("linux-arm", { URL: map });
|
||||
else if (project === "linux-i386") cuberiteCache.set("linux-ia32", { URL: map });
|
||||
else if (project === "android") {
|
||||
const serverIndex = map.findIndex(file => file.toLowerCase().endsWith("server.zip"));
|
||||
const server = map[serverIndex];
|
||||
delete map[serverIndex];
|
||||
map = map.filter(Boolean);
|
||||
for (const file of map) {
|
||||
const fileURL = new URL(file);
|
||||
const plat = path.basename(fileURL.pathname).replace(path.extname(fileURL.pathname), "");
|
||||
if (plat.startsWith("x86_64")) cuberiteCache.set("android-x64", { URL: [server, file] });
|
||||
else if (plat.startsWith("x86")) cuberiteCache.set("android-ia32", { URL: [server, file] });
|
||||
else if (plat.startsWith("arm64")) cuberiteCache.set("android-arm64", { URL: [server, file] });
|
||||
else if (plat.startsWith("arm")) cuberiteCache.set("android-arm", { URL: [server, file] });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
export async function syncCaches() {
|
||||
await Promise.all([
|
||||
listMojang(),
|
||||
listSpigot(),
|
||||
listPaperProject(),
|
||||
listPurpurProject(),
|
||||
listGlowstoneProject(),
|
||||
listCuberite(),
|
||||
]);
|
||||
}
|
143
packages/core/src/platform/java/main.ts
Normal file
143
packages/core/src/platform/java/main.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { extendsFS } from "@sirherobrine23/extends";
|
||||
import { http } from "@sirherobrine23/http";
|
||||
import AdmZip from "adm-zip";
|
||||
import child_process from "node:child_process";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { finished } from "node:stream/promises";
|
||||
import tar from "tar";
|
||||
import { customEvent, defineEvents } from "../../serverRun.js";
|
||||
import * as javaVersions from "./listVersion.js";
|
||||
|
||||
export type platform = "mojang" | "spigot" | "paper" | "purpur" | "glowstone" | "folia" | "cuberite";
|
||||
export type javaEvents = defineEvents<{
|
||||
logLine(lineString: string): void;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Return boolean if Class input is Java class server
|
||||
* @param event - Java class
|
||||
* @returns
|
||||
*/
|
||||
export function isJava(event: Java<any>): event is Java<any> {
|
||||
return event instanceof Java;
|
||||
}
|
||||
|
||||
export class Java<P extends platform> extends customEvent<javaEvents> {
|
||||
readonly serverFolder: string;
|
||||
readonly rootServer: string;
|
||||
readonly platform: P;
|
||||
constructor(rootServer: string, platform: P) {
|
||||
super();
|
||||
if ((!(["mojang", "spigot", "paper", "purpur", "glowstone", "folia", "cuberite"]).includes(platform))) throw new Error("Invalid platform");
|
||||
this.platform = platform;
|
||||
this.rootServer = rootServer;
|
||||
this.serverFolder = path.join(rootServer, "server");
|
||||
Object.defineProperty(this, "rootServer", { writable: false });
|
||||
Object.defineProperty(this, "serverFolder", { writable: false });
|
||||
Object.defineProperty(this, "platform", { writable: false });
|
||||
}
|
||||
|
||||
async installServer(version: string | number) {
|
||||
const { platform } = this;
|
||||
if (!(await extendsFS.exists(this.serverFolder))) await fs.mkdir(this.serverFolder, { recursive: true });
|
||||
if (platform === "mojang") {
|
||||
if (!javaVersions.mojangCache.size) await javaVersions.listMojang();
|
||||
await finished((await http.streamRequest(javaVersions.mojangCache.get(version).URL)).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
} else if (platform === "spigot") {
|
||||
if (!javaVersions.spigotCache.size) await javaVersions.listSpigot();
|
||||
const spigotRel = javaVersions.spigotCache.get(version);
|
||||
await finished((await spigotRel.getServer()).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
if (typeof spigotRel.craftbukkit === "function") await finished((await spigotRel.craftbukkit()).pipe(createWriteStream(path.join(this.serverFolder, "craftbukkit.jar"))));
|
||||
} else if (platform === "paper") {
|
||||
if (!javaVersions.paperCache.size) await javaVersions.listPaperProject();
|
||||
await finished((await http.streamRequest(javaVersions.paperCache.get(version).URL)).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
} else if (platform === "purpur") {
|
||||
if (!javaVersions.purpurCache.size) await javaVersions.listPaperProject();
|
||||
await finished((await http.streamRequest(javaVersions.purpurCache.get(version).URL)).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
} else if (platform === "glowstone") {
|
||||
if (!javaVersions.glowstoneCache.size) await javaVersions.listPaperProject();
|
||||
await finished((await http.streamRequest(javaVersions.glowstoneCache.get(version).URL)).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
} else if (platform === "folia") {
|
||||
if (!javaVersions.foliaCache.size) await javaVersions.listGlowstoneProject();
|
||||
await finished((await http.streamRequest(javaVersions.foliaCache.get(version).URL)).pipe(createWriteStream(path.join(this.serverFolder, "server.jar"))));
|
||||
} else if (platform === "cuberite") {
|
||||
if (javaVersions.cuberiteCache.size < 3) await javaVersions.listCuberite();
|
||||
const files = javaVersions.cuberiteCache.get(`${process.platform}-${process.arch}`).URL;
|
||||
for (const fileURL of files) {
|
||||
if (fileURL.endsWith(".zip")) {
|
||||
const tmpFile = path.join(tmpdir(), Date.now() + "_" + path.basename((new URL(fileURL)).pathname));
|
||||
await finished((await http.streamRequest(fileURL)).pipe(createWriteStream(tmpFile)));
|
||||
await new Promise<void>((done, reject) => (new AdmZip(tmpFile)).extractAllToAsync(this.serverFolder, true, true, err => err ? reject(err) : done()));
|
||||
await fs.rm(tmpFile, { force: true });
|
||||
} else await finished((await http.streamRequest(fileURL)).pipe(tar.extract({cwd: this.serverFolder})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverProcess?: child_process.ChildProcess;
|
||||
async runServer() {
|
||||
if (this.platform === "cuberite") {
|
||||
let execPath = path.join(this.serverFolder, "Cuberite");
|
||||
if (process.platform === "win32") execPath += ".exe";
|
||||
this.serverProcess = child_process.spawn(execPath, {
|
||||
cwd: this.serverFolder,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
} else {
|
||||
this.serverProcess = child_process.spawn("java", [
|
||||
"-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",
|
||||
"-jar", "server.jar",
|
||||
], {
|
||||
cwd: this.serverFolder,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
}
|
||||
|
||||
// Break line
|
||||
([
|
||||
readline.createInterface(this.serverProcess.stdout),
|
||||
readline.createInterface(this.serverProcess.stderr),
|
||||
]).forEach(readline => readline.on("error", err => this.emit("error", err)).on("line", line => this.emit("logLine", line)));
|
||||
|
||||
this.on("logLine", (line) => {
|
||||
line = line.replace(/^.*?\[.*\]:/, "").trim();
|
||||
if (line.startsWith("Starting Minecraft server on")) {
|
||||
if (line.lastIndexOf(":") === -1) return null;
|
||||
// const port = Number(line.slice(line.lastIndexOf(":")+1));
|
||||
// this.emit("portListen", {
|
||||
// port,
|
||||
// localListen: "0.0.0.0"
|
||||
// });
|
||||
// this.ports.push({
|
||||
// port,
|
||||
// localListen: "0.0.0.0"
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
return this.serverProcess;
|
||||
}
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
export * from "./serverManeger.js";
|
||||
export * as serverManeger from "./serverManeger.js";
|
||||
export * as Bedrock from "./servers/bedrock.js";
|
||||
export * as Java from "./servers/java.js";
|
||||
// Bedrock platform
|
||||
export * as Bedrock from "./platform/bedrock/index.js";
|
||||
export * as bedrock from "./platform/bedrock/index.js";
|
||||
export { isBedrock } from "./platform/bedrock/index.js";
|
||||
|
||||
// Java platform
|
||||
export * as Java from "./platform/java/index.js";
|
||||
export * as java from "./platform/java/index.js";
|
||||
export { isJava } from "./platform/java/index.js";
|
@ -1,317 +0,0 @@
|
||||
import readline from "node:readline";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { extendsFS } from "@sirherobrine23/extends";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { format } from "node:util";
|
||||
import sanitizeFilename from "sanitize-filename";
|
||||
import child_process from "node:child_process";
|
||||
import crypto from "node:crypto";
|
||||
import stream from "node:stream";
|
||||
import path from "node:path";
|
||||
import tar from "tar";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
|
||||
|
||||
// Default bds maneger core
|
||||
const ENVROOT = process.env.BDSCOREROOT || process.env.bdscoreroot;
|
||||
export const bdsManegerRoot = ENVROOT ? path.resolve(process.cwd(), ENVROOT) : path.join(os.homedir(), ".bdsmaneger");
|
||||
if (!(await extendsFS.exists(bdsManegerRoot))) await fs.mkdir(bdsManegerRoot, {recursive: true});
|
||||
export type withPromise<T> = T|Promise<T>;
|
||||
|
||||
export interface manegerOptions {
|
||||
ID?: string,
|
||||
newID?: boolean,
|
||||
};
|
||||
|
||||
// only letters and numbers
|
||||
const idReg = /^[a-zA-Z0-9_]+$/;
|
||||
|
||||
export interface serverManegerV1 {
|
||||
id: string,
|
||||
rootPath: string,
|
||||
serverFolder: string,
|
||||
backup: string,
|
||||
logs: string,
|
||||
platform: "java"|"bedrock",
|
||||
runCommand(options: Omit<runOptions, "cwd">): ReturnType<typeof runServer>
|
||||
};
|
||||
|
||||
/**
|
||||
* Platform path maneger
|
||||
*/
|
||||
export async function serverManeger(platform: serverManegerV1["platform"], options: manegerOptions): Promise<serverManegerV1> {
|
||||
if (!((["java", "bedrock"]).includes(platform))) throw new TypeError("Invalid platform target!");
|
||||
if (!options) throw new TypeError("Please add serverManeger options!");
|
||||
const platformFolder = path.join(bdsManegerRoot, platform);
|
||||
if ((await fs.readdir(platformFolder).then(a => a.length).catch(() => 0)) === 0 && options.newID === undefined) options.newID = true;
|
||||
|
||||
// Create or check if exists
|
||||
if (options.newID === true) {
|
||||
while(true) {
|
||||
options.ID = typeof crypto.randomUUID === "function" ? crypto.randomUUID().split("-").join("_") : crypto.randomBytes(crypto.randomInt(8, 14)).toString("hex");
|
||||
if (!(idReg.test(options.ID))) continue;
|
||||
if (!((await fs.readdir(platformFolder).catch(() => [])).includes(options.ID))) break;
|
||||
}
|
||||
await fs.mkdir(path.join(platformFolder, options.ID), {recursive: true});
|
||||
} else {
|
||||
// Test invalid ID
|
||||
if (String(options.ID).length > 32) throw new TypeError("options.ID is invalid, is very long!");
|
||||
if (!(!!options.ID && idReg.test(options.ID))) throw new TypeError("options.ID is invalid");
|
||||
else if (!((await fs.readdir(platformFolder)).includes(options.ID))) throw new Error("ID not exists")
|
||||
}
|
||||
|
||||
// Folders
|
||||
const rootPath = path.join(platformFolder, path.posix.resolve("/", sanitizeFilename(options.ID)));
|
||||
const serverFolder = path.join(rootPath, "server");
|
||||
const backup = path.join(rootPath, "backups");
|
||||
const log = path.join(rootPath, "logs");
|
||||
|
||||
for await (const p of [
|
||||
serverFolder,
|
||||
backup,
|
||||
log,
|
||||
]) if (!(await extendsFS.exists(p))) await fs.mkdir(p, {recursive: true});
|
||||
|
||||
return {
|
||||
id: options.ID,
|
||||
platform,
|
||||
rootPath,
|
||||
serverFolder,
|
||||
backup,
|
||||
logs: log,
|
||||
async runCommand(options: Omit<runOptions, "cwd">) {
|
||||
return runServer({...options, cwd: serverFolder});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function listIDs(): Promise<{id: string, platform: "bedrock"|"java", delete: () => Promise<void>}[]> {
|
||||
const main = [];
|
||||
for await (const platform of ["bedrock", "java"]) {
|
||||
try {
|
||||
const platformFolder = path.join(bdsManegerRoot, platform);
|
||||
if (!(await extendsFS.exists(platformFolder))) continue;
|
||||
const IDs = await fs.readdir(platformFolder);
|
||||
for await (const id of IDs) main.push({
|
||||
id: id,
|
||||
platform,
|
||||
async delete() {
|
||||
return fs.rm(path.join(platformFolder, id), {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
return main;
|
||||
}
|
||||
|
||||
export type portListen = {
|
||||
port: number,
|
||||
protocol: "TCP"|"UDP"|"both",
|
||||
listenOn?: string,
|
||||
listenFrom?: "server"|"plugin"
|
||||
};
|
||||
|
||||
export type playerAction = {
|
||||
playerName: string,
|
||||
onDate: Date,
|
||||
action: string,
|
||||
extra?: any
|
||||
};
|
||||
|
||||
export type runOptions = {
|
||||
cwd: string,
|
||||
env?: {[k: string]: string|number|boolean},
|
||||
command: string,
|
||||
args?: (string|number|boolean)[],
|
||||
stdio?: child_process.StdioOptions,
|
||||
paths: serverManegerV1,
|
||||
serverActions?: {
|
||||
stop?(this: serverRun): withPromise<void>,
|
||||
playerAction?(this: serverRun, lineString: string): withPromise<null|void|playerAction>,
|
||||
hotBackup?(this: serverRun): withPromise<stream.Readable|void>,
|
||||
portListen?(this: serverRun, lineString: string): withPromise<void|portListen>,
|
||||
onAvaible?(this: serverRun, lineString: string): withPromise<void|Date>,
|
||||
postStart?: ((this: serverRun) => withPromise<void>)[],
|
||||
}
|
||||
};
|
||||
|
||||
export declare class serverRun extends child_process.ChildProcess {
|
||||
on(event: string, listener: (...args: any[]) => void): this;
|
||||
once(event: string, listener: (...args: any[]) => void): this;
|
||||
on(event: "error", listener: (err: Error) => void): this;
|
||||
once(event: "error", listener: (err: Error) => void): this;
|
||||
on(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
||||
once(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
||||
on(event: "disconnect", listener: () => void): this;
|
||||
once(event: "disconnect", listener: () => void): this;
|
||||
on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
||||
once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
||||
on(event: "message", listener: (message: child_process.Serializable, sendHandle: child_process.SendHandle) => void): this;
|
||||
once(event: "message", listener: (message: child_process.Serializable, sendHandle: child_process.SendHandle) => void): this;
|
||||
on(event: "spawn", listener: () => void): this;
|
||||
once(event: "spawn", listener: () => void): this;
|
||||
on(event: "warning", listener: (data: any) => void): this;
|
||||
once(event: "warning", listener: (data: any) => void): this;
|
||||
|
||||
// BDS Assigns
|
||||
once(event: "line", fn: (data: string, from: "stdout"|"stderr") => void): this;
|
||||
on(event: "line", fn: (data: string, from: "stdout"|"stderr") => void): this;
|
||||
once(event: "player", fn: (playerInfo: playerAction) => void): this;
|
||||
on(event: "player", fn: (playerInfo: playerAction) => void): this;
|
||||
once(event: "portListening", fn: (portInfo: portListen) => void): this;
|
||||
on(event: "portListening", fn: (portInfo: portListen) => void): this;
|
||||
once(event: "serverAvaible", fn: (date: Date) => void): this;
|
||||
on(event: "serverAvaible", fn: (date: Date) => void): this;
|
||||
once(event: "backup", fn: (filePath: string) => void): this;
|
||||
on(event: "backup", fn: (filePath: string) => void): this;
|
||||
once(event: "hotBackup", fn: (fileStream: stream.Readable) => void): this;
|
||||
on(event: "hotBackup", fn: (fileStream: stream.Readable) => void): this;
|
||||
|
||||
avaibleDate?: Date;
|
||||
runOptions: runOptions;
|
||||
portListening: portListen[];
|
||||
logPath: {stderr: string, stdout: string, merged: string};
|
||||
playerActions: playerAction[];
|
||||
stdoutInterface: readline.Interface;
|
||||
stderrInterface: readline.Interface;
|
||||
|
||||
stopServer(): Promise<{code?: number, signal?: NodeJS.Signals}>;
|
||||
sendCommand(streamPipe: stream.Readable): this;
|
||||
sendCommand(...args: (string|number|boolean)[]): this;
|
||||
hotBackup(): this & Promise<Awaited<ReturnType<runOptions["serverActions"]["hotBackup"]>>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run servers globally and hormonally across servers
|
||||
*/
|
||||
export async function runServer(options: runOptions): Promise<serverRun> {
|
||||
if (!options.stdio) options.stdio = ["pipe", "pipe", "pipe"];
|
||||
const child = child_process.spawn(options.command, [...((options.args ?? []).map(String))], {
|
||||
// maxBuffer: Infinity,
|
||||
stdio: options.stdio,
|
||||
cwd: options.cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...Object.keys(options.env ?? {}).reduce((acc, a) => {
|
||||
acc[a] = String(options.env[a]);
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
}) as serverRun;
|
||||
child.runOptions = options;
|
||||
child.portListening = [];
|
||||
child.playerActions = [];
|
||||
for (const std of [child.stdout, child.stderr]) if (!std) {
|
||||
child.kill("SIGKILL");
|
||||
throw new TypeError("Stdout or Stderr stream disabled, killed process, cannot continue to exec server, check stdio passed to spawn!");
|
||||
}
|
||||
|
||||
// Log Write
|
||||
const currentDate = new Date();
|
||||
const baseLog = path.join(options.paths.logs, format("%s_%s_%s_%s-%s-%s", currentDate.getDate(), currentDate.getMonth()+1, currentDate.getFullYear(), currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds()));
|
||||
await fs.mkdir(baseLog, {recursive: true});
|
||||
child.logPath = {stdout: path.join(baseLog, "stdout.log"), stderr: path.join(baseLog, "stderr.log"), merged: path.join(baseLog, "server.log")};
|
||||
const allLog = createWriteStream(child.logPath.merged);
|
||||
child.stdout.pipe(allLog);
|
||||
child.stdout.pipe(createWriteStream(child.logPath.stdout));
|
||||
child.stderr.pipe(allLog);
|
||||
child.stderr.pipe(createWriteStream(child.logPath.stderr));
|
||||
|
||||
// Lines
|
||||
const stdout = child.stdoutInterface = readline.createInterface(child.stdout).on("line", data => child.emit("line", data, "stdout")).on("error", err => child.emit("error", err));
|
||||
const stderr = child.stderrInterface = readline.createInterface(child.stderr).on("line", data => child.emit("line", data, "stderr")).on("error", err => child.emit("error", err));
|
||||
|
||||
if (typeof options.serverActions?.playerAction === "function") {
|
||||
for (const std of [stdout, stderr]) std.on("line", async data => {
|
||||
const playerData = await Promise.resolve(options.serverActions.playerAction.call(child, data) as ReturnType<typeof options.serverActions.playerAction>);
|
||||
if (!playerData) return;
|
||||
child.playerActions.push(playerData);
|
||||
child.emit("player", playerData);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof options.serverActions?.portListen === "function") {
|
||||
for (const std of [stdout, stderr]) std.on("line", async data => {
|
||||
const portData = await Promise.resolve(options.serverActions.portListen.call(child, data) as ReturnType<typeof options.serverActions.portListen>);
|
||||
if (!portData) return;
|
||||
portData.listenFrom ??= "server";
|
||||
child.portListening.push(portData);
|
||||
child.emit("portListening", portData);
|
||||
});
|
||||
}
|
||||
|
||||
child.sendCommand = function (...args) {
|
||||
if (!child.stdin.writable) {
|
||||
child.emit("error", new Error("cannot send command to server"));
|
||||
return child;
|
||||
};
|
||||
if (args[0] instanceof stream.Readable) {
|
||||
args[0].on("data", data => child.stdin.write(data)).once("close", () => child.stdin.write("\n"));
|
||||
return child;
|
||||
}
|
||||
child.stdin.write(args.map(String).join(" ")+"\n");
|
||||
return child;
|
||||
}
|
||||
|
||||
child.stopServer = async function () {
|
||||
child.sendCommand("");
|
||||
const stop = options.serverActions?.stop ?? function () {
|
||||
child.kill("SIGINT");
|
||||
const kill = setTimeout(() => {
|
||||
clearTimeout(kill);
|
||||
if (child.exitCode !== null) return;
|
||||
child.kill("SIGKILL");
|
||||
}, 2500);
|
||||
};
|
||||
Promise.resolve().then(() => stop.call(child)).catch(err => child.emit("error", err));
|
||||
return new Promise((done, reject) => child.once("error", reject).once("exit", (code, signal) => done({code, signal})));
|
||||
}
|
||||
|
||||
child.hotBackup = function hotBackup() {
|
||||
return Object.assign({}, Promise.resolve().then((async () => {
|
||||
if (!options.serverActions?.hotBackup) throw new Error("Hot backup disabled to current platform!");
|
||||
child.emit("backup", "start");
|
||||
return Promise.resolve(options.serverActions.hotBackup.call(child) as ReturnType<typeof options.serverActions.hotBackup>).then(data => {
|
||||
child.emit("backup", "success");
|
||||
return data;
|
||||
}).catch(err => {
|
||||
child.emit("backup", "fail");
|
||||
return Promise.reject(err);
|
||||
});
|
||||
})), child);
|
||||
}
|
||||
|
||||
if (typeof options.serverActions?.onAvaible === "function") {
|
||||
let run = options.serverActions.onAvaible;
|
||||
for (const std of [stdout, stderr]) std.on("line", async data => {
|
||||
if (!run) return null;
|
||||
const avaibleDate = await Promise.resolve(run.call(child, data) as ReturnType<typeof run>);
|
||||
if (!avaibleDate) return;
|
||||
child.avaibleDate = avaibleDate;
|
||||
if (options.serverActions?.postStart) for (const ss of options.serverActions?.postStart) Promise.resolve().then(() => ss.call(child)).catch(err => child.emit("error", err));
|
||||
});
|
||||
} else if (options.serverActions?.postStart?.length > 0) child.emit("warning", "no post actions run!");
|
||||
|
||||
child.once("close", async () => {
|
||||
const cDate = new Date();
|
||||
const month = String(cDate.getMonth()+1 > 9 ? cDate.getMonth()+1 : "0"+(cDate.getMonth()+1).toString());
|
||||
const day = String(cDate.getDate() > 9 ? cDate.getDate() : "0"+((cDate.getDate()).toString()));
|
||||
const backupFile = path.join(options.paths.backup, String(cDate.getFullYear()), month, day, `${cDate.getHours()}_${cDate.getMinutes()}.tgz`);
|
||||
try {
|
||||
if (!(await extendsFS.exists(path.dirname(backupFile)))) await fs.mkdir(path.dirname(backupFile), {recursive: true});
|
||||
const ff = await fs.readdir(options.paths.serverFolder);
|
||||
await pipeline(tar.create({
|
||||
gzip: true,
|
||||
cwd: options.paths.serverFolder,
|
||||
prefix: ""
|
||||
}, ff), createWriteStream(backupFile));
|
||||
child.emit("backup", backupFile);
|
||||
} catch (err) {
|
||||
if (await extendsFS.exists(backupFile)) await fs.unlink(backupFile);
|
||||
child.emit("error", err);
|
||||
}
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
70
packages/core/src/serverRun.ts
Normal file
70
packages/core/src/serverRun.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import EventEmitter from "node:events";
|
||||
|
||||
export type EventMap = Record<string, (...args: any[]) => void>;
|
||||
export type defineEvents<T extends EventMap> = T;
|
||||
type EventKey<T extends EventMap> = string & keyof T;
|
||||
|
||||
export interface customEvent<T extends EventMap> extends EventEmitter {
|
||||
emit<K extends EventKey<T>>(eventName: K, ...args: Parameters<T[K]>): boolean;
|
||||
emit(name: "error", err: Error): boolean;
|
||||
|
||||
on<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
on(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
prependListener<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
prependListener(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
once<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
once(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
prependOnceListener<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
prependOnceListener(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
removeListener<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
removeListener(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
off<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
|
||||
off(eventName: "error", fn: (err: Error) => void): this;
|
||||
|
||||
removeAllListeners<K extends EventKey<T>>(eventName: K): this;
|
||||
removeAllListeners(eventName: "error"): this;
|
||||
|
||||
rawListeners<K extends EventKey<T>>(eventName: K): (T[K])[];
|
||||
rawListeners(eventName: "error"): ((err: Error) => void)[];
|
||||
|
||||
eventNames(): (EventKey<T> | "error")[];
|
||||
}
|
||||
|
||||
export class customEvent<T extends EventMap> extends EventEmitter {
|
||||
constructor() {
|
||||
super({captureRejections: true});
|
||||
}
|
||||
};
|
||||
|
||||
export class versionsStorages<T> extends Map<string, T> {
|
||||
constructor(origem: Record<string, T> = {}) {
|
||||
super(Object.keys(origem).map(key => ([key, origem[key]])));
|
||||
}
|
||||
|
||||
prettyVersion(serverVersion: string|number): string {
|
||||
const checkIsNumber = (arg0: string|number) => typeof arg0 === "number" ? arg0 : Number(arg0).toString() === arg0 ? Number(arg0) : arg0;
|
||||
serverVersion = checkIsNumber(serverVersion);
|
||||
if (typeof serverVersion === "number") return Array.from(this.keys()).at(serverVersion);
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
get(serverVersion: string|number): T {
|
||||
return super.get(this.prettyVersion(serverVersion));
|
||||
}
|
||||
|
||||
has(serverVersion: string|number): boolean {
|
||||
return super.has(this.prettyVersion(serverVersion));
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Array.from(super.keys()).reduce<{[version: string]: T}>((acc, key) => {
|
||||
acc[key] = super.get(key);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
@ -1,388 +0,0 @@
|
||||
import fsOld, { promises as fs } from "node:fs";
|
||||
import coreHttp, { Github } from "@sirherobrine23/http";
|
||||
import { runOptions, serverManegerV1 } from "../serverManeger.js";
|
||||
import { oracleStorage } from "../internal.js";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { Readable } from "node:stream";
|
||||
import extendsFS, { promiseChildProcess } from "@sirherobrine23/extends";
|
||||
import semver from "semver";
|
||||
import unzip from "unzipper";
|
||||
import utils from "node:util";
|
||||
import path from "node:path";
|
||||
import tar from "tar";
|
||||
|
||||
export interface bedrockOptions {
|
||||
/**
|
||||
* Alternative server instead of official Mojang server
|
||||
*/
|
||||
altServer?: "mojang"|"pocketmine"|"powernukkit"|"nukkit"|"cloudbust",
|
||||
};
|
||||
|
||||
const pocketmineGithub = await Github.repositoryManeger("pmmp", "PocketMine-MP");
|
||||
export type bedrockList = {
|
||||
date: Date,
|
||||
version: string,
|
||||
release: "preview"|"stable",
|
||||
downloads: {
|
||||
php?: {
|
||||
installPHP(serverPath: serverManegerV1): Promise<void>,
|
||||
},
|
||||
server: {
|
||||
getServer(): Promise<Readable>,
|
||||
url?: string,
|
||||
urls?: {
|
||||
[platform in NodeJS.Platform]?: {
|
||||
[arch in NodeJS.Architecture]?: string
|
||||
}
|
||||
}
|
||||
[K: string]: any
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* List Minecrft bedrock server versions
|
||||
*
|
||||
* @param altServer - Alternative server of official Mojang
|
||||
* @returns
|
||||
*/
|
||||
export async function listVersions(altServer?: bedrockOptions["altServer"]): Promise<bedrockList[]> {
|
||||
if (!altServer) altServer = "mojang";
|
||||
if (altServer) if (!(["mojang", "cloudbust", "cloudbust", "nukkit", "pocketmine", "powernukkit"]).includes(altServer)) throw new TypeError("Invalid alt server");
|
||||
if (altServer === "pocketmine") {
|
||||
return (await pocketmineGithub.release.getRelease()).filter(rel => (rel.assets.find(assert => assert.name.endsWith(".phar")) ?? {}).browser_download_url).map(rel => ({
|
||||
date: new Date(rel.created_at),
|
||||
version: rel.tag_name,
|
||||
release: rel.prerelease ? "preview" : "stable",
|
||||
downloads: {
|
||||
php: {
|
||||
async installPHP(serverPath: serverManegerV1) {
|
||||
const phpFile = (await oracleStorage.listFiles("php_bin")).find(file => file.name.includes(process.platform) && file.name.includes(process.arch));
|
||||
if (!phpFile) throw new Error(`Unable to find php files for ${process.platform} with architecture ${process.arch}`);
|
||||
if (phpFile.name.endsWith(".tar.gz")||phpFile.name.endsWith(".tgz")||phpFile.name.endsWith(".tar")) await pipeline(oracleStorage.getFile(phpFile.name), tar.extract({cwd: serverPath.serverFolder}));
|
||||
else if (phpFile.name.endsWith(".zip")) await pipeline(oracleStorage.getFile(phpFile.name), unzip.Extract({path: serverPath.serverFolder}));
|
||||
else throw new Error("Found file is not supported!");
|
||||
return null
|
||||
},
|
||||
},
|
||||
server: {
|
||||
url: (rel.assets.find(assert => assert.name.endsWith(".phar")) ?? {}).browser_download_url,
|
||||
async getServer() {
|
||||
const pharFile = rel.assets.find(assert => assert.name.endsWith(".phar"));
|
||||
if (!pharFile) throw new Error("Version not includes server file!");
|
||||
return coreHttp.streamRequest(pharFile.browser_download_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (altServer === "powernukkit") {
|
||||
const releases_version = (await coreHttp.jsonRequest<{[k: string]: {version: string, releaseTime: number, minecraftVersion: string, artefacts: string[], commitId: string, snapshotBuild?: number}[]}>("https://raw.githubusercontent.com/PowerNukkit/powernukkit-version-aggregator/master/powernukkit-versions.json")).body;
|
||||
return Object.keys(releases_version).reduce((acc, key) => acc.concat(releases_version[key]), [] as (typeof releases_version)[string]).map(data => {
|
||||
const dt = new Date(data.releaseTime);
|
||||
const getArtefactExtension = (artefactId: string) => (artefactId.includes("REDUCED_JAR")) ? ".jar" : (artefactId.includes("REDUCED_SOURCES_JAR")) ? "-sources.jar" : (artefactId.includes("SHADED_JAR")) ? "-shaded.jar" : (artefactId.includes("SHADED_SOURCES_JAR")) ? "-shaded-sources.jar" : (artefactId.includes("JAVADOC_JAR")) ? "-javadoc.jar" : ".unknown";
|
||||
function buildArtefactUrl(data: any, artefactId?: string) {
|
||||
const buildReleaseArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : utils.format("https://search.maven.org/remotecontent?filepath=org/powernukkit/powernukkit/%s/powernukkit-%s%s", data.version, data.version, getArtefactExtension(artefactId));
|
||||
const buildSnapshotArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : utils.format("https://oss.sonatype.org/content/repositories/snapshots/org/powernukkit/powernukkit/%s-SNAPSHOT/powernukkit-%s-%s%s", data.version.substring(0, data.version.indexOf("-SNAPSHOT")), data.version.substring(0, data.version.indexOf("-SNAPSHOT")), dt.getUTCFullYear().toString().padStart(4, "0") + (dt.getUTCMonth() + 1).toString().padStart(2, "0") + dt.getUTCDate().toString().padStart(2, "0") + "." + dt.getUTCHours().toString().padStart(2, "0") + dt.getUTCMinutes().toString().padStart(2, "0") + dt.getUTCSeconds().toString().padStart(2, "0") + "-" + data.snapshotBuild, getArtefactExtension(artefactId));
|
||||
if (artefactId == "GIT_SOURCE") {
|
||||
if (data.commitId) return utils.format("https://github.com/PowerNukkit/PowerNukkit/tree/%s", data.commitId);
|
||||
else if (data.snapshotBuild && data.artefacts.includes("SHADED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "SHADED_SOURCES_JAR");
|
||||
else if (data.snapshotBuild && data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "REDUCED_SOURCES_JAR");
|
||||
else if (data.artefacts.includes("SHADED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "SHADED_SOURCES_JAR");
|
||||
else if (data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "REDUCED_SOURCES_JAR");
|
||||
} else if (data.snapshotBuild) return buildSnapshotArtefactUrl(data, artefactId);
|
||||
else return buildReleaseArtefactUrl(data, artefactId);
|
||||
return null;
|
||||
}
|
||||
const artefacts = data.artefacts.reduce((acc, artefactId) => {acc[artefactId] = buildArtefactUrl(data, artefactId); return acc;}, {} as {[key: string]: string});
|
||||
return {
|
||||
date: dt,
|
||||
version: data.version,
|
||||
release: data.snapshotBuild ? "stable" : "preview",
|
||||
downloads: {
|
||||
server: {
|
||||
mcpeVersion: data.minecraftVersion,
|
||||
url: artefacts.SHADED_JAR || artefacts.REDUCED_JAR,
|
||||
async getServer() {
|
||||
if (!(artefacts.SHADED_JAR || artefacts.REDUCED_JAR)) throw new Error("Cannot get server file to the version!");
|
||||
return coreHttp.streamRequest(artefacts.SHADED_JAR || artefacts.REDUCED_JAR)
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
} else if (altServer === "cloudbust"||altServer === "nukkit") {
|
||||
const { body: { jobs } } = await coreHttp.jsonRequest<{jobs: {name: string, _class: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${altServer === "nukkit" ? "Nukkit" : "Server"}/api/json`);
|
||||
const buildFiles = await Promise.all(jobs.filter(b => b._class === "org.jenkinsci.plugins.workflow.job.WorkflowJob").map(b => b.name).map(async branch => {
|
||||
const { body: { builds } } = await coreHttp.jsonRequest<{builds: {_class: string, number: number, url: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${altServer === "nukkit" ? "Nukkit" : "Server"}/job/${branch}/api/json`);
|
||||
return Promise.all(builds.map(async build => {
|
||||
const { body: { artifacts, result, timestamp } } = await coreHttp.jsonRequest<{result: "SUCCESS", timestamp: number, artifacts: {displayPath: string, fileName: string, relativePath: string}[]}>(`https://ci.opencollab.dev/job/NukkitX/job/${altServer === "nukkit" ? "Nukkit" : "Server"}/job/${branch}/${build.number}/api/json`);
|
||||
if (result !== "SUCCESS") return [];
|
||||
return artifacts.filter(f => f.relativePath.endsWith(".jar")).map(target => ({
|
||||
buildNumber: build.number,
|
||||
branch,
|
||||
releaseDate: new Date(timestamp),
|
||||
url: `https://ci.opencollab.dev/job/NukkitX/job/${altServer === "nukkit" ? "Nukkit" : "Server"}/job/${branch}/${build.number}/artifact/${target.relativePath}`,
|
||||
}));
|
||||
}));
|
||||
})).then(r => r.flat(2));
|
||||
return buildFiles.sort((b, a) => a.releaseDate.getTime() - b.releaseDate.getTime()).map(rel => ({
|
||||
date: rel.releaseDate,
|
||||
release: "preview",
|
||||
version: `${rel.branch}_${rel.buildNumber}`,
|
||||
downloads: {
|
||||
server: {
|
||||
url: rel.url,
|
||||
async getServer() {
|
||||
return coreHttp.streamRequest(rel.url);
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (altServer === "mojang") {
|
||||
|
||||
return (await coreHttp.jsonRequest<{version: string, date: Date, release?: "stable"|"preview", url: {[platform in NodeJS.Platform]?: {[arch in NodeJS.Architecture]?: string}}}[]>("https://sirherobrine23.github.io/BedrockFetch/all.json")).body.sort((b, a) => semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version)))).map(rel => ({
|
||||
version: rel.version,
|
||||
date: new Date(rel.date),
|
||||
release: rel.release === "preview" ? "preview" : "stable",
|
||||
downloads: {
|
||||
server: {
|
||||
url: rel.url[process.platform]?.[process.arch],
|
||||
async getServer() {
|
||||
const platformURL = (rel.url[process.platform] ?? rel.url["linux"]);
|
||||
if (!platformURL) throw new Error("Cannot get platform URL");
|
||||
const arch = platformURL[process.arch] ?? platformURL["x64"];
|
||||
if (!arch) throw new Error("Cannot get bedrock server to current arch");
|
||||
return coreHttp.streamRequest(arch);
|
||||
},
|
||||
urls: rel.url
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else throw new Error("Invalid platform");
|
||||
}
|
||||
|
||||
export async function installServer(serverPath: serverManegerV1, options: bedrockOptions & {version?: string, allowBeta?: boolean}) {
|
||||
const versions = await listVersions(options?.altServer);
|
||||
if (!options.altServer) options.altServer = "mojang";
|
||||
if (options.altServer === "pocketmine") {
|
||||
const rel = options.version === "latest" ? versions.at(0) : versions.find(rel => rel.version === options.version);
|
||||
if (!rel) throw new Error("Version not exsists");
|
||||
await rel.downloads.php.installPHP(serverPath);
|
||||
await pipeline(await rel.downloads.server.getServer(), fsOld.createWriteStream(path.join(serverPath.serverFolder, "server.phar")));
|
||||
return {
|
||||
...rel,
|
||||
id: serverPath.id,
|
||||
};
|
||||
} else if (options.altServer === "cloudbust" || options.altServer === "powernukkit" || options.altServer === "nukkit") {
|
||||
if ((["cloudbust", "nukkit"]).includes(options.altServer)) options.version = "latest";
|
||||
const rel = options.version === "latest" ? versions.at(0) : versions.find(rel => rel.version === options.version);
|
||||
if (!rel) throw new Error("Version not exists");
|
||||
await pipeline(await rel.downloads.server.getServer(), fsOld.createWriteStream(path.join(serverPath.serverFolder, "server.jar")));
|
||||
return {
|
||||
...rel,
|
||||
id: serverPath.id,
|
||||
};
|
||||
} else if (options.altServer === "mojang") {
|
||||
const bedrockVersion = versions.find(rel => {
|
||||
if (rel.release === "preview") if (options.allowBeta !== true) return false;
|
||||
const version = (options.version ?? "latest").trim();
|
||||
if (version.toLowerCase() === "latest") return true;
|
||||
return rel.version === version;
|
||||
});
|
||||
if (!bedrockVersion) throw new Error("Não existe essa versão");
|
||||
let downloadUrl = bedrockVersion.downloads.server.url;
|
||||
if ((["android", "linux"] as NodeJS.Process["platform"][]).includes(process.platform) && process.arch !== "x64") {
|
||||
if (!downloadUrl) {
|
||||
for (const emu of ["qemu-x86_64-static", "qemu-x86_64", "box64"]) {
|
||||
if (downloadUrl) break;
|
||||
if (await promiseChildProcess.commandExists(emu)) downloadUrl = bedrockVersion.downloads.server.urls.linux?.x64;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!downloadUrl) throw new Error(`Não existe o URL de download para ${process.platform} na arquitetura ${process.arch}`);
|
||||
|
||||
const filesBackup = ["server.properties", "valid_known_packs.json", "permissions.json", "allowlist.json", "whitelist.json"];
|
||||
const datS = (await Promise.all(filesBackup.map(async f => !await extendsFS.exists(path.join(serverPath.serverFolder, f)) ? null : ({path: f, data: await fs.readFile(path.join(serverPath.serverFolder, f))})))).filter(a => !!a);
|
||||
await pipeline(await coreHttp.streamRequest(downloadUrl), unzip.Extract({path: serverPath.serverFolder}));
|
||||
await Promise.all(datS.map(async f => fs.writeFile(f.path, f.data)));
|
||||
return {
|
||||
...bedrockVersion,
|
||||
id: serverPath.id,
|
||||
};
|
||||
} else throw new Error("Invalid platform");
|
||||
}
|
||||
|
||||
export async function startServer(maneger: serverManegerV1, options: bedrockOptions) {
|
||||
if (!options.altServer) options.altServer = "mojang";
|
||||
if (options.altServer === "powernukkit"||options.altServer === "cloudbust") {
|
||||
return maneger.runCommand({
|
||||
command: "java",
|
||||
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",
|
||||
"-jar", "server.jar",
|
||||
],
|
||||
paths: maneger,
|
||||
serverActions: {
|
||||
stop() {
|
||||
this.sendCommand("stop");
|
||||
},
|
||||
}
|
||||
})
|
||||
} else if (options.altServer === "pocketmine") {
|
||||
return maneger.runCommand({
|
||||
command: (await extendsFS.readdir(maneger.serverFolder)).find(file => file.endsWith("php")||file.endsWith("php.exe")),
|
||||
args: [
|
||||
"server.phar",
|
||||
"--no-wizard"
|
||||
],
|
||||
paths: maneger,
|
||||
serverActions: {
|
||||
stop() {
|
||||
this.sendCommand("stop")
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
if (process.platform === "darwin") throw new Error("Run in docker or podman!");
|
||||
const run: Omit<runOptions, "cwd"> = {
|
||||
command: path.join(maneger.serverFolder, "bedrock_server"+(process.platform === "win32" ? ".exe" : "")),
|
||||
paths: maneger,
|
||||
serverActions: {
|
||||
stop() {
|
||||
this.sendCommand("stop");
|
||||
},
|
||||
portListen(lineString) {
|
||||
// [INFO] IPv4 supported, port: 19132
|
||||
// [2023-03-08 13:01:57 INFO] Listening on IPv4 port: 19132
|
||||
const ipProtocol = lineString.slice(lineString.indexOf("IPv"), lineString.indexOf("IPv")+4);
|
||||
if (ipProtocol) {
|
||||
let port = lineString.slice(lineString.lastIndexOf("port:")+5).trim();
|
||||
if (port.indexOf(":") !== -1) port = port.slice(0, port.lastIndexOf(":"));
|
||||
return {
|
||||
protocol: "UDP",
|
||||
listenOn: ipProtocol.toLowerCase() === "ipv4" ? "0.0.0.0" : "[::]",
|
||||
port: Number(port),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
playerAction(lineString) {
|
||||
lineString = lineString.replace(/^(.*)?\[.*\]/, "").trim();
|
||||
if (lineString.startsWith("Player")) {
|
||||
lineString = lineString.replace("Player", "").trim();
|
||||
|
||||
// Spawned, disconnected, connected
|
||||
let action: string;
|
||||
if (lineString.startsWith("Spawned")) action = "Spawned";
|
||||
else if (lineString.startsWith("disconnected")) action = "disconnected";
|
||||
else if (lineString.startsWith("connected")) action = "connected";
|
||||
if (!action) return null;
|
||||
lineString = lineString.replace(action, "").trim();
|
||||
if (lineString.startsWith(":")) lineString = lineString.slice(1).trim();
|
||||
|
||||
let playerName = lineString.substring(0, lineString.indexOf("xuid:")-1).trim();
|
||||
if (!playerName) return null;
|
||||
if (playerName.endsWith(",")) playerName = playerName.slice(0, playerName.length - 1);
|
||||
|
||||
let xuid: string;
|
||||
if (lineString.indexOf("xuid:") !== -1) {
|
||||
xuid = lineString.slice(lineString.indexOf("xuid:")+5).trim();
|
||||
if (!xuid) xuid = null;
|
||||
}
|
||||
|
||||
return {
|
||||
onDate: new Date(),
|
||||
action,
|
||||
playerName,
|
||||
extra: {
|
||||
xuid
|
||||
}
|
||||
};
|
||||
};
|
||||
return null;
|
||||
},
|
||||
onAvaible(data) {
|
||||
// [2023-03-06 21:37:27:699 INFO] Server started.
|
||||
data = data.replace(/^.*?\[.*\]/, "").trim();
|
||||
if (data.includes("started") && data.includes("Server")) return new Date();
|
||||
return null
|
||||
},
|
||||
async hotBackup() {
|
||||
const ff = (await fs.readdir(this.runOptions.paths.serverFolder)).filter(ff => {
|
||||
let ok = ff.endsWith(".json");
|
||||
if (!ok) ok = ff === "server.properties";
|
||||
if (!ok) ok = ff === "worlds";
|
||||
return ok;
|
||||
});
|
||||
return tar.create({
|
||||
gzip: true,
|
||||
cwd: this.runOptions.paths.serverFolder,
|
||||
prefix: ""
|
||||
}, ff);
|
||||
},
|
||||
postStart: [
|
||||
async function() {
|
||||
let breaked = false;
|
||||
this.once("close", () => breaked = true);
|
||||
let w: any;
|
||||
this.once("close", () => w.close());
|
||||
while(true) {
|
||||
if (breaked) break;
|
||||
await new Promise(done => {
|
||||
this.once("close", done);
|
||||
w = fsOld.watch(this.runOptions.paths.serverFolder, {recursive: true}, () => {w.close(); done(null);}).on("error", () => {});
|
||||
});
|
||||
await new Promise(done => setTimeout(done, 1000));
|
||||
const cDate = new Date();
|
||||
const month = String(cDate.getMonth()+1 > 9 ? cDate.getMonth()+1 : "0"+(cDate.getMonth()+1).toString());
|
||||
const day = String(cDate.getDate() > 9 ? cDate.getDate() : "0"+((cDate.getDate()).toString()));
|
||||
const backupFile = path.join(this.runOptions.paths.backup, "hotBackup", String(cDate.getFullYear()), month, day, `${cDate.getHours()}_${cDate.getMinutes()}.tgz`);
|
||||
if (!(await extendsFS.exists(path.dirname(backupFile)))) await fs.mkdir(path.dirname(backupFile), {recursive: true});
|
||||
const ff = (await fs.readdir(this.runOptions.paths.serverFolder)).filter(ff => {
|
||||
let ok = ff.endsWith(".json");
|
||||
if (!ok) ok = ff === "server.properties";
|
||||
if (!ok) ok = ff === "worlds";
|
||||
return ok;
|
||||
});
|
||||
const hotTar = tar.create({
|
||||
gzip: true,
|
||||
cwd: this.runOptions.paths.serverFolder,
|
||||
prefix: ""
|
||||
}, ff);
|
||||
this.emit("hotBackup", hotTar);
|
||||
await pipeline(hotTar, fsOld.createWriteStream(backupFile));
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
if ((["android", "linux"] as NodeJS.Process["platform"][]).includes(process.platform) && process.arch !== "x64") {
|
||||
for (const emu of ["qemu-x86_64-static", "qemu-x86_64", "box64"]) {
|
||||
if (await promiseChildProcess.commandExists(emu)) {
|
||||
run.args = [run.command, ...run.args];
|
||||
run.command = emu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maneger.runCommand(run);
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
import { serverManegerV1 } from "../serverManeger.js";
|
||||
import { oracleStorage } from "../internal.js";
|
||||
import { extendsFS } from "@sirherobrine23/extends";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import coreHttp, { Github } from "@sirherobrine23/http";
|
||||
import semver from "semver";
|
||||
import stream from "node:stream";
|
||||
import utils from "node:util";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
export interface javaOptions {
|
||||
/**
|
||||
* Alternative server instead of official Mojang server
|
||||
*/
|
||||
altServer?: "mojang"|"spigot"|"paper"|"purpur"|"glowstone"|"folia"|"cuberite"
|
||||
};
|
||||
|
||||
export type javaList = {
|
||||
version: string,
|
||||
release: "stable"|"snapshot",
|
||||
date?: Date,
|
||||
platform?: NodeJS.Platform;
|
||||
architecture?: NodeJS.Architecture;
|
||||
getFile: {
|
||||
fileName: string;
|
||||
stream(): Promise<stream.Readable>;
|
||||
fileURL?: string;
|
||||
}[],
|
||||
};
|
||||
|
||||
export async function listVersions(altServer?: javaOptions["altServer"]): Promise<javaList[]> {
|
||||
if (!altServer) altServer = "mojang";
|
||||
if (altServer) if(!(["mojang", "paper", "folia", "purpur", "spigot", "glowstone", "cuberite"]).includes(altServer)) throw new TypeError("Invalid alt server!");
|
||||
if (altServer === "purpur") {
|
||||
return (await Promise.all((await coreHttp.jsonRequest<{versions: string[]}>("https://api.purpurmc.org/v2/purpur")).body.versions.map(async (version): Promise<javaList> => ({
|
||||
version,
|
||||
release: "stable",
|
||||
date: new Date((await coreHttp.jsonRequest<{timestamp: number}>(utils.format("https://api.purpurmc.org/v2/purpur/%s/latest", version))).body.timestamp),
|
||||
getFile: [
|
||||
{
|
||||
fileName: "server.jar",
|
||||
fileURL: utils.format("https://api.purpurmc.org/v2/purpur/%s/latest/download", version),
|
||||
async stream() {return coreHttp.streamRequest(utils.format("https://api.purpurmc.org/v2/purpur/%s/latest/download", version));},
|
||||
}
|
||||
],
|
||||
})))).sort((b, a) => semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version))));
|
||||
} else if (altServer === "paper" || altServer === "folia") {
|
||||
const uriPATH = ["/v2/projects", altServer];
|
||||
return (await Promise.all((await coreHttp.jsonRequest<{versions: string[]}>(new URL(uriPATH.join("/"), "https://api.papermc.io"))).body.versions.map(async (version): Promise<javaList> => {
|
||||
const build = (await coreHttp.jsonRequest<{builds: number[]}>(new URL(uriPATH.concat(["versions", version]).join("/"), "https://api.papermc.io"))).body.builds.at(-1);
|
||||
const data = (await coreHttp.jsonRequest<{time: string, downloads: {[k: string]: {name: string, sha256: string}}}>(new URL(uriPATH.concat(["versions", version, "builds", build.toString()]).join("/"), "https://api.papermc.io"))).body;
|
||||
const fileUrl = new URL(uriPATH.concat(["versions", version, "builds", build.toString(), "downloads", data.downloads["application"].name]).join("/"), "https://api.papermc.io");
|
||||
return {
|
||||
version,
|
||||
date: new Date(data.time),
|
||||
release: "stable",
|
||||
getFile: [{
|
||||
fileURL: fileUrl.toString(),
|
||||
fileName: "server.jar",
|
||||
async stream() {return coreHttp.streamRequest(fileUrl);}
|
||||
}]
|
||||
}
|
||||
}))).sort((b, a) => semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version))));
|
||||
} else if (altServer === "spigot") {
|
||||
const fileList = await oracleStorage.listFiles("SpigotBuild");
|
||||
return fileList.filter(f => f.name.endsWith(".jar") && !f.name.includes("craftbukkit-")).map((file): javaList => {
|
||||
let version: string;
|
||||
if (!(version = semver.valid(semver.coerce(file.name.replace("SpigotBuild/", "").replace(".jar", ""))))) return null;
|
||||
const craftBuckit = ([...fileList]).reverse().find(file => file.name.startsWith("SpigotBuild/craftbukkit-"+version) || file.name.startsWith("SpigotBuild/craftbukkit-"+(version.endsWith(".0") ? version.slice(0, -2) : version)));
|
||||
return {
|
||||
release: "stable",
|
||||
version,
|
||||
getFile: [
|
||||
...(craftBuckit ? [{
|
||||
fileName: path.basename(craftBuckit.name),
|
||||
async stream() {
|
||||
return craftBuckit.getFile();
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
fileName: "server.jar",
|
||||
async stream() {
|
||||
return file.getFile();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}).filter(rel => !!rel).sort((b, a) => semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version))));
|
||||
} else if (altServer === "glowstone") {
|
||||
const repo = await Github.repositoryManeger("GlowstoneMC", "Glowstone");
|
||||
const rels = await repo.release.getRelease();
|
||||
return rels.filter(rel => !!rel.assets.find(asset => asset.name.endsWith(".jar"))).map(rel => ({
|
||||
version: rel.tag_name,
|
||||
release: "stable",
|
||||
fileUrl: rel.assets.find(asset => asset.name.endsWith(".jar")).browser_download_url,
|
||||
getFile: [{
|
||||
fileURL: rel.assets.find(asset => asset.name.endsWith(".jar")).browser_download_url,
|
||||
fileName: "server.jar",
|
||||
async stream() {
|
||||
return coreHttp.streamRequest(rel.assets.find(asset => asset.name.endsWith(".jar")).browser_download_url);
|
||||
},
|
||||
}]
|
||||
}));
|
||||
} else if (altServer === "cuberite") {
|
||||
const buildFiles: {buildNumber: number; project: string; releaseDate: Date; url: string;}[] = [];
|
||||
for (const project of ["android", "linux-aarch64", "linux-armhf", "linux-i386", "linux-x86_64", "darwin-x86_64"]) {
|
||||
const { builds = [] } = await coreHttp.jsonRequestBody<{builds: {number: number, _class: string}[]}>(`https://builds.cuberite.org/job/${project}/api/json`);
|
||||
await Promise.all(builds.slice(0, 16).map(async job => {
|
||||
const { artifacts = [], result, timestamp } = await coreHttp.jsonRequestBody<{timestamp: number, result: string, artifacts: {relativePath: string, fileName: string}[]}>(`https://builds.cuberite.org/job/${project}/${job.number}/api/json`);
|
||||
if (result !== "SUCCESS") return;
|
||||
artifacts.filter(file => !file.fileName.endsWith(".sha1")).forEach(file => buildFiles.push({
|
||||
project,
|
||||
url: `https://builds.cuberite.org/job/${project}/${job.number}/artifact/${file.relativePath}`,
|
||||
buildNumber: job.number,
|
||||
releaseDate: new Date(timestamp),
|
||||
}));
|
||||
}));
|
||||
}
|
||||
return buildFiles.concat([
|
||||
{
|
||||
buildNumber: -1,
|
||||
project: "windows-x86_64",
|
||||
releaseDate: new Date(),
|
||||
url: "https://download.cuberite.org/windows-x86_64/Cuberite.zip",
|
||||
},
|
||||
{
|
||||
buildNumber: -1,
|
||||
project: "windows-x86",
|
||||
releaseDate: new Date(),
|
||||
url: "https://download.cuberite.org/windows-i386/Cuberite.zip",
|
||||
},
|
||||
]).sort((b, a) => a.releaseDate.getTime() - b.releaseDate.getTime()).map(rel => ({
|
||||
date: rel.releaseDate,
|
||||
version: `${rel.project}_${rel.buildNumber}`,
|
||||
platform: rel.project.startsWith("windows") ? "win32" : rel.project.startsWith("linux") ? "linux" : rel.project.startsWith("android") ? "android" : rel.project.startsWith("darwin") ? "darwin" : undefined,
|
||||
architecture: rel.project.endsWith("x86_64") ? "x64" : rel.project.endsWith("i386") ? "ia32" : rel.project.endsWith("armhf") ? "arm" : rel.project.endsWith("aarch64") ? "arm64" : undefined,
|
||||
release: "stable",
|
||||
fileUrl: rel.url,
|
||||
getFile: [{
|
||||
fileName: "server.jar",
|
||||
async stream() {
|
||||
return coreHttp.streamRequest(rel.url);
|
||||
},
|
||||
}]
|
||||
}));
|
||||
} else if (altServer === "mojang") {
|
||||
return (await Promise.all((await coreHttp.jsonRequest<{versions: {id: string, releaseTime: string, url: string, type: "snapshot"|"release"}[]}>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json")).body.versions.map(async (data): Promise<javaList> => {
|
||||
const fileURL = (await coreHttp.jsonRequest<{downloads: {[k: string]: {size: number, url: string}}}>(data.url)).body.downloads?.["server"]?.url;
|
||||
if (!fileURL) return null;
|
||||
return {
|
||||
version: data.id,
|
||||
date: new Date(data.releaseTime),
|
||||
release: data.type === "snapshot" ? "snapshot" : "stable",
|
||||
getFile: [{
|
||||
fileName: "server.jar",
|
||||
async stream() {
|
||||
return coreHttp.streamRequest(fileURL);
|
||||
},
|
||||
}],
|
||||
};
|
||||
}))).filter(a => !!a);
|
||||
} else throw new Error("Invalid platform");
|
||||
}
|
||||
|
||||
export async function installServer(serverPath: serverManegerV1, options: javaOptions & {version?: string, allowBeta?: boolean}) {
|
||||
if (!options.altServer) options.altServer = "mojang";
|
||||
const version = (await listVersions(options.altServer)).filter(rel => rel.release === "stable" ? true : !!options.allowBeta).find(rel => (!options.version || options.version === "latest" || rel.version === options.version));
|
||||
if (!version) throw new Error("The specified version does not exist!");
|
||||
for (const file of version.getFile) await pipeline(await file.stream(), fs.createWriteStream(path.join(serverPath.serverFolder, file.fileName)));
|
||||
await fs.promises.writeFile(path.join(serverPath.serverFolder, "eula.txt"), "eula=true\n");
|
||||
return {
|
||||
id: serverPath.id,
|
||||
version: version.version,
|
||||
release: version.release,
|
||||
date: version.date,
|
||||
};
|
||||
}
|
||||
|
||||
export async function startServer(serverPath: serverManegerV1, options: javaOptions) {
|
||||
if (!options.altServer) options.altServer = "mojang";
|
||||
// Java server
|
||||
if (await extendsFS.exists(path.join(serverPath.serverFolder, "server.jar"))) {
|
||||
return serverPath.runCommand({
|
||||
command: "java",
|
||||
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",
|
||||
"-jar", "server.jar",
|
||||
"--nogui",
|
||||
// Save world in worlds folder
|
||||
"--universe", "worlds",
|
||||
// ...extraArgs,
|
||||
],
|
||||
paths: serverPath,
|
||||
serverActions: {
|
||||
stop() {
|
||||
this.sendCommand("stop");
|
||||
},
|
||||
portListen(lineString) {
|
||||
// [21:19:18] [Server thread/INFO]: Starting Minecraft server on *:25565
|
||||
lineString = lineString.replace(/^.*?\[.*\]:/, "").trim();
|
||||
if (lineString.startsWith("Starting Minecraft server on")) {
|
||||
if (lineString.lastIndexOf(":") === -1) return null;
|
||||
const port = Number(lineString.slice(lineString.lastIndexOf(":")+1));
|
||||
return {
|
||||
port,
|
||||
protocol: "TCP",
|
||||
listenFrom: "server"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("install server or check if server installed correctly!");
|
||||
}
|
12
packages/core/tests/bedrock.ts
Normal file
12
packages/core/tests/bedrock.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import path from "node:path";
|
||||
import { listVersion, Bedrock } from "../src/platform/bedrock/index.js";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
const server = new Bedrock(path.join(homedir(), ".bdsmaneger/playgroud/mojang"), "pocketmine");
|
||||
server.once("installedVersion", version => console.log("Installed %s server", version));
|
||||
await listVersion.listPocketmineProject();
|
||||
console.log("Init install");
|
||||
await server.installServer(0);
|
||||
server.on("logLine", (line) => console.log(line[0]));
|
||||
const pr = await server.runServer();
|
||||
process.stdin.pipe(pr.stdin);
|
11
packages/core/tests/java.ts
Normal file
11
packages/core/tests/java.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import path from "node:path";
|
||||
import { Java } from "../src/platform/java/index.js";
|
||||
import { homedir } from "node:os";
|
||||
import { rm } from "node:fs/promises";
|
||||
|
||||
await rm(path.join(homedir(), ".bdsmaneger/playgroud/java"), { recursive: true }).catch(() => {});
|
||||
const server = new Java(path.join(homedir(), ".bdsmaneger/playgroud/java"), "cuberite");
|
||||
await server.installServer(0);
|
||||
server.on("logLine", line => console.log(line));
|
||||
const run = await server.runServer();
|
||||
process.stdin.pipe(run.stdin);
|
7
packages/verapi/.dockerignore
Normal file
7
packages/verapi/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Typescript
|
||||
**/*.js
|
||||
**/*.d.ts
|
||||
**/*.tsbuildinfo
|
@ -1,129 +1,182 @@
|
||||
#!/usr/bin/env node
|
||||
import { Java, Bedrock } from "@the-bds-maneger/core";
|
||||
import { createServer } from "node:http";
|
||||
import { Bedrock, Java } from "@the-bds-maneger/core";
|
||||
import express from "express";
|
||||
import expressLayer from "express/lib/router/layer.js";
|
||||
import { createServer } from "node:http";
|
||||
import { format } from "node:util";
|
||||
|
||||
process.on("unhandledRejection", err => console.error(err));
|
||||
|
||||
expressLayer.prototype.handle_request = async function handle_request_promised(...args) {
|
||||
var fn = this.handle;
|
||||
if (fn.length > 3) return args.at(-1)();
|
||||
await Promise.resolve().then(() => fn.call(this, ...args)).catch(args.at(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} nextTime
|
||||
*/
|
||||
function printDate(nextTime = 0) {
|
||||
const dd = new Date(Date.now() + nextTime);
|
||||
return format("%f/%f/%f %f:%f:%f", dd.getDate(), dd.getMonth() + 1, dd.getFullYear(), dd.getMinutes(), dd.getHours(), dd.getSeconds());
|
||||
}
|
||||
|
||||
const interval = 1000 * 60 * 60 * 2;
|
||||
console.log("Initial versions");
|
||||
await Promise.all([Bedrock.listVersion.syncCaches(), Java.listVersion.syncCaches()]);
|
||||
console.log("Next sync in", printDate(interval));
|
||||
setInterval(async () => {
|
||||
console.log("Sync versions");
|
||||
await Promise.all([Bedrock.listVersion.syncCaches(), Java.listVersion.syncCaches()]);
|
||||
console.log("Next sync in", printDate(interval));
|
||||
}, interval);
|
||||
|
||||
const app = express();
|
||||
app.use((req, res, next) => {
|
||||
if (!(req.query.pretty === "off" || req.query.pretty === "false")) {
|
||||
res.json = (body) => res.setHeader("Content-Type", "application/json").send(JSON.stringify(body, null, 2));
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
const server = createServer(app);
|
||||
server.listen(Number(process.env.PORT || 3000), () => {
|
||||
const addr = server.address();
|
||||
console.log("Listening on http://localhost:%f", Number(typeof addr === "object" ? addr.port : addr));
|
||||
if (typeof addr === "string") console.log("Server listen on socket %O path", addr);
|
||||
else if (typeof addr === "number") console.log("Listening on http://localhost:%f", addr);
|
||||
else if (typeof addr === "object") console.log("Listen on http://localhost:%s", addr?.port);
|
||||
});
|
||||
|
||||
// Bedrock
|
||||
const bedrockRoute = express.Router();
|
||||
app.use("/bedrock", bedrockRoute);
|
||||
const bedrockStash = {};
|
||||
bedrockRoute.get("/", async ({ res }) => {
|
||||
const bedrockOficial = (await (async () => {if (bedrockStash["oficial"]) return bedrockStash["oficial"];bedrockStash["oficial"] = await Bedrock.listVersions();setTimeout(() => delete bedrockStash["oficial"], 60000);return bedrockStash["oficial"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
const cloudbust = (await (async () => {if (bedrockStash["cloudbust"]) return bedrockStash["cloudbust"];bedrockStash["cloudbust"] = await Bedrock.listVersions("cloudbust");setTimeout(() => delete bedrockStash["cloudbust"], 60000);return bedrockStash["cloudbust"];})()).at(0);
|
||||
const nukkit = (await (async () => {if (bedrockStash["nukkit"]) return bedrockStash["nukkit"];bedrockStash["nukkit"] = await Bedrock.listVersions("nukkit");setTimeout(() => delete bedrockStash["nukkit"], 60000);return bedrockStash["nukkit"];})()).at(0);
|
||||
const powernukkit = (await (async () => {if (bedrockStash["powernukkit"]) return bedrockStash["powernukkit"];bedrockStash["powernukkit"] = await Bedrock.listVersions("powernukkit");setTimeout(() => delete bedrockStash["powernukkit"], 60000);return bedrockStash["powernukkit"];})()).at(0);
|
||||
const pocketmine = (await (async () => {if (bedrockStash["pocketmine"]) return bedrockStash["pocketmine"];bedrockStash["pocketmine"] = await Bedrock.listVersions("pocketmine");setTimeout(() => delete bedrockStash["pocketmine"], 60000);return bedrockStash["pocketmine"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
|
||||
return res.json({
|
||||
bedrockOficial,
|
||||
pocketmine,
|
||||
cloudbust,
|
||||
nukkit,
|
||||
powernukkit
|
||||
bedrockOficial: Bedrock.listVersion.mojangCache,
|
||||
pocketmine: Bedrock.listVersion.pocketmineCache,
|
||||
cloudbust: Bedrock.listVersion.cloudburstCache,
|
||||
nukkit: Bedrock.listVersion.nukkitCache,
|
||||
powernukkit: Bedrock.listVersion.powernukkitCache
|
||||
});
|
||||
});
|
||||
|
||||
bedrockRoute.get("/((oficial|cloudbust|nukkit|nukkit|powernukkit|pocketmine))", async (req, res) => {
|
||||
let platform = req.params[0];
|
||||
if (!bedrockStash[platform]) {
|
||||
if (platform === "oficial") platform = null;
|
||||
bedrockStash[(platform === null ? "oficial" : platform)] = await Bedrock.listVersions(platform);
|
||||
setTimeout(() => delete bedrockStash[(platform === null ? "oficial" : platform)], 60000);
|
||||
}
|
||||
const list = bedrockStash[(platform === null ? "oficial" : platform)];
|
||||
/** @type {"oficial" | "cloudbust" | "nukkit" | "nukkit" | "powernukkit" | "pocketmine"} */
|
||||
const platform = req.params[0];
|
||||
const ver = String(req.query.v || req.query.version || "");
|
||||
if (ver) {
|
||||
const data = list.find(e => e.version === ver);
|
||||
if (!data) return res.status(400).json({error: "Not found version."});
|
||||
return res.json(data);
|
||||
if (platform === "oficial") {
|
||||
if (!ver) return res.json(Bedrock.listVersion.mojangCache.toJSON());
|
||||
else {
|
||||
if (!(Bedrock.listVersion.mojangCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Bedrock.listVersion.mojangCache.get(ver));
|
||||
}
|
||||
} else if (platform === "pocketmine") {
|
||||
if (!ver) return res.json(Bedrock.listVersion.pocketmineCache.toJSON());
|
||||
else {
|
||||
if (!(Bedrock.listVersion.pocketmineCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Bedrock.listVersion.pocketmineCache.get(ver));
|
||||
}
|
||||
} else if (platform === "nukkit") {
|
||||
if (!ver) return res.json(Bedrock.listVersion.nukkitCache.toJSON());
|
||||
else {
|
||||
if (!(Bedrock.listVersion.nukkitCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Bedrock.listVersion.nukkitCache.get(ver));
|
||||
}
|
||||
} else if (platform === "cloudbust") {
|
||||
if (!ver) return res.json(Bedrock.listVersion.cloudburstCache.toJSON());
|
||||
else {
|
||||
if (!(Bedrock.listVersion.cloudburstCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Bedrock.listVersion.cloudburstCache.get(ver));
|
||||
}
|
||||
} else if (platform === "powernukkit") {
|
||||
if (!ver) return res.json(Bedrock.listVersion.powernukkitCache.toJSON());
|
||||
else {
|
||||
if (!(Bedrock.listVersion.powernukkitCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Bedrock.listVersion.powernukkitCache.get(ver));
|
||||
}
|
||||
}
|
||||
return res.json(list);
|
||||
});
|
||||
|
||||
// Java
|
||||
const javaRoute = express.Router();
|
||||
app.use("/java", javaRoute);
|
||||
const javaStash = {};
|
||||
javaRoute.get("/", async ({ res }) => {
|
||||
const javaOficial = (await (async () => {if (javaStash["oficial"]) return javaStash["oficial"]; javaStash["oficial"] = await Java.listVersions(); setTimeout(() => delete javaStash["oficial"], 6000); return javaStash["oficial"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
const spigot = (await (async () => {if (javaStash["spigot"]) return javaStash["spigot"]; javaStash["spigot"] = await Java.listVersions("spigot"); setTimeout(() => delete javaStash["spigot"], 6000); return javaStash["spigot"];})()).at(0);
|
||||
const paper = (await (async () => {if (javaStash["paper"]) return javaStash["paper"]; javaStash["paper"] = await Java.listVersions("paper"); setTimeout(() => delete javaStash["paper"], 6000); return javaStash["paper"];})()).at(0);
|
||||
const glowstone = (await (async () => {if (javaStash["glowstone"]) return javaStash["glowstone"]; javaStash["glowstone"] = await Java.listVersions("glowstone"); setTimeout(() => delete javaStash["glowstone"], 6000); return javaStash["glowstone"];})()).at(0);
|
||||
const purpur = (await (async () => {if (javaStash["purpur"]) return javaStash["purpur"]; javaStash["purpur"] = await Java.listVersions("purpur"); setTimeout(() => delete javaStash["purpur"], 6000); return javaStash["purpur"];})()).at(0);
|
||||
const folia = (await (async () => {if (javaStash["folia"]) return javaStash["folia"]; javaStash["folia"] = await Java.listVersions("folia"); setTimeout(() => delete javaStash["folia"], 6000); return javaStash["folia"];})()).at(0);
|
||||
const cuberite = (await (async () => {if (javaStash["cuberite"]) return javaStash["cuberite"]; javaStash["cuberite"] = await Java.listVersions("cuberite"); setTimeout(() => delete javaStash["cuberite"], 6000); return javaStash["cuberite"];})()).at(0);
|
||||
|
||||
return res.json({
|
||||
javaOficial,
|
||||
spigot,
|
||||
paper,
|
||||
glowstone,
|
||||
purpur,
|
||||
folia,
|
||||
cuberite
|
||||
javaOficial: Java.listVersion.mojangCache,
|
||||
spigot: Java.listVersion.spigotCache,
|
||||
paper: Java.listVersion.paperCache,
|
||||
glowstone: Java.listVersion.glowstoneCache,
|
||||
purpur: Java.listVersion.purpurCache,
|
||||
folia: Java.listVersion.foliaCache,
|
||||
cuberite: Java.listVersion.cuberiteCache
|
||||
});
|
||||
});
|
||||
javaRoute.get("/((oficial|spigot|paper|purpur|glowstone|folia|cuberite))", async (req, res) => {
|
||||
let platform = req.params[0];
|
||||
if (!javaStash[platform]) {
|
||||
if (platform === "oficial") platform = null;
|
||||
javaStash[(platform === null ? "oficial" : platform)] = await Java.listVersions(platform);
|
||||
setTimeout(() => delete javaStash[(platform === null ? "oficial" : platform)], 60000);
|
||||
}
|
||||
const list = javaStash[(platform === null ? "oficial" : platform)];
|
||||
/** @type {"oficial" | "spigot" | "paper" | "purpur" | "glowstone" | "folia" | "cuberite"} */
|
||||
const platform = req.params[0];
|
||||
const ver = String(req.query.v || req.query.version || "");
|
||||
if (ver) {
|
||||
const data = list.find(e => e.version === ver);
|
||||
if (!data) return res.status(400).json({error: "Not found version."});
|
||||
return res.json(data);
|
||||
if (platform === "oficial") {
|
||||
if (!ver) return res.json(Java.listVersion.mojangCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.mojangCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.mojangCache.get(ver));
|
||||
}
|
||||
} else if (platform === "spigot") {
|
||||
if (!ver) return res.json(Java.listVersion.spigotCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.spigotCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.spigotCache.get(ver));
|
||||
}
|
||||
} else if (platform === "paper") {
|
||||
if (!ver) return res.json(Java.listVersion.paperCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.paperCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.paperCache.get(ver));
|
||||
}
|
||||
} else if (platform === "purpur") {
|
||||
if (!ver) return res.json(Java.listVersion.purpurCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.purpurCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.purpurCache.get(ver));
|
||||
}
|
||||
} else if (platform === "glowstone") {
|
||||
if (!ver) return res.json(Java.listVersion.glowstoneCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.glowstoneCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.glowstoneCache.get(ver));
|
||||
}
|
||||
} else if (platform === "cuberite") {
|
||||
if (!ver) return res.json(Java.listVersion.cuberiteCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.cuberiteCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.cuberiteCache.get(ver));
|
||||
}
|
||||
} else if (platform === "folia") {
|
||||
if (!ver) return res.json(Java.listVersion.foliaCache.toJSON());
|
||||
else {
|
||||
if (!(Java.listVersion.foliaCache.has(ver))) return res.status(400).json({error: "This version not exists!"});
|
||||
return res.json(Java.listVersion.foliaCache.get(ver));
|
||||
}
|
||||
}
|
||||
return res.json(list);
|
||||
});
|
||||
|
||||
app.get("/", async ({ res }) => {
|
||||
// Bedrock
|
||||
const bedrockOficial = (await (async () => {if (bedrockStash["oficial"]) return bedrockStash["oficial"];bedrockStash["oficial"] = await Bedrock.listVersions();setTimeout(() => delete bedrockStash["oficial"], 60000);return bedrockStash["oficial"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
const cloudbust = (await (async () => {if (bedrockStash["cloudbust"]) return bedrockStash["cloudbust"];bedrockStash["cloudbust"] = await Bedrock.listVersions("cloudbust");setTimeout(() => delete bedrockStash["cloudbust"], 60000);return bedrockStash["cloudbust"];})()).at(0);
|
||||
const nukkit = (await (async () => {if (bedrockStash["nukkit"]) return bedrockStash["nukkit"];bedrockStash["nukkit"] = await Bedrock.listVersions("nukkit");setTimeout(() => delete bedrockStash["nukkit"], 60000);return bedrockStash["nukkit"];})()).at(0);
|
||||
const powernukkit = (await (async () => {if (bedrockStash["powernukkit"]) return bedrockStash["powernukkit"];bedrockStash["powernukkit"] = await Bedrock.listVersions("powernukkit");setTimeout(() => delete bedrockStash["powernukkit"], 60000);return bedrockStash["powernukkit"];})()).at(0);
|
||||
const pocketmine = (await (async () => {if (bedrockStash["pocketmine"]) return bedrockStash["pocketmine"];bedrockStash["pocketmine"] = await Bedrock.listVersions("pocketmine");setTimeout(() => delete bedrockStash["pocketmine"], 60000);return bedrockStash["pocketmine"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
|
||||
// Java
|
||||
const javaOficial = (await (async () => {if (javaStash["oficial"]) return javaStash["oficial"]; javaStash["oficial"] = await Java.listVersions(); setTimeout(() => delete javaStash["oficial"], 6000); return javaStash["oficial"];})()).filter(rel => rel.release === "stable").at(0);
|
||||
const spigot = (await (async () => {if (javaStash["spigot"]) return javaStash["spigot"]; javaStash["spigot"] = await Java.listVersions("spigot"); setTimeout(() => delete javaStash["spigot"], 6000); return javaStash["spigot"];})()).at(0);
|
||||
const paper = (await (async () => {if (javaStash["paper"]) return javaStash["paper"]; javaStash["paper"] = await Java.listVersions("paper"); setTimeout(() => delete javaStash["paper"], 6000); return javaStash["paper"];})()).at(0);
|
||||
const glowstone = (await (async () => {if (javaStash["glowstone"]) return javaStash["glowstone"]; javaStash["glowstone"] = await Java.listVersions("glowstone"); setTimeout(() => delete javaStash["glowstone"], 6000); return javaStash["glowstone"];})()).at(0);
|
||||
const purpur = (await (async () => {if (javaStash["purpur"]) return javaStash["purpur"]; javaStash["purpur"] = await Java.listVersions("purpur"); setTimeout(() => delete javaStash["purpur"], 6000); return javaStash["purpur"];})()).at(0);
|
||||
const folia = (await (async () => {if (javaStash["folia"]) return javaStash["folia"]; javaStash["folia"] = await Java.listVersions("folia"); setTimeout(() => delete javaStash["folia"], 6000); return javaStash["folia"];})()).at(0);
|
||||
const cuberite = (await (async () => {if (javaStash["cuberite"]) return javaStash["cuberite"]; javaStash["cuberite"] = await Java.listVersions("cuberite"); setTimeout(() => delete javaStash["cuberite"], 6000); return javaStash["cuberite"];})()).at(0);
|
||||
|
||||
return res.json({
|
||||
bedrock: {
|
||||
bedrockOficial,
|
||||
pocketmine,
|
||||
cloudbust,
|
||||
nukkit,
|
||||
powernukkit
|
||||
bedrockOficial: Bedrock.listVersion.mojangCache,
|
||||
pocketmine: Bedrock.listVersion.pocketmineCache,
|
||||
cloudbust: Bedrock.listVersion.cloudburstCache,
|
||||
nukkit: Bedrock.listVersion.nukkitCache,
|
||||
powernukkit: Bedrock.listVersion.powernukkitCache
|
||||
},
|
||||
java: {
|
||||
javaOficial,
|
||||
spigot,
|
||||
paper,
|
||||
glowstone,
|
||||
purpur,
|
||||
folia,
|
||||
cuberite
|
||||
javaOficial: Java.listVersion.mojangCache,
|
||||
spigot: Java.listVersion.spigotCache,
|
||||
paper: Java.listVersion.paperCache,
|
||||
glowstone: Java.listVersion.glowstoneCache,
|
||||
purpur: Java.listVersion.purpurCache,
|
||||
folia: Java.listVersion.foliaCache,
|
||||
cuberite: Java.listVersion.cuberiteCache
|
||||
}
|
||||
})
|
||||
});
|
||||
|
9
railway.json
Normal file
9
railway.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"build": {
|
||||
"builder": "DOCKERFILE",
|
||||
"dockerfilePath": "packages/verapiDockerfile"
|
||||
},
|
||||
"deploy": {
|
||||
"restartPolicyType": "ALWAYS"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user