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```"
+ }
+ }
+ }
+}