@atproto/ozone 0.0.16 → 0.0.17-next.0

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 (66) hide show
  1. package/dist/api/moderation/util.d.ts +1 -1
  2. package/dist/auth-verifier.d.ts +7 -11
  3. package/dist/config/config.d.ts +1 -0
  4. package/dist/config/env.d.ts +1 -2
  5. package/dist/config/secrets.d.ts +0 -2
  6. package/dist/daemon/event-pusher.d.ts +2 -0
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/db/schema/moderation_subject_status.d.ts +2 -2
  9. package/dist/index.js +821 -1055
  10. package/dist/index.js.map +3 -3
  11. package/dist/lexicon/index.d.ts +8 -0
  12. package/dist/lexicon/lexicons.d.ts +298 -0
  13. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
  14. package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
  15. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
  16. package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
  17. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
  18. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +2 -1
  19. package/dist/lexicon/types/com/atproto/label/defs.d.ts +18 -0
  20. package/dist/mod-service/index.d.ts +2 -2
  21. package/package.json +5 -4
  22. package/src/api/admin/createCommunicationTemplate.ts +1 -1
  23. package/src/api/admin/deleteCommunicationTemplate.ts +1 -1
  24. package/src/api/admin/emitModerationEvent.ts +6 -2
  25. package/src/api/admin/getModerationEvent.ts +1 -1
  26. package/src/api/admin/getRecord.ts +1 -1
  27. package/src/api/admin/getRepo.ts +1 -1
  28. package/src/api/admin/listCommunicationTemplates.ts +1 -1
  29. package/src/api/admin/queryModerationEvents.ts +1 -1
  30. package/src/api/admin/queryModerationStatuses.ts +1 -1
  31. package/src/api/admin/searchRepos.ts +1 -1
  32. package/src/api/admin/updateCommunicationTemplate.ts +1 -1
  33. package/src/api/admin/util.ts +1 -1
  34. package/src/api/moderation/createReport.ts +1 -2
  35. package/src/api/proxied.ts +8 -8
  36. package/src/api/temp/fetchLabels.ts +1 -1
  37. package/src/auth-verifier.ts +19 -29
  38. package/src/config/config.ts +10 -7
  39. package/src/config/env.ts +2 -4
  40. package/src/config/secrets.ts +0 -6
  41. package/src/context.ts +1 -3
  42. package/src/daemon/context.ts +2 -2
  43. package/src/daemon/event-pusher.ts +9 -1
  44. package/src/db/schema/moderation_subject_status.ts +6 -1
  45. package/src/lexicon/index.ts +23 -0
  46. package/src/lexicon/lexicons.ts +327 -1
  47. package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
  48. package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
  49. package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
  50. package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
  51. package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
  52. package/src/lexicon/types/com/atproto/admin/defs.ts +3 -0
  53. package/src/lexicon/types/com/atproto/label/defs.ts +68 -0
  54. package/src/mod-service/index.ts +4 -3
  55. package/src/mod-service/status.ts +42 -26
  56. package/tests/__snapshots__/get-record.test.ts.snap +4 -4
  57. package/tests/__snapshots__/get-repo.test.ts.snap +2 -2
  58. package/tests/communication-templates.test.ts +7 -7
  59. package/tests/get-record.test.ts +17 -7
  60. package/tests/get-repo.test.ts +24 -12
  61. package/tests/moderation-appeals.test.ts +24 -52
  62. package/tests/moderation-events.test.ts +87 -130
  63. package/tests/moderation-status-tags.test.ts +16 -31
  64. package/tests/moderation-statuses.test.ts +125 -58
  65. package/tests/moderation.test.ts +140 -287
  66. package/tests/repo-search.test.ts +11 -4
@@ -1,32 +1,22 @@
1
- import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
2
- import AtpAgent from '@atproto/api'
1
+ import {
2
+ TestNetwork,
3
+ SeedClient,
4
+ basicSeed,
5
+ ModeratorClient,
6
+ } from '@atproto/dev-env'
3
7
  import { REASONSPAM } from '../src/lexicon/types/com/atproto/moderation/defs'
