106 lines
2.8 KiB
TypeScript
106 lines
2.8 KiB
TypeScript
// TODO: randomly pick color
|
|
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));
|
|
|
|
function make_image(
|
|
{ text, color = 0, outline_color = 0xff_ff_ff_ff }: {
|
|
text: string;
|
|
color?: number;
|
|
outline_color?: number;
|
|
},
|
|
): Image {
|
|
let image = Image.renderText(
|
|
font,
|
|
64,
|
|
text,
|
|
0,
|
|
new TextLayout({ verticalAlign: "center", horizontalAlign: "middle" }),
|
|
);
|
|
|
|
const outline = Image.renderText(
|
|
font,
|
|
64,
|
|
text,
|
|
outline_color,
|
|
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_img = Image.renderText(
|
|
font,
|
|
64,
|
|
text,
|
|
color,
|
|
new TextLayout({ verticalAlign: "center", horizontalAlign: "middle" }),
|
|
);
|
|
|
|
const final = image.composite(text_img);
|
|
return final.crop(0, 0, final.width, final.height - 22);
|
|
}
|
|
|
|
// the whole cache thing def sketches me out, but people probably won't be *that* malicious
|
|
const MAX_PRN_LENGTH = 128;
|
|
// shouldn't really be a problem
|
|
const MAX_PRN_CHOICES = 256;
|
|
|
|
// let's stick to hex even though BigInt() can handle prefixes (prefices?)
|
|
const hex_string_to_color = (s: string): number | undefined => {
|
|
const parsed = parseInt(s, 16);
|
|
if (Number.isNaN(parsed)) return;
|
|
// JANK! SORRY
|
|
return Number((BigInt(parsed) << 8n) | 0xffn);
|
|
};
|
|
|
|
const image_response = (data: Uint8Array): Response =>
|
|
new Response(data, {
|
|
headers: { "Content-Type": contentType("png") },
|
|
});
|
|
|
|
Deno.serve({ port: 61265 }, async (req) => {
|
|
const url = new URL(req.url);
|
|
const params = url.searchParams;
|
|
const prns = params.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 fg_str = params.get("fg");
|
|
const outline_str = params.get("outline");
|
|
|
|
const sentence_id = params.get("sentence_id");
|
|
const sentence_index = params.get("sentence_index");
|
|
|
|
console.log(
|
|
`${sentence_id}#${sentence_index}: chose ${prn} from ${prns}. ${fg_str} on ${outline_str}`,
|
|
);
|
|
|
|
const fg = hex_string_to_color(fg_str ?? "0");
|
|
const outline = hex_string_to_color(outline_str ?? "ffffff");
|
|
|
|
const resp = image_response(
|
|
await make_image({ text: prn, color: fg, outline_color: outline }).encode(),
|
|
);
|
|
return resp;
|
|
});
|