import { open } from "node:fs/promises"; import { basename } from "node:path"; const origin = (process.env.STASHBAY_ORIGIN ?? "https://stashbay.net").replace(/\/$/, ""); const key = process.env.STASHBAY_API_KEY; const path = process.argv[2]; if (!key || !path) throw new Error("Set STASHBAY_API_KEY and run: node upload.mjs FILE"); async function api(path, body) { const response = await fetch(`${origin}/api/v1${path}`, { method: "POST", headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" }, body: JSON.stringify(body), redirect: "error", }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); return response.json(); } const file = await open(path, "r"); try { const { size } = await file.stat(); const plan = await api("/uploads/init", { filename: basename(path), sizeBytes: size }); console.error(`Upload ID: ${plan.fileId}`); if (!Number.isSafeInteger(plan.partSize) || plan.partSize < 1 || plan.partSize > 64 * 1024 * 1024) { throw new Error("Unexpected part size"); } const parts = []; for (let number = 1; number <= plan.partCount; number++) { const offset = (number - 1) * plan.partSize; const bytes = Buffer.alloc(Math.min(plan.partSize, size - offset)); let filled = 0; while (filled < bytes.length) { const { bytesRead } = await file.read(bytes, filled, bytes.length - filled, offset + filled); if (!bytesRead) throw new Error("File changed during upload"); filled += bytesRead; } const address = plan.single ? plan.uploadUrl : (await api("/uploads/parts", { fileId: plan.fileId, from: number, count: 1 })).parts[0].url; const url = new URL(address, origin); const headers = {}; if (plan.strategy === "proxy" && url.origin === new URL(origin).origin) { headers.Authorization = `Bearer ${key}`; } else if (plan.strategy !== "presigned" || url.protocol !== "https:") { throw new Error("Unexpected upload URL or strategy"); } if (plan.single && plan.strategy === "presigned") headers["If-None-Match"] = "*"; const response = await fetch(url, { method: "PUT", headers, body: bytes, redirect: "error" }); if (!response.ok) throw new Error(`PUT ${response.status}: ${await response.text()}`); if (!plan.single) { const etag = plan.strategy === "proxy" ? (await response.json()).etag : response.headers.get("etag"); if (!etag) throw new Error("Missing part ETag"); parts.push({ partNumber: number, etag: etag.replace(/^"|"$/g, "") }); } } const result = await api("/uploads/complete", { fileId: plan.fileId, parts }); console.log(result.shareUrl ?? JSON.stringify(result)); } finally { await file.close(); }