import { randomUUID } from 'node:crypto' import { writeFile, unlink } from 'node:fs/promises' import path from 'node:path' import { preprocess } from '../lib/imagemagick.js' import { runChafa, runJp2a, runAsciiImageConverter, runImgToTxt, } from '../lib/converters.js' const RUNNERS = { chafa: runChafa, jp2a: runJp2a, 'ascii-image-converter': runAsciiImageConverter, img2txt: runImgToTxt, } export async function convertRoute(fastify) { fastify.post('/convert', async (request, reply) => { let tempInput = null let tempProcessed = null try { const parts = request.parts() let tool = null let flags = {} for await (const part of parts) { if (part.type === 'file' && part.fieldname === 'image') { const SAFE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff', '.tif', '.avif']) const rawExt = path.extname(part.filename || '').toLowerCase() const ext = SAFE_EXTS.has(rawExt) ? rawExt : '.jpg' tempInput = `/tmp/asciinator-${randomUUID()}${ext}` const buf = await part.toBuffer() await writeFile(tempInput, buf) } else if (part.type === 'field') { if (part.fieldname === 'tool') tool = part.value if (part.fieldname === 'flags') { try { flags = JSON.parse(part.value) } catch { flags = {} } } } } if (!tempInput) return reply.code(400).type('text/plain').send('No image provided') if (!tool) return reply.code(400).type('text/plain').send('No tool specified') const runner = RUNNERS[tool] if (!runner) return reply.code(400).type('text/plain').send(`Unknown tool: ${tool}`) const format = tool === 'jp2a' ? 'jpg' : 'png' tempProcessed = await preprocess(tempInput, format) const result = await runner(tempProcessed, flags) return reply.type('text/plain').send(result.stdout) } catch (err) { if (err.exitCode !== undefined) { return reply.code(422).type('text/plain').send(err.stderr || err.message) } return reply.code(400).type('text/plain').send(err.message) } finally { if (tempInput) await unlink(tempInput).catch(() => {}) if (tempProcessed) await unlink(tempProcessed).catch(() => {}) } }) }