@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
|
@@ -29,6 +29,19 @@ describe('views with thread gating', () => {
|
|
|
29
29
|
await network.close()
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
+
// check that replyDisabled state is applied correctly in a simple method like getPosts
|
|
33
|
+
const checkReplyDisabled = async (
|
|
34
|
+
uri: string,
|
|
35
|
+
user: string,
|
|
36
|
+
blocked: boolean | undefined,
|
|
37
|
+
) => {
|
|
38
|
+
const res = await agent.api.app.bsky.feed.getPosts(
|
|
39
|
+
{ uris: [uri] },
|
|
40
|
+
{ headers: await network.serviceHeaders(user) },
|
|
41
|
+
)
|
|
42
|
+
expect(res.data.posts[0].viewer?.replyDisabled).toBe(blocked)
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
it('applies gate for empty rules.', async () => {
|
|
33
46
|
const post = await sc.post(sc.dids.carol, 'empty rules')
|
|
34
47
|
await pdsAgent.api.app.bsky.feed.threadgate.create(
|
|
@@ -46,8 +59,9 @@ describe('views with thread gating', () => {
|
|
|
46
59
|
)
|
|
47
60
|
assert(isThreadViewPost(thread))
|
|
48
61
|
expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
|
|
49
|
-
expect(thread.viewer).toEqual({
|
|
62
|
+
expect(thread.post.viewer).toEqual({ replyDisabled: true })
|
|
50
63
|
expect(thread.replies?.length).toEqual(0)
|
|
64
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
|
|
51
65
|
})
|
|
52
66
|
|
|
53
67
|
it('applies gate for mention rule.', async () => {
|
|
@@ -98,7 +112,8 @@ describe('views with thread gating', () => {
|
|
|
98
112
|
{ headers: await network.serviceHeaders(sc.dids.alice) },
|
|
99
113
|
)
|
|
100
114
|
assert(isThreadViewPost(aliceThread))
|
|
101
|
-
expect(aliceThread.viewer).toEqual({
|
|
115
|
+
expect(aliceThread.post.viewer).toEqual({ replyDisabled: true })
|
|
116
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
|
|
102
117
|
const {
|
|
103
118
|
data: { thread: danThread },
|
|
104
119
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -107,7 +122,8 @@ describe('views with thread gating', () => {
|
|
|
107
122
|
)
|
|
108
123
|
assert(isThreadViewPost(danThread))
|
|
109
124
|
expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
|
|
110
|
-
expect(danThread.viewer).toEqual({
|
|
125
|
+
expect(danThread.post.viewer).toEqual({ replyDisabled: false })
|
|
126
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
|
|
111
127
|
const [reply, ...otherReplies] = danThread.replies ?? []
|
|
112
128
|
assert(isThreadViewPost(reply))
|
|
113
129
|
expect(otherReplies.length).toEqual(0)
|
|
@@ -146,7 +162,8 @@ describe('views with thread gating', () => {
|
|
|
146
162
|
{ headers: await network.serviceHeaders(sc.dids.dan) },
|
|
147
163
|
)
|
|
148
164
|
assert(isThreadViewPost(danThread))
|
|
149
|
-
expect(danThread.viewer).toEqual({
|
|
165
|
+
expect(danThread.post.viewer).toEqual({ replyDisabled: true })
|
|
166
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, true)
|
|
150
167
|
const {
|
|
151
168
|
data: { thread: aliceThread },
|
|
152
169
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -155,7 +172,8 @@ describe('views with thread gating', () => {
|
|
|
155
172
|
)
|
|
156
173
|
assert(isThreadViewPost(aliceThread))
|
|
157
174
|
expect(forSnapshot(aliceThread.post.threadgate)).toMatchSnapshot()
|
|
158
|
-
expect(aliceThread.viewer).toEqual({
|
|
175
|
+
expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
|
|
176
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
|
|
159
177
|
const [reply, ...otherReplies] = aliceThread.replies ?? []
|
|
160
178
|
assert(isThreadViewPost(reply))
|
|
161
179
|
expect(otherReplies.length).toEqual(0)
|
|
@@ -235,7 +253,8 @@ describe('views with thread gating', () => {
|
|
|
235
253
|
{ headers: await network.serviceHeaders(sc.dids.bob) },
|
|
236
254
|
)
|
|
237
255
|
assert(isThreadViewPost(bobThread))
|
|
238
|
-
expect(bobThread.viewer).toEqual({
|
|
256
|
+
expect(bobThread.post.viewer).toEqual({ replyDisabled: true })
|
|
257
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.bob, true)
|
|
239
258
|
const {
|
|
240
259
|
data: { thread: aliceThread },
|
|
241
260
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -243,7 +262,8 @@ describe('views with thread gating', () => {
|
|
|
243
262
|
{ headers: await network.serviceHeaders(sc.dids.alice) },
|
|
244
263
|
)
|
|
245
264
|
assert(isThreadViewPost(aliceThread))
|
|
246
|
-
expect(aliceThread.viewer).toEqual({
|
|
265
|
+
expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
|
|
266
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
|
|
247
267
|
const {
|
|
248
268
|
data: { thread: danThread },
|
|
249
269
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -252,7 +272,8 @@ describe('views with thread gating', () => {
|
|
|
252
272
|
)
|
|
253
273
|
assert(isThreadViewPost(danThread))
|
|
254
274
|
expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
|
|
255
|
-
expect(danThread.viewer).toEqual({
|
|
275
|
+
expect(danThread.post.viewer).toEqual({ replyDisabled: false })
|
|
276
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
|
|
256
277
|
const [reply1, reply2, ...otherReplies] = aliceThread.replies ?? []
|
|
257
278
|
assert(isThreadViewPost(reply1))
|
|
258
279
|
assert(isThreadViewPost(reply2))
|
|
@@ -292,8 +313,9 @@ describe('views with thread gating', () => {
|
|
|
292
313
|
)
|
|
293
314
|
assert(isThreadViewPost(thread))
|
|
294
315
|
expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
|
|
295
|
-
expect(thread.viewer).toEqual({
|
|
316
|
+
expect(thread.post.viewer).toEqual({ replyDisabled: true })
|
|
296
317
|
expect(thread.replies?.length).toEqual(0)
|
|
318
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
|
|
297
319
|
})
|
|
298
320
|
|
|
299
321
|
it('applies gate for multiple rules.', async () => {
|
|
@@ -339,7 +361,8 @@ describe('views with thread gating', () => {
|
|
|
339
361
|
{ headers: await network.serviceHeaders(sc.dids.bob) },
|
|
340
362
|
)
|
|
341
363
|
assert(isThreadViewPost(bobThread))
|
|
342
|
-
expect(bobThread.viewer).toEqual({
|
|
364
|
+
expect(bobThread.post.viewer).toEqual({ replyDisabled: true })
|
|
365
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.bob, true)
|
|
343
366
|
const {
|
|
344
367
|
data: { thread: aliceThread },
|
|
345
368
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -347,7 +370,8 @@ describe('views with thread gating', () => {
|
|
|
347
370
|
{ headers: await network.serviceHeaders(sc.dids.alice) },
|
|
348
371
|
)
|
|
349
372
|
assert(isThreadViewPost(aliceThread))
|
|
350
|
-
expect(aliceThread.viewer).toEqual({
|
|
373
|
+
expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
|
|
374
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
|
|
351
375
|
const {
|
|
352
376
|
data: { thread: danThread },
|
|
353
377
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -356,7 +380,8 @@ describe('views with thread gating', () => {
|
|
|
356
380
|
)
|
|
357
381
|
assert(isThreadViewPost(danThread))
|
|
358
382
|
expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
|
|
359
|
-
expect(danThread.viewer).toEqual({
|
|
383
|
+
expect(danThread.post.viewer).toEqual({ replyDisabled: false })
|
|
384
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
|
|
360
385
|
const [reply1, reply2, ...otherReplies] = aliceThread.replies ?? []
|
|
361
386
|
assert(isThreadViewPost(reply1))
|
|
362
387
|
assert(isThreadViewPost(reply2))
|
|
@@ -387,7 +412,8 @@ describe('views with thread gating', () => {
|
|
|
387
412
|
)
|
|
388
413
|
assert(isThreadViewPost(thread))
|
|
389
414
|
expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
|
|
390
|
-
expect(thread.viewer).toEqual({
|
|
415
|
+
expect(thread.post.viewer).toEqual({ replyDisabled: false })
|
|
416
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
|
|
391
417
|
const [reply, ...otherReplies] = thread.replies ?? []
|
|
392
418
|
assert(isThreadViewPost(reply))
|
|
393
419
|
expect(otherReplies.length).toEqual(0)
|
|
@@ -438,7 +464,8 @@ describe('views with thread gating', () => {
|
|
|
438
464
|
{ headers: await network.serviceHeaders(sc.dids.dan) },
|
|
439
465
|
)
|
|
440
466
|
assert(isThreadViewPost(danThread))
|
|
441
|
-
expect(danThread.viewer).toEqual({
|
|
467
|
+
expect(danThread.post.viewer).toEqual({ replyDisabled: true })
|
|
468
|
+
await checkReplyDisabled(orphanedReply.ref.uriStr, sc.dids.dan, true)
|
|
442
469
|
const {
|
|
443
470
|
data: { thread: aliceThread },
|
|
444
471
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -451,7 +478,8 @@ describe('views with thread gating', () => {
|
|
|
451
478
|
aliceThread.parent.uri === post.ref.uriStr,
|
|
452
479
|
)
|
|
453
480
|
expect(aliceThread.post.threadgate).toMatchSnapshot()
|
|
454
|
-
expect(aliceThread.viewer).toEqual({
|
|
481
|
+
expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
|
|
482
|
+
await checkReplyDisabled(orphanedReply.ref.uriStr, sc.dids.alice, false)
|
|
455
483
|
const [reply, ...otherReplies] = aliceThread.replies ?? []
|
|
456
484
|
assert(isThreadViewPost(reply))
|
|
457
485
|
expect(otherReplies.length).toEqual(0)
|
|
@@ -480,7 +508,8 @@ describe('views with thread gating', () => {
|
|
|
480
508
|
)
|
|
481
509
|
assert(isThreadViewPost(thread))
|
|
482
510
|
expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
|
|
483
|
-
expect(thread.viewer).toEqual({
|
|
511
|
+
expect(thread.post.viewer).toEqual({ replyDisabled: false })
|
|
512
|
+
await checkReplyDisabled(post.ref.uriStr, sc.dids.carol, false)
|
|
484
513
|
const [reply, ...otherReplies] = thread.replies ?? []
|
|
485
514
|
assert(isThreadViewPost(reply))
|
|
486
515
|
expect(otherReplies.length).toEqual(0)
|
|
@@ -516,10 +545,11 @@ describe('views with thread gating', () => {
|
|
|
516
545
|
{ headers: await network.serviceHeaders(sc.dids.alice) },
|
|
517
546
|
)
|
|
518
547
|
assert(isThreadViewPost(thread))
|
|
519
|
-
expect(thread.viewer).toEqual({
|
|
548
|
+
expect(thread.post.viewer).toEqual({ replyDisabled: true }) // nobody can reply to this, not even alice.
|
|
520
549
|
expect(thread.replies).toBeUndefined()
|
|
521
550
|
expect(thread.parent).toBeUndefined()
|
|
522
551
|
expect(thread.post.threadgate).toBeUndefined()
|
|
552
|
+
await checkReplyDisabled(badReply.ref.uriStr, sc.dids.alice, true)
|
|
523
553
|
// check feed view
|
|
524
554
|
const {
|
|
525
555
|
data: { feed },
|
|
@@ -552,8 +582,9 @@ describe('views with thread gating', () => {
|
|
|
552
582
|
)
|
|
553
583
|
assert(isThreadViewPost(threadA))
|
|
554
584
|
expect(threadA.post.threadgate).toBeUndefined()
|
|
555
|
-
expect(threadA.viewer).toEqual({
|
|
585
|
+
expect(threadA.post.viewer).toEqual({})
|
|
556
586
|
expect(threadA.replies?.length).toEqual(1)
|
|
587
|
+
await checkReplyDisabled(postA.ref.uriStr, sc.dids.alice, undefined)
|
|
557
588
|
const {
|
|
558
589
|
data: { thread: threadB },
|
|
559
590
|
} = await agent.api.app.bsky.feed.getPostThread(
|
|
@@ -562,7 +593,8 @@ describe('views with thread gating', () => {
|
|
|
562
593
|
)
|
|
563
594
|
assert(isThreadViewPost(threadB))
|
|
564
595
|
expect(threadB.post.threadgate).toBeUndefined()
|
|
565
|
-
expect(threadB.viewer).toEqual({
|
|
596
|
+
expect(threadB.post.viewer).toEqual({})
|
|
597
|
+
await checkReplyDisabled(postB.ref.uriStr, sc.dids.alice, undefined)
|
|
566
598
|
expect(threadB.replies?.length).toEqual(1)
|
|
567
599
|
})
|
|
568
600
|
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
2
|
import AtpAgent from '@atproto/api'
|
|
3
3
|
import { TestNetwork, SeedClient } from '@atproto/dev-env'
|
|
4
|
-
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
|
|
5
4
|
import { forSnapshot, getOriginator, paginateAll } from '../_util'
|
|
6
5
|
import basicSeed from '../seeds/basic'
|
|
7
6
|
import { FeedAlgorithm } from '../../src/api/app/bsky/util/feed'
|
|
@@ -182,11 +181,11 @@ describe('timeline views', () => {
|
|
|
182
181
|
})
|
|
183
182
|
|
|
184
183
|
it('blocks posts, reposts, replies by actor takedown', async () => {
|
|
185
|
-
|
|
184
|
+
await Promise.all(
|
|
186
185
|
[bob, carol].map((did) =>
|
|
187
|
-
agent.api.com.atproto.admin.
|
|
186
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
188
187
|
{
|
|
189
|
-
|
|
188
|
+
event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
|
|
190
189
|
subject: {
|
|
191
190
|
$type: 'com.atproto.admin.defs#repoRef',
|
|
192
191
|
did,
|
|
@@ -211,10 +210,14 @@ describe('timeline views', () => {
|
|
|
211
210
|
|
|
212
211
|
// Cleanup
|
|
213
212
|
await Promise.all(
|
|
214
|
-
|
|
215
|
-
agent.api.com.atproto.admin.
|
|
213
|
+
[bob, carol].map((did) =>
|
|
214
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
216
215
|
{
|
|
217
|
-
|
|
216
|
+
event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
|
|
217
|
+
subject: {
|
|
218
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
219
|
+
did,
|
|
220
|
+
},
|
|
218
221
|
createdBy: 'did:example:admin',
|
|
219
222
|
reason: 'Y',
|
|
220
223
|
},
|
|
@@ -230,11 +233,11 @@ describe('timeline views', () => {
|
|
|
230
233
|
it('blocks posts, reposts, replies by record takedown.', async () => {
|
|
231
234
|
const postRef1 = sc.posts[dan][1].ref // Repost
|
|
232
235
|
const postRef2 = sc.replies[bob][0].ref // Post and reply parent
|
|
233
|
-
|
|
236
|
+
await Promise.all(
|
|
234
237
|
[postRef1, postRef2].map((postRef) =>
|
|
235
|
-
agent.api.com.atproto.admin.
|
|
238
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
236
239
|
{
|
|
237
|
-
|
|
240
|
+
event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
|
|
238
241
|
subject: {
|
|
239
242
|
$type: 'com.atproto.repo.strongRef',
|
|
240
243
|
uri: postRef.uriStr,
|
|
@@ -260,10 +263,15 @@ describe('timeline views', () => {
|
|
|
260
263
|
|
|
261
264
|
// Cleanup
|
|
262
265
|
await Promise.all(
|
|
263
|
-
|
|
264
|
-
agent.api.com.atproto.admin.
|
|
266
|
+
[postRef1, postRef2].map((postRef) =>
|
|
267
|
+
agent.api.com.atproto.admin.emitModerationEvent(
|
|
265
268
|
{
|
|
266
|
-
|
|
269
|
+
event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
|
|
270
|
+
subject: {
|
|
271
|
+
$type: 'com.atproto.repo.strongRef',
|
|
272
|
+
uri: postRef.uriStr,
|
|
273
|
+
cid: postRef.cidStr,
|
|
274
|
+
},
|
|
267
275
|
createdBy: 'did:example:admin',
|
|
268
276
|
reason: 'Y',
|
|
269
277
|
},
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { HandlerAuth } from '@atproto/xrpc-server';
|
|
3
|
-
import * as ComAtprotoAdminDefs from './defs';
|
|
4
|
-
export interface QueryParams {
|
|
5
|
-
id: number;
|
|
6
|
-
}
|
|
7
|
-
export declare type InputSchema = undefined;
|
|
8
|
-
export declare type OutputSchema = ComAtprotoAdminDefs.ReportViewDetail;
|
|
9
|
-
export declare type HandlerInput = undefined;
|
|
10
|
-
export interface HandlerSuccess {
|
|
11
|
-
encoding: 'application/json';
|
|
12
|
-
body: OutputSchema;
|
|
13
|
-
headers?: {
|
|
14
|
-
[key: string]: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export interface HandlerError {
|
|
18
|
-
status: number;
|
|
19
|
-
message?: string;
|
|
20
|
-
}
|
|
21
|
-
export declare type HandlerOutput = HandlerError | HandlerSuccess;
|
|
22
|
-
export declare type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
23
|
-
auth: HA;
|
|
24
|
-
params: QueryParams;
|
|
25
|
-
input: HandlerInput;
|
|
26
|
-
req: express.Request;
|
|
27
|
-
res: express.Response;
|
|
28
|
-
};
|
|
29
|
-
export declare type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { HandlerAuth } from '@atproto/xrpc-server';
|
|
3
|
-
import * as ComAtprotoAdminDefs from './defs';
|
|
4
|
-
export interface QueryParams {
|
|
5
|
-
}
|
|
6
|
-
export interface InputSchema {
|
|
7
|
-
actionId: number;
|
|
8
|
-
reportIds: number[];
|
|
9
|
-
createdBy: string;
|
|
10
|
-
[k: string]: unknown;
|
|
11
|
-
}
|
|
12
|
-
export declare type OutputSchema = ComAtprotoAdminDefs.ActionView;
|
|
13
|
-
export interface HandlerInput {
|
|
14
|
-
encoding: 'application/json';
|
|
15
|
-
body: InputSchema;
|
|
16
|
-
}
|
|
17
|
-
export interface HandlerSuccess {
|
|
18
|
-
encoding: 'application/json';
|
|
19
|
-
body: OutputSchema;
|
|
20
|
-
headers?: {
|
|
21
|
-
[key: string]: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export interface HandlerError {
|
|
25
|
-
status: number;
|
|
26
|
-
message?: string;
|
|
27
|
-
}
|
|
28
|
-
export declare type HandlerOutput = HandlerError | HandlerSuccess;
|
|
29
|
-
export declare type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
30
|
-
auth: HA;
|
|
31
|
-
params: QueryParams;
|
|
32
|
-
input: HandlerInput;
|
|
33
|
-
req: express.Request;
|
|
34
|
-
res: express.Response;
|
|
35
|
-
};
|
|
36
|
-
export declare type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { HandlerAuth } from '@atproto/xrpc-server';
|
|
3
|
-
import * as ComAtprotoAdminDefs from './defs';
|
|
4
|
-
export interface QueryParams {
|
|
5
|
-
}
|
|
6
|
-
export interface InputSchema {
|
|
7
|
-
id: number;
|
|
8
|
-
reason: string;
|
|
9
|
-
createdBy: string;
|
|
10
|
-
[k: string]: unknown;
|
|
11
|
-
}
|
|
12
|
-
export declare type OutputSchema = ComAtprotoAdminDefs.ActionView;
|
|
13
|
-
export interface HandlerInput {
|
|
14
|
-
encoding: 'application/json';
|
|
15
|
-
body: InputSchema;
|
|
16
|
-
}
|
|
17
|
-
export interface HandlerSuccess {
|
|
18
|
-
encoding: 'application/json';
|
|
19
|
-
body: OutputSchema;
|
|
20
|
-
headers?: {
|
|
21
|
-
[key: string]: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export interface HandlerError {
|
|
25
|
-
status: number;
|
|
26
|
-
message?: string;
|
|
27
|
-
}
|
|
28
|
-
export declare type HandlerOutput = HandlerError | HandlerSuccess;
|
|
29
|
-
export declare type HandlerReqCtx<HA extends HandlerAuth = never> = {
|
|
30
|
-
auth: HA;
|
|
31
|
-
params: QueryParams;
|
|
32
|
-
input: HandlerInput;
|
|
33
|
-
req: express.Request;
|
|
34
|
-
res: express.Response;
|
|
35
|
-
};
|
|
36
|
-
export declare type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Server } from '../../../../lexicon'
|
|
2
|
-
import AppContext from '../../../../context'
|
|
3
|
-
import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
|
|
4
|
-
import {
|
|
5
|
-
isRecordView,
|
|
6
|
-
isRepoView,
|
|
7
|
-
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
8
|
-
|
|
9
|
-
export default function (server: Server, ctx: AppContext) {
|
|
10
|
-
server.com.atproto.admin.getModerationAction({
|
|
11
|
-
auth: ctx.roleVerifier,
|
|
12
|
-
handler: async ({ params, auth }) => {
|
|
13
|
-
const { id } = params
|
|
14
|
-
const db = ctx.db.getPrimary()
|
|
15
|
-
const moderationService = ctx.services.moderation(db)
|
|
16
|
-
const result = await moderationService.getActionOrThrow(id)
|
|
17
|
-
|
|
18
|
-
const [action, accountInfo] = await Promise.all([
|
|
19
|
-
moderationService.views.actionDetail(result),
|
|
20
|
-
getPdsAccountInfo(ctx, result.subjectDid),
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
// add in pds account info if available
|
|
24
|
-
if (isRepoView(action.subject)) {
|
|
25
|
-
action.subject = addAccountInfoToRepoView(
|
|
26
|
-
action.subject,
|
|
27
|
-
accountInfo,
|
|
28
|
-
auth.credentials.moderator,
|
|
29
|
-
)
|
|
30
|
-
} else if (isRecordView(action.subject)) {
|
|
31
|
-
action.subject.repo = addAccountInfoToRepoView(
|
|
32
|
-
action.subject.repo,
|
|
33
|
-
accountInfo,
|
|
34
|
-
auth.credentials.moderator,
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
encoding: 'application/json',
|
|
40
|
-
body: action,
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Server } from '../../../../lexicon'
|
|
2
|
-
import AppContext from '../../../../context'
|
|
3
|
-
import {
|
|
4
|
-
isRecordView,
|
|
5
|
-
isRepoView,
|
|
6
|
-
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
7
|
-
import { addAccountInfoToRepoView, getPdsAccountInfo } from './util'
|
|
8
|
-
|
|
9
|
-
export default function (server: Server, ctx: AppContext) {
|
|
10
|
-
server.com.atproto.admin.getModerationReport({
|
|
11
|
-
auth: ctx.roleVerifier,
|
|
12
|
-
handler: async ({ params, auth }) => {
|
|
13
|
-
const { id } = params
|
|
14
|
-
const db = ctx.db.getPrimary()
|
|
15
|
-
const moderationService = ctx.services.moderation(db)
|
|
16
|
-
const result = await moderationService.getReportOrThrow(id)
|
|
17
|
-
const [report, accountInfo] = await Promise.all([
|
|
18
|
-
moderationService.views.reportDetail(result),
|
|
19
|
-
getPdsAccountInfo(ctx, result.subjectDid),
|
|
20
|
-
])
|
|
21
|
-
|
|
22
|
-
// add in pds account info if available
|
|
23
|
-
if (isRepoView(report.subject)) {
|
|
24
|
-
report.subject = addAccountInfoToRepoView(
|
|
25
|
-
report.subject,
|
|
26
|
-
accountInfo,
|
|
27
|
-
auth.credentials.moderator,
|
|
28
|
-
)
|
|
29
|
-
} else if (isRecordView(report.subject)) {
|
|
30
|
-
report.subject.repo = addAccountInfoToRepoView(
|
|
31
|
-
report.subject.repo,
|
|
32
|
-
accountInfo,
|
|
33
|
-
auth.credentials.moderator,
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
encoding: 'application/json',
|
|
39
|
-
body: report,
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
})
|
|
43
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Server } from '../../../../lexicon'
|
|
2
|
-
import AppContext from '../../../../context'
|
|
3
|
-
|
|
4
|
-
export default function (server: Server, ctx: AppContext) {
|
|
5
|
-
server.com.atproto.admin.resolveModerationReports({
|
|
6
|
-
auth: ctx.roleVerifier,
|
|
7
|
-
handler: async ({ input }) => {
|
|
8
|
-
const db = ctx.db.getPrimary()
|
|
9
|
-
const moderationService = ctx.services.moderation(db)
|
|
10
|
-
const { actionId, reportIds, createdBy } = input.body
|
|
11
|
-
|
|
12
|
-
const moderationAction = await db.transaction(async (dbTxn) => {
|
|
13
|
-
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
14
|
-
await moderationTxn.resolveReports({ reportIds, actionId, createdBy })
|
|
15
|
-
return await moderationTxn.getActionOrThrow(actionId)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
encoding: 'application/json',
|
|
20
|
-
body: await moderationService.views.action(moderationAction),
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AuthRequiredError,
|
|
3
|
-
InvalidRequestError,
|
|
4
|
-
UpstreamFailureError,
|
|
5
|
-
} from '@atproto/xrpc-server'
|
|
6
|
-
import {
|
|
7
|
-
ACKNOWLEDGE,
|
|
8
|
-
ESCALATE,
|
|
9
|
-
TAKEDOWN,
|
|
10
|
-
} from '../../../../lexicon/types/com/atproto/admin/defs'
|
|
11
|
-
import { Server } from '../../../../lexicon'
|
|
12
|
-
import AppContext from '../../../../context'
|
|
13
|
-
import { retryHttp } from '../../../../util/retry'
|
|
14
|
-
|
|
15
|
-
export default function (server: Server, ctx: AppContext) {
|
|
16
|
-
server.com.atproto.admin.reverseModerationAction({
|
|
17
|
-
auth: ctx.roleVerifier,
|
|
18
|
-
handler: async ({ input, auth }) => {
|
|
19
|
-
const access = auth.credentials
|
|
20
|
-
const db = ctx.db.getPrimary()
|
|
21
|
-
const moderationService = ctx.services.moderation(db)
|
|
22
|
-
const { id, createdBy, reason } = input.body
|
|
23
|
-
|
|
24
|
-
const { result, restored } = await db.transaction(async (dbTxn) => {
|
|
25
|
-
const moderationTxn = ctx.services.moderation(dbTxn)
|
|
26
|
-
const labelTxn = ctx.services.label(dbTxn)
|
|
27
|
-
const now = new Date()
|
|
28
|
-
|
|
29
|
-
const existing = await moderationTxn.getAction(id)
|
|
30
|
-
if (!existing) {
|
|
31
|
-
throw new InvalidRequestError('Moderation action does not exist')
|
|
32
|
-
}
|
|
33
|
-
if (existing.reversedAt !== null) {
|
|
34
|
-
throw new InvalidRequestError(
|
|
35
|
-
'Moderation action has already been reversed',
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// apply access rules
|
|
40
|
-
|
|
41
|
-
// if less than moderator access then can only reverse ack and escalation actions
|
|
42
|
-
if (
|
|
43
|
-
!access.moderator &&
|
|
44
|
-
![ACKNOWLEDGE, ESCALATE].includes(existing.action)
|
|
45
|
-
) {
|
|
46
|
-
throw new AuthRequiredError(
|
|
47
|
-
'Must be a full moderator to reverse this type of action',
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
// if less than moderator access then cannot reverse takedown on an account
|
|
51
|
-
if (
|
|
52
|
-
!access.moderator &&
|
|
53
|
-
existing.action === TAKEDOWN &&
|
|
54
|
-
existing.subjectType === 'com.atproto.admin.defs#repoRef'
|
|
55
|
-
) {
|
|
56
|
-
throw new AuthRequiredError(
|
|
57
|
-
'Must be a full moderator to reverse an account takedown',
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const { result, restored } = await moderationTxn.revertAction({
|
|
62
|
-
id,
|
|
63
|
-
createdAt: now,
|
|
64
|
-
createdBy,
|
|
65
|
-
reason,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// invert creates & negates
|
|
69
|
-
const { createLabelVals, negateLabelVals } = result
|
|
70
|
-
const negate =
|
|
71
|
-
createLabelVals && createLabelVals.length > 0
|
|
72
|
-
? createLabelVals.split(' ')
|
|
73
|
-
: undefined
|
|
74
|
-
const create =
|
|
75
|
-
negateLabelVals && negateLabelVals.length > 0
|
|
76
|
-
? negateLabelVals.split(' ')
|
|
77
|
-
: undefined
|
|
78
|
-
await labelTxn.formatAndCreate(
|
|
79
|
-
ctx.cfg.labelerDid,
|
|
80
|
-
result.subjectUri ?? result.subjectDid,
|
|
81
|
-
result.subjectCid,
|
|
82
|
-
{ create, negate },
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
return { result, restored }
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
if (restored && ctx.moderationPushAgent) {
|
|
89
|
-
const agent = ctx.moderationPushAgent
|
|
90
|
-
const { subjects } = restored
|
|
91
|
-
const results = await Promise.allSettled(
|
|
92
|
-
subjects.map((subject) =>
|
|
93
|
-
retryHttp(() =>
|
|
94
|
-
agent.api.com.atproto.admin.updateSubjectStatus({
|
|
95
|
-
subject,
|
|
96
|
-
takedown: {
|
|
97
|
-
applied: false,
|
|
98
|
-
},
|
|
99
|
-
}),
|
|
100
|
-
),
|
|
101
|
-
),
|
|
102
|
-
)
|
|
103
|
-
const hadFailure = results.some((r) => r.status === 'rejected')
|
|
104
|
-
if (hadFailure) {
|
|
105
|
-
throw new UpstreamFailureError('failed to revert action on PDS')
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
encoding: 'application/json',
|
|
111
|
-
body: await moderationService.views.action(result),
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
}
|