diff --git a/src/queries.ts b/src/queries.ts index 3ca805a8..e93027d9 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -6,6 +6,7 @@ import { Storages } from '@/storages.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { type DittoRelation } from '@/interfaces/DittoFilter.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; +import { fallbackAuthor } from '@/utils.ts'; import { findReplyTag, getTagSet } from '@/utils/tags.ts'; const debug = Debug('ditto:queries'); @@ -38,15 +39,21 @@ const getEvent = async ( .then(([event]) => event); }; -/** Get a Nostr `set_medatadata` event for a user's pubkey. */ -const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise => { +/** + * Get a Nostr `set_medatadata` event for a user's pubkey. + * @deprecated Use `store.query` directly. + */ +async function getAuthor(pubkey: string, opts: GetEventOpts = {}): Promise { const store = await Storages.db(); const { signal = AbortSignal.timeout(1000) } = opts; - return await store.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal }) - .then((events) => hydrateEvents({ events, store, signal })) - .then(([event]) => event); -}; + const events = await store.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal }); + const event = events[0] ?? fallbackAuthor(pubkey); + + await hydrateEvents({ events: [event], store, signal }); + + return event; +} /** Get users the given pubkey follows. */ const getFollows = async (pubkey: string, signal?: AbortSignal): Promise => { diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index a30608ca..8b2067e9 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -7,6 +7,7 @@ import { z } from 'zod'; import { DittoTables } from '@/db/DittoTables.ts'; import { Conf } from '@/config.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; +import { fallbackAuthor } from '@/utils.ts'; import { findQuoteTag } from '@/utils/tags.ts'; import { findQuoteInContent } from '@/utils/note.ts'; import { getAmount } from '@/utils/bolt11.ts'; @@ -229,19 +230,29 @@ function gatherQuotes({ events, store, signal }: HydrateOpts): Promise { +async function gatherAuthors({ events, store, signal }: HydrateOpts): Promise { const pubkeys = new Set(events.map((event) => { - if (event.kind === 9735) { + if (event.kind === 9735) { // FIXME: This code doesn't belong in this function. const pubkey = event.tags.find(([name]) => name === 'p')?.[1]; if (pubkey) return pubkey; } return event.pubkey; })); - return store.query( + const authors = await store.query( [{ kinds: [0], authors: [...pubkeys], limit: pubkeys.size }], { signal }, ); + + for (const pubkey of pubkeys) { + const author = authors.find((e) => matchFilter({ kinds: [0], authors: [pubkey] }, e)); + if (author) { + const fallback = fallbackAuthor(pubkey); + authors.push(fallback); + } + } + + return authors; } /** Collect users from the events. */ diff --git a/src/utils.ts b/src/utils.ts index ae257374..a1298de9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -74,6 +74,30 @@ function isURL(value: unknown): boolean { return z.string().url().safeParse(value).success; } -export { bech32ToPubkey, eventAge, findTag, isNostrId, isURL, type Nip05, nostrDate, nostrNow, parseNip05 }; +/** Render an empty author event so other things can stick to it. */ +function fallbackAuthor(pubkey: string): NostrEvent { + return { + kind: 0, + pubkey, + content: '', + tags: [], + created_at: nostrNow(), + id: '', + sig: '', + }; +} + +export { + bech32ToPubkey, + eventAge, + fallbackAuthor, + findTag, + isNostrId, + isURL, + type Nip05, + nostrDate, + nostrNow, + parseNip05, +}; export { Time } from '@/utils/time.ts';