diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 68e28fd..2896b96 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -1,7 +1,8 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { insertUser } from '@/db/users.ts'; -import { type Filter, findReplyTag, nip19, z } from '@/deps.ts'; +import { findReplyTag, nip19, z } from '@/deps.ts'; +import { type DittoFilter } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; @@ -137,7 +138,7 @@ const accountStatusesController: AppController = async (c) => { return c.json([]); } - const filter: Filter<1> = { authors: [pubkey], kinds: [1], since, until, limit }; + const filter: DittoFilter<1> = { authors: [pubkey], kinds: [1], relations: ['author'], since, until, limit }; if (tagged) { filter['#t'] = [tagged]; } @@ -256,7 +257,7 @@ const favouritesController: AppController = async (c) => { .map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1]) .filter((id): id is string => !!id); - const events1 = await mixer.getFilters([{ kinds: [1], ids }], { timeout: Time.seconds(1) }); + const events1 = await mixer.getFilters([{ kinds: [1], ids, relations: ['author'] }], { timeout: Time.seconds(1) }); const statuses = await Promise.all(events1.map((event) => renderStatus(event, c.get('pubkey')))); return paginated(c, events1, statuses); diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index 15417c8..b0f71ce 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -1,6 +1,7 @@ import { AppController } from '@/app.ts'; import * as eventsDB from '@/db/events.ts'; -import { type Event, type Filter, nip19, z } from '@/deps.ts'; +import { type Event, nip19, z } from '@/deps.ts'; +import { type DittoFilter } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; import { booleanParamSchema } from '@/schema.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts'; @@ -65,9 +66,10 @@ const searchController: AppController = async (c) => { function searchEvents({ q, type, limit, account_id }: SearchQuery): Promise { if (type === 'hashtags') return Promise.resolve([]); - const filter: Filter = { + const filter: DittoFilter = { kinds: typeToKinds(type), search: q, + relations: ['author'], limit, }; @@ -98,8 +100,8 @@ async function lookupEvent(query: SearchQuery): Promise { } /** Get filters to lookup the input value. */ -async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise { - const filters: Filter[] = []; +async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise { + const filters: DittoFilter[] = []; const accounts = !type || type === 'accounts'; const statuses = !type || type === 'statuses'; @@ -138,7 +140,7 @@ async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise ({ ...filter, relations: ['author'] })); } export { searchController }; diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 0f6be9c..114923d 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -1,7 +1,7 @@ import { type AppController } from '@/app.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; import { type Event, ISO6391, z } from '@/deps.ts'; -import { getAncestors, getDescendants, getEvent } from '@/queries.ts'; +import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; import { createEvent, paginationSchema, parseBody } from '@/utils/web.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -29,7 +29,7 @@ const createStatusSchema = z.object({ const statusController: AppController = async (c) => { const id = c.req.param('id'); - const event = await getEvent(id, { kind: 1 }); + const event = await getEvent(id, { kind: 1, relations: ['author'] }); if (event) { return c.json(await renderStatus(event, c.get('pubkey'))); } @@ -83,12 +83,13 @@ const createStatusController: AppController = async (c) => { tags, }, c); - return c.json(await renderStatus(event, c.get('pubkey'))); + const author = await getAuthor(event.pubkey); + return c.json(await renderStatus({ ...event, author }, c.get('pubkey'))); }; const contextController: AppController = async (c) => { const id = c.req.param('id'); - const event = await getEvent(id, { kind: 1 }); + const event = await getEvent(id, { kind: 1, relations: ['author'] }); async function renderStatuses(events: Event<1>[]) { const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey')))); @@ -109,7 +110,7 @@ const contextController: AppController = async (c) => { const favouriteController: AppController = async (c) => { const id = c.req.param('id'); - const target = await getEvent(id, { kind: 1 }); + const target = await getEvent(id, { kind: 1, relations: ['author'] }); if (target) { await createEvent({ diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index d7aa677..8cdd7d4 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -1,7 +1,7 @@ import { type AppController } from '@/app.ts'; import { z } from '@/deps.ts'; import { type DittoFilter } from '@/filter.ts'; -import { getFeedPubkeys } from '@/queries.ts'; +import { getAuthor, getFeedPubkeys } from '@/queries.ts'; import { Sub } from '@/subs.ts'; import { bech32ToPubkey } from '@/utils.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -63,7 +63,8 @@ const streamingController: AppController = (c) => { if (filter) { for await (const event of Sub.sub(socket, '1', [filter])) { - const status = await renderStatus(event, pubkey); + const author = await getAuthor(event.pubkey); + const status = await renderStatus({ ...event, author }, pubkey); if (status) { send('update', status); } diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 712f741..d3ffcdc 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -34,7 +34,10 @@ const hashtagTimelineController: AppController = (c) => { /** Render statuses for timelines. */ async function renderStatuses(c: AppContext, filters: DittoFilter<1>[]) { - const events = await mixer.getFilters(filters, { timeout: Time.seconds(1) }); + const events = await mixer.getFilters( + filters.map((filter) => ({ ...filter, relations: ['author'] })), + { timeout: Time.seconds(1) }, + ); if (!events.length) { return c.json([]); diff --git a/src/db/events.ts b/src/db/events.ts index e3ac876..a941e65 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -304,4 +304,4 @@ function buildUserSearchContent(event: Event<0>): string { return [name, nip05, about].filter(Boolean).join('\n'); } -export { countFilters, deleteFilters, getFilters, insertEvent }; +export { countFilters, deleteFilters, type DittoEvent, getFilters, insertEvent }; diff --git a/src/filter.ts b/src/filter.ts index cb43e2c..e4e336f 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -44,4 +44,4 @@ function matchDittoFilters(filters: DittoFilter[], event: Event, data: EventData return false; } -export { type DittoFilter, type GetFiltersOpts, matchDittoFilters }; +export { type DittoFilter, type GetFiltersOpts, matchDittoFilters, type Relation }; diff --git a/src/queries.ts b/src/queries.ts index 95bbaab..de9bd33 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -1,5 +1,6 @@ import * as eventsDB from '@/db/events.ts'; -import { type Event, type Filter, findReplyTag } from '@/deps.ts'; +import { type Event, findReplyTag } from '@/deps.ts'; +import { type DittoFilter, type Relation } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; interface GetEventOpts { @@ -7,6 +8,8 @@ interface GetEventOpts { timeout?: number; /** Event kind. */ kind?: K; + /** Relations to include on the event. */ + relations?: Relation[]; } /** Get a Nostr event by its ID. */ @@ -14,8 +17,8 @@ const getEvent = async ( id: string, opts: GetEventOpts = {}, ): Promise | undefined> => { - const { kind, timeout = 1000 } = opts; - const filter: Filter = { ids: [id], limit: 1 }; + const { kind, relations, timeout = 1000 } = opts; + const filter: DittoFilter = { ids: [id], relations, limit: 1 }; if (kind) { filter.kinds = [kind]; } @@ -57,7 +60,7 @@ async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise const inReplyTo = replyTag ? replyTag[1] : undefined; if (inReplyTo) { - const parentEvent = await getEvent(inReplyTo, { kind: 1 }); + const parentEvent = await getEvent(inReplyTo, { kind: 1, relations: ['author'] }); if (parentEvent) { result.push(parentEvent); @@ -70,7 +73,7 @@ async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise } function getDescendants(eventId: string): Promise[]> { - return mixer.getFilters([{ kinds: [1], '#e': [eventId] }], { limit: 200, timeout: 2000 }); + return mixer.getFilters([{ kinds: [1], '#e': [eventId], relations: ['author'] }], { limit: 200, timeout: 2000 }); } /** Returns whether the pubkey is followed by a local user. */ diff --git a/src/views/mastodon/notifications.ts b/src/views/mastodon/notifications.ts index 59e5665..0e6b3c4 100644 --- a/src/views/mastodon/notifications.ts +++ b/src/views/mastodon/notifications.ts @@ -1,4 +1,5 @@ import { type Event } from '@/deps.ts'; +import { getAuthor } from '@/queries.ts'; import { nostrDate } from '@/utils.ts'; import { accountFromPubkey } from '@/views/mastodon/accounts.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -11,7 +12,8 @@ function renderNotification(event: Event, viewerPubkey?: string) { } async function renderNotificationMention(event: Event<1>, viewerPubkey?: string) { - const status = await renderStatus(event, viewerPubkey); + const author = await getAuthor(event.pubkey); + const status = await renderStatus({ ...event, author }, viewerPubkey); if (!status) return; return { diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index 131dcd6..86bdd13 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -2,7 +2,7 @@ import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ad import { Conf } from '@/config.ts'; import * as eventsDB from '@/db/events.ts'; -import { type Event, findReplyTag, nip19 } from '@/deps.ts'; +import { findReplyTag, nip19 } from '@/deps.ts'; import { getMediaLinks, parseNoteContent } from '@/note.ts'; import { getAuthor } from '@/queries.ts'; import { jsonMediaDataSchema } from '@/schemas/nostr.ts'; @@ -12,10 +12,8 @@ import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts'; import { renderEmojis } from '@/views/mastodon/emojis.ts'; -async function renderStatus(event: Event<1>, viewerPubkey?: string) { - const profile = await getAuthor(event.pubkey); - const account = profile ? await renderAccount(profile) : await accountFromPubkey(event.pubkey); - +async function renderStatus(event: eventsDB.DittoEvent<1>, viewerPubkey?: string) { + const account = event.author ? await renderAccount(event.author) : await accountFromPubkey(event.pubkey); const replyTag = findReplyTag(event); const mentionedPubkeys = [