Skip to content

Commit

Permalink
Remove author blocks from getLikes (#3012)
Browse files Browse the repository at this point in the history
* Capture the issue in a test case

* Remove 3p blocks in getLikes

* fix test that captures the bug

* remove unnecessary hydration

* Remove `!viewer` check
  • Loading branch information
rafaelbsky authored Nov 26, 2024
1 parent 4bf368f commit 3303ff1
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 14 deletions.
36 changes: 24 additions & 12 deletions packages/bsky/src/api/app/bsky/feed/getLikes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { normalizeDatetimeAlways } from '@atproto/syntax'
import { Server } from '../../../../lexicon'
import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getLikes'
import AppContext from '../../../../context'
import { createPipeline } from '../../../../pipeline'
import { createPipeline, RulesFnInput } from '../../../../pipeline'
import {
HydrateCtx,
HydrationState,
Hydrator,
mergeStates,
} from '../../../../hydration/hydrator'
import { Views } from '../../../../views'
import { parseString } from '../../../../hydration/util'
Expand Down Expand Up @@ -43,8 +44,10 @@ const skeleton = async (inputs: {
params: Params
}): Promise<Skeleton> => {
const { ctx, params } = inputs
const authorDid = creatorFromUri(params.uri)

if (clearlyBadCursor(params.cursor)) {
return { likes: [] }
return { authorDid, likes: [] }
}
if (looksLikeNonSortedCursor(params.cursor)) {
throw new InvalidRequestError(
Expand All @@ -57,6 +60,7 @@ const skeleton = async (inputs: {
limit: params.limit,
})
return {
authorDid,
likes: likesRes.uris,
cursor: parseString(likesRes.cursor),
}
Expand All @@ -68,18 +72,25 @@ const hydration = async (inputs: {
skeleton: Skeleton
}) => {
const { ctx, params, skeleton } = inputs
return await ctx.hydrator.hydrateLikes(skeleton.likes, params.hydrateCtx)
const likesState = await ctx.hydrator.hydrateLikes(
skeleton.authorDid,
skeleton.likes,
params.hydrateCtx,
)
return likesState
}

const noBlocks = (inputs: {
ctx: Context
skeleton: Skeleton
hydration: HydrationState
}) => {
const { ctx, skeleton, hydration } = inputs
skeleton.likes = skeleton.likes.filter((uri) => {
const creator = creatorFromUri(uri)
return !ctx.views.viewerBlockExists(creator, hydration)
const noBlocks = (input: RulesFnInput<Context, Params, Skeleton>) => {
const { ctx, skeleton, hydration } = input

skeleton.likes = skeleton.likes.filter((likeUri) => {
const like = hydration.likes?.get(likeUri)
if (!like) return false
const likerDid = creatorFromUri(likeUri)
return (
!hydration.likeBlocks?.get(likeUri) &&
!ctx.views.viewerBlockExists(likerDid, hydration)
)
})
return skeleton
}
Expand Down Expand Up @@ -123,6 +134,7 @@ type Context = {
type Params = QueryParams & { hydrateCtx: HydrateCtx }

type Skeleton = {
authorDid: string
likes: string[]
cursor?: string
}
Expand Down
30 changes: 28 additions & 2 deletions packages/bsky/src/hydration/hydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export type HydrationState = {
listViewers?: ListViewerStates
listItems?: ListItems
likes?: Likes
likeBlocks?: LikeBlocks
labels?: Labels
feedgens?: FeedGens
feedgenViewers?: FeedGenViewerStates
Expand All @@ -121,6 +122,9 @@ type PostBlockPairs = {
root?: RelationshipPair
}

export type LikeBlock = boolean
export type LikeBlocks = HydrationMap<LikeBlock>

export type FollowBlock = boolean
export type FollowBlocks = HydrationMap<FollowBlock>

Expand Down Expand Up @@ -746,12 +750,33 @@ export class Hydrator {
// - like
// - profile
// - list basic
async hydrateLikes(uris: string[], ctx: HydrateCtx): Promise<HydrationState> {
async hydrateLikes(
authorDid: string,
uris: string[],
ctx: HydrateCtx,
): Promise<HydrationState> {
const [likes, profileState] = await Promise.all([
this.feed.getLikes(uris, ctx.includeTakedowns),
this.hydrateProfiles(uris.map(didFromUri), ctx),
])
return mergeStates(profileState, { likes, ctx })

const pairs: RelationshipPair[] = []
for (const [uri, like] of likes) {
if (like) {
pairs.push([authorDid, didFromUri(uri)])
}
}
const blocks = await this.graph.getBidirectionalBlocks(pairs)
const likeBlocks = new HydrationMap<LikeBlock>()
for (const [uri, like] of likes) {
if (like) {
likeBlocks.set(uri, blocks.isBlocked(authorDid, didFromUri(uri)))
} else {
likeBlocks.set(uri, null)
}
}

return mergeStates(profileState, { likes, likeBlocks, ctx })
}

// app.bsky.feed.getRepostedBy#repostedBy
Expand Down Expand Up @@ -1154,6 +1179,7 @@ export const mergeStates = (
listViewers: mergeMaps(stateA.listViewers, stateB.listViewers),
listItems: mergeMaps(stateA.listItems, stateB.listItems),
likes: mergeMaps(stateA.likes, stateB.likes),
likeBlocks: mergeMaps(stateA.likeBlocks, stateB.likeBlocks),
labels: mergeMaps(stateA.labels, stateB.labels),
feedgens: mergeMaps(stateA.feedgens, stateB.feedgens),
feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs),
Expand Down
76 changes: 76 additions & 0 deletions packages/bsky/tests/views/likes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ describe('pds like views', () => {
// account dids, for convenience
let alice: string
let bob: string
let carol: string
let frankie: string

beforeAll(async () => {
network = await TestNetwork.create({
Expand All @@ -19,9 +21,17 @@ describe('pds like views', () => {
agent = network.bsky.getClient()
sc = network.getSeedClient()
await likesSeed(sc)
await sc.createAccount('frankie', {
handle: 'frankie.test',
email: '[email protected]',
password: 'password',
})
await network.processAll()

alice = sc.dids.alice
bob = sc.dids.bob
carol = sc.dids.carol
frankie = sc.dids.frankie
})

afterAll(async () => {
Expand Down Expand Up @@ -108,4 +118,70 @@ describe('pds like views', () => {
}),
)
})

it(`author viewer doesn't see likes by user the author blocked`, async () => {
await sc.like(frankie, sc.posts[alice][1].ref)
await network.processAll()

const beforeBlock = await agent.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await network.serviceHeaders(alice, ids.AppBskyFeedGetLikes) },
)

expect(beforeBlock.data.likes.map((like) => like.actor.did)).toStrictEqual([
sc.dids.frankie,
sc.dids.eve,
sc.dids.dan,
sc.dids.carol,
sc.dids.bob,
])

await sc.block(alice, frankie)
await network.processAll()

const afterBlock = await agent.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await network.serviceHeaders(alice, ids.AppBskyFeedGetLikes) },
)

expect(afterBlock.data.likes.map((like) => like.actor.did)).toStrictEqual([
sc.dids.eve,
sc.dids.dan,
sc.dids.carol,
sc.dids.bob,
])
})

it(`non-author viewer doesn't see likes by user the author blocked and by user the viewer blocked `, async () => {
await sc.unblock(alice, frankie)
await network.processAll()

const beforeBlock = await agent.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await network.serviceHeaders(bob, ids.AppBskyFeedGetLikes) },
)

expect(beforeBlock.data.likes.map((like) => like.actor.did)).toStrictEqual([
sc.dids.frankie,
sc.dids.eve,
sc.dids.dan,
sc.dids.carol,
sc.dids.bob,
])

await sc.block(alice, frankie)
await sc.block(bob, carol)
await network.processAll()

const afterBlock = await agent.app.bsky.feed.getLikes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: await network.serviceHeaders(bob, ids.AppBskyFeedGetLikes) },
)

expect(afterBlock.data.likes.map((like) => like.actor.did)).toStrictEqual([
sc.dids.eve,
sc.dids.dan,
sc.dids.bob,
])
})
})

0 comments on commit 3303ff1

Please sign in to comment.