@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.
Files changed (170) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  3. package/dist/context.d.ts +15 -0
  4. package/dist/db/index.js +26 -1
  5. package/dist/db/index.js.map +3 -3
  6. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  7. package/dist/db/migrations/index.d.ts +1 -0
  8. package/dist/db/pagination.d.ts +2 -1
  9. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  10. package/dist/db/tables/moderation.d.ts +24 -34
  11. package/dist/feed-gen/types.d.ts +1 -1
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.js +2750 -2121
  14. package/dist/index.js.map +3 -3
  15. package/dist/lexicon/index.d.ts +11 -18
  16. package/dist/lexicon/lexicons.d.ts +414 -399
  17. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  18. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  19. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
  20. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  21. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  22. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  23. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  24. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  25. package/dist/migrate-moderation-data.d.ts +1 -0
  26. package/dist/services/actor/views.d.ts +2 -5
  27. package/dist/services/feed/index.d.ts +1 -0
  28. package/dist/services/feed/util.d.ts +9 -1
  29. package/dist/services/feed/views.d.ts +6 -17
  30. package/dist/services/graph/index.d.ts +5 -29
  31. package/dist/services/graph/types.d.ts +1 -0
  32. package/dist/services/moderation/index.d.ts +135 -72
  33. package/dist/services/moderation/pagination.d.ts +36 -0
  34. package/dist/services/moderation/status.d.ts +13 -0
  35. package/dist/services/moderation/types.d.ts +35 -0
  36. package/dist/services/moderation/views.d.ts +18 -14
  37. package/dist/util/debug.d.ts +1 -1
  38. package/package.json +11 -11
  39. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  40. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  41. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  42. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  43. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  44. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  45. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  46. package/src/api/app/bsky/feed/getPostThread.ts +15 -54
  47. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  48. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  49. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  50. package/src/api/app/bsky/feed/searchPosts.ts +20 -17
  51. package/src/api/app/bsky/graph/getList.ts +6 -3
  52. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  53. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  54. package/src/api/app/bsky/graph/getLists.ts +2 -1
  55. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  56. package/src/api/blob-resolver.ts +6 -11
  57. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  58. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  59. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  60. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  61. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  62. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  63. package/src/api/com/atproto/moderation/util.ts +38 -20
  64. package/src/api/index.ts +8 -14
  65. package/src/auth.ts +29 -21
  66. package/src/auto-moderator/index.ts +26 -19
  67. package/src/context.ts +4 -0
  68. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  69. package/src/db/migrations/index.ts +1 -0
  70. package/src/db/pagination.ts +26 -3
  71. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
  72. package/src/db/tables/moderation.ts +35 -52
  73. package/src/feed-gen/best-of-follows.ts +6 -3
  74. package/src/feed-gen/bsky-team.ts +1 -1
  75. package/src/feed-gen/hot-classic.ts +1 -1
  76. package/src/feed-gen/mutuals.ts +6 -2
  77. package/src/feed-gen/types.ts +1 -1
  78. package/src/feed-gen/whats-hot.ts +1 -1
  79. package/src/feed-gen/with-friends.ts +7 -3
  80. package/src/index.ts +2 -1
  81. package/src/lexicon/index.ts +30 -67
  82. package/src/lexicon/lexicons.ts +526 -491
  83. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  84. package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
  85. package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
  86. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  87. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  88. package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
  89. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
  90. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  91. package/src/migrate-moderation-data.ts +414 -0
  92. package/src/services/actor/views.ts +5 -14
  93. package/src/services/feed/index.ts +26 -7
  94. package/src/services/feed/util.ts +47 -19
  95. package/src/services/feed/views.ts +68 -4
  96. package/src/services/graph/index.ts +21 -3
  97. package/src/services/graph/types.ts +1 -0
  98. package/src/services/indexing/plugins/block.ts +2 -3
  99. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  100. package/src/services/indexing/plugins/follow.ts +2 -3
  101. package/src/services/indexing/plugins/like.ts +2 -3
  102. package/src/services/indexing/plugins/list-block.ts +2 -3
  103. package/src/services/indexing/plugins/list-item.ts +2 -3
  104. package/src/services/indexing/plugins/list.ts +2 -3
  105. package/src/services/indexing/plugins/post.ts +3 -4
  106. package/src/services/indexing/plugins/repost.ts +2 -3
  107. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  108. package/src/services/label/index.ts +2 -3
  109. package/src/services/moderation/index.ts +380 -395
  110. package/src/services/moderation/pagination.ts +96 -0
  111. package/src/services/moderation/status.ts +244 -0
  112. package/src/services/moderation/types.ts +49 -0
  113. package/src/services/moderation/views.ts +278 -329
  114. package/src/util/debug.ts +2 -2
  115. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  116. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  117. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  118. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  119. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  120. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  121. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  122. package/tests/admin/get-record.test.ts +5 -9
  123. package/tests/admin/get-repo.test.ts +5 -9
  124. package/tests/admin/moderation-events.test.ts +221 -0
  125. package/tests/admin/moderation-statuses.test.ts +145 -0
  126. package/tests/admin/moderation.test.ts +512 -860
  127. package/tests/admin/repo-search.test.ts +2 -3
  128. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  129. package/tests/auto-moderator/takedowns.test.ts +45 -18
  130. package/tests/feed-generation.test.ts +57 -9
  131. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  132. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  133. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  134. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  135. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  136. package/tests/views/actor-search.test.ts +2 -3
  137. package/tests/views/author-feed.test.ts +42 -36
  138. package/tests/views/follows.test.ts +40 -35
  139. package/tests/views/list-feed.test.ts +17 -9
  140. package/tests/views/notifications.test.ts +13 -9
  141. package/tests/views/profile.test.ts +20 -18
  142. package/tests/views/thread.test.ts +54 -26
  143. package/tests/views/threadgating.test.ts +51 -19
  144. package/tests/views/timeline.test.ts +21 -13
  145. package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
  146. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  147. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  148. package/dist/lexicon/types/com/atproto/admin/getModerationReport.d.ts +0 -29
  149. package/dist/lexicon/types/com/atproto/admin/resolveModerationReports.d.ts +0 -36
  150. package/dist/lexicon/types/com/atproto/admin/reverseModerationAction.d.ts +0 -36
  151. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  152. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  153. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  154. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  155. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  156. package/src/lexicon/types/com/atproto/admin/getModerationAction.ts +0 -41
  157. package/src/lexicon/types/com/atproto/admin/resolveModerationReports.ts +0 -49
  158. package/src/lexicon/types/com/atproto/admin/reverseModerationAction.ts +0 -49
  159. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  160. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  161. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  162. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  163. package/tests/admin/get-moderation-action.test.ts +0 -100
  164. package/tests/admin/get-moderation-actions.test.ts +0 -164
  165. package/tests/admin/get-moderation-report.test.ts +0 -100
  166. package/tests/admin/get-moderation-reports.test.ts +0 -332
  167. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  168. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  169. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  170. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
