Support imeta tags

This commit is contained in:
Alex Gleason 2024-05-18 13:22:20 -05:00
parent c8f9483795
commit 7d34b9401e
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
6 changed files with 46 additions and 49 deletions

View File

@ -96,7 +96,7 @@ const createStatusController: AppController = async (c) => {
if (data.media_ids?.length) {
const media = await getUnattachedMediaByIds(kysely, data.media_ids)
.then((media) => media.filter(({ pubkey }) => pubkey === viewerPubkey))
.then((media) => media.map(({ url, data }) => ['media', url, data]));
.then((media) => media.map(({ data }) => ['imeta', ...data]));
tags.push(...media);
}

View File

@ -3,13 +3,12 @@ import uuid62 from 'uuid62';
import { DittoDB } from '@/db/DittoDB.ts';
import { DittoTables } from '@/db/DittoTables.ts';
import { type MediaData } from '@/schemas/nostr.ts';
interface UnattachedMedia {
id: string;
pubkey: string;
url: string;
data: MediaData;
data: string[][]; // NIP-94 tags
uploaded_at: number;
}

View File

@ -9,27 +9,12 @@ const signedEventSchema = n.event()
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
.refine(verifyEvent, 'Event signature is invalid');
/** Media data schema from `"media"` tags. */
const mediaDataSchema = z.object({
blurhash: z.string().optional().catch(undefined),
cid: z.string().optional().catch(undefined),
description: z.string().max(200).optional().catch(undefined),
height: z.number().int().positive().optional().catch(undefined),
mime: z.string().optional().catch(undefined),
name: z.string().optional().catch(undefined),
size: z.number().int().positive().optional().catch(undefined),
width: z.number().int().positive().optional().catch(undefined),
});
/** Kind 0 content schema for the Ditto server admin user. */
const serverMetaSchema = n.metadata().and(z.object({
tagline: z.string().optional().catch(undefined),
email: z.string().optional().catch(undefined),
}));
/** Media data from `"media"` tags. */
type MediaData = z.infer<typeof mediaDataSchema>;
/** NIP-11 Relay Information Document. */
const relayInfoDocSchema = z.object({
name: z.string().transform((val) => val.slice(0, 30)).optional().catch(undefined),
@ -47,12 +32,4 @@ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()
/** NIP-30 custom emoji tag. */
type EmojiTag = z.infer<typeof emojiTagSchema>;
export {
type EmojiTag,
emojiTagSchema,
type MediaData,
mediaDataSchema,
relayInfoDocSchema,
serverMetaSchema,
signedEventSchema,
};
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, serverMetaSchema, signedEventSchema };

View File

@ -8,26 +8,37 @@ interface FileMeta {
}
/** Upload a file, track it in the database, and return the resulting media object. */
async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal) {
const { name, type, size } = file;
async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal): Promise<string[][]> {
const { type, size } = file;
const { pubkey, description } = meta;
if (file.size > Conf.maxUploadSize) {
throw new Error('File size is too large.');
}
const { url } = await uploader.upload(file, { signal });
const { url, sha256, cid } = await uploader.upload(file, { signal });
return insertUnattachedMedia({
pubkey,
url,
data: {
name,
size,
description,
mime: type,
},
});
const data: string[][] = [
['url', url],
['m', type],
['size', size.toString()],
];
if (sha256) {
data.push(['x', sha256]);
}
if (cid) {
data.push(['cid', cid]);
}
if (description) {
data.push(['alt', description]);
}
await insertUnattachedMedia({ pubkey, url, data });
return data;
}
export { uploadFile };

View File

@ -6,15 +6,21 @@ type DittoAttachment = TypeFest.SetOptional<UnattachedMedia, 'id' | 'pubkey' | '
function renderAttachment(media: DittoAttachment) {
const { id, data, url } = media;
const m = data.find(([name]) => name === 'm')?.[1];
const alt = data.find(([name]) => name === 'alt')?.[1];
const cid = data.find(([name]) => name === 'cid')?.[1];
const blurhash = data.find(([name]) => name === 'blurhash')?.[1];
return {
id: id ?? url ?? data.cid,
type: getAttachmentType(data.mime ?? ''),
id: id ?? url,
type: getAttachmentType(m ?? ''),
url,
preview_url: url,
remote_url: null,
description: data.description ?? '',
blurhash: data.blurhash || null,
cid: data.cid,
description: alt ?? '',
blurhash: blurhash || null,
cid: cid,
};
}

View File

@ -1,4 +1,4 @@
import { NostrEvent, NSchema as n } from '@nostrify/nostrify';
import { NostrEvent } from '@nostrify/nostrify';
import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts';
import { nip19 } from 'nostr-tools';
@ -12,7 +12,6 @@ import { unfurlCardCached } from '@/utils/unfurl.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
import { renderEmojis } from '@/views/mastodon/emojis.ts';
import { mediaDataSchema } from '@/schemas/nostr.ts';
interface RenderStatusOpts {
viewerPubkey?: string;
@ -80,8 +79,13 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<
const mediaLinks = getMediaLinks(links);
const mediaTags: DittoAttachment[] = event.tags
.filter((tag) => tag[0] === 'media')
.map(([_, url, json]) => ({ url, data: n.json().pipe(mediaDataSchema).parse(json) }));
.filter(([name]) => name === 'imeta')
.map(([_, ...entries]) => {
const data = entries.map((entry) => entry.split(' '));
const url = data.find(([name]) => name === 'url')?.[1];
return { url, data };
})
.filter((media): media is DittoAttachment => !!media.url);
const media = [...mediaLinks, ...mediaTags];