diff --git a/src/lib/microcosm/feed.ts b/src/lib/microcosm/feed.ts index 69b8ffb..db14695 100644 --- a/src/lib/microcosm/feed.ts +++ b/src/lib/microcosm/feed.ts @@ -201,7 +201,10 @@ export async function buildCustomFeed( batch.order.map(item => { const rec = batch.records.get(item.post) return rec - ? hydratePostFromRecord(item.post, rec, signal).catch(() => undefined) + ? hydratePostFromRecord(item.post, rec, signal, { + counts: true, + viewerDid: opts.viewerDid, + }).catch(() => undefined) : Promise.resolve(undefined) }), ) diff --git a/src/lib/microcosm/hydrate.ts b/src/lib/microcosm/hydrate.ts index ccdf1aa..e6bc0ed 100644 --- a/src/lib/microcosm/hydrate.ts +++ b/src/lib/microcosm/hydrate.ts @@ -677,28 +677,50 @@ export async function hydratePost( /** * Build a `PostView` from an ALREADY-FETCHED record (e.g. from Slingshot's - * batch feed hydration), resolving only the author. Lite by design: no counts / - * viewer state. This is what makes batched feeds fast — the post records come - * in one request and only author identities remain. + * batch feed hydration), resolving the author and (unless `opts.counts` is + * false) the like/repost/reply/quote counts + viewer state from Constellation. + * The post record is already in hand, so only the author + count lookups remain. */ export async function hydratePostFromRecord( postUri: string, recordRes: {cid?: string; value: unknown}, signal?: AbortSignal, + opts?: {counts?: boolean; viewerDid?: string}, ): Promise { const parsed = parseAtUri(postUri) if (!parsed) return undefined - const author = await hydrateProfileBasic(parsed.did, signal).catch( - () => undefined, - ) + const wantCounts = opts?.counts ?? false + + const [author, likes, reposts, replies, quotes, viewer, embed] = + await Promise.all([ + hydrateProfileBasic(parsed.did, signal).catch(() => undefined), + wantCounts + ? constellation.likeCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.repostCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.replyCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.quoteCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts && opts?.viewerDid + ? hydrateViewerState(postUri, opts.viewerDid, signal).catch( + () => undefined, + ) + : Promise.resolve(undefined), + hydrateEmbed( + parsed.did, + (recordRes.value as AppBskyFeedPost.Record)?.embed, + MAX_QUOTE_DEPTH, + signal, + ).catch(() => undefined) as Promise, + ]) + if (!author) return undefined const record = recordRes.value as AppBskyFeedPost.Record - const embed = (await hydrateEmbed( - parsed.did, - record?.embed, - MAX_QUOTE_DEPTH, - signal, - ).catch(() => undefined)) as AppBskyFeedDefs.PostView['embed'] return { $type: 'app.bsky.feed.defs#postView', uri: postUri, @@ -706,7 +728,12 @@ export async function hydratePostFromRecord( author, record, embed, + likeCount: likes?.total, + repostCount: reposts?.total, + replyCount: replies?.total, + quoteCount: quotes?.total, indexedAt: record?.createdAt ?? new Date().toISOString(), + viewer, } }