@@ -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
  }
@@ -317,28 +303,6 @@ const getChildrenData = (
317
303
  }))
318
304
  }
319
305
 
320
- const checkViewerCanReply = async (
321
- db: DatabaseSchema,
322
- anchor: PostInfo | null,
323
- viewer: string | null,
324
- owner: string,
325
- root: PostRecord | null,
326
- threadgate: ThreadgateRecord | null,
327
- ) => {
328
- if (!viewer) return false
329
- // @TODO re-enable invalidReplyRoot check
330
- // if (anchor?.invalidReplyRoot || anchor?.violatesThreadGate) return false
331
- if (anchor?.violatesThreadGate) return false
332
- const viewerViolatesThreadGate = await violatesThreadGate(
333
- db,
334
- viewer,
335
- owner,
336
- root,
337
- threadgate,
338
- )
339
- return !viewerViolatesThreadGate
340
- }
341
-
342
306
  class ParentNotFoundError extends Error {
343
307
  constructor(public uri: string) {
344
308
  super(`Parent not found: ${uri}`)
@@ -364,7 +328,4 @@ type SkeletonState = {
364
328
  threadData: PostThread
365
329
  }
366
330
 
367
- type HydrationState = SkeletonState &
368
- FeedHydrationState & {
369
- viewerCanReply: boolean
370
- }
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
 
@@ -2,11 +2,14 @@ import AppContext from '../../../../context'
2
2
  import { Server } from '../../../../lexicon'
3
3
  import { InvalidRequestError } from '@atproto/xrpc-server'
4
4
  import AtpAgent from '@atproto/api'
5
- import { AtUri } from '@atproto/syntax'
6
5
  import { mapDefined } from '@atproto/common'
7
6
  import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts'
8
7
  import { Database } from '../../../../db'
9
- import { FeedHydrationState, FeedService } from '../../../../services/feed'
8
+ import {
9
+ FeedHydrationState,
10
+ FeedRow,
11
+ FeedService,
12
+ } from '../../../../services/feed'
10
13
  import { ActorService } from '../../../../services/actor'
11
14
  import { createPipeline } from '../../../../pipeline'
12
15
 
@@ -51,9 +54,11 @@ const skeleton = async (
51
54
  cursor: params.cursor,
52
55
  limit: params.limit,
53
56
  })
57
+ const postUris = res.data.posts.map((a) => a.uri)
58
+ const feedItems = await ctx.feedService.postUrisToFeedItems(postUris)
54
59
  return {
55
60
  params,
56
- postUris: res.data.posts.map((a) => a.uri),
61
+ feedItems,
57
62
  cursor: res.data.cursor,
58
63
  hitsTotal: res.data.hitsTotal,
59
64
  }
@@ -64,12 +69,10 @@ const hydration = async (
64
69
  ctx: Context,
65
70
  ): Promise<HydrationState> => {
66
71
  const { feedService } = ctx
67
- const { params, postUris } = state
68
- const uris = new Set<string>(postUris)
69
- const dids = new Set<string>(postUris.map((uri) => new AtUri(uri).hostname))
72
+ const { params, feedItems } = state
73
+ const refs = feedService.feedItemRefs(feedItems)
70
74
  const hydrated = await feedService.feedHydration({
71
- uris,
72
- dids,
75
+ ...refs,
73
76
  viewer: params.viewer,
74
77
  })
75
78
  return { ...state, ...hydrated }
@@ -77,32 +80,32 @@ const hydration = async (
77
80
 
78
81
  const noBlocks = (state: HydrationState): HydrationState => {
79
82
  const { viewer } = state.params
80
- state.postUris = state.postUris.filter((uri) => {
81
- const post = state.posts[uri]
82
- if (!viewer || !post) return true
83
- return !state.bam.block([viewer, post.creator])
83
+ state.feedItems = state.feedItems.filter((item) => {
84
+ if (!viewer) return true
85
+ return !state.bam.block([viewer, item.postAuthorDid])
84
86
  })
85
87
  return state
86
88
  }
87
89
 
88
90
  const presentation = (state: HydrationState, ctx: Context) => {
89
91
  const { feedService, actorService } = ctx
90
- const { postUris, profiles, params } = state
92
+ const { feedItems, profiles, params } = state
91
93
  const actors = actorService.views.profileBasicPresentation(
92
94
  Object.keys(profiles),
93
95
  state,
94
- { viewer: params.viewer },
96
+ params.viewer,
95
97
  )
96
98
 
97
- const postViews = mapDefined(postUris, (uri) =>
99
+ const postViews = mapDefined(feedItems, (item) =>
98
100
  feedService.views.formatPostView(
99
- uri,
101
+ item.postUri,
100
102
  actors,
101
103
  state.posts,
102
104
  state.threadgates,
103
105
  state.embeds,
104
106
  state.labels,
105
107
  state.lists,
108
+ params.viewer,
106
109
  ),
107
110
  )
108
111
  return { posts: postViews, cursor: state.cursor, hitsTotal: state.hitsTotal }
@@ -119,7 +122,7 @@ type Params = QueryParams & { viewer: string | null }
119
122
 
120
123
  type SkeletonState = {
121
124
  params: Params
122
- postUris: string[]
125
+ feedItems: FeedRow[]
123
126
  hitsTotal?: number
124
127
  cursor?: string
125
128
  }
@@ -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) {