Support imeta tags
This commit is contained in:
parent
c8f9483795
commit
7d34b9401e
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
Loading…
Reference in New Issue