video.cerulea.blue/appview/main.ts

116 lines
3.2 KiB
TypeScript

import { ensureDir } from "@std/fs";
import { serveDir, serveFile } from "@std/http/file-server";
import * as z from "@zod/mini";
import { db } from "./db.ts";
import { resolveDid } from "../common/identity.ts";
import { VIDEO_PATTERN } from "../common/routes.ts";
async function fetchVideo(req: Request): Promise<Response> {
if (req.method === "OPTIONS") {
return new Response(null, {
headers: {
"access-control-allow-origin": "*",
},
});
}
if (req.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const BodySchema = z.object({
repo: z.string().check(z.startsWith("did:")),
blob: z.string(),
});
const body = BodySchema.parse(await req.json());
const existingVideo = db.getVideo(body.repo, body.blob);
if (existingVideo !== undefined) {
return new Response(JSON.stringify({ filename: existingVideo }), {
headers: {
"content-type": "application/json",
"access-control-allow-origin": "*",
},
});
}
if (!db.inAllowlist(body.repo)) {
return new Response(
JSON.stringify({
error: "Denied",
message: "repo is not allowlisted on AppView",
}),
{
status: 400,
headers: {
"content-type": "application/json",
"access-control-allow-origin": "*",
},
},
);
}
const doc = await resolveDid(body.repo);
const pdsBaseURL = doc.service?.find(it => it.id === "#atproto_pds")?.serviceEndpoint;
if (!pdsBaseURL || typeof pdsBaseURL !== "string") return new Response(null, { status: 400 });
let filename: string = crypto.randomUUID();
{
const getBlobURL = new URL("/xrpc/com.atproto.sync.getBlob", pdsBaseURL);
getBlobURL.searchParams.set("did", body.repo);
getBlobURL.searchParams.set("cid", body.blob);
const blobResponse = await fetch(getBlobURL, {
headers: { "user-agent": "video.cerulea.app" },
});
const contentType = blobResponse.headers.get("content-type");
if (contentType === "video/mp4") filename += ".mp4";
if (contentType === "video/webm") filename += ".webm";
await ensureDir("./data/videos");
using file = await Deno.open("./data/videos/" + filename, {
write: true,
createNew: true,
});
await blobResponse.body?.pipeTo(file.writable);
db.addVideo(body.repo, body.blob, filename);
}
return new Response(JSON.stringify({ filename }), {
headers: {
"content-type": "application/json",
"access-control-allow-origin": "*",
},
});
}
export default {
async fetch(req: Request, _info): Promise<Response> {
const pathname = new URL(req.url).pathname;
if (pathname === "/xrpc/blue.cerulea.video.fetchVideo") {
return await fetchVideo(req);
}
if (pathname.startsWith("/user-content/")) {
return await serveDir(req, {
fsRoot: "./data/videos",
quiet: true,
enableCors: true,
urlRoot: "user-content",
});
}
const videoRoute = VIDEO_PATTERN.exec(req.url);
if (videoRoute) {
return await serveFile(req, "./web/viewer.html");
}
return await serveDir(req, { fsRoot: "./web", quiet: true });
},
} satisfies Deno.ServeDefaultExport;