diff --git a/index.html b/index.html new file mode 100644 index 0000000..bbffab7 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + Redoc + + + + + + + + + + + + + diff --git a/index.ts b/index.ts index 85da1fe..5ab0ad1 100644 --- a/index.ts +++ b/index.ts @@ -3,13 +3,25 @@ const app = new Hono() const joypixels = require("emoji-toolkit"); import { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readFile, writeFile } from 'fs' import { $ } from 'bun'; +import { cors } from 'hono/cors' import ytSearch from 'yt-search'; import { writeMDFPWMv3 } from './mdfpwmWriter'; let ytdlpPath = "yt-dlp" if(existsSync("./yt-dlp")) { ytdlpPath = "./yt-dlp" } +const openApi = JSON.parse(readFileSync("openapi.json").toString("utf8")) +const index = readFileSync("index.html").toString("utf8") +app.use("*", cors()) + +app.get("/", async (c) => { + return c.html(index) +}) + +app.get("/openapi.json", async (c) => { + return c.json(openApi); +}) app.post("/youtube", async (c) => { const auth = c.req.header('Authorization') if (auth !== process.env.PASSWORD) { @@ -62,14 +74,14 @@ app.post("/youtube", async (c) => { rmSync("test.opus") } } - + return c.body(readFileSync(filename), { headers: { 'Content-Type': `audio/vnd.${body.mdfpwm ? "m" : ""}dfpwm`, 'Content-Disposition': `attachment; filename="${filename}"`, }, }); - + }) app.post('/render', async (c) => { const auth = c.req.header('Authorization') @@ -100,7 +112,7 @@ app.post('/render', async (c) => { size = { width: w, height: h }; extraArgs += ` -W ${w} -H ${h}`; - } + } if(body?.extraArgs) { if (typeof body.extraArgs !== 'string') { return c.text('extraArgs must be a string', 400) @@ -159,17 +171,17 @@ app.post('/render', async (c) => { if (!existsSync('cache')) { mkdirSync('cache'); } - + writeFileSync(filename, buffer); } - + } else { return c.text('Please provide either emoji or imageUrl', 400); } if(!existsSync(filename+".bimg")) { let sanjuuniPath = "sanjuuni"; - + if(existsSync("./sanjuuni")) { sanjuuniPath = "./sanjuuni"; } @@ -182,8 +194,8 @@ app.post('/render', async (c) => { return c.text(bimg) }) -export default { +export default { port: 7427, host: "127.0.0.1", - fetch: app.fetch, -} + fetch: app.fetch, +} diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..4d4d522 --- /dev/null +++ b/openapi.json @@ -0,0 +1,144 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Sophie's convert API", + "version": "1.0.0", + "description": "API for use in Computercraft to download, convert and render media. Supports emojis, image rendering, and youtube audio." + }, + "servers": [ + { + "url": "http://127.0.0.1:7427" + } + ], + "paths": { + "/youtube": { + "post": { + "summary": "Download and convert YouTube audio", + "description": "Downloads a YouTube video matching the given `title` and `artist` and converts it to either DFPWM or MDFPWM format.", + "security": [ + { "bearerAuth": [] } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["title", "artist"], + "properties": { + "title": { + "type": "string", + "description": "Song title", + "example": "Never Gonna Give You Up" + }, + "artist": { + "type": "string", + "description": "Artist name", + "example": "Rick Astley" + }, + "mdfpwm": { + "type": "boolean", + "description": "If true, produces MDFPWM instead of DFPWM", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Audio file generated successfully", + "content": { + "audio/vnd.dfpwm": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "audio/vnd.mdfpwm": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { "description": "Missing or invalid parameters" }, + "401": { "description": "Unauthorized" }, + "500": { "description": "Internal server error" } + } + } + }, + "/render": { + "post": { + "summary": "Render an emoji or image to bimg format", + "description": "Renders an emoji (via JoyPixels) or arbitrary image to `.bimg` format using the `sanjuuni` tool. Optionally resizes the image before conversion. You cannot use both emojis and image URLs at the same time.", + "security": [ + { "bearerAuth": [] } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "size": { + "type": "string", + "pattern": "^\\d{1,4}x\\d{1,4}$", + "description": "Desired image size in format `x` (max 1000x1000)", + "example": "64x64" + }, + "extraArgs": { + "type": "string", + "description": "Extra command-line arguments to pass to sanjuuni", + "example": "-b -W 128 -H 128" + }, + "emoji": { + "type": "string", + "description": "Unicode emoji to render", + "example": "🔥" + }, + "imageUrl": { + "type": "string", + "format": "uri", + "description": "URL of an image to render", + "example": "https://example.com/image.png" + } + }, + "oneOf": [ + { "required": ["emoji"] }, + { "required": ["imageUrl"] } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Rendered `.bimg` output", + "content": { + "text/plain": { + "schema": { "type": "string" } + } + } + }, + "400": { "description": "Missing parameters or invalid size format" }, + "401": { "description": "Unauthorized" }, + "500": { "description": "Internal server error" } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "token", + "description": "Use the `Authorization` header with your API password:\n```\nAuthorization: \n```" + } + } + } +}