diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index b008077e2b8..fa2226fb0ba 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -54,7 +54,10 @@ const hydration = async (input: { skeleton: SkeletonState }) => { const { ctx, params, skeleton } = input - return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], params.hydrateCtx) + return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], { + ...params.hydrateCtx, + includeTakedowns: true, + }) } const presentation = (input: { diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index 67a9bee51ef..52422d8dde1 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -61,7 +61,9 @@ export class Views { // ------------ actorIsTakendown(did: string, state: HydrationState): boolean { - return !!state.actors?.get(did)?.takedownRef + if (state.actors?.get(did)?.takedownRef) return true + if (state.labels?.get(did)?.isTakendown) return true + return false } viewerBlockExists(did: string, state: HydrationState): boolean { diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts index dc7f8243a58..b7118c75a34 100644 --- a/packages/bsky/tests/views/takedown-labels.test.ts +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -1,11 +1,19 @@ import AtpAgent from '@atproto/api' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' +import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' describe('bsky takedown labels', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient + let takendownSubjects: string[] + + let aliceListRef: RecordRef + let carolListRef: RecordRef + let aliceGenRef: RecordRef + let bobGenRef: RecordRef + let carolGenRef: RecordRef + beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_views_takedown_labels', @@ -13,18 +21,36 @@ describe('bsky takedown labels', () => { agent = network.bsky.getClient() sc = network.getSeedClient() await basicSeed(sc) - await network.processAll() - }) - afterAll(async () => { - await network.close() - }) + aliceListRef = await sc.createList(sc.dids.alice, 'alice list', 'mod') + carolListRef = await sc.createList(sc.dids.carol, 'carol list', 'mod') + aliceGenRef = await sc.createFeedGen( + sc.dids.alice, + 'did:web:example.com', + 'alice generator', + ) + bobGenRef = await sc.createFeedGen( + sc.dids.bob, + 'did:web:example.com', + 'bob generator', + ) + carolGenRef = await sc.createFeedGen( + sc.dids.carol, + 'did:web:example.com', + 'carol generator', + ) + + await network.processAll() - const addTakedownLabels = async (subjects: string[]) => { - if (subjects.length === 0) return + takendownSubjects = [ + sc.posts[sc.dids.alice][0].ref.uriStr, + sc.dids.carol, + aliceListRef.uriStr, + aliceGenRef.uriStr, + ] const src = network.ozone.ctx.cfg.service.did const cts = new Date().toISOString() - const labels = subjects.map((uri) => ({ + const labels = takendownSubjects.map((uri) => ({ src, uri, cid: '', @@ -34,19 +60,25 @@ describe('bsky takedown labels', () => { })) await network.bsky.db.db.insertInto('label').values(labels).execute() - } - const clearTakedownLabels = async () => { - await network.bsky.db.db - .deleteFrom('label') - .where('val', '=', '!takedown') - .execute() - } + }) + + afterAll(async () => { + await network.close() + }) + + it('takesdown profiles', async () => { + const attempt = agent.api.app.bsky.actor.getProfile({ + actor: sc.dids.carol, + }) + await expect(attempt).rejects.toThrow('Account has been suspended') + const res = await agent.api.app.bsky.actor.getProfiles({ + actors: [sc.dids.alice, sc.dids.bob, sc.dids.carol], + }) + expect(res.data.profiles.length).toBe(2) + expect(res.data.profiles.some((p) => p.did === sc.dids.carol)).toBe(false) + }) it('takesdown posts', async () => { - await addTakedownLabels([ - sc.posts[sc.dids.alice][0].ref.uriStr, - sc.dids.carol, - ]) const uris = [ sc.posts[sc.dids.alice][0].ref.uriStr, sc.posts[sc.dids.alice][1].ref.uriStr, @@ -55,21 +87,48 @@ describe('bsky takedown labels', () => { sc.posts[sc.dids.dan][1].ref.uriStr, sc.replies[sc.dids.alice][0].ref.uriStr, ] - const posts = await agent.api.app.bsky.feed.getPosts( - { uris }, - { headers: await network.serviceHeaders(sc.dids.alice) }, - ) + const res = await agent.api.app.bsky.feed.getPosts({ uris }) - expect(posts.data.posts.length).toBe(4) - expect(posts.data.posts.some((p) => p.author.did === sc.dids.carol)).toBe( + expect(res.data.posts.length).toBe(4) + expect(res.data.posts.some((p) => p.author.did === sc.dids.carol)).toBe( false, ) expect( - posts.data.posts.some( + res.data.posts.some( (p) => p.uri === sc.posts[sc.dids.alice][0].ref.uriStr, ), ).toBe(false) + }) - await clearTakedownLabels() + it('takesdown lists', async () => { + // record takedown + const attempt1 = agent.api.app.bsky.graph.getList({ + list: aliceListRef.uriStr, + }) + await expect(attempt1).rejects.toThrow('List not found') + + // actor takedown + const attempt2 = agent.api.app.bsky.graph.getList({ + list: carolListRef.uriStr, + }) + await expect(attempt2).rejects.toThrow('List not found') + }) + + it('takesdown feed generators', async () => { + const res = await agent.api.app.bsky.feed.getFeedGenerators({ + feeds: [aliceGenRef.uriStr, bobGenRef.uriStr, carolGenRef.uriStr], + }) + expect(res.data.feeds.length).toBe(1) + expect(res.data.feeds.at(0)?.uri).toEqual(bobGenRef.uriStr) + }) + + it('only applies if the relevant labeler is configured', async () => { + const res = await agent.api.app.bsky.actor.getProfile( + { + actor: sc.dids.carol, + }, + { headers: { 'atproto-accept-labelers': 'did:web:example.com' } }, + ) + expect(res.data.did).toEqual(sc.dids.carol) }) }) diff --git a/packages/dev-env/src/seed/client.ts b/packages/dev-env/src/seed/client.ts index 1e5a9ae10d4..f4c2283543d 100644 --- a/packages/dev-env/src/seed/client.ts +++ b/packages/dev-env/src/seed/client.ts @@ -81,6 +81,10 @@ export class SeedClient< string, Record }> > + feedgens: Record< + string, + Record }> + > dids: Record constructor(public network: Network, public agent: AtpAgent) { @@ -93,6 +97,7 @@ export class SeedClient< this.replies = {} this.reposts = {} this.lists = {} + this.feedgens = {} this.dids = {} } @@ -396,6 +401,25 @@ export class SeedClient< return ref } + async createFeedGen(by: string, feedDid: string, name: string) { + const res = await this.agent.api.app.bsky.feed.generator.create( + { repo: by }, + { + did: feedDid, + displayName: name, + createdAt: new Date().toISOString(), + }, + this.getHeaders(by), + ) + this.feedgens[by] ??= {} + const ref = new RecordRef(res.uri, res.cid) + this.feedgens[by][ref.uriStr] = { + ref: ref, + items: {}, + } + return ref + } + async addToList(by: string, subject: string, list: RecordRef) { const res = await this.agent.api.app.bsky.graph.listitem.create( { repo: by },