4
8
 
5
9
  describe('moderation-status-tags', () => {
6
10
  let network: TestNetwork
7
- let agent: AtpAgent
8
- let pdsAgent: AtpAgent
9
11
  let sc: SeedClient
10
-
11
- const emitModerationEvent = async (eventData) => {
12
- return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, {
13
- encoding: 'application/json',
14
- headers: network.bsky.adminAuthHeaders('moderator'),
15
- })
16
- }
17
-
18
- const queryModerationStatuses = (statusQuery) =>
19
- agent.api.com.atproto.admin.queryModerationStatuses(statusQuery, {
20
- headers: network.bsky.adminAuthHeaders('moderator'),
21
- })
12
+ let modClient: ModeratorClient
22
13
 
23
14
  beforeAll(async () => {
24
15
  network = await TestNetwork.create({
25
16
  dbPostgresSchema: 'ozone_moderation_status_tags',
26
17
  })
27
- agent = network.ozone.getClient()
28
- pdsAgent = network.pds.getClient()
29
18
  sc = network.getSeedClient()
19
+ modClient = network.ozone.getModClient()
30
20
  await basicSeed(sc)
31
21
  await network.processAll()
32
22
  })
@@ -41,25 +31,21 @@ describe('moderation-status-tags', () => {
41
31
  $type: 'com.atproto.admin.defs#repoRef',
42
32
  did: sc.dids.bob,
43
33
  }
44
- await emitModerationEvent({
34
+ await sc.createReport({
35
+ reasonType: REASONSPAM,
36
+ reason: 'X',
45
37
  subject: bobsAccount,
46
- event: {
47
- $type: 'com.atproto.admin.defs#modEventReport',
48
- comment: 'X',
49
- reportType: REASONSPAM,
50
- },
51
- createdBy: sc.dids.alice,
38
+ reportedBy: sc.dids.alice,
52
39
  })
53
- await emitModerationEvent({
40
+ await modClient.emitModerationEvent({
54
41
  subject: bobsAccount,
55
42
  event: {
56
43
  $type: 'com.atproto.admin.defs#modEventTag',
57
44
  add: ['interaction-churn'],
58
45
  remove: [],
59
46
  },
60
- createdBy: sc.dids.alice,
61
47
  })
62
- const { data: statusAfterInteractionTag } = await queryModerationStatuses(
48
+ const statusAfterInteractionTag = await modClient.queryModerationStatuses(
63
49
  {
64
50
  subject: bobsAccount.did,
65
51
  },
@@ -68,16 +54,15 @@ describe('moderation-status-tags', () => {
68
54
  'interaction-churn',
69
55
  )
70
56
 
71
- await emitModerationEvent({
57
+ await modClient.emitModerationEvent({
72
58
  subject: bobsAccount,
73
59
  event: {
74
60
  $type: 'com.atproto.admin.defs#modEventTag',
75
61
  remove: ['interaction-churn'],
76
62
  add: ['follow-churn'],
77
63
  },
78
- createdBy: sc.dids.alice,
79
64
  })
80
- const { data: statusAfterFollowTag } = await queryModerationStatuses({
65
+ const statusAfterFollowTag = await modClient.queryModerationStatuses({
81
66
  subject: bobsAccount.did,
82
67
  })
83
68
 
@@ -1,6 +1,11 @@
1
1
  import assert from 'node:assert'
2
- import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
3
- import AtpAgent, {
2
+ import {
3
+ TestNetwork,
4
+ SeedClient,
5
+ basicSeed,
6
+ ModeratorClient,
7
+ } from '@atproto/dev-env'
8
+ import {
4
9
  ComAtprotoAdminDefs,
5
10
  ComAtprotoAdminQueryModerationStatuses,
6
11
  } from '@atproto/api'
@@ -9,24 +14,15 @@ import {
9
14
  REASONMISLEADING,
10
15
  REASONSPAM,
11
16
  } from '../src/lexicon/types/com/atproto/moderation/defs'
17
+ import {
18
+ REVIEWOPEN,
19
+ REVIEWNONE,
20
+ } from '../src/lexicon/types/com/atproto/admin/defs'
12
21
 
13
22
  describe('moderation-statuses', () => {
14
23
  let network: TestNetwork
15
- let agent: AtpAgent
16
- let pdsAgent: AtpAgent
17
24
  let sc: SeedClient
18
-
19
- const emitModerationEvent = async (eventData) => {
20
- return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, {
21
- encoding: 'application/json',
22
- headers: network.ozone.adminAuthHeaders('moderator'),
23
- })
24
- }
25
-
26
- const queryModerationStatuses = (statusQuery) =>
27
- agent.api.com.atproto.admin.queryModerationStatuses(statusQuery, {
28
- headers: network.ozone.adminAuthHeaders('moderator'),
29
- })
25
+ let modClient: ModeratorClient
30
26
 
31
27
  const seedEvents = async () => {
32
28
  const bobsAccount = {
@@ -49,25 +45,19 @@ describe('moderation-statuses', () => {
49
45
  }
50
46
 
51
47
  for (let i = 0; i < 4; i++) {
52
- await emitModerationEvent({
53
- event: {
54
- $type: 'com.atproto.admin.defs#modEventReport',
55
- reportType: i % 2 ? REASONSPAM : REASONMISLEADING,
56
- comment: 'X',
57
- },
48
+ await sc.createReport({
49
+ reasonType: i % 2 ? REASONSPAM : REASONMISLEADING,
50
+ reason: 'X',
58
51
  // Report bob's account by alice and vice versa
59
52
  subject: i % 2 ? bobsAccount : carlasAccount,
60
- createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
53
+ reportedBy: i % 2 ? sc.dids.alice : sc.dids.bob,
61
54
  })
62
- await emitModerationEvent({
63
- event: {
64
- $type: 'com.atproto.admin.defs#modEventReport',
65
- reportType: REASONSPAM,
66
- comment: 'X',
67
- },
55
+ await sc.createReport({
56
+ reasonType: REASONSPAM,
57
+ reason: 'X',
68
58
  // Report bob's post by alice and vice versa
69
59
  subject: i % 2 ? bobsPost : alicesPost,
70
- createdBy: i % 2 ? sc.dids.alice : sc.dids.bob,
60
+ reportedBy: i % 2 ? sc.dids.alice : sc.dids.bob,
71
61
  })
72
62
  }
73
63
  }
@@ -76,9 +66,8 @@ describe('moderation-statuses', () => {
76
66
  network = await TestNetwork.create({
77
67
  dbPostgresSchema: 'ozone_moderation_statuses',
78
68
  })
79
- agent = network.ozone.getClient()
80
- pdsAgent = network.pds.getClient()
81
69
  sc = network.getSeedClient()
70
+ modClient = network.ozone.getModClient()
82
71
  await basicSeed(sc)
83
72
  await network.processAll()
84
73
  await seedEvents()
@@ -90,26 +79,26 @@ describe('moderation-statuses', () => {
90
79
 
91
80
  describe('query statuses', () => {
92
81
  it('returns statuses for subjects that received moderation events', async () => {
93
- const response = await queryModerationStatuses({})
82
+ const response = await modClient.queryModerationStatuses({})
94
83
 
95
- expect(forSnapshot(response.data.subjectStatuses)).toMatchSnapshot()
84
+ expect(forSnapshot(response.subjectStatuses)).toMatchSnapshot()
96
85
  })
97
86
 
98
87
  it('returns statuses filtered by subject language', async () => {
99
- const klingonQueue = await queryModerationStatuses({
88
+ const klingonQueue = await modClient.queryModerationStatuses({
100
89
  tags: ['lang:i'],
101
90
  })
102
91
 
103
- expect(forSnapshot(klingonQueue.data.subjectStatuses)).toMatchSnapshot()
92
+ expect(forSnapshot(klingonQueue.subjectStatuses)).toMatchSnapshot()
104
93
 
105
- const nonKlingonQueue = await queryModerationStatuses({
94
+ const nonKlingonQueue = await modClient.queryModerationStatuses({
106
95
  excludeTags: ['lang:i'],
107
96
  })
108
97
 
109
98
  // Verify that the klingon tagged subject is not returned when excluding klingon
110
- expect(
111
- nonKlingonQueue.data.subjectStatuses.map((s) => s.id),
112
- ).not.toContain(klingonQueue.data.subjectStatuses[0].id)
99
+ expect(nonKlingonQueue.subjectStatuses.map((s) => s.id)).not.toContain(
100
+ klingonQueue.subjectStatuses[0].id,
101
+ )
113
102
  })
114
103
 
115
104
  it('returns paginated statuses', async () => {
@@ -121,13 +110,13 @@ describe('moderation-statuses', () => {
121
110
  const statuses: ComAtprotoAdminDefs.SubjectStatusView[] = []
122
111
  let count = 0
123
112
  do {
124
- const results = await queryModerationStatuses({
113
+ const results = await modClient.queryModerationStatuses({
125
114
  limit: 1,
126
115
  cursor,
127
116
  ...params,
128
117
  })
129
- cursor = results.data.cursor
130
- statuses.push(...results.data.subjectStatuses)
118
+ cursor = results.cursor
119
+ statuses.push(...results.subjectStatuses)
131
120
  count++
132
121
  // The count is just a brake-check to prevent infinite loop
133
122
  } while (cursor && count < 10)
@@ -139,13 +128,12 @@ describe('moderation-statuses', () => {
139
128
  expect(list[0].id).toEqual(7)
140
129
  expect(list[list.length - 1].id).toEqual(1)
141
130
 
142
- await emitModerationEvent({
131
+ await modClient.emitModerationEvent({
143
132
  subject: list[1].subject,
144
133
  event: {
145
134
  $type: 'com.atproto.admin.defs#modEventAcknowledge',
146
135
  comment: 'X',
147
136
  },
148
- createdBy: sc.dids.bob,
149
137
  })
150
138
 
151
139
  const listReviewedFirst = await getPaginatedStatuses({
@@ -160,11 +148,95 @@ describe('moderation-statuses', () => {
160
148
  })
161
149
  })
162
150
 
151
+ describe('reviewState changes', () => {
152
+ it('only sets state to #reviewNone on first non-impactful event', async () => {
153
+ const bobsAccount = {
154
+ $type: 'com.atproto.admin.defs#repoRef',
155
+ did: sc.dids.bob,
156
+ }
157
+ const alicesPost = {
158
+ $type: 'com.atproto.repo.strongRef',
159
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
160
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
161
+ }
162
+ const getBobsAccountStatus = async () => {
163
+ const data = await modClient.queryModerationStatuses({
164
+ subject: bobsAccount.did,
165
+ })
166
+
167
+ return data.subjectStatuses[0]
168
+ }
169
+ // Since bob's account already had a reviewState, it won't be changed by non-impactful events
170
+ const bobsAccountStatusBeforeTag = await getBobsAccountStatus()
171
+
172
+ await Promise.all([
173
+ modClient.emitModerationEvent({
174
+ subject: bobsAccount,
175
+ event: {
176
+ $type: 'com.atproto.admin.defs#modEventTag',
177
+ add: ['newTag'],
178
+ remove: [],
179
+ comment: 'X',
180
+ },
181
+ createdBy: sc.dids.alice,
182
+ }),
183
+ modClient.emitModerationEvent({
184
+ subject: bobsAccount,
185
+ event: {
186
+ $type: 'com.atproto.admin.defs#modEventComment',
187
+ comment: 'X',
188
+ },
189
+ createdBy: sc.dids.alice,
190
+ }),
191
+ ])
192
+ const bobsAccountStatusAfterTag = await getBobsAccountStatus()
193
+
194
+ expect(bobsAccountStatusBeforeTag.reviewState).toEqual(
195
+ bobsAccountStatusAfterTag.reviewState,
196
+ )
197
+
198
+ // Since alice's post didn't have a reviewState it is set to reviewNone on first non-impactful event
199
+ const getAlicesPostStatus = async () => {
200
+ const data = await modClient.queryModerationStatuses({
201
+ subject: alicesPost.uri,
202
+ })
203
+
204
+ return data.subjectStatuses[0]
205
+ }
206
+
207
+ const alicesPostStatusBeforeTag = await getAlicesPostStatus()
208
+ expect(alicesPostStatusBeforeTag).toBeUndefined()
209
+
210
+ await modClient.emitModerationEvent({
211
+ subject: alicesPost,
212
+ event: {
213
+ $type: 'com.atproto.admin.defs#modEventComment',
214
+ comment: 'X',
215
+ },
216
+ createdBy: sc.dids.alice,
217
+ })
218
+ const alicesPostStatusAfterTag = await getAlicesPostStatus()
219
+ expect(alicesPostStatusAfterTag.reviewState).toEqual(REVIEWNONE)
220
+
221
+ await modClient.emitModerationEvent({
222
+ subject: alicesPost,
223
+ event: {
224
+ $type: 'com.atproto.admin.defs#modEventReport',
225
+ reportType: REASONMISLEADING,
226
+ comment: 'X',
227
+ },
228
+ createdBy: sc.dids.alice,
229
+ })
230
+ const alicesPostStatusAfterReport = await getAlicesPostStatus()
231
+ expect(alicesPostStatusAfterReport.reviewState).toEqual(REVIEWOPEN)
232
+ })
233
+ })
234
+
163
235
  describe('blobs', () => {
164
236
  it('are tracked on takendown subject', async () => {
165
237
  const post = sc.posts[sc.dids.carol][0]
166
238
  assert(post.images.length > 1)
167
- await emitModerationEvent({
239
+ await modClient.emitModerationEvent({
168
240
  event: {
169
241
  $type: 'com.atproto.admin.defs#modEventTakedown',
170
242
  },
@@ -176,11 +248,9 @@ describe('moderation-statuses', () => {
176
248
  subjectBlobCids: [post.images[0].image.ref.toString()],
177
249
  createdBy: sc.dids.alice,
178
250
  })
179
- const { data: result } =
180
- await pdsAgent.api.com.atproto.admin.queryModerationStatuses(
181
- { subject: post.ref.uriStr },
182
- { headers: network.ozone.adminAuthHeaders('moderator') },
183
- )
251
+ const result = await modClient.queryModerationStatuses({
252
+ subject: post.ref.uriStr,
253
+ })
184
254
  expect(result.subjectStatuses.length).toBe(1)
185
255
  expect(result.subjectStatuses[0]).toMatchObject({
186
256
  takendown: true,
@@ -190,7 +260,7 @@ describe('moderation-statuses', () => {
190
260
 
191
261
  it('are tracked on reverse-takendown subject based on previous status', async () => {
192
262
  const post = sc.posts[sc.dids.carol][0]
193
- await emitModerationEvent({
263
+ await modClient.emitModerationEvent({
194
264
  event: {
195
265
  $type: 'com.atproto.admin.defs#modEventReverseTakedown',
196
266
  },
@@ -199,13 +269,10 @@ describe('moderation-statuses', () => {
199
269
  uri: post.ref.uriStr,
200
270
  cid: post.ref.cidStr,
201
271
  },
202
- createdBy: sc.dids.alice,
203
272
  })
204
- const { data: result } =
205
- await pdsAgent.api.com.atproto.admin.queryModerationStatuses(
206
- { subject: post.ref.uriStr },
207
- { headers: network.ozone.adminAuthHeaders('moderator') },
208
- )
273
+ const result = await modClient.queryModerationStatuses({
274
+ subject: post.ref.uriStr,
275
+ })
209
276
  expect(result.subjectStatuses.length).toBe(1)
210
277
  expect(result.subjectStatuses[0]).toMatchObject({
211
278
  takendown: false,