@atproto/bsky 0.0.15 → 0.0.17

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.
Files changed (236) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  3. package/dist/cache/read-through.d.ts +30 -0
  4. package/dist/config.d.ts +18 -0
  5. package/dist/context.d.ts +21 -6
  6. package/dist/daemon/config.d.ts +15 -0
  7. package/dist/daemon/context.d.ts +15 -0
  8. package/dist/daemon/index.d.ts +23 -0
  9. package/dist/daemon/logger.d.ts +3 -0
  10. package/dist/daemon/notifications.d.ts +18 -0
  11. package/dist/daemon/services.d.ts +11 -0
  12. package/dist/db/database-schema.d.ts +1 -2
  13. package/dist/db/index.js +41 -1
  14. package/dist/db/index.js.map +3 -3
  15. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  16. package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
  17. package/dist/db/migrations/index.d.ts +2 -0
  18. package/dist/db/pagination.d.ts +2 -1
  19. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  20. package/dist/db/tables/moderation.d.ts +24 -34
  21. package/dist/did-cache.d.ts +10 -7
  22. package/dist/feed-gen/types.d.ts +1 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +4370 -2758
  25. package/dist/index.js.map +3 -3
  26. package/dist/indexer/context.d.ts +2 -0
  27. package/dist/indexer/index.d.ts +1 -0
  28. package/dist/lexicon/index.d.ts +23 -18
  29. package/dist/lexicon/lexicons.d.ts +561 -412
  30. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  31. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  32. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
  33. package/dist/lexicon/types/com/atproto/admin/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
  34. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  35. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  36. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  37. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  38. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  39. package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
  40. package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
  41. package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
  42. package/dist/logger.d.ts +1 -0
  43. package/dist/migrate-moderation-data.d.ts +1 -0
  44. package/dist/redis.d.ts +10 -1
  45. package/dist/services/actor/index.d.ts +18 -4
  46. package/dist/services/actor/views.d.ts +6 -8
  47. package/dist/services/feed/index.d.ts +7 -4
  48. package/dist/services/feed/util.d.ts +9 -1
  49. package/dist/services/feed/views.d.ts +11 -21
  50. package/dist/services/graph/index.d.ts +5 -29
  51. package/dist/services/graph/types.d.ts +1 -0
  52. package/dist/services/index.d.ts +3 -7
  53. package/dist/services/label/index.d.ts +10 -4
  54. package/dist/services/moderation/index.d.ts +134 -72
  55. package/dist/services/moderation/pagination.d.ts +36 -0
  56. package/dist/services/moderation/status.d.ts +13 -0
  57. package/dist/services/moderation/types.d.ts +35 -0
  58. package/dist/services/moderation/views.d.ts +18 -14
  59. package/dist/services/types.d.ts +3 -0
  60. package/dist/services/util/notification.d.ts +5 -0
  61. package/dist/services/util/post.d.ts +6 -6
  62. package/dist/util/debug.d.ts +1 -1
  63. package/dist/util/retry.d.ts +1 -6
  64. package/package.json +11 -11
  65. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  66. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  67. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  68. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  69. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  70. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  71. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  72. package/src/api/app/bsky/feed/getPostThread.ts +15 -54
  73. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  74. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  75. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  76. package/src/api/app/bsky/feed/searchPosts.ts +20 -17
  77. package/src/api/app/bsky/graph/getList.ts +6 -3
  78. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  79. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  80. package/src/api/app/bsky/graph/getLists.ts +2 -1
  81. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  82. package/src/api/blob-resolver.ts +6 -11
  83. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  84. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  85. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  86. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  87. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  88. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  89. package/src/api/com/atproto/moderation/util.ts +38 -20
  90. package/src/api/index.ts +8 -14
  91. package/src/auth.ts +29 -21
  92. package/src/auto-moderator/index.ts +26 -19
  93. package/src/cache/read-through.ts +151 -0
  94. package/src/config.ts +90 -1
  95. package/src/context.ts +11 -7
  96. package/src/daemon/config.ts +60 -0
  97. package/src/daemon/context.ts +27 -0
  98. package/src/daemon/index.ts +78 -0
  99. package/src/daemon/logger.ts +6 -0
  100. package/src/daemon/notifications.ts +54 -0
  101. package/src/daemon/services.ts +22 -0
  102. package/src/db/database-schema.ts +0 -2
  103. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  104. package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
  105. package/src/db/migrations/index.ts +2 -0
  106. package/src/db/pagination.ts +26 -3
  107. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
  108. package/src/db/tables/moderation.ts +35 -52
  109. package/src/did-cache.ts +33 -56
  110. package/src/feed-gen/bsky-team.ts +1 -1
  111. package/src/feed-gen/hot-classic.ts +1 -1
  112. package/src/feed-gen/index.ts +0 -4
  113. package/src/feed-gen/mutuals.ts +6 -2
  114. package/src/feed-gen/types.ts +1 -1
  115. package/src/index.ts +57 -17
  116. package/src/indexer/context.ts +5 -0
  117. package/src/indexer/index.ts +10 -7
  118. package/src/lexicon/index.ts +80 -67
  119. package/src/lexicon/lexicons.ts +698 -507
  120. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  121. package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
  122. package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
  123. package/src/lexicon/types/com/atproto/admin/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
  124. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  125. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  126. package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
  127. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
  128. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  129. package/src/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
  130. package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
  131. package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
  132. package/src/logger.ts +2 -0
  133. package/src/migrate-moderation-data.ts +414 -0
  134. package/src/redis.ts +43 -3
  135. package/src/services/actor/index.ts +55 -7
  136. package/src/services/actor/views.ts +18 -21
  137. package/src/services/feed/index.ts +52 -19
  138. package/src/services/feed/util.ts +47 -19
  139. package/src/services/feed/views.ts +87 -13
  140. package/src/services/graph/index.ts +21 -3
  141. package/src/services/graph/types.ts +1 -0
  142. package/src/services/index.ts +14 -14
  143. package/src/services/indexing/index.ts +7 -10
  144. package/src/services/indexing/plugins/block.ts +2 -3
  145. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  146. package/src/services/indexing/plugins/follow.ts +2 -3
  147. package/src/services/indexing/plugins/like.ts +2 -3
  148. package/src/services/indexing/plugins/list-block.ts +2 -3
  149. package/src/services/indexing/plugins/list-item.ts +2 -3
  150. package/src/services/indexing/plugins/list.ts +2 -3
  151. package/src/services/indexing/plugins/post.ts +16 -4
  152. package/src/services/indexing/plugins/repost.ts +2 -3
  153. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  154. package/src/services/label/index.ts +68 -25
  155. package/src/services/moderation/index.ts +380 -395
  156. package/src/services/moderation/pagination.ts +96 -0
  157. package/src/services/moderation/status.ts +241 -0
  158. package/src/services/moderation/types.ts +49 -0
  159. package/src/services/moderation/views.ts +278 -329
  160. package/src/services/types.ts +4 -0
  161. package/src/services/util/notification.ts +70 -0
  162. package/src/util/debug.ts +2 -2
  163. package/src/util/retry.ts +1 -44
  164. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  165. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  166. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  167. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  168. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  169. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  170. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  171. package/tests/admin/get-record.test.ts +5 -9
  172. package/tests/admin/get-repo.test.ts +10 -12
  173. package/tests/admin/moderation-events.test.ts +221 -0
  174. package/tests/admin/moderation-statuses.test.ts +145 -0
  175. package/tests/admin/moderation.test.ts +512 -860
  176. package/tests/admin/repo-search.test.ts +3 -3
  177. package/tests/algos/hot-classic.test.ts +1 -2
  178. package/tests/auth.test.ts +1 -1
  179. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  180. package/tests/auto-moderator/labeler.test.ts +19 -20
  181. package/tests/auto-moderator/takedowns.test.ts +61 -28
  182. package/tests/blob-resolver.test.ts +4 -2
  183. package/tests/daemon.test.ts +191 -0
  184. package/tests/did-cache.test.ts +20 -5
  185. package/tests/feed-generation.test.ts +57 -9
  186. package/tests/handle-invalidation.test.ts +1 -5
  187. package/tests/indexing.test.ts +20 -13
  188. package/tests/redis-cache.test.ts +231 -0
  189. package/tests/seeds/basic.ts +3 -0
  190. package/tests/subscription/repo.test.ts +4 -7
  191. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  192. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  193. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  194. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  195. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  196. package/tests/views/actor-search.test.ts +2 -3
  197. package/tests/views/author-feed.test.ts +42 -36
  198. package/tests/views/follows.test.ts +40 -35
  199. package/tests/views/list-feed.test.ts +17 -9
  200. package/tests/views/notifications.test.ts +13 -9
  201. package/tests/views/profile.test.ts +20 -19
  202. package/tests/views/thread.test.ts +117 -94
  203. package/tests/views/threadgating.test.ts +89 -19
  204. package/tests/views/timeline.test.ts +21 -13
  205. package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
  206. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  207. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  208. package/dist/db/tables/did-cache.d.ts +0 -10
  209. package/dist/feed-gen/best-of-follows.d.ts +0 -29
  210. package/dist/feed-gen/whats-hot.d.ts +0 -29
  211. package/dist/feed-gen/with-friends.d.ts +0 -3
  212. package/dist/label-cache.d.ts +0 -19
  213. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  214. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  215. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  216. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  217. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  218. package/src/db/tables/did-cache.ts +0 -13
  219. package/src/feed-gen/best-of-follows.ts +0 -74
  220. package/src/feed-gen/whats-hot.ts +0 -101
  221. package/src/feed-gen/with-friends.ts +0 -39
  222. package/src/label-cache.ts +0 -90
  223. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  224. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  225. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  226. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  227. package/tests/admin/get-moderation-action.test.ts +0 -100
  228. package/tests/admin/get-moderation-actions.test.ts +0 -164
  229. package/tests/admin/get-moderation-report.test.ts +0 -100
  230. package/tests/admin/get-moderation-reports.test.ts +0 -332
  231. package/tests/algos/whats-hot.test.ts +0 -118
  232. package/tests/algos/with-friends.test.ts +0 -145
  233. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  234. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  235. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  236. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
