@atproto/bsky 0.0.14 → 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.
Files changed (195) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/api/app/bsky/feed/searchPosts.d.ts +3 -0
  3. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  4. package/dist/config.d.ts +2 -2
  5. package/dist/context.d.ts +16 -1
  6. package/dist/db/index.js +26 -1
  7. package/dist/db/index.js.map +3 -3
  8. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  9. package/dist/db/migrations/index.d.ts +1 -0
  10. package/dist/db/pagination.d.ts +2 -1
  11. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  12. package/dist/db/tables/moderation.d.ts +24 -34
  13. package/dist/feed-gen/types.d.ts +1 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +3332 -2430
  16. package/dist/index.js.map +3 -3
  17. package/dist/lexicon/index.d.ts +18 -18
  18. package/dist/lexicon/lexicons.d.ts +460 -385
  19. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  20. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  21. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +116 -48
  22. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  23. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  24. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  25. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  26. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  27. package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/fetchLabels.d.ts} +7 -3
  28. package/dist/migrate-moderation-data.d.ts +1 -0
  29. package/dist/services/actor/views.d.ts +2 -5
  30. package/dist/services/feed/index.d.ts +1 -0
  31. package/dist/services/feed/util.d.ts +9 -1
  32. package/dist/services/feed/views.d.ts +6 -17
  33. package/dist/services/graph/index.d.ts +5 -29
  34. package/dist/services/graph/types.d.ts +1 -0
  35. package/dist/services/moderation/index.d.ts +135 -72
  36. package/dist/services/moderation/pagination.d.ts +36 -0
  37. package/dist/services/moderation/status.d.ts +13 -0
  38. package/dist/services/moderation/types.d.ts +35 -0
  39. package/dist/services/moderation/views.d.ts +18 -14
  40. package/dist/util/debug.d.ts +1 -1
  41. package/package.json +14 -15
  42. package/src/api/app/bsky/actor/getSuggestions.ts +45 -21
  43. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  44. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  45. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  46. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  47. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  48. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  49. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  50. package/src/api/app/bsky/feed/getPostThread.ts +31 -58
  51. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  52. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  53. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  54. package/src/api/app/bsky/feed/searchPosts.ts +130 -0
  55. package/src/api/app/bsky/graph/getList.ts +6 -3
  56. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  57. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  58. package/src/api/app/bsky/graph/getLists.ts +2 -1
  59. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  60. package/src/api/blob-resolver.ts +6 -11
  61. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  62. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  63. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  64. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  65. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  66. package/src/api/com/atproto/admin/util.ts +3 -1
  67. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  68. package/src/api/com/atproto/moderation/util.ts +38 -20
  69. package/src/api/com/atproto/temp/fetchLabels.ts +30 -0
  70. package/src/api/index.ts +12 -14
  71. package/src/auth.ts +29 -21
  72. package/src/auto-moderator/index.ts +26 -19
  73. package/src/config.ts +6 -6
  74. package/src/context.ts +15 -9
  75. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  76. package/src/db/migrations/index.ts +1 -0
  77. package/src/db/pagination.ts +26 -3
  78. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +51 -55
  79. package/src/db/tables/moderation.ts +35 -52
  80. package/src/feed-gen/best-of-follows.ts +6 -3
  81. package/src/feed-gen/bsky-team.ts +1 -1
  82. package/src/feed-gen/hot-classic.ts +1 -1
  83. package/src/feed-gen/mutuals.ts +6 -2
  84. package/src/feed-gen/types.ts +1 -1
  85. package/src/feed-gen/whats-hot.ts +1 -1
  86. package/src/feed-gen/with-friends.ts +7 -3
  87. package/src/index.ts +2 -1
  88. package/src/lexicon/index.ts +52 -67
  89. package/src/lexicon/lexicons.ts +674 -579
  90. package/src/lexicon/types/app/bsky/actor/defs.ts +2 -2
  91. package/src/lexicon/types/app/bsky/actor/searchActors.ts +2 -2
  92. package/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +2 -2
  93. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  94. package/src/lexicon/types/app/bsky/feed/searchPosts.ts +3 -3
  95. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -2
  96. package/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts +4 -4
  97. package/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts +3 -3
  98. package/src/lexicon/types/com/atproto/admin/defs.ts +278 -84
  99. package/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +1 -1
  100. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  101. package/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +1 -1
  102. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  103. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +8 -15
  104. package/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +70 -0
  105. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  106. package/src/lexicon/types/com/atproto/label/defs.ts +9 -9
  107. package/src/lexicon/types/com/atproto/label/queryLabels.ts +2 -2
  108. package/src/lexicon/types/com/atproto/repo/applyWrites.ts +1 -1
  109. package/src/lexicon/types/com/atproto/repo/createRecord.ts +2 -2
  110. package/src/lexicon/types/com/atproto/repo/deleteRecord.ts +2 -2
  111. package/src/lexicon/types/com/atproto/repo/listRecords.ts +1 -1
  112. package/src/lexicon/types/com/atproto/repo/putRecord.ts +3 -3
  113. package/src/lexicon/types/com/atproto/sync/listBlobs.ts +1 -1
  114. package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +4 -4
  115. package/src/lexicon/types/com/atproto/{admin/getModerationActions.ts → temp/fetchLabels.ts} +3 -5
  116. package/src/migrate-moderation-data.ts +414 -0
  117. package/src/services/actor/views.ts +5 -14
  118. package/src/services/feed/index.ts +26 -7
  119. package/src/services/feed/util.ts +47 -19
  120. package/src/services/feed/views.ts +68 -4
  121. package/src/services/graph/index.ts +21 -3
  122. package/src/services/graph/types.ts +1 -0
  123. package/src/services/indexing/plugins/block.ts +2 -3
  124. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  125. package/src/services/indexing/plugins/follow.ts +2 -3
  126. package/src/services/indexing/plugins/like.ts +2 -3
  127. package/src/services/indexing/plugins/list-block.ts +2 -3
  128. package/src/services/indexing/plugins/list-item.ts +2 -3
  129. package/src/services/indexing/plugins/list.ts +2 -3
  130. package/src/services/indexing/plugins/post.ts +3 -4
  131. package/src/services/indexing/plugins/repost.ts +2 -3
  132. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  133. package/src/services/label/index.ts +2 -3
  134. package/src/services/moderation/index.ts +380 -395
  135. package/src/services/moderation/pagination.ts +96 -0
  136. package/src/services/moderation/status.ts +244 -0
  137. package/src/services/moderation/types.ts +49 -0
  138. package/src/services/moderation/views.ts +278 -329
  139. package/src/util/debug.ts +2 -2
  140. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  141. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  142. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  143. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  144. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  145. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  146. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  147. package/tests/admin/get-record.test.ts +5 -9
  148. package/tests/admin/get-repo.test.ts +38 -9
  149. package/tests/admin/moderation-events.test.ts +221 -0
  150. package/tests/admin/moderation-statuses.test.ts +145 -0
  151. package/tests/admin/moderation.test.ts +512 -860
  152. package/tests/admin/repo-search.test.ts +2 -3
  153. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  154. package/tests/auto-moderator/takedowns.test.ts +45 -18
  155. package/tests/feed-generation.test.ts +57 -9
  156. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  157. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  158. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  159. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  160. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  161. package/tests/views/actor-search.test.ts +2 -3
  162. package/tests/views/author-feed.test.ts +42 -36
  163. package/tests/views/follows.test.ts +40 -35
  164. package/tests/views/list-feed.test.ts +17 -9
  165. package/tests/views/notifications.test.ts +13 -9
  166. package/tests/views/profile.test.ts +20 -18
  167. package/tests/views/suggestions.test.ts +15 -7
  168. package/tests/views/thread.test.ts +54 -26
  169. package/tests/views/threadgating.test.ts +51 -19
  170. package/tests/views/timeline.test.ts +21 -13
  171. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  172. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  173. package/dist/lexicon/types/com/atproto/admin/resolveModerationReports.d.ts +0 -36
  174. package/dist/lexicon/types/com/atproto/admin/reverseModerationAction.d.ts +0 -36
  175. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  176. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  177. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  178. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  179. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  180. package/src/lexicon/types/com/atproto/admin/getModerationAction.ts +0 -41
  181. package/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +0 -49
  182. package/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +0 -49
  183. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  184. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  185. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  186. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  187. package/tests/admin/get-moderation-action.test.ts +0 -100
  188. package/tests/admin/get-moderation-actions.test.ts +0 -164
  189. package/tests/admin/get-moderation-report.test.ts +0 -100
  190. package/tests/admin/get-moderation-reports.test.ts +0 -332
  191. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  192. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  193. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  194. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
  195. /package/dist/api/com/atproto/{admin/resolveModerationReports.d.ts → temp/fetchLabels.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { mapDefined } from '@atproto/common'
1
2
  import { Server } from '../../../../lexicon'
2
3
  import AppContext from '../../../../context'
3
4
  import { FeedGenInfo, FeedService } from '../../../../services/feed'
@@ -60,7 +61,7 @@ const hydration = async (state: SkeletonState, ctx: Context) => {
60
61
 
61
62
  const presentation = (state: HydrationState, ctx: Context) => {
62
63
  const { feedService } = ctx
63
- const feeds = state.generators.map((gen) =>
64
+ const feeds = mapDefined(state.generators, (gen) =>
64
65
  feedService.views.formatFeedGeneratorView(gen, state.profiles),
65
66
  )
66
67
  return { feeds }
@@ -105,9 +105,7 @@ const noBlocksOrMutes = (state: HydrationState) => {
105
105
  const presentation = (state: HydrationState, ctx: Context) => {
106
106
  const { feedService } = ctx
107
107
  const { feedItems, cursor, params } = state
108
- const feed = feedService.views.formatFeed(feedItems, state, {
109
- viewer: params.viewer,
110
- })
108
+ const feed = feedService.views.formatFeed(feedItems, state, params.viewer)
111
109
  return { feed, cursor }
112
110
  }
113
111
 
@@ -6,27 +6,21 @@ import {
6
6
  NotFoundPost,
7
7
  ThreadViewPost,
8
8
  isNotFoundPost,
9
- isThreadViewPost,
10
9
  } from '../../../../lexicon/types/app/bsky/feed/defs'
11
- import { Record as PostRecord } from '../../../../lexicon/types/app/bsky/feed/post'
12
- import { Record as ThreadgateRecord } from '../../../../lexicon/types/app/bsky/feed/threadgate'
13
10
  import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPostThread'
14
11
  import AppContext from '../../../../context'
15
12
  import {
16
13
  FeedService,
17
14
  FeedRow,
18
15
  FeedHydrationState,
19
- PostInfo,
20
16
  } from '../../../../services/feed'
21
17
  import {
22
18
  getAncestorsAndSelfQb,
23
19
  getDescendentsQb,
24
20
  } from '../../../../services/util/post'
25
21
  import { Database } from '../../../../db'
26
- import DatabaseSchema from '../../../../db/database-schema'
27
22
  import { setRepoRev } from '../../../util'
28
23
  import { ActorInfoMap, ActorService } from '../../../../services/actor'
29
- import { violatesThreadGate } from '../../../../services/feed/util'
30
24
  import { createPipeline, noRules } from '../../../../pipeline'
31
25
 
32
26
  export default function (server: Server, ctx: AppContext) {
@@ -80,21 +74,7 @@ const hydration = async (state: SkeletonState, ctx: Context) => {
80
74
  } = state
81
75
  const relevant = getRelevantIds(threadData)
82
76
  const hydrated = await feedService.feedHydration({ ...relevant, viewer })
83
- // check root reply interaction rules
84
- const anchorPostUri = threadData.post.postUri
85
- const rootUri = threadData.post.replyRoot || anchorPostUri
86
- const anchor = hydrated.posts[anchorPostUri]
87
- const root = hydrated.posts[rootUri]
88
- const gate = hydrated.threadgates[rootUri]?.record
89
- const viewerCanReply = await checkViewerCanReply(
90
- ctx.db.db,
91
- anchor ?? null,
92
- viewer,
93
- new AtUri(rootUri).host,
94
- (root?.record ?? null) as PostRecord | null,
95
- gate ?? null,
96
- )
97
- return { ...state, ...hydrated, viewerCanReply }
77
+ return { ...state, ...hydrated }
98
78
  }
99
79
 
100
80
  const presentation = (state: HydrationState, ctx: Context) => {
@@ -103,16 +83,19 @@ const presentation = (state: HydrationState, ctx: Context) => {
103
83
  const actors = actorService.views.profileBasicPresentation(
104
84
  Object.keys(profiles),
105
85
  state,
106
- { viewer: params.viewer },
86
+ params.viewer,
87
+ )
88
+ const thread = composeThread(
89
+ state.threadData,
90
+ actors,
91
+ state,
92
+ ctx,
93
+ params.viewer,
107
94
  )
108
- const thread = composeThread(state.threadData, actors, state, ctx)
109
95
  if (isNotFoundPost(thread)) {
110
96
  // @TODO technically this could be returned as a NotFoundPost based on lexicon
111
97
  throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound')
112
98
  }
113
- if (isThreadViewPost(thread) && params.viewer) {
114
- thread.viewer = { canReply: state.viewerCanReply }
115
- }
116
99
  return { thread }
117
100
  }
118
101
 
@@ -121,6 +104,7 @@ const composeThread = (
121
104
  actors: ActorInfoMap,
122
105
  state: HydrationState,
123
106
  ctx: Context,
107
+ viewer: string | null,
124
108
  ) => {
125
109
  const { feedService } = ctx
126
110
  const { posts, threadgates, embeds, blocks, labels, lists } = state
@@ -133,6 +117,7 @@ const composeThread = (
133
117
  embeds,
134
118
  labels,
135
119
  lists,
120
+ viewer,
136
121
  )
137
122
 
138
123
  // replies that are invalid due to reply-gating:
@@ -179,14 +164,14 @@ const composeThread = (
179
164
  notFound: true,
180
165
  }
181
166
  } else {
182
- parent = composeThread(threadData.parent, actors, state, ctx)
167
+ parent = composeThread(threadData.parent, actors, state, ctx, viewer)
183
168
  }
184
169
  }
185
170
 
186
171
  let replies: (ThreadViewPost | NotFoundPost | BlockedPost)[] | undefined
187
172
  if (threadData.replies && !badReply) {
188
173
  replies = threadData.replies.flatMap((reply) => {
189
- const thread = composeThread(reply, actors, state, ctx)
174
+ const thread = composeThread(reply, actors, state, ctx, viewer)
190
175
  // e.g. don't bother including #postNotFound reply placeholders for takedowns. either way matches api contract.
191
176
  const skip = []
192
177
  return isNotFoundPost(thread) ? skip : thread
@@ -223,6 +208,7 @@ const getRelevantIds = (
223
208
  if (thread.post.replyRoot) {
224
209
  // ensure root is included for checking interactions
225
210
  uris.add(thread.post.replyRoot)
211
+ dids.add(new AtUri(thread.post.replyRoot).hostname)
226
212
  }
227
213
  return { dids, uris }
228
214
  }
@@ -255,11 +241,15 @@ const getThreadData = async (
255
241
  .orderBy('sortAt', 'desc')
256
242
  .execute(),
257
243
  ])
258
- const parentsByUri = parents.reduce((acc, parent) => {
259
- return Object.assign(acc, { [parent.postUri]: parent })
244
+ // prevent self-referential loops
245
+ const includedPosts = new Set<string>([uri])
246
+ const parentsByUri = parents.reduce((acc, post) => {
247
+ return Object.assign(acc, { [post.uri]: post })
260
248
  }, {} as Record<string, FeedRow>)
261
249
  const childrenByParentUri = children.reduce((acc, child) => {
262
250
  if (!child.replyParent) return acc
251
+ if (includedPosts.has(child.uri)) return acc
252
+ includedPosts.add(child.uri)
263
253
  acc[child.replyParent] ??= []
264
254
  acc[child.replyParent].push(child)
265
255
  return acc
@@ -269,7 +259,12 @@ const getThreadData = async (
269
259
  return {
270
260
  post,
271
261
  parent: post.replyParent
272
- ? getParentData(parentsByUri, post.replyParent, parentHeight)
262
+ ? getParentData(
263
+ parentsByUri,
264
+ includedPosts,
265
+ post.replyParent,
266
+ parentHeight,
267
+ )
273
268
  : undefined,
274
269
  replies: getChildrenData(childrenByParentUri, uri, depth),
275
270
  }
@@ -277,16 +272,19 @@ const getThreadData = async (
277
272
 
278
273
  const getParentData = (
279
274
  postsByUri: Record<string, FeedRow>,
275
+ includedPosts: Set<string>,
280
276
  uri: string,
281
277
  depth: number,
282
278
  ): PostThread | ParentNotFoundError | undefined => {
283
279
  if (depth < 1) return undefined
280
+ if (includedPosts.has(uri)) return undefined
281
+ includedPosts.add(uri)
284
282
  const post = postsByUri[uri]
285
283
  if (!post) return new ParentNotFoundError(uri)
286
284
  return {
287
285
  post,
288
286
  parent: post.replyParent
289
- ? getParentData(postsByUri, post.replyParent, depth - 1)
287
+ ? getParentData(postsByUri, includedPosts, post.replyParent, depth - 1)
290
288
  : undefined,
291
289
  replies: [],
292
290
  }
@@ -305,28 +303,6 @@ const getChildrenData = (
305
303
  }))
306
304
  }
307
305
 
308
- const checkViewerCanReply = async (
309
- db: DatabaseSchema,
310
- anchor: PostInfo | null,
311
- viewer: string | null,
312
- owner: string,
313
- root: PostRecord | null,
314
- threadgate: ThreadgateRecord | null,
315
- ) => {
316
- if (!viewer) return false
317
- // @TODO re-enable invalidReplyRoot check
318
- // if (anchor?.invalidReplyRoot || anchor?.violatesThreadGate) return false
319
- if (anchor?.violatesThreadGate) return false
320
- const viewerViolatesThreadGate = await violatesThreadGate(
321
- db,
322
- viewer,
323
- owner,
324
- root,
325
- threadgate,
326
- )
327
- return !viewerViolatesThreadGate
328
- }
329
-
330
306
  class ParentNotFoundError extends Error {
331
307
  constructor(public uri: string) {
332
308
  super(`Parent not found: ${uri}`)
@@ -352,7 +328,4 @@ type SkeletonState = {
352
328
  threadData: PostThread
353
329
  }
354
330
 
355
- type HydrationState = SkeletonState &
356
- FeedHydrationState & {
357
- viewerCanReply: boolean
358
- }
331
+ type HydrationState = SkeletonState & FeedHydrationState
@@ -1,10 +1,13 @@
1
1
  import { dedupeStrs } from '@atproto/common'
2
- import { AtUri } from '@atproto/syntax'
3
2
  import { Server } from '../../../../lexicon'
4
3
  import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPosts'
5
4
  import AppContext from '../../../../context'
6
5
  import { Database } from '../../../../db'
7
- import { FeedHydrationState, FeedService } from '../../../../services/feed'
6
+ import {
7
+ FeedHydrationState,
8
+ FeedRow,
9
+ FeedService,
10
+ } from '../../../../services/feed'
8
11
  import { createPipeline } from '../../../../pipeline'
9
12
  import { ActorService } from '../../../../services/actor'
10
13
 
@@ -31,18 +34,18 @@ export default function (server: Server, ctx: AppContext) {
31
34
  })
32
35
  }
33
36
 
34
- const skeleton = async (params: Params) => {
35
- return { params, postUris: dedupeStrs(params.uris) }
37
+ const skeleton = async (params: Params, ctx: Context) => {
38
+ const deduped = dedupeStrs(params.uris)
39
+ const feedItems = await ctx.feedService.postUrisToFeedItems(deduped)
40
+ return { params, feedItems }
36
41
  }
37
42
 
38
43
  const hydration = async (state: SkeletonState, ctx: Context) => {
39
44
  const { feedService } = ctx
40
- const { params, postUris } = state
41
- const uris = new Set<string>(postUris)
42
- const dids = new Set<string>(postUris.map((uri) => new AtUri(uri).hostname))
45
+ const { params, feedItems } = state
46
+ const refs = feedService.feedItemRefs(feedItems)
43
47
  const hydrated = await feedService.feedHydration({
44
- uris,
45
- dids,
48
+ ...refs,
46
49
  viewer: params.viewer,
47
50
  })
48
51
  return { ...state, ...hydrated }
@@ -50,32 +53,32 @@ const hydration = async (state: SkeletonState, ctx: Context) => {
50
53
 
51
54
  const noBlocks = (state: HydrationState) => {
52
55
  const { viewer } = state.params
53
- state.postUris = state.postUris.filter((uri) => {
54
- const post = state.posts[uri]
55
- if (!viewer || !post) return true
56
- return !state.bam.block([viewer, post.creator])
56
+ state.feedItems = state.feedItems.filter((item) => {
57
+ if (!viewer) return true
58
+ return !state.bam.block([viewer, item.postAuthorDid])
57
59
  })
58
60
  return state
59
61
  }
60
62
 
61
63
  const presentation = (state: HydrationState, ctx: Context) => {
62
64
  const { feedService, actorService } = ctx
63
- const { postUris, profiles, params } = state
65
+ const { feedItems, profiles, params } = state
64
66
  const SKIP = []
65
67
  const actors = actorService.views.profileBasicPresentation(
66
68
  Object.keys(profiles),
67
69
  state,
68
- { viewer: params.viewer },
70
+ params.viewer,
69
71
  )
70
- const postViews = postUris.flatMap((uri) => {
72
+ const postViews = feedItems.flatMap((item) => {
71
73
  const postView = feedService.views.formatPostView(
72
- uri,
74
+ item.postUri,
73
75
  actors,
74
76
  state.posts,
75
77
  state.threadgates,
76
78
  state.embeds,
77
79
  state.labels,
78
80
  state.lists,
81
+ params.viewer,
79
82
  )
80
83
  return postView ?? SKIP
81
84
  })
@@ -92,7 +95,7 @@ type Params = QueryParams & { viewer: string | null }
92
95
 
93
96
  type SkeletonState = {
94
97
  params: Params
95
- postUris: string[]
98
+ feedItems: FeedRow[]
96
99
  }
97
100
 
98
101
  type HydrationState = SkeletonState & FeedHydrationState
@@ -1,3 +1,4 @@
1
+ import { mapDefined } from '@atproto/common'
1
2
  import { Server } from '../../../../lexicon'
2
3
  import AppContext from '../../../../context'
3
4
 
@@ -23,7 +24,7 @@ export default function (server: Server, ctx: AppContext) {
23
24
  const creators = genList.map((gen) => gen.creator)
24
25
  const profiles = await actorService.views.profilesBasic(creators, viewer)
25
26
 
26
- const feedViews = genList.map((gen) =>
27
+ const feedViews = mapDefined(genList, (gen) =>
27
28
  feedService.views.formatFeedGeneratorView(gen, profiles),
28
29
  )
29
30
 
@@ -146,9 +146,7 @@ const noBlocksOrMutes = (state: HydrationState): HydrationState => {
146
146
  const presentation = (state: HydrationState, ctx: Context) => {
147
147
  const { feedService } = ctx
148
148
  const { feedItems, cursor, params } = state
149
- const feed = feedService.views.formatFeed(feedItems, state, {
150
- viewer: params.viewer,
151
- })
149
+ const feed = feedService.views.formatFeed(feedItems, state, params.viewer)
152
150
  return { feed, cursor }
153
151
  }
154
152
 
@@ -0,0 +1,130 @@
1
+ import AppContext from '../../../../context'
2
+ import { Server } from '../../../../lexicon'
3
+ import { InvalidRequestError } from '@atproto/xrpc-server'
4
+ import AtpAgent from '@atproto/api'
5
+ import { mapDefined } from '@atproto/common'
6
+ import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts'
7
+ import { Database } from '../../../../db'
8
+ import {
9
+ FeedHydrationState,
10
+ FeedRow,
11
+ FeedService,
12
+ } from '../../../../services/feed'
13
+ import { ActorService } from '../../../../services/actor'
14
+ import { createPipeline } from '../../../../pipeline'
15
+
16
+ export default function (server: Server, ctx: AppContext) {
17
+ const searchPosts = createPipeline(
18
+ skeleton,
19
+ hydration,
20
+ noBlocks,
21
+ presentation,
22
+ )
23
+ server.app.bsky.feed.searchPosts({
24
+ auth: ctx.authOptionalVerifier,
25
+ handler: async ({ auth, params }) => {
26
+ const viewer = auth.credentials.did
27
+ const db = ctx.db.getReplica('search')
28
+ const feedService = ctx.services.feed(db)
29
+ const actorService = ctx.services.actor(db)
30
+ const searchAgent = ctx.searchAgent
31
+ if (!searchAgent) {
32
+ throw new InvalidRequestError('Search not available')
33
+ }
34
+
35
+ const results = await searchPosts(
36
+ { ...params, viewer },
37
+ { db, feedService, actorService, searchAgent },
38
+ )
39
+
40
+ return {
41
+ encoding: 'application/json',
42
+ body: results,
43
+ }
44
+ },
45
+ })
46
+ }
47
+
48
+ const skeleton = async (
49
+ params: Params,
50
+ ctx: Context,
51
+ ): Promise<SkeletonState> => {
52
+ const res = await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton({
53
+ q: params.q,
54
+ cursor: params.cursor,
55
+ limit: params.limit,
56
+ })
57
+ const postUris = res.data.posts.map((a) => a.uri)
58
+ const feedItems = await ctx.feedService.postUrisToFeedItems(postUris)
59
+ return {
60
+ params,
61
+ feedItems,
62
+ cursor: res.data.cursor,
63
+ hitsTotal: res.data.hitsTotal,
64
+ }
65
+ }
66
+
67
+ const hydration = async (
68
+ state: SkeletonState,
69
+ ctx: Context,
70
+ ): Promise<HydrationState> => {
71
+ const { feedService } = ctx
72
+ const { params, feedItems } = state
73
+ const refs = feedService.feedItemRefs(feedItems)
74
+ const hydrated = await feedService.feedHydration({
75
+ ...refs,
76
+ viewer: params.viewer,
77
+ })
78
+ return { ...state, ...hydrated }
79
+ }
80
+
81
+ const noBlocks = (state: HydrationState): HydrationState => {
82
+ const { viewer } = state.params
83
+ state.feedItems = state.feedItems.filter((item) => {
84
+ if (!viewer) return true
85
+ return !state.bam.block([viewer, item.postAuthorDid])
86
+ })
87
+ return state
88
+ }
89
+
90
+ const presentation = (state: HydrationState, ctx: Context) => {
91
+ const { feedService, actorService } = ctx
92
+ const { feedItems, profiles, params } = state
93
+ const actors = actorService.views.profileBasicPresentation(
94
+ Object.keys(profiles),
95
+ state,
96
+ params.viewer,
97
+ )
98
+
99
+ const postViews = mapDefined(feedItems, (item) =>
100
+ feedService.views.formatPostView(
101
+ item.postUri,
102
+ actors,
103
+ state.posts,
104
+ state.threadgates,
105
+ state.embeds,
106
+ state.labels,
107
+ state.lists,
108
+ params.viewer,
109
+ ),
110
+ )
111
+ return { posts: postViews, cursor: state.cursor, hitsTotal: state.hitsTotal }
112
+ }
113
+
114
+ type Context = {
115
+ db: Database
116
+ feedService: FeedService
117
+ actorService: ActorService
118
+ searchAgent: AtpAgent
119
+ }
120
+
121
+ type Params = QueryParams & { viewer: string | null }
122
+
123
+ type SkeletonState = {
124
+ params: Params
125
+ feedItems: FeedRow[]
126
+ hitsTotal?: number
127
+ cursor?: string
128
+ }
129
+
130
+ type HydrationState = SkeletonState & FeedHydrationState
@@ -91,17 +91,20 @@ const presentation = (state: HydrationState, ctx: Context) => {
91
91
  const actors = actorService.views.profilePresentation(
92
92
  Object.keys(profileState.profiles),
93
93
  profileState,
94
- { viewer: params.viewer },
94
+ params.viewer,
95
95
  )
96
96
  const creator = actors[list.creator]
97
97
  if (!creator) {
98
98
  throw new InvalidRequestError(`Actor not found: ${list.handle}`)
99
99
  }
100
100
  const listView = graphService.formatListView(list, actors)
101
+ if (!listView) {
102
+ throw new InvalidRequestError('List not found')
103
+ }
101
104
  const items = mapDefined(listItems, (item) => {
102
105
  const subject = actors[item.did]
103
106
  if (!subject) return
104
- return { subject }
107
+ return { uri: item.uri, subject }
105
108
  })
106
109
  return { list: listView, items, cursor }
107
110
  }
@@ -119,7 +122,7 @@ type Params = QueryParams & {
119
122
  type SkeletonState = {
120
123
  params: Params
121
124
  list: Actor & ListInfo
122
- listItems: (Actor & { cid: string; sortAt: string })[]
125
+ listItems: (Actor & { uri: string; cid: string; sortAt: string })[]
123
126
  cursor?: string
124
127
  }
125
128
 
@@ -1,3 +1,4 @@
1
+ import { mapDefined } from '@atproto/common'
1
2
  import { Server } from '../../../../lexicon'
2
3
  import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListBlocks'
3
4
  import { paginate, TimeCidKeyset } from '../../../../db/pagination'
@@ -87,9 +88,9 @@ const presentation = (state: HydrationState, ctx: Context) => {
87
88
  const actors = actorService.views.profilePresentation(
88
89
  Object.keys(profileState.profiles),
89
90
  profileState,
90
- { viewer: params.viewer },
91
+ params.viewer,
91
92
  )
92
- const lists = listInfos.map((list) =>
93
+ const lists = mapDefined(listInfos, (list) =>
93
94
  graphService.formatListView(list, actors),
94
95
  )
95
96
  return { lists, cursor }
@@ -1,3 +1,4 @@
1
+ import { mapDefined } from '@atproto/common'
1
2
  import { Server } from '../../../../lexicon'
2
3
  import { paginate, TimeCidKeyset } from '../../../../db/pagination'
3
4
  import AppContext from '../../../../context'
@@ -34,7 +35,7 @@ export default function (server: Server, ctx: AppContext) {
34
35
  const actorService = ctx.services.actor(db)
35
36
  const profiles = await actorService.views.profiles(listsRes, requester)
36
37
 
37
- const lists = listsRes.map((row) =>
38
+ const lists = mapDefined(listsRes, (row) =>
38
39
  graphService.formatListView(row, profiles),
39
40
  )
40
41
 
@@ -1,3 +1,4 @@
1
+ import { mapDefined } from '@atproto/common'
1
2
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
3
  import { Server } from '../../../../lexicon'
3
4
  import { paginate, TimeCidKeyset } from '../../../../db/pagination'
@@ -39,7 +40,7 @@ export default function (server: Server, ctx: AppContext) {
39
40
  throw new InvalidRequestError(`Actor not found: ${actor}`)
40
41
  }
41
42
 
42
- const lists = listsRes.map((row) =>
43
+ const lists = mapDefined(listsRes, (row) =>
43
44
  graphService.formatListView(row, profiles),
44
45
  )
45
46
 
@@ -57,7 +57,9 @@ export default function (server: Server, ctx: AppContext) {
57
57
  const gen = genInfos[row.uri]
58
58
  if (!gen) continue
59
59
  const view = feedService.views.formatFeedGeneratorView(gen, profiles)
60
- genViews.push(view)
60
+ if (view) {
61
+ genViews.push(view)
62
+ }
61
63
  }
62
64
 
63
65
  return {
@@ -6,11 +6,11 @@ import { CID } from 'multiformats/cid'
6
6
  import { ensureValidDid } from '@atproto/syntax'
7
7
  import { forwardStreamErrors, VerifyCidTransform } from '@atproto/common'
8
8
  import { IdResolver, DidNotFoundError } from '@atproto/identity'
9
- import { TAKEDOWN } from '../lexicon/types/com/atproto/admin/defs'
10
9
  import AppContext from '../context'
11
10
  import { httpLogger as log } from '../logger'
12
11
  import { retryHttp } from '../util/retry'
13
12
  import { Database } from '../db'
13
+ import { sql } from 'kysely'
14
14
 
15
15
  // Resolve and verify blob from its origin host
16
16
 
@@ -84,19 +84,14 @@ export async function resolveBlob(
84
84
  idResolver: IdResolver,
85
85
  ) {
86
86
  const cidStr = cid.toString()
87
+
87
88
  const [{ pds }, takedown] = await Promise.all([
88
89
  idResolver.did.resolveAtprotoData(did), // @TODO cache did info
89
90
  db.db
90
- .selectFrom('moderation_action_subject_blob')
91
- .select('actionId')
92
- .innerJoin(
93
- 'moderation_action',
94
- 'moderation_action.id',
95
- 'moderation_action_subject_blob.actionId',
96
- )
97
- .where('cid', '=', cidStr)
98
- .where('action', '=', TAKEDOWN)
99
- .where('reversedAt', 'is', null)
91
+ .selectFrom('moderation_subject_status')
92
+ .select('id')
93
+ .where('blobCids', '@>', sql`CAST(${JSON.stringify([cidStr])} AS JSONB)`)
94
+ .where('takendown', 'is', true)
100
95
  .executeTakeFirst(),
101
96
  ])
102
97
  if (takedown) {