diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 96d4afa..473dd63 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -1,4 +1,4 @@ -import { NostrFilter } from '@nostrify/nostrify'; +import { NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; import { z } from 'zod'; @@ -6,7 +6,6 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; -import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; import { addTag, deleteTag, findReplyTag, getTagSet } from '@/tags.ts'; import { uploadFile } from '@/upload.ts'; @@ -198,7 +197,7 @@ const updateCredentialsController: AppController = async (c) => { } const author = await getAuthor(pubkey); - const meta = author ? jsonMetaContentSchema.parse(author.content) : {}; + const meta = author ? n.json().pipe(n.metadata()).catch({}).parse(author.content) : {}; const { avatar: avatarFile, diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index d4239d3..188d68f 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -1,6 +1,8 @@ +import { NSchema as n } from '@nostrify/nostrify'; + import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { jsonServerMetaSchema } from '@/schemas/nostr.ts'; +import { serverMetaSchema } from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; const instanceController: AppController = async (c) => { @@ -8,7 +10,7 @@ const instanceController: AppController = async (c) => { const { signal } = c.req.raw; const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal }); - const meta = jsonServerMetaSchema.parse(event?.content); + const meta = n.json().pipe(serverMetaSchema).catch({}).parse(event?.content); /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; diff --git a/src/controllers/api/pleroma.ts b/src/controllers/api/pleroma.ts index 51f48dc..4b693c4 100644 --- a/src/controllers/api/pleroma.ts +++ b/src/controllers/api/pleroma.ts @@ -1,3 +1,4 @@ +import { NSchema as n } from '@nostrify/nostrify'; import { z } from 'zod'; import { type AppController } from '@/app.ts'; @@ -6,7 +7,6 @@ import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/p import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Storages } from '@/storages.ts'; import { createAdminEvent } from '@/utils/api.ts'; -import { jsonSchema } from '@/schema.ts'; const frontendConfigController: AppController = async (c) => { const configs = await getConfigs(c.req.raw.signal); @@ -75,7 +75,7 @@ async function getConfigs(signal: AbortSignal): Promise { try { const decrypted = await new AdminSigner().nip44.decrypt(Conf.pubkey, event.content); - return jsonSchema.pipe(configSchema.array()).catch([]).parse(decrypted); + return n.json().pipe(configSchema.array()).catch([]).parse(decrypted); } catch (_e) { return []; } diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index f595e22..e674d0e 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -1,10 +1,9 @@ -import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; import { z } from 'zod'; import { AppController } from '@/app.ts'; import { booleanParamSchema } from '@/schema.ts'; -import { nostrIdSchema } from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; import { dedupeEvents } from '@/utils.ts'; import { nip05Cache } from '@/utils/nip05.ts'; @@ -20,7 +19,7 @@ const searchQuerySchema = z.object({ type: z.enum(['accounts', 'statuses', 'hashtags']).optional(), resolve: booleanParamSchema.optional().transform(Boolean), following: z.boolean().default(false), - account_id: nostrIdSchema.optional(), + account_id: n.id().optional(), limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)), }); diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 2c05dbd..56ea38b 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -1,4 +1,4 @@ -import { NIP05, NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NIP05, NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import ISO6391 from 'iso-639-1'; import { nip19 } from 'nostr-tools'; import { z } from 'zod'; @@ -7,7 +7,6 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; -import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { addTag, deleteTag } from '@/tags.ts'; import { createEvent, paginationSchema, parseBody, updateListEvent } from '@/utils/api.ts'; import { renderEventAccounts } from '@/views.ts'; @@ -406,7 +405,7 @@ const zapController: AppController = async (c) => { const target = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'], signal }); const author = target?.author; - const meta = jsonMetaContentSchema.parse(author?.content); + const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); if (target && lnurl) { diff --git a/src/controllers/nostr/relay-info.ts b/src/controllers/nostr/relay-info.ts index 7f1ddf7..a56df51 100644 --- a/src/controllers/nostr/relay-info.ts +++ b/src/controllers/nostr/relay-info.ts @@ -1,12 +1,14 @@ +import { NSchema as n } from '@nostrify/nostrify'; + import { AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { jsonServerMetaSchema } from '@/schemas/nostr.ts'; +import { serverMetaSchema } from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; const relayInfoController: AppController = async (c) => { const { signal } = c.req.raw; const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal }); - const meta = jsonServerMetaSchema.parse(event?.content); + const meta = n.json().pipe(serverMetaSchema).catch({}).parse(event?.content); return c.json({ name: meta.name ?? 'Ditto', diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 2fe8f92..7d70ad9 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -1,14 +1,15 @@ -import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; +import { + NostrClientCLOSE, + NostrClientCOUNT, + NostrClientEVENT, + NostrClientMsg, + NostrClientREQ, + NostrEvent, + NostrFilter, + NSchema as n, +} from '@nostrify/nostrify'; import { relayInfoController } from '@/controllers/nostr/relay-info.ts'; import * as pipeline from '@/pipeline.ts'; -import { - type ClientCLOSE, - type ClientCOUNT, - type ClientEVENT, - type ClientMsg, - clientMsgSchema, - type ClientREQ, -} from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; import type { AppController } from '@/app.ts'; @@ -30,7 +31,7 @@ function connectStream(socket: WebSocket) { const controllers = new Map(); socket.onmessage = (e) => { - const result = n.json().pipe(clientMsgSchema).safeParse(e.data); + const result = n.json().pipe(n.clientMsg()).safeParse(e.data); if (result.success) { handleMsg(result.data); } else { @@ -45,7 +46,7 @@ function connectStream(socket: WebSocket) { }; /** Handle client message. */ - function handleMsg(msg: ClientMsg) { + function handleMsg(msg: NostrClientMsg) { switch (msg[0]) { case 'REQ': handleReq(msg); @@ -63,7 +64,7 @@ function connectStream(socket: WebSocket) { } /** Handle REQ. Start a subscription. */ - async function handleReq([_, subId, ...rest]: ClientREQ): Promise { + async function handleReq([_, subId, ...rest]: NostrClientREQ): Promise { const filters = prepareFilters(rest); const controller = new AbortController(); @@ -88,7 +89,7 @@ function connectStream(socket: WebSocket) { } /** Handle EVENT. Store the event. */ - async function handleEvent([_, event]: ClientEVENT): Promise { + async function handleEvent([_, event]: NostrClientEVENT): Promise { try { // This will store it (if eligible) and run other side-effects. await pipeline.handleEvent(event, AbortSignal.timeout(1000)); @@ -104,7 +105,7 @@ function connectStream(socket: WebSocket) { } /** Handle CLOSE. Close the subscription. */ - function handleClose([_, subId]: ClientCLOSE): void { + function handleClose([_, subId]: NostrClientCLOSE): void { const controller = controllers.get(subId); if (controller) { controller.abort(); @@ -113,7 +114,7 @@ function connectStream(socket: WebSocket) { } /** Handle COUNT. Return the number of events matching the filters. */ - async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise { + async function handleCount([_, subId, ...rest]: NostrClientCOUNT): Promise { const { count } = await Storages.db.count(prepareFilters(rest)); send(['COUNT', subId, { count, approximate: false }]); } @@ -127,7 +128,7 @@ function connectStream(socket: WebSocket) { } /** Enforce the filters with certain criteria. */ -function prepareFilters(filters: ClientREQ[2][]): NostrFilter[] { +function prepareFilters(filters: NostrClientREQ[2][]): NostrFilter[] { return filters.map((filter) => { const narrow = Boolean(filter.ids?.length || filter.authors?.length); const search = narrow ? filter.search : `domain:${Conf.url.host} ${filter.search ?? ''}`; diff --git a/src/filter.ts b/src/filter.ts index 6247378..59b0298 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,9 +1,8 @@ -import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import stringifyStable from 'fast-stable-stringify'; import { z } from 'zod'; import { isReplaceableKind } from '@/kinds.ts'; -import { nostrIdSchema } from '@/schemas/nostr.ts'; /** Microfilter to get one specific event by ID. */ type IdMicrofilter = { ids: [NostrEvent['id']] }; @@ -42,8 +41,8 @@ function getMicroFilters(event: NostrEvent): MicroFilter[] { /** Microfilter schema. */ const microFilterSchema = z.union([ - z.object({ ids: z.tuple([nostrIdSchema]) }).strict(), - z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([nostrIdSchema]) }).strict(), + z.object({ ids: z.tuple([n.id()]) }).strict(), + z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([n.id()]) }).strict(), ]); /** Checks whether the filter is a microfilter. */ diff --git a/src/pipeline.ts b/src/pipeline.ts index 16876f2..b193617 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -111,7 +111,7 @@ async function parseMetadata(event: NostrEvent, signal: AbortSignal): Promise(schema: T) { )); } -/** Parses a JSON string into its native type. */ -const jsonSchema = z.string().transform((value, ctx) => { - try { - return JSON.parse(value) as unknown; - } catch (_e) { - ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid JSON' }); - return z.NEVER; - } -}); - /** https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem */ const decode64Schema = z.string().transform((value, ctx) => { try { @@ -48,4 +38,4 @@ const booleanParamSchema = z.enum(['true', 'false']).transform((value) => value /** Schema for `File` objects. */ const fileSchema = z.custom((value) => value instanceof File); -export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, jsonSchema, safeUrlSchema }; +export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, safeUrlSchema }; diff --git a/src/schemas/nostr.ts b/src/schemas/nostr.ts index 7f51f6c..a42b9f0 100644 --- a/src/schemas/nostr.ts +++ b/src/schemas/nostr.ts @@ -1,80 +1,14 @@ +import { NSchema as n } from '@nostrify/nostrify'; import { getEventHash, verifyEvent } from 'nostr-tools'; import { z } from 'zod'; -import { jsonSchema, safeUrlSchema } from '@/schema.ts'; - -/** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */ -const nostrIdSchema = z.string().regex(/^[0-9a-f]{64}$/); -/** Nostr kinds are positive integers. */ -const kindSchema = z.number().int().nonnegative(); - -/** Nostr event schema. */ -const eventSchema = z.object({ - id: nostrIdSchema, - kind: kindSchema, - tags: z.array(z.array(z.string())), - content: z.string(), - created_at: z.number(), - pubkey: nostrIdSchema, - sig: z.string(), -}); +import { safeUrlSchema } from '@/schema.ts'; /** Nostr event schema that also verifies the event's signature. */ -const signedEventSchema = eventSchema +const signedEventSchema = n.event() .refine((event) => event.id === getEventHash(event), 'Event ID does not match hash') .refine(verifyEvent, 'Event signature is invalid'); -/** Nostr relay filter schema. */ -const filterSchema = z.object({ - kinds: kindSchema.array().optional(), - ids: nostrIdSchema.array().optional(), - authors: nostrIdSchema.array().optional(), - since: z.number().int().nonnegative().optional(), - until: z.number().int().nonnegative().optional(), - limit: z.number().int().nonnegative().optional(), - search: z.string().optional(), -}).passthrough().and( - z.record( - z.custom<`#${string}`>((val) => typeof val === 'string' && val.startsWith('#')), - z.string().array(), - ).catch({}), -); - -const clientReqSchema = z.tuple([z.literal('REQ'), z.string().min(1)]).rest(filterSchema); -const clientEventSchema = z.tuple([z.literal('EVENT'), signedEventSchema]); -const clientCloseSchema = z.tuple([z.literal('CLOSE'), z.string().min(1)]); -const clientCountSchema = z.tuple([z.literal('COUNT'), z.string().min(1)]).rest(filterSchema); - -/** Client message to a Nostr relay. */ -const clientMsgSchema = z.union([ - clientReqSchema, - clientEventSchema, - clientCloseSchema, - clientCountSchema, -]); - -/** REQ message from client to relay. */ -type ClientREQ = z.infer; -/** EVENT message from client to relay. */ -type ClientEVENT = z.infer; -/** CLOSE message from client to relay. */ -type ClientCLOSE = z.infer; -/** COUNT message from client to relay. */ -type ClientCOUNT = z.infer; -/** Client message to a Nostr relay. */ -type ClientMsg = z.infer; - -/** Kind 0 content schema. */ -const metaContentSchema = z.object({ - name: z.string().optional().catch(undefined), - about: z.string().optional().catch(undefined), - picture: z.string().optional().catch(undefined), - banner: z.string().optional().catch(undefined), - nip05: z.string().optional().catch(undefined), - lud06: z.string().optional().catch(undefined), - lud16: z.string().optional().catch(undefined), -}).partial().passthrough(); - /** Media data schema from `"media"` tags. */ const mediaDataSchema = z.object({ blurhash: z.string().optional().catch(undefined), @@ -88,40 +22,25 @@ const mediaDataSchema = z.object({ }); /** Kind 0 content schema for the Ditto server admin user. */ -const serverMetaSchema = metaContentSchema.extend({ +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; -/** Parses kind 0 content from a JSON string. */ -const jsonMetaContentSchema = jsonSchema.pipe(metaContentSchema).catch({}); - -/** Parses media data from a JSON string. */ -const jsonMediaDataSchema = jsonSchema.pipe(mediaDataSchema).catch({}); - -/** Parses server admin meta from a JSON string. */ -const jsonServerMetaSchema = jsonSchema.pipe(serverMetaSchema).catch({}); - /** NIP-11 Relay Information Document. */ const relayInfoDocSchema = z.object({ name: z.string().transform((val) => val.slice(0, 30)).optional().catch(undefined), description: z.string().transform((val) => val.slice(0, 3000)).optional().catch(undefined), - pubkey: nostrIdSchema.optional().catch(undefined), + pubkey: n.id().optional().catch(undefined), contact: safeUrlSchema.optional().catch(undefined), supported_nips: z.number().int().nonnegative().array().optional().catch(undefined), software: safeUrlSchema.optional().catch(undefined), icon: safeUrlSchema.optional().catch(undefined), }); -/** NIP-46 signer response. */ -const connectResponseSchema = z.object({ - id: z.string(), - result: signedEventSchema, -}); - /** Parses a Nostr emoji tag. */ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url()]); @@ -129,23 +48,11 @@ const emojiTagSchema = z.tuple([z.literal('emoji'), z.string(), z.string().url() type EmojiTag = z.infer; export { - type ClientCLOSE, - type ClientCOUNT, - type ClientEVENT, - type ClientMsg, - clientMsgSchema, - type ClientREQ, - connectResponseSchema, type EmojiTag, emojiTagSchema, - filterSchema, - jsonMediaDataSchema, - jsonMetaContentSchema, - jsonServerMetaSchema, type MediaData, mediaDataSchema, - metaContentSchema, - nostrIdSchema, relayInfoDocSchema, + serverMetaSchema, signedEventSchema, }; diff --git a/src/storages/events-db.ts b/src/storages/events-db.ts index 74bcb01..ba34b3c 100644 --- a/src/storages/events-db.ts +++ b/src/storages/events-db.ts @@ -1,4 +1,4 @@ -import { NIP50, NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify'; +import { NIP50, NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify'; import Debug from '@soapbox/stickynotes/debug'; import { Kysely, type SelectQueryBuilder } from 'kysely'; @@ -7,7 +7,6 @@ import { DittoTables } from '@/db/DittoTables.ts'; import { normalizeFilters } from '@/filter.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { isDittoInternalKind, isParameterizedReplaceableKind, isReplaceableKind } from '@/kinds.ts'; -import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { purifyEvent } from '@/storages/hydrate.ts'; import { isNostrId, isURL } from '@/utils.ts'; import { abortError } from '@/utils/abort.ts'; @@ -412,7 +411,7 @@ function buildSearchContent(event: NostrEvent): string { /** Build search content for a user. */ function buildUserSearchContent(event: NostrEvent): string { - const { name, nip05, about } = jsonMetaContentSchema.parse(event.content); + const { name, nip05, about } = n.json().pipe(n.metadata()).catch({}).parse(event.content); return [name, nip05, about].filter(Boolean).join('\n'); } diff --git a/src/utils.ts b/src/utils.ts index c56abb8..e9213ed 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,16 +1,13 @@ -import { NostrEvent } from '@nostrify/nostrify'; -import { EventTemplate, getEventHash, nip19 } from 'nostr-tools'; - -import { nostrIdSchema } from '@/schemas/nostr.ts'; +import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; +import { nip19 } from 'nostr-tools'; +import { z } from 'zod'; /** Get the current time in Nostr format. */ const nostrNow = (): number => Math.floor(Date.now() / 1000); + /** Convenience function to convert Nostr dates into native Date objects. */ const nostrDate = (seconds: number): Date => new Date(seconds * 1000); -/** Pass to sort() to sort events by date. */ -const eventDateComparator = (a: NostrEvent, b: NostrEvent): number => b.created_at - a.created_at; - /** Get pubkey from bech32 string, if applicable. */ function bech32ToPubkey(bech32: string): string | undefined { try { @@ -86,54 +83,20 @@ function dedupeEvents(events: NostrEvent[]): NostrEvent[] { return [...new Map(events.map((event) => [event.id, event])).values()]; } -/** Return a copy of the event with the given tags removed. */ -function stripTags(event: E, tags: string[] = []): E { - if (!tags.length) return event; - return { - ...event, - tags: event.tags.filter(([name]) => !tags.includes(name)), - }; -} - -/** Ensure the template and event match on their shared keys. */ -function eventMatchesTemplate(event: NostrEvent, template: EventTemplate): boolean { - const whitelist = ['nonce']; - - event = stripTags(event, whitelist); - template = stripTags(template, whitelist); - - if (template.created_at > event.created_at) { - return false; - } - - return getEventHash(event) === getEventHash({ - pubkey: event.pubkey, - ...template, - created_at: event.created_at, - }); -} - /** Test whether the value is a Nostr ID. */ function isNostrId(value: unknown): boolean { - return nostrIdSchema.safeParse(value).success; + return n.id().safeParse(value).success; } /** Test whether the value is a URL. */ function isURL(value: unknown): boolean { - try { - new URL(value as string); - return true; - } catch (_) { - return false; - } + return z.string().url().safeParse(value).success; } export { bech32ToPubkey, dedupeEvents, eventAge, - eventDateComparator, - eventMatchesTemplate, findTag, isNostrId, isURL, diff --git a/src/utils/nip98.ts b/src/utils/nip98.ts index 74e60a4..c33da87 100644 --- a/src/utils/nip98.ts +++ b/src/utils/nip98.ts @@ -1,13 +1,13 @@ -import { NostrEvent } from '@nostrify/nostrify'; +import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { EventTemplate, nip13 } from 'nostr-tools'; -import { decode64Schema, jsonSchema } from '@/schema.ts'; +import { decode64Schema } from '@/schema.ts'; import { signedEventSchema } from '@/schemas/nostr.ts'; import { eventAge, findTag, nostrNow, sha256 } from '@/utils.ts'; import { Time } from '@/utils/time.ts'; /** Decode a Nostr event from a base64 encoded string. */ -const decode64EventSchema = decode64Schema.pipe(jsonSchema).pipe(signedEventSchema); +const decode64EventSchema = decode64Schema.pipe(n.json()).pipe(signedEventSchema); interface ParseAuthRequestOpts { /** Max event age (in ms). */ diff --git a/src/views/activitypub/actor.ts b/src/views/activitypub/actor.ts index 9ca9a27..cfd40ba 100644 --- a/src/views/activitypub/actor.ts +++ b/src/views/activitypub/actor.ts @@ -1,5 +1,6 @@ +import { NSchema as n } from '@nostrify/nostrify'; + import { Conf } from '@/config.ts'; -import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { getPublicKeyPem } from '@/utils/rsa.ts'; import type { NostrEvent } from '@nostrify/nostrify'; @@ -7,7 +8,7 @@ import type { Actor } from '@/schemas/activitypub.ts'; /** Nostr metadata event to ActivityPub actor. */ async function renderActor(event: NostrEvent, username: string): Promise { - const content = jsonMetaContentSchema.parse(event.content); + const content = n.json().pipe(n.metadata()).catch({}).parse(event.content); return { type: 'Person', diff --git a/src/views/mastodon/accounts.ts b/src/views/mastodon/accounts.ts index 18f3a9d..e00856e 100644 --- a/src/views/mastodon/accounts.ts +++ b/src/views/mastodon/accounts.ts @@ -1,9 +1,9 @@ +import { NSchema as n } from '@nostrify/nostrify'; import { nip19, UnsignedEvent } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { lodash } from '@/deps.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; -import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { getLnurl } from '@/utils/lnurl.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { Nip05, nostrDate, nostrNow, parseNip05 } from '@/utils.ts'; @@ -28,7 +28,7 @@ async function renderAccount( about, lud06, lud16, - } = jsonMetaContentSchema.parse(event.content); + } = n.json().pipe(n.metadata()).catch({}).parse(event.content); const npub = nip19.npubEncode(pubkey); const parsed05 = await parseAndVerifyNip05(nip05, pubkey); diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index f63c501..683c667 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -1,11 +1,10 @@ -import { NostrEvent } from '@nostrify/nostrify'; +import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts'; import { nip19 } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { getMediaLinks, parseNoteContent } from '@/note.ts'; -import { jsonMediaDataSchema } from '@/schemas/nostr.ts'; import { Storages } from '@/storages.ts'; import { findReplyTag } from '@/tags.ts'; import { nostrDate } from '@/utils.ts'; @@ -13,6 +12,7 @@ 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 statusOpts { viewerPubkey?: string; @@ -78,7 +78,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise { const mediaTags: DittoAttachment[] = event.tags .filter((tag) => tag[0] === 'media') - .map(([_, url, json]) => ({ url, data: jsonMediaDataSchema.parse(json) })); + .map(([_, url, json]) => ({ url, data: n.json().pipe(mediaDataSchema).parse(json) })); const media = [...mediaLinks, ...mediaTags]; diff --git a/src/workers/trends.worker.ts b/src/workers/trends.worker.ts index 819883f..33fd1a1 100644 --- a/src/workers/trends.worker.ts +++ b/src/workers/trends.worker.ts @@ -1,8 +1,8 @@ +import { NSchema } from '@nostrify/nostrify'; import * as Comlink from 'comlink'; import { Sqlite } from '@/deps.ts'; import { hashtagSchema } from '@/schema.ts'; -import { nostrIdSchema } from '@/schemas/nostr.ts'; import { generateDateRange, Time } from '@/utils/time.ts'; interface GetTrendingTagsOpts { @@ -102,7 +102,7 @@ export const TrendsWorker = { }, addTagUsages(pubkey: string, hashtags: string[], date = new Date()): void { - const pubkey8 = nostrIdSchema.parse(pubkey).substring(0, 8); + const pubkey8 = NSchema.id().parse(pubkey).substring(0, 8); const tags = hashtagSchema.array().min(1).parse(hashtags); db.query(