@atproto/ozone 0.1.14 → 0.1.16
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/CHANGELOG.md +16 -0
- package/dist/api/chat/getActorMetadata.d.ts +4 -0
- package/dist/api/chat/getActorMetadata.d.ts.map +1 -0
- package/dist/api/chat/getActorMetadata.js +20 -0
- package/dist/api/chat/getActorMetadata.js.map +1 -0
- package/dist/api/chat/getMessageContext.d.ts +4 -0
- package/dist/api/chat/getMessageContext.d.ts.map +1 -0
- package/dist/api/chat/getMessageContext.js +34 -0
- package/dist/api/chat/getMessageContext.js.map +1 -0
- package/dist/api/chat/index.d.ts +4 -0
- package/dist/api/chat/index.d.ts.map +1 -0
- package/dist/api/chat/index.js +14 -0
- package/dist/api/chat/index.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/moderation/emitEvent.d.ts.map +1 -1
- package/dist/api/moderation/emitEvent.js +22 -0
- package/dist/api/moderation/emitEvent.js.map +1 -1
- package/dist/api/moderation/getRecord.d.ts.map +1 -1
- package/dist/api/moderation/getRecord.js +3 -2
- package/dist/api/moderation/getRecord.js.map +1 -1
- package/dist/api/moderation/getRepo.d.ts.map +1 -1
- package/dist/api/moderation/getRepo.js +3 -2
- package/dist/api/moderation/getRepo.js.map +1 -1
- package/dist/api/proxied.d.ts.map +1 -1
- package/dist/api/proxied.js +10 -0
- package/dist/api/proxied.js.map +1 -1
- package/dist/api/util.js +1 -0
- package/dist/api/util.js.map +1 -1
- package/dist/config/config.d.ts +5 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +9 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +2 -0
- package/dist/config/env.js.map +1 -1
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +26 -0
- package/dist/context.js.map +1 -1
- package/dist/db/migrations/20240506T225055595Z-message-subject.d.ts +4 -0
- package/dist/db/migrations/20240506T225055595Z-message-subject.d.ts.map +1 -0
- package/dist/db/migrations/20240506T225055595Z-message-subject.js +24 -0
- package/dist/db/migrations/20240506T225055595Z-message-subject.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/moderation_event.d.ts +2 -1
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/lexicon/index.d.ts +59 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +164 -1
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +898 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +924 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts +11 -0
- package/dist/lexicon/types/chat/bsky/actor/declaration.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/declaration.js +17 -0
- package/dist/lexicon/types/chat/bsky/actor/declaration.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/defs.d.ts +21 -0
- package/dist/lexicon/types/chat/bsky/actor/defs.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/defs.js +16 -0
- package/dist/lexicon/types/chat/bsky/actor/defs.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/deleteAccount.d.ts +33 -0
- package/dist/lexicon/types/chat/bsky/actor/deleteAccount.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/deleteAccount.js +3 -0
- package/dist/lexicon/types/chat/bsky/actor/deleteAccount.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/exportAccountData.d.ts +32 -0
- package/dist/lexicon/types/chat/bsky/actor/exportAccountData.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/actor/exportAccountData.js +3 -0
- package/dist/lexicon/types/chat/bsky/actor/exportAccountData.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +109 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.js +106 -0
- package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/deleteMessageForSelf.d.ts +39 -0
- package/dist/lexicon/types/chat/bsky/convo/deleteMessageForSelf.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/deleteMessageForSelf.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/deleteMessageForSelf.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvo.d.ts +36 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvo.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvo.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvo.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.d.ts +36 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/getConvoForMembers.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getLog.d.ts +40 -0
- package/dist/lexicon/types/chat/bsky/convo/getLog.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getLog.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/getLog.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts +42 -0
- package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/getMessages.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/getMessages.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/leaveConvo.d.ts +41 -0
- package/dist/lexicon/types/chat/bsky/convo/leaveConvo.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/leaveConvo.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/leaveConvo.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/listConvos.d.ts +38 -0
- package/dist/lexicon/types/chat/bsky/convo/listConvos.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/listConvos.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/listConvos.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/muteConvo.d.ts +41 -0
- package/dist/lexicon/types/chat/bsky/convo/muteConvo.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/muteConvo.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/muteConvo.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.d.ts +39 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessage.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.d.ts +49 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.js +16 -0
- package/dist/lexicon/types/chat/bsky/convo/sendMessageBatch.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/unmuteConvo.d.ts +41 -0
- package/dist/lexicon/types/chat/bsky/convo/unmuteConvo.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/unmuteConvo.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/unmuteConvo.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/updateRead.d.ts +42 -0
- package/dist/lexicon/types/chat/bsky/convo/updateRead.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/convo/updateRead.js +3 -0
- package/dist/lexicon/types/chat/bsky/convo/updateRead.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getActorMetadata.d.ts +47 -0
- package/dist/lexicon/types/chat/bsky/moderation/getActorMetadata.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getActorMetadata.js +16 -0
- package/dist/lexicon/types/chat/bsky/moderation/getActorMetadata.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getMessageContext.d.ts +43 -0
- package/dist/lexicon/types/chat/bsky/moderation/getMessageContext.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/getMessageContext.js +3 -0
- package/dist/lexicon/types/chat/bsky/moderation/getMessageContext.js.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/updateActorAccess.d.ts +31 -0
- package/dist/lexicon/types/chat/bsky/moderation/updateActorAccess.d.ts.map +1 -0
- package/dist/lexicon/types/chat/bsky/moderation/updateActorAccess.js +3 -0
- package/dist/lexicon/types/chat/bsky/moderation/updateActorAccess.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/mod-service/index.d.ts +2 -1
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +10 -2
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/subject.d.ts +35 -2
- package/dist/mod-service/subject.d.ts.map +1 -1
- package/dist/mod-service/subject.js +85 -1
- package/dist/mod-service/subject.js.map +1 -1
- package/dist/mod-service/views.d.ts +5 -2
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +17 -6
- package/dist/mod-service/views.js.map +1 -1
- package/dist/util.d.ts +8 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +41 -1
- package/dist/util.js.map +1 -1
- package/package.json +5 -4
- package/src/api/chat/getActorMetadata.ts +22 -0
- package/src/api/chat/getMessageContext.ts +39 -0
- package/src/api/chat/index.ts +10 -0
- package/src/api/index.ts +2 -0
- package/src/api/moderation/emitEvent.ts +31 -0
- package/src/api/moderation/getRecord.ts +3 -2
- package/src/api/moderation/getRepo.ts +3 -2
- package/src/api/proxied.ts +14 -0
- package/src/api/util.ts +1 -0
- package/src/config/config.ts +16 -0
- package/src/config/env.ts +4 -0
- package/src/context.ts +36 -1
- package/src/db/migrations/20240506T225055595Z-message-subject.ts +21 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/moderation_event.ts +5 -1
- package/src/lexicon/index.ts +254 -0
- package/src/lexicon/lexicons.ts +926 -0
- package/src/lexicon/types/chat/bsky/actor/declaration.ts +25 -0
- package/src/lexicon/types/chat/bsky/actor/defs.ts +34 -0
- package/src/lexicon/types/chat/bsky/actor/deleteAccount.ts +42 -0
- package/src/lexicon/types/chat/bsky/actor/exportAccountData.ts +38 -0
- package/src/lexicon/types/chat/bsky/convo/defs.ts +215 -0
- package/src/lexicon/types/chat/bsky/convo/deleteMessageForSelf.ts +48 -0
- package/src/lexicon/types/chat/bsky/convo/getConvo.ts +46 -0
- package/src/lexicon/types/chat/bsky/convo/getConvoForMembers.ts +46 -0
- package/src/lexicon/types/chat/bsky/convo/getLog.ts +53 -0
- package/src/lexicon/types/chat/bsky/convo/getMessages.ts +53 -0
- package/src/lexicon/types/chat/bsky/convo/leaveConvo.ts +50 -0
- package/src/lexicon/types/chat/bsky/convo/listConvos.ts +48 -0
- package/src/lexicon/types/chat/bsky/convo/muteConvo.ts +50 -0
- package/src/lexicon/types/chat/bsky/convo/sendMessage.ts +48 -0
- package/src/lexicon/types/chat/bsky/convo/sendMessageBatch.ts +68 -0
- package/src/lexicon/types/chat/bsky/convo/unmuteConvo.ts +50 -0
- package/src/lexicon/types/chat/bsky/convo/updateRead.ts +51 -0
- package/src/lexicon/types/chat/bsky/moderation/getActorMetadata.ts +67 -0
- package/src/lexicon/types/chat/bsky/moderation/getMessageContext.ts +54 -0
- package/src/lexicon/types/chat/bsky/moderation/updateActorAccess.ts +40 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
- package/src/mod-service/index.ts +10 -3
- package/src/mod-service/subject.ts +73 -2
- package/src/mod-service/views.ts +30 -5
- package/src/util.ts +50 -0
- package/tests/3p-labeler.test.ts +271 -0
- package/tests/__snapshots__/moderation.test.ts.snap +30 -0
- package/tests/get-lists.test.ts +109 -0
- package/tests/moderation.test.ts +39 -0
package/src/mod-service/views.ts
CHANGED
|
@@ -28,10 +28,12 @@ import { formatLabel, signLabel } from './util'
|
|
|
28
28
|
import { LabelRow } from '../db/schema/label'
|
|
29
29
|
import { dbLogger } from '../logger'
|
|
30
30
|
import { httpLogger } from '../logger'
|
|
31
|
+
import { ParsedLabelers } from '../util'
|
|
31
32
|
|
|
32
33
|
export type AuthHeaders = {
|
|
33
34
|
headers: {
|
|
34
35
|
authorization: string
|
|
36
|
+
'atproto-accept-labelers'?: string
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -210,10 +212,14 @@ export class ModerationViews {
|
|
|
210
212
|
}
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
async repoDetail(
|
|
214
|
-
|
|
215
|
+
async repoDetail(
|
|
216
|
+
did: string,
|
|
217
|
+
labelers?: ParsedLabelers,
|
|
218
|
+
): Promise<RepoViewDetail | undefined> {
|
|
219
|
+
const [repos, localLabels, externalLabels] = await Promise.all([
|
|
215
220
|
this.repos([did]),
|
|
216
221
|
this.labels(did),
|
|
222
|
+
this.getExternalLabels([did], labelers),
|
|
217
223
|
])
|
|
218
224
|
const repo = repos.get(did)
|
|
219
225
|
if (!repo) return
|
|
@@ -223,7 +229,7 @@ export class ModerationViews {
|
|
|
223
229
|
moderation: {
|
|
224
230
|
...repo.moderation,
|
|
225
231
|
},
|
|
226
|
-
labels,
|
|
232
|
+
labels: [...localLabels, ...externalLabels],
|
|
227
233
|
}
|
|
228
234
|
}
|
|
229
235
|
|
|
@@ -293,6 +299,7 @@ export class ModerationViews {
|
|
|
293
299
|
|
|
294
300
|
async recordDetail(
|
|
295
301
|
subject: RecordSubject,
|
|
302
|
+
labelers?: ParsedLabelers,
|
|
296
303
|
): Promise<RecordViewDetail | undefined> {
|
|
297
304
|
const [records, subjectStatusesResult] = await Promise.all([
|
|
298
305
|
this.records([subject]),
|
|
@@ -303,9 +310,10 @@ export class ModerationViews {
|
|
|
303
310
|
|
|
304
311
|
const status = subjectStatusesResult.get(subject.uri)
|
|
305
312
|
|
|
306
|
-
const [blobs, labels, subjectStatus] = await Promise.all([
|
|
313
|
+
const [blobs, labels, externalLabels, subjectStatus] = await Promise.all([
|
|
307
314
|
this.blob(findBlobRefs(record.value)),
|
|
308
315
|
this.labels(record.uri),
|
|
316
|
+
this.getExternalLabels([record.uri], labelers),
|
|
309
317
|
status ? this.formatSubjectStatus(status) : Promise.resolve(undefined),
|
|
310
318
|
])
|
|
311
319
|
const selfLabels = getSelfLabels({
|
|
@@ -313,6 +321,7 @@ export class ModerationViews {
|
|
|
313
321
|
cid: record.cid,
|
|
314
322
|
record: record.value,
|
|
315
323
|
})
|
|
324
|
+
|
|
316
325
|
return {
|
|
317
326
|
...record,
|
|
318
327
|
blobs,
|
|
@@ -320,10 +329,26 @@ export class ModerationViews {
|
|
|
320
329
|
...record.moderation,
|
|
321
330
|
subjectStatus,
|
|
322
331
|
},
|
|
323
|
-
labels: [...labels, ...selfLabels],
|
|
332
|
+
labels: [...labels, ...selfLabels, ...externalLabels],
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
|
|
336
|
+
async getExternalLabels(
|
|
337
|
+
subjects: string[],
|
|
338
|
+
labelers?: ParsedLabelers,
|
|
339
|
+
): Promise<Label[]> {
|
|
340
|
+
if (!labelers?.dids.length && !labelers?.redact.size) return []
|
|
341
|
+
|
|
342
|
+
const {
|
|
343
|
+
data: { labels },
|
|
344
|
+
} = await this.appviewAgent.api.com.atproto.label.queryLabels({
|
|
345
|
+
uriPatterns: subjects,
|
|
346
|
+
sources: labelers.dids,
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
return labels
|
|
350
|
+
}
|
|
351
|
+
|
|
327
352
|
formatReport(report: ModerationEventRowWithHandle): ReportOutput {
|
|
328
353
|
return {
|
|
329
354
|
id: report.id,
|
package/src/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosError } from 'axios'
|
|
2
|
+
import { parseList } from 'structured-headers'
|
|
2
3
|
import { XRPCError, ResponseType } from '@atproto/xrpc'
|
|
3
4
|
import { RetryOptions, retry } from '@atproto/common'
|
|
4
5
|
import Database from './db'
|
|
@@ -45,3 +46,52 @@ export function retryableHttp(err: unknown) {
|
|
|
45
46
|
const retryableHttpStatusCodes = new Set([
|
|
46
47
|
408, 425, 429, 500, 502, 503, 504, 522, 524,
|
|
47
48
|
])
|
|
49
|
+
|
|
50
|
+
export type ParsedLabelers = {
|
|
51
|
+
dids: string[]
|
|
52
|
+
redact: Set<string>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const LABELER_HEADER_NAME = 'atproto-accept-labelers'
|
|
56
|
+
|
|
57
|
+
export const parseLabelerHeader = (
|
|
58
|
+
header: string | undefined,
|
|
59
|
+
ignoreDid?: string,
|
|
60
|
+
): ParsedLabelers | null => {
|
|
61
|
+
if (!header) return null
|
|
62
|
+
const labelerDids = new Set<string>()
|
|
63
|
+
const redactDids = new Set<string>()
|
|
64
|
+
const parsed = parseList(header)
|
|
65
|
+
for (const item of parsed) {
|
|
66
|
+
const did = item[0].toString()
|
|
67
|
+
if (!did) {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
if (did === ignoreDid) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
labelerDids.add(did)
|
|
74
|
+
const redact = item[1].get('redact')?.valueOf()
|
|
75
|
+
if (redact === true) {
|
|
76
|
+
redactDids.add(did)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
dids: [...labelerDids],
|
|
81
|
+
redact: redactDids,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const defaultLabelerHeader = (dids: string[]): ParsedLabelers => {
|
|
86
|
+
return {
|
|
87
|
+
dids,
|
|
88
|
+
redact: new Set(dids),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const formatLabelerHeader = (parsed: ParsedLabelers): string => {
|
|
93
|
+
const parts = parsed.dids.map((did) =>
|
|
94
|
+
parsed.redact.has(did) ? `${did};redact` : did,
|
|
95
|
+
)
|
|
96
|
+
return parts.join(',')
|
|
97
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SeedClient,
|
|
3
|
+
TestNetwork,
|
|
4
|
+
basicSeed,
|
|
5
|
+
TestOzone,
|
|
6
|
+
ModeratorClient,
|
|
7
|
+
createOzoneDid,
|
|
8
|
+
} from '@atproto/dev-env'
|
|
9
|
+
import AtpAgent from '@atproto/api'
|
|
10
|
+
import { Secp256k1Keypair } from '@atproto/crypto'
|
|
11
|
+
import { LABELER_HEADER_NAME } from '../src/util'
|
|
12
|
+
|
|
13
|
+
describe('labels from 3p labelers', () => {
|
|
14
|
+
let network: TestNetwork
|
|
15
|
+
let ozone: TestOzone
|
|
16
|
+
let thirdPartyLabeler: TestOzone
|
|
17
|
+
let agent: AtpAgent
|
|
18
|
+
let thirdPartyAgent: AtpAgent
|
|
19
|
+
let sc: SeedClient
|
|
20
|
+
let modClient: ModeratorClient
|
|
21
|
+
let thirdPartyModClient: ModeratorClient
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
network = await TestNetwork.create({
|
|
25
|
+
dbPostgresSchema: 'ozone_admin_third_party_labeler',
|
|
26
|
+
})
|
|
27
|
+
ozone = network.ozone
|
|
28
|
+
|
|
29
|
+
const ozoneKey = await Secp256k1Keypair.create({ exportable: true })
|
|
30
|
+
const ozoneDid = await createOzoneDid(network.plc.url, ozoneKey)
|
|
31
|
+
thirdPartyLabeler = await TestOzone.create({
|
|
32
|
+
port: ozone.port + 10,
|
|
33
|
+
plcUrl: network.plc.url,
|
|
34
|
+
signingKey: ozoneKey,
|
|
35
|
+
serverDid: ozoneDid,
|
|
36
|
+
dbPostgresSchema: `ozone_admin_third_party_labeler`,
|
|
37
|
+
dbPostgresUrl: ozone.ctx.cfg.db.postgresUrl,
|
|
38
|
+
appviewUrl: network.bsky.url,
|
|
39
|
+
appviewDid: network.bsky.ctx.cfg.serverDid,
|
|
40
|
+
appviewPushEvents: true,
|
|
41
|
+
pdsUrl: network.pds.url,
|
|
42
|
+
pdsDid: network.pds.ctx.cfg.service.did,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
thirdPartyAgent = thirdPartyLabeler.getClient()
|
|
46
|
+
agent = ozone.getClient()
|
|
47
|
+
sc = network.getSeedClient()
|
|
48
|
+
modClient = ozone.getModClient()
|
|
49
|
+
thirdPartyModClient = thirdPartyLabeler.getModClient()
|
|
50
|
+
await basicSeed(sc)
|
|
51
|
+
await network.processAll()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
afterAll(async () => {
|
|
55
|
+
await network.close()
|
|
56
|
+
await thirdPartyLabeler.close()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const getPostSubject = () => ({
|
|
60
|
+
$type: 'com.atproto.repo.strongRef',
|
|
61
|
+
uri: sc.posts[sc.dids.alice][0].ref.uriStr,
|
|
62
|
+
cid: sc.posts[sc.dids.alice][0].ref.cidStr,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const adjustLabels = async ({
|
|
66
|
+
uri,
|
|
67
|
+
cid,
|
|
68
|
+
src,
|
|
69
|
+
createLabelVals = [],
|
|
70
|
+
negateLabelVals = [],
|
|
71
|
+
}: {
|
|
72
|
+
uri: string
|
|
73
|
+
src: string
|
|
74
|
+
cid?: string
|
|
75
|
+
createLabelVals?: string[]
|
|
76
|
+
negateLabelVals?: string[]
|
|
77
|
+
}) => {
|
|
78
|
+
const labelEntries = createLabelVals.map((val) => ({
|
|
79
|
+
uri,
|
|
80
|
+
cid: cid || '',
|
|
81
|
+
val,
|
|
82
|
+
cts: new Date().toISOString(),
|
|
83
|
+
neg: false,
|
|
84
|
+
src,
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
negateLabelVals.forEach((val) => {
|
|
88
|
+
labelEntries.push({
|
|
89
|
+
uri,
|
|
90
|
+
cid: cid || '',
|
|
91
|
+
val,
|
|
92
|
+
cts: new Date().toISOString(),
|
|
93
|
+
neg: true,
|
|
94
|
+
src,
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
await network.bsky.db.db.insertInto('label').values(labelEntries).execute()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const labelAccount = async (
|
|
101
|
+
client: ModeratorClient,
|
|
102
|
+
{
|
|
103
|
+
createLabelVals = [],
|
|
104
|
+
negateLabelVals = [],
|
|
105
|
+
}: { createLabelVals?: string[]; negateLabelVals?: string[] },
|
|
106
|
+
) => {
|
|
107
|
+
const subject = {
|
|
108
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
109
|
+
did: sc.dids.alice,
|
|
110
|
+
}
|
|
111
|
+
await client.emitEvent(
|
|
112
|
+
{
|
|
113
|
+
subject,
|
|
114
|
+
event: {
|
|
115
|
+
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
|
116
|
+
createLabelVals,
|
|
117
|
+
negateLabelVals,
|
|
118
|
+
},
|
|
119
|
+
createdBy: sc.dids.carol,
|
|
120
|
+
},
|
|
121
|
+
'moderator',
|
|
122
|
+
)
|
|
123
|
+
await adjustLabels({
|
|
124
|
+
createLabelVals,
|
|
125
|
+
negateLabelVals,
|
|
126
|
+
uri: sc.dids.alice,
|
|
127
|
+
src: client.ozone.ctx.cfg.service.did,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const labelPost = async (
|
|
132
|
+
client: ModeratorClient,
|
|
133
|
+
{
|
|
134
|
+
createLabelVals = [],
|
|
135
|
+
negateLabelVals = [],
|
|
136
|
+
}: { createLabelVals?: string[]; negateLabelVals?: string[] },
|
|
137
|
+
) => {
|
|
138
|
+
const subject = getPostSubject()
|
|
139
|
+
await client.emitEvent(
|
|
140
|
+
{
|
|
141
|
+
subject,
|
|
142
|
+
event: {
|
|
143
|
+
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
|
144
|
+
createLabelVals,
|
|
145
|
+
negateLabelVals,
|
|
146
|
+
},
|
|
147
|
+
createdBy: sc.dids.carol,
|
|
148
|
+
},
|
|
149
|
+
'moderator',
|
|
150
|
+
)
|
|
151
|
+
await adjustLabels({
|
|
152
|
+
createLabelVals,
|
|
153
|
+
negateLabelVals,
|
|
154
|
+
uri: subject.uri,
|
|
155
|
+
cid: subject.cid,
|
|
156
|
+
src: client.ozone.ctx.cfg.service.did,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
describe('record labels', () => {
|
|
161
|
+
it('includes only labels from current authority by default', async () => {
|
|
162
|
+
await Promise.all([
|
|
163
|
+
labelPost(modClient, { createLabelVals: ['spam'] }),
|
|
164
|
+
labelPost(thirdPartyModClient, { createLabelVals: ['weird'] }),
|
|
165
|
+
])
|
|
166
|
+
|
|
167
|
+
const [
|
|
168
|
+
{ data: recordFromCurrentAuthority },
|
|
169
|
+
{ data: recordFromThirdParty },
|
|
170
|
+
] = await Promise.all([
|
|
171
|
+
agent.api.tools.ozone.moderation.getRecord(
|
|
172
|
+
{ uri: sc.posts[sc.dids.alice][0].ref.uriStr },
|
|
173
|
+
{ headers: await ozone.modHeaders() },
|
|
174
|
+
),
|
|
175
|
+
thirdPartyAgent.api.tools.ozone.moderation.getRecord(
|
|
176
|
+
{ uri: sc.posts[sc.dids.alice][0].ref.uriStr },
|
|
177
|
+
{ headers: await thirdPartyLabeler.modHeaders() },
|
|
178
|
+
),
|
|
179
|
+
])
|
|
180
|
+
|
|
181
|
+
const currentAuthorityLabels = recordFromCurrentAuthority.labels?.map(
|
|
182
|
+
(l) => l.val,
|
|
183
|
+
)
|
|
184
|
+
const thirdPartyLabels = recordFromThirdParty.labels?.map((l) => l.val)
|
|
185
|
+
expect(currentAuthorityLabels).toContain('spam')
|
|
186
|
+
expect(currentAuthorityLabels).not.toContain('weird')
|
|
187
|
+
expect(thirdPartyLabels).toContain('weird')
|
|
188
|
+
expect(thirdPartyLabels).not.toContain('spam')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('includes labels from all authorities requested via header', async () => {
|
|
192
|
+
const authHeaders = await ozone.modHeaders()
|
|
193
|
+
const { data: recordIncludingExternalLabels } =
|
|
194
|
+
await agent.api.tools.ozone.moderation.getRecord(
|
|
195
|
+
{ uri: sc.posts[sc.dids.alice][0].ref.uriStr },
|
|
196
|
+
{
|
|
197
|
+
headers: {
|
|
198
|
+
...authHeaders,
|
|
199
|
+
[LABELER_HEADER_NAME]: [
|
|
200
|
+
thirdPartyLabeler.ctx.cfg.service.did,
|
|
201
|
+
ozone.ctx.cfg.service.did,
|
|
202
|
+
].join(','),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
const labelVals = recordIncludingExternalLabels.labels?.map((l) => l.val)
|
|
207
|
+
const labelSources = recordIncludingExternalLabels.labels?.map(
|
|
208
|
+
(l) => l.src,
|
|
209
|
+
)
|
|
210
|
+
expect(labelVals).toContain('weird')
|
|
211
|
+
expect(labelVals).toContain('spam')
|
|
212
|
+
expect(labelSources).toContain(thirdPartyLabeler.ctx.cfg.service.did)
|
|
213
|
+
expect(labelSources).toContain(ozone.ctx.cfg.service.did)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('repo labels', () => {
|
|
218
|
+
it('includes only labels from current authority by default', async () => {
|
|
219
|
+
await Promise.all([
|
|
220
|
+
labelAccount(modClient, { createLabelVals: ['spam'] }),
|
|
221
|
+
labelAccount(thirdPartyModClient, { createLabelVals: ['weird'] }),
|
|
222
|
+
])
|
|
223
|
+
|
|
224
|
+
const [{ data: repoFromCurrentAuthority }, { data: repoFromThirdParty }] =
|
|
225
|
+
await Promise.all([
|
|
226
|
+
agent.api.tools.ozone.moderation.getRepo(
|
|
227
|
+
{ did: sc.dids.alice },
|
|
228
|
+
{ headers: await ozone.modHeaders() },
|
|
229
|
+
),
|
|
230
|
+
thirdPartyAgent.api.tools.ozone.moderation.getRepo(
|
|
231
|
+
{ did: sc.dids.alice },
|
|
232
|
+
{ headers: await thirdPartyLabeler.modHeaders() },
|
|
233
|
+
),
|
|
234
|
+
])
|
|
235
|
+
|
|
236
|
+
const currentAuthorityLabels = repoFromCurrentAuthority.labels?.map(
|
|
237
|
+
(l) => l.val,
|
|
238
|
+
)
|
|
239
|
+
const thirdPartyLabels = repoFromThirdParty.labels?.map((l) => l.val)
|
|
240
|
+
expect(currentAuthorityLabels).toContain('spam')
|
|
241
|
+
expect(currentAuthorityLabels).not.toContain('weird')
|
|
242
|
+
expect(thirdPartyLabels).toContain('weird')
|
|
243
|
+
expect(thirdPartyLabels).not.toContain('spam')
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('includes labels from all authorities requested via header', async () => {
|
|
247
|
+
const authHeaders = await ozone.modHeaders()
|
|
248
|
+
const { data: recordIncludingExternalLabels } =
|
|
249
|
+
await agent.api.tools.ozone.moderation.getRepo(
|
|
250
|
+
{ did: sc.dids.alice },
|
|
251
|
+
{
|
|
252
|
+
headers: {
|
|
253
|
+
...authHeaders,
|
|
254
|
+
[LABELER_HEADER_NAME]: [
|
|
255
|
+
thirdPartyLabeler.ctx.cfg.service.did,
|
|
256
|
+
ozone.ctx.cfg.service.did,
|
|
257
|
+
].join(','),
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
)
|
|
261
|
+
const labelVals = recordIncludingExternalLabels.labels?.map((l) => l.val)
|
|
262
|
+
const labelSources = recordIncludingExternalLabels.labels?.map(
|
|
263
|
+
(l) => l.src,
|
|
264
|
+
)
|
|
265
|
+
expect(labelVals).toContain('weird')
|
|
266
|
+
expect(labelVals).toContain('spam')
|
|
267
|
+
expect(labelSources).toContain(thirdPartyLabeler.ctx.cfg.service.did)
|
|
268
|
+
expect(labelSources).toContain(ozone.ctx.cfg.service.did)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
})
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
+
exports[`moderation reporting creates reports of a DM chat. 1`] = `
|
|
4
|
+
Array [
|
|
5
|
+
Object {
|
|
6
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
7
|
+
"id": 12,
|
|
8
|
+
"reasonType": "com.atproto.moderation.defs#reasonSpam",
|
|
9
|
+
"reportedBy": "user(0)",
|
|
10
|
+
"subject": Object {
|
|
11
|
+
"$type": "chat.bsky.convo.defs#messageRef",
|
|
12
|
+
"convoId": "testconvoid1",
|
|
13
|
+
"did": "user(1)",
|
|
14
|
+
"messageId": "testmessageid1",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
Object {
|
|
18
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
19
|
+
"id": 14,
|
|
20
|
+
"reason": "defamation",
|
|
21
|
+
"reasonType": "com.atproto.moderation.defs#reasonOther",
|
|
22
|
+
"reportedBy": "user(1)",
|
|
23
|
+
"subject": Object {
|
|
24
|
+
"$type": "chat.bsky.convo.defs#messageRef",
|
|
25
|
+
"convoId": "testconvoid2",
|
|
26
|
+
"did": "user(1)",
|
|
27
|
+
"messageId": "testmessageid2",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
`;
|
|
32
|
+
|
|
3
33
|
exports[`moderation reporting creates reports of a record. 1`] = `
|
|
4
34
|
Array [
|
|
5
35
|
Object {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SeedClient,
|
|
3
|
+
TestNetwork,
|
|
4
|
+
TestOzone,
|
|
5
|
+
basicSeed,
|
|
6
|
+
ModeratorClient,
|
|
7
|
+
RecordRef,
|
|
8
|
+
} from '@atproto/dev-env'
|
|
9
|
+
import AtpAgent, { BSKY_LABELER_DID } from '@atproto/api'
|
|
10
|
+
import { TAKEDOWN_LABEL } from '../src/mod-service'
|
|
11
|
+
|
|
12
|
+
describe('admin get lists', () => {
|
|
13
|
+
let network: TestNetwork
|
|
14
|
+
let ozone: TestOzone
|
|
15
|
+
let agent: AtpAgent
|
|
16
|
+
let appviewAgent: AtpAgent
|
|
17
|
+
let sc: SeedClient
|
|
18
|
+
let modClient: ModeratorClient
|
|
19
|
+
let alicesList: RecordRef
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
network = await TestNetwork.create({
|
|
23
|
+
dbPostgresSchema: 'ozone_admin_get_lists',
|
|
24
|
+
})
|
|
25
|
+
ozone = network.ozone
|
|
26
|
+
agent = ozone.getClient()
|
|
27
|
+
appviewAgent = network.bsky.getClient()
|
|
28
|
+
sc = network.getSeedClient()
|
|
29
|
+
modClient = ozone.getModClient()
|
|
30
|
+
await basicSeed(sc)
|
|
31
|
+
alicesList = await sc.createList(sc.dids.alice, "Alice's List", 'mod')
|
|
32
|
+
AtpAgent.configure({ appLabelers: [ozone.ctx.cfg.service.did] })
|
|
33
|
+
await network.processAll()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
afterAll(async () => {
|
|
37
|
+
AtpAgent.configure({ appLabelers: [BSKY_LABELER_DID] })
|
|
38
|
+
await network.close()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const getAlicesList = async () => {
|
|
42
|
+
const [{ data: fromOzone }, { data: fromAppview }] = await Promise.all([
|
|
43
|
+
agent.api.app.bsky.graph.getLists(
|
|
44
|
+
{ actor: sc.dids.alice },
|
|
45
|
+
{ headers: await ozone.modHeaders() },
|
|
46
|
+
),
|
|
47
|
+
appviewAgent.api.app.bsky.graph.getLists({ actor: sc.dids.alice }),
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
return { fromOzone, fromAppview }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
it('returns lists from takendown account', async () => {
|
|
54
|
+
const beforeTakedown = await getAlicesList()
|
|
55
|
+
expect(beforeTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
|
|
56
|
+
expect(beforeTakedown.fromAppview.lists[0].uri).toEqual(alicesList.uriStr)
|
|
57
|
+
|
|
58
|
+
// Takedown alice's account
|
|
59
|
+
await modClient.emitEvent({
|
|
60
|
+
event: { $type: 'tools.ozone.moderation.defs#modEventTakedown' },
|
|
61
|
+
subject: {
|
|
62
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
63
|
+
did: sc.dids.alice,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
await network.processAll()
|
|
67
|
+
|
|
68
|
+
const afterTakedown = await getAlicesList()
|
|
69
|
+
|
|
70
|
+
// Verify that takendown list is shown when queried through ozone but not through appview
|
|
71
|
+
expect(afterTakedown.fromAppview.lists.length).toBe(0)
|
|
72
|
+
expect(afterTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
|
|
73
|
+
|
|
74
|
+
// Reverse alice's account takedown
|
|
75
|
+
await modClient.emitEvent({
|
|
76
|
+
event: { $type: 'tools.ozone.moderation.defs#modEventReverseTakedown' },
|
|
77
|
+
subject: {
|
|
78
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
79
|
+
did: sc.dids.alice,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
await network.processAll()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns takendown lists', async () => {
|
|
86
|
+
const beforeTakedown = await getAlicesList()
|
|
87
|
+
expect(beforeTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
|
|
88
|
+
expect(beforeTakedown.fromAppview.lists[0].uri).toEqual(alicesList.uriStr)
|
|
89
|
+
|
|
90
|
+
// Takedown alice's list using a !takedown label
|
|
91
|
+
await network.bsky.db.db
|
|
92
|
+
.insertInto('label')
|
|
93
|
+
.values({
|
|
94
|
+
src: ozone.ctx.cfg.service.did,
|
|
95
|
+
uri: alicesList.uriStr,
|
|
96
|
+
cid: alicesList.cidStr,
|
|
97
|
+
val: TAKEDOWN_LABEL,
|
|
98
|
+
neg: false,
|
|
99
|
+
cts: new Date().toISOString(),
|
|
100
|
+
})
|
|
101
|
+
.execute()
|
|
102
|
+
|
|
103
|
+
const afterTakedown = await getAlicesList()
|
|
104
|
+
|
|
105
|
+
// Verify that takendown list is shown when queried through ozone but not through appview
|
|
106
|
+
expect(afterTakedown.fromAppview.lists.length).toBe(0)
|
|
107
|
+
expect(afterTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
|
|
108
|
+
})
|
|
109
|
+
})
|
package/tests/moderation.test.ts
CHANGED
|
@@ -149,6 +149,45 @@ describe('moderation', () => {
|
|
|
149
149
|
})
|
|
150
150
|
await expect(promiseB).resolves.toBeDefined()
|
|
151
151
|
})
|
|
152
|
+
|
|
153
|
+
it('creates reports of a DM chat.', async () => {
|
|
154
|
+
const messageId1 = 'testmessageid1'
|
|
155
|
+
const messageId2 = 'testmessageid2'
|
|
156
|
+
const reportA = await sc.createReport({
|
|
157
|
+
reportedBy: sc.dids.alice,
|
|
158
|
+
reasonType: REASONSPAM,
|
|
159
|
+
subject: {
|
|
160
|
+
$type: 'chat.bsky.convo.defs#messageRef',
|
|
161
|
+
did: sc.dids.carol,
|
|
162
|
+
messageId: messageId1,
|
|
163
|
+
convoId: 'testconvoid1',
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
const reportB = await sc.createReport({
|
|
167
|
+
reportedBy: sc.dids.carol,
|
|
168
|
+
reasonType: REASONOTHER,
|
|
169
|
+
reason: 'defamation',
|
|
170
|
+
subject: {
|
|
171
|
+
$type: 'chat.bsky.convo.defs#messageRef',
|
|
172
|
+
did: sc.dids.carol,
|
|
173
|
+
messageId: messageId2,
|
|
174
|
+
convoId: 'testconvoid2',
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
178
|
+
const events = await ozone.ctx.db.db
|
|
179
|
+
.selectFrom('moderation_event')
|
|
180
|
+
.selectAll()
|
|
181
|
+
.where('subjectMessageId', 'in', [messageId1, messageId2])
|
|
182
|
+
.where('action', '=', 'tools.ozone.moderation.defs#modEventReport')
|
|
183
|
+
.execute()
|
|
184
|
+
expect(events.length).toBe(2)
|
|
185
|
+
expect(
|
|
186
|
+
events.every(
|
|
187
|
+
(row) => row.subjectType === 'chat.bsky.convo.defs#messageRef',
|
|
188
|
+
),
|
|
189
|
+
).toBe(true)
|
|
190
|
+
})
|
|
152
191
|
})
|
|
153
192
|
|
|
154
193
|
describe('actioning', () => {
|