From 659fb30da64a5535de4871b25fe0c4fcaed006e6 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Tue, 3 Dec 2024 21:16:30 -0500 Subject: [PATCH 1/5] appview: begin rewiring logic for applying modlist, based on owner status. --- packages/bsky/src/views/index.ts | 43 ++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index 7f61ec92a6b..c1fe3c449bf 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -35,7 +35,11 @@ import { VideoUriBuilder, parsePostgate, } from './util' -import { uriToDid as creatorFromUri, safePinnedPost } from '../util/uris' +import { + uriToDid as creatorFromUri, + safePinnedPost, + uriToDid, +} from '../util/uris' import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' import { isSelfLabels } from '../lexicon/types/com/atproto/label/defs' import { @@ -111,18 +115,33 @@ export class Views { viewerBlockExists(did: string, state: HydrationState): boolean { const actor = state.profileViewers?.get(did) if (!actor) return false - return ( - !!actor.blockedBy || - !!actor.blocking || - !!actor.blockedByList || - !!actor.blockingByList - ) + if (actor.blockedBy || actor.blocking) { + return true + } + if (actor.blockedByList) { + return this.recordActive(actor.blockedByList, state) + } + if (actor.blockingByList) { + return this.recordActive(actor.blockingByList, state) + } + return false } viewerMuteExists(did: string, state: HydrationState): boolean { const actor = state.profileViewers?.get(did) if (!actor) return false - return actor.muted || !!actor.mutedByList + if (actor.muted) { + return true + } + if (actor.mutedByList) { + return this.recordActive(actor.mutedByList, state) + } + return true + } + + recordActive(uri: string, state: HydrationState) { + const did = uriToDid(uri) + return !this.actorIsTakendown(did, state) } viewerSeesNeedsReview(did: string, state: HydrationState): boolean { @@ -282,8 +301,12 @@ export class Views { ): ProfileViewerState | undefined { const viewer = state.profileViewers?.get(did) if (!viewer) return - const blockedByUri = viewer.blockedBy || viewer.blockedByList - const blockingUri = viewer.blocking || viewer.blockingByList + const blockedByUri = + viewer.blockedBy || + (viewer.blockedByList && this.recordActive(viewer.blockedByList, state)) + const blockingUri = + viewer.blocking || + (viewer.blockingByList && this.recordActive(viewer.blockingByList, state)) const block = !!blockedByUri || !!blockingUri return { muted: viewer.muted || !!viewer.mutedByList, From 3c13ed2c053a56a139c706a2dc998483ad8884f0 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Thu, 5 Dec 2024 18:00:10 -0500 Subject: [PATCH 2/5] appview: unify logic for checking list-block/mutes --- .../src/api/app/bsky/feed/getAuthorFeed.ts | 10 ++- packages/bsky/src/views/index.ts | 77 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index d58500c07c4..74f67fec3d4 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -144,13 +144,19 @@ const noBlocksOrMutedReposts = (inputs: { }): Skeleton => { const { ctx, skeleton, hydration } = inputs const relationship = hydration.profileViewers?.get(skeleton.actor.did) - if (relationship?.blocking || relationship?.blockingByList) { + if ( + relationship && + (relationship.blocking || ctx.views.blockingByList(relationship, hydration)) + ) { throw new InvalidRequestError( `Requester has blocked actor: ${skeleton.actor.did}`, 'BlockedActor', ) } - if (relationship?.blockedBy || relationship?.blockedByList) { + if ( + relationship && + (relationship.blockedBy || ctx.views.blockedByList(relationship, hydration)) + ) { throw new InvalidRequestError( `Requester is blocked by actor: ${skeleton.actor.did}`, 'BlockedByActor', diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index c1fe3c449bf..51651641721 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -7,7 +7,7 @@ import { ProfileViewDetailed, ProfileView, ProfileViewBasic, - ViewerState as ProfileViewerState, + ViewerState as ProfileViewer, } from '../lexicon/types/app/bsky/actor/defs' import { BlockedPost, @@ -77,6 +77,7 @@ import { } from '../lexicon/types/app/bsky/labeler/defs' import { Notification } from '../proto/bsky_pb' import { postUriToThreadgateUri, postUriToPostgateUri } from '../util/uris' +import { ProfileViewerState } from '../hydration/actor' export class Views { public imgUriBuilder: ImageUriBuilder = this.opts.imgUriBuilder @@ -115,33 +116,39 @@ export class Views { viewerBlockExists(did: string, state: HydrationState): boolean { const actor = state.profileViewers?.get(did) if (!actor) return false - if (actor.blockedBy || actor.blocking) { - return true - } - if (actor.blockedByList) { - return this.recordActive(actor.blockedByList, state) - } - if (actor.blockingByList) { - return this.recordActive(actor.blockingByList, state) - } - return false + return !!( + actor.blockedBy || + actor.blocking || + this.blockedByList(actor, state) || + this.blockingByList(actor, state) + ) } viewerMuteExists(did: string, state: HydrationState): boolean { const actor = state.profileViewers?.get(did) if (!actor) return false - if (actor.muted) { - return true - } - if (actor.mutedByList) { - return this.recordActive(actor.mutedByList, state) - } - return true + return !!(actor.muted || this.mutedByList(actor, state)) + } + + blockingByList(viewer: ProfileViewerState, state: HydrationState) { + return ( + viewer.blockingByList && this.recordActive(viewer.blockingByList, state) + ) + } + + blockedByList(viewer: ProfileViewerState, state: HydrationState) { + return ( + viewer.blockedByList && this.recordActive(viewer.blockedByList, state) + ) + } + + mutedByList(viewer: ProfileViewerState, state: HydrationState) { + return viewer.mutedByList && this.recordActive(viewer.mutedByList, state) } recordActive(uri: string, state: HydrationState) { const did = uriToDid(uri) - return !this.actorIsTakendown(did, state) + return this.actorIsTakendown(did, state) ? undefined : uri } viewerSeesNeedsReview(did: string, state: HydrationState): boolean { @@ -295,28 +302,22 @@ export class Views { } } - profileViewer( - did: string, - state: HydrationState, - ): ProfileViewerState | undefined { + profileViewer(did: string, state: HydrationState): ProfileViewer | undefined { const viewer = state.profileViewers?.get(did) if (!viewer) return - const blockedByUri = - viewer.blockedBy || - (viewer.blockedByList && this.recordActive(viewer.blockedByList, state)) - const blockingUri = - viewer.blocking || - (viewer.blockingByList && this.recordActive(viewer.blockingByList, state)) + const blockedByList = this.blockedByList(viewer, state) + const blockedByUri = viewer.blockedBy || blockedByList + const blockingByList = this.blockingByList(viewer, state) + const blockingUri = viewer.blocking || blockingByList const block = !!blockedByUri || !!blockingUri + const mutedByList = this.mutedByList(viewer, state) return { - muted: viewer.muted || !!viewer.mutedByList, - mutedByList: viewer.mutedByList - ? this.listBasic(viewer.mutedByList, state) - : undefined, + muted: !!(viewer.muted || mutedByList), + mutedByList: mutedByList ? this.listBasic(mutedByList, state) : undefined, blockedBy: !!blockedByUri, blocking: blockingUri, - blockingByList: viewer.blockingByList - ? this.listBasic(viewer.blockingByList, state) + blockingByList: blockingByList + ? this.listBasic(blockingByList, state) : undefined, following: viewer.following && !block ? viewer.following : undefined, followedBy: viewer.followedBy && !block ? viewer.followedBy : undefined, @@ -346,11 +347,11 @@ export class Views { blockedProfileViewer( did: string, state: HydrationState, - ): ProfileViewerState | undefined { + ): ProfileViewer | undefined { const viewer = state.profileViewers?.get(did) if (!viewer) return - const blockedByUri = viewer.blockedBy || viewer.blockedByList - const blockingUri = viewer.blocking || viewer.blockingByList + const blockedByUri = viewer.blockedBy || this.blockedByList(viewer, state) + const blockingUri = viewer.blocking || this.blockingByList(viewer, state) return { blockedBy: !!blockedByUri, blocking: blockingUri, From 0aaef36039b7a2842cf8def2c27fbc569ff69cd2 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Fri, 6 Dec 2024 00:01:58 -0500 Subject: [PATCH 3/5] appview: apply actor takedowns for 3p list-blocks --- packages/bsky/src/hydration/graph.ts | 46 ++++++++--------- packages/bsky/src/hydration/hydrator.ts | 65 ++++++++++++++++++++----- packages/bsky/src/views/index.ts | 15 ++++-- 3 files changed, 86 insertions(+), 40 deletions(-) diff --git a/packages/bsky/src/hydration/graph.ts b/packages/bsky/src/hydration/graph.ts index 6556af42db0..a153e96ca65 100644 --- a/packages/bsky/src/hydration/graph.ts +++ b/packages/bsky/src/hydration/graph.ts @@ -46,50 +46,46 @@ export type ListAggs = HydrationMap export type RelationshipPair = [didA: string, didB: string] const dedupePairs = (pairs: RelationshipPair[]): RelationshipPair[] => { - const mapped = pairs.reduce( - (acc, cur) => { - const sorted = ([...cur] as RelationshipPair).sort() - acc[sorted.join('-')] = sorted - return acc - }, - {} as Record, - ) - return Object.values(mapped) + const deduped = pairs.reduce((acc, pair) => { + return acc.set(Blocks.key(...pair), pair) + }, new Map()) + return [...deduped.values()] } export class Blocks { - _blocks: Map = new Map() + _blocks: Map = new Map() // did:a,did:b -> block constructor() {} static key(didA: string, didB: string): string { return [didA, didB].sort().join(',') } - set(didA: string, didB: string, exists: boolean): Blocks { + set(didA: string, didB: string, block: BlockEntry): Blocks { const key = Blocks.key(didA, didB) - this._blocks.set(key, exists) + this._blocks.set(key, block) return this } - has(didA: string, didB: string): boolean { + get(didA: string, didB: string): BlockEntry | null { + if (didA === didB) return null // ignore self-blocks const key = Blocks.key(didA, didB) - return this._blocks.has(key) - } - - isBlocked(didA: string, didB: string): boolean { - if (didA === didB) return false // ignore self-blocks - const key = Blocks.key(didA, didB) - return this._blocks.get(key) ?? false + return this._blocks.get(key) ?? null } merge(blocks: Blocks): Blocks { - blocks._blocks.forEach((exists, key) => { - this._blocks.set(key, exists) + blocks._blocks.forEach((block, key) => { + this._blocks.set(key, block) }) return this } } +// No "blocking" vs. "blocked" directionality: only suitable for bidirectional block checks +export type BlockEntry = { + blockUri: string | undefined + blockListUri: string | undefined +} + export class GraphHydrator { constructor(public dataplane: DataPlaneClient) {} @@ -162,7 +158,11 @@ export class GraphHydrator { const blocks = new Blocks() for (let i = 0; i < deduped.length; i++) { const pair = deduped[i] - blocks.set(pair.a, pair.b, res.exists[i] ?? false) + const block = res.blocks[i] + blocks.set(pair.a, pair.b, { + blockUri: block.blockedBy || block.blocking || undefined, + blockListUri: block.blockedByList || block.blockingByList || undefined, + }) } return blocks } diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index 562bc9cf1e8..f812f03114a 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -43,7 +43,7 @@ import { mergeNestedMaps, mergeManyMaps, } from './util' -import { uriToDid as didFromUri } from '../util/uris' +import { uriToDid as didFromUri, uriToDid } from '../util/uris' import { FeedGenAggs, FeedGens, @@ -507,12 +507,14 @@ export class Hydrator { } } // replace embed/parent/root pairs with block state - const blocks = await this.graph.getBidirectionalBlocks(relationships) + const blocks = await this.hydrateBidirectionalBlocks( + pairsToMap(relationships), + ) for (const [uri, { embed, parent, root }] of postBlocksPairs) { postBlocks.set(uri, { - embed: !!embed && blocks.isBlocked(...embed), - parent: !!parent && blocks.isBlocked(...parent), - root: !!root && blocks.isBlocked(...root), + embed: !!embed && !!isBlocked(blocks, embed), + parent: !!parent && !!isBlocked(blocks, parent), + root: !!root && !!isBlocked(blocks, root), }) } return postBlocks @@ -708,8 +710,8 @@ export class Hydrator { ) }, ) - const blocks = await this.graph.getBidirectionalBlocks( - listCreatorMemberPairs, + const blocks = await this.hydrateBidirectionalBlocks( + pairsToMap(listCreatorMemberPairs), ) // sample top list items per starter pack based on their follows const listMemberAggs = await this.actor.getProfileAggregates(listMemberDids) @@ -724,7 +726,8 @@ export class Hydrator { // update aggregation with list items for top 12 most followed members agg.listItemSampleUris = [ ...members.listitems.filter( - (li) => ctx.viewer === creator || !blocks?.isBlocked(creator, li.did), + (li) => + ctx.viewer === creator || !isBlocked(blocks, [creator, li.did]), ), ] .sort((li1, li2) => { @@ -766,11 +769,11 @@ export class Hydrator { pairs.push([authorDid, didFromUri(uri)]) } } - const blocks = await this.graph.getBidirectionalBlocks(pairs) + const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs)) const likeBlocks = new HydrationMap() for (const [uri, like] of likes) { if (like) { - likeBlocks.set(uri, blocks.isBlocked(authorDid, didFromUri(uri))) + likeBlocks.set(uri, isBlocked(blocks, [authorDid, didFromUri(uri)])) } else { likeBlocks.set(uri, null) } @@ -850,13 +853,13 @@ export class Hydrator { pairs.push([didFromUri(uri), follow.record.subject]) } } - const blocks = await this.graph.getBidirectionalBlocks(pairs) + const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs)) const followBlocks = new HydrationMap() for (const [uri, follow] of follows) { if (follow) { followBlocks.set( uri, - blocks.isBlocked(didFromUri(uri), follow.record.subject), + isBlocked(blocks, [didFromUri(uri), follow.record.subject]), ) } else { followBlocks.set(uri, null) @@ -878,10 +881,32 @@ export class Hydrator { const result = new HydrationMap>() const blocks = await this.graph.getBidirectionalBlocks(pairs) + // lookup list authors to apply takedown status to blocklists + const listAuthorDids = new Set() + for (const [source, targets] of didMap) { + for (const target of targets) { + const block = blocks.get(source, target) + if (block?.blockListUri) { + listAuthorDids.add(uriToDid(block.blockListUri)) + } + } + } + + const activeListAuthors = await this.actor.getActors( + [...listAuthorDids], + false, + ) + for (const [source, targets] of didMap) { const didBlocks = new HydrationMap() for (const target of targets) { - didBlocks.set(target, blocks.isBlocked(source, target)) + const block = blocks.get(source, target) + const isBlocked = !!( + block?.blockUri || + (block?.blockListUri && + activeListAuthors.get(uriToDid(block.blockListUri))) + ) + didBlocks.set(target, isBlocked) } result.set(source, didBlocks) } @@ -1150,6 +1175,20 @@ const getListUrisFromThreadgates = (gates: Threadgates) => { return uris } +const isBlocked = (blocks: BidirectionalBlocks, [a, b]: RelationshipPair) => { + return blocks.get(a)?.get(b) ?? null +} + +const pairsToMap = (pairs: RelationshipPair[]): Map => { + const map = new Map() + for (const [a, b] of pairs) { + const list = map.get(a) ?? [] + list.push(b) + map.set(a, list) + } + return map +} + export const mergeStates = ( stateA: HydrationState, stateB: HydrationState, diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index 51651641721..291ad363723 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -106,9 +106,10 @@ export class Views { } actorIsTakendown(did: string, state: HydrationState): boolean { - if (state.actors?.get(did)?.takedownRef) return true - if (state.actors?.get(did)?.upstreamStatus === 'takendown') return true - if (state.actors?.get(did)?.upstreamStatus === 'suspended') return true + const actor = state.actors?.get(did) + if (actor?.takedownRef) return true + if (actor?.upstreamStatus === 'takendown') return true + if (actor?.upstreamStatus === 'suspended') return true if (state.labels?.get(did)?.isTakendown) return true return false } @@ -148,7 +149,13 @@ export class Views { recordActive(uri: string, state: HydrationState) { const did = uriToDid(uri) - return this.actorIsTakendown(did, state) ? undefined : uri + const actor = state.actors?.get(did) + if (!actor || this.actorIsTakendown(did, state)) { + // actor may not be present when takedowns are eagerly applied during hydration. + // so it's important to _try_ to hydrate the actor for records checked this way. + return + } + return uri } viewerSeesNeedsReview(did: string, state: HydrationState): boolean { From 3b5e2e5ebe367a87ee3a12677f514e8ab465547d Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Fri, 6 Dec 2024 01:16:03 -0500 Subject: [PATCH 4/5] appview: apply actor takedowns for 1p list-blocks, fix dataplane method --- .../data-plane/server/routes/relationships.ts | 104 +++++++++++------- packages/bsky/src/hydration/hydrator.ts | 20 +++- packages/bsky/tests/views/author-feed.test.ts | 2 +- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/packages/bsky/src/data-plane/server/routes/relationships.ts b/packages/bsky/src/data-plane/server/routes/relationships.ts index d6029e169e8..a291db9d20e 100644 --- a/packages/bsky/src/data-plane/server/routes/relationships.ts +++ b/packages/bsky/src/data-plane/server/routes/relationships.ts @@ -90,7 +90,7 @@ export default (db: Database): Partial> => ({ async getBlockExistence(req) { const { pairs } = req if (pairs.length === 0) { - return { exists: [] } + return { exists: [], blocks: [] } } const { ref } = db.db.dynamic const sourceRef = ref('pair.source') @@ -101,48 +101,72 @@ export default (db: Database): Partial> => ({ .select([ sql`${sourceRef}`.as('source'), sql`${targetRef}`.as('target'), + (eb) => + eb + .selectFrom('actor_block') + .whereRef('actor_block.creator', '=', sourceRef) + .whereRef('actor_block.subjectDid', '=', targetRef) + .select('uri') + .as('blocking'), + (eb) => + eb + .selectFrom('actor_block') + .whereRef('actor_block.creator', '=', targetRef) + .whereRef('actor_block.subjectDid', '=', sourceRef) + .select('uri') + .as('blockedBy'), + (eb) => + eb + .selectFrom('list_item') + .innerJoin( + 'list_block', + 'list_block.subjectUri', + 'list_item.listUri', + ) + .whereRef('list_block.creator', '=', sourceRef) + .whereRef('list_item.subjectDid', '=', targetRef) + .select('list_item.listUri') + .as('blockingByList'), + (eb) => + eb + .selectFrom('list_item') + .innerJoin( + 'list_block', + 'list_block.subjectUri', + 'list_item.listUri', + ) + .whereRef('list_block.creator', '=', targetRef) + .whereRef('list_item.subjectDid', '=', sourceRef) + .select('list_item.listUri') + .as('blockedByList'), ]) - .whereExists((qb) => - qb - .selectFrom('actor_block') - .whereRef('actor_block.creator', '=', sourceRef) - .whereRef('actor_block.subjectDid', '=', targetRef) - .select('uri'), - ) - .orWhereExists((qb) => - qb - .selectFrom('actor_block') - .whereRef('actor_block.creator', '=', targetRef) - .whereRef('actor_block.subjectDid', '=', sourceRef) - .select('uri'), - ) - .orWhereExists((qb) => - qb - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', sourceRef) - .whereRef('list_item.subjectDid', '=', targetRef) - .select('list_item.listUri'), - ) - .orWhereExists((qb) => - qb - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', targetRef) - .whereRef('list_item.subjectDid', '=', sourceRef) - .select('list_item.listUri'), - ) .execute() - const existMap = res.reduce((acc, cur) => { - const key = [cur.source, cur.target].sort().join(',') - return acc.set(key, true) - }, new Map()) - const exists = pairs.map((pair) => { - const key = [pair.a, pair.b].sort().join(',') - return existMap.get(key) === true - }) + const getKey = (a, b) => [a, b].sort().join(',') + const lookup = res.reduce((acc, cur) => { + const key = getKey(cur.source, cur.target) + return acc.set(key, cur) + }, new Map()) return { - exists, + exists: pairs.map((pair) => { + const item = lookup.get(getKey(pair.a, pair.b)) + if (!item) return false + return !!( + item.blocking || + item.blockedBy || + item.blockingByList || + item.blockedByList + ) + }), + blocks: pairs.map((pair) => { + const item = lookup.get(getKey(pair.a, pair.b)) + if (!item) return {} + return { + blockedBy: item.blockedBy || undefined, + blocking: item.blocking || undefined, + blockedByList: item.blockedByList || undefined, + blockingByList: item.blockingByList || undefined, + } + }), } }, }) diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index f812f03114a..9c6aa35c101 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -276,10 +276,11 @@ export class Hydrator { // - profile basic async hydrateLists(uris: string[], ctx: HydrateCtx): Promise { const [listsState, profilesState] = await Promise.all([ - await this.hydrateListsBasic(uris, ctx), - await this.hydrateProfilesBasic(uris.map(didFromUri), ctx), + this.hydrateListsBasic(uris, ctx, { + skipAuthors: true, // handled via author profile hydration + }), + this.hydrateProfilesBasic(uris.map(didFromUri), ctx), ]) - return mergeStates(listsState, profilesState) } @@ -288,19 +289,26 @@ export class Hydrator { async hydrateListsBasic( uris: string[], ctx: HydrateCtx, + opts?: { skipAuthors: boolean }, ): Promise { - const [lists, listAggs, listViewers, labels] = await Promise.all([ + const includeAuthorDids = opts?.skipAuthors ? [] : uris.map(uriToDid) + const [lists, listAggs, listViewers, labels, actors] = await Promise.all([ this.graph.getLists(uris, ctx.includeTakedowns), this.graph.getListAggregates(uris.map((uri) => ({ uri }))), ctx.viewer ? this.graph.getListViewerStates(uris, ctx.viewer) : undefined, - this.label.getLabelsForSubjects(uris, ctx.labelers), + this.label.getLabelsForSubjects( + [...uris, ...includeAuthorDids], + ctx.labelers, + ), + this.actor.getActors(includeAuthorDids, ctx.includeTakedowns), ]) if (!ctx.includeTakedowns) { actionTakedownLabels(uris, lists, labels) + actionTakedownLabels(includeAuthorDids, actors, labels) } - return { lists, listAggs, listViewers, labels, ctx } + return { lists, listAggs, listViewers, labels, actors, ctx } } // app.bsky.graph.defs#listItemView diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index 23f4f0072fe..64798f3ffae 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -423,7 +423,7 @@ describe('pds author feed views', () => { await sc.post(alice, 'not pinned post') const post = await createAndPinPost() await sc.post(alice, 'not pinned post') - + await network.processAll() const { data } = await agent.api.app.bsky.feed.getAuthorFeed( { actor: sc.accounts[alice].handle, includePins: true }, { From 0d1a968c73fe403cc425e3f98a5f07b70a637594 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Fri, 6 Dec 2024 01:32:27 -0500 Subject: [PATCH 5/5] appview: test takedown on modlist author, application of list --- .../bsky/tests/views/labels-takedown.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/bsky/tests/views/labels-takedown.test.ts b/packages/bsky/tests/views/labels-takedown.test.ts index 5e2e58581bb..f77e9820c8b 100644 --- a/packages/bsky/tests/views/labels-takedown.test.ts +++ b/packages/bsky/tests/views/labels-takedown.test.ts @@ -37,6 +37,16 @@ describe('bsky takedown labels', () => { sc.getHeaders(sc.dids.carol), ) carolListRef = await sc.createList(sc.dids.carol, 'carol list', 'mod') + // alice blocks dan via carol's list, and carol is takendown + await sc.addToList(sc.dids.carol, sc.dids.dan, carolListRef) + await pdsAgent.app.bsky.graph.listblock.create( + { repo: sc.dids.alice }, + { + subject: carolListRef.uriStr, + createdAt: new Date().toISOString(), + }, + sc.getHeaders(sc.dids.alice), + ) aliceGenRef = await sc.createFeedGen( sc.dids.alice, 'did:web:example.com', @@ -190,6 +200,25 @@ describe('bsky takedown labels', () => { expect(profile.viewer?.blockingByList).toBeUndefined() }) + it('author takedown halts application of mod lists', async () => { + const { data: profile } = await agent.app.bsky.actor.getProfile( + { + actor: sc.dids.dan, // blocked via carol's list, and carol is takendown + }, + { + headers: await network.serviceHeaders( + sc.dids.alice, + ids.AppBskyActorGetProfile, + ), + }, + ) + expect(profile.did).toBe(sc.dids.dan) + expect(profile.viewer).not.toBeUndefined() + expect(profile.viewer?.blockedBy).toBe(false) + expect(profile.viewer?.blocking).toBeUndefined() + expect(profile.viewer?.blockingByList).toBeUndefined() + }) + it('takesdown feed generators', async () => { const res = await agent.api.app.bsky.feed.getFeedGenerators({ feeds: [aliceGenRef.uriStr, bobGenRef.uriStr, carolGenRef.uriStr],