import { contentType } from "https://deno.land/std@0.202.0/media_types/mod.ts"; import { Image, TextLayout, } from "https://deno.land/x/imagescript@1.2.15/ImageScript.js"; const font = await fetch("https://static.pyrope.net/courier-std-bold.otf") .then((r) => r.arrayBuffer()) .then((b) => new Uint8Array(b)); // deciding now: pronouns (e.g. he/him) can be at most 32 chars let images: Map = new Map(); function make_image(s: string): Image { console.log(`MAKING AN IMAGE FOR '${s}' (EXPENSIVE!)`); let image = Image.renderText( font, 64, s, 0, new TextLayout({ verticalAlign: "center", horizontalAlign: "middle" }), ); const outline = Image.renderText( font, 64, s, 0xff_ff_ff_ff, new TextLayout({ verticalAlign: "center", horizontalAlign: "middle" }), ); for (const hshift of [-2, 0, 2]) { for (const vshift of [-2, 0, 2]) { image = image.composite(outline, hshift, vshift); } } const text = Image.renderText( font, 64, s, 0x00_00_00_ff, new TextLayout({ verticalAlign: "center", horizontalAlign: "middle" }), ); const final = image.composite(text); return final.crop(0, 0, final.width, final.height - 22); } async function get_pronoun_image(prn: string): Promise { if (images.has(prn)) { const entry = images.get(prn)!; entry.fetched++; return entry.image; } const image = await make_image(prn).encode(); images.set(prn, { image, fetched: 1 }); return image; } // the whole cache thing def sketches me out, but people probably won't be *that* malicious const MAX_PRN_LENGTH = 32; const MAX_PRN_CACHE = 64; // shouldn't really be a problem const MAX_PRN_CHOICES = 128; function clean_up() { if (images.size <= MAX_PRN_CACHE) { console.log( `not cleaning up, we only have ${images.size} cached which is less than ${MAX_PRN_CACHE}`, ); return; } // reverse order! const entries = [...images.entries()].toSorted(([_a, a], [_b, b]) => b.fetched - a.fetched ); console.log("before clean up:", images); images = new Map(entries.slice(0, MAX_PRN_CACHE)); console.log("after clean up:", images); } const image_response = (data: Uint8Array): Response => new Response(data, { headers: { "Content-Type": contentType("png") }, }); Deno.serve({ port: 61265 }, async (req) => { const prns = (new URL(req.url)).searchParams.getAll("p"); if (prns.some((p) => p.length > MAX_PRN_LENGTH)) { return new Response(`MAX_PRN_LENGTH = ${MAX_PRN_LENGTH}`, { status: 413 }); } if (prns.length > MAX_PRN_CHOICES) { return new Response(`MAX_PRN_CHOICES = ${MAX_PRN_CHOICES}`, { status: 413, }); } const prn = prns[Math.floor(Math.random() * prns.length)] ?? "NONE, APPARENTLY"; const resp = image_response(await get_pronoun_image(prn)); clean_up(); return resp; });