ditto/src/utils/upload.ts
2024-11-07 11:09:20 -06:00

97 lines
2.4 KiB
TypeScript

import { HTTPException } from '@hono/hono/http-exception';
import { Stickynotes } from '@soapbox/stickynotes';
import { crypto } from '@std/crypto';
import { encodeHex } from '@std/encoding/hex';
import { encode } from 'blurhash';
import sharp from 'sharp';
import { AppContext } from '@/app.ts';
import { Conf } from '@/config.ts';
import { DittoUpload, dittoUploads } from '@/DittoUploads.ts';
const console = new Stickynotes('ditto:uploader');
interface FileMeta {
pubkey: string;
description?: string;
}
/** Upload a file, track it in the database, and return the resulting media object. */
export async function uploadFile(
c: AppContext,
file: File,
meta: FileMeta,
signal?: AbortSignal,
): Promise<DittoUpload> {
const uploader = c.get('uploader');
if (!uploader) {
throw new HTTPException(500, {
res: c.json({ error: 'No uploader configured.' }),
});
}
const { pubkey, description } = meta;
if (file.size > Conf.maxUploadSize) {
throw new Error('File size is too large.');
}
const tags = await uploader.upload(file, { signal });
const url = tags[0][1];
if (description) {
tags.push(['alt', description]);
}
const x = tags.find(([key]) => key === 'x')?.[1];
const m = tags.find(([key]) => key === 'm')?.[1];
const dim = tags.find(([key]) => key === 'dim')?.[1];
const blurhash = tags.find(([key]) => key === 'blurhash')?.[1];
if (!x) {
const sha256 = encodeHex(await crypto.subtle.digest('SHA-256', file.stream()));
tags.push(['x', sha256]);
}
if (!m) {
tags.push(['m', file.type]);
}
if (!blurhash || !dim) {
try {
const bytes = await new Response(file.stream()).bytes();
const img = sharp(bytes);
const { width, height } = await img.metadata();
if (!dim && (width && height)) {
tags.push(['dim', `${width}x${height}`]);
}
if (!blurhash && (width && height)) {
const pixels = await img
.raw()
.ensureAlpha()
.toBuffer({ resolveWithObject: false })
.then((buffer) => new Uint8ClampedArray(buffer));
const blurhash = encode(pixels, width, height, 4, 4);
tags.push(['blurhash', blurhash]);
}
} catch (e) {
console.error(`Error parsing image metadata: ${e}`);
}
}
const upload = {
id: crypto.randomUUID(),
url,
tags,
pubkey,
uploadedAt: new Date(),
};
dittoUploads.set(upload.id, upload);
return upload;
}