@@ -1,6 +1,5 @@
1
1
  import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api'
2
2
  import { TestNetwork, SeedClient } from '@atproto/dev-env'
3
- import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
4
3
  import { forSnapshot, stripViewerFromThread } from '../_util'
5
4
  import basicSeed from '../seeds/basic'
6
5
  import assert from 'assert'
@@ -166,22 +165,21 @@ describe('pds thread views', () => {
166
165
 
167
166
  describe('takedown', () => {
168
167
  it('blocks post by actor', async () => {
169
- const { data: modAction } =
170
- await agent.api.com.atproto.admin.takeModerationAction(
171
- {
172
- action: TAKEDOWN,
173
- subject: {
174
- $type: 'com.atproto.admin.defs#repoRef',
175
- did: alice,
176
- },
177
- createdBy: 'did:example:admin',
178
- reason: 'Y',
179
- },
180
- {
181
- encoding: 'application/json',
182
- headers: network.pds.adminAuthHeaders(),
168
+ await agent.api.com.atproto.admin.emitModerationEvent(
169
+ {
170
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
171
+ subject: {
172
+ $type: 'com.atproto.admin.defs#repoRef',
173
+ did: alice,
183
174
  },
184
- )
175
+ createdBy: 'did:example:admin',
176
+ reason: 'Y',
177
+ },
178
+ {
179
+ encoding: 'application/json',
180
+ headers: network.pds.adminAuthHeaders(),
181
+ },
182
+ )
185
183
 
186
184
  // Same as shallow post thread test, minus alice
187
185
  const promise = agent.api.app.bsky.feed.getPostThread(
@@ -194,9 +192,13 @@ describe('pds thread views', () => {
194
192
  )
195
193
 
196
194
  // Cleanup
197
- await agent.api.com.atproto.admin.reverseModerationAction(
195
+ await agent.api.com.atproto.admin.emitModerationEvent(
198
196
  {
199
- id: modAction.id,
197
+ event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
198
+ subject: {
199
+ $type: 'com.atproto.admin.defs#repoRef',
200
+ did: alice,
201
+ },
200
202
  createdBy: 'did:example:admin',
201
203
  reason: 'Y',
202
204
  },
@@ -208,22 +210,21 @@ describe('pds thread views', () => {
208
210
  })
209
211
 
210
212
  it('blocks replies by actor', async () => {
211
- const { data: modAction } =
212
- await agent.api.com.atproto.admin.takeModerationAction(
213
- {
214
- action: TAKEDOWN,
215
- subject: {
216
- $type: 'com.atproto.admin.defs#repoRef',
217
- did: carol,
218
- },
219
- createdBy: 'did:example:admin',
220
- reason: 'Y',
221
- },
222
- {
223
- encoding: 'application/json',
224
- headers: network.pds.adminAuthHeaders(),
213
+ await agent.api.com.atproto.admin.emitModerationEvent(
214
+ {
215
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
216
+ subject: {
217
+ $type: 'com.atproto.admin.defs#repoRef',
218
+ did: carol,
225
219
  },
226
- )
220
+ createdBy: 'did:example:admin',
221
+ reason: 'Y',
222
+ },
223
+ {
224
+ encoding: 'application/json',
225
+ headers: network.pds.adminAuthHeaders(),
226
+ },
227
+ )
227
228
 
228
229
  // Same as deep post thread test, minus carol
229
230
  const thread = await agent.api.app.bsky.feed.getPostThread(
@@ -234,9 +235,13 @@ describe('pds thread views', () => {
234
235
  expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
235
236
 
236
237
  // Cleanup
237
- await agent.api.com.atproto.admin.reverseModerationAction(
238
+ await agent.api.com.atproto.admin.emitModerationEvent(
238
239
  {
239
- id: modAction.id,
240
+ event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
241
+ subject: {
242
+ $type: 'com.atproto.admin.defs#repoRef',
243
+ did: carol,
244
+ },
240
245
  createdBy: 'did:example:admin',
241
246
  reason: 'Y',
242
247
  },
@@ -248,22 +253,21 @@ describe('pds thread views', () => {
248
253
  })
249
254
 
250
255
  it('blocks ancestors by actor', async () => {
251
- const { data: modAction } =
252
- await agent.api.com.atproto.admin.takeModerationAction(
253
- {
254
- action: TAKEDOWN,
255
- subject: {
256
- $type: 'com.atproto.admin.defs#repoRef',
257
- did: bob,
258
- },
259
- createdBy: 'did:example:admin',
260
- reason: 'Y',
261
- },
262
- {
263
- encoding: 'application/json',
264
- headers: network.pds.adminAuthHeaders(),
256
+ await agent.api.com.atproto.admin.emitModerationEvent(
257
+ {
258
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
259
+ subject: {
260
+ $type: 'com.atproto.admin.defs#repoRef',
261
+ did: bob,
265
262
  },
266
- )
263
+ createdBy: 'did:example:admin',
264
+ reason: 'Y',
265
+ },
266
+ {
267
+ encoding: 'application/json',
268
+ headers: network.pds.adminAuthHeaders(),
269
+ },
270
+ )
267
271
 
268
272
  // Same as ancestor post thread test, minus bob
269
273
  const thread = await agent.api.app.bsky.feed.getPostThread(
@@ -274,9 +278,13 @@ describe('pds thread views', () => {
274
278
  expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
275
279
 
276
280
  // Cleanup
277
- await agent.api.com.atproto.admin.reverseModerationAction(
281
+ await agent.api.com.atproto.admin.emitModerationEvent(
278
282
  {
279
- id: modAction.id,
283
+ event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
284
+ subject: {
285
+ $type: 'com.atproto.admin.defs#repoRef',
286
+ did: bob,
287
+ },
280
288
  createdBy: 'did:example:admin',
281
289
  reason: 'Y',
282
290
  },
@@ -289,23 +297,22 @@ describe('pds thread views', () => {
289
297
 
290
298
  it('blocks post by record', async () => {
291
299
  const postRef = sc.posts[alice][1].ref
292
- const { data: modAction } =
293
- await agent.api.com.atproto.admin.takeModerationAction(
294
- {
295
- action: TAKEDOWN,
296
- subject: {
297
- $type: 'com.atproto.repo.strongRef',
298
- uri: postRef.uriStr,
299
- cid: postRef.cidStr,
300
- },
301
- createdBy: 'did:example:admin',
302
- reason: 'Y',
303
- },
304
- {
305
- encoding: 'application/json',
306
- headers: network.pds.adminAuthHeaders(),
300
+ await agent.api.com.atproto.admin.emitModerationEvent(
301
+ {
302
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
303
+ subject: {
304
+ $type: 'com.atproto.repo.strongRef',
305
+ uri: postRef.uriStr,
306
+ cid: postRef.cidStr,
307
307
  },
308
- )
308
+ createdBy: 'did:example:admin',
309
+ reason: 'Y',
310
+ },
311
+ {
312
+ encoding: 'application/json',
313
+ headers: network.pds.adminAuthHeaders(),
314
+ },
315
+ )
309
316
 
310
317
  const promise = agent.api.app.bsky.feed.getPostThread(
311
318
  { depth: 1, uri: postRef.uriStr },
@@ -317,9 +324,14 @@ describe('pds thread views', () => {
317
324
  )
318
325
 
319
326
  // Cleanup
320
- await agent.api.com.atproto.admin.reverseModerationAction(
327
+ await agent.api.com.atproto.admin.emitModerationEvent(
321
328
  {
322
- id: modAction.id,
329
+ event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
330
+ subject: {
331
+ $type: 'com.atproto.repo.strongRef',
332
+ uri: postRef.uriStr,
333
+ cid: postRef.cidStr,
334
+ },
323
335
  createdBy: 'did:example:admin',
324
336
  reason: 'Y',
325
337
  },
@@ -338,23 +350,22 @@ describe('pds thread views', () => {
338
350
 
339
351
  const parent = threadPreTakedown.data.thread.parent?.['post']
340
352
 
341
- const { data: modAction } =
342
- await agent.api.com.atproto.admin.takeModerationAction(
343
- {
344
- action: TAKEDOWN,
345
- subject: {
346
- $type: 'com.atproto.repo.strongRef',
347
- uri: parent.uri,
348
- cid: parent.cid,
349
- },
350
- createdBy: 'did:example:admin',
351
- reason: 'Y',
352
- },
353
- {
354
- encoding: 'application/json',
355
- headers: network.pds.adminAuthHeaders(),
353
+ await agent.api.com.atproto.admin.emitModerationEvent(
354
+ {
355
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
356
+ subject: {
357
+ $type: 'com.atproto.repo.strongRef',
358
+ uri: parent.uri,
359
+ cid: parent.cid,
356
360
  },
357
- )
361
+ createdBy: 'did:example:admin',
362
+ reason: 'Y',
363
+ },
364
+ {
365
+ encoding: 'application/json',
366
+ headers: network.pds.adminAuthHeaders(),
367
+ },
368
+ )
358
369
 
359
370
  // Same as ancestor post thread test, minus parent post
360
371
  const thread = await agent.api.app.bsky.feed.getPostThread(
@@ -365,9 +376,14 @@ describe('pds thread views', () => {
365
376
  expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
366
377
 
367
378
  // Cleanup
368
- await agent.api.com.atproto.admin.reverseModerationAction(
379
+ await agent.api.com.atproto.admin.emitModerationEvent(
369
380
  {
370
- id: modAction.id,
381
+ event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' },
382
+ subject: {
383
+ $type: 'com.atproto.repo.strongRef',
384
+ uri: parent.uri,
385
+ cid: parent.cid,
386
+ },
371
387
  createdBy: 'did:example:admin',
372
388
  reason: 'Y',
373
389
  },
@@ -386,11 +402,11 @@ describe('pds thread views', () => {
386
402
  const post1 = threadPreTakedown.data.thread.replies?.[0].post
387
403
  const post2 = threadPreTakedown.data.thread.replies?.[1].replies[0].post
388
404
 
389
- const actionResults = await Promise.all(
405
+ await Promise.all(
390
406
  [post1, post2].map((post) =>
391
- agent.api.com.atproto.admin.takeModerationAction(
407
+ agent.api.com.atproto.admin.emitModerationEvent(
392
408
  {
393
- action: TAKEDOWN,
409
+ event: { $type: 'com.atproto.admin.defs#modEventTakedown' },
394
410
  subject: {
395
411
  $type: 'com.atproto.repo.strongRef',
396
412
  uri: post.uri,
@@ -417,10 +433,17 @@ describe('pds thread views', () => {
417
433
 
418
434
  // Cleanup
419
435
  await Promise.all(
420
- actionResults.map((result) =>
421
- agent.api.com.atproto.admin.reverseModerationAction(
436
+ [post1, post2].map((post) =>
437
+ agent.api.com.atproto.admin.emitModerationEvent(
422
438
  {
423
- id: result.data.id,
439
+ event: {
440
+ $type: 'com.atproto.admin.defs#modEventReverseTakedown',
441
+ },
442
+ subject: {
443
+ $type: 'com.atproto.repo.strongRef',
444
+ uri: post.uri,
445
+ cid: post.cid,
446
+ },
424
447
  createdBy: 'did:example:admin',
425
448
  reason: 'Y',
426
449
  },
@@ -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(
@@ -36,6 +49,7 @@ describe('views with thread gating', () => {
36
49
  { post: post.ref.uriStr, createdAt: iso(), allow: [] },
37
50
  sc.getHeaders(sc.dids.carol),
38
51
  )
52
+ await network.processAll()
39
53
  await sc.reply(sc.dids.alice, post.ref, post.ref, 'empty rules reply')
40
54
  await network.processAll()
41
55
  const {
@@ -46,8 +60,36 @@ describe('views with thread gating', () => {
46
60
  )
47
61
  assert(isThreadViewPost(thread))
48
62
  expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
49
- expect(thread.viewer).toEqual({ canReply: false })
63
+ expect(thread.post.viewer).toEqual({ replyDisabled: true })
50
64
  expect(thread.replies?.length).toEqual(0)
65
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
66
+ })
67
+
68
+ it('does not generate notifications when post violates threadgate.', async () => {
69
+ const post = await sc.post(sc.dids.carol, 'notifications')
70
+ await pdsAgent.api.app.bsky.feed.threadgate.create(
71
+ { repo: sc.dids.carol, rkey: post.ref.uri.rkey },
72
+ { post: post.ref.uriStr, createdAt: iso(), allow: [] },
73
+ sc.getHeaders(sc.dids.carol),
74
+ )
75
+ await network.processAll()
76
+ const reply = await sc.reply(
77
+ sc.dids.alice,
78
+ post.ref,
79
+ post.ref,
80
+ 'notifications reply',
81
+ )
82
+ await network.processAll()
83
+ const {
84
+ data: { notifications },
85
+ } = await agent.api.app.bsky.notification.listNotifications(
86
+ {},
87
+ { headers: await network.serviceHeaders(sc.dids.carol) },
88
+ )
89
+ const notificationFromReply = notifications.find(
90
+ (notif) => notif.uri === reply.ref.uriStr,
91
+ )
92
+ expect(notificationFromReply).toBeUndefined()
51
93
  })
52
94
 
53
95
  it('applies gate for mention rule.', async () => {
@@ -78,6 +120,7 @@ describe('views with thread gating', () => {
78
120
  },
79
121
  sc.getHeaders(sc.dids.carol),
80
122
  )
123
+ await network.processAll()
81
124
  await sc.reply(
82
125
  sc.dids.alice,
83
126
  post.ref,
@@ -98,7 +141,8 @@ describe('views with thread gating', () => {
98
141
  { headers: await network.serviceHeaders(sc.dids.alice) },
99
142
  )
100
143
  assert(isThreadViewPost(aliceThread))
101
- expect(aliceThread.viewer).toEqual({ canReply: false })
144
+ expect(aliceThread.post.viewer).toEqual({ replyDisabled: true })
145
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
102
146
  const {
103
147
  data: { thread: danThread },
104
148
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -107,7 +151,8 @@ describe('views with thread gating', () => {
107
151
  )
108
152
  assert(isThreadViewPost(danThread))
109
153
  expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
110
- expect(danThread.viewer).toEqual({ canReply: true })
154
+ expect(danThread.post.viewer).toEqual({ replyDisabled: false })
155
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
111
156
  const [reply, ...otherReplies] = danThread.replies ?? []
112
157
  assert(isThreadViewPost(reply))
113
158
  expect(otherReplies.length).toEqual(0)
@@ -125,6 +170,7 @@ describe('views with thread gating', () => {
125
170
  },
126
171
  sc.getHeaders(sc.dids.carol),
127
172
  )
173
+ await network.processAll()
128
174
  // carol only follows alice
129
175
  await sc.reply(
130
176
  sc.dids.dan,
@@ -146,7 +192,8 @@ describe('views with thread gating', () => {
146
192
  { headers: await network.serviceHeaders(sc.dids.dan) },
147
193
  )
148
194
  assert(isThreadViewPost(danThread))
149
- expect(danThread.viewer).toEqual({ canReply: false })
195
+ expect(danThread.post.viewer).toEqual({ replyDisabled: true })
196
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, true)
150
197
  const {
151
198
  data: { thread: aliceThread },
152
199
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -155,7 +202,8 @@ describe('views with thread gating', () => {
155
202
  )
156
203
  assert(isThreadViewPost(aliceThread))
157
204
  expect(forSnapshot(aliceThread.post.threadgate)).toMatchSnapshot()
158
- expect(aliceThread.viewer).toEqual({ canReply: true })
205
+ expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
206
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
159
207
  const [reply, ...otherReplies] = aliceThread.replies ?? []
160
208
  assert(isThreadViewPost(reply))
161
209
  expect(otherReplies.length).toEqual(0)
@@ -213,6 +261,7 @@ describe('views with thread gating', () => {
213
261
  },
214
262
  sc.getHeaders(sc.dids.carol),
215
263
  )
264
+ await network.processAll()
216
265
  //
217
266
  await sc.reply(sc.dids.bob, post.ref, post.ref, 'list rule reply disallow')
218
267
  const aliceReply = await sc.reply(
@@ -235,7 +284,8 @@ describe('views with thread gating', () => {
235
284
  { headers: await network.serviceHeaders(sc.dids.bob) },
236
285
  )
237
286
  assert(isThreadViewPost(bobThread))
238
- expect(bobThread.viewer).toEqual({ canReply: false })
287
+ expect(bobThread.post.viewer).toEqual({ replyDisabled: true })
288
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.bob, true)
239
289
  const {
240
290
  data: { thread: aliceThread },
241
291
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -243,7 +293,8 @@ describe('views with thread gating', () => {
243
293
  { headers: await network.serviceHeaders(sc.dids.alice) },
244
294
  )
245
295
  assert(isThreadViewPost(aliceThread))
246
- expect(aliceThread.viewer).toEqual({ canReply: true })
296
+ expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
297
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
247
298
  const {
248
299
  data: { thread: danThread },
249
300
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -252,7 +303,8 @@ describe('views with thread gating', () => {
252
303
  )
253
304
  assert(isThreadViewPost(danThread))
254
305
  expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
255
- expect(danThread.viewer).toEqual({ canReply: true })
306
+ expect(danThread.post.viewer).toEqual({ replyDisabled: false })
307
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
256
308
  const [reply1, reply2, ...otherReplies] = aliceThread.replies ?? []
257
309
  assert(isThreadViewPost(reply1))
258
310
  assert(isThreadViewPost(reply2))
@@ -277,6 +329,7 @@ describe('views with thread gating', () => {
277
329
  },
278
330
  sc.getHeaders(sc.dids.carol),
279
331
  )
332
+ await network.processAll()
280
333
  await sc.reply(
281
334
  sc.dids.alice,
282
335
  post.ref,
@@ -292,8 +345,9 @@ describe('views with thread gating', () => {
292
345
  )
293
346
  assert(isThreadViewPost(thread))
294
347
  expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
295
- expect(thread.viewer).toEqual({ canReply: false })
348
+ expect(thread.post.viewer).toEqual({ replyDisabled: true })
296
349
  expect(thread.replies?.length).toEqual(0)
350
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, true)
297
351
  })
298
352
 
299
353
  it('applies gate for multiple rules.', async () => {
@@ -317,6 +371,7 @@ describe('views with thread gating', () => {
317
371
  },
318
372
  sc.getHeaders(sc.dids.carol),
319
373
  )
374
+ await network.processAll()
320
375
  // carol only follows alice, and the post mentions dan.
321
376
  await sc.reply(sc.dids.bob, post.ref, post.ref, 'multi rule reply disallow')
322
377
  const aliceReply = await sc.reply(
@@ -339,7 +394,8 @@ describe('views with thread gating', () => {
339
394
  { headers: await network.serviceHeaders(sc.dids.bob) },
340
395
  )
341
396
  assert(isThreadViewPost(bobThread))
342
- expect(bobThread.viewer).toEqual({ canReply: false })
397
+ expect(bobThread.post.viewer).toEqual({ replyDisabled: true })
398
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.bob, true)
343
399
  const {
344
400
  data: { thread: aliceThread },
345
401
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -347,7 +403,8 @@ describe('views with thread gating', () => {
347
403
  { headers: await network.serviceHeaders(sc.dids.alice) },
348
404
  )
349
405
  assert(isThreadViewPost(aliceThread))
350
- expect(aliceThread.viewer).toEqual({ canReply: true })
406
+ expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
407
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
351
408
  const {
352
409
  data: { thread: danThread },
353
410
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -356,7 +413,8 @@ describe('views with thread gating', () => {
356
413
  )
357
414
  assert(isThreadViewPost(danThread))
358
415
  expect(forSnapshot(danThread.post.threadgate)).toMatchSnapshot()
359
- expect(danThread.viewer).toEqual({ canReply: true })
416
+ expect(danThread.post.viewer).toEqual({ replyDisabled: false })
417
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.dan, false)
360
418
  const [reply1, reply2, ...otherReplies] = aliceThread.replies ?? []
361
419
  assert(isThreadViewPost(reply1))
362
420
  assert(isThreadViewPost(reply2))
@@ -372,6 +430,7 @@ describe('views with thread gating', () => {
372
430
  { post: post.ref.uriStr, createdAt: iso() },
373
431
  sc.getHeaders(sc.dids.carol),
374
432
  )
433
+ await network.processAll()
375
434
  const aliceReply = await sc.reply(
376
435
  sc.dids.alice,
377
436
  post.ref,
@@ -387,7 +446,8 @@ describe('views with thread gating', () => {
387
446
  )
388
447
  assert(isThreadViewPost(thread))
389
448
  expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
390
- expect(thread.viewer).toEqual({ canReply: true })
449
+ expect(thread.post.viewer).toEqual({ replyDisabled: false })
450
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.alice, false)
391
451
  const [reply, ...otherReplies] = thread.replies ?? []
392
452
  assert(isThreadViewPost(reply))
393
453
  expect(otherReplies.length).toEqual(0)
@@ -406,6 +466,7 @@ describe('views with thread gating', () => {
406
466
  },
407
467
  sc.getHeaders(sc.dids.carol),
408
468
  )
469
+ await network.processAll()
409
470
  // carol only follows alice
410
471
  const orphanedReply = await sc.reply(
411
472
  sc.dids.alice,
@@ -438,7 +499,8 @@ describe('views with thread gating', () => {
438
499
  { headers: await network.serviceHeaders(sc.dids.dan) },
439
500
  )
440
501
  assert(isThreadViewPost(danThread))
441
- expect(danThread.viewer).toEqual({ canReply: false })
502
+ expect(danThread.post.viewer).toEqual({ replyDisabled: true })
503
+ await checkReplyDisabled(orphanedReply.ref.uriStr, sc.dids.dan, true)
442
504
  const {
443
505
  data: { thread: aliceThread },
444
506
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -451,7 +513,8 @@ describe('views with thread gating', () => {
451
513
  aliceThread.parent.uri === post.ref.uriStr,
452
514
  )
453
515
  expect(aliceThread.post.threadgate).toMatchSnapshot()
454
- expect(aliceThread.viewer).toEqual({ canReply: true })
516
+ expect(aliceThread.post.viewer).toEqual({ replyDisabled: false })
517
+ await checkReplyDisabled(orphanedReply.ref.uriStr, sc.dids.alice, false)
455
518
  const [reply, ...otherReplies] = aliceThread.replies ?? []
456
519
  assert(isThreadViewPost(reply))
457
520
  expect(otherReplies.length).toEqual(0)
@@ -465,6 +528,7 @@ describe('views with thread gating', () => {
465
528
  { post: post.ref.uriStr, createdAt: iso(), allow: [] },
466
529
  sc.getHeaders(sc.dids.carol),
467
530
  )
531
+ await network.processAll()
468
532
  const selfReply = await sc.reply(
469
533
  sc.dids.carol,
470
534
  post.ref,
@@ -480,7 +544,8 @@ describe('views with thread gating', () => {
480
544
  )
481
545
  assert(isThreadViewPost(thread))
482
546
  expect(forSnapshot(thread.post.threadgate)).toMatchSnapshot()
483
- expect(thread.viewer).toEqual({ canReply: true })
547
+ expect(thread.post.viewer).toEqual({ replyDisabled: false })
548
+ await checkReplyDisabled(post.ref.uriStr, sc.dids.carol, false)
484
549
  const [reply, ...otherReplies] = thread.replies ?? []
485
550
  assert(isThreadViewPost(reply))
486
551
  expect(otherReplies.length).toEqual(0)
@@ -498,6 +563,7 @@ describe('views with thread gating', () => {
498
563
  },
499
564
  sc.getHeaders(sc.dids.carol),
500
565
  )
566
+ await network.processAll()
501
567
  // carol only follows alice
502
568
  const badReply = await sc.reply(
503
569
  sc.dids.dan,
@@ -516,10 +582,11 @@ describe('views with thread gating', () => {
516
582
  { headers: await network.serviceHeaders(sc.dids.alice) },
517
583
  )
518
584
  assert(isThreadViewPost(thread))
519
- expect(thread.viewer).toEqual({ canReply: false }) // nobody can reply to this, not even alice.
585
+ expect(thread.post.viewer).toEqual({ replyDisabled: true }) // nobody can reply to this, not even alice.
520
586
  expect(thread.replies).toBeUndefined()
521
587
  expect(thread.parent).toBeUndefined()
522
588
  expect(thread.post.threadgate).toBeUndefined()
589
+ await checkReplyDisabled(badReply.ref.uriStr, sc.dids.alice, true)
523
590
  // check feed view
524
591
  const {
525
592
  data: { feed },
@@ -541,6 +608,7 @@ describe('views with thread gating', () => {
541
608
  { post: postB.ref.uriStr, createdAt: iso(), allow: [] },
542
609
  sc.getHeaders(sc.dids.carol),
543
610
  )
611
+ await network.processAll()
544
612
  await sc.reply(sc.dids.alice, postA.ref, postA.ref, 'ungated reply')
545
613
  await sc.reply(sc.dids.alice, postB.ref, postB.ref, 'ungated reply')
546
614
  await network.processAll()
@@ -552,8 +620,9 @@ describe('views with thread gating', () => {
552
620
  )
553
621
  assert(isThreadViewPost(threadA))
554
622
  expect(threadA.post.threadgate).toBeUndefined()
555
- expect(threadA.viewer).toEqual({ canReply: true })
623
+ expect(threadA.post.viewer).toEqual({})
556
624
  expect(threadA.replies?.length).toEqual(1)
625
+ await checkReplyDisabled(postA.ref.uriStr, sc.dids.alice, undefined)
557
626
  const {
558
627
  data: { thread: threadB },
559
628
  } = await agent.api.app.bsky.feed.getPostThread(
@@ -562,7 +631,8 @@ describe('views with thread gating', () => {
562
631
  )
563
632
  assert(isThreadViewPost(threadB))
564
633
  expect(threadB.post.threadgate).toBeUndefined()
565
- expect(threadB.viewer).toEqual({ canReply: true })
634
+ expect(threadB.post.viewer).toEqual({})
635
+ await checkReplyDisabled(postB.ref.uriStr, sc.dids.alice, undefined)
566
636
  expect(threadB.replies?.length).toEqual(1)
567
637
  })
568
638
  })