252 lines
7.8 KiB
TypeScript
252 lines
7.8 KiB
TypeScript
import { SockpuppetPlus } from "@cgg/sockpuppet";
|
|
import { serveDir, serveFile } from "@std/http/file-server";
|
|
import { BearMetalStore } from "@bearmetal/store";
|
|
import { ensureDir, ensureFile, exists } from "@std/fs";
|
|
import { Router } from "./router.ts";
|
|
import { getPackVersion } from "./util/packVersion.ts";
|
|
import { createTagRoutes } from "./tags/routes.ts";
|
|
import { createResourcesRoutes } from "./resources/routes.ts";
|
|
|
|
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
|
|
|
|
const sockpuppet = new SockpuppetPlus();
|
|
sockpuppet.addHandler((req: Request) => {
|
|
const url = new URL(req.url);
|
|
if (!url.pathname.startsWith("/images")) return;
|
|
|
|
return serveFile(req, url.searchParams.get("location") as string);
|
|
});
|
|
|
|
// sockpuppet.addHandler((req: Request) => {
|
|
// const method = req.method;
|
|
// if (method === "OPTIONS") {
|
|
// return new Response(null, {
|
|
// status: 200,
|
|
// headers: {
|
|
// "Access-Control-Allow-Origin": "*",
|
|
// "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
// },
|
|
// });
|
|
// }
|
|
// });
|
|
|
|
const router = new Router();
|
|
router.route("/api/dir")
|
|
.get(async () => {
|
|
using store = new BearMetalStore();
|
|
const mcPath = store.get("mcPath") as string;
|
|
if (mcPath) {
|
|
const worlds: {
|
|
world: string;
|
|
icon: string;
|
|
path: string;
|
|
}[] = [];
|
|
try {
|
|
for await (const file of Deno.readDir(mcPath)) {
|
|
if (file.isDirectory && file.name.startsWith("saves")) {
|
|
for await (
|
|
const world of Deno.readDir(mcPath + "/" + file.name)
|
|
) {
|
|
if (world.isDirectory) {
|
|
for await (
|
|
const f of Deno.readDir(
|
|
mcPath + "/" + file.name + "/" + world.name,
|
|
)
|
|
) {
|
|
if (f.name.endsWith(".dat")) {
|
|
worlds.push({
|
|
world: world.name,
|
|
icon: Deno.realPathSync(
|
|
mcPath + "/" + file.name + "/" + world.name +
|
|
"/icon.png",
|
|
),
|
|
path: Deno.realPathSync(
|
|
mcPath + "/" + file.name + "/" + world.name,
|
|
),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e: any) {
|
|
store.set("mcPath", "");
|
|
return new Response(e, { status: 500 });
|
|
}
|
|
return new Response(JSON.stringify({ worlds, mcPath }), {
|
|
status: 200,
|
|
});
|
|
}
|
|
return new Response(JSON.stringify({ mcPath }), {
|
|
status: mcPath ? 200 : 500,
|
|
});
|
|
})
|
|
.post(async (req) => {
|
|
using store = new BearMetalStore();
|
|
const formData = await req.formData();
|
|
const dir = formData.get("mcPath") as string;
|
|
if (!dir) return new Response(null, { status: 400 });
|
|
|
|
store.set("mcPath", dir);
|
|
if (!store.get("mcPath")) return new Response(null, { status: 500 });
|
|
|
|
return new Response(null, { status: 200 });
|
|
});
|
|
|
|
router.route("/api/world")
|
|
.post(async (req) => {
|
|
using store = new BearMetalStore();
|
|
const worldPath = await req.text();
|
|
if (!worldPath) return new Response(null, { status: 400 });
|
|
|
|
const mcPath = store.get("mcPath") as string;
|
|
if (!mcPath) {
|
|
return new Response("Tried to set world, but MC path is not set.", {
|
|
status: 500,
|
|
});
|
|
}
|
|
const realWorldPath = Deno.realPathSync(worldPath);
|
|
store.set("world", realWorldPath);
|
|
return new Response(null, { status: 200 });
|
|
})
|
|
.get((req) => {
|
|
using store = new BearMetalStore();
|
|
const worldPath = store.get("world") as string;
|
|
if (!worldPath) return new Response(null, { status: 400 });
|
|
return new Response(worldPath.split("/").pop() as string, { status: 200 });
|
|
});
|
|
|
|
router.route("/api/pack")
|
|
.get(() => {
|
|
using store = new BearMetalStore();
|
|
return new Response(
|
|
JSON.stringify({ packName: store.get("packname") as string }),
|
|
{ status: 200 },
|
|
);
|
|
})
|
|
.post(async (req) => {
|
|
using store = new BearMetalStore();
|
|
const formData = await req.formData();
|
|
const packName = formData.get("packName") as string;
|
|
if (!packName) return new Response(null, { status: 400 });
|
|
createPack(store, packName);
|
|
|
|
store.set("packname", packName);
|
|
return new Response(JSON.stringify({ packName }), { status: 200 });
|
|
});
|
|
|
|
router.route("/api/pack/namespaces")
|
|
.get(async () => {
|
|
using store = new BearMetalStore();
|
|
|
|
const namespaces = Array.from(Deno.readDirSync(store.get("packlocation")))
|
|
.filter((dir) => dir.isDirectory).map((dir) => dir.name);
|
|
return new Response(JSON.stringify(namespaces), { status: 200 });
|
|
})
|
|
.post(async (req) => {
|
|
using store = new BearMetalStore();
|
|
|
|
const namespace = await req.text();
|
|
if (!namespace) {
|
|
return new Response("Namespace is required", { status: 400 });
|
|
}
|
|
|
|
const namespaceRx = /^[a-zA-Z0-9_\-]+$/;
|
|
if (!namespaceRx.test(namespace)) {
|
|
return new Response(
|
|
"Namespace must only contain letters, numbers, underscores, and dashes",
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
await ensureDir(store.get("packlocation") + "/" + namespace);
|
|
return new Response(null, { status: 200 });
|
|
});
|
|
|
|
router.route("/api/packs")
|
|
.get(async () => {
|
|
using store = new BearMetalStore();
|
|
const packs: { name: string; path: string }[] = [];
|
|
|
|
const world = store.get("world") as string;
|
|
for await (const pack of Deno.readDir(world + "/datapacks")) {
|
|
if (
|
|
pack.isDirectory &&
|
|
await exists(world + "/datapacks/" + pack.name + "/bmp_dev")
|
|
) {
|
|
packs.push({
|
|
name: pack.name,
|
|
path: Deno.realPathSync(world + "/datapacks/" + pack.name),
|
|
});
|
|
}
|
|
}
|
|
|
|
return new Response("No BMP packs found", { status: 400 });
|
|
});
|
|
|
|
router.route("/api/pack/version")
|
|
.get(() => {
|
|
using store = new BearMetalStore();
|
|
const version = getPackVersion(store);
|
|
return new Response(version.toString(), {
|
|
status: 200,
|
|
});
|
|
})
|
|
.post(async (req) => {
|
|
using store = new BearMetalStore();
|
|
const version = await req.text();
|
|
if (!version) return new Response(null, { status: 400 });
|
|
|
|
try {
|
|
store.set("version", version);
|
|
const packMeta = Deno.readTextFileSync(
|
|
store.get("packlocation") + "/pack.mcmeta",
|
|
);
|
|
const packMetaJson = JSON.parse(packMeta);
|
|
packMetaJson.pack.pack_format = parseInt(version);
|
|
await Deno.writeTextFile(
|
|
store.get("packlocation") + "/pack.mcmeta",
|
|
JSON.stringify(packMetaJson),
|
|
);
|
|
} catch (e: any) {
|
|
return new Response(e, { status: 500 });
|
|
}
|
|
return new Response(null, { status: 200 });
|
|
});
|
|
|
|
router.route("/api/versions")
|
|
.get(() => {
|
|
const versions = Array.from(Deno.readDirSync(installPath + "pack_versions"))
|
|
.filter((v) => v.isFile).map((version) =>
|
|
version.name.replace(".json", "")
|
|
);
|
|
return new Response(JSON.stringify(versions), { status: 200 });
|
|
});
|
|
|
|
createTagRoutes(router);
|
|
createResourcesRoutes(router);
|
|
|
|
sockpuppet.addHandler((req: Request) => {
|
|
if (new URL(req.url).pathname.startsWith("/api")) return;
|
|
|
|
return serveDir(req, {
|
|
fsRoot: installPath + "dist",
|
|
});
|
|
});
|
|
|
|
sockpuppet.addHandler(router.handle);
|
|
|
|
async function createPack(store: BearMetalStore, packName: string) {
|
|
const realWorldPath = store.get("world");
|
|
store.set("packlocation", realWorldPath + "/datapacks/" + packName);
|
|
await ensureDir(store.get("packlocation"));
|
|
await ensureFile(store.get("packlocation") + "/bmp_dev");
|
|
await ensureFile(store.get("packlocation") + "/pack.mcmeta");
|
|
if (!Deno.readTextFileSync(store.get("packlocation") + "/pack.mcmeta")) {
|
|
await Deno.writeTextFile(
|
|
store.get("packlocation") + "/pack.mcmeta",
|
|
`{"pack":{"pack_format":48,"description":"${packName}"}}`,
|
|
);
|
|
}
|
|
}
|