@atproto/bsky 0.0.93 → 0.0.95

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 (120) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
  3. package/dist/api/app/bsky/feed/getFeed.js +5 -1
  4. package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
  5. package/dist/api/app/bsky/feed/getPostThread.d.ts.map +1 -1
  6. package/dist/api/app/bsky/feed/getPostThread.js +9 -2
  7. package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
  8. package/dist/api/app/bsky/feed/getQuotes.d.ts.map +1 -1
  9. package/dist/api/app/bsky/feed/getQuotes.js +5 -4
  10. package/dist/api/app/bsky/feed/getQuotes.js.map +1 -1
  11. package/dist/api/app/bsky/graph/searchStarterPacks.d.ts +4 -0
  12. package/dist/api/app/bsky/graph/searchStarterPacks.d.ts.map +1 -0
  13. package/dist/api/app/bsky/graph/searchStarterPacks.js +76 -0
  14. package/dist/api/app/bsky/graph/searchStarterPacks.js.map +1 -0
  15. package/dist/api/app/bsky/notification/listNotifications.js +12 -4
  16. package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
  17. package/dist/api/index.d.ts.map +1 -1
  18. package/dist/api/index.js +2 -0
  19. package/dist/api/index.js.map +1 -1
  20. package/dist/config.d.ts +6 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +19 -0
  23. package/dist/config.js.map +1 -1
  24. package/dist/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.d.ts +4 -0
  25. package/dist/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.d.ts.map +1 -0
  26. package/dist/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.js +21 -0
  27. package/dist/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.js.map +1 -0
  28. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  29. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  30. package/dist/data-plane/server/db/migrations/index.js +2 -1
  31. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  32. package/dist/data-plane/server/db/tables/starter-pack.d.ts +1 -0
  33. package/dist/data-plane/server/db/tables/starter-pack.d.ts.map +1 -1
  34. package/dist/data-plane/server/indexing/plugins/starter-pack.d.ts.map +1 -1
  35. package/dist/data-plane/server/indexing/plugins/starter-pack.js +1 -0
  36. package/dist/data-plane/server/indexing/plugins/starter-pack.js.map +1 -1
  37. package/dist/data-plane/server/routes/search.d.ts.map +1 -1
  38. package/dist/data-plane/server/routes/search.js +21 -0
  39. package/dist/data-plane/server/routes/search.js.map +1 -1
  40. package/dist/hydration/actor.d.ts.map +1 -1
  41. package/dist/hydration/actor.js +6 -2
  42. package/dist/hydration/actor.js.map +1 -1
  43. package/dist/hydration/label.d.ts +1 -0
  44. package/dist/hydration/label.d.ts.map +1 -1
  45. package/dist/hydration/label.js +7 -0
  46. package/dist/hydration/label.js.map +1 -1
  47. package/dist/lexicon/index.d.ts +6 -0
  48. package/dist/lexicon/index.d.ts.map +1 -1
  49. package/dist/lexicon/index.js +12 -0
  50. package/dist/lexicon/index.js.map +1 -1
  51. package/dist/lexicon/lexicons.d.ts +17818 -7726
  52. package/dist/lexicon/lexicons.d.ts.map +1 -1
  53. package/dist/lexicon/lexicons.js +156 -0
  54. package/dist/lexicon/lexicons.js.map +1 -1
  55. package/dist/lexicon/types/app/bsky/graph/searchStarterPacks.d.ts +40 -0
  56. package/dist/lexicon/types/app/bsky/graph/searchStarterPacks.d.ts.map +1 -0
  57. package/dist/lexicon/types/app/bsky/graph/searchStarterPacks.js +3 -0
  58. package/dist/lexicon/types/app/bsky/graph/searchStarterPacks.js.map +1 -0
  59. package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts +6 -0
  60. package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts.map +1 -1
  61. package/dist/lexicon/types/app/bsky/unspecced/defs.js +10 -0
  62. package/dist/lexicon/types/app/bsky/unspecced/defs.js.map +1 -1
  63. package/dist/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.d.ts +46 -0
  64. package/dist/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.d.ts.map +1 -0
  65. package/dist/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.js +3 -0
  66. package/dist/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.js.map +1 -0
  67. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +1 -0
  68. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
  69. package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
  70. package/dist/lexicon/types/com/atproto/temp/addReservedHandle.d.ts +39 -0
  71. package/dist/lexicon/types/com/atproto/temp/addReservedHandle.d.ts.map +1 -0
  72. package/dist/lexicon/types/com/atproto/temp/addReservedHandle.js +3 -0
  73. package/dist/lexicon/types/com/atproto/temp/addReservedHandle.js.map +1 -0
  74. package/dist/proto/bsky_connect.d.ts +10 -1
  75. package/dist/proto/bsky_connect.d.ts.map +1 -1
  76. package/dist/proto/bsky_connect.js +9 -0
  77. package/dist/proto/bsky_connect.js.map +1 -1
  78. package/dist/proto/bsky_pb.d.ts +49 -0
  79. package/dist/proto/bsky_pb.d.ts.map +1 -1
  80. package/dist/proto/bsky_pb.js +144 -3
  81. package/dist/proto/bsky_pb.js.map +1 -1
  82. package/dist/views/index.d.ts +1 -0
  83. package/dist/views/index.d.ts.map +1 -1
  84. package/dist/views/index.js +9 -0
  85. package/dist/views/index.js.map +1 -1
  86. package/package.json +11 -11
  87. package/proto/bsky.proto +14 -0
  88. package/src/api/app/bsky/feed/getFeed.ts +8 -1
  89. package/src/api/app/bsky/feed/getPostThread.ts +13 -7
  90. package/src/api/app/bsky/feed/getQuotes.ts +12 -5
  91. package/src/api/app/bsky/graph/searchStarterPacks.ts +118 -0
  92. package/src/api/app/bsky/notification/listNotifications.ts +14 -4
  93. package/src/api/index.ts +2 -0
  94. package/src/config.ts +26 -0
  95. package/src/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.ts +19 -0
  96. package/src/data-plane/server/db/migrations/index.ts +1 -0
  97. package/src/data-plane/server/db/tables/starter-pack.ts +1 -0
  98. package/src/data-plane/server/indexing/plugins/starter-pack.ts +1 -0
  99. package/src/data-plane/server/routes/search.ts +30 -0
  100. package/src/hydration/actor.ts +11 -5
  101. package/src/hydration/label.ts +10 -0
  102. package/src/lexicon/index.ts +36 -0
  103. package/src/lexicon/lexicons.ts +166 -2
  104. package/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts +50 -0
  105. package/src/lexicon/types/app/bsky/unspecced/defs.ts +24 -0
  106. package/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts +56 -0
  107. package/src/lexicon/types/chat/bsky/convo/defs.ts +1 -0
  108. package/src/lexicon/types/com/atproto/temp/addReservedHandle.ts +48 -0
  109. package/src/proto/bsky_connect.ts +11 -0
  110. package/src/proto/bsky_pb.ts +137 -0
  111. package/src/views/index.ts +12 -0
  112. package/tests/__snapshots__/feed-generation.test.ts.snap +49 -7
  113. package/tests/feed-generation.test.ts +82 -28
  114. package/tests/query-labels.test.ts +1 -1
  115. package/tests/views/known-followers.test.ts +1 -1
  116. package/tests/views/labels-needs-review.test.ts +168 -0
  117. package/tests/views/starter-packs.test.ts +62 -0
  118. package/tsconfig.build.tsbuildinfo +1 -1
  119. package/tsconfig.tests.tsbuildinfo +1 -1
  120. /package/tests/views/{takedown-labels.test.ts → labels-takedown.test.ts} +0 -0
@@ -176,6 +176,8 @@ import {
176
176
  SearchFeedGeneratorsResponse,
177
177
  SearchPostsRequest,
178
178
  SearchPostsResponse,
179
+ SearchStarterPacksRequest,
180
+ SearchStarterPacksResponse,
179
181
  TakedownActorRequest,
180
182
  TakedownActorResponse,
181
183
  TakedownBlobRequest,
@@ -819,6 +821,15 @@ export const Service = {
819
821
  O: SearchPostsResponse,
820
822
  kind: MethodKind.Unary,
821
823
  },
824
+ /**
825
+ * @generated from rpc bsky.Service.SearchStarterPacks
826
+ */
827
+ searchStarterPacks: {
828
+ name: 'SearchStarterPacks',
829
+ I: SearchStarterPacksRequest,
830
+ O: SearchStarterPacksResponse,
831
+ kind: MethodKind.Unary,
832
+ },
822
833
  /**
823
834
  * Suggestions
824
835
  *
@@ -9445,6 +9445,143 @@ export class SearchPostsResponse extends Message<SearchPostsResponse> {
9445
9445
  }
9446
9446
  }
9447
9447
 
9448
+ /**
9449
+ * - Return uris of starter packs matching term, paginated
9450
+ * - `searchStarterPacks` skeleton
9451
+ *
9452
+ * @generated from message bsky.SearchStarterPacksRequest
9453
+ */
9454
+ export class SearchStarterPacksRequest extends Message<SearchStarterPacksRequest> {
9455
+ /**
9456
+ * @generated from field: string term = 1;
9457
+ */
9458
+ term = ''
9459
+
9460
+ /**
9461
+ * @generated from field: int32 limit = 2;
9462
+ */
9463
+ limit = 0
9464
+
9465
+ /**
9466
+ * @generated from field: string cursor = 3;
9467
+ */
9468
+ cursor = ''
9469
+
9470
+ constructor(data?: PartialMessage<SearchStarterPacksRequest>) {
9471
+ super()
9472
+ proto3.util.initPartial(data, this)
9473
+ }
9474
+
9475
+ static readonly runtime: typeof proto3 = proto3
9476
+ static readonly typeName = 'bsky.SearchStarterPacksRequest'
9477
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
9478
+ { no: 1, name: 'term', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
9479
+ { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ },
9480
+ { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
9481
+ ])
9482
+
9483
+ static fromBinary(
9484
+ bytes: Uint8Array,
9485
+ options?: Partial<BinaryReadOptions>,
9486
+ ): SearchStarterPacksRequest {
9487
+ return new SearchStarterPacksRequest().fromBinary(bytes, options)
9488
+ }
9489
+
9490
+ static fromJson(
9491
+ jsonValue: JsonValue,
9492
+ options?: Partial<JsonReadOptions>,
9493
+ ): SearchStarterPacksRequest {
9494
+ return new SearchStarterPacksRequest().fromJson(jsonValue, options)
9495
+ }
9496
+
9497
+ static fromJsonString(
9498
+ jsonString: string,
9499
+ options?: Partial<JsonReadOptions>,
9500
+ ): SearchStarterPacksRequest {
9501
+ return new SearchStarterPacksRequest().fromJsonString(jsonString, options)
9502
+ }
9503
+
9504
+ static equals(
9505
+ a:
9506
+ | SearchStarterPacksRequest
9507
+ | PlainMessage<SearchStarterPacksRequest>
9508
+ | undefined,
9509
+ b:
9510
+ | SearchStarterPacksRequest
9511
+ | PlainMessage<SearchStarterPacksRequest>
9512
+ | undefined,
9513
+ ): boolean {
9514
+ return proto3.util.equals(SearchStarterPacksRequest, a, b)
9515
+ }
9516
+ }
9517
+
9518
+ /**
9519
+ * @generated from message bsky.SearchStarterPacksResponse
9520
+ */
9521
+ export class SearchStarterPacksResponse extends Message<SearchStarterPacksResponse> {
9522
+ /**
9523
+ * @generated from field: repeated string uris = 1;
9524
+ */
9525
+ uris: string[] = []
9526
+
9527
+ /**
9528
+ * @generated from field: string cursor = 2;
9529
+ */
9530
+ cursor = ''
9531
+
9532
+ constructor(data?: PartialMessage<SearchStarterPacksResponse>) {
9533
+ super()
9534
+ proto3.util.initPartial(data, this)
9535
+ }
9536
+
9537
+ static readonly runtime: typeof proto3 = proto3
9538
+ static readonly typeName = 'bsky.SearchStarterPacksResponse'
9539
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
9540
+ {
9541
+ no: 1,
9542
+ name: 'uris',
9543
+ kind: 'scalar',
9544
+ T: 9 /* ScalarType.STRING */,
9545
+ repeated: true,
9546
+ },
9547
+ { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
9548
+ ])
9549
+
9550
+ static fromBinary(
9551
+ bytes: Uint8Array,
9552
+ options?: Partial<BinaryReadOptions>,
9553
+ ): SearchStarterPacksResponse {
9554
+ return new SearchStarterPacksResponse().fromBinary(bytes, options)
9555
+ }
9556
+
9557
+ static fromJson(
9558
+ jsonValue: JsonValue,
9559
+ options?: Partial<JsonReadOptions>,
9560
+ ): SearchStarterPacksResponse {
9561
+ return new SearchStarterPacksResponse().fromJson(jsonValue, options)
9562
+ }
9563
+
9564
+ static fromJsonString(
9565
+ jsonString: string,
9566
+ options?: Partial<JsonReadOptions>,
9567
+ ): SearchStarterPacksResponse {
9568
+ return new SearchStarterPacksResponse().fromJsonString(jsonString, options)
9569
+ }
9570
+
9571
+ static equals(
9572
+ a:
9573
+ | SearchStarterPacksResponse
9574
+ | PlainMessage<SearchStarterPacksResponse>
9575
+ | undefined,
9576
+ b:
9577
+ | SearchStarterPacksResponse
9578
+ | PlainMessage<SearchStarterPacksResponse>
9579
+ | undefined,
9580
+ ): boolean {
9581
+ return proto3.util.equals(SearchStarterPacksResponse, a, b)
9582
+ }
9583
+ }
9584
+
9448
9585
  /**
9449
9586
  * - Return DIDs of suggested follows for a user, excluding anyone they already follow
9450
9587
  * - `getSuggestions`, `getSuggestedFollowsByActor`
@@ -125,6 +125,15 @@ export class Views {
125
125
  return actor.muted || !!actor.mutedByList
126
126
  }
127
127
 
128
+ viewerSeesNeedsReview(did: string, state: HydrationState): boolean {
129
+ const { labels, profileViewers, ctx } = state
130
+ return (
131
+ !labels?.get(did)?.needsReview ||
132
+ ctx?.viewer === did ||
133
+ !!profileViewers?.get(did)?.following
134
+ )
135
+ }
136
+
128
137
  replyIsHiddenByThreadgate(
129
138
  replyUri: string,
130
139
  rootPostUri: string,
@@ -850,6 +859,9 @@ export class Views {
850
859
  if (this.viewerBlockExists(post.author.did, state)) {
851
860
  return this.blockedPost(uri, post.author.did, state)
852
861
  }
862
+ if (!this.viewerSeesNeedsReview(post.author.did, state)) {
863
+ return undefined
864
+ }
853
865
  return {
854
866
  $type: 'app.bsky.feed.defs#threadViewPost',
855
867
  post,
@@ -397,9 +397,9 @@ Array [
397
397
  "muted": false,
398
398
  },
399
399
  },
400
- "description": "Provides all feed candidates, blindly ignoring pagination limit",
400
+ "description": "Echoes back the same cursor it received",
401
401
  "did": "user(0)",
402
- "displayName": "Bad Pagination",
402
+ "displayName": "Bad Pagination Cursor",
403
403
  "indexedAt": "1970-01-01T00:00:00.000Z",
404
404
  "labels": Array [],
405
405
  "likeCount": 0,
@@ -439,9 +439,9 @@ Array [
439
439
  "muted": false,
440
440
  },
441
441
  },
442
- "description": "Provides even-indexed feed candidates",
442
+ "description": "Provides all feed candidates, blindly ignoring pagination limit",
443
443
  "did": "user(0)",
444
- "displayName": "Even",
444
+ "displayName": "Bad Pagination Limit",
445
445
  "indexedAt": "1970-01-01T00:00:00.000Z",
446
446
  "labels": Array [],
447
447
  "likeCount": 0,
@@ -481,15 +481,57 @@ Array [
481
481
  "muted": false,
482
482
  },
483
483
  },
484
+ "description": "Provides even-indexed feed candidates",
485
+ "did": "user(0)",
486
+ "displayName": "Even",
487
+ "indexedAt": "1970-01-01T00:00:00.000Z",
488
+ "labels": Array [],
489
+ "likeCount": 0,
490
+ "uri": "record(7)",
491
+ "viewer": Object {},
492
+ },
493
+ Object {
494
+ "cid": "cids(7)",
495
+ "creator": Object {
496
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg",
497
+ "createdAt": "1970-01-01T00:00:00.000Z",
498
+ "description": "its me!",
499
+ "did": "user(1)",
500
+ "displayName": "ali",
501
+ "handle": "alice.test",
502
+ "indexedAt": "1970-01-01T00:00:00.000Z",
503
+ "labels": Array [
504
+ Object {
505
+ "cid": "cids(2)",
506
+ "cts": "1970-01-01T00:00:00.000Z",
507
+ "src": "user(1)",
508
+ "uri": "record(3)",
509
+ "val": "self-label-a",
510
+ },
511
+ Object {
512
+ "cid": "cids(2)",
513
+ "cts": "1970-01-01T00:00:00.000Z",
514
+ "src": "user(1)",
515
+ "uri": "record(3)",
516
+ "val": "self-label-b",
517
+ },
518
+ ],
519
+ "viewer": Object {
520
+ "blockedBy": false,
521
+ "followedBy": "record(2)",
522
+ "following": "record(1)",
523
+ "muted": false,
524
+ },
525
+ },
484
526
  "description": "Provides all feed candidates",
485
527
  "did": "user(0)",
486
528
  "displayName": "All",
487
529
  "indexedAt": "1970-01-01T00:00:00.000Z",
488
530
  "labels": Array [],
489
531
  "likeCount": 2,
490
- "uri": "record(7)",
532
+ "uri": "record(8)",
491
533
  "viewer": Object {
492
- "like": "record(8)",
534
+ "like": "record(9)",
493
535
  },
494
536
  },
495
537
  ]
@@ -1950,7 +1992,7 @@ Object {
1950
1992
  },
1951
1993
  "description": "Provides all feed candidates, blindly ignoring pagination limit",
1952
1994
  "did": "user(0)",
1953
- "displayName": "Bad Pagination",
1995
+ "displayName": "Bad Pagination Limit",
1954
1996
  "indexedAt": "1970-01-01T00:00:00.000Z",
1955
1997
  "labels": Array [],
1956
1998
  "likeCount": 0,
@@ -30,7 +30,8 @@ describe('feed generation', () => {
30
30
  let feedUriAllRef: RecordRef
31
31
  let feedUriEven: string
32
32
  let feedUriOdd: string // Unsupported by feed gen
33
- let feedUriBadPagination: string
33
+ let feedUriBadPaginationLimit: string
34
+ let feedUriBadPaginationCursor: string
34
35
  let feedUriPrime: string // Taken-down
35
36
  let feedUriPrimeRef: RecordRef
36
37
  let feedUriNeedsAuth: string
@@ -47,10 +48,15 @@ describe('feed generation', () => {
47
48
  await network.processAll()
48
49
  alice = sc.dids.alice
49
50
  const allUri = AtUri.make(alice, 'app.bsky.feed.generator', 'all')
50
- const feedUriBadPagination = AtUri.make(
51
+ const feedUriBadPaginationLimit = AtUri.make(
51
52
  alice,
52
53
  'app.bsky.feed.generator',
53
- 'bad-pagination',
54
+ 'bad-pagination-limit',
55
+ )
56
+ const feedUriBadPaginationCursor = AtUri.make(
57
+ alice,
58
+ 'app.bsky.feed.generator',
59
+ 'bad-pagination-cursor',
54
60
  )
55
61
  const evenUri = AtUri.make(alice, 'app.bsky.feed.generator', 'even')
56
62
  const primeUri = AtUri.make(alice, 'app.bsky.feed.generator', 'prime')
@@ -62,7 +68,12 @@ describe('feed generation', () => {
62
68
  gen = await network.createFeedGen({
63
69
  [allUri.toString()]: feedGenHandler('all'),
64
70
  [evenUri.toString()]: feedGenHandler('even'),
65
- [feedUriBadPagination.toString()]: feedGenHandler('bad-pagination'),
71
+ [feedUriBadPaginationLimit.toString()]: feedGenHandler(
72
+ 'bad-pagination-limit',
73
+ ),
74
+ [feedUriBadPaginationCursor.toString()]: feedGenHandler(
75
+ 'bad-pagination-cursor',
76
+ ),
66
77
  [primeUri.toString()]: feedGenHandler('prime'),
67
78
  [needsAuthUri.toString()]: feedGenHandler('needs-auth'),
68
79
  })
@@ -70,7 +81,7 @@ describe('feed generation', () => {
70
81
  const feedSuggestions = [
71
82
  { uri: allUri.toString(), order: 1 },
72
83
  { uri: evenUri.toString(), order: 2 },
73
- { uri: feedUriBadPagination.toString(), order: 3 },
84
+ { uri: feedUriBadPaginationLimit.toString(), order: 3 },
74
85
  { uri: primeUri.toString(), order: 4 },
75
86
  ]
76
87
  await network.bsky.db.db
@@ -116,17 +127,31 @@ describe('feed generation', () => {
116
127
  },
117
128
  sc.getHeaders(alice),
118
129
  )
119
- const badPagination = await pdsAgent.api.app.bsky.feed.generator.create(
120
- { repo: alice, rkey: 'bad-pagination' },
121
- {
122
- did: gen.did,
123
- displayName: 'Bad Pagination',
124
- description:
125
- 'Provides all feed candidates, blindly ignoring pagination limit',
126
- createdAt: new Date().toISOString(),
127
- },
128
- sc.getHeaders(alice),
129
- )
130
+
131
+ const badPaginationLimit =
132
+ await pdsAgent.api.app.bsky.feed.generator.create(
133
+ { repo: alice, rkey: 'bad-pagination-limit' },
134
+ {
135
+ did: gen.did,
136
+ displayName: 'Bad Pagination Limit',
137
+ description:
138
+ 'Provides all feed candidates, blindly ignoring pagination limit',
139
+ createdAt: new Date().toISOString(),
140
+ },
141
+ sc.getHeaders(alice),
142
+ )
143
+ const badPaginationCursor =
144
+ await pdsAgent.api.app.bsky.feed.generator.create(
145
+ { repo: alice, rkey: 'bad-pagination-cursor' },
146
+ {
147
+ did: gen.did,
148
+ displayName: 'Bad Pagination Cursor',
149
+ description: 'Echoes back the same cursor it received',
150
+ createdAt: new Date().toISOString(),
151
+ },
152
+ sc.getHeaders(alice),
153
+ )
154
+
130
155
  // Taken-down
131
156
  const prime = await pdsAgent.api.app.bsky.feed.generator.create(
132
157
  { repo: alice, rkey: 'prime' },
@@ -157,7 +182,8 @@ describe('feed generation', () => {
157
182
  feedUriAllRef = new RecordRef(all.uri, all.cid)
158
183
  feedUriEven = even.uri
159
184
  feedUriOdd = odd.uri
160
- feedUriBadPagination = badPagination.uri
185
+ feedUriBadPaginationLimit = badPaginationLimit.uri
186
+ feedUriBadPaginationCursor = badPaginationCursor.uri
161
187
  feedUriPrime = prime.uri
162
188
  feedUriPrimeRef = new RecordRef(prime.uri, prime.cid)
163
189
  feedUriNeedsAuth = needsAuth.uri
@@ -203,12 +229,13 @@ describe('feed generation', () => {
203
229
 
204
230
  const paginatedAll = results(await paginateAll(paginator))
205
231
 
206
- expect(paginatedAll.length).toEqual(5)
232
+ expect(paginatedAll.length).toEqual(6)
207
233
  expect(paginatedAll[0].uri).toEqual(feedUriOdd)
208
234
  expect(paginatedAll[1].uri).toEqual(feedUriNeedsAuth)
209
- expect(paginatedAll[2].uri).toEqual(feedUriBadPagination)
210
- expect(paginatedAll[3].uri).toEqual(feedUriEven)
211
- expect(paginatedAll[4].uri).toEqual(feedUriAll)
235
+ expect(paginatedAll[2].uri).toEqual(feedUriBadPaginationCursor)
236
+ expect(paginatedAll[3].uri).toEqual(feedUriBadPaginationLimit)
237
+ expect(paginatedAll[4].uri).toEqual(feedUriEven)
238
+ expect(paginatedAll[5].uri).toEqual(feedUriAll)
212
239
  expect(paginatedAll.map((fg) => fg.uri)).not.toContain(feedUriPrime) // taken-down
213
240
  expect(forSnapshot(paginatedAll)).toMatchSnapshot()
214
241
  })
@@ -490,7 +517,7 @@ describe('feed generation', () => {
490
517
  expect(res.data.feeds.map((f) => f.uri)).toEqual([
491
518
  feedUriAll,
492
519
  feedUriEven,
493
- feedUriBadPagination,
520
+ feedUriBadPaginationLimit,
494
521
  ])
495
522
  })
496
523
 
@@ -605,7 +632,7 @@ describe('feed generation', () => {
605
632
 
606
633
  it('paginates, handling feed not respecting limit.', async () => {
607
634
  const res = await agent.api.app.bsky.feed.getFeed(
608
- { feed: feedUriBadPagination, limit: 3 },
635
+ { feed: feedUriBadPaginationLimit, limit: 3 },
609
636
  {
610
637
  headers: await network.serviceHeaders(
611
638
  alice,
@@ -640,6 +667,22 @@ describe('feed generation', () => {
640
667
  })
641
668
  })
642
669
 
670
+ it('returns empty cursor with feeds that echo back the same cursor from the param.', async () => {
671
+ const res = await agent.api.app.bsky.feed.getFeed(
672
+ { feed: feedUriBadPaginationCursor, cursor: '1', limit: 2 },
673
+ {
674
+ headers: await network.serviceHeaders(
675
+ alice,
676
+ ids.AppBskyFeedGetFeed,
677
+ gen.did,
678
+ ),
679
+ },
680
+ )
681
+
682
+ expect(res.data.cursor).toBeUndefined()
683
+ expect(res.data.feed).toHaveLength(2)
684
+ })
685
+
643
686
  it('resolves contents of taken-down feed.', async () => {
644
687
  const tryGetFeed = agent.api.app.bsky.feed.getFeed(
645
688
  { feed: feedUriPrime },
@@ -712,7 +755,13 @@ describe('feed generation', () => {
712
755
 
713
756
  const feedGenHandler =
714
757
  (
715
- feedName: 'even' | 'all' | 'prime' | 'bad-pagination' | 'needs-auth',
758
+ feedName:
759
+ | 'even'
760
+ | 'all'
761
+ | 'prime'
762
+ | 'bad-pagination-limit'
763
+ | 'bad-pagination-cursor'
764
+ | 'needs-auth',
716
765
  ): SkeletonHandler =>
717
766
  async ({ req, params }) => {
718
767
  if (feedName === 'needs-auth' && !req.headers.authorization) {
@@ -753,17 +802,22 @@ describe('feed generation', () => {
753
802
  return true
754
803
  })
755
804
  const feedResults =
756
- feedName === 'bad-pagination'
805
+ feedName === 'bad-pagination-limit'
757
806
  ? fullFeed.slice(offset) // does not respect limit
758
807
  : fullFeed.slice(offset, offset + limit)
759
808
  const lastResult = feedResults.at(-1)
809
+ const cursorResult =
810
+ feedName === 'bad-pagination-cursor'
811
+ ? cursor
812
+ : lastResult
813
+ ? (fullFeed.indexOf(lastResult) + 1).toString()
814
+ : undefined
815
+
760
816
  return {
761
817
  encoding: 'application/json',
762
818
  body: {
763
819
  feed: feedResults,
764
- cursor: lastResult
765
- ? (fullFeed.indexOf(lastResult) + 1).toString()
766
- : undefined,
820
+ cursor: cursorResult,
767
821
  $auth: jwtBody(req.headers.authorization), // for testing purposes
768
822
  },
769
823
  }
@@ -13,7 +13,7 @@ describe('label hydration', () => {
13
13
 
14
14
  beforeAll(async () => {
15
15
  network = await TestNetwork.create({
16
- dbPostgresSchema: 'bsky_label_hydration',
16
+ dbPostgresSchema: 'bsky_query_labels',
17
17
  })
18
18
  pdsAgent = network.pds.getClient()
19
19
  sc = network.getSeedClient()
@@ -14,7 +14,7 @@ describe('known followers (social proof)', () => {
14
14
 
15
15
  beforeAll(async () => {
16
16
  network = await TestNetwork.create({
17
- dbPostgresSchema: 'bsky_views_block',
17
+ dbPostgresSchema: 'bsky_known_followers',
18
18
  })
19
19
  agent = network.bsky.getClient()
20
20
  pdsAgent = network.pds.getClient()
@@ -0,0 +1,168 @@
1
+ import { AtpAgent } from '@atproto/api'
2
+ import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
3
+ import { ids } from '../../src/lexicon/lexicons'
4
+ import assert from 'assert'
5
+ import { isThreadViewPost } from '../../src/lexicon/types/app/bsky/feed/defs'
6
+
7
+ describe('bsky needs-review labels', () => {
8
+ let network: TestNetwork
9
+ let agent: AtpAgent
10
+ let sc: SeedClient
11
+
12
+ beforeAll(async () => {
13
+ network = await TestNetwork.create({
14
+ dbPostgresSchema: 'bsky_views_needs_review_labels',
15
+ })
16
+ agent = network.bsky.getClient()
17
+ sc = network.getSeedClient()
18
+ await basicSeed(sc)
19
+
20
+ await sc.createAccount('geoff', {
21
+ email: 'geoff@test.com',
22
+ handle: 'geoff.test',
23
+ password: 'geoff',
24
+ })
25
+
26
+ await sc.reply(
27
+ sc.dids.geoff,
28
+ sc.posts[sc.dids.alice][0].ref,
29
+ sc.posts[sc.dids.alice][0].ref,
30
+ 'my name geoff',
31
+ )
32
+
33
+ await sc.post(
34
+ sc.dids.geoff,
35
+ 'her name alice',
36
+ undefined,
37
+ undefined,
38
+ sc.posts[sc.dids.alice][0].ref,
39
+ )
40
+
41
+ await sc.follow(sc.dids.bob, sc.dids.geoff)
42
+
43
+ await network.processAll()
44
+
45
+ AtpAgent.configure({ appLabelers: [network.ozone.ctx.cfg.service.did] })
46
+ await network.bsky.db.db
47
+ .insertInto('label')
48
+ .values({
49
+ src: network.ozone.ctx.cfg.service.did,
50
+ uri: sc.dids.geoff,
51
+ cid: '',
52
+ val: 'needs-review',
53
+ neg: false,
54
+ cts: new Date().toISOString(),
55
+ })
56
+ .execute()
57
+ })
58
+
59
+ afterAll(async () => {
60
+ await network.close()
61
+ })
62
+
63
+ it('applies to thread replies.', async () => {
64
+ const {
65
+ data: { thread },
66
+ } = await agent.app.bsky.feed.getPostThread({
67
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
68
+ })
69
+ assert(isThreadViewPost(thread))
70
+ expect(
71
+ thread.replies?.some((reply) => {
72
+ return (
73
+ isThreadViewPost(reply) && reply.post.author.did === sc.dids.geoff
74
+ )
75
+ }),
76
+ ).toBe(false)
77
+ })
78
+
79
+ it('applies to quote lists.', async () => {
80
+ const {
81
+ data: { posts },
82
+ } = await agent.app.bsky.feed.getQuotes({
83
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
84
+ })
85
+ expect(
86
+ posts.some((post) => {
87
+ return post.author.did === sc.dids.geoff
88
+ }),
89
+ ).toBe(false)
90
+ })
91
+
92
+ it('applies to reply, quote, and mention notifications.', async () => {
93
+ const {
94
+ data: { notifications },
95
+ } = await agent.app.bsky.notification.listNotifications(
96
+ {},
97
+ {
98
+ headers: await network.serviceHeaders(
99
+ sc.dids.alice,
100
+ ids.AppBskyNotificationListNotifications,
101
+ ),
102
+ },
103
+ )
104
+ expect(
105
+ notifications.some((notif) => {
106
+ return notif.reason === 'reply' && notif.author.did === sc.dids.geoff
107
+ }),
108
+ ).toBe(false)
109
+ expect(
110
+ notifications.some((notif) => {
111
+ return notif.reason === 'quote' && notif.author.did === sc.dids.geoff
112
+ }),
113
+ ).toBe(false)
114
+ expect(
115
+ notifications.some((notif) => {
116
+ return notif.reason === 'mention' && notif.author.did === sc.dids.geoff
117
+ }),
118
+ ).toBe(false)
119
+ })
120
+
121
+ it('does not apply to self.', async () => {
122
+ const {
123
+ data: { thread },
124
+ } = await agent.app.bsky.feed.getPostThread(
125
+ {
126
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
127
+ },
128
+ {
129
+ headers: await network.serviceHeaders(
130
+ sc.dids.geoff,
131
+ ids.AppBskyFeedGetPostThread,
132
+ ),
133
+ },
134
+ )
135
+ assert(isThreadViewPost(thread))
136
+ expect(
137
+ thread.replies?.some((reply) => {
138
+ return (
139
+ isThreadViewPost(reply) && reply.post.author.did === sc.dids.geoff
140
+ )
141
+ }),
142
+ ).toBe(true)
143
+ })
144
+
145
+ it('does not apply to followers.', async () => {
146
+ const {
147
+ data: { thread },
148
+ } = await agent.app.bsky.feed.getPostThread(
149
+ {
150
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
151
+ },
152
+ {
153
+ headers: await network.serviceHeaders(
154
+ sc.dids.bob, // follows geoff
155
+ ids.AppBskyFeedGetPostThread,
156
+ ),
157
+ },
158
+ )
159
+ assert(isThreadViewPost(thread))
160
+ expect(
161
+ thread.replies?.some((reply) => {
162
+ return (
163
+ isThreadViewPost(reply) && reply.post.author.did === sc.dids.geoff
164
+ )
165
+ }),
166
+ ).toBe(true)
167
+ })
168
+ })