@atproto/bsky 0.0.28 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/auto-moderator/index.d.ts +1 -14
  4. package/dist/context.d.ts +0 -3
  5. package/dist/db/index.js +34593 -3478
  6. package/dist/db/index.js.map +3 -3
  7. package/dist/db/pagination.d.ts +1 -0
  8. package/dist/index.d.ts +0 -4
  9. package/dist/index.js +1531 -1723
  10. package/dist/index.js.map +3 -3
  11. package/dist/indexer/config.d.ts +0 -8
  12. package/dist/lexicon/index.d.ts +0 -2
  13. package/dist/lexicon/lexicons.d.ts +38 -47
  14. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +2 -2
  15. package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +7 -0
  16. package/package.json +7 -6
  17. package/src/api/app/bsky/actor/getSuggestions.ts +1 -1
  18. package/src/api/app/bsky/actor/searchActors.ts +1 -0
  19. package/src/api/app/bsky/feed/getActorFeeds.ts +6 -0
  20. package/src/api/app/bsky/feed/getActorLikes.ts +4 -0
  21. package/src/api/app/bsky/feed/getAuthorFeed.ts +4 -0
  22. package/src/api/app/bsky/feed/getFeed.ts +8 -5
  23. package/src/api/app/bsky/feed/getLikes.ts +4 -0
  24. package/src/api/app/bsky/feed/getListFeed.ts +4 -0
  25. package/src/api/app/bsky/feed/getRepostedBy.ts +4 -0
  26. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +1 -1
  27. package/src/api/app/bsky/feed/getTimeline.ts +4 -0
  28. package/src/api/app/bsky/feed/searchPosts.ts +1 -0
  29. package/src/api/app/bsky/graph/getBlocks.ts +7 -0
  30. package/src/api/app/bsky/graph/getFollowers.ts +4 -0
  31. package/src/api/app/bsky/graph/getFollows.ts +4 -0
  32. package/src/api/app/bsky/graph/getList.ts +4 -0
  33. package/src/api/app/bsky/graph/getListBlocks.ts +4 -0
  34. package/src/api/app/bsky/graph/getListMutes.ts +7 -0
  35. package/src/api/app/bsky/graph/getLists.ts +7 -0
  36. package/src/api/app/bsky/graph/getMutes.ts +7 -0
  37. package/src/api/app/bsky/notification/listNotifications.ts +3 -0
  38. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +7 -1
  39. package/src/api/index.ts +0 -6
  40. package/src/auto-moderator/index.ts +9 -176
  41. package/src/context.ts +0 -6
  42. package/src/db/pagination.ts +3 -0
  43. package/src/index.ts +1 -6
  44. package/src/indexer/config.ts +0 -29
  45. package/src/lexicon/index.ts +0 -12
  46. package/src/lexicon/lexicons.ts +43 -50
  47. package/src/lexicon/types/com/atproto/admin/defs.ts +2 -0
  48. package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +13 -0
  49. package/src/logger.ts +32 -0
  50. package/tests/auto-moderator/labeler.test.ts +2 -0
  51. package/tests/feed-generation.test.ts +0 -6
  52. package/tests/views/notifications.test.ts +9 -0
  53. package/tests/views/timeline.test.ts +8 -0
  54. package/dist/api/app/bsky/feed/describeFeedGenerator.d.ts +0 -3
  55. package/dist/api/app/bsky/feed/getFeedSkeleton.d.ts +0 -3
  56. package/dist/api/app/bsky/unspecced/getTimelineSkeleton.d.ts +0 -3
  57. package/dist/auto-moderator/abyss.d.ts +0 -48
  58. package/dist/auto-moderator/fuzzy-matcher.d.ts +0 -14
  59. package/dist/feed-gen/bsky-team.d.ts +0 -3
  60. package/dist/feed-gen/hot-classic.d.ts +0 -3
  61. package/dist/feed-gen/index.d.ts +0 -2
  62. package/dist/feed-gen/mutuals.d.ts +0 -3
  63. package/dist/feed-gen/types.d.ts +0 -15
  64. package/dist/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.d.ts +0 -35
  65. package/src/api/app/bsky/feed/describeFeedGenerator.ts +0 -21
  66. package/src/api/app/bsky/feed/getFeedSkeleton.ts +0 -30
  67. package/src/api/app/bsky/unspecced/getTimelineSkeleton.ts +0 -26
  68. package/src/auto-moderator/abyss.ts +0 -114
  69. package/src/auto-moderator/fuzzy-matcher.ts +0 -126
  70. package/src/feed-gen/bsky-team.ts +0 -42
  71. package/src/feed-gen/hot-classic.ts +0 -55
  72. package/src/feed-gen/index.ts +0 -17
  73. package/src/feed-gen/mutuals.ts +0 -57
  74. package/src/feed-gen/types.ts +0 -32
  75. package/src/lexicon/types/app/bsky/unspecced/getTimelineSkeleton.ts +0 -49
  76. package/tests/algos/hot-classic.test.ts +0 -87
  77. package/tests/auto-moderator/fuzzy-matcher.test.ts +0 -163
  78. package/tests/auto-moderator/takedowns.test.ts +0 -202
