@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.
- package/dist/api/moderation/util.d.ts +1 -1
- package/dist/auth-verifier.d.ts +7 -11
- package/dist/config/config.d.ts +1 -0
- package/dist/config/env.d.ts +1 -2
- package/dist/config/secrets.d.ts +0 -2
- package/dist/daemon/event-pusher.d.ts +2 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +2 -2
- package/dist/index.js +821 -1055
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +8 -0
- package/dist/lexicon/lexicons.d.ts +298 -0
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
- package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
- package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
- package/dist/lexicon/types/app/bsky/labeler/getServices.d.ts +36 -0
- package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +2 -1
- package/dist/lexicon/types/com/atproto/label/defs.d.ts +18 -0
- package/dist/mod-service/index.d.ts +2 -2
- package/package.json +5 -4
- package/src/api/admin/createCommunicationTemplate.ts +1 -1
- package/src/api/admin/deleteCommunicationTemplate.ts +1 -1
- package/src/api/admin/emitModerationEvent.ts +6 -2
- package/src/api/admin/getModerationEvent.ts +1 -1
- package/src/api/admin/getRecord.ts +1 -1
- package/src/api/admin/getRepo.ts +1 -1
- package/src/api/admin/listCommunicationTemplates.ts +1 -1
- package/src/api/admin/queryModerationEvents.ts +1 -1
- package/src/api/admin/queryModerationStatuses.ts +1 -1
- package/src/api/admin/searchRepos.ts +1 -1
- package/src/api/admin/updateCommunicationTemplate.ts +1 -1
- package/src/api/admin/util.ts +1 -1
- package/src/api/moderation/createReport.ts +1 -2
- package/src/api/proxied.ts +8 -8
- package/src/api/temp/fetchLabels.ts +1 -1
- package/src/auth-verifier.ts +19 -29
- package/src/config/config.ts +10 -7
- package/src/config/env.ts +2 -4
- package/src/config/secrets.ts +0 -6
- package/src/context.ts +1 -3
- package/src/daemon/context.ts +2 -2
- package/src/daemon/event-pusher.ts +9 -1
- package/src/db/schema/moderation_subject_status.ts +6 -1
- package/src/lexicon/index.ts +23 -0
- package/src/lexicon/lexicons.ts +327 -1
- package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
- package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
- package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
- package/src/lexicon/types/app/bsky/labeler/getServices.ts +51 -0
- package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +3 -0
- package/src/lexicon/types/com/atproto/label/defs.ts +68 -0
- package/src/mod-service/index.ts +4 -3
- package/src/mod-service/status.ts +42 -26
- package/tests/__snapshots__/get-record.test.ts.snap +4 -4
- package/tests/__snapshots__/get-repo.test.ts.snap +2 -2
- package/tests/communication-templates.test.ts +7 -7
- package/tests/get-record.test.ts +17 -7
- package/tests/get-repo.test.ts +24 -12
- package/tests/moderation-appeals.test.ts +24 -52
- package/tests/moderation-events.test.ts +87 -130
- package/tests/moderation-status-tags.test.ts +16 -31
- package/tests/moderation-statuses.test.ts +125 -58
- package/tests/moderation.test.ts +140 -287
- package/tests/repo-search.test.ts +11 -4
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
|
34
|
+
await sc.createReport({
|
|
35
|
+
reasonType: REASONSPAM,
|
|
36
|
+
reason: 'X',
|
|
45
37
|
subject: bobsAccount,
|
|
46
|
-
|
|
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
|
|
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
|
|
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 {
|
|
3
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
53
|
+
reportedBy: i % 2 ? sc.dids.alice : sc.dids.bob,
|
|
61
54
|
})
|
|
62
|
-
await
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
112
|
-
)
|
|
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.
|
|
130
|
-
statuses.push(...results.
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
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,
|