@atproto/bsky 0.0.37 → 0.0.39

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 (149) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts +2 -3
  3. package/dist/api/app/bsky/feed/getListFeed.d.ts +2 -2
  4. package/dist/api/app/bsky/feed/getTimeline.d.ts +4 -2
  5. package/dist/api/app/bsky/labeler/getServices.d.ts +3 -0
  6. package/dist/api/util.d.ts +9 -2
  7. package/dist/auth-verifier.d.ts +1 -1
  8. package/dist/context.d.ts +3 -0
  9. package/dist/data-plane/server/db/database-schema.d.ts +2 -2
  10. package/dist/data-plane/server/db/migrations/20240226T225725627Z-labelers.d.ts +3 -0
  11. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  12. package/dist/data-plane/server/db/tables/labeler.d.ts +13 -0
  13. package/dist/data-plane/server/indexing/index.d.ts +2 -0
  14. package/dist/data-plane/server/indexing/plugins/labeler.d.ts +10 -0
  15. package/dist/data-plane/server/util.d.ts +6 -6
  16. package/dist/hydration/actor.d.ts +3 -0
  17. package/dist/hydration/hydrator.d.ts +27 -22
  18. package/dist/hydration/label.d.ts +23 -9
  19. package/dist/index.js +4100 -4645
  20. package/dist/index.js.map +3 -3
  21. package/dist/lexicon/index.d.ts +7 -27
  22. package/dist/lexicon/lexicons.d.ts +516 -1463
  23. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
  24. package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
  25. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  26. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  27. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
  28. package/dist/lexicon/types/{com/atproto/admin/searchRepos.d.ts → app/bsky/labeler/getServices.d.ts} +7 -7
  29. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
  30. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
  31. package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -0
  32. package/dist/lexicon/types/com/atproto/server/describeServer.d.ts +7 -0
  33. package/dist/proto/bsky_connect.d.ts +7 -1
  34. package/dist/proto/bsky_pb.d.ts +25 -0
  35. package/dist/util.d.ts +7 -0
  36. package/dist/views/index.d.ts +3 -0
  37. package/dist/views/types.d.ts +2 -1
  38. package/package.json +14 -13
  39. package/proto/bsky.proto +12 -0
  40. package/src/api/app/bsky/actor/getProfile.ts +21 -17
  41. package/src/api/app/bsky/actor/getProfiles.ts +16 -7
  42. package/src/api/app/bsky/actor/getSuggestions.ts +18 -13
  43. package/src/api/app/bsky/actor/searchActors.ts +9 -5
  44. package/src/api/app/bsky/actor/searchActorsTypeahead.ts +12 -5
  45. package/src/api/app/bsky/feed/getActorFeeds.ts +16 -6
  46. package/src/api/app/bsky/feed/getActorLikes.ts +18 -8
  47. package/src/api/app/bsky/feed/getAuthorFeed.ts +18 -19
  48. package/src/api/app/bsky/feed/getFeed.ts +14 -7
  49. package/src/api/app/bsky/feed/getFeedGenerator.ts +8 -2
  50. package/src/api/app/bsky/feed/getFeedGenerators.ts +16 -5
  51. package/src/api/app/bsky/feed/getLikes.ts +13 -6
  52. package/src/api/app/bsky/feed/getListFeed.ts +13 -7
  53. package/src/api/app/bsky/feed/getPostThread.ts +15 -8
  54. package/src/api/app/bsky/feed/getPosts.ts +14 -5
  55. package/src/api/app/bsky/feed/getRepostedBy.ts +13 -6
  56. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +8 -2
  57. package/src/api/app/bsky/feed/getTimeline.ts +14 -8
  58. package/src/api/app/bsky/feed/searchPosts.ts +9 -5
  59. package/src/api/app/bsky/graph/getBlocks.ts +10 -9
  60. package/src/api/app/bsky/graph/getFollowers.ts +23 -15
  61. package/src/api/app/bsky/graph/getFollows.ts +23 -15
  62. package/src/api/app/bsky/graph/getList.ts +14 -8
  63. package/src/api/app/bsky/graph/getListBlocks.ts +10 -7
  64. package/src/api/app/bsky/graph/getListMutes.ts +10 -7
  65. package/src/api/app/bsky/graph/getLists.ts +9 -7
  66. package/src/api/app/bsky/graph/getMutes.ts +10 -8
  67. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +10 -7
  68. package/src/api/app/bsky/graph/muteActor.ts +1 -1
  69. package/src/api/app/bsky/labeler/getServices.ts +46 -0
  70. package/src/api/app/bsky/notification/listNotifications.ts +12 -8
  71. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +6 -3
  72. package/src/api/com/atproto/admin/getAccountInfos.ts +10 -3
  73. package/src/api/index.ts +2 -0
  74. package/src/api/util.ts +19 -4
  75. package/src/auth-verifier.ts +2 -2
  76. package/src/context.ts +20 -0
  77. package/src/data-plane/server/db/database-schema.ts +4 -4
  78. package/src/data-plane/server/db/migrations/20240226T225725627Z-labelers.ts +27 -0
  79. package/src/data-plane/server/db/migrations/index.ts +1 -0
  80. package/src/data-plane/server/db/tables/labeler.ts +16 -0
  81. package/src/data-plane/server/indexing/index.ts +4 -0
  82. package/src/data-plane/server/indexing/plugins/labeler.ts +77 -0
  83. package/src/data-plane/server/routes/interactions.ts +17 -1
  84. package/src/data-plane/server/routes/labels.ts +4 -2
  85. package/src/data-plane/server/routes/profile.ts +15 -1
  86. package/src/data-plane/server/routes/records.ts +1 -0
  87. package/src/hydration/actor.ts +6 -0
  88. package/src/hydration/hydrator.ts +171 -97
  89. package/src/hydration/label.ts +106 -20
  90. package/src/index.ts +1 -3
  91. package/src/lexicon/index.ts +22 -137
  92. package/src/lexicon/lexicons.ts +552 -1635
  93. package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
  94. package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
  95. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  96. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  97. package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
  98. package/src/lexicon/types/{com/atproto/admin/searchRepos.ts → app/bsky/labeler/getServices.ts} +8 -8
  99. package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
  100. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
  101. package/src/lexicon/types/com/atproto/label/defs.ts +78 -0
  102. package/src/lexicon/types/com/atproto/server/describeServer.ts +18 -0
  103. package/src/proto/bsky_connect.ts +11 -0
  104. package/src/proto/bsky_pb.ts +146 -0
  105. package/src/util.ts +44 -0
  106. package/src/views/index.ts +77 -8
  107. package/src/views/types.ts +6 -3
  108. package/tests/__snapshots__/feed-generation.test.ts.snap +12 -45
  109. package/tests/_util.ts +21 -0
  110. package/tests/data-plane/__snapshots__/indexing.test.ts.snap +20 -8
  111. package/tests/label-hydration.test.ts +162 -0
  112. package/tests/views/__snapshots__/author-feed.test.ts.snap +0 -46
  113. package/tests/views/__snapshots__/block-lists.test.ts.snap +7 -17
  114. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  115. package/tests/views/__snapshots__/labeler-service.test.ts.snap +156 -0
  116. package/tests/views/__snapshots__/list-feed.test.ts.snap +0 -20
  117. package/tests/views/__snapshots__/mute-lists.test.ts.snap +10 -18
  118. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -4
  119. package/tests/views/__snapshots__/notifications.test.ts.snap +0 -9
  120. package/tests/views/__snapshots__/posts.test.ts.snap +0 -7
  121. package/tests/views/__snapshots__/profile.test.ts.snap +40 -6
  122. package/tests/views/__snapshots__/thread.test.ts.snap +0 -38
  123. package/tests/views/__snapshots__/threadgating.test.ts.snap +2 -0
  124. package/tests/views/__snapshots__/timeline.test.ts.snap +0 -145
  125. package/tests/views/labeler-service.test.ts +156 -0
  126. package/tests/views/takedown-labels.test.ts +133 -0
  127. package/tests/views/timeline.test.ts +7 -2
  128. package/dist/data-plane/server/db/tables/moderation.d.ts +0 -42
  129. package/dist/lexicon/types/com/atproto/admin/createCommunicationTemplate.d.ts +0 -37
  130. package/dist/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.d.ts +0 -25
  131. package/dist/lexicon/types/com/atproto/admin/emitModerationEvent.d.ts +0 -45
  132. package/dist/lexicon/types/com/atproto/admin/getModerationEvent.d.ts +0 -29
  133. package/dist/lexicon/types/com/atproto/admin/getRecord.d.ts +0 -31
  134. package/dist/lexicon/types/com/atproto/admin/getRepo.d.ts +0 -30
  135. package/dist/lexicon/types/com/atproto/admin/listCommunicationTemplates.d.ts +0 -31
  136. package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +0 -48
  137. package/dist/lexicon/types/com/atproto/admin/queryModerationStatuses.d.ts +0 -50
  138. package/dist/lexicon/types/com/atproto/admin/updateCommunicationTemplate.d.ts +0 -39
  139. package/src/data-plane/server/db/tables/moderation.ts +0 -59
  140. package/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts +0 -54
  141. package/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts +0 -38
  142. package/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +0 -67
  143. package/src/lexicon/types/com/atproto/admin/getModerationEvent.ts +0 -41
  144. package/src/lexicon/types/com/atproto/admin/getRecord.ts +0 -43
  145. package/src/lexicon/types/com/atproto/admin/getRepo.ts +0 -42
  146. package/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts +0 -44
  147. package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +0 -73
  148. package/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +0 -74
  149. package/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts +0 -57
@@ -22,7 +22,13 @@ import {
22
22
  Lists,
23
23
  RelationshipPair,
24
24
  } from './graph'
25
- import { LabelHydrator, Labels } from './label'
25
+ import {
26
+ LabelHydrator,
27
+ LabelerAggs,
28
+ LabelerViewerStates,
29
+ Labelers,
30
+ Labels,
31
+ } from './label'
26
32
  import { HydrationMap, RecordInfo, didFromUri, urisByCollection } from './util'
27
33
  import {
28
34
  FeedGenAggs,
@@ -39,9 +45,16 @@ import {
39
45
  FeedItem,
40
46
  ItemRef,
41
47
  } from './feed'
48
+ import { ParsedLabelers } from '../util'
49
+
50
+ export type HydrateCtx = {
51
+ labelers: ParsedLabelers
52
+ viewer: string | null
53
+ includeTakedowns?: boolean
54
+ }
42
55
 
43
56
  export type HydrationState = {
44
- viewer?: string | null
57
+ ctx?: HydrateCtx
45
58
  actors?: Actors
46
59
  profileViewers?: ProfileViewerStates
47
60
  profileAggs?: ProfileAggs
@@ -61,6 +74,9 @@ export type HydrationState = {
61
74
  feedgens?: FeedGens
62
75
  feedgenViewers?: FeedGenViewerStates
63
76
  feedgenAggs?: FeedGenAggs
77
+ labelers?: Labelers
78
+ labelerViewers?: LabelerViewerStates
79
+ labelerAggs?: LabelerAggs
64
80
  }
65
81
 
66
82
  export type PostBlock = { embed: boolean; reply: boolean }
@@ -76,14 +92,11 @@ export class Hydrator {
76
92
  graph: GraphHydrator
77
93
  label: LabelHydrator
78
94
 
79
- constructor(
80
- public dataplane: DataPlaneClient,
81
- public opts?: { labelsFromIssuerDids?: string[] },
82
- ) {
95
+ constructor(public dataplane: DataPlaneClient) {
83
96
  this.actor = new ActorHydrator(dataplane)
84
97
  this.feed = new FeedHydrator(dataplane)
85
98
  this.graph = new GraphHydrator(dataplane)
86
- this.label = new LabelHydrator(dataplane, opts)
99
+ this.label = new LabelHydrator(dataplane)
87
100
  }
88
101
 
89
102
  // app.bsky.actor.defs#profileView
@@ -92,8 +105,10 @@ export class Hydrator {
92
105
  // Note: builds on the naive profile viewer hydrator and removes references to lists that have been deleted
93
106
  async hydrateProfileViewers(
94
107
  dids: string[],
95
- viewer: string,
108
+ ctx: HydrateCtx,
96
109
  ): Promise<HydrationState> {
110
+ const viewer = ctx.viewer
111
+ if (!viewer) return {}
97
112
  const profileViewers = await this.actor.getProfileViewerStatesNaive(
98
113
  dids,
99
114
  viewer,
@@ -102,14 +117,14 @@ export class Hydrator {
102
117
  profileViewers?.forEach((item) => {
103
118
  listUris.push(...listUrisFromProfileViewer(item))
104
119
  })
105
- const listState = await this.hydrateListsBasic(listUris, viewer)
120
+ const listState = await this.hydrateListsBasic(listUris, ctx)
106
121
  // if a list no longer exists or is not a mod list, then remove from viewer state
107
122
  profileViewers?.forEach((item) => {
108
123
  removeNonModListsFromProfileViewer(item, listState)
109
124
  })
110
125
  return mergeStates(listState, {
111
126
  profileViewers,
112
- viewer,
127
+ ctx,
113
128
  })
114
129
  }
115
130
 
@@ -118,18 +133,20 @@ export class Hydrator {
118
133
  // - list basic
119
134
  async hydrateProfiles(
120
135
  dids: string[],
121
- viewer: string | null,
122
- includeTakedowns = false,
136
+ ctx: HydrateCtx,
123
137
  ): Promise<HydrationState> {
124
138
  const [actors, labels, profileViewersState] = await Promise.all([
125
- this.actor.getActors(dids, includeTakedowns),
126
- this.label.getLabelsForSubjects(labelSubjectsForDid(dids)),
127
- viewer ? this.hydrateProfileViewers(dids, viewer) : undefined,
139
+ this.actor.getActors(dids, ctx.includeTakedowns),
140
+ this.label.getLabelsForSubjects(labelSubjectsForDid(dids), ctx.labelers),
141
+ this.hydrateProfileViewers(dids, ctx),
128
142
  ])
143
+ if (!ctx.includeTakedowns) {
144
+ actionTakedownLabels(dids, actors, labels)
145
+ }
129
146
  return mergeStates(profileViewersState ?? {}, {
130
147
  actors,
131
148
  labels,
132
- viewer,
149
+ ctx,
133
150
  })
134
151
  }
135
152
 
@@ -139,10 +156,9 @@ export class Hydrator {
139
156
  // - list basic
140
157
  async hydrateProfilesBasic(
141
158
  dids: string[],
142
- viewer: string | null,
143
- includeTakedowns = false,
159
+ ctx: HydrateCtx,
144
160
  ): Promise<HydrationState> {
145
- return this.hydrateProfiles(dids, viewer, includeTakedowns)
161
+ return this.hydrateProfiles(dids, ctx)
146
162
  }
147
163
 
148
164
  // app.bsky.actor.defs#profileViewDetailed
@@ -151,11 +167,10 @@ export class Hydrator {
151
167
  // - list basic
152
168
  async hydrateProfilesDetailed(
153
169
  dids: string[],
154
- viewer: string | null,
155
- includeTakedowns = false,
170
+ ctx: HydrateCtx,
156
171
  ): Promise<HydrationState> {
157
172
  const [state, profileAggs] = await Promise.all([
158
- this.hydrateProfiles(dids, viewer, includeTakedowns),
173
+ this.hydrateProfiles(dids, ctx),
159
174
  this.actor.getProfileAggregates(dids),
160
175
  ])
161
176
  return {
@@ -167,14 +182,12 @@ export class Hydrator {
167
182
  // app.bsky.graph.defs#listView
168
183
  // - list
169
184
  // - profile basic
170
- async hydrateLists(
171
- uris: string[],
172
- viewer: string | null,
173
- ): Promise<HydrationState> {
185
+ async hydrateLists(uris: string[], ctx: HydrateCtx): Promise<HydrationState> {
174
186
  const [listsState, profilesState] = await Promise.all([
175
- await this.hydrateListsBasic(uris, viewer),
176
- await this.hydrateProfilesBasic(uris.map(didFromUri), viewer),
187
+ await this.hydrateListsBasic(uris, ctx),
188
+ await this.hydrateProfilesBasic(uris.map(didFromUri), ctx),
177
189
  ])
190
+
178
191
  return mergeStates(listsState, profilesState)
179
192
  }
180
193
 
@@ -182,13 +195,19 @@ export class Hydrator {
182
195
  // - list basic
183
196
  async hydrateListsBasic(
184
197
  uris: string[],
185
- viewer: string | null,
198
+ ctx: HydrateCtx,
186
199
  ): Promise<HydrationState> {
187
- const [lists, listViewers] = await Promise.all([
200
+ const [lists, listViewers, labels] = await Promise.all([
188
201
  this.graph.getLists(uris),
189
- viewer ? this.graph.getListViewerStates(uris, viewer) : undefined,
202
+ ctx.viewer ? this.graph.getListViewerStates(uris, ctx.viewer) : undefined,
203
+ this.label.getLabelsForSubjects(uris, ctx.labelers),
190
204
  ])
191
- return { lists, listViewers, viewer }
205
+
206
+ if (!ctx.includeTakedowns) {
207
+ actionTakedownLabels(uris, lists, labels)
208
+ }
209
+
210
+ return { lists, listViewers, labels, ctx }
192
211
  }
193
212
 
194
213
  // app.bsky.graph.defs#listItemView
@@ -197,7 +216,7 @@ export class Hydrator {
197
216
  // - list basic
198
217
  async hydrateListItems(
199
218
  uris: string[],
200
- viewer: string | null,
219
+ ctx: HydrateCtx,
201
220
  ): Promise<HydrationState> {
202
221
  const listItems = await this.graph.getListItems(uris)
203
222
  const dids: string[] = []
@@ -206,8 +225,8 @@ export class Hydrator {
206
225
  dids.push(item.record.subject)
207
226
  }
208
227
  })
209
- const profileState = await this.hydrateProfiles(dids, viewer)
210
- return mergeStates(profileState, { listItems, viewer })
228
+ const profileState = await this.hydrateProfiles(dids, ctx)
229
+ return mergeStates(profileState, { listItems, ctx })
211
230
  }
212
231
 
213
232
  // app.bsky.feed.defs#postView
@@ -220,16 +239,18 @@ export class Hydrator {
220
239
  // - feedgen
221
240
  // - profile
222
241
  // - list basic
242
+ // - mod service
243
+ // - profile
244
+ // - list basic
223
245
  async hydratePosts(
224
246
  refs: ItemRef[],
225
- viewer: string | null,
226
- includeTakedowns = false,
247
+ ctx: HydrateCtx,
227
248
  state: HydrationState = {},
228
249
  ): Promise<HydrationState> {
229
250
  const uris = refs.map((ref) => ref.uri)
230
251
  const postsLayer0 = await this.feed.getPosts(
231
252
  uris,
232
- includeTakedowns,
253
+ ctx.includeTakedowns,
233
254
  state.posts,
234
255
  )
235
256
  // first level embeds plus thread roots we haven't fetched yet
@@ -239,7 +260,7 @@ export class Hydrator {
239
260
  const postUrisLayer1 = urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? []
240
261
  const postsLayer1 = await this.feed.getPosts(
241
262
  [...postUrisLayer1, ...additionalRootUris],
242
- includeTakedowns,
263
+ ctx.includeTakedowns,
243
264
  )
244
265
  // second level embeds, ignoring any additional root uris we mixed-in to the previous layer
245
266
  const urisLayer2 = nestedRecordUrisFromPosts(postsLayer1, postUrisLayer1)
@@ -252,7 +273,7 @@ export class Hydrator {
252
273
  }
253
274
  }
254
275
  const [postsLayer2, threadgates] = await Promise.all([
255
- this.feed.getPosts(postUrisLayer2, includeTakedowns),
276
+ this.feed.getPosts(postUrisLayer2, ctx.includeTakedowns),
256
277
  this.feed.getThreadgatesForPosts([...threadRootUris.values()]),
257
278
  ])
258
279
  // collect list/feedgen embeds, lists in threadgates, post record hydration
@@ -265,6 +286,10 @@ export class Hydrator {
265
286
  ...(urisLayer1ByCollection.get(ids.AppBskyFeedGenerator) ?? []),
266
287
  ...(urisLayer2ByCollection.get(ids.AppBskyFeedGenerator) ?? []),
267
288
  ]
289
+ const nestedLabelerDids = [
290
+ ...(urisLayer1ByCollection.get(ids.AppBskyLabelerService) ?? []),
291
+ ...(urisLayer2ByCollection.get(ids.AppBskyLabelerService) ?? []),
292
+ ].map((uri) => new AtUri(uri).hostname)
268
293
  const posts =
269
294
  mergeManyMaps(postsLayer0, postsLayer1, postsLayer2) ?? postsLayer0
270
295
  const allPostUris = [...posts.keys()]
@@ -276,29 +301,36 @@ export class Hydrator {
276
301
  profileState,
277
302
  listState,
278
303
  feedGenState,
304
+ labelerState,
279
305
  ] = await Promise.all([
280
306
  this.feed.getPostAggregates(refs),
281
- viewer ? this.feed.getPostViewerStates(refs, viewer) : undefined,
282
- this.label.getLabelsForSubjects(allPostUris),
307
+ ctx.viewer ? this.feed.getPostViewerStates(refs, ctx.viewer) : undefined,
308
+ this.label.getLabelsForSubjects(allPostUris, ctx.labelers),
283
309
  this.hydratePostBlocks(posts),
284
- this.hydrateProfiles(
285
- allPostUris.map(didFromUri),
286
- viewer,
287
- includeTakedowns,
288
- ),
289
- this.hydrateLists([...nestedListUris, ...gateListUris], viewer),
290
- this.hydrateFeedGens(nestedFeedGenUris, viewer),
310
+ this.hydrateProfiles(allPostUris.map(didFromUri), ctx),
311
+ this.hydrateLists([...nestedListUris, ...gateListUris], ctx),
312
+ this.hydrateFeedGens(nestedFeedGenUris, ctx),
313
+ this.hydrateLabelers(nestedLabelerDids, ctx),
291
314
  ])
315
+ if (!ctx.includeTakedowns) {
316
+ actionTakedownLabels(allPostUris, posts, labels)
317
+ }
292
318
  // combine all hydration state
293
- return mergeManyStates(profileState, listState, feedGenState, {
294
- posts,
295
- postAggs,
296
- postViewers,
297
- postBlocks,
298
- labels,
299
- threadgates,
300
- viewer,
301
- })
319
+ return mergeManyStates(
320
+ profileState,
321
+ listState,
322
+ feedGenState,
323
+ labelerState,
324
+ {
325
+ posts,
326
+ postAggs,
327
+ postViewers,
328
+ postBlocks,
329
+ labels,
330
+ threadgates,
331
+ ctx,
332
+ },
333
+ )
302
334
  }
303
335
 
304
336
  private async hydratePostBlocks(posts: Posts): Promise<PostBlocks> {
@@ -354,19 +386,14 @@ export class Hydrator {
354
386
  // - ...
355
387
  async hydrateFeedItems(
356
388
  items: FeedItem[],
357
- viewer: string | null,
358
- includeTakedowns = false,
389
+ ctx: HydrateCtx,
359
390
  ): Promise<HydrationState> {
360
391
  const postUris = items.map((item) => item.post.uri)
361
392
  const repostUris = mapDefined(items, (item) => item.repost?.uri)
362
393
  const [posts, reposts, repostProfileState] = await Promise.all([
363
- this.feed.getPosts(postUris, includeTakedowns),
364
- this.feed.getReposts(repostUris, includeTakedowns),
365
- this.hydrateProfiles(
366
- repostUris.map(didFromUri),
367
- viewer,
368
- includeTakedowns,
369
- ),
394
+ this.feed.getPosts(postUris, ctx.includeTakedowns),
395
+ this.feed.getReposts(repostUris, ctx.includeTakedowns),
396
+ this.hydrateProfiles(repostUris.map(didFromUri), ctx),
370
397
  ])
371
398
  const postAndReplyRefs: ItemRef[] = []
372
399
  posts.forEach((post, uri) => {
@@ -376,15 +403,10 @@ export class Hydrator {
376
403
  postAndReplyRefs.push(post.record.reply.root, post.record.reply.parent)
377
404
  }
378
405
  })
379
- const postState = await this.hydratePosts(
380
- postAndReplyRefs,
381
- viewer,
382
- includeTakedowns,
383
- { posts },
384
- )
406
+ const postState = await this.hydratePosts(postAndReplyRefs, ctx, { posts })
385
407
  return mergeManyStates(postState, repostProfileState, {
386
408
  reposts,
387
- viewer,
409
+ ctx,
388
410
  })
389
411
  }
390
412
 
@@ -400,9 +422,9 @@ export class Hydrator {
400
422
  // - list basic
401
423
  async hydrateThreadPosts(
402
424
  refs: ItemRef[],
403
- viewer: string | null,
425
+ ctx: HydrateCtx,
404
426
  ): Promise<HydrationState> {
405
- return this.hydratePosts(refs, viewer)
427
+ return this.hydratePosts(refs, ctx)
406
428
  }
407
429
 
408
430
  // app.bsky.feed.defs#generatorView
@@ -411,20 +433,27 @@ export class Hydrator {
411
433
  // - list basic
412
434
  async hydrateFeedGens(
413
435
  uris: string[], // @TODO any way to get refs here?
414
- viewer: string | null,
436
+ ctx: HydrateCtx,
415
437
  ): Promise<HydrationState> {
416
- const [feedgens, feedgenAggs, feedgenViewers, profileState] =
438
+ const [feedgens, feedgenAggs, feedgenViewers, profileState, labels] =
417
439
  await Promise.all([
418
- this.feed.getFeedGens(uris),
440
+ this.feed.getFeedGens(uris, ctx.includeTakedowns),
419
441
  this.feed.getFeedGenAggregates(uris.map((uri) => ({ uri }))),
420
- viewer ? this.feed.getFeedGenViewerStates(uris, viewer) : undefined,
421
- this.hydrateProfiles(uris.map(didFromUri), viewer),
442
+ ctx.viewer
443
+ ? this.feed.getFeedGenViewerStates(uris, ctx.viewer)
444
+ : undefined,
445
+ this.hydrateProfiles(uris.map(didFromUri), ctx),
446
+ this.label.getLabelsForSubjects(uris, ctx.labelers),
422
447
  ])
448
+ if (!ctx.includeTakedowns) {
449
+ actionTakedownLabels(uris, feedgens, labels)
450
+ }
423
451
  return mergeStates(profileState, {
424
452
  feedgens,
425
453
  feedgenAggs,
426
454
  feedgenViewers,
427
- viewer,
455
+ labels,
456
+ ctx,
428
457
  })
429
458
  }
430
459
 
@@ -432,27 +461,24 @@ export class Hydrator {
432
461
  // - like
433
462
  // - profile
434
463
  // - list basic
435
- async hydrateLikes(
436
- uris: string[],
437
- viewer: string | null,
438
- ): Promise<HydrationState> {
464
+ async hydrateLikes(uris: string[], ctx: HydrateCtx): Promise<HydrationState> {
439
465
  const [likes, profileState] = await Promise.all([
440
466
  this.feed.getLikes(uris),
441
- this.hydrateProfiles(uris.map(didFromUri), viewer),
467
+ this.hydrateProfiles(uris.map(didFromUri), ctx),
442
468
  ])
443
- return mergeStates(profileState, { likes, viewer })
469
+ return mergeStates(profileState, { likes, ctx })
444
470
  }
445
471
 
446
472
  // app.bsky.feed.getRepostedBy#repostedBy
447
473
  // - repost
448
474
  // - profile
449
475
  // - list basic
450
- async hydrateReposts(uris: string[], viewer: string | null) {
476
+ async hydrateReposts(uris: string[], ctx: HydrateCtx) {
451
477
  const [reposts, profileState] = await Promise.all([
452
478
  this.feed.getReposts(uris),
453
- this.hydrateProfiles(uris.map(didFromUri), viewer),
479
+ this.hydrateProfiles(uris.map(didFromUri), ctx),
454
480
  ])
455
- return mergeStates(profileState, { reposts, viewer })
481
+ return mergeStates(profileState, { reposts, ctx })
456
482
  }
457
483
 
458
484
  // app.bsky.notification.listNotifications#notification
@@ -461,7 +487,7 @@ export class Hydrator {
461
487
  // - list basic
462
488
  async hydrateNotifications(
463
489
  notifs: Notification[],
464
- viewer: string | null,
490
+ ctx: HydrateCtx,
465
491
  ): Promise<HydrationState> {
466
492
  const uris = notifs.map((notif) => notif.uri)
467
493
  const collections = urisByCollection(uris)
@@ -475,16 +501,17 @@ export class Hydrator {
475
501
  this.feed.getLikes(likeUris), // reason: like
476
502
  this.feed.getReposts(repostUris), // reason: repost
477
503
  this.graph.getFollows(followUris), // reason: follow
478
- this.label.getLabelsForSubjects(uris),
479
- this.hydrateProfiles(uris.map(didFromUri), viewer),
504
+ this.label.getLabelsForSubjects(uris, ctx.labelers),
505
+ this.hydrateProfiles(uris.map(didFromUri), ctx),
480
506
  ])
507
+ actionTakedownLabels(postUris, posts, labels)
481
508
  return mergeStates(profileState, {
482
509
  posts,
483
510
  likes,
484
511
  reposts,
485
512
  follows,
486
513
  labels,
487
- viewer,
514
+ ctx,
488
515
  })
489
516
  }
490
517
 
@@ -512,6 +539,31 @@ export class Hydrator {
512
539
  return { follows, followBlocks }
513
540
  }
514
541
 
542
+ // app.bsky.labeler.def#labelerViewDetailed
543
+ // - labeler
544
+ // - profile
545
+ // - list basic
546
+ async hydrateLabelers(
547
+ dids: string[],
548
+ ctx: HydrateCtx,
549
+ ): Promise<HydrationState> {
550
+ const [labelers, labelerAggs, labelerViewers, profileState] =
551
+ await Promise.all([
552
+ this.label.getLabelers(dids),
553
+ this.label.getLabelerAggregates(dids),
554
+ ctx.viewer
555
+ ? this.label.getLabelerViewerStates(dids, ctx.viewer)
556
+ : undefined,
557
+ this.hydrateProfiles(dids.map(didFromUri), ctx),
558
+ ])
559
+ return mergeStates(profileState, {
560
+ labelers,
561
+ labelerAggs,
562
+ labelerViewers,
563
+ ctx,
564
+ })
565
+ }
566
+
515
567
  // ad-hoc record hydration
516
568
  // in com.atproto.repo.getRecord
517
569
  async getRecord(
@@ -560,6 +612,11 @@ export class Hydrator {
560
612
  (await this.feed.getFeedGens([uri], includeTakedowns)).get(uri) ??
561
613
  undefined
562
614
  )
615
+ } else if (collection === ids.AppBskyLabelerService) {
616
+ return (
617
+ (await this.label.getLabelers([uri], includeTakedowns)).get(uri) ??
618
+ undefined
619
+ )
563
620
  } else if (collection === ids.AppBskyActorProfile) {
564
621
  const did = parsed.hostname
565
622
  const actor = (await this.actor.getActors([did], includeTakedowns)).get(
@@ -681,11 +738,13 @@ export const mergeStates = (
681
738
  stateB: HydrationState,
682
739
  ): HydrationState => {
683
740
  assert(
684
- !stateA.viewer || !stateB.viewer || stateA.viewer === stateB.viewer,
741
+ !stateA.ctx?.viewer ||
742
+ !stateB.ctx?.viewer ||
743
+ stateA.ctx?.viewer === stateB.ctx?.viewer,
685
744
  'incompatible viewers',
686
745
  )
687
746
  return {
688
- viewer: stateA.viewer ?? stateB.viewer,
747
+ ctx: stateA.ctx ?? stateB.ctx,
689
748
  actors: mergeMaps(stateA.actors, stateB.actors),
690
749
  profileAggs: mergeMaps(stateA.profileAggs, stateB.profileAggs),
691
750
  profileViewers: mergeMaps(stateA.profileViewers, stateB.profileViewers),
@@ -705,6 +764,9 @@ export const mergeStates = (
705
764
  feedgens: mergeMaps(stateA.feedgens, stateB.feedgens),
706
765
  feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs),
707
766
  feedgenViewers: mergeMaps(stateA.feedgenViewers, stateB.feedgenViewers),
767
+ labelers: mergeMaps(stateA.labelers, stateB.labelers),
768
+ labelerAggs: mergeMaps(stateA.labelerAggs, stateB.labelerAggs),
769
+ labelerViewers: mergeMaps(stateA.labelerViewers, stateB.labelerViewers),
708
770
  }
709
771
  }
710
772
 
@@ -724,3 +786,15 @@ const mergeManyStates = (...states: HydrationState[]) => {
724
786
  const mergeManyMaps = <T>(...maps: HydrationMap<T>[]) => {
725
787
  return maps.reduce(mergeMaps, undefined as HydrationMap<T> | undefined)
726
788
  }
789
+
790
+ const actionTakedownLabels = <T>(
791
+ keys: string[],
792
+ hydrationMap: HydrationMap<T>,
793
+ labels: Labels,
794
+ ) => {
795
+ for (const key of keys) {
796
+ if (labels.get(key)?.isTakendown) {
797
+ hydrationMap.set(key, null)
798
+ }
799
+ }
800
+ }
@@ -1,36 +1,122 @@
1
1
  import { DataPlaneClient } from '../data-plane/client'
2
2
  import { Label } from '../lexicon/types/com/atproto/label/defs'
3
- import { HydrationMap, parseJsonBytes } from './util'
3
+ import { Record as LabelerRecord } from '../lexicon/types/app/bsky/labeler/service'
4
+ import {
5
+ HydrationMap,
6
+ RecordInfo,
7
+ parseJsonBytes,
8
+ parseRecord,
9
+ parseString,
10
+ } from './util'
11
+ import { AtUri } from '@atproto/syntax'
12
+ import { ids } from '../lexicon/lexicons'
13
+ import { ParsedLabelers } from '../util'
4
14
 
5
15
  export type { Label } from '../lexicon/types/com/atproto/label/defs'
6
16
 
7
- export type Labels = HydrationMap<Label[]>
17
+ export type SubjectLabels = {
18
+ isTakendown: boolean
19
+ labels: Label[]
20
+ }
21
+
22
+ export type Labels = HydrationMap<SubjectLabels>
23
+
24
+ export type LabelerAgg = {
25
+ likes: number
26
+ }
27
+
28
+ export type LabelerAggs = HydrationMap<LabelerAgg>
29
+
30
+ export type Labeler = RecordInfo<LabelerRecord>
31
+ export type Labelers = HydrationMap<Labeler>
32
+
33
+ export type LabelerViewerState = {
34
+ like?: string
35
+ }
36
+
37
+ export type LabelerViewerStates = HydrationMap<LabelerViewerState>
8
38
 
9
39
  export class LabelHydrator {
10
- constructor(
11
- public dataplane: DataPlaneClient,
12
- public opts?: { labelsFromIssuerDids?: string[] },
13
- ) {}
40
+ constructor(public dataplane: DataPlaneClient) {}
14
41
 
15
42
  async getLabelsForSubjects(
16
43
  subjects: string[],
17
- issuers?: string[],
44
+ labelers: ParsedLabelers,
18
45
  ): Promise<Labels> {
19
- issuers = ([] as string[])
20
- .concat(issuers ?? [])
21
- .concat(this.opts?.labelsFromIssuerDids ?? [])
22
- if (!subjects.length || !issuers.length) return new HydrationMap<Label[]>()
23
- const res = await this.dataplane.getLabels({ subjects, issuers })
46
+ if (!subjects.length || !labelers.dids.length)
47
+ return new HydrationMap<SubjectLabels>()
48
+ const res = await this.dataplane.getLabels({
49
+ subjects,
50
+ issuers: labelers.dids,
51
+ })
24
52
  return res.labels.reduce((acc, cur) => {
25
- const label = parseJsonBytes(cur) as Label | undefined
26
- if (!label || label.neg) return acc
27
- const entry = acc.get(label.uri)
28
- if (entry) {
29
- entry.push(label)
30
- } else {
31
- acc.set(label.uri, [label])
53
+ const parsed = parseJsonBytes(cur) as Label | undefined
54
+ if (!parsed || parsed.neg) return acc
55
+ const { sig: _, ...label } = parsed
56
+ let entry = acc.get(label.uri)
57
+ if (!entry) {
58
+ entry = {
59
+ isTakendown: false,
60
+ labels: [],
61
+ }
62
+ acc.set(label.uri, entry)
63
+ }
64
+ entry.labels.push(label)
65
+ if (
66
+ TAKEDOWN_LABELS.includes(label.val) &&
67
+ !label.neg &&
68
+ labelers.redact.has(label.src)
69
+ ) {
70
+ entry.isTakendown = true
32
71
  }
33
72
  return acc
34
- }, new HydrationMap<Label[]>())
73
+ }, new HydrationMap<SubjectLabels>())
35
74
  }
75
+
76
+ async getLabelers(
77
+ dids: string[],
78
+ includeTakedowns = false,
79
+ ): Promise<Labelers> {
80
+ const res = await this.dataplane.getLabelerRecords({
81
+ uris: dids.map(labelerDidToUri),
82
+ })
83
+ return dids.reduce((acc, did, i) => {
84
+ const record = parseRecord<LabelerRecord>(
85
+ res.records[i],
86
+ includeTakedowns,
87
+ )
88
+ return acc.set(did, record ?? null)
89
+ }, new HydrationMap<Labeler>())
90
+ }
91
+
92
+ async getLabelerViewerStates(
93
+ dids: string[],
94
+ viewer: string,
95
+ ): Promise<LabelerViewerStates> {
96
+ const likes = await this.dataplane.getLikesByActorAndSubjects({
97
+ actorDid: viewer,
98
+ refs: dids.map((did) => ({ uri: labelerDidToUri(did) })),
99
+ })
100
+ return dids.reduce((acc, did, i) => {
101
+ return acc.set(did, {
102
+ like: parseString(likes.uris[i]),
103
+ })
104
+ }, new HydrationMap<LabelerViewerState>())
105
+ }
106
+
107
+ async getLabelerAggregates(dids: string[]): Promise<LabelerAggs> {
108
+ const refs = dids.map((did) => ({ uri: labelerDidToUri(did) }))
109
+ const counts = await this.dataplane.getInteractionCounts({ refs })
110
+ return dids.reduce((acc, did, i) => {
111
+ return acc.set(did, {
112
+ likes: counts.likes[i] ?? 0,
113
+ })
114
+ }, new HydrationMap<LabelerAgg>())
115
+ }
116
+ }
117
+
118
+ const labelerDidToUri = (did: string): string => {
119
+ return AtUri.make(did, ids.AppBskyLabelerService, 'self').toString()
36
120
  }
121
+
122
+ const TAKEDOWN_LABELS = ['!takedown', '!suspend']
package/src/index.ts CHANGED
@@ -77,9 +77,7 @@ export class BskyAppView {
77
77
  httpVersion: config.dataplaneHttpVersion,
78
78
  rejectUnauthorized: !config.dataplaneIgnoreBadTls,
79
79
  })
80
- const hydrator = new Hydrator(dataplane, {
81
- labelsFromIssuerDids: config.labelsFromIssuerDids,
82
- })
80
+ const hydrator = new Hydrator(dataplane)
83
81
  const views = new Views(imgUriBuilder)
84
82
 
85
83
  const bsyncClient = createBsyncClient({