@@ -1,49 +0,0 @@
1
- /**
2
- * GENERATED CODE - DO NOT MODIFY
3
- */
4
- import express from 'express'
5
- import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
- import { lexicons } from '../../../../lexicons'
7
- import { isObj, hasProp } from '../../../../util'
8
- import { CID } from 'multiformats/cid'
9
- import { HandlerAuth } from '@atproto/xrpc-server'
10
- import * as AppBskyFeedDefs from '../feed/defs'
11
-
12
- export interface QueryParams {
13
- limit: number
14
- cursor?: string
15
- }
16
-
17
- export type InputSchema = undefined
18
-
19
- export interface OutputSchema {
20
- cursor?: string
21
- feed: AppBskyFeedDefs.SkeletonFeedPost[]
22
- [k: string]: unknown
23
- }
24
-
25
- export type HandlerInput = undefined
26
-
27
- export interface HandlerSuccess {
28
- encoding: 'application/json'
29
- body: OutputSchema
30
- headers?: { [key: string]: string }
31
- }
32
-
33
- export interface HandlerError {
34
- status: number
35
- message?: string
36
- error?: 'UnknownFeed'
37
- }
38
-
39
- export type HandlerOutput = HandlerError | HandlerSuccess
40
- export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
- auth: HA
42
- params: QueryParams
43
- input: HandlerInput
44
- req: express.Request
45
- res: express.Response
46
- }
47
- export type Handler<HA extends HandlerAuth = never> = (
48
- ctx: HandlerReqCtx<HA>,
49
- ) => Promise<HandlerOutput> | HandlerOutput
@@ -1,87 +0,0 @@
1
- import AtpAgent, { AtUri } from '@atproto/api'
2
- import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
3
- import { makeAlgos } from '../../src'
4
-
5
- describe('algo hot-classic', () => {
6
- let network: TestNetwork
7
- let agent: AtpAgent
8
- let sc: SeedClient
9
-
10
- // account dids, for convenience
11
- let alice: string
12
- let bob: string
13
-
14
- const feedPublisherDid = 'did:example:feed-publisher'
15
- const feedUri = AtUri.make(
16
- feedPublisherDid,
17
- 'app.bsky.feed.generator',
18
- 'hot-classic',
19
- ).toString()
20
-
21
- beforeAll(async () => {
22
- network = await TestNetwork.create({
23
- dbPostgresSchema: 'bsky_algo_hot_classic',
24
- bsky: { algos: makeAlgos(feedPublisherDid) },
25
- })
26
- agent = new AtpAgent({ service: network.bsky.url })
27
- sc = network.getSeedClient()
28
- await basicSeed(sc)
29
-
30
- alice = sc.dids.alice
31
- bob = sc.dids.bob
32
- await network.processAll()
33
- })
34
-
35
- afterAll(async () => {
36
- await network.close()
37
- })
38
-
39
- it('returns well liked posts', async () => {
40
- const img = await sc.uploadFile(
41
- alice,
42
- '../dev-env/src/seed/img/key-landscape-small.jpg',
43
- 'image/jpeg',
44
- )
45
- const one = await sc.post(alice, 'first post', undefined, [img])
46
- const two = await sc.post(bob, 'bobby boi')
47
- const three = await sc.reply(bob, one.ref, one.ref, 'reply')
48
-
49
- for (let i = 0; i < 12; i++) {
50
- const name = `user${i}`
51
- await sc.createAccount(name, {
52
- handle: `user${i}.test`,
53
- email: `user${i}@test.com`,
54
- password: 'password',
55
- })
56
- await sc.like(sc.dids[name], one.ref)
57
- await sc.like(sc.dids[name], two.ref)
58
- await sc.like(sc.dids[name], three.ref)
59
- }
60
- await network.processAll()
61
-
62
- const res = await agent.api.app.bsky.feed.getFeed(
63
- { feed: feedUri },
64
- { headers: await network.serviceHeaders(alice) },
65
- )
66
- const feedUris = res.data.feed.map((i) => i.post.uri).sort()
67
- const expected = [one.ref.uriStr, two.ref.uriStr].sort()
68
- expect(feedUris).toEqual(expected)
69
- })
70
-
71
- it('paginates', async () => {
72
- const res = await agent.api.app.bsky.feed.getFeed(
73
- { feed: feedUri },
74
- { headers: await network.serviceHeaders(alice) },
75
- )
76
- const first = await agent.api.app.bsky.feed.getFeed(
77
- { feed: feedUri, limit: 1 },
78
- { headers: await network.serviceHeaders(alice) },
79
- )
80
- const second = await agent.api.app.bsky.feed.getFeed(
81
- { feed: feedUri, cursor: first.data.cursor },
82
- { headers: await network.serviceHeaders(alice) },
83
- )
84
-
85
- expect([...first.data.feed, ...second.data.feed]).toEqual(res.data.feed)
86
- })
87
- })
@@ -1,163 +0,0 @@
1
- import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
2
- import { FuzzyMatcher, encode } from '../../src/auto-moderator/fuzzy-matcher'
3
- import { AtpAgent } from '@atproto/api'
4
- import { ImageInvalidator } from '../../src/image/invalidator'
5
-
6
- describe('fuzzy matcher', () => {
7
- let network: TestNetwork
8
- let agent: AtpAgent
9
- let sc: SeedClient
10
- let fuzzyMatcher: FuzzyMatcher
11
-
12
- let alice: string
13
-
14
- beforeAll(async () => {
15
- network = await TestNetwork.create({
16
- dbPostgresSchema: 'fuzzy_matcher',
17
- bsky: {
18
- imgInvalidator: new NoopInvalidator(),
19
- indexer: {
20
- fuzzyMatchB64: encode(['evil']),
21
- },
22
- },
23
- })
24
- fuzzyMatcher = new FuzzyMatcher(['evil', 'mean', 'bad'], ['baddie'])
25
- agent = network.pds.getClient()
26
- sc = network.getSeedClient()
27
- await basicSeed(sc)
28
- await network.processAll()
29
- alice = sc.dids.alice
30
- })
31
-
32
- afterAll(async () => {
33
- await network.close()
34
- })
35
-
36
- const getAllReports = () => {
37
- return network.ozone.ctx.db.db
38
- .selectFrom('moderation_event')
39
- .where('action', '=', 'com.atproto.admin.defs#modEventReport')
40
- .selectAll()
41
- .orderBy('id', 'asc')
42
- .execute()
43
- }
44
-
45
- it('identifies fuzzy matches', () => {
46
- expect(fuzzyMatcher.getMatches('evil.john.test')).toMatchObject(['evil'])
47
- expect(fuzzyMatcher.getMatches('john.evil.test')).toMatchObject(['evil'])
48
- expect(fuzzyMatcher.getMatches('john.test.evil')).toMatchObject(['evil'])
49
- expect(fuzzyMatcher.getMatches('ev1l.test.john')).toMatchObject(['evil'])
50
- expect(fuzzyMatcher.getMatches('ev-1l.test.john')).toMatchObject(['evil'])
51
- expect(fuzzyMatcher.getMatches('ev-11.test.john')).toMatchObject(['evil'])
52
- expect(fuzzyMatcher.getMatches('ev.-1.l-test.john')).toMatchObject(['evil'])
53
- })
54
-
55
- it('identifies fuzzy false positivies', () => {
56
- expect(fuzzyMatcher.getMatches('john.test')).toHaveLength(0)
57
- expect(fuzzyMatcher.getMatches('good.john.test')).toHaveLength(0)
58
- expect(fuzzyMatcher.getMatches('john.baddie.test')).toHaveLength(0)
59
- })
60
-
61
- it('doesnt label any of the content in the seed', async () => {
62
- const reports = await getAllReports()
63
- expect(reports.length).toBe(0)
64
- })
65
-
66
- it('flags a handle with an unacceptable word', async () => {
67
- await sc.updateHandle(alice, 'evil.test')
68
- await network.processAll()
69
- const reports = await getAllReports()
70
- expect(reports.length).toBe(1)
71
- expect(reports.at(-1)?.subjectDid).toEqual(alice)
72
- })
73
-
74
- it('flags a profile with an unacceptable displayName', async () => {
75
- const res = await agent.api.com.atproto.repo.putRecord(
76
- {
77
- repo: alice,
78
- collection: 'app.bsky.actor.profile',
79
- rkey: 'self',
80
- record: {
81
- displayName: 'evil alice',
82
- },
83
- },
84
- { headers: sc.getHeaders(alice), encoding: 'application/json' },
85
- )
86
- await network.processAll()
87
-
88
- const reports = await getAllReports()
89
- expect(reports.length).toBe(2)
90
- expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri)
91
- expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid)
92
- })
93
-
94
- it('flags a list with an unacceptable name', async () => {
95
- const res = await agent.api.com.atproto.repo.createRecord(
96
- {
97
- repo: alice,
98
- collection: 'app.bsky.graph.list',
99
- rkey: 'list',
100
- record: {
101
- name: 'myevillist',
102
- purpose: 'app.bsky.graph.defs#modList',
103
- createdAt: new Date().toISOString(),
104
- },
105
- },
106
- { headers: sc.getHeaders(alice), encoding: 'application/json' },
107
- )
108
- await network.processAll()
109
-
110
- const reports = await getAllReports()
111
- expect(reports.length).toBe(3)
112
- expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri)
113
- expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid)
114
- })
115
-
116
- it('flags a feed generator with an unacceptable displayName', async () => {
117
- const res = await agent.api.com.atproto.repo.createRecord(
118
- {
119
- repo: alice,
120
- collection: 'app.bsky.feed.generator',
121
- rkey: 'generator',
122
- record: {
123
- did: alice,
124
- displayName: 'myevilfeed',
125
- createdAt: new Date().toISOString(),
126
- },
127
- },
128
- { headers: sc.getHeaders(alice), encoding: 'application/json' },
129
- )
130
- await network.processAll()
131
-
132
- const reports = await getAllReports()
133
- expect(reports.length).toBe(4)
134
- expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri)
135
- expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid)
136
- })
137
-
138
- it('flags a record with an unacceptable rkey', async () => {
139
- const res = await agent.api.com.atproto.repo.createRecord(
140
- {
141
- repo: alice,
142
- collection: 'app.bsky.feed.generator',
143
- rkey: 'evilrkey',
144
- record: {
145
- did: alice,
146
- displayName: 'totally fine feed',
147
- createdAt: new Date().toISOString(),
148
- },
149
- },
150
- { headers: sc.getHeaders(alice), encoding: 'application/json' },
151
- )
152
- await network.processAll()
153
-
154
- const reports = await getAllReports()
155
- expect(reports.length).toBe(5)
156
- expect(reports.at(-1)?.subjectUri).toEqual(res.data.uri)
157
- expect(reports.at(-1)?.subjectCid).toEqual(res.data.cid)
158
- })
159
- })
160
-
161
- class NoopInvalidator implements ImageInvalidator {
162
- async invalidate() {}
163
- }
@@ -1,202 +0,0 @@
1
- import fs from 'fs/promises'
2
- import { TestNetwork, SeedClient, ImageRef, usersSeed } from '@atproto/dev-env'
3
- import { AtpAgent } from '@atproto/api'
4
- import { AutoModerator } from '../../src/auto-moderator'
5
- import { sha256RawToCid } from '@atproto/common'
6
- import { CID } from 'multiformats/cid'
7
- import { AtUri } from '@atproto/syntax'
8
- import { ImageFlagger } from '../../src/auto-moderator/abyss'
9
- import { ImageInvalidator } from '../../src/image/invalidator'
10
- import { sha256 } from '@atproto/crypto'
11
- import { ids } from '../../src/lexicon/lexicons'
12
- import { TestOzone } from '@atproto/dev-env/src/ozone'
13
- import { PrimaryDatabase } from '../../src'
14
-
15
- // outside of test suite so that TestLabeler can access them
16
- let badCid1: CID | undefined = undefined
17
- let badCid2: CID | undefined = undefined
18
-
19
- describe('takedowner', () => {
20
- let network: TestNetwork
21
- let ozone: TestOzone
22
- let bskyDb: PrimaryDatabase
23
- let autoMod: AutoModerator
24
- let testInvalidator: TestInvalidator
25
- let pdsAgent: AtpAgent
26
- let sc: SeedClient
27
- let alice: string
28
- let badBlob1: ImageRef
29
- let badBlob2: ImageRef
30
- let goodBlob: ImageRef
31
-
32
- beforeAll(async () => {
33
- testInvalidator = new TestInvalidator()
34
- network = await TestNetwork.create({
35
- dbPostgresSchema: 'bsky_automod_takedown',
36
- bsky: {
37
- imgInvalidator: testInvalidator,
38
- },
39
- })
40
- ozone = network.ozone
41
- bskyDb = network.bsky.ctx.db.getPrimary()
42
- autoMod = network.bsky.indexer.ctx.autoMod
43
- autoMod.imageFlagger = new TestFlagger()
44
- pdsAgent = new AtpAgent({ service: network.pds.url })
45
- sc = network.getSeedClient()
46
- await usersSeed(sc)
47
- await network.processAll()
48
- alice = sc.dids.alice
49
- const fileBytes1 = await fs.readFile(
50
- '../dev-env/src/seed/img/key-portrait-small.jpg',
51
- )
52
- const fileBytes2 = await fs.readFile(
53
- '../dev-env/src/seed/img/key-portrait-large.jpg',
54
- )
55
- badCid1 = sha256RawToCid(await sha256(fileBytes1))
56
- badCid2 = sha256RawToCid(await sha256(fileBytes2))
57
- goodBlob = await sc.uploadFile(
58
- alice,
59
- '../dev-env/src/seed/img/key-landscape-small.jpg',
60
- 'image/jpeg',
61
- )
62
- badBlob1 = await sc.uploadFile(
63
- alice,
64
- '../dev-env/src/seed/img/key-portrait-small.jpg',
65
- 'image/jpeg',
66
- )
67
- badBlob2 = await sc.uploadFile(
68
- alice,
69
- '../dev-env/src/seed/img/key-portrait-large.jpg',
70
- 'image/jpeg',
71
- )
72
- })
73
-
74
- afterAll(async () => {
75
- await network.close()
76
- })
77
-
78
- it('takes down flagged content in posts', async () => {
79
- const post = await sc.post(alice, 'blah', undefined, [goodBlob, badBlob1])
80
- await network.processAll()
81
- const [modStatus, takedownEvent] = await Promise.all([
82
- ozone.ctx.db.db
83
- .selectFrom('moderation_subject_status')
84
- .where('did', '=', alice)
85
- .where(
86
- 'recordPath',
87
- '=',
88
- `${post.ref.uri.collection}/${post.ref.uri.rkey}`,
89
- )
90
- .select(['takendown', 'id'])
91
- .executeTakeFirst(),
92
- ozone.ctx.db.db
93
- .selectFrom('moderation_event')
94
- .where('subjectDid', '=', alice)
95
- .where('action', '=', 'com.atproto.admin.defs#modEventTakedown')
96
- .selectAll()
97
- .executeTakeFirst(),
98
- ])
99
- if (!modStatus || !takedownEvent) {
100
- throw new Error('expected mod action')
101
- }
102
- expect(modStatus.takendown).toEqual(true)
103
- const record = await bskyDb.db
104
- .selectFrom('record')
105
- .where('uri', '=', post.ref.uriStr)
106
- .select('takedownRef')
107
- .executeTakeFirst()
108
- expect(record?.takedownRef).toEqual(`BSKY-TAKEDOWN-${takedownEvent.id}`)
109
-
110
- const recordPds = await network.pds.ctx.actorStore.read(
111
- post.ref.uri.hostname,
112
- (store) =>
113
- store.db.db
114
- .selectFrom('record')
115
- .where('uri', '=', post.ref.uriStr)
116
- .select('takedownRef')
117
- .executeTakeFirst(),
118
- )
119
- expect(recordPds?.takedownRef).toEqual(`BSKY-TAKEDOWN-${takedownEvent.id}`)
120
-
121
- expect(testInvalidator.invalidated.length).toBe(1)
122
- expect(testInvalidator.invalidated[0].subject).toBe(
123
- badBlob1.image.ref.toString(),
124
- )
125
- })
126
-
127
- it('takes down flagged content in profiles', async () => {
128
- const res = await pdsAgent.api.com.atproto.repo.putRecord(
129
- {
130
- repo: alice,
131
- collection: ids.AppBskyActorProfile,
132
- rkey: 'self',
133
- record: {
134
- avatar: badBlob2.image,
135
- },
136
- },
137
- { headers: sc.getHeaders(alice), encoding: 'application/json' },
138
- )
139
- await network.processAll()
140
- const [modStatus, takedownEvent] = await Promise.all([
141
- ozone.ctx.db.db
142
- .selectFrom('moderation_subject_status')
143
- .where('did', '=', alice)
144
- .where('recordPath', '=', `${ids.AppBskyActorProfile}/self`)
145
- .select(['takendown', 'id'])
146
- .executeTakeFirst(),
147
- ozone.ctx.db.db
148
- .selectFrom('moderation_event')
149
- .where('subjectDid', '=', alice)
150
- .where(
151
- 'subjectUri',
152
- '=',
153
- AtUri.make(alice, ids.AppBskyActorProfile, 'self').toString(),
154
- )
155
- .where('action', '=', 'com.atproto.admin.defs#modEventTakedown')
156
- .selectAll()
157
- .executeTakeFirst(),
158
- ])
159
- if (!modStatus || !takedownEvent) {
160
- throw new Error('expected mod action')
161
- }
162
- expect(modStatus.takendown).toEqual(true)
163
- const recordBsky = await bskyDb.db
164
- .selectFrom('record')
165
- .where('uri', '=', res.data.uri)
166
- .select('takedownRef')
167
- .executeTakeFirst()
168
- expect(recordBsky?.takedownRef).toEqual(`BSKY-TAKEDOWN-${takedownEvent.id}`)
169
-
170
- const recordPds = await network.pds.ctx.actorStore.read(alice, (store) =>
171
- store.db.db
172
- .selectFrom('record')
173
- .where('uri', '=', res.data.uri)
174
- .select('takedownRef')
175
- .executeTakeFirst(),
176
- )
177
- expect(recordPds?.takedownRef).toEqual(`BSKY-TAKEDOWN-${takedownEvent.id}`)
178
-
179
- expect(testInvalidator.invalidated.length).toBe(2)
180
- expect(testInvalidator.invalidated[1].subject).toBe(
181
- badBlob2.image.ref.toString(),
182
- )
183
- })
184
- })
185
-
186
- class TestInvalidator implements ImageInvalidator {
187
- public invalidated: { subject: string; paths: string[] }[] = []
188
- async invalidate(subject: string, paths: string[]) {
189
- this.invalidated.push({ subject, paths })
190
- }
191
- }
192
-
193
- class TestFlagger implements ImageFlagger {
194
- async scanImage(_did: string, cid: CID, _uri: AtUri): Promise<string[]> {
195
- if (cid.equals(badCid1)) {
196
- return ['kill-it']
197
- } else if (cid.equals(badCid2)) {
198
- return ['with-fire']
199
- }
200
- return []
201
- }
202
- }