From 6a7ff645fed45fbfe12a53df11609268e8f8625b Mon Sep 17 00:00:00 2001 From: strawberry-raccoon <253751817+strawberry-raccoon@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:53:40 +0000 Subject: [PATCH] Add converting from URL (#11) --- public/script.js | 31 ++++++++++++++++++++++ src/index.tsx | 2 ++ src/pages/root.tsx | 14 ++++++++++ src/pages/url.tsx | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 src/pages/url.tsx diff --git a/public/script.js b/public/script.js index 8b89194..4c4c26e 100644 --- a/public/script.js +++ b/public/script.js @@ -1,4 +1,6 @@ const webroot = document.querySelector("meta[name='webroot']").content; +const urlInput = document.querySelector("#url-input"); +const urlSubmit = document.querySelector("#url-submit"); const fileInput = document.querySelector('input[type="file"]'); const dropZone = document.getElementById("dropzone"); const convertButton = document.querySelector("input[type='submit']"); @@ -33,6 +35,35 @@ dropZone.addEventListener("drop", (e) => { } }); +urlSubmit.addEventListener("click", (e) => { + e.preventDefault(); + handleUrl(urlInput.value); +}); + +function handleUrl(url) { + fetch(`${webroot}/url`, { + method: "POST", + body: JSON.stringify({ url }), + headers: { "Content-Type": "application/json" }, + }) + .then((res) => res.json()) + .then((res) => { + const fileList = document.querySelector("#file-list"); + + const row = document.createElement("tr"); + row.innerHTML = ` + ${res.filename} + + ${(res.fileSizeBytes / 1024).toFixed(2)} kB + Remove + `; + + fileList.appendChild(row); + fileNames.push(res.filename); + }) + .catch(console.error); +} + // Extracted handleFile function for reusability in drag-and-drop and file input function handleFile(file) { const fileList = document.querySelector("#file-list"); diff --git a/src/index.tsx b/src/index.tsx index c163e74..c647b98 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -16,6 +16,7 @@ import { listConverters } from "./pages/listConverters"; import { results } from "./pages/results"; import { root } from "./pages/root"; import { upload } from "./pages/upload"; +import { url } from "./pages/url"; import { user } from "./pages/user"; import { healthcheck } from "./pages/healthcheck"; @@ -41,6 +42,7 @@ const app = new Elysia({ .use(user) .use(root) .use(upload) + .use(url) .use(history) .use(convert) .use(download) diff --git a/src/pages/root.tsx b/src/pages/root.tsx index bf1a73c..9930664 100644 --- a/src/pages/root.tsx +++ b/src/pages/root.tsx @@ -152,6 +152,20 @@ export const root = new Elysia().use(userService).get( class="absolute inset-0 size-full cursor-pointer opacity-0" /> +
{ + const contentDisposition = headers.get("Content-Disposition"); + if (contentDisposition) { + const match = /filename="([^"]+)"/.exec(contentDisposition); + if (match && match[1]) { + return sanitize(match[1]); + } + } + const path = new URL(url).pathname; + const lastPart = path.split("/").at(-1); + const contentType = headers.get("content-type"); + const extension = contentType ? mime.getExtension(contentType) : null; + if (!lastPart) { + if (extension) { + return `${randomUUID()}.${extension}`; + } + return randomUUID(); + } + if (!lastPart.includes(".") && extension) { + return `${sanitize(lastPart)}.${extension}`; + } + return sanitize(lastPart); +}; + +export const url = new Elysia().use(userService).post( + "/url", + async ({ body, redirect, user, cookie: { jobId } }) => { + if (!jobId?.value) { + return redirect(`${WEBROOT}/`, 302); + } + + const existingJob = await db + .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?") + .get(jobId.value, user.id); + + if (!existingJob) { + return redirect(`${WEBROOT}/`, 302); + } + + const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; + + const res = await fetch(body.url); + if (!res.ok) { + throw new Error(`Failed to download URL, received ${res.status}`); + } + const filename = getFilename(body.url, res.headers); + const fileSizeBytes = await Bun.write(`${userUploadsDir}${filename}`, await res.blob()); + + return { + message: "Files downloaded successfully.", + filename, + fileSizeBytes, + }; + }, + { body: t.Object({ url: t.String() }), auth: true }, +);