@atproto/bsky 0.0.15 → 0.0.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 +11 -0
- package/dist/api/com/atproto/moderation/util.d.ts +4 -3
- package/dist/context.d.ts +15 -0
- package/dist/db/index.js +26 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/pagination.d.ts +2 -1
- package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
- package/dist/db/tables/moderation.d.ts +24 -34
- package/dist/feed-gen/types.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2750 -2121
- package/dist/index.js.map +3 -3
- package/dist/lexicon/index.d.ts +11 -18
- package/dist/lexicon/lexicons.d.ts +414 -399
- package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
- package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
- package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
- package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
- package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
- package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
- package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
- package/dist/migrate-moderation-data.d.ts +1 -0
- package/dist/services/actor/views.d.ts +2 -5
- package/dist/services/feed/index.d.ts +1 -0
- package/dist/services/feed/util.d.ts +9 -1
- package/dist/services/feed/views.d.ts +6 -17
- package/dist/services/graph/index.d.ts +5 -29
- package/dist/services/graph/types.d.ts +1 -0
- package/dist/services/moderation/index.d.ts +135 -72
- package/dist/services/moderation/pagination.d.ts +36 -0
- package/dist/services/moderation/status.d.ts +13 -0
- package/dist/services/moderation/types.d.ts +35 -0
- package/dist/services/moderation/views.d.ts +18 -14
- package/dist/util/debug.d.ts +1 -1
- package/package.json +11 -11
- package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
- package/src/api/app/bsky/feed/getFeed.ts +9 -9
- package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
- package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
- package/src/api/app/bsky/feed/getListFeed.ts +1 -3
- package/src/api/app/bsky/feed/getPostThread.ts +15 -54
- package/src/api/app/bsky/feed/getPosts.ts +21 -18
- package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
- package/src/api/app/bsky/feed/getTimeline.ts +1 -3
- package/src/api/app/bsky/feed/searchPosts.ts +20 -17
- package/src/api/app/bsky/graph/getList.ts +6 -3
- package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
- package/src/api/app/bsky/graph/getListMutes.ts +2 -1
- package/src/api/app/bsky/graph/getLists.ts +2 -1
- package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
- package/src/api/blob-resolver.ts +6 -11
- package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
- package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
- package/src/api/com/atproto/admin/getRecord.ts +1 -0
- package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
- package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
- package/src/api/com/atproto/moderation/createReport.ts +9 -7
- package/src/api/com/atproto/moderation/util.ts +38 -20
- package/src/api/index.ts +8 -14
- package/src/auth.ts +29 -21
- package/src/auto-moderator/index.ts +26 -19
- package/src/context.ts +4 -0
- package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/pagination.ts +26 -3
- package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
- package/src/db/tables/moderation.ts +35 -52
- package/src/feed-gen/best-of-follows.ts +6 -3
- package/src/feed-gen/bsky-team.ts +1 -1
- package/src/feed-gen/hot-classic.ts +1 -1
- package/src/feed-gen/mutuals.ts +6 -2
- package/src/feed-gen/types.ts +1 -1
- package/src/feed-gen/whats-hot.ts +1 -1
- package/src/feed-gen/with-friends.ts +7 -3
- package/src/index.ts +2 -1
- package/src/lexicon/index.ts +30 -67
- package/src/lexicon/lexicons.ts +526 -491
- package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
- package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
- package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
- package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
- package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
- package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
- package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
- package/src/migrate-moderation-data.ts +414 -0
- package/src/services/actor/views.ts +5 -14
- package/src/services/feed/index.ts +26 -7
- package/src/services/feed/util.ts +47 -19
- package/src/services/feed/views.ts +68 -4
- package/src/services/graph/index.ts +21 -3
- package/src/services/graph/types.ts +1 -0
- package/src/services/indexing/plugins/block.ts +2 -3
- package/src/services/indexing/plugins/feed-generator.ts +2 -3
- package/src/services/indexing/plugins/follow.ts +2 -3
- package/src/services/indexing/plugins/like.ts +2 -3
- package/src/services/indexing/plugins/list-block.ts +2 -3
- package/src/services/indexing/plugins/list-item.ts +2 -3
- package/src/services/indexing/plugins/list.ts +2 -3
- package/src/services/indexing/plugins/post.ts +3 -4
- package/src/services/indexing/plugins/repost.ts +2 -3
- package/src/services/indexing/plugins/thread-gate.ts +2 -3
- package/src/services/label/index.ts +2 -3
- package/src/services/moderation/index.ts +380 -395
- package/src/services/moderation/pagination.ts +96 -0
- package/src/services/moderation/status.ts +244 -0
- package/src/services/moderation/types.ts +49 -0
- package/src/services/moderation/views.ts +278 -329
- package/src/util/debug.ts +2 -2
- package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
- package/tests/__snapshots__/indexing.test.ts.snap +0 -6
- package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
- package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
- package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
- package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
- package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
- package/tests/admin/get-record.test.ts +5 -9
- package/tests/admin/get-repo.test.ts +5 -9
- package/tests/admin/moderation-events.test.ts +221 -0
- package/tests/admin/moderation-statuses.test.ts +145 -0
- package/tests/admin/moderation.test.ts +512 -860
- package/tests/admin/repo-search.test.ts +2 -3
- package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
- package/tests/auto-moderator/takedowns.test.ts +45 -18
- package/tests/feed-generation.test.ts +57 -9
- package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
- package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
- package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
- package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
- package/tests/views/actor-search.test.ts +2 -3
- package/tests/views/author-feed.test.ts +42 -36
- package/tests/views/follows.test.ts +40 -35
- package/tests/views/list-feed.test.ts +17 -9
- package/tests/views/notifications.test.ts +13 -9
- package/tests/views/profile.test.ts +20 -18
- package/tests/views/thread.test.ts +54 -26
- package/tests/views/threadgating.test.ts +51 -19
- package/tests/views/timeline.test.ts +21 -13
- package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
- package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
- package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
- package/dist/lexicon/types/com/atproto/admin/getModerationReport.d.ts +0 -29
- package/dist/lexicon/types/com/atproto/admin/resolveModerationReports.d.ts +0 -36
- package/dist/lexicon/types/com/atproto/admin/reverseModerationAction.d.ts +0 -36
- package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
- package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
- package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
- package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
- package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
- package/src/lexicon/types/com/atproto/admin/getModerationAction.ts +0 -41
- package/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +0 -49
- package/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +0 -49
- package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
- package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
- package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
- package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
- package/tests/admin/get-moderation-action.test.ts +0 -100
- package/tests/admin/get-moderation-actions.test.ts +0 -164
- package/tests/admin/get-moderation-report.test.ts +0 -100
- package/tests/admin/get-moderation-reports.test.ts +0 -332
- /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
- /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { TestNetwork, ImageRef, RecordRef, SeedClient } from '@atproto/dev-env'
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import AtpAgent, {
|
|
3
|
+
ComAtprotoAdminEmitModerationEvent,
|
|
4
|
+
ComAtprotoAdminQueryModerationStatuses,
|
|
5
|
+
ComAtprotoModerationCreateReport,
|
|
6
|
+
} from '@atproto/api'
|
|
4
7
|
import { AtUri } from '@atproto/syntax'
|
|
5
8
|
import { forSnapshot } from '../_util'
|
|
6
9
|
import basicSeed from '../seeds/basic'
|
|
7
10
|
import {
|
|
8
|
-
|
|
9
|
-
ESCALATE,
|
|
10
|
-
FLAG,
|
|
11
|
-
TAKEDOWN,
|
|
12
|
-
} from '../../src/lexicon/types/com/atproto/admin/defs'
|
|
13
|
-
import {
|
|
11
|
+
REASONMISLEADING,
|
|
14
12
|
REASONOTHER,
|
|
15
13
|
REASONSPAM,
|
|
16
14
|
} from '../../src/lexicon/types/com/atproto/moderation/defs'
|
|
17
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
ModEventLabel,
|
|
17
|
+
ModEventTakedown,
|
|
18
|
+
REVIEWCLOSED,
|
|
19
|
+
REVIEWESCALATED,
|
|
20
|
+
} from '../../src/lexicon/types/com/atproto/admin/defs'
|
|
21
|
+
import { PeriodicModerationEventReversal } from '../../src'
|
|
22
|
+
|
|
23
|
+
type BaseCreateReportParams =
|
|
24
|
+
| { account: string }
|
|
25
|
+
| { content: { uri: string; cid: string } }
|
|
26
|
+
type CreateReportParams = BaseCreateReportParams & {
|
|
27
|
+
author: string
|
|
28
|
+
} & Omit<ComAtprotoModerationCreateReport.InputSchema, 'subject'>
|
|
29
|
+
|
|
30
|
+
type TakedownParams = BaseCreateReportParams &
|
|
31
|
+
Omit<ComAtprotoAdminEmitModerationEvent.InputSchema, 'subject'>
|
|
18
32
|
|
|
19
33
|
describe('moderation', () => {
|
|
20
34
|
let network: TestNetwork
|
|
@@ -22,6 +36,99 @@ describe('moderation', () => {
|
|
|
22
36
|
let pdsAgent: AtpAgent
|
|
23
37
|
let sc: SeedClient
|
|
24
38
|
|
|
39
|
+
const createReport = async (params: CreateReportParams) => {
|
|
40
|
+
const { author, ...rest } = params
|
|
41
|
+
return agent.api.com.atproto.moderation.createReport(
|
|
42
|
+
{
|
|
43
|
+
// Set default type to spam
|
|
44
|
+
reasonType: REASONSPAM,
|
|
45
|
+
...rest,
|
|
46
|
+
subject:
|
|
47
|
+
'account' in params
|
|
48
|
+
? {
|
|
49
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
50
|
+
did: params.account,
|
|
51
|
+
}
|
|
52
|
+
: {
|
|
53
|
+
$type: 'com.atproto.repo.strongRef',
|
|
54
|
+
uri: params.content.uri,
|
|
55
|
+
cid: params.content.cid,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
headers: await network.serviceHeaders(author),
|
|
60
|
+
encoding: 'application/json',
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const performTakedown = async ({
|
|
66
|
+
durationInHours,
|
|
67
|
+
...rest
|
|
68
|
+
}: TakedownParams & Pick<ModEventTakedown, 'durationInHours'>) =>
|
|
69
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
70
|
+
{
|
|
71
|
+
event: {
|
|
72
|
+
$type: 'com.atproto.admin.defs#modEventTakedown',
|
|
73
|
+
durationInHours,
|
|
74
|
+
},
|
|
75
|
+
subject:
|
|
76
|
+
'account' in rest
|
|
77
|
+
? {
|
|
78
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
79
|
+
did: rest.account,
|
|
80
|
+
}
|
|
81
|
+
: {
|
|
82
|
+
$type: 'com.atproto.repo.strongRef',
|
|
83
|
+
uri: rest.content.uri,
|
|
84
|
+
cid: rest.content.cid,
|
|
85
|
+
},
|
|
86
|
+
createdBy: 'did:example:admin',
|
|
87
|
+
...rest,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
encoding: 'application/json',
|
|
91
|
+
headers: network.bsky.adminAuthHeaders(),
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const performReverseTakedown = async (params: TakedownParams) =>
|
|
96
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
97
|
+
{
|
|
98
|
+
event: {
|
|
99
|
+
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
|
|
100
|
+
},
|
|
101
|
+
subject:
|
|
102
|
+
'account' in params
|
|
103
|
+
? {
|
|
104
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
105
|
+
did: params.account,
|
|
106
|
+
}
|
|
107
|
+
: {
|
|
108
|
+
$type: 'com.atproto.repo.strongRef',
|
|
109
|
+
uri: params.content.uri,
|
|
110
|
+
cid: params.content.cid,
|
|
111
|
+
},
|
|
112
|
+
createdBy: 'did:example:admin',
|
|
113
|
+
...params,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
encoding: 'application/json',
|
|
117
|
+
headers: network.bsky.adminAuthHeaders(),
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const getStatuses = async (
|
|
122
|
+
params: ComAtprotoAdminQueryModerationStatuses.QueryParams,
|
|
123
|
+
) => {
|
|
124
|
+
const { data } = await agent.api.com.atproto.admin.queryModerationStatuses(
|
|
125
|
+
params,
|
|
126
|
+
{ headers: network.bsky.adminAuthHeaders() },
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return data
|
|
130
|
+
}
|
|
131
|
+
|
|
25
132
|
beforeAll(async () => {
|
|
26
133
|
network = await TestNetwork.create({
|
|
27
134
|
dbPostgresSchema: 'bsky_moderation',
|
|
@@ -39,89 +146,51 @@ describe('moderation', () => {
|
|
|
39
146
|
|
|
40
147
|
describe('reporting', () => {
|
|
41
148
|
it('creates reports of a repo.', async () => {
|
|
42
|
-
const { data: reportA } =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
encoding: 'application/json',
|
|
54
|
-
},
|
|
55
|
-
)
|
|
56
|
-
const { data: reportB } =
|
|
57
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
58
|
-
{
|
|
59
|
-
reasonType: REASONOTHER,
|
|
60
|
-
reason: 'impersonation',
|
|
61
|
-
subject: {
|
|
62
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
63
|
-
did: sc.dids.bob,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
headers: await network.serviceHeaders(sc.dids.carol),
|
|
68
|
-
encoding: 'application/json',
|
|
69
|
-
},
|
|
70
|
-
)
|
|
149
|
+
const { data: reportA } = await createReport({
|
|
150
|
+
reasonType: REASONSPAM,
|
|
151
|
+
account: sc.dids.bob,
|
|
152
|
+
author: sc.dids.alice,
|
|
153
|
+
})
|
|
154
|
+
const { data: reportB } = await createReport({
|
|
155
|
+
reasonType: REASONOTHER,
|
|
156
|
+
reason: 'impersonation',
|
|
157
|
+
account: sc.dids.bob,
|
|
158
|
+
author: sc.dids.carol,
|
|
159
|
+
})
|
|
71
160
|
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
72
161
|
})
|
|
73
162
|
|
|
74
163
|
it("allows reporting a repo that doesn't exist.", async () => {
|
|
75
|
-
const promise =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
did: 'did:plc:unknown',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
85
|
-
encoding: 'application/json',
|
|
86
|
-
},
|
|
87
|
-
)
|
|
164
|
+
const promise = createReport({
|
|
165
|
+
reasonType: REASONSPAM,
|
|
166
|
+
account: 'did:plc:unknown',
|
|
167
|
+
author: sc.dids.alice,
|
|
168
|
+
})
|
|
88
169
|
await expect(promise).resolves.toBeDefined()
|
|
89
170
|
})
|
|
90
171
|
|
|
91
172
|
it('creates reports of a record.', async () => {
|
|
92
173
|
const postA = sc.posts[sc.dids.bob][0].ref
|
|
93
174
|
const postB = sc.posts[sc.dids.bob][1].ref
|
|
94
|
-
const { data: reportA } =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
reason: 'defamation',
|
|
114
|
-
subject: {
|
|
115
|
-
$type: 'com.atproto.repo.strongRef',
|
|
116
|
-
uri: postB.uriStr,
|
|
117
|
-
cid: postB.cidStr,
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
headers: await network.serviceHeaders(sc.dids.carol),
|
|
122
|
-
encoding: 'application/json',
|
|
123
|
-
},
|
|
124
|
-
)
|
|
175
|
+
const { data: reportA } = await createReport({
|
|
176
|
+
author: sc.dids.alice,
|
|
177
|
+
reasonType: REASONSPAM,
|
|
178
|
+
content: {
|
|
179
|
+
$type: 'com.atproto.repo.strongRef',
|
|
180
|
+
uri: postA.uriStr,
|
|
181
|
+
cid: postA.cidStr,
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
const { data: reportB } = await createReport({
|
|
185
|
+
reasonType: REASONOTHER,
|
|
186
|
+
reason: 'defamation',
|
|
187
|
+
content: {
|
|
188
|
+
$type: 'com.atproto.repo.strongRef',
|
|
189
|
+
uri: postB.uriStr,
|
|
190
|
+
cid: postB.cidStr,
|
|
191
|
+
},
|
|
192
|
+
author: sc.dids.carol,
|
|
193
|
+
})
|
|
125
194
|
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
126
195
|
})
|
|
127
196
|
|
|
@@ -131,682 +200,238 @@ describe('moderation', () => {
|
|
|
131
200
|
const postUriBad = new AtUri(postA.uriStr)
|
|
132
201
|
postUriBad.rkey = 'badrkey'
|
|
133
202
|
|
|
134
|
-
const promiseA =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
cid: postA.cidStr,
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
145
|
-
encoding: 'application/json',
|
|
203
|
+
const promiseA = createReport({
|
|
204
|
+
reasonType: REASONSPAM,
|
|
205
|
+
content: {
|
|
206
|
+
$type: 'com.atproto.repo.strongRef',
|
|
207
|
+
uri: postUriBad.toString(),
|
|
208
|
+
cid: postA.cidStr,
|
|
146
209
|
},
|
|
147
|
-
|
|
210
|
+
author: sc.dids.alice,
|
|
211
|
+
})
|
|
148
212
|
await expect(promiseA).resolves.toBeDefined()
|
|
149
213
|
|
|
150
|
-
const promiseB =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
cid: postA.cidStr, // bad cid
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
headers: await network.serviceHeaders(sc.dids.carol),
|
|
162
|
-
encoding: 'application/json',
|
|
214
|
+
const promiseB = createReport({
|
|
215
|
+
reasonType: REASONOTHER,
|
|
216
|
+
reason: 'defamation',
|
|
217
|
+
content: {
|
|
218
|
+
$type: 'com.atproto.repo.strongRef',
|
|
219
|
+
uri: postB.uri.toString(),
|
|
220
|
+
cid: postA.cidStr, // bad cid
|
|
163
221
|
},
|
|
164
|
-
|
|
222
|
+
author: sc.dids.carol,
|
|
223
|
+
})
|
|
165
224
|
await expect(promiseB).resolves.toBeDefined()
|
|
166
225
|
})
|
|
167
226
|
})
|
|
168
227
|
|
|
169
228
|
describe('actioning', () => {
|
|
170
229
|
it('resolves reports on repos and records.', async () => {
|
|
171
|
-
const { data: reportA } =
|
|
172
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
173
|
-
{
|
|
174
|
-
reasonType: REASONSPAM,
|
|
175
|
-
subject: {
|
|
176
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
177
|
-
did: sc.dids.bob,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
182
|
-
encoding: 'application/json',
|
|
183
|
-
},
|
|
184
|
-
)
|
|
185
230
|
const post = sc.posts[sc.dids.bob][1].ref
|
|
186
|
-
const { data: reportB } =
|
|
187
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
188
|
-
{
|
|
189
|
-
reasonType: REASONOTHER,
|
|
190
|
-
reason: 'defamation',
|
|
191
|
-
subject: {
|
|
192
|
-
$type: 'com.atproto.repo.strongRef',
|
|
193
|
-
uri: post.uri.toString(),
|
|
194
|
-
cid: post.cid.toString(),
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
headers: await network.serviceHeaders(sc.dids.carol),
|
|
199
|
-
encoding: 'application/json',
|
|
200
|
-
},
|
|
201
|
-
)
|
|
202
|
-
const { data: action } =
|
|
203
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
204
|
-
{
|
|
205
|
-
action: TAKEDOWN,
|
|
206
|
-
subject: {
|
|
207
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
208
|
-
did: sc.dids.bob,
|
|
209
|
-
},
|
|
210
|
-
createdBy: 'did:example:admin',
|
|
211
|
-
reason: 'Y',
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
encoding: 'application/json',
|
|
215
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
216
|
-
},
|
|
217
|
-
)
|
|
218
|
-
const { data: actionResolvedReports } =
|
|
219
|
-
await agent.api.com.atproto.admin.resolveModerationReports(
|
|
220
|
-
{
|
|
221
|
-
actionId: action.id,
|
|
222
|
-
reportIds: [reportB.id, reportA.id],
|
|
223
|
-
createdBy: 'did:example:admin',
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
encoding: 'application/json',
|
|
227
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
228
|
-
},
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
expect(forSnapshot(actionResolvedReports)).toMatchSnapshot()
|
|
232
|
-
|
|
233
|
-
// Cleanup
|
|
234
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
235
|
-
{
|
|
236
|
-
id: action.id,
|
|
237
|
-
createdBy: 'did:example:admin',
|
|
238
|
-
reason: 'Y',
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
encoding: 'application/json',
|
|
242
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
243
|
-
},
|
|
244
|
-
)
|
|
245
|
-
})
|
|
246
231
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const { data: reportA } =
|
|
253
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
254
|
-
{
|
|
255
|
-
reasonType: REASONSPAM,
|
|
256
|
-
subject: {
|
|
257
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
258
|
-
did: unknownDid,
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
263
|
-
encoding: 'application/json',
|
|
264
|
-
},
|
|
265
|
-
)
|
|
266
|
-
const { data: reportB } =
|
|
267
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
268
|
-
{
|
|
269
|
-
reasonType: REASONOTHER,
|
|
270
|
-
reason: 'defamation',
|
|
271
|
-
subject: {
|
|
272
|
-
$type: 'com.atproto.repo.strongRef',
|
|
273
|
-
uri: unknownPostUri,
|
|
274
|
-
cid: unknownPostCid,
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
headers: await network.serviceHeaders(sc.dids.carol),
|
|
279
|
-
encoding: 'application/json',
|
|
280
|
-
},
|
|
281
|
-
)
|
|
282
|
-
// Take action on deleted content
|
|
283
|
-
const { data: action } =
|
|
284
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
285
|
-
{
|
|
286
|
-
action: FLAG,
|
|
287
|
-
subject: {
|
|
288
|
-
$type: 'com.atproto.repo.strongRef',
|
|
289
|
-
uri: unknownPostUri,
|
|
290
|
-
cid: unknownPostCid,
|
|
291
|
-
},
|
|
292
|
-
createdBy: 'did:example:admin',
|
|
293
|
-
reason: 'Y',
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
encoding: 'application/json',
|
|
297
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
298
|
-
},
|
|
299
|
-
)
|
|
300
|
-
await agent.api.com.atproto.admin.resolveModerationReports(
|
|
301
|
-
{
|
|
302
|
-
actionId: action.id,
|
|
303
|
-
reportIds: [reportB.id, reportA.id],
|
|
304
|
-
createdBy: 'did:example:admin',
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
encoding: 'application/json',
|
|
308
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
309
|
-
},
|
|
310
|
-
)
|
|
311
|
-
// Check report and action details
|
|
312
|
-
const { data: recordActionDetail } =
|
|
313
|
-
await agent.api.com.atproto.admin.getModerationAction(
|
|
314
|
-
{ id: action.id },
|
|
315
|
-
{ headers: network.bsky.adminAuthHeaders() },
|
|
316
|
-
)
|
|
317
|
-
const { data: reportADetail } =
|
|
318
|
-
await agent.api.com.atproto.admin.getModerationReport(
|
|
319
|
-
{ id: reportA.id },
|
|
320
|
-
{ headers: network.bsky.adminAuthHeaders() },
|
|
321
|
-
)
|
|
322
|
-
const { data: reportBDetail } =
|
|
323
|
-
await agent.api.com.atproto.admin.getModerationReport(
|
|
324
|
-
{ id: reportB.id },
|
|
325
|
-
{ headers: network.bsky.adminAuthHeaders() },
|
|
326
|
-
)
|
|
327
|
-
expect(
|
|
328
|
-
forSnapshot({
|
|
329
|
-
recordActionDetail,
|
|
330
|
-
reportADetail,
|
|
331
|
-
reportBDetail,
|
|
332
|
-
}),
|
|
333
|
-
).toMatchSnapshot()
|
|
334
|
-
// Cleanup
|
|
335
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
336
|
-
{
|
|
337
|
-
id: action.id,
|
|
338
|
-
createdBy: 'did:example:admin',
|
|
339
|
-
reason: 'Y',
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
encoding: 'application/json',
|
|
343
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
344
|
-
},
|
|
345
|
-
)
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
it('does not resolve report for mismatching repo.', async () => {
|
|
349
|
-
const { data: report } =
|
|
350
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
351
|
-
{
|
|
352
|
-
reasonType: REASONSPAM,
|
|
353
|
-
subject: {
|
|
354
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
355
|
-
did: sc.dids.bob,
|
|
356
|
-
},
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
360
|
-
encoding: 'application/json',
|
|
361
|
-
},
|
|
362
|
-
)
|
|
363
|
-
const { data: action } =
|
|
364
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
365
|
-
{
|
|
366
|
-
action: TAKEDOWN,
|
|
367
|
-
subject: {
|
|
368
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
369
|
-
did: sc.dids.carol,
|
|
370
|
-
},
|
|
371
|
-
createdBy: 'did:example:admin',
|
|
372
|
-
reason: 'Y',
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
encoding: 'application/json',
|
|
376
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
377
|
-
},
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
const promise = agent.api.com.atproto.admin.resolveModerationReports(
|
|
381
|
-
{
|
|
382
|
-
actionId: action.id,
|
|
383
|
-
reportIds: [report.id],
|
|
384
|
-
createdBy: 'did:example:admin',
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
encoding: 'application/json',
|
|
388
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
389
|
-
},
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
await expect(promise).rejects.toThrow(
|
|
393
|
-
`Report ${report.id} cannot be resolved by action`,
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
// Cleanup
|
|
397
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
398
|
-
{
|
|
399
|
-
id: action.id,
|
|
400
|
-
createdBy: 'did:example:admin',
|
|
401
|
-
reason: 'Y',
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
encoding: 'application/json',
|
|
405
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
406
|
-
},
|
|
407
|
-
)
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
it('does not resolve report for mismatching record.', async () => {
|
|
411
|
-
const postRef1 = sc.posts[sc.dids.alice][0].ref
|
|
412
|
-
const postRef2 = sc.posts[sc.dids.bob][0].ref
|
|
413
|
-
const { data: report } =
|
|
414
|
-
await agent.api.com.atproto.moderation.createReport(
|
|
415
|
-
{
|
|
416
|
-
reasonType: REASONSPAM,
|
|
417
|
-
subject: {
|
|
418
|
-
$type: 'com.atproto.repo.strongRef',
|
|
419
|
-
uri: postRef1.uriStr,
|
|
420
|
-
cid: postRef1.cidStr,
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
headers: await network.serviceHeaders(sc.dids.alice),
|
|
425
|
-
encoding: 'application/json',
|
|
426
|
-
},
|
|
427
|
-
)
|
|
428
|
-
const { data: action } =
|
|
429
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
430
|
-
{
|
|
431
|
-
action: TAKEDOWN,
|
|
432
|
-
subject: {
|
|
433
|
-
$type: 'com.atproto.repo.strongRef',
|
|
434
|
-
uri: postRef2.uriStr,
|
|
435
|
-
cid: postRef2.cidStr,
|
|
436
|
-
},
|
|
437
|
-
createdBy: 'did:example:admin',
|
|
438
|
-
reason: 'Y',
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
encoding: 'application/json',
|
|
442
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
443
|
-
},
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
const promise = agent.api.com.atproto.admin.resolveModerationReports(
|
|
447
|
-
{
|
|
448
|
-
actionId: action.id,
|
|
449
|
-
reportIds: [report.id],
|
|
450
|
-
createdBy: 'did:example:admin',
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
encoding: 'application/json',
|
|
454
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
455
|
-
},
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
await expect(promise).rejects.toThrow(
|
|
459
|
-
`Report ${report.id} cannot be resolved by action`,
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
// Cleanup
|
|
463
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
464
|
-
{
|
|
465
|
-
id: action.id,
|
|
466
|
-
createdBy: 'did:example:admin',
|
|
467
|
-
reason: 'Y',
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
encoding: 'application/json',
|
|
471
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
472
|
-
},
|
|
473
|
-
)
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
it('supports escalating and acknowledging for triage.', async () => {
|
|
477
|
-
const postRef1 = sc.posts[sc.dids.alice][0].ref
|
|
478
|
-
const postRef2 = sc.posts[sc.dids.bob][0].ref
|
|
479
|
-
const { data: action1 } =
|
|
480
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
481
|
-
{
|
|
482
|
-
action: ESCALATE,
|
|
483
|
-
subject: {
|
|
484
|
-
$type: 'com.atproto.repo.strongRef',
|
|
485
|
-
uri: postRef1.uri.toString(),
|
|
486
|
-
cid: postRef1.cid.toString(),
|
|
487
|
-
},
|
|
488
|
-
createdBy: 'did:example:admin',
|
|
489
|
-
reason: 'Y',
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
encoding: 'application/json',
|
|
493
|
-
headers: network.bsky.adminAuthHeaders('triage'),
|
|
494
|
-
},
|
|
495
|
-
)
|
|
496
|
-
expect(action1).toEqual(
|
|
497
|
-
expect.objectContaining({
|
|
498
|
-
action: ESCALATE,
|
|
499
|
-
subject: {
|
|
500
|
-
$type: 'com.atproto.repo.strongRef',
|
|
501
|
-
uri: postRef1.uriStr,
|
|
502
|
-
cid: postRef1.cidStr,
|
|
503
|
-
},
|
|
232
|
+
await Promise.all([
|
|
233
|
+
createReport({
|
|
234
|
+
reasonType: REASONSPAM,
|
|
235
|
+
account: sc.dids.bob,
|
|
236
|
+
author: sc.dids.alice,
|
|
504
237
|
}),
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
{
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
$type: 'com.atproto.repo.strongRef',
|
|
512
|
-
uri: postRef2.uri.toString(),
|
|
513
|
-
cid: postRef2.cid.toString(),
|
|
514
|
-
},
|
|
515
|
-
createdBy: 'did:example:admin',
|
|
516
|
-
reason: 'Y',
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
encoding: 'application/json',
|
|
520
|
-
headers: network.bsky.adminAuthHeaders('triage'),
|
|
521
|
-
},
|
|
522
|
-
)
|
|
523
|
-
expect(action2).toEqual(
|
|
524
|
-
expect.objectContaining({
|
|
525
|
-
action: ACKNOWLEDGE,
|
|
526
|
-
subject: {
|
|
527
|
-
$type: 'com.atproto.repo.strongRef',
|
|
528
|
-
uri: postRef2.uriStr,
|
|
529
|
-
cid: postRef2.cidStr,
|
|
238
|
+
createReport({
|
|
239
|
+
reasonType: REASONOTHER,
|
|
240
|
+
reason: 'defamation',
|
|
241
|
+
content: {
|
|
242
|
+
uri: post.uri.toString(),
|
|
243
|
+
cid: post.cid.toString(),
|
|
530
244
|
},
|
|
245
|
+
author: sc.dids.carol,
|
|
531
246
|
}),
|
|
532
|
-
)
|
|
533
|
-
// Cleanup
|
|
534
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
535
|
-
{
|
|
536
|
-
id: action1.id,
|
|
537
|
-
createdBy: 'did:example:admin',
|
|
538
|
-
reason: 'Y',
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
encoding: 'application/json',
|
|
542
|
-
headers: network.bsky.adminAuthHeaders('triage'),
|
|
543
|
-
},
|
|
544
|
-
)
|
|
545
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
546
|
-
{
|
|
547
|
-
id: action2.id,
|
|
548
|
-
createdBy: 'did:example:admin',
|
|
549
|
-
reason: 'Y',
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
encoding: 'application/json',
|
|
553
|
-
headers: network.bsky.adminAuthHeaders('triage'),
|
|
554
|
-
},
|
|
555
|
-
)
|
|
556
|
-
})
|
|
247
|
+
])
|
|
557
248
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
562
|
-
{
|
|
563
|
-
action: ACKNOWLEDGE,
|
|
564
|
-
subject: {
|
|
565
|
-
$type: 'com.atproto.repo.strongRef',
|
|
566
|
-
uri: postRef.uriStr,
|
|
567
|
-
cid: postRef.cidStr,
|
|
568
|
-
},
|
|
569
|
-
createdBy: 'did:example:admin',
|
|
570
|
-
reason: 'Y',
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
encoding: 'application/json',
|
|
574
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
575
|
-
},
|
|
576
|
-
)
|
|
577
|
-
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
|
|
578
|
-
{
|
|
579
|
-
action: FLAG,
|
|
580
|
-
subject: {
|
|
581
|
-
$type: 'com.atproto.repo.strongRef',
|
|
582
|
-
uri: postRef.uriStr,
|
|
583
|
-
cid: postRef.cidStr,
|
|
584
|
-
},
|
|
585
|
-
createdBy: 'did:example:admin',
|
|
586
|
-
reason: 'Y',
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
encoding: 'application/json',
|
|
590
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
591
|
-
},
|
|
592
|
-
)
|
|
593
|
-
await expect(flagPromise).rejects.toThrow(
|
|
594
|
-
'Subject already has an active action:',
|
|
595
|
-
)
|
|
249
|
+
await performTakedown({
|
|
250
|
+
account: sc.dids.bob,
|
|
251
|
+
})
|
|
596
252
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
id: acknowledge.id,
|
|
601
|
-
createdBy: 'did:example:admin',
|
|
602
|
-
reason: 'Y',
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
encoding: 'application/json',
|
|
606
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
607
|
-
},
|
|
608
|
-
)
|
|
609
|
-
const { data: flag } =
|
|
610
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
611
|
-
{
|
|
612
|
-
action: FLAG,
|
|
613
|
-
subject: {
|
|
614
|
-
$type: 'com.atproto.repo.strongRef',
|
|
615
|
-
uri: postRef.uriStr,
|
|
616
|
-
cid: postRef.cidStr,
|
|
617
|
-
},
|
|
618
|
-
createdBy: 'did:example:admin',
|
|
619
|
-
reason: 'Y',
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
encoding: 'application/json',
|
|
623
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
624
|
-
},
|
|
625
|
-
)
|
|
253
|
+
const moderationStatusOnBobsAccount = await getStatuses({
|
|
254
|
+
subject: sc.dids.bob,
|
|
255
|
+
})
|
|
626
256
|
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
{
|
|
635
|
-
encoding: 'application/json',
|
|
636
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
257
|
+
// Validate that subject status is set to review closed and takendown flag is on
|
|
258
|
+
expect(moderationStatusOnBobsAccount.subjectStatuses[0]).toMatchObject({
|
|
259
|
+
reviewState: REVIEWCLOSED,
|
|
260
|
+
takendown: true,
|
|
261
|
+
subject: {
|
|
262
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
263
|
+
did: sc.dids.bob,
|
|
637
264
|
},
|
|
638
|
-
)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// Cleanup
|
|
268
|
+
await performReverseTakedown({
|
|
269
|
+
account: sc.dids.bob,
|
|
270
|
+
})
|
|
639
271
|
})
|
|
640
272
|
|
|
641
|
-
it('
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
},
|
|
650
|
-
createdBy: 'did:example:admin',
|
|
651
|
-
reason: 'Y',
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
encoding: 'application/json',
|
|
655
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
656
|
-
},
|
|
657
|
-
)
|
|
658
|
-
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
|
|
273
|
+
it('supports escalating a subject', async () => {
|
|
274
|
+
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
275
|
+
const alicesPostSubject = {
|
|
276
|
+
$type: 'com.atproto.repo.strongRef',
|
|
277
|
+
uri: alicesPostRef.uri.toString(),
|
|
278
|
+
cid: alicesPostRef.cid.toString(),
|
|
279
|
+
}
|
|
280
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
659
281
|
{
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
did: sc.dids.alice,
|
|
282
|
+
event: {
|
|
283
|
+
$type: 'com.atproto.admin.defs#modEventEscalate',
|
|
284
|
+
comment: 'Y',
|
|
664
285
|
},
|
|
286
|
+
subject: alicesPostSubject,
|
|
665
287
|
createdBy: 'did:example:admin',
|
|
666
|
-
reason: 'Y',
|
|
667
288
|
},
|
|
668
289
|
{
|
|
669
290
|
encoding: 'application/json',
|
|
670
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
291
|
+
headers: network.bsky.adminAuthHeaders('triage'),
|
|
671
292
|
},
|
|
672
293
|
)
|
|
673
|
-
await expect(flagPromise).rejects.toThrow(
|
|
674
|
-
'Subject already has an active action:',
|
|
675
|
-
)
|
|
676
294
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
295
|
+
const alicesPostStatus = await getStatuses({
|
|
296
|
+
subject: alicesPostRef.uri.toString(),
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
expect(alicesPostStatus.subjectStatuses[0]).toMatchObject({
|
|
300
|
+
reviewState: REVIEWESCALATED,
|
|
301
|
+
takendown: false,
|
|
302
|
+
subject: alicesPostSubject,
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('adds persistent comment on subject through comment event', async () => {
|
|
307
|
+
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
308
|
+
const alicesPostSubject = {
|
|
309
|
+
$type: 'com.atproto.repo.strongRef',
|
|
310
|
+
uri: alicesPostRef.uri.toString(),
|
|
311
|
+
cid: alicesPostRef.cid.toString(),
|
|
312
|
+
}
|
|
313
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
314
|
+
{
|
|
315
|
+
event: {
|
|
316
|
+
$type: 'com.atproto.admin.defs#modEventComment',
|
|
317
|
+
sticky: true,
|
|
318
|
+
comment: 'This is a persistent note',
|
|
319
|
+
},
|
|
320
|
+
subject: alicesPostSubject,
|
|
681
321
|
createdBy: 'did:example:admin',
|
|
682
|
-
reason: 'Y',
|
|
683
322
|
},
|
|
684
323
|
{
|
|
685
324
|
encoding: 'application/json',
|
|
686
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
325
|
+
headers: network.bsky.adminAuthHeaders('triage'),
|
|
687
326
|
},
|
|
688
327
|
)
|
|
689
|
-
const { data: flag } =
|
|
690
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
691
|
-
{
|
|
692
|
-
action: FLAG,
|
|
693
|
-
subject: {
|
|
694
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
695
|
-
did: sc.dids.alice,
|
|
696
|
-
},
|
|
697
|
-
createdBy: 'did:example:admin',
|
|
698
|
-
reason: 'Y',
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
encoding: 'application/json',
|
|
702
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
703
|
-
},
|
|
704
|
-
)
|
|
705
328
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
encoding: 'application/json',
|
|
715
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
716
|
-
},
|
|
329
|
+
const alicesPostStatus = await getStatuses({
|
|
330
|
+
subject: alicesPostRef.uri.toString(),
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
expect(alicesPostStatus.subjectStatuses[0].comment).toEqual(
|
|
334
|
+
'This is a persistent note',
|
|
717
335
|
)
|
|
718
336
|
})
|
|
719
337
|
|
|
720
|
-
it('
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
action: ACKNOWLEDGE,
|
|
728
|
-
subject: {
|
|
729
|
-
$type: 'com.atproto.repo.strongRef',
|
|
730
|
-
uri: postA.ref.uriStr,
|
|
731
|
-
cid: postA.ref.cidStr,
|
|
732
|
-
},
|
|
733
|
-
subjectBlobCids: [img.image.ref.toString()],
|
|
734
|
-
createdBy: 'did:example:admin',
|
|
735
|
-
reason: 'Y',
|
|
736
|
-
},
|
|
737
|
-
{
|
|
738
|
-
encoding: 'application/json',
|
|
739
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
740
|
-
},
|
|
741
|
-
)
|
|
742
|
-
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
|
|
743
|
-
{
|
|
744
|
-
action: FLAG,
|
|
338
|
+
it('reverses status when revert event is triggered.', async () => {
|
|
339
|
+
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
340
|
+
const emitModEvent = async (
|
|
341
|
+
event: ComAtprotoAdminEmitModerationEvent.InputSchema['event'],
|
|
342
|
+
overwrites: Partial<ComAtprotoAdminEmitModerationEvent.InputSchema> = {},
|
|
343
|
+
) => {
|
|
344
|
+
const baseAction = {
|
|
745
345
|
subject: {
|
|
746
346
|
$type: 'com.atproto.repo.strongRef',
|
|
747
|
-
uri:
|
|
748
|
-
cid:
|
|
347
|
+
uri: alicesPostRef.uriStr,
|
|
348
|
+
cid: alicesPostRef.cidStr,
|
|
749
349
|
},
|
|
750
|
-
subjectBlobCids: [img.image.ref.toString()],
|
|
751
|
-
createdBy: 'did:example:admin',
|
|
752
|
-
reason: 'Y',
|
|
753
|
-
},
|
|
754
|
-
{
|
|
755
|
-
encoding: 'application/json',
|
|
756
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
757
|
-
},
|
|
758
|
-
)
|
|
759
|
-
await expect(flagPromise).rejects.toThrow(
|
|
760
|
-
'Blob already has an active action:',
|
|
761
|
-
)
|
|
762
|
-
// Reverse current then retry
|
|
763
|
-
await agent.api.com.atproto.admin.reverseModerationAction(
|
|
764
|
-
{
|
|
765
|
-
id: acknowledge.id,
|
|
766
350
|
createdBy: 'did:example:admin',
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
{
|
|
770
|
-
encoding: 'application/json',
|
|
771
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
772
|
-
},
|
|
773
|
-
)
|
|
774
|
-
const { data: flag } =
|
|
775
|
-
await agent.api.com.atproto.admin.takeModerationAction(
|
|
351
|
+
}
|
|
352
|
+
return agent.api.com.atproto.admin.emitModerationEvent(
|
|
776
353
|
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
uri: postB.ref.uriStr,
|
|
781
|
-
cid: postB.ref.cidStr,
|
|
782
|
-
},
|
|
783
|
-
subjectBlobCids: [img.image.ref.toString()],
|
|
784
|
-
createdBy: 'did:example:admin',
|
|
785
|
-
reason: 'Y',
|
|
354
|
+
event,
|
|
355
|
+
...baseAction,
|
|
356
|
+
...overwrites,
|
|
786
357
|
},
|
|
787
358
|
{
|
|
788
359
|
encoding: 'application/json',
|
|
789
360
|
headers: network.bsky.adminAuthHeaders(),
|
|
790
361
|
},
|
|
791
362
|
)
|
|
363
|
+
}
|
|
364
|
+
// Validate that subject status is marked as escalated
|
|
365
|
+
await emitModEvent({
|
|
366
|
+
$type: 'com.atproto.admin.defs#modEventReport',
|
|
367
|
+
reportType: REASONSPAM,
|
|
368
|
+
})
|
|
369
|
+
await emitModEvent({
|
|
370
|
+
$type: 'com.atproto.admin.defs#modEventReport',
|
|
371
|
+
reportType: REASONMISLEADING,
|
|
372
|
+
})
|
|
373
|
+
await emitModEvent({
|
|
374
|
+
$type: 'com.atproto.admin.defs#modEventEscalate',
|
|
375
|
+
})
|
|
376
|
+
const alicesPostStatusAfterEscalation = await getStatuses({
|
|
377
|
+
subject: alicesPostRef.uriStr,
|
|
378
|
+
})
|
|
379
|
+
expect(
|
|
380
|
+
alicesPostStatusAfterEscalation.subjectStatuses[0].reviewState,
|
|
381
|
+
).toEqual(REVIEWESCALATED)
|
|
792
382
|
|
|
793
|
-
//
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
383
|
+
// Validate that subject status is marked as takendown
|
|
384
|
+
|
|
385
|
+
await emitModEvent({
|
|
386
|
+
$type: 'com.atproto.admin.defs#modEventLabel',
|
|
387
|
+
createLabelVals: ['nsfw'],
|
|
388
|
+
negateLabelVals: [],
|
|
389
|
+
})
|
|
390
|
+
const { data: takedownAction } = await emitModEvent({
|
|
391
|
+
$type: 'com.atproto.admin.defs#modEventTakedown',
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const alicesPostStatusAfterTakedown = await getStatuses({
|
|
395
|
+
subject: alicesPostRef.uriStr,
|
|
396
|
+
})
|
|
397
|
+
expect(alicesPostStatusAfterTakedown.subjectStatuses[0]).toMatchObject({
|
|
398
|
+
reviewState: REVIEWCLOSED,
|
|
399
|
+
takendown: true,
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
await emitModEvent({
|
|
403
|
+
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
|
|
404
|
+
})
|
|
405
|
+
const alicesPostStatusAfterRevert = await getStatuses({
|
|
406
|
+
subject: alicesPostRef.uriStr,
|
|
407
|
+
})
|
|
408
|
+
// Validate that after reverting, the status of the subject is reverted to the last status changing event
|
|
409
|
+
expect(alicesPostStatusAfterRevert.subjectStatuses[0]).toMatchObject({
|
|
410
|
+
reviewState: REVIEWCLOSED,
|
|
411
|
+
takendown: false,
|
|
412
|
+
})
|
|
413
|
+
// Validate that after reverting, the last review date of the subject
|
|
414
|
+
// DOES NOT update to the the last status changing event
|
|
415
|
+
expect(
|
|
416
|
+
new Date(
|
|
417
|
+
alicesPostStatusAfterEscalation.subjectStatuses[0]
|
|
418
|
+
.lastReviewedAt as string,
|
|
419
|
+
) <
|
|
420
|
+
new Date(
|
|
421
|
+
alicesPostStatusAfterRevert.subjectStatuses[0]
|
|
422
|
+
.lastReviewedAt as string,
|
|
423
|
+
),
|
|
424
|
+
).toBeTruthy()
|
|
805
425
|
})
|
|
806
426
|
|
|
807
|
-
it('negates an existing label
|
|
427
|
+
it('negates an existing label.', async () => {
|
|
808
428
|
const { ctx } = network.bsky
|
|
809
429
|
const post = sc.posts[sc.dids.bob][0].ref
|
|
430
|
+
const bobsPostSubject = {
|
|
431
|
+
$type: 'com.atproto.repo.strongRef',
|
|
432
|
+
uri: post.uriStr,
|
|
433
|
+
cid: post.cidStr,
|
|
434
|
+
}
|
|
810
435
|
const labelingService = ctx.services.label(ctx.db.getPrimary())
|
|
811
436
|
await labelingService.formatAndCreate(
|
|
812
437
|
ctx.cfg.labelerDid,
|
|
@@ -814,16 +439,18 @@ describe('moderation', () => {
|
|
|
814
439
|
post.cidStr,
|
|
815
440
|
{ create: ['kittens'] },
|
|
816
441
|
)
|
|
817
|
-
|
|
442
|
+
await emitLabelEvent({
|
|
818
443
|
negateLabelVals: ['kittens'],
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
uri: post.uriStr,
|
|
822
|
-
cid: post.cidStr,
|
|
823
|
-
},
|
|
444
|
+
createLabelVals: [],
|
|
445
|
+
subject: bobsPostSubject,
|
|
824
446
|
})
|
|
825
447
|
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
826
|
-
|
|
448
|
+
|
|
449
|
+
await emitLabelEvent({
|
|
450
|
+
createLabelVals: ['kittens'],
|
|
451
|
+
negateLabelVals: [],
|
|
452
|
+
subject: bobsPostSubject,
|
|
453
|
+
})
|
|
827
454
|
await expect(getRecordLabels(post.uriStr)).resolves.toEqual(['kittens'])
|
|
828
455
|
// Cleanup
|
|
829
456
|
await labelingService.formatAndCreate(
|
|
@@ -838,8 +465,9 @@ describe('moderation', () => {
|
|
|
838
465
|
const { ctx } = network.bsky
|
|
839
466
|
const post = sc.posts[sc.dids.bob][0].ref
|
|
840
467
|
const labelingService = ctx.services.label(ctx.db.getPrimary())
|
|
841
|
-
|
|
468
|
+
await emitLabelEvent({
|
|
842
469
|
negateLabelVals: ['bears'],
|
|
470
|
+
createLabelVals: [],
|
|
843
471
|
subject: {
|
|
844
472
|
$type: 'com.atproto.repo.strongRef',
|
|
845
473
|
uri: post.uriStr,
|
|
@@ -847,7 +475,15 @@ describe('moderation', () => {
|
|
|
847
475
|
},
|
|
848
476
|
})
|
|
849
477
|
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
850
|
-
await
|
|
478
|
+
await emitLabelEvent({
|
|
479
|
+
createLabelVals: ['bears'],
|
|
480
|
+
negateLabelVals: [],
|
|
481
|
+
subject: {
|
|
482
|
+
$type: 'com.atproto.repo.strongRef',
|
|
483
|
+
uri: post.uriStr,
|
|
484
|
+
cid: post.cidStr,
|
|
485
|
+
},
|
|
486
|
+
})
|
|
851
487
|
await expect(getRecordLabels(post.uriStr)).resolves.toEqual(['bears'])
|
|
852
488
|
// Cleanup
|
|
853
489
|
await labelingService.formatAndCreate(
|
|
@@ -860,7 +496,7 @@ describe('moderation', () => {
|
|
|
860
496
|
|
|
861
497
|
it('creates non-existing labels and reverses.', async () => {
|
|
862
498
|
const post = sc.posts[sc.dids.bob][0].ref
|
|
863
|
-
|
|
499
|
+
await emitLabelEvent({
|
|
864
500
|
createLabelVals: ['puppies', 'doggies'],
|
|
865
501
|
negateLabelVals: [],
|
|
866
502
|
subject: {
|
|
@@ -873,35 +509,20 @@ describe('moderation', () => {
|
|
|
873
509
|
'puppies',
|
|
874
510
|
'doggies',
|
|
875
511
|
])
|
|
876
|
-
await
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
it('no-ops when creating an existing label and reverses.', async () => {
|
|
881
|
-
const { ctx } = network.bsky
|
|
882
|
-
const post = sc.posts[sc.dids.bob][0].ref
|
|
883
|
-
const labelingService = ctx.services.label(ctx.db.getPrimary())
|
|
884
|
-
await labelingService.formatAndCreate(
|
|
885
|
-
ctx.cfg.labelerDid,
|
|
886
|
-
post.uriStr,
|
|
887
|
-
post.cidStr,
|
|
888
|
-
{ create: ['birds'] },
|
|
889
|
-
)
|
|
890
|
-
const action = await actionWithLabels({
|
|
891
|
-
createLabelVals: ['birds'],
|
|
512
|
+
await emitLabelEvent({
|
|
513
|
+
negateLabelVals: ['puppies', 'doggies'],
|
|
514
|
+
createLabelVals: [],
|
|
892
515
|
subject: {
|
|
893
516
|
$type: 'com.atproto.repo.strongRef',
|
|
894
517
|
uri: post.uriStr,
|
|
895
518
|
cid: post.cidStr,
|
|
896
519
|
},
|
|
897
520
|
})
|
|
898
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual(['birds'])
|
|
899
|
-
await reverse(action.id)
|
|
900
521
|
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
901
522
|
})
|
|
902
523
|
|
|
903
524
|
it('creates labels on a repo and reverses.', async () => {
|
|
904
|
-
|
|
525
|
+
await emitLabelEvent({
|
|
905
526
|
createLabelVals: ['puppies', 'doggies'],
|
|
906
527
|
negateLabelVals: [],
|
|
907
528
|
subject: {
|
|
@@ -913,7 +534,14 @@ describe('moderation', () => {
|
|
|
913
534
|
'puppies',
|
|
914
535
|
'doggies',
|
|
915
536
|
])
|
|
916
|
-
await
|
|
537
|
+
await emitLabelEvent({
|
|
538
|
+
negateLabelVals: ['puppies', 'doggies'],
|
|
539
|
+
createLabelVals: [],
|
|
540
|
+
subject: {
|
|
541
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
542
|
+
did: sc.dids.bob,
|
|
543
|
+
},
|
|
544
|
+
})
|
|
917
545
|
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual([])
|
|
918
546
|
})
|
|
919
547
|
|
|
@@ -926,7 +554,7 @@ describe('moderation', () => {
|
|
|
926
554
|
null,
|
|
927
555
|
{ create: ['kittens'] },
|
|
928
556
|
)
|
|
929
|
-
|
|
557
|
+
await emitLabelEvent({
|
|
930
558
|
createLabelVals: ['puppies'],
|
|
931
559
|
negateLabelVals: ['kittens'],
|
|
932
560
|
subject: {
|
|
@@ -935,22 +563,32 @@ describe('moderation', () => {
|
|
|
935
563
|
},
|
|
936
564
|
})
|
|
937
565
|
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual(['puppies'])
|
|
938
|
-
|
|
566
|
+
|
|
567
|
+
await emitLabelEvent({
|
|
568
|
+
negateLabelVals: ['puppies'],
|
|
569
|
+
createLabelVals: ['kittens'],
|
|
570
|
+
subject: {
|
|
571
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
572
|
+
did: sc.dids.bob,
|
|
573
|
+
},
|
|
574
|
+
})
|
|
939
575
|
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual(['kittens'])
|
|
940
576
|
})
|
|
941
577
|
|
|
942
578
|
it('does not allow triage moderators to label.', async () => {
|
|
943
|
-
const attemptLabel = agent.api.com.atproto.admin.
|
|
579
|
+
const attemptLabel = agent.api.com.atproto.admin.emitModerationEvent(
|
|
944
580
|
{
|
|
945
|
-
|
|
581
|
+
event: {
|
|
582
|
+
$type: 'com.atproto.admin.defs#modEventLabel',
|
|
583
|
+
negateLabelVals: ['a'],
|
|
584
|
+
createLabelVals: ['b', 'c'],
|
|
585
|
+
},
|
|
946
586
|
createdBy: 'did:example:moderator',
|
|
947
587
|
reason: 'Y',
|
|
948
588
|
subject: {
|
|
949
589
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
950
590
|
did: sc.dids.bob,
|
|
951
591
|
},
|
|
952
|
-
negateLabelVals: ['a'],
|
|
953
|
-
createLabelVals: ['b', 'c'],
|
|
954
592
|
},
|
|
955
593
|
{
|
|
956
594
|
encoding: 'application/json',
|
|
@@ -962,23 +600,30 @@ describe('moderation', () => {
|
|
|
962
600
|
)
|
|
963
601
|
})
|
|
964
602
|
|
|
603
|
+
it('does not allow take down event on takendown post or reverse takedown on available post.', async () => {
|
|
604
|
+
await performTakedown({
|
|
605
|
+
account: sc.dids.bob,
|
|
606
|
+
})
|
|
607
|
+
await expect(
|
|
608
|
+
performTakedown({
|
|
609
|
+
account: sc.dids.bob,
|
|
610
|
+
}),
|
|
611
|
+
).rejects.toThrow('Subject is already taken down')
|
|
612
|
+
|
|
613
|
+
// Cleanup
|
|
614
|
+
await performReverseTakedown({
|
|
615
|
+
account: sc.dids.bob,
|
|
616
|
+
})
|
|
617
|
+
await expect(
|
|
618
|
+
performReverseTakedown({
|
|
619
|
+
account: sc.dids.bob,
|
|
620
|
+
}),
|
|
621
|
+
).rejects.toThrow('Subject is not taken down')
|
|
622
|
+
})
|
|
965
623
|
it('fans out repo takedowns to pds', async () => {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
action: TAKEDOWN,
|
|
970
|
-
createdBy: 'did:example:moderator',
|
|
971
|
-
reason: 'Y',
|
|
972
|
-
subject: {
|
|
973
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
974
|
-
did: sc.dids.bob,
|
|
975
|
-
},
|
|
976
|
-
},
|
|
977
|
-
{
|
|
978
|
-
encoding: 'application/json',
|
|
979
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
980
|
-
},
|
|
981
|
-
)
|
|
624
|
+
await performTakedown({
|
|
625
|
+
account: sc.dids.bob,
|
|
626
|
+
})
|
|
982
627
|
|
|
983
628
|
const res1 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
984
629
|
{
|
|
@@ -989,7 +634,7 @@ describe('moderation', () => {
|
|
|
989
634
|
expect(res1.data.takedown?.applied).toBe(true)
|
|
990
635
|
|
|
991
636
|
// cleanup
|
|
992
|
-
await
|
|
637
|
+
await performReverseTakedown({ account: sc.dids.bob })
|
|
993
638
|
|
|
994
639
|
const res2 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
995
640
|
{
|
|
@@ -1004,24 +649,9 @@ describe('moderation', () => {
|
|
|
1004
649
|
const post = sc.posts[sc.dids.bob][0]
|
|
1005
650
|
const uri = post.ref.uriStr
|
|
1006
651
|
const cid = post.ref.cidStr
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
action: TAKEDOWN,
|
|
1011
|
-
createdBy: 'did:example:moderator',
|
|
1012
|
-
reason: 'Y',
|
|
1013
|
-
subject: {
|
|
1014
|
-
$type: 'com.atproto.repo.strongRef',
|
|
1015
|
-
uri,
|
|
1016
|
-
cid,
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
{
|
|
1020
|
-
encoding: 'application/json',
|
|
1021
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
1022
|
-
},
|
|
1023
|
-
)
|
|
1024
|
-
|
|
652
|
+
await performTakedown({
|
|
653
|
+
content: { uri, cid },
|
|
654
|
+
})
|
|
1025
655
|
const res1 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
1026
656
|
{ uri },
|
|
1027
657
|
{ headers: network.pds.adminAuthHeaders() },
|
|
@@ -1029,7 +659,7 @@ describe('moderation', () => {
|
|
|
1029
659
|
expect(res1.data.takedown?.applied).toBe(true)
|
|
1030
660
|
|
|
1031
661
|
// cleanup
|
|
1032
|
-
await
|
|
662
|
+
await performReverseTakedown({ content: { uri, cid } })
|
|
1033
663
|
|
|
1034
664
|
const res2 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
1035
665
|
{ uri },
|
|
@@ -1039,33 +669,39 @@ describe('moderation', () => {
|
|
|
1039
669
|
})
|
|
1040
670
|
|
|
1041
671
|
it('allows full moderators to takedown.', async () => {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
{
|
|
1045
|
-
|
|
1046
|
-
createdBy: 'did:example:moderator',
|
|
1047
|
-
reason: 'Y',
|
|
1048
|
-
subject: {
|
|
1049
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
1050
|
-
did: sc.dids.bob,
|
|
1051
|
-
},
|
|
672
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
673
|
+
{
|
|
674
|
+
event: {
|
|
675
|
+
$type: 'com.atproto.admin.defs#modEventTakedown',
|
|
1052
676
|
},
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
677
|
+
createdBy: 'did:example:moderator',
|
|
678
|
+
subject: {
|
|
679
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
680
|
+
did: sc.dids.bob,
|
|
1056
681
|
},
|
|
1057
|
-
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
encoding: 'application/json',
|
|
685
|
+
headers: network.bsky.adminAuthHeaders('moderator'),
|
|
686
|
+
},
|
|
687
|
+
)
|
|
1058
688
|
// cleanup
|
|
1059
|
-
await reverse(
|
|
689
|
+
await reverse({
|
|
690
|
+
subject: {
|
|
691
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
692
|
+
did: sc.dids.bob,
|
|
693
|
+
},
|
|
694
|
+
})
|
|
1060
695
|
})
|
|
1061
696
|
|
|
1062
697
|
it('does not allow non-full moderators to takedown.', async () => {
|
|
1063
698
|
const attemptTakedownTriage =
|
|
1064
|
-
agent.api.com.atproto.admin.
|
|
699
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
1065
700
|
{
|
|
1066
|
-
|
|
701
|
+
event: {
|
|
702
|
+
$type: 'com.atproto.admin.defs#modEventTakedown',
|
|
703
|
+
},
|
|
1067
704
|
createdBy: 'did:example:moderator',
|
|
1068
|
-
reason: 'Y',
|
|
1069
705
|
subject: {
|
|
1070
706
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
1071
707
|
did: sc.dids.bob,
|
|
@@ -1081,61 +717,76 @@ describe('moderation', () => {
|
|
|
1081
717
|
)
|
|
1082
718
|
})
|
|
1083
719
|
it('automatically reverses actions marked with duration', async () => {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
},
|
|
1099
|
-
{
|
|
1100
|
-
encoding: 'application/json',
|
|
1101
|
-
headers: network.bsky.adminAuthHeaders('moderator'),
|
|
1102
|
-
},
|
|
720
|
+
await createReport({
|
|
721
|
+
reasonType: REASONSPAM,
|
|
722
|
+
account: sc.dids.bob,
|
|
723
|
+
author: sc.dids.alice,
|
|
724
|
+
})
|
|
725
|
+
const { data: action } = await performTakedown({
|
|
726
|
+
account: sc.dids.bob,
|
|
727
|
+
// Use negative value to set the expiry time in the past so that the action is automatically reversed
|
|
728
|
+
// right away without having to wait n number of hours for a successful assertion
|
|
729
|
+
durationInHours: -1,
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
const { data: statusesAfterTakedown } =
|
|
733
|
+
await agent.api.com.atproto.admin.queryModerationStatuses(
|
|
734
|
+
{ subject: sc.dids.bob },
|
|
735
|
+
{ headers: network.bsky.adminAuthHeaders('moderator') },
|
|
1103
736
|
)
|
|
1104
737
|
|
|
1105
|
-
|
|
1106
|
-
|
|
738
|
+
expect(statusesAfterTakedown.subjectStatuses[0]).toMatchObject({
|
|
739
|
+
takendown: true,
|
|
740
|
+
})
|
|
741
|
+
|
|
1107
742
|
// In the actual app, this will be instantiated and run on server startup
|
|
1108
|
-
const periodicReversal = new
|
|
743
|
+
const periodicReversal = new PeriodicModerationEventReversal(
|
|
1109
744
|
network.bsky.ctx,
|
|
1110
745
|
)
|
|
1111
746
|
await periodicReversal.findAndRevertDueActions()
|
|
1112
747
|
|
|
1113
|
-
const { data:
|
|
1114
|
-
|
|
1115
|
-
{
|
|
748
|
+
const [{ data: eventList }, { data: statuses }] = await Promise.all([
|
|
749
|
+
agent.api.com.atproto.admin.queryModerationEvents(
|
|
750
|
+
{ subject: sc.dids.bob },
|
|
1116
751
|
{ headers: network.bsky.adminAuthHeaders('moderator') },
|
|
1117
|
-
)
|
|
752
|
+
),
|
|
753
|
+
agent.api.com.atproto.admin.queryModerationStatuses(
|
|
754
|
+
{ subject: sc.dids.bob },
|
|
755
|
+
{ headers: network.bsky.adminAuthHeaders('moderator') },
|
|
756
|
+
),
|
|
757
|
+
])
|
|
1118
758
|
|
|
759
|
+
expect(statuses.subjectStatuses[0]).toMatchObject({
|
|
760
|
+
takendown: false,
|
|
761
|
+
reviewState: REVIEWCLOSED,
|
|
762
|
+
})
|
|
1119
763
|
// Verify that the automatic reversal is attributed to the original moderator of the temporary action
|
|
1120
764
|
// and that the reason is set to indicate that the action was automatically reversed.
|
|
1121
|
-
expect(
|
|
765
|
+
expect(eventList.events[0]).toMatchObject({
|
|
1122
766
|
createdBy: action.createdBy,
|
|
1123
|
-
|
|
767
|
+
event: {
|
|
768
|
+
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
|
|
769
|
+
comment:
|
|
770
|
+
'[SCHEDULED_REVERSAL] Reverting action as originally scheduled',
|
|
771
|
+
},
|
|
1124
772
|
})
|
|
1125
|
-
|
|
1126
|
-
// Verify that labels are also reversed when takedown action is reversed
|
|
1127
|
-
const labelsAfterReversal = await getRepoLabels(sc.dids.bob)
|
|
1128
|
-
expect(labelsAfterReversal).not.toContain('takendown')
|
|
1129
773
|
})
|
|
1130
774
|
|
|
1131
|
-
async function
|
|
1132
|
-
opts: Partial<
|
|
1133
|
-
subject:
|
|
775
|
+
async function emitLabelEvent(
|
|
776
|
+
opts: Partial<ComAtprotoAdminEmitModerationEvent.InputSchema> & {
|
|
777
|
+
subject: ComAtprotoAdminEmitModerationEvent.InputSchema['subject']
|
|
778
|
+
createLabelVals: ModEventLabel['createLabelVals']
|
|
779
|
+
negateLabelVals: ModEventLabel['negateLabelVals']
|
|
1134
780
|
},
|
|
1135
781
|
) {
|
|
1136
|
-
const
|
|
782
|
+
const { createLabelVals, negateLabelVals, ...rest } = opts
|
|
783
|
+
const result = await agent.api.com.atproto.admin.emitModerationEvent(
|
|
1137
784
|
{
|
|
1138
|
-
|
|
785
|
+
event: {
|
|
786
|
+
$type: 'com.atproto.admin.defs#modEventLabel',
|
|
787
|
+
createLabelVals,
|
|
788
|
+
negateLabelVals,
|
|
789
|
+
},
|
|
1139
790
|
createdBy: 'did:example:admin',
|
|
1140
791
|
reason: 'Y',
|
|
1141
792
|
...opts,
|
|
@@ -1148,12 +799,19 @@ describe('moderation', () => {
|
|
|
1148
799
|
return result.data
|
|
1149
800
|
}
|
|
1150
801
|
|
|
1151
|
-
async function reverse(
|
|
1152
|
-
|
|
802
|
+
async function reverse(
|
|
803
|
+
opts: Partial<ComAtprotoAdminEmitModerationEvent.InputSchema> & {
|
|
804
|
+
subject: ComAtprotoAdminEmitModerationEvent.InputSchema['subject']
|
|
805
|
+
},
|
|
806
|
+
) {
|
|
807
|
+
await agent.api.com.atproto.admin.emitModerationEvent(
|
|
1153
808
|
{
|
|
1154
|
-
|
|
809
|
+
event: {
|
|
810
|
+
$type: 'com.atproto.admin.defs#modEventReverseTakedown',
|
|
811
|
+
},
|
|
1155
812
|
createdBy: 'did:example:admin',
|
|
1156
813
|
reason: 'Y',
|
|
814
|
+
...opts,
|
|
1157
815
|
},
|
|
1158
816
|
{
|
|
1159
817
|
encoding: 'application/json',
|
|
@@ -1185,7 +843,6 @@ describe('moderation', () => {
|
|
|
1185
843
|
let post: { ref: RecordRef; images: ImageRef[] }
|
|
1186
844
|
let blob: ImageRef
|
|
1187
845
|
let imageUri: string
|
|
1188
|
-
let actionId: number
|
|
1189
846
|
beforeAll(async () => {
|
|
1190
847
|
const { ctx } = network.bsky
|
|
1191
848
|
post = sc.posts[sc.dids.carol][0]
|
|
@@ -1201,24 +858,23 @@ describe('moderation', () => {
|
|
|
1201
858
|
await fetch(imageUri)
|
|
1202
859
|
const cached = await fetch(imageUri)
|
|
1203
860
|
expect(cached.headers.get('x-cache')).toEqual('hit')
|
|
1204
|
-
|
|
1205
|
-
{
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
$type: 'com.atproto.repo.strongRef',
|
|
1209
|
-
uri: post.ref.uriStr,
|
|
1210
|
-
cid: post.ref.cidStr,
|
|
1211
|
-
},
|
|
1212
|
-
subjectBlobCids: [blob.image.ref.toString()],
|
|
1213
|
-
createdBy: 'did:example:admin',
|
|
1214
|
-
reason: 'Y',
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
encoding: 'application/json',
|
|
1218
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
861
|
+
await performTakedown({
|
|
862
|
+
content: {
|
|
863
|
+
uri: post.ref.uriStr,
|
|
864
|
+
cid: post.ref.cidStr,
|
|
1219
865
|
},
|
|
1220
|
-
|
|
1221
|
-
|
|
866
|
+
subjectBlobCids: [blob.image.ref.toString()],
|
|
867
|
+
})
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
it('sets blobCids in moderation status', async () => {
|
|
871
|
+
const { subjectStatuses } = await getStatuses({
|
|
872
|
+
subject: post.ref.uriStr,
|
|
873
|
+
})
|
|
874
|
+
|
|
875
|
+
expect(subjectStatuses[0].subjectBlobCids).toEqual([
|
|
876
|
+
blob.image.ref.toString(),
|
|
877
|
+
])
|
|
1222
878
|
})
|
|
1223
879
|
|
|
1224
880
|
it('prevents resolution of blob', async () => {
|
|
@@ -1249,17 +905,13 @@ describe('moderation', () => {
|
|
|
1249
905
|
})
|
|
1250
906
|
|
|
1251
907
|
it('restores blob when action is reversed.', async () => {
|
|
1252
|
-
await
|
|
1253
|
-
{
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
reason: 'Y',
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
encoding: 'application/json',
|
|
1260
|
-
headers: network.bsky.adminAuthHeaders(),
|
|
908
|
+
await performReverseTakedown({
|
|
909
|
+
content: {
|
|
910
|
+
uri: post.ref.uriStr,
|
|
911
|
+
cid: post.ref.cidStr,
|
|
1261
912
|
},
|
|
1262
|
-
|
|
913
|
+
subjectBlobCids: [blob.image.ref.toString()],
|
|
914
|
+
})
|
|
1263
915
|
|
|
1264
916
|
// Can resolve blob
|
|
1265
917
|
const blobPath = `/blob/${sc.dids.carol}/${blob.image.ref.toString()}`
|