@atproto/bsky 0.0.224 → 0.0.225

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 (146) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/api/app/bsky/actor/getProfile.d.ts.map +1 -1
  3. package/dist/api/app/bsky/actor/getProfile.js +2 -1
  4. package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
  5. package/dist/api/app/bsky/actor/getProfiles.d.ts.map +1 -1
  6. package/dist/api/app/bsky/actor/getProfiles.js +2 -1
  7. package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
  8. package/dist/api/app/bsky/actor/searchActors.d.ts.map +1 -1
  9. package/dist/api/app/bsky/actor/searchActors.js +2 -1
  10. package/dist/api/app/bsky/actor/searchActors.js.map +1 -1
  11. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  12. package/dist/api/app/bsky/feed/getAuthorFeed.js +2 -1
  13. package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
  14. package/dist/api/app/bsky/feed/getLikes.d.ts.map +1 -1
  15. package/dist/api/app/bsky/feed/getLikes.js +2 -1
  16. package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
  17. package/dist/api/app/bsky/feed/getPostThread.d.ts.map +1 -1
  18. package/dist/api/app/bsky/feed/getPostThread.js +2 -1
  19. package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
  20. package/dist/api/app/bsky/feed/getQuotes.d.ts.map +1 -1
  21. package/dist/api/app/bsky/feed/getQuotes.js +2 -1
  22. package/dist/api/app/bsky/feed/getQuotes.js.map +1 -1
  23. package/dist/api/app/bsky/feed/getRepostedBy.d.ts.map +1 -1
  24. package/dist/api/app/bsky/feed/getRepostedBy.js +2 -1
  25. package/dist/api/app/bsky/feed/getRepostedBy.js.map +1 -1
  26. package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
  27. package/dist/api/app/bsky/feed/searchPosts.js +2 -1
  28. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  29. package/dist/api/app/bsky/graph/getActorStarterPacks.d.ts.map +1 -1
  30. package/dist/api/app/bsky/graph/getActorStarterPacks.js +2 -1
  31. package/dist/api/app/bsky/graph/getActorStarterPacks.js.map +1 -1
  32. package/dist/api/app/bsky/graph/getFollowers.d.ts.map +1 -1
  33. package/dist/api/app/bsky/graph/getFollowers.js +2 -1
  34. package/dist/api/app/bsky/graph/getFollowers.js.map +1 -1
  35. package/dist/api/app/bsky/graph/getFollows.d.ts.map +1 -1
  36. package/dist/api/app/bsky/graph/getFollows.js +2 -1
  37. package/dist/api/app/bsky/graph/getFollows.js.map +1 -1
  38. package/dist/api/app/bsky/graph/getList.d.ts.map +1 -1
  39. package/dist/api/app/bsky/graph/getList.js +2 -1
  40. package/dist/api/app/bsky/graph/getList.js.map +1 -1
  41. package/dist/api/app/bsky/graph/getLists.d.ts.map +1 -1
  42. package/dist/api/app/bsky/graph/getLists.js +2 -1
  43. package/dist/api/app/bsky/graph/getLists.js.map +1 -1
  44. package/dist/api/app/bsky/graph/getStarterPack.d.ts.map +1 -1
  45. package/dist/api/app/bsky/graph/getStarterPack.js +2 -1
  46. package/dist/api/app/bsky/graph/getStarterPack.js.map +1 -1
  47. package/dist/api/app/bsky/graph/getStarterPacks.d.ts.map +1 -1
  48. package/dist/api/app/bsky/graph/getStarterPacks.js +2 -1
  49. package/dist/api/app/bsky/graph/getStarterPacks.js.map +1 -1
  50. package/dist/api/app/bsky/graph/searchStarterPacks.d.ts.map +1 -1
  51. package/dist/api/app/bsky/graph/searchStarterPacks.js +2 -1
  52. package/dist/api/app/bsky/graph/searchStarterPacks.js.map +1 -1
  53. package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -1
  54. package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js +2 -1
  55. package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -1
  56. package/dist/api/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
  57. package/dist/api/app/bsky/unspecced/getPostThreadV2.js +2 -1
  58. package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  59. package/dist/auth-verifier.d.ts +1 -0
  60. package/dist/auth-verifier.d.ts.map +1 -1
  61. package/dist/auth-verifier.js +1 -0
  62. package/dist/auth-verifier.js.map +1 -1
  63. package/dist/data-plane/server/indexing/plugins/feed-generator.js +2 -1
  64. package/dist/data-plane/server/indexing/plugins/feed-generator.js.map +1 -1
  65. package/dist/data-plane/server/indexing/plugins/list.js +2 -1
  66. package/dist/data-plane/server/indexing/plugins/list.js.map +1 -1
  67. package/dist/data-plane/server/indexing/plugins/post.js +3 -3
  68. package/dist/data-plane/server/indexing/plugins/post.js.map +1 -1
  69. package/dist/data-plane/server/indexing/plugins/profile.js +3 -2
  70. package/dist/data-plane/server/indexing/plugins/profile.js.map +1 -1
  71. package/dist/hydration/hydrator.d.ts +2 -0
  72. package/dist/hydration/hydrator.d.ts.map +1 -1
  73. package/dist/hydration/hydrator.js +8 -0
  74. package/dist/hydration/hydrator.js.map +1 -1
  75. package/dist/hydration/util.d.ts +13 -7
  76. package/dist/hydration/util.d.ts.map +1 -1
  77. package/dist/hydration/util.js +30 -10
  78. package/dist/hydration/util.js.map +1 -1
  79. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +1 -0
  80. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  81. package/dist/lexicons/app/bsky/actor/defs.defs.js +1 -0
  82. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  83. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
  84. package/dist/lexicons/app/bsky/actor/profile.defs.js +2 -10
  85. package/dist/lexicons/app/bsky/actor/profile.defs.js.map +1 -1
  86. package/dist/lexicons/app/bsky/embed/external.defs.d.ts.map +1 -1
  87. package/dist/lexicons/app/bsky/embed/external.defs.js +1 -1
  88. package/dist/lexicons/app/bsky/embed/external.defs.js.map +1 -1
  89. package/dist/lexicons/app/bsky/embed/images.defs.d.ts +3 -0
  90. package/dist/lexicons/app/bsky/embed/images.defs.d.ts.map +1 -1
  91. package/dist/lexicons/app/bsky/embed/images.defs.js +1 -5
  92. package/dist/lexicons/app/bsky/embed/images.defs.js.map +1 -1
  93. package/dist/lexicons/app/bsky/embed/video.defs.d.ts.map +1 -1
  94. package/dist/lexicons/app/bsky/embed/video.defs.js +2 -6
  95. package/dist/lexicons/app/bsky/embed/video.defs.js.map +1 -1
  96. package/dist/lexicons/app/bsky/feed/generator.defs.d.ts.map +1 -1
  97. package/dist/lexicons/app/bsky/feed/generator.defs.js +1 -5
  98. package/dist/lexicons/app/bsky/feed/generator.defs.js.map +1 -1
  99. package/dist/lexicons/app/bsky/graph/list.defs.d.ts.map +1 -1
  100. package/dist/lexicons/app/bsky/graph/list.defs.js +1 -5
  101. package/dist/lexicons/app/bsky/graph/list.defs.js.map +1 -1
  102. package/dist/lexicons/app/bsky/video/defs.defs.js +1 -1
  103. package/dist/lexicons/app/bsky/video/defs.defs.js.map +1 -1
  104. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +2 -6
  105. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts.map +1 -1
  106. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js +1 -1
  107. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js.map +1 -1
  108. package/dist/views/index.d.ts.map +1 -1
  109. package/dist/views/index.js +13 -8
  110. package/dist/views/index.js.map +1 -1
  111. package/dist/views/util.d.ts +1 -2
  112. package/dist/views/util.d.ts.map +1 -1
  113. package/dist/views/util.js +1 -5
  114. package/dist/views/util.js.map +1 -1
  115. package/package.json +9 -9
  116. package/src/api/app/bsky/actor/getProfile.ts +3 -1
  117. package/src/api/app/bsky/actor/getProfiles.ts +3 -1
  118. package/src/api/app/bsky/actor/searchActors.ts +3 -1
  119. package/src/api/app/bsky/feed/getAuthorFeed.ts +3 -1
  120. package/src/api/app/bsky/feed/getLikes.ts +3 -1
  121. package/src/api/app/bsky/feed/getPostThread.ts +2 -1
  122. package/src/api/app/bsky/feed/getQuotes.ts +3 -1
  123. package/src/api/app/bsky/feed/getRepostedBy.ts +3 -1
  124. package/src/api/app/bsky/feed/searchPosts.ts +3 -1
  125. package/src/api/app/bsky/graph/getActorStarterPacks.ts +3 -1
  126. package/src/api/app/bsky/graph/getFollowers.ts +3 -1
  127. package/src/api/app/bsky/graph/getFollows.ts +3 -1
  128. package/src/api/app/bsky/graph/getList.ts +3 -1
  129. package/src/api/app/bsky/graph/getLists.ts +3 -1
  130. package/src/api/app/bsky/graph/getStarterPack.ts +3 -1
  131. package/src/api/app/bsky/graph/getStarterPacks.ts +3 -1
  132. package/src/api/app/bsky/graph/searchStarterPacks.ts +3 -1
  133. package/src/api/app/bsky/unspecced/getPostThreadOtherV2.ts +2 -1
  134. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +2 -1
  135. package/src/auth-verifier.ts +1 -0
  136. package/src/data-plane/server/indexing/plugins/feed-generator.ts +2 -2
  137. package/src/data-plane/server/indexing/plugins/list.ts +2 -2
  138. package/src/data-plane/server/indexing/plugins/post.ts +4 -4
  139. package/src/data-plane/server/indexing/plugins/profile.ts +3 -3
  140. package/src/hydration/hydrator.ts +6 -0
  141. package/src/hydration/util.ts +40 -21
  142. package/src/views/index.ts +19 -15
  143. package/src/views/util.ts +1 -5
  144. package/tests/views/__snapshots__/profile.test.ts.snap +3 -0
  145. package/tests/views/blocks.test.ts +69 -0
  146. package/tests/views/profile.test.ts +77 -0
@@ -2,19 +2,24 @@ import { Timestamp } from '@bufbuild/protobuf'
2
2
  import {
3
3
  AtUriString,
4
4
  Cid,
5
- Infer,
5
+ InferInput,
6
+ InferOutput,
6
7
  LexParseOptions,
7
8
  LexValue,
8
9
  RecordSchema,
9
10
  Schema,
10
11
  TypedLexMap,
11
12
  ValidateOptions,
12
- lexParse,
13
+ lexParseJsonBytes,
13
14
  parseCidSafe,
14
15
  } from '@atproto/lex'
15
16
  import { AtUri } from '@atproto/syntax'
16
17
  import { Record as RecordEntry } from '../proto/bsky_pb'
17
18
 
19
+ const PARSE_OPTIONS: LexParseOptions & ValidateOptions = {
20
+ strict: false,
21
+ }
22
+
18
23
  export class HydrationMap<K, T> extends Map<K, T | null> implements Merges {
19
24
  merge(map: HydrationMap<K, T>): this {
20
25
  for (const [key, val] of map) {
@@ -28,8 +33,8 @@ export interface Merges {
28
33
  merge<T extends this>(map: T): this
29
34
  }
30
35
 
31
- export type RecordInfo<T extends { $type: string }> = {
32
- record: T & TypedLexMap
36
+ export type RecordInfo<T extends TypedLexMap> = {
37
+ record: T
33
38
  cid: string
34
39
  sortedAt: Date
35
40
  indexedAt: Date
@@ -70,17 +75,32 @@ export function parseRecord<TSchema extends RecordSchema>(
70
75
  recordSchema: TSchema,
71
76
  recordEntry: RecordEntry,
72
77
  includeTakedowns: boolean,
73
- ): RecordInfo<Infer<TSchema>> | undefined {
78
+ ): RecordInfo<InferInput<TSchema>> | undefined {
74
79
  if (!includeTakedowns && recordEntry.takenDown) {
75
80
  return undefined
76
81
  }
77
82
 
78
83
  const cid = recordEntry.cid
79
- if (!cid) return
84
+ if (!cid) {
85
+ return undefined
86
+ }
87
+
88
+ if (recordEntry.record.byteLength === 0) {
89
+ return undefined
90
+ }
80
91
 
81
- const record = parseJsonBytes(recordSchema, recordEntry.record)
92
+ const record = lexParseJsonBytes(recordEntry.record, PARSE_OPTIONS)
82
93
  if (!record) {
83
- return
94
+ return undefined
95
+ }
96
+
97
+ // @NOTE We cannot use parse mode here. We must return the original to ensure
98
+ // that the caller gets the same data as what is stored in the PDS (in case of
99
+ // records). This is important because the receiver of the data should be able
100
+ // to compute the right record CID.
101
+
102
+ if (!recordSchema.$matches(record, PARSE_OPTIONS)) {
103
+ return undefined
84
104
  }
85
105
 
86
106
  return {
@@ -92,23 +112,22 @@ export function parseRecord<TSchema extends RecordSchema>(
92
112
  }
93
113
  }
94
114
 
115
+ /**
116
+ * Decodes binary data containing a JSON representation of a Lex value, and
117
+ * validates it against the provided schema, in parse mode (i.e., allowing
118
+ * coercion & defaults).
119
+ *
120
+ * Returns undefined if the input is empty (from dataplane's empty value
121
+ * convention), or if the validation fails.
122
+ */
95
123
  export const parseJsonBytes = <TSchema extends Schema<LexValue>>(
96
124
  schema: TSchema,
97
125
  bytes: Uint8Array | undefined,
98
- options: LexParseOptions & ValidateOptions = { strict: false },
99
- ): Infer<TSchema> | undefined => {
126
+ ): InferOutput<TSchema> | undefined => {
100
127
  if (!bytes || bytes.byteLength === 0) return undefined
101
-
102
- // @NOTE Buffer.from(bytes) creates a copy of the ArrayBuffer
103
- const jsonBuffer = Buffer.from(
104
- bytes.buffer,
105
- bytes.byteOffset,
106
- bytes.byteLength,
107
- )
108
- const jsonString = jsonBuffer.toString('utf8')
109
-
110
- const value = lexParse(jsonString, options)
111
- return schema.ifMatches(value, options)
128
+ const value = lexParseJsonBytes(bytes, PARSE_OPTIONS)
129
+ const result = schema.safeParse(value, PARSE_OPTIONS)
130
+ return result.success ? result.value : undefined
112
131
  }
113
132
 
114
133
  export const parseString = <T extends string | undefined>(
@@ -1,5 +1,11 @@
1
1
  import { HOUR, MINUTE, mapDefined } from '@atproto/common'
2
- import { $Typed, Un$Typed, Unknown$TypedObject, UriString } from '@atproto/lex'
2
+ import {
3
+ $Typed,
4
+ Un$Typed,
5
+ Unknown$TypedObject,
6
+ UriString,
7
+ getBlobCidString,
8
+ } from '@atproto/lex'
3
9
  import {
4
10
  AtUri,
5
11
  AtUriString,
@@ -104,12 +110,7 @@ import {
104
110
  isSelfLabelsType,
105
111
  isVideoEmbedType,
106
112
  } from './types.js'
107
- import {
108
- VideoUriBuilder,
109
- cidFromBlobJson,
110
- parsePostgate,
111
- parseThreadGate,
112
- } from './util'
113
+ import { VideoUriBuilder, parsePostgate, parseThreadGate } from './util'
113
114
 
114
115
  const notificationDeletedRecord =
115
116
  app.bsky.notification.defs.recordDeleted.$build({})
@@ -169,6 +170,7 @@ export class Views {
169
170
  }
170
171
 
171
172
  viewerBlockExists(did: DidString, state: HydrationState): boolean {
173
+ if (state.ctx?.skipViewerBlocks) return false
172
174
  const viewer = state.profileViewers?.get(did)
173
175
  if (!viewer) return false
174
176
  return !!(
@@ -277,7 +279,7 @@ export class Views {
277
279
  ? this.imgUriBuilder.getPresetUri(
278
280
  'banner',
279
281
  did,
280
- cidFromBlobJson(actor.profile.banner),
282
+ getBlobCidString(actor.profile.banner),
281
283
  )
282
284
  : undefined,
283
285
  followersCount: profileAggs?.followers ?? 0,
@@ -356,7 +358,7 @@ export class Views {
356
358
  ? this.imgUriBuilder.getPresetUri(
357
359
  'avatar',
358
360
  did,
359
- cidFromBlobJson(actor.profile.avatar),
361
+ getBlobCidString(actor.profile.avatar),
360
362
  )
361
363
  : undefined,
362
364
  // associated.feedgens and associated.lists info not necessarily included
@@ -576,6 +578,7 @@ export class Views {
576
578
  }
577
579
 
578
580
  const uri = AtUri.make(did, app.bsky.actor.status.$nsid, 'self').toString()
581
+ const labels = state.labels?.getBySubject(uri)
579
582
 
580
583
  const minDuration = 5 * MINUTE
581
584
  const maxDuration = 4 * HOUR
@@ -602,6 +605,7 @@ export class Views {
602
605
  record.embed && isExternalEmbedType(record.embed)
603
606
  ? this.externalEmbed(did, record.embed)
604
607
  : undefined,
608
+ labels,
605
609
  expiresAt,
606
610
  isActive,
607
611
  }
@@ -672,7 +676,7 @@ export class Views {
672
676
  ? this.imgUriBuilder.getPresetUri(
673
677
  'avatar',
674
678
  creator,
675
- cidFromBlobJson(list.record.avatar),
679
+ getBlobCidString(list.record.avatar),
676
680
  )
677
681
  : undefined,
678
682
  listItemCount: listAgg?.listItems ?? 0,
@@ -916,7 +920,7 @@ export class Views {
916
920
  ? this.imgUriBuilder.getPresetUri(
917
921
  'avatar',
918
922
  creatorDid,
919
- cidFromBlobJson(feedgen.record.avatar),
923
+ getBlobCidString(feedgen.record.avatar),
920
924
  )
921
925
  : undefined,
922
926
  likeCount: aggs?.likes ?? 0,
@@ -2076,12 +2080,12 @@ export class Views {
2076
2080
  thumb: this.imgUriBuilder.getPresetUri(
2077
2081
  'feed_thumbnail',
2078
2082
  did,
2079
- cidFromBlobJson(img.image),
2083
+ getBlobCidString(img.image),
2080
2084
  ),
2081
2085
  fullsize: this.imgUriBuilder.getPresetUri(
2082
2086
  'feed_fullsize',
2083
2087
  did,
2084
- cidFromBlobJson(img.image),
2088
+ getBlobCidString(img.image),
2085
2089
  ),
2086
2090
  alt: img.alt,
2087
2091
  aspectRatio: img.aspectRatio,
@@ -2092,7 +2096,7 @@ export class Views {
2092
2096
  }
2093
2097
 
2094
2098
  videoEmbed(did: DidString, embed: VideoEmbed): $Typed<VideoEmbedView> {
2095
- const cid = cidFromBlobJson(embed.video)
2099
+ const cid = getBlobCidString(embed.video)
2096
2100
  return app.bsky.embed.video.view.$build({
2097
2101
  cid,
2098
2102
  playlist: this.videoUriBuilder.playlist({ did, cid }),
@@ -2117,7 +2121,7 @@ export class Views {
2117
2121
  ? this.imgUriBuilder.getPresetUri(
2118
2122
  'feed_thumbnail',
2119
2123
  did,
2120
- cidFromBlobJson(thumb),
2124
+ getBlobCidString(thumb),
2121
2125
  )
2122
2126
  : undefined,
2123
2127
  },
package/src/views/util.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as util from 'node:util'
2
- import { AtUriString, BlobRef, DidString, UriString } from '@atproto/lex'
2
+ import { AtUriString, DidString, UriString } from '@atproto/lex'
3
3
  import {
4
4
  GateRecord,
5
5
  PostRecord,
@@ -59,10 +59,6 @@ type ParsedThreadGate = {
59
59
  allowListUris?: AtUriString[]
60
60
  }
61
61
 
62
- export const cidFromBlobJson = (json: BlobRef): string => {
63
- return json.ref.toString()
64
- }
65
-
66
62
  export const parsePostgate = ({
67
63
  gate,
68
64
  viewerDid,
@@ -722,6 +722,7 @@ Object {
722
722
  "expiresAt": "1970-01-01T00:00:00.000Z",
723
723
  "isActive": true,
724
724
  "isDisabled": false,
725
+ "labels": Array [],
725
726
  "record": Object {
726
727
  "$type": "app.bsky.actor.status",
727
728
  "createdAt": "1970-01-01T00:00:00.000Z",
@@ -755,6 +756,7 @@ Object {
755
756
  "expiresAt": "1970-01-01T00:00:00.000Z",
756
757
  "isActive": false,
757
758
  "isDisabled": false,
759
+ "labels": Array [],
758
760
  "record": Object {
759
761
  "$type": "app.bsky.actor.status",
760
762
  "createdAt": "1970-01-01T00:00:00.000Z",
@@ -788,6 +790,7 @@ Object {
788
790
  "expiresAt": "1970-01-01T00:00:00.000Z",
789
791
  "isActive": true,
790
792
  "isDisabled": true,
793
+ "labels": Array [],
791
794
  "record": Object {
792
795
  "$type": "app.bsky.actor.status",
793
796
  "createdAt": "1970-01-01T00:00:00.000Z",
@@ -817,4 +817,73 @@ describe('pds views with blocking', () => {
817
817
  expect(knownFollowers?.count).toBe(1)
818
818
  expect(knownFollowers?.followers).toHaveLength(0)
819
819
  })
820
+
821
+ describe('mod service sees through blocks', () => {
822
+ let modServiceDid: string
823
+
824
+ beforeAll(async () => {
825
+ modServiceDid = network.bsky.ctx.cfg.modServiceDid
826
+ // alice blocks the mod service
827
+ await pdsAgent.app.bsky.graph.block.create(
828
+ { repo: alice },
829
+ { createdAt: new Date().toISOString(), subject: modServiceDid },
830
+ sc.getHeaders(alice),
831
+ )
832
+ await network.processAll()
833
+ })
834
+
835
+ it('mod service viewer preserves block state on getProfile', async () => {
836
+ // alice blocked the mod service, mod service views alice's profile
837
+ const res = await agent.app.bsky.actor.getProfile(
838
+ { actor: alice },
839
+ {
840
+ headers: await network.serviceHeaders(
841
+ modServiceDid,
842
+ ids.AppBskyActorGetProfile,
843
+ ),
844
+ },
845
+ )
846
+ // block state is still present on the viewer
847
+ expect(res.data.viewer?.blockedBy).toBe(true)
848
+ expect(res.data.viewer?.blocking).toBeUndefined()
849
+ })
850
+
851
+ it('mod service sees through blocks on getPostThread', async () => {
852
+ // alice has posts; mod service should see them even though alice blocked mod service
853
+ const { data } = await agent.app.bsky.feed.getPostThread(
854
+ { depth: 1, uri: sc.posts[alice][0].ref.uriStr },
855
+ {
856
+ headers: await network.serviceHeaders(
857
+ modServiceDid,
858
+ ids.AppBskyFeedGetPostThread,
859
+ ),
860
+ },
861
+ )
862
+ assertIsThreadViewPost(data.thread)
863
+ expect(data.thread.post.uri).toBe(sc.posts[alice][0].ref.uriStr)
864
+ })
865
+
866
+ it('mod service sees through blocks on getPostThreadV2', async () => {
867
+ const { data } = await agent.app.bsky.unspecced.getPostThreadV2(
868
+ { anchor: sc.posts[alice][0].ref.uriStr },
869
+ {
870
+ headers: await network.serviceHeaders(
871
+ modServiceDid,
872
+ ids.AppBskyUnspeccedGetPostThreadV2,
873
+ ),
874
+ },
875
+ )
876
+ expect(data.thread).toEqual(
877
+ expect.arrayContaining([
878
+ expect.objectContaining({
879
+ uri: sc.posts[alice][0].ref.uriStr,
880
+ depth: 0,
881
+ value: expect.objectContaining({
882
+ $type: 'app.bsky.unspecced.defs#threadItemPost',
883
+ }),
884
+ }),
885
+ ]),
886
+ )
887
+ })
888
+ })
820
889
  })
@@ -16,6 +16,7 @@ describe('pds profile views', () => {
16
16
  let agent: AtpAgent
17
17
  let pdsAgent: AtpAgent
18
18
  let sc: SeedClient
19
+ let labelerDid: string
19
20
 
20
21
  // account dids, for convenience
21
22
  let alice: string
@@ -32,6 +33,7 @@ describe('pds profile views', () => {
32
33
  agent = network.bsky.getAgent()
33
34
  pdsAgent = network.pds.getAgent()
34
35
  sc = network.getSeedClient()
36
+ labelerDid = network.bsky.ctx.cfg.labelsFromIssuerDids[0]
35
37
  await basicSeed(sc)
36
38
 
37
39
  await sc.createAccount('eve', {
@@ -499,6 +501,60 @@ describe('pds profile views', () => {
499
501
  })
500
502
  })
501
503
 
504
+ describe('labeled', () => {
505
+ beforeAll(async () => {
506
+ const res = await sc.agent.com.atproto.repo.putRecord(
507
+ {
508
+ repo: alice,
509
+ collection: ids.AppBskyActorStatus,
510
+ rkey: 'self',
511
+ record: {
512
+ status: 'app.bsky.actor.status#live',
513
+ embed,
514
+ durationMinutes: 10,
515
+ createdAt: new Date().toISOString(),
516
+ },
517
+ },
518
+ {
519
+ headers: sc.getHeaders(alice),
520
+ encoding: 'application/json',
521
+ },
522
+ )
523
+ await network.processAll()
524
+
525
+ await createLabel({
526
+ src: labelerDid,
527
+ uri: res.data.uri,
528
+ cid: res.data.cid,
529
+ val: 'spam',
530
+ })
531
+ await network.processAll()
532
+ })
533
+
534
+ it('returns labels on statusView', async () => {
535
+ const { data } = await agent.api.app.bsky.actor.getProfile(
536
+ { actor: alice },
537
+ {
538
+ headers: {
539
+ 'atproto-accept-labelers': labelerDid,
540
+ ...(await network.serviceHeaders(
541
+ bob,
542
+ ids.AppBskyActorGetProfile,
543
+ )),
544
+ },
545
+ },
546
+ )
547
+
548
+ expect(data.status?.labels).toBeDefined()
549
+ expect(data.status?.labels?.length).toBe(1)
550
+ expect(data.status?.labels?.at(0)?.val).toBe('spam')
551
+ })
552
+ })
553
+
554
+ /*
555
+ * THIS ONE MUST BE LAST, since a takedown of a `self` rkey record prevents
556
+ * subsequent hydrations of that record.
557
+ */
502
558
  describe('when taken down', () => {
503
559
  beforeAll(async () => {
504
560
  const res = await sc.agent.com.atproto.repo.putRecord(
@@ -655,4 +711,25 @@ describe('pds profile views', () => {
655
711
  { headers: sc.getHeaders(did), encoding: 'application/json' },
656
712
  )
657
713
  }
714
+
715
+ const createLabel = async (opts: {
716
+ src?: string
717
+ uri: string
718
+ cid: string
719
+ val: string
720
+ exp?: string
721
+ }) => {
722
+ await network.bsky.db.db
723
+ .insertInto('label')
724
+ .values({
725
+ uri: opts.uri,
726
+ cid: opts.cid,
727
+ val: opts.val,
728
+ cts: new Date().toISOString(),
729
+ exp: opts.exp ?? null,
730
+ neg: false,
731
+ src: opts.src ?? labelerDid,
732
+ })
733
+ .execute()
734
+ }
658
735
  })