export function json(content: unknown) { const headers = new Headers(); headers.append("Content-Type", "application/json"); return new Response(JSON.stringify(content), { headers, }); } export const isValidUrl = (urlString: string) => { try { return Boolean(new URL(urlString)); } catch (_e) { return false; } }; export const fixRenderedMarkdown = (content: string) => { return content.replace("***\n", "---") .replace("----------------", "---") .replace("\n---", "---") .replace(/^(date:[^'\n]*)'|'/gm, (match, p1, p2) => { if (p1) { // This is a line starting with date: followed by single quotes return p1.replace(/'/gm, ""); } else if (p2) { return ""; } else { // This is a line with single quotes, but not starting with date: return match; } }); }; type StreamMessage = { type: "info"; message: string; } | { type: "error"; message: string; } | { type: "warning"; message: string; } | { type: "finished"; url: string; }; export async function fetchStream( url: string, cb: (chunk: StreamMessage) => void, init?: RequestInit, ) { const res = await fetch(url, init); if (!res.body) return; let buffer = ""; const reader = res.body .pipeThrough(new TextDecoderStream()) .pipeThrough( new TransformStream({ transform(chunk, controller) { buffer += chunk; let idx; while ((idx = buffer.indexOf("\n")) >= 0) { const line = buffer.slice(0, idx).trim(); buffer = buffer.slice(idx + 1); if (line) controller.enqueue(line); } }, flush(controller) { const line = buffer.trim(); if (line) controller.enqueue(line); }, }), ) .getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; cb(JSON.parse(value)); } } export function hashString(message: string) { let hash = 0; for (let i = 0; i < message.length; i++) { const char = message.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; } export const createStreamResponse = () => { const encoder = new TextEncoder(); let controller: ReadableStreamDefaultController; const body = new ReadableStream({ start(c) { controller = c; }, }); const response = new Response(body, { headers: { // newline-delimited JSON "content-type": "application/x-ndjson; charset=utf-8", // prevent intermediaries from buffering/transforming "cache-control": "no-cache, no-transform", "x-content-type-options": "nosniff", // nginx hint to disable proxy buffering "x-accel-buffering": "no", // if you control compression, keep it off for streams // "content-encoding": "identity", }, }); const send = (obj: unknown) => { controller.enqueue(encoder.encode(JSON.stringify(obj) + "\n")); // ← delimiter }; const cancel = () => controller.close(); function info(message: string) { return send({ type: "info", message }); } function error(message: string) { return send({ type: "error", message }); } function warning(message: string) { return send({ type: "warning", message }); } return { response, cancel, send, info, error, warning, }; }; export type StreamResponse = ReturnType; export function debounce) => void>( this: ThisParameterType, fn: T, delay = 300, ) { let timer: ReturnType | undefined; return (...args: Parameters) => { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } export function parseRating(rating: string | number) { if (typeof rating == "number") return rating; try { const res = parseInt(rating); if (!Number.isNaN(res)) return res; } catch (_e) { // This is okay } return rating.length / 2; } export async function convertOggToMp3( oggData: ArrayBuffer, ): Promise { const ffmpeg = new Deno.Command("ffmpeg", { args: ["-f", "ogg", "-i", "pipe:0", "-f", "mp3", "pipe:1"], stdin: "piped", stdout: "piped", stderr: "null", }); const process = ffmpeg.spawn(); const writer = process.stdin.getWriter(); await writer.write(new Uint8Array(oggData)); await writer.close(); const output = await process.output(); const { code } = await process.status; if (code !== 0) throw new Error(`FFmpeg exited with code ${code}`); return output.stdout; }