summaryrefslogtreecommitdiff
path: root/tools/portraits/server.ts
diff options
context:
space:
mode:
authorskylar <me@casper>2026-11-01 05:43:24 -0800
committerskylar <me@casper>2026-11-01 05:43:24 -0800
commite69ad01e700e80fa959dfee08cd556ca0d777ffd (patch)
tree645fc9db2142e87a5ef302733a77ba2fec848d88 /tools/portraits/server.ts
Yesod: self-hosted WebXR rooms
Start a room in a world and hang out in VR from the headset's browser: A-Frame + Networked-A-Frame over EasyRTC, with a coturn relay that can hide everyone's IP address. Ships with the Campfire, Temple, Cabin, Balcony and Test room worlds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/portraits/server.ts')
-rw-r--r--tools/portraits/server.ts27
1 files changed, 27 insertions, 0 deletions
diff --git a/tools/portraits/server.ts b/tools/portraits/server.ts
new file mode 100644
index 0000000..8af80f5
--- /dev/null
+++ b/tools/portraits/server.ts
@@ -0,0 +1,27 @@
+// Renders avatar portraits for the lobby. Serves render.html plus the project's
+// public files, and saves the PNGs the page POSTs back into ./out.
+//
+// deno run --allow-net=127.0.0.1:8097 --allow-read --allow-write=tools/portraits tools/portraits/server.ts
+// then open http://localhost:8097/ (or ?slugs=name1,name2) and wait for the title "done".
+const HERE = new URL('.', import.meta.url).pathname;
+const PUBLIC = new URL('../../public', import.meta.url).pathname;
+await Deno.mkdir(`${HERE}out`, { recursive: true });
+
+const TYPES: Record<string, string> = { html: 'text/html', js: 'text/javascript', vrm: 'application/octet-stream' };
+
+Deno.serve({ port: 8097, hostname: '127.0.0.1' }, async (req) => {
+ const url = new URL(req.url);
+ if (url.pathname.includes('..')) return new Response('no', { status: 400 });
+ if (req.method === 'POST' && url.pathname.startsWith('/save/')) {
+ const slug = url.pathname.slice(6).replace(/[^a-z0-9-]/g, '');
+ await Deno.writeFile(`${HERE}out/${slug}.png`, new Uint8Array(await req.arrayBuffer()));
+ return new Response('ok');
+ }
+ const file = url.pathname === '/' ? `${HERE}render.html` : PUBLIC + url.pathname;
+ try {
+ const ext = file.split('.').pop() || '';
+ return new Response(await Deno.readFile(file), { headers: { 'content-type': TYPES[ext] || 'application/octet-stream' } });
+ } catch {
+ return new Response('not found', { status: 404 });
+ }
+});