diff --git a/fixtures/nostrbuild-gif.json b/fixtures/nostrbuild-gif.json new file mode 100644 index 0000000..49a969a --- /dev/null +++ b/fixtures/nostrbuild-gif.json @@ -0,0 +1,34 @@ +{ + "status": "success", + "message": "Upload successful.", + "data": [ + { + "input_name": "APIv2", + "name": "e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "sha256": "0a71f1c9dd982079bc52e96403368209cbf9507c5f6956134686f56e684b6377", + "original_sha256": "e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3", + "type": "picture", + "mime": "image/gif", + "size": 1796276, + "blurhash": "LGH-S^Vwm]x]04kX-qR-R]SL5FxZ", + "dimensions": { + "width": 360, + "height": 216 + }, + "dimensionsString": "360x216", + "url": "https://image.nostr.build/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "thumbnail": "https://image.nostr.build/thumb/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "responsive": { + "240p": "https://image.nostr.build/resp/240p/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "360p": "https://image.nostr.build/resp/360p/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "480p": "https://image.nostr.build/resp/480p/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "720p": "https://image.nostr.build/resp/720p/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif", + "1080p": "https://image.nostr.build/resp/1080p/e5f6e0e380536780efa774e8d3c8a5a040e3f9f99dbb48910b261c32872ee3a3.gif" + }, + "metadata": { + "date:create": "2024-05-18T02:11:39+00:00", + "date:modify": "2024-05-18T02:11:39+00:00" + } + } + ] +} \ No newline at end of file diff --git a/fixtures/nostrbuild-mp3.json b/fixtures/nostrbuild-mp3.json new file mode 100644 index 0000000..42a60b4 --- /dev/null +++ b/fixtures/nostrbuild-mp3.json @@ -0,0 +1,29 @@ +{ + "status": "success", + "message": "Upload successful.", + "data": [ + { + "id": 0, + "input_name": "APIv2", + "name": "f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "url": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "thumbnail": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "responsive": { + "240p": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "360p": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "480p": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "720p": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3", + "1080p": "https://media.nostr.build/av/f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725.mp3" + }, + "blurhash": "", + "sha256": "f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725", + "original_sha256": "f94665e6877741feb3fa3031342f95ae2ee00caae1cc651ce31ed6d524e05725", + "type": "video", + "mime": "audio/mpeg", + "size": 1519616, + "metadata": [], + "dimensions": [], + "dimensionsString": "0x0" + } + ] +} \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 6fe62b9..f3b472e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -136,6 +136,10 @@ class Conf { return Deno.env.get('IPFS_API_URL') || 'http://localhost:5001'; }, }; + /** nostr.build API endpoint when the `nostrbuild` uploader is used. */ + static get nostrbuildEndpoint(): string { + return Deno.env.get('NOSTRBUILD_ENDPOINT') || 'https://nostr.build/api/v2/upload/files'; + } /** Module to upload files with. */ static get uploader() { return Deno.env.get('DITTO_UPLOADER'); diff --git a/src/schemas/nostrbuild.ts b/src/schemas/nostrbuild.ts new file mode 100644 index 0000000..c9fd680 --- /dev/null +++ b/src/schemas/nostrbuild.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +export const nostrbuildFileSchema = z.object({ + name: z.string(), + url: z.string().url(), + thumbnail: z.string(), + blurhash: z.string(), + sha256: z.string(), + mime: z.string(), + dimensions: z.object({ + width: z.number(), + height: z.number(), + }), +}); + +export const nostrbuildSchema = z.object({ + data: nostrbuildFileSchema.array().min(1), +}); diff --git a/src/upload.ts b/src/upload.ts index 40184f0..5b43f39 100644 --- a/src/upload.ts +++ b/src/upload.ts @@ -16,7 +16,7 @@ async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal): Pro throw new Error('File size is too large.'); } - const { url, sha256, cid } = await uploader.upload(file, { signal }); + const { url, sha256, cid, blurhash } = await uploader.upload(file, { signal }); const data: string[][] = [ ['url', url], @@ -32,6 +32,10 @@ async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal): Pro data.push(['cid', cid]); } + if (blurhash) { + data.push(['blurhash', blurhash]); + } + if (description) { data.push(['alt', description]); } diff --git a/src/uploaders/config.ts b/src/uploaders/config.ts index 5b4c7af..8ce22b6 100644 --- a/src/uploaders/config.ts +++ b/src/uploaders/config.ts @@ -2,6 +2,7 @@ import { Conf } from '@/config.ts'; import { ipfsUploader } from '@/uploaders/ipfs.ts'; import { localUploader } from '@/uploaders/local.ts'; +import { nostrbuildUploader } from '@/uploaders/nostrbuild.ts'; import { s3Uploader } from '@/uploaders/s3.ts'; import type { Uploader } from './types.ts'; @@ -25,6 +26,8 @@ function uploader() { return ipfsUploader; case 'local': return localUploader; + case 'nostrbuild': + return nostrbuildUploader; default: throw new Error('No `DITTO_UPLOADER` configured. Uploads are disabled.'); } diff --git a/src/uploaders/nostrbuild.ts b/src/uploaders/nostrbuild.ts new file mode 100644 index 0000000..d9eed24 --- /dev/null +++ b/src/uploaders/nostrbuild.ts @@ -0,0 +1,33 @@ +import { Conf } from '@/config.ts'; +import { nostrbuildSchema } from '@/schemas/nostrbuild.ts'; + +import type { Uploader } from './types.ts'; + +/** nostr.build uploader. */ +export const nostrbuildUploader: Uploader = { + async upload(file) { + const formData = new FormData(); + formData.append('fileToUpload', file); + + const response = await fetch(Conf.nostrbuildEndpoint, { + method: 'POST', + body: formData, + }); + + const json = await response.json(); + console.log(JSON.stringify(json)); + + const [data] = nostrbuildSchema.parse(json).data; + + return { + id: data.url, + sha256: data.sha256, + url: data.url, + blurhash: data.blurhash, + }; + }, + // deno-lint-ignore require-await + async delete(): Promise { + return; + }, +}; diff --git a/src/uploaders/types.ts b/src/uploaders/types.ts index c514ad1..ac5bf05 100644 --- a/src/uploaders/types.ts +++ b/src/uploaders/types.ts @@ -14,6 +14,8 @@ interface UploadResult { url: string; /** SHA-256 hash of the file. */ sha256?: string; + /** Blurhash of the file. */ + blurhash?: string; /** IPFS CID of the file. */ cid?: string; }