@atproto/ozone 0.1.9 → 0.1.11
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/moderation/emitEvent.d.ts.map +1 -1
- package/dist/api/moderation/emitEvent.js +4 -0
- package/dist/api/moderation/emitEvent.js.map +1 -1
- package/dist/api/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/api/moderation/queryStatuses.js +2 -1
- package/dist/api/moderation/queryStatuses.js.map +1 -1
- package/dist/api/util.d.ts +1 -1
- package/dist/api/util.d.ts.map +1 -1
- package/dist/api/util.js +2 -0
- package/dist/api/util.js.map +1 -1
- package/dist/db/migrations/20240408T192432676Z-mute-reporting.d.ts +4 -0
- package/dist/db/migrations/20240408T192432676Z-mute-reporting.d.ts.map +1 -0
- package/dist/db/migrations/20240408T192432676Z-mute-reporting.js +18 -0
- package/dist/db/migrations/20240408T192432676Z-mute-reporting.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 +1 -1
- package/dist/db/schema/moderation_event.d.ts.map +1 -1
- package/dist/db/schema/moderation_subject_status.d.ts +1 -0
- package/dist/db/schema/moderation_subject_status.d.ts.map +1 -1
- package/dist/lexicon/lexicons.d.ts +68 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +78 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +16 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +21 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +22 -2
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js +22 -2
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryStatuses.d.ts.map +1 -1
- package/dist/mod-service/index.d.ts +6 -2
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +25 -1
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/lang-data.d.ts +12 -0
- package/dist/mod-service/lang-data.d.ts.map +1 -0
- package/dist/mod-service/lang-data.js +555 -0
- package/dist/mod-service/lang-data.js.map +1 -0
- package/dist/mod-service/lang.d.ts +1 -0
- package/dist/mod-service/lang.d.ts.map +1 -1
- package/dist/mod-service/lang.js +50 -0
- package/dist/mod-service/lang.js.map +1 -1
- package/dist/mod-service/status.d.ts +21 -1
- package/dist/mod-service/status.d.ts.map +1 -1
- package/dist/mod-service/status.js +23 -0
- package/dist/mod-service/status.js.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +3 -0
- package/dist/mod-service/views.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +4 -3
- package/src/api/moderation/emitEvent.ts +9 -0
- package/src/api/moderation/queryStatuses.ts +2 -0
- package/src/api/util.ts +2 -0
- package/src/db/migrations/20240408T192432676Z-mute-reporting.ts +15 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/moderation_event.ts +3 -0
- package/src/db/schema/moderation_subject_status.ts +1 -0
- package/src/lexicon/lexicons.ts +80 -0
- package/src/lexicon/types/app/bsky/actor/defs.ts +38 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +56 -0
- package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +3 -0
- package/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +2 -0
- package/src/mod-service/index.ts +33 -0
- package/src/mod-service/lang-data.ts +561 -0
- package/src/mod-service/lang.ts +34 -0
- package/src/mod-service/status.ts +26 -0
- package/src/mod-service/views.ts +3 -0
- package/tests/__snapshots__/get-record.test.ts.snap +2 -2
- package/tests/__snapshots__/moderation-events.test.ts.snap +6 -1
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +1 -1
- package/tests/lang.test.ts +109 -0
- package/tests/report-muting.test.ts +100 -0
|
@@ -57,6 +57,15 @@ const getSubjectStatusForModerationEvent = ({
|
|
|
57
57
|
suspendUntil: null,
|
|
58
58
|
lastReviewedAt: createdAt,
|
|
59
59
|
}
|
|
60
|
+
case 'tools.ozone.moderation.defs#modEventUnmuteReporter':
|
|
61
|
+
return {
|
|
62
|
+
lastReviewedBy: createdBy,
|
|
63
|
+
muteReportingUntil: null,
|
|
64
|
+
// It's not likely to receive an unmute event that does not already have a status row
|
|
65
|
+
// but if it does happen, default to unnecessary
|
|
66
|
+
reviewState: defaultReviewState,
|
|
67
|
+
lastReviewedAt: createdAt,
|
|
68
|
+
}
|
|
60
69
|
case 'tools.ozone.moderation.defs#modEventUnmute':
|
|
61
70
|
return {
|
|
62
71
|
lastReviewedBy: createdBy,
|
|
@@ -76,6 +85,18 @@ const getSubjectStatusForModerationEvent = ({
|
|
|
76
85
|
? new Date(Date.now() + durationInHours * HOUR).toISOString()
|
|
77
86
|
: null,
|
|
78
87
|
}
|
|
88
|
+
case 'tools.ozone.moderation.defs#modEventMuteReporter':
|
|
89
|
+
return {
|
|
90
|
+
lastReviewedBy: createdBy,
|
|
91
|
+
lastReviewedAt: createdAt,
|
|
92
|
+
// By default, mute for 24hrs
|
|
93
|
+
muteReportingUntil: new Date(
|
|
94
|
+
Date.now() + (durationInHours || 24) * HOUR,
|
|
95
|
+
).toISOString(),
|
|
96
|
+
// It's not likely to receive a mute event on a subject that does not already have a status row
|
|
97
|
+
// but if it does happen, default to unnecessary
|
|
98
|
+
reviewState: defaultReviewState,
|
|
99
|
+
}
|
|
79
100
|
case 'tools.ozone.moderation.defs#modEventMute':
|
|
80
101
|
return {
|
|
81
102
|
lastReviewedBy: createdBy,
|
|
@@ -140,6 +161,11 @@ export const adjustModerationSubjectStatus = async (
|
|
|
140
161
|
.selectAll()
|
|
141
162
|
.executeTakeFirst()
|
|
142
163
|
|
|
164
|
+
// If reporting is muted for this reporter, we don't want to update the subject status
|
|
165
|
+
if (meta?.isReporterMuted) {
|
|
166
|
+
return currentStatus || null
|
|
167
|
+
}
|
|
168
|
+
|
|
143
169
|
const isAppealEvent =
|
|
144
170
|
action === 'tools.ozone.moderation.defs#modEventReport' &&
|
|
145
171
|
meta?.reportType === REASONAPPEAL
|
package/src/mod-service/views.ts
CHANGED
|
@@ -108,6 +108,7 @@ export class ModerationViews {
|
|
|
108
108
|
|
|
109
109
|
if (
|
|
110
110
|
[
|
|
111
|
+
'tools.ozone.moderation.defs#modEventMuteReporter',
|
|
111
112
|
'tools.ozone.moderation.defs#modEventTakedown',
|
|
112
113
|
'tools.ozone.moderation.defs#modEventMute',
|
|
113
114
|
].includes(event.action)
|
|
@@ -157,6 +158,7 @@ export class ModerationViews {
|
|
|
157
158
|
eventView.event = {
|
|
158
159
|
...eventView.event,
|
|
159
160
|
reportType: event.meta?.reportType ?? undefined,
|
|
161
|
+
isReporterMuted: !!event.meta?.isReporterMuted,
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
|
|
@@ -500,6 +502,7 @@ export class ModerationViews {
|
|
|
500
502
|
lastReportedAt: status.lastReportedAt ?? undefined,
|
|
501
503
|
lastAppealedAt: status.lastAppealedAt ?? undefined,
|
|
502
504
|
muteUntil: status.muteUntil ?? undefined,
|
|
505
|
+
muteReportingUntil: status.muteReportingUntil ?? undefined,
|
|
503
506
|
suspendUntil: status.suspendUntil ?? undefined,
|
|
504
507
|
takendown: status.takendown ?? undefined,
|
|
505
508
|
appealed: status.appealed ?? undefined,
|
|
@@ -42,7 +42,7 @@ Object {
|
|
|
42
42
|
"subjectBlobCids": Array [],
|
|
43
43
|
"subjectRepoHandle": "alice.test",
|
|
44
44
|
"tags": Array [
|
|
45
|
-
"lang:
|
|
45
|
+
"lang:en",
|
|
46
46
|
],
|
|
47
47
|
"takendown": true,
|
|
48
48
|
"updatedAt": "1970-01-01T00:00:00.000Z",
|
|
@@ -141,7 +141,7 @@ Object {
|
|
|
141
141
|
"subjectBlobCids": Array [],
|
|
142
142
|
"subjectRepoHandle": "alice.test",
|
|
143
143
|
"tags": Array [
|
|
144
|
-
"lang:
|
|
144
|
+
"lang:en",
|
|
145
145
|
],
|
|
146
146
|
"takendown": true,
|
|
147
147
|
"updatedAt": "1970-01-01T00:00:00.000Z",
|
|
@@ -7,6 +7,7 @@ Object {
|
|
|
7
7
|
"event": Object {
|
|
8
8
|
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
9
9
|
"comment": "X",
|
|
10
|
+
"isReporterMuted": false,
|
|
10
11
|
"reportType": "com.atproto.moderation.defs#reasonMisleading",
|
|
11
12
|
},
|
|
12
13
|
"id": 1,
|
|
@@ -77,6 +78,7 @@ Array [
|
|
|
77
78
|
"event": Object {
|
|
78
79
|
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
79
80
|
"comment": "X",
|
|
81
|
+
"isReporterMuted": false,
|
|
80
82
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
81
83
|
},
|
|
82
84
|
"id": 11,
|
|
@@ -113,6 +115,7 @@ Array [
|
|
|
113
115
|
"event": Object {
|
|
114
116
|
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
115
117
|
"comment": "X",
|
|
118
|
+
"isReporterMuted": false,
|
|
116
119
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
117
120
|
},
|
|
118
121
|
"id": 5,
|
|
@@ -135,6 +138,7 @@ Array [
|
|
|
135
138
|
"event": Object {
|
|
136
139
|
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
137
140
|
"comment": "X",
|
|
141
|
+
"isReporterMuted": false,
|
|
138
142
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
139
143
|
},
|
|
140
144
|
"id": 10,
|
|
@@ -152,7 +156,7 @@ Array [
|
|
|
152
156
|
"event": Object {
|
|
153
157
|
"$type": "tools.ozone.moderation.defs#modEventTag",
|
|
154
158
|
"add": Array [
|
|
155
|
-
"lang:
|
|
159
|
+
"lang:en",
|
|
156
160
|
],
|
|
157
161
|
"remove": Array [],
|
|
158
162
|
},
|
|
@@ -172,6 +176,7 @@ Array [
|
|
|
172
176
|
"event": Object {
|
|
173
177
|
"$type": "tools.ozone.moderation.defs#modEventReport",
|
|
174
178
|
"comment": "X",
|
|
179
|
+
"isReporterMuted": false,
|
|
175
180
|
"reportType": "com.atproto.moderation.defs#reasonSpam",
|
|
176
181
|
},
|
|
177
182
|
"id": 3,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ModeratorClient,
|
|
3
|
+
SeedClient,
|
|
4
|
+
TestNetwork,
|
|
5
|
+
basicSeed,
|
|
6
|
+
} from '@atproto/dev-env'
|
|
7
|
+
import AtpAgent from '@atproto/api'
|
|
8
|
+
import { REASONSPAM } from '../src/lexicon/types/com/atproto/moderation/defs'
|
|
9
|
+
|
|
10
|
+
describe('moderation status language tagging', () => {
|
|
11
|
+
let network: TestNetwork
|
|
12
|
+
let agent: AtpAgent
|
|
13
|
+
let sc: SeedClient
|
|
14
|
+
let modClient: ModeratorClient
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
network = await TestNetwork.create({
|
|
18
|
+
dbPostgresSchema: 'ozone_blob_divert_test',
|
|
19
|
+
ozone: {
|
|
20
|
+
blobDivertUrl: `https://blob-report.com`,
|
|
21
|
+
blobDivertAdminPassword: 'test-auth-token',
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
agent = network.pds.getClient()
|
|
25
|
+
sc = network.getSeedClient()
|
|
26
|
+
modClient = network.ozone.getModClient()
|
|
27
|
+
await basicSeed(sc)
|
|
28
|
+
await network.processAll()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
await network.close()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const getStatus = async (subject: string) => {
|
|
36
|
+
const { subjectStatuses } = await modClient.queryStatuses({
|
|
37
|
+
subject,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return subjectStatuses[0]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
it('Adds language tag to post from text', async () => {
|
|
44
|
+
const createPostAndReport = async (text: string) => {
|
|
45
|
+
const post = await sc.post(sc.dids.carol, text)
|
|
46
|
+
await network.processAll()
|
|
47
|
+
const report = await sc.createReport({
|
|
48
|
+
reasonType: REASONSPAM,
|
|
49
|
+
subject: {
|
|
50
|
+
$type: 'com.atproto.repo.strongRef',
|
|
51
|
+
uri: post.ref.uriStr,
|
|
52
|
+
cid: post.ref.cidStr,
|
|
53
|
+
},
|
|
54
|
+
reportedBy: sc.dids.alice,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return { post, report }
|
|
58
|
+
}
|
|
59
|
+
const [japanesePost, greekPost] = await Promise.all([
|
|
60
|
+
createPostAndReport('Xで有名な人達+反AIや絵描きによくない'),
|
|
61
|
+
createPostAndReport(
|
|
62
|
+
'Λορεμ ιπσθμ δολορ σιτ αμετ, μει θτ vιδιτ νοστρθμ προπριαε',
|
|
63
|
+
),
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
const [japanesePostStatus, greekPostStatus] = await Promise.all([
|
|
67
|
+
getStatus(japanesePost.post.ref.uriStr),
|
|
68
|
+
getStatus(greekPost.post.ref.uriStr),
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
expect(japanesePostStatus.tags).toContain('lang:ja')
|
|
72
|
+
expect(greekPostStatus.tags).toContain('lang:el')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('Uses name/description text for language tag for list', async () => {
|
|
76
|
+
const createListAndReport = async (name: string, description?: string) => {
|
|
77
|
+
const list = await sc.createList(sc.dids.carol, name, 'mod', {
|
|
78
|
+
description,
|
|
79
|
+
})
|
|
80
|
+
await network.processAll()
|
|
81
|
+
const report = await sc.createReport({
|
|
82
|
+
reasonType: REASONSPAM,
|
|
83
|
+
subject: {
|
|
84
|
+
$type: 'com.atproto.repo.strongRef',
|
|
85
|
+
uri: list.uriStr,
|
|
86
|
+
cid: list.cidStr,
|
|
87
|
+
},
|
|
88
|
+
reportedBy: sc.dids.alice,
|
|
89
|
+
})
|
|
90
|
+
return { list, report }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const [listWithDescription, listWithoutDescription] = await Promise.all([
|
|
94
|
+
createListAndReport(
|
|
95
|
+
'よくない',
|
|
96
|
+
'Xで有名な人達+反AIや絵描きによくない感情を持つ人達+絵描き詐称',
|
|
97
|
+
),
|
|
98
|
+
createListAndReport('人達+反AIや絵描きによくない感情'),
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
const [japaneseListStatus, chineseListStatus] = await Promise.all([
|
|
102
|
+
getStatus(listWithDescription.list.uriStr),
|
|
103
|
+
getStatus(listWithoutDescription.list.uriStr),
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
expect(japaneseListStatus.tags).toContain('lang:ja')
|
|
107
|
+
expect(chineseListStatus.tags).toContain('lang:ja')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TestNetwork,
|
|
3
|
+
SeedClient,
|
|
4
|
+
basicSeed,
|
|
5
|
+
ModeratorClient,
|
|
6
|
+
} from '@atproto/dev-env'
|
|
7
|
+
import {
|
|
8
|
+
ComAtprotoModerationDefs,
|
|
9
|
+
ToolsOzoneModerationDefs,
|
|
10
|
+
} from '@atproto/api'
|
|
11
|
+
import {
|
|
12
|
+
REVIEWNONE,
|
|
13
|
+
REVIEWOPEN,
|
|
14
|
+
} from '../src/lexicon/types/tools/ozone/moderation/defs'
|
|
15
|
+
|
|
16
|
+
describe('report-muting', () => {
|
|
17
|
+
let network: TestNetwork
|
|
18
|
+
let sc: SeedClient
|
|
19
|
+
let modClient: ModeratorClient
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
network = await TestNetwork.create({
|
|
23
|
+
dbPostgresSchema: 'ozone_report_muting',
|
|
24
|
+
})
|
|
25
|
+
sc = network.getSeedClient()
|
|
26
|
+
modClient = network.ozone.getModClient()
|
|
27
|
+
await basicSeed(sc)
|
|
28
|
+
await network.processAll()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
await network.close()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const assertSubjectStatus = async (
|
|
36
|
+
subject: string,
|
|
37
|
+
status?: string,
|
|
38
|
+
): Promise<ToolsOzoneModerationDefs.SubjectStatusView | undefined> => {
|
|
39
|
+
const res = await modClient.queryStatuses({
|
|
40
|
+
subject,
|
|
41
|
+
})
|
|
42
|
+
expect(res.subjectStatuses[0]?.reviewState).toEqual(status)
|
|
43
|
+
return res.subjectStatuses[0]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
it('does not change reviewState when muted reporter reports', async () => {
|
|
47
|
+
const bobsPostSubject = {
|
|
48
|
+
$type: 'com.atproto.repo.strongRef',
|
|
49
|
+
uri: sc.posts[sc.dids.bob][1].ref.uriStr,
|
|
50
|
+
cid: sc.posts[sc.dids.bob][1].ref.cidStr,
|
|
51
|
+
}
|
|
52
|
+
const carolsAccountSubject = {
|
|
53
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
54
|
+
did: sc.dids.carol,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await modClient.emitEvent({
|
|
58
|
+
event: {
|
|
59
|
+
$type: 'tools.ozone.moderation.defs#modEventMuteReporter',
|
|
60
|
+
durationInHours: 24,
|
|
61
|
+
},
|
|
62
|
+
subject: carolsAccountSubject,
|
|
63
|
+
})
|
|
64
|
+
await sc.createReport({
|
|
65
|
+
reportedBy: sc.dids.carol,
|
|
66
|
+
reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
|
|
67
|
+
reason: 'misleading',
|
|
68
|
+
subject: bobsPostSubject,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Verify that a subject status was not created for bob's post since the reporter was muted
|
|
72
|
+
await assertSubjectStatus(bobsPostSubject.uri, undefined)
|
|
73
|
+
// Verify, however, that the event was logged
|
|
74
|
+
await modClient.queryEvents({
|
|
75
|
+
subject: bobsPostSubject.uri,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Verify that reporting mute duration is stored for the reporter
|
|
79
|
+
const carolsStatus = await assertSubjectStatus(sc.dids.carol, REVIEWNONE)
|
|
80
|
+
expect(
|
|
81
|
+
new Date(`${carolsStatus?.muteReportingUntil}`).getTime(),
|
|
82
|
+
).toBeGreaterThan(Date.now())
|
|
83
|
+
|
|
84
|
+
await modClient.emitEvent({
|
|
85
|
+
event: {
|
|
86
|
+
$type: 'tools.ozone.moderation.defs#modEventUnmuteReporter',
|
|
87
|
+
},
|
|
88
|
+
subject: carolsAccountSubject,
|
|
89
|
+
})
|
|
90
|
+
await sc.createReport({
|
|
91
|
+
reportedBy: sc.dids.carol,
|
|
92
|
+
reasonType: ComAtprotoModerationDefs.REASONMISLEADING,
|
|
93
|
+
reason: 'misleading',
|
|
94
|
+
subject: bobsPostSubject,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Verify that a subject status was created for bob's post since the reporter was no longer muted
|
|
98
|
+
await assertSubjectStatus(bobsPostSubject.uri, REVIEWOPEN)
|
|
99
|
+
})
|
|
100
|
+
})
|