Skip to content

Commit

Permalink
[threadContext 2] Add threadContext to threadViewPost - backend (#3313)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelbsky authored Jan 21, 2025
1 parent b1dd050 commit 6b8a51e
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 46 deletions.
51 changes: 50 additions & 1 deletion packages/bsky/src/hydration/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
split,
} from './util'
import { dedupeStrs } from '@atproto/common'
import { postUriToThreadgateUri, postUriToPostgateUri } from '../util/uris'
import {
postUriToThreadgateUri,
postUriToPostgateUri,
uriToDid as didFromUri,
} from '../util/uris'

export type Post = RecordInfo<PostRecord> & {
violatesThreadGate: boolean
Expand All @@ -32,6 +36,12 @@ export type PostViewerState = {

export type PostViewerStates = HydrationMap<PostViewerState>

export type ThreadContext = {
like?: string
}

export type ThreadContexts = HydrationMap<ThreadContext>

export type PostAgg = {
likes: number
replies: number
Expand Down Expand Up @@ -157,6 +167,45 @@ export class FeedHydrator {
}, new Map<string, boolean>())
}

async getThreadContexts(refs: ThreadRef[]): Promise<ThreadContexts> {
if (!refs.length) return new HydrationMap<ThreadContext>()

const refsByRootAuthor = refs.reduce((acc, ref) => {
const { threadRoot } = ref
const rootAuthor = didFromUri(threadRoot)
const existingValue = acc.get(rootAuthor) ?? []
return acc.set(rootAuthor, [...existingValue, ref])
}, new Map<string, ThreadRef[]>())
const refsByRootAuthorEntries = Array.from(refsByRootAuthor.entries())

const likesPromises = refsByRootAuthorEntries.map(
([rootAuthor, refsForAuthor]) =>
this.dataplane.getLikesByActorAndSubjects({
actorDid: rootAuthor,
refs: refsForAuthor.map(({ uri, cid }) => ({ uri, cid })),
}),
)

const rootAuthorsLikes = await Promise.all(likesPromises)

const likesByUri = refsByRootAuthorEntries.reduce(
(acc, [_rootAuthor, refsForAuthor], i) => {
const likesForRootAuthor = rootAuthorsLikes[i]
refsForAuthor.forEach(({ uri }, j) => {
acc.set(uri, likesForRootAuthor.uris[j])
})
return acc
},
new Map<string, string>(),
)

return refs.reduce((acc, { uri }) => {
return acc.set(uri, {
like: parseString(likesByUri.get(uri)),
})
}, new HydrationMap<ThreadContext>())
}

async getPostAggregates(refs: ItemRef[]): Promise<PostAggs> {
if (!refs.length) return new HydrationMap<PostAgg>()
const counts = await this.dataplane.getInteractionCounts({ refs })
Expand Down
87 changes: 68 additions & 19 deletions packages/bsky/src/hydration/hydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
Threadgates,
Postgates,
FeedItem,
ThreadContexts,
ThreadRef,
} from './feed'
import { ParsedLabelers } from '../util'

Expand Down Expand Up @@ -89,6 +91,7 @@ export type HydrationState = {
posts?: Posts
postAggs?: PostAggs
postViewers?: PostViewerStates
threadContexts?: ThreadContexts
postBlocks?: PostBlocks
reposts?: Reposts
follows?: Follows
Expand Down Expand Up @@ -341,29 +344,24 @@ export class Hydrator {
state: HydrationState = {},
): Promise<HydrationState> {
const uris = refs.map((ref) => ref.uri)

state.posts ??= new HydrationMap<Post>()
const addPostsToHydrationState = (posts: Posts) => {
posts.forEach((post, uri) => {
state.posts ??= new HydrationMap<Post>()
state.posts.set(uri, post)
})
}

// layer 0: the posts in the thread
const postsLayer0 = await this.feed.getPosts(
uris,
ctx.includeTakedowns,
state.posts,
)
// first level embeds plus thread roots we haven't fetched yet
const urisLayer1 = nestedRecordUrisFromPosts(postsLayer0)
addPostsToHydrationState(postsLayer0)

const additionalRootUris = rootUrisFromPosts(postsLayer0) // supports computing threadgates
const urisLayer1ByCollection = urisByCollection(urisLayer1)
const embedPostUrisLayer1 =
urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? []
const postsLayer1 = await this.feed.getPosts(
[...embedPostUrisLayer1, ...additionalRootUris],
ctx.includeTakedowns,
)
// second level embeds, ignoring any additional root uris we mixed-in to the previous layer
const urisLayer2 = nestedRecordUrisFromPosts(
postsLayer1,
embedPostUrisLayer1,
)
const urisLayer2ByCollection = urisByCollection(urisLayer2)
const embedPostUrisLayer2 =
urisLayer2ByCollection.get(ids.AppBskyFeedPost) ?? []
const threadRootUris = new Set<string>()
for (const [uri, post] of postsLayer0) {
if (post) {
Expand All @@ -385,10 +383,38 @@ export class Hydrator {
postUrisWithThreadgates.add(uri)
}
}

// layer 1: first level embeds plus thread roots we haven't fetched yet
const urisLayer1 = nestedRecordUrisFromPosts(postsLayer0)
const urisLayer1ByCollection = urisByCollection(urisLayer1)
const embedPostUrisLayer1 =
urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? []
const postsLayer1 = await this.feed.getPosts(
[...embedPostUrisLayer1, ...additionalRootUris],
ctx.includeTakedowns,
state.posts,
)
addPostsToHydrationState(postsLayer1)

// layer 2: second level embeds, ignoring any additional root uris we mixed-in to the previous layer
const urisLayer2 = nestedRecordUrisFromPosts(
postsLayer1,
embedPostUrisLayer1,
)
const urisLayer2ByCollection = urisByCollection(urisLayer2)
const embedPostUrisLayer2 =
urisLayer2ByCollection.get(ids.AppBskyFeedPost) ?? []

const [postsLayer2, threadgates] = await Promise.all([
this.feed.getPosts(embedPostUrisLayer2, ctx.includeTakedowns),
this.feed.getPosts(
embedPostUrisLayer2,
ctx.includeTakedowns,
state.posts,
),
this.feed.getThreadgatesForPosts([...postUrisWithThreadgates.values()]),
])
addPostsToHydrationState(postsLayer2)

// collect list/feedgen embeds, lists in threadgates, post record hydration
const threadgateListUris = getListUrisFromThreadgates(threadgates)
const nestedListUris = [
Expand Down Expand Up @@ -597,7 +623,29 @@ export class Hydrator {
refs: ItemRef[],
ctx: HydrateCtx,
): Promise<HydrationState> {
return this.hydratePosts(refs, ctx)
const postsState = await this.hydratePosts(refs, ctx)

const { posts } = postsState
const postsList = posts ? Array.from(posts.entries()) : []

const isDefined = (
entry: [string, Post | null],
): entry is [string, Post] => {
const [, post] = entry
return !!post
}

const threadRefs: ThreadRef[] = postsList
.filter(isDefined)
.map(([uri, post]) => ({
uri,
cid: post.cid,
threadRoot: post.record.reply?.root.uri ?? uri,
}))

const threadContexts = await this.feed.getThreadContexts(threadRefs)

return mergeStates(postsState, { threadContexts })
}

// app.bsky.feed.defs#generatorView
Expand Down Expand Up @@ -1168,6 +1216,7 @@ export const mergeStates = (
posts: mergeMaps(stateA.posts, stateB.posts),
postAggs: mergeMaps(stateA.postAggs, stateB.postAggs),
postViewers: mergeMaps(stateA.postViewers, stateB.postViewers),
threadContexts: mergeMaps(stateA.threadContexts, stateB.threadContexts),
postBlocks: mergeMaps(stateA.postBlocks, stateB.postBlocks),
reposts: mergeMaps(stateA.reposts, stateB.reposts),
follows: mergeMaps(stateA.follows, stateB.follows),
Expand Down
9 changes: 9 additions & 0 deletions packages/bsky/src/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,9 @@ export class Views {
opts.depth,
)
: undefined,
threadContext: {
rootAuthorLike: state.threadContexts?.get(post.uri)?.like,
},
}
}

Expand Down Expand Up @@ -829,6 +832,9 @@ export class Views {
$type: 'app.bsky.feed.defs#threadViewPost',
post,
parent: this.threadParent(parentUri, rootUri, state, height - 1),
threadContext: {
rootAuthorLike: state.threadContexts?.get(post.uri)?.like,
},
}
}

Expand Down Expand Up @@ -873,6 +879,9 @@ export class Views {
state,
depth - 1,
),
threadContext: {
rootAuthorLike: state.threadContexts?.get(post.uri)?.like,
},
}
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ Object {
},
},
"replies": Array [],
"threadContext": Object {},
},
}
`;
Expand Down Expand Up @@ -668,6 +669,7 @@ Object {
},
},
"replies": Array [],
"threadContext": Object {},
},
}
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
}
`;
Expand Down Expand Up @@ -215,6 +216,7 @@ Object {
},
},
"replies": Array [],
"threadContext": Object {},
},
}
`;
Expand Down Expand Up @@ -298,6 +300,7 @@ Object {
"uri": "record(7)",
},
],
"threadContext": Object {},
},
}
`;
Expand Down
5 changes: 5 additions & 0 deletions packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
}
`;
Expand Down Expand Up @@ -255,8 +256,10 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
],
"threadContext": Object {},
},
}
`;
Expand Down Expand Up @@ -412,8 +415,10 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
],
"threadContext": Object {},
},
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
Object {
"$type": "app.bsky.feed.defs#threadViewPost",
Expand Down Expand Up @@ -309,8 +310,10 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
],
"threadContext": Object {},
}
`;

Expand Down
3 changes: 3 additions & 0 deletions packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
Object {
"$type": "app.bsky.feed.defs#threadViewPost",
Expand Down Expand Up @@ -283,7 +284,9 @@ Object {
"threadMuted": false,
},
},
"threadContext": Object {},
},
],
"threadContext": Object {},
}
`;
Loading

0 comments on commit 6b8a51e

Please sign in to comment.