277 lines
8.7 KiB
TypeScript

import { Sockpuppet } from "@bearmetal/sockpuppet";
import { serveDir, serveFile } from "@std/http/file-server";
import { BearMetalStore } from "@bearmetal/store";
import { ensureDir, ensureFile, exists } from "@std/fs";
import { Router } from "@bearmetal/router";
import { getPackVersion } from "./util/packVersion.ts";
import { createTagRoutes } from "./tags/routes.ts";
import { createResourcesRoutes } from "./resources/routes.ts";
import { readDirDirs } from "./util/readDir.ts";
import { unzipResources } from "./resources/unzip.ts";
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
const sockpuppet = new Sockpuppet();
// 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);
// });
const router = new Router();
router.route("/images").get((req,ctx) => serveFile(req,ctx.url.searchParams.get("location") as string));
router.route("/api/dir")
.get(async () => {
console.log("hitting dir");
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),
);
const packVersionSchema = Deno.readTextFileSync(
installPath + "pack_versions/" + version + ".json",
);
const packVersionSchemaJson = JSON.parse(packVersionSchema);
packVersionSchemaJson.mcVersion = store.get("mcVersion");
const versionResourceDir = await readDirDirs(installPath + "resources/");
if (!versionResourceDir.includes(version)) {
unzipResources();
}
} 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);
router.route("/api/stream/test")
.get(() => {
const stream = new ReadableStream({
async start(controller) {
const enc = new TextEncoder();
controller.enqueue(enc.encode("Hello"));
controller.enqueue(enc.encode(", "));
controller.enqueue(enc.encode("World!"));
controller.close();
},
});
return new Response(stream, {
headers: {
"content-type": "text/plain",
"x-content-type-options": "nosniff",
},
});
})
// sockpuppet.addHandler((req: Request) => {
// if (new URL(req.url).pathname.startsWith("/api")) return;
// return serveDir(req, {
// fsRoot: installPath + "dist",
// });
// });
router.route("/").get((req) => serveDir(req, {fsRoot:installPath + "dist"}));
router.route("/ws").get(sockpuppet.handler)
Deno.serve(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}"}}`,
);
}
}