mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Support imeta tags
This commit is contained in:
parent
c8f9483795
commit
7d34b9401e
6 changed files with 46 additions and 49 deletions
|
|
@ -96,7 +96,7 @@ const createStatusController: AppController = async (c) => {
|
||||||
if (data.media_ids?.length) {
|
if (data.media_ids?.length) {
|
||||||
const media = await getUnattachedMediaByIds(kysely, data.media_ids)
|
const media = await getUnattachedMediaByIds(kysely, data.media_ids)
|
||||||
.then((media) => media.filter(({ pubkey }) => pubkey === viewerPubkey))
|
.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);
|
tags.push(...media);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import uuid62 from 'uuid62';
|
||||||
|
|
||||||
import { DittoDB } from '@/db/DittoDB.ts';
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { type MediaData } from '@/schemas/nostr.ts';
|
|
||||||
|
|
||||||
interface UnattachedMedia {
|
interface UnattachedMedia {
|
||||||
id: string;
|
id: string;
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
url: string;
|
url: string;
|
||||||
data: MediaData;
|
data: string[][]; // NIP-94 tags
|
||||||
uploaded_at: number;
|
uploaded_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,12 @@ const signedEventSchema = n.event()
|
||||||
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
.refine((event) => event.id === getEventHash(event), 'Event ID does not match hash')
|
||||||
.refine(verifyEvent, 'Event signature is invalid');
|
.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. */
|
/** Kind 0 content schema for the Ditto server admin user. */
|
||||||
const serverMetaSchema = n.metadata().and(z.object({
|
const serverMetaSchema = n.metadata().and(z.object({
|
||||||
tagline: z.string().optional().catch(undefined),
|
tagline: z.string().optional().catch(undefined),
|
||||||
email: 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. */
|
/** NIP-11 Relay Information Document. */
|
||||||
const relayInfoDocSchema = z.object({
|
const relayInfoDocSchema = z.object({
|
||||||
name: z.string().transform((val) => val.slice(0, 30)).optional().catch(undefined),
|
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. */
|
/** NIP-30 custom emoji tag. */
|
||||||
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
type EmojiTag = z.infer<typeof emojiTagSchema>;
|
||||||
|
|
||||||
export {
|
export { type EmojiTag, emojiTagSchema, relayInfoDocSchema, serverMetaSchema, signedEventSchema };
|
||||||
type EmojiTag,
|
|
||||||
emojiTagSchema,
|
|
||||||
type MediaData,
|
|
||||||
mediaDataSchema,
|
|
||||||
relayInfoDocSchema,
|
|
||||||
serverMetaSchema,
|
|
||||||
signedEventSchema,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,37 @@ interface FileMeta {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Upload a file, track it in the database, and return the resulting media object. */
|
/** Upload a file, track it in the database, and return the resulting media object. */
|
||||||
async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal) {
|
async function uploadFile(file: File, meta: FileMeta, signal?: AbortSignal): Promise<string[][]> {
|
||||||
const { name, type, size } = file;
|
const { type, size } = file;
|
||||||
const { pubkey, description } = meta;
|
const { pubkey, description } = meta;
|
||||||
|
|
||||||
if (file.size > Conf.maxUploadSize) {
|
if (file.size > Conf.maxUploadSize) {
|
||||||
throw new Error('File size is too large.');
|
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({
|
const data: string[][] = [
|
||||||
pubkey,
|
['url', url],
|
||||||
url,
|
['m', type],
|
||||||
data: {
|
['size', size.toString()],
|
||||||
name,
|
];
|
||||||
size,
|
|
||||||
description,
|
if (sha256) {
|
||||||
mime: type,
|
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 };
|
export { uploadFile };
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,21 @@ type DittoAttachment = TypeFest.SetOptional<UnattachedMedia, 'id' | 'pubkey' | '
|
||||||
|
|
||||||
function renderAttachment(media: DittoAttachment) {
|
function renderAttachment(media: DittoAttachment) {
|
||||||
const { id, data, url } = media;
|
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 {
|
return {
|
||||||
id: id ?? url ?? data.cid,
|
id: id ?? url,
|
||||||
type: getAttachmentType(data.mime ?? ''),
|
type: getAttachmentType(m ?? ''),
|
||||||
url,
|
url,
|
||||||
preview_url: url,
|
preview_url: url,
|
||||||
remote_url: null,
|
remote_url: null,
|
||||||
description: data.description ?? '',
|
description: alt ?? '',
|
||||||
blurhash: data.blurhash || null,
|
blurhash: blurhash || null,
|
||||||
cid: data.cid,
|
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 { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { unfurlCardCached } from '@/utils/unfurl.ts';
|
||||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||||
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
|
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
|
||||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||||
import { mediaDataSchema } from '@/schemas/nostr.ts';
|
|
||||||
|
|
||||||
interface RenderStatusOpts {
|
interface RenderStatusOpts {
|
||||||
viewerPubkey?: string;
|
viewerPubkey?: string;
|
||||||
|
|
@ -80,8 +79,13 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<
|
||||||
const mediaLinks = getMediaLinks(links);
|
const mediaLinks = getMediaLinks(links);
|
||||||
|
|
||||||
const mediaTags: DittoAttachment[] = event.tags
|
const mediaTags: DittoAttachment[] = event.tags
|
||||||
.filter((tag) => tag[0] === 'media')
|
.filter(([name]) => name === 'imeta')
|
||||||
.map(([_, url, json]) => ({ url, data: n.json().pipe(mediaDataSchema).parse(json) }));
|
.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];
|
const media = [...mediaLinks, ...mediaTags];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue