@atproto/bsky 0.0.194 → 0.0.196

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 (99) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
  3. package/dist/api/app/bsky/feed/searchPosts.js +60 -14
  4. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  5. package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js +1 -2
  6. package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -1
  7. package/dist/api/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
  8. package/dist/api/app/bsky/unspecced/getPostThreadV2.js +1 -1
  9. package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  10. package/dist/auth-verifier.d.ts +1 -0
  11. package/dist/auth-verifier.d.ts.map +1 -1
  12. package/dist/auth-verifier.js +10 -1
  13. package/dist/auth-verifier.js.map +1 -1
  14. package/dist/config.d.ts +6 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +15 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/data-plane/server/routes/search.d.ts.map +1 -1
  19. package/dist/data-plane/server/routes/search.js +15 -1
  20. package/dist/data-plane/server/routes/search.js.map +1 -1
  21. package/dist/data-plane/server/util.d.ts +7 -0
  22. package/dist/data-plane/server/util.d.ts.map +1 -1
  23. package/dist/data-plane/server/util.js +38 -1
  24. package/dist/data-plane/server/util.js.map +1 -1
  25. package/dist/feature-gates.d.ts +17 -6
  26. package/dist/feature-gates.d.ts.map +1 -1
  27. package/dist/feature-gates.js +24 -13
  28. package/dist/feature-gates.js.map +1 -1
  29. package/dist/hydration/feed.d.ts +4 -1
  30. package/dist/hydration/feed.d.ts.map +1 -1
  31. package/dist/hydration/feed.js +10 -2
  32. package/dist/hydration/feed.js.map +1 -1
  33. package/dist/hydration/hydrator.d.ts +5 -2
  34. package/dist/hydration/hydrator.d.ts.map +1 -1
  35. package/dist/hydration/hydrator.js +17 -3
  36. package/dist/hydration/hydrator.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/lexicon/index.d.ts +2 -0
  41. package/dist/lexicon/index.d.ts.map +1 -1
  42. package/dist/lexicon/index.js +4 -0
  43. package/dist/lexicon/index.js.map +1 -1
  44. package/dist/lexicon/lexicons.d.ts +98 -28
  45. package/dist/lexicon/lexicons.d.ts.map +1 -1
  46. package/dist/lexicon/lexicons.js +52 -14
  47. package/dist/lexicon/lexicons.js.map +1 -1
  48. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +0 -2
  49. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  50. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  51. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.d.ts +0 -2
  52. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -1
  53. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -1
  54. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts +0 -2
  55. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
  56. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  57. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.d.ts +28 -0
  58. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.d.ts.map +1 -0
  59. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.js +7 -0
  60. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.js.map +1 -0
  61. package/dist/proto/bsky_pb.d.ts +8 -0
  62. package/dist/proto/bsky_pb.d.ts.map +1 -1
  63. package/dist/proto/bsky_pb.js +20 -0
  64. package/dist/proto/bsky_pb.js.map +1 -1
  65. package/dist/views/index.d.ts +6 -4
  66. package/dist/views/index.d.ts.map +1 -1
  67. package/dist/views/index.js +38 -21
  68. package/dist/views/index.js.map +1 -1
  69. package/dist/views/threads-v2.d.ts +3 -2
  70. package/dist/views/threads-v2.d.ts.map +1 -1
  71. package/dist/views/threads-v2.js +124 -16
  72. package/dist/views/threads-v2.js.map +1 -1
  73. package/package.json +7 -7
  74. package/proto/bsky.proto +2 -0
  75. package/src/api/app/bsky/feed/searchPosts.ts +78 -11
  76. package/src/api/app/bsky/unspecced/getPostThreadOtherV2.ts +1 -2
  77. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +4 -1
  78. package/src/auth-verifier.ts +14 -1
  79. package/src/config.ts +23 -0
  80. package/src/data-plane/server/routes/search.ts +18 -1
  81. package/src/data-plane/server/util.ts +51 -0
  82. package/src/feature-gates.ts +28 -9
  83. package/src/hydration/feed.ts +17 -1
  84. package/src/hydration/hydrator.ts +17 -1
  85. package/src/index.ts +2 -0
  86. package/src/lexicon/index.ts +13 -0
  87. package/src/lexicon/lexicons.ts +52 -16
  88. package/src/lexicon/types/app/bsky/actor/defs.ts +0 -2
  89. package/src/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.ts +0 -2
  90. package/src/lexicon/types/app/bsky/unspecced/getPostThreadV2.ts +0 -2
  91. package/src/lexicon/types/com/atproto/lexicon/resolveLexicon.ts +46 -0
  92. package/src/proto/bsky_pb.ts +12 -0
  93. package/src/views/index.ts +54 -44
  94. package/src/views/threads-v2.ts +158 -25
  95. package/tests/utils.test.ts +45 -0
  96. package/tests/views/post-search.test.ts +221 -0
  97. package/tests/views/thread-v2.test.ts +2 -109
  98. package/tsconfig.build.tsbuildinfo +1 -1
  99. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -52,15 +52,16 @@ export type ThreadTreeVisible = ThreadBlockedNode | ThreadNoUnauthenticatedNode
52
52
  export type ThreadTreeOther = ThreadOtherAnchorPostNode | ThreadOtherPostNode;
53
53
  export type ThreadTree = ThreadTreeVisible | ThreadTreeOther;
54
54
  /** This function mutates the tree parameter. */
55
- export declare function sortTrimFlattenThreadTree(anchorTree: ThreadTree, options: SortTrimFlattenOptions): any;
55
+ export declare function sortTrimFlattenThreadTree(anchorTree: ThreadTree, options: SortTrimFlattenOptions, useExploration?: boolean): any;
56
56
  type SortTrimFlattenOptions = {
57
57
  branchingFactor: GetPostThreadV2QueryParams['branchingFactor'];
58
58
  opDid: string;
59
- prioritizeFollowedUsers: boolean;
60
59
  sort?: GetPostThreadV2QueryParams['sort'];
61
60
  viewer: HydrateCtx['viewer'];
62
61
  threadTagsBumpDown: readonly string[];
63
62
  threadTagsHide: readonly string[];
63
+ visibilityTagRankPrefix: string;
64
64
  };
65
+ export declare function sortTrimThreadTreeExploration(n: ThreadTree, opts: SortTrimFlattenOptions): ThreadTree;
65
66
  export {};
66
67
  //# sourceMappingURL=threads-v2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"threads-v2.d.ts","sourceRoot":"","sources":["../../src/views/threads-v2.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"threads-v2.d.ts","sourceRoot":"","sources":["../../src/views/threads-v2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,EAClB,cAAc,EACf,MAAM,0CAA0C,CAAA;AACjD,OAAO,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,0DAA0D,CAAA;AACxG,OAAO,EACL,WAAW,IAAI,0BAA0B,EACzC,UAAU,EACX,MAAM,qDAAqD,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQxC,KAAK,eAAe,CAAC,CAAC,SAAS,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CACxD,UAAU,EACV,OAAO,CACR,GAAG;IACF,KAAK,EAAE,CAAC,CAAA;CACT,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,eAAe,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAE/E,MAAM,MAAM,gCAAgC,GAAG,eAAe,CAC5D,MAAM,CAAC,2BAA2B,CAAC,CACpC,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,eAAe,CACnD,MAAM,CAAC,kBAAkB,CAAC,CAC3B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,eAAe,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAA;AAEzE,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AACD,KAAK,2BAA2B,GAAG;IACjC,IAAI,EAAE,mBAAmB,CAAA;IACzB,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;IAC9B,IAAI,EAAE,gCAAgC,CAAA;CACvC,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,EAAE,uBAAuB,CAAA;CAC9B,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;IAC9B,OAAO,EAAE,UAAU,EAAE,GAAG,SAAS,CAAA;CAClC,CAAA;AAED,KAAK,oBAAoB,CAAC,CAAC,SAAS,eAAe,CAAC,OAAO,CAAC,IAAI,IAAI,CAClE,eAAe,EACf,OAAO,CACR,GAAG;IACF,KAAK,EAAE,CAAC,CAAA;CACT,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,CACzD,MAAM,CAAC,cAAc,CAAC,CACvB,CAAA;AAKD,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG;QAAE,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC3D,OAAO,EAAE,mBAAmB,EAAE,GAAG,SAAS,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,YAAY,CAAA;IAClB,IAAI,EAAE,wBAAwB,CAAA;IAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,OAAO,EAAE,mBAAmB,EAAE,GAAG,SAAS,CAAA;CAC3C,CAAA;AAQD,MAAM,MAAM,iBAAiB,GACzB,iBAAiB,GACjB,2BAA2B,GAC3B,kBAAkB,GAClB,cAAc,CAAA;AAElB,MAAM,MAAM,eAAe,GAAG,yBAAyB,GAAG,mBAAmB,CAAA;AAE7E,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,eAAe,CAAA;AAE5D,gDAAgD;AAChD,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,sBAAsB,EAC/B,cAAc,CAAC,EAAE,OAAO,OAOzB;AAED,KAAK,sBAAsB,GAAG;IAC5B,eAAe,EAAE,0BAA0B,CAAC,iBAAiB,CAAC,CAAA;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,0BAA0B,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC5B,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,uBAAuB,EAAE,MAAM,CAAA;CAChC,CAAA;AA2OD,wBAAgB,6BAA6B,CAC3C,CAAC,EAAE,UAAU,EACb,IAAI,EAAE,sBAAsB,GAC3B,UAAU,CAoCZ"}
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sortTrimFlattenThreadTree = sortTrimFlattenThreadTree;
4
- const api_1 = require("@atproto/api");
5
- const post_1 = require("../lexicon/types/app/bsky/feed/post");
4
+ exports.sortTrimThreadTreeExploration = sortTrimThreadTreeExploration;
6
5
  const isNodeWithReplies = (node) => 'replies' in node && node.replies !== undefined;
7
6
  const isPostNode = (node) => node.type === 'post' || node.type === 'hiddenPost';
8
7
  /** This function mutates the tree parameter. */
9
- function sortTrimFlattenThreadTree(anchorTree, options) {
10
- const sortedAnchorTree = sortTrimThreadTree(anchorTree, options);
8
+ function sortTrimFlattenThreadTree(anchorTree, options, useExploration) {
9
+ const sortedAnchorTree = useExploration
10
+ ? sortTrimThreadTreeExploration(anchorTree, options)
11
+ : sortTrimThreadTree(anchorTree, options);
11
12
  return flattenTree(sortedAnchorTree);
12
13
  }
13
- const isPostRecord = (0, api_1.asPredicate)(post_1.validateRecord);
14
14
  /** This function mutates the tree parameter. */
15
15
  function sortTrimThreadTree(n, opts) {
16
16
  if (!isNodeWithReplies(n)) {
@@ -50,7 +50,7 @@ function applyBumping(aNode, bNode, opts) {
50
50
  if (!isPostNode(bNode)) {
51
51
  return null;
52
52
  }
53
- const { opDid, prioritizeFollowedUsers, viewer, threadTagsBumpDown, threadTagsHide, } = opts;
53
+ const { opDid, viewer, threadTagsBumpDown, threadTagsHide } = opts;
54
54
  const maybeBump = (bump, predicateFn) => {
55
55
  const aPredicate = predicateFn(aNode);
56
56
  const bPredicate = predicateFn(bNode);
@@ -82,22 +82,13 @@ function applyBumping(aNode, bNode, opts) {
82
82
  // Followers posts.
83
83
  [
84
84
  'up',
85
- (i) => i.type === 'post' &&
86
- prioritizeFollowedUsers &&
87
- !!i.item.value.post.author.viewer?.following,
85
+ (i) => i.type === 'post' && !!i.item.value.post.author.viewer?.following,
88
86
  ],
89
87
  // Bump-down tags.
90
88
  [
91
89
  'down',
92
90
  (i) => i.type === 'post' && threadTagsBumpDown.some((t) => i.tags.has(t)),
93
91
  ],
94
- // Pushpin-only.
95
- [
96
- 'down',
97
- (i) => i.type === 'post' &&
98
- isPostRecord(i.item.value.post.record) &&
99
- i.item.value.post.record.text.trim() === '📌',
100
- ],
101
92
  /*
102
93
  Bumps within hidden replies.
103
94
  This determines the order of hidden replies:
@@ -202,4 +193,121 @@ function* flattenInDirection({ tree, direction, }) {
202
193
  }
203
194
  }
204
195
  }
196
+ function sortTrimThreadTreeExploration(n, opts) {
197
+ if (!isNodeWithReplies(n)) {
198
+ return n;
199
+ }
200
+ const node = n;
201
+ if (node.replies) {
202
+ node.replies.sort((an, bn) => {
203
+ if (!isPostNode(an)) {
204
+ return 1;
205
+ }
206
+ if (!isPostNode(bn)) {
207
+ return -1;
208
+ }
209
+ const aNode = an;
210
+ const bNode = bn;
211
+ // First applies bumping.
212
+ const bump = applyBumpingExploration(aNode, bNode, opts);
213
+ if (bump !== null) {
214
+ return bump;
215
+ }
216
+ // Then applies sorting.
217
+ return applySortingExploration(aNode, bNode, opts);
218
+ });
219
+ // Trimming: after sorting, apply branching factor to all levels of replies except the anchor direct replies.
220
+ if (node.item.depth !== 0) {
221
+ node.replies = node.replies.slice(0, opts.branchingFactor);
222
+ }
223
+ node.replies.forEach((reply) => sortTrimThreadTreeExploration(reply, opts));
224
+ }
225
+ return node;
226
+ }
227
+ function applyBumpingExploration(aNode, bNode, opts) {
228
+ if (!isPostNode(aNode)) {
229
+ return null;
230
+ }
231
+ if (!isPostNode(bNode)) {
232
+ return null;
233
+ }
234
+ const { opDid, viewer } = opts;
235
+ const maybeBump = (bump, predicateFn) => {
236
+ const aPredicate = predicateFn(aNode);
237
+ const bPredicate = predicateFn(bNode);
238
+ if (aPredicate && bPredicate) {
239
+ return applySortingExploration(aNode, bNode, opts);
240
+ }
241
+ else if (aPredicate) {
242
+ return bump === 'up' ? -1 : 1;
243
+ }
244
+ else if (bPredicate) {
245
+ return bump === 'up' ? 1 : -1;
246
+ }
247
+ return null;
248
+ };
249
+ // The order of the bumps determines the priority with which they are applied.
250
+ // Bumps-up applied first make the item appear higher in the list than later bumps-up.
251
+ // Bumps-down applied first make the item appear lower in the list than later bumps-down.
252
+ const bumps = [
253
+ /*
254
+ General bumps.
255
+ */
256
+ // OP replies.
257
+ ['up', (i) => i.item.value.post.author.did === opDid],
258
+ // Viewer replies.
259
+ ['up', (i) => i.item.value.post.author.did === viewer],
260
+ ];
261
+ for (const [bump, predicateFn] of bumps) {
262
+ const bumpResult = maybeBump(bump, predicateFn);
263
+ if (bumpResult !== null) {
264
+ return bumpResult;
265
+ }
266
+ }
267
+ return null;
268
+ }
269
+ function applySortingExploration(aNode, bNode, opts) {
270
+ const { visibilityTagRankPrefix: rp } = opts;
271
+ const a = aNode.item.value;
272
+ const ar = !rp ? 0 : parseRankFromTag(rp, findRankTag(aNode.tags, rp));
273
+ const b = bNode.item.value;
274
+ const br = !rp ? 0 : parseRankFromTag(rp, findRankTag(bNode.tags, rp));
275
+ // Only customize sort for visible posts.
276
+ if (aNode.type === 'post' && bNode.type === 'post') {
277
+ const { sort } = opts;
278
+ if (sort === 'oldest') {
279
+ return a.post.indexedAt.localeCompare(b.post.indexedAt);
280
+ }
281
+ if (sort === 'top') {
282
+ const aLikes = a.post.likeCount ?? 0;
283
+ const bLikes = b.post.likeCount ?? 0;
284
+ const aTop = topSortValue(aLikes, aNode.hasOPLike);
285
+ const bTop = topSortValue(bLikes, bNode.hasOPLike);
286
+ const aRank = aTop + ar;
287
+ const bRank = bTop + br;
288
+ if (aRank !== bRank) {
289
+ return bRank - aRank;
290
+ }
291
+ }
292
+ }
293
+ // Fallback to newest.
294
+ return b.post.indexedAt.localeCompare(a.post.indexedAt);
295
+ }
296
+ function findRankTag(tags, prefix) {
297
+ return Array.from(tags.values()).find((tag) => tag.startsWith(prefix));
298
+ }
299
+ function parseRankFromTag(prefix, tag) {
300
+ if (!tag)
301
+ return 0;
302
+ try {
303
+ const rank = parseInt(tag.slice(prefix.length), 10);
304
+ if (typeof rank !== 'number' || isNaN(rank)) {
305
+ return 0;
306
+ }
307
+ return rank;
308
+ }
309
+ catch (e) {
310
+ return 0;
311
+ }
312
+ }
205
313
  //# sourceMappingURL=threads-v2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"threads-v2.js","sourceRoot":"","sources":["../../src/views/threads-v2.ts"],"names":[],"mappings":"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","sourcesContent":["import { asPredicate } from '@atproto/api'\nimport { HydrateCtx } from '../hydration/hydrator'\nimport { validateRecord as validatePostRecord } from '../lexicon/types/app/bsky/feed/post'\nimport {\n ThreadItemBlocked,\n ThreadItemNoUnauthenticated,\n ThreadItemNotFound,\n ThreadItemPost,\n} from '../lexicon/types/app/bsky/unspecced/defs'\nimport { ThreadItem as ThreadOtherItem } from '../lexicon/types/app/bsky/unspecced/getPostThreadOtherV2'\nimport {\n QueryParams as GetPostThreadV2QueryParams,\n ThreadItem,\n} from '../lexicon/types/app/bsky/unspecced/getPostThreadV2'\nimport { $Typed } from '../lexicon/util'\n\ntype ThreadMaybeOtherPostNode = ThreadPostNode | ThreadOtherPostNode\ntype ThreadNodeWithReplies =\n | ThreadPostNode\n | ThreadOtherPostNode\n | ThreadOtherAnchorPostNode\n\ntype ThreadItemValue<T extends ThreadItem['value']> = Omit<\n ThreadItem,\n 'value'\n> & {\n value: T\n}\n\nexport type ThreadItemValueBlocked = ThreadItemValue<$Typed<ThreadItemBlocked>>\n\nexport type ThreadItemValueNoUnauthenticated = ThreadItemValue<\n $Typed<ThreadItemNoUnauthenticated>\n>\n\nexport type ThreadItemValueNotFound = ThreadItemValue<\n $Typed<ThreadItemNotFound>\n>\n\nexport type ThreadItemValuePost = ThreadItemValue<$Typed<ThreadItemPost>>\n\ntype ThreadBlockedNode = {\n type: 'blocked'\n item: ThreadItemValueBlocked\n}\ntype ThreadNoUnauthenticatedNode = {\n type: 'noUnauthenticated'\n parent: ThreadTree | undefined\n item: ThreadItemValueNoUnauthenticated\n}\n\ntype ThreadNotFoundNode = {\n type: 'notFound'\n item: ThreadItemValueNotFound\n}\n\ntype ThreadPostNode = {\n type: 'post'\n item: ThreadItemValuePost\n tags: Set<string>\n hasOPLike: boolean\n parent: ThreadTree | undefined\n replies: ThreadTree[] | undefined\n}\n\ntype ThreadOtherItemValue<T extends ThreadOtherItem['value']> = Omit<\n ThreadOtherItem,\n 'value'\n> & {\n value: T\n}\n\nexport type ThreadOtherItemValuePost = ThreadOtherItemValue<\n $Typed<ThreadItemPost>\n>\n\n// This is an intermediary type that doesn't map to the views.\n// It is useful to differentiate between the anchor post and the replies for the hidden case,\n// while also differentiating between hidden and visible cases.\nexport type ThreadOtherAnchorPostNode = {\n type: 'hiddenAnchor'\n item: Omit<ThreadOtherItem, 'value'> & { value: undefined }\n replies: ThreadOtherPostNode[] | undefined\n}\n\nexport type ThreadOtherPostNode = {\n type: 'hiddenPost'\n item: ThreadOtherItemValuePost\n tags: Set<string>\n replies: ThreadOtherPostNode[] | undefined\n}\n\nconst isNodeWithReplies = (node: ThreadTree): node is ThreadNodeWithReplies =>\n 'replies' in node && node.replies !== undefined\n\nconst isPostNode = (node: ThreadTree): node is ThreadMaybeOtherPostNode =>\n node.type === 'post' || node.type === 'hiddenPost'\n\nexport type ThreadTreeVisible =\n | ThreadBlockedNode\n | ThreadNoUnauthenticatedNode\n | ThreadNotFoundNode\n | ThreadPostNode\n\nexport type ThreadTreeOther = ThreadOtherAnchorPostNode | ThreadOtherPostNode\n\nexport type ThreadTree = ThreadTreeVisible | ThreadTreeOther\n\n/** This function mutates the tree parameter. */\nexport function sortTrimFlattenThreadTree(\n anchorTree: ThreadTree,\n options: SortTrimFlattenOptions,\n) {\n const sortedAnchorTree = sortTrimThreadTree(anchorTree, options)\n\n return flattenTree(sortedAnchorTree)\n}\n\ntype SortTrimFlattenOptions = {\n branchingFactor: GetPostThreadV2QueryParams['branchingFactor']\n opDid: string\n prioritizeFollowedUsers: boolean\n sort?: GetPostThreadV2QueryParams['sort']\n viewer: HydrateCtx['viewer']\n threadTagsBumpDown: readonly string[]\n threadTagsHide: readonly string[]\n}\n\nconst isPostRecord = asPredicate(validatePostRecord)\n\n/** This function mutates the tree parameter. */\nfunction sortTrimThreadTree(\n n: ThreadTree,\n opts: SortTrimFlattenOptions,\n): ThreadTree {\n if (!isNodeWithReplies(n)) {\n return n\n }\n const node: ThreadNodeWithReplies = n\n\n if (node.replies) {\n node.replies.sort((an: ThreadTree, bn: ThreadTree) => {\n if (!isPostNode(an)) {\n return 1\n }\n if (!isPostNode(bn)) {\n return -1\n }\n const aNode: ThreadMaybeOtherPostNode = an\n const bNode: ThreadMaybeOtherPostNode = bn\n\n // First applies bumping.\n const bump = applyBumping(aNode, bNode, opts)\n if (bump !== null) {\n return bump\n }\n\n // Then applies sorting.\n return applySorting(aNode, bNode, opts)\n })\n\n // Trimming: after sorting, apply branching factor to all levels of replies except the anchor direct replies.\n if (node.item.depth !== 0) {\n node.replies = node.replies.slice(0, opts.branchingFactor)\n }\n\n node.replies.forEach((reply) => sortTrimThreadTree(reply, opts))\n }\n\n return node\n}\n\nfunction applyBumping(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number | null {\n if (!isPostNode(aNode)) {\n return null\n }\n if (!isPostNode(bNode)) {\n return null\n }\n\n const {\n opDid,\n prioritizeFollowedUsers,\n viewer,\n threadTagsBumpDown,\n threadTagsHide,\n } = opts\n\n type BumpDirection = 'up' | 'down'\n type BumpPredicateFn = (i: ThreadMaybeOtherPostNode) => boolean\n\n const maybeBump = (\n bump: BumpDirection,\n predicateFn: BumpPredicateFn,\n ): number | null => {\n const aPredicate = predicateFn(aNode)\n const bPredicate = predicateFn(bNode)\n if (aPredicate && bPredicate) {\n return applySorting(aNode, bNode, opts)\n } else if (aPredicate) {\n return bump === 'up' ? -1 : 1\n } else if (bPredicate) {\n return bump === 'up' ? 1 : -1\n }\n return null\n }\n\n // The order of the bumps determines the priority with which they are applied.\n // Bumps-up applied first make the item appear higher in the list than later bumps-up.\n // Bumps-down applied first make the item appear lower in the list than later bumps-down.\n const bumps: [BumpDirection, BumpPredicateFn][] = [\n /*\n General bumps.\n */\n // OP replies.\n ['up', (i) => i.item.value.post.author.did === opDid],\n // Viewer replies.\n ['up', (i) => i.item.value.post.author.did === viewer],\n\n /*\n Bumps within visible replies.\n */\n // Followers posts.\n [\n 'up',\n (i) =>\n i.type === 'post' &&\n prioritizeFollowedUsers &&\n !!i.item.value.post.author.viewer?.following,\n ],\n // Bump-down tags.\n [\n 'down',\n (i) => i.type === 'post' && threadTagsBumpDown.some((t) => i.tags.has(t)),\n ],\n // Pushpin-only.\n [\n 'down',\n (i) =>\n i.type === 'post' &&\n isPostRecord(i.item.value.post.record) &&\n i.item.value.post.record.text.trim() === '📌',\n ],\n\n /*\n Bumps within hidden replies.\n This determines the order of hidden replies:\n 1. hidden by threadgate.\n 2. hidden by tags.\n 3. muted by viewer.\n */\n // Muted account by the viewer.\n ['down', (i) => i.type === 'hiddenPost' && i.item.value.mutedByViewer],\n // Hidden by tags.\n [\n 'down',\n (i) =>\n i.type === 'hiddenPost' && threadTagsHide.some((t) => i.tags.has(t)),\n ],\n // Hidden by threadgate.\n ['down', (i) => i.type === 'hiddenPost' && i.item.value.hiddenByThreadgate],\n ]\n\n for (const [bump, predicateFn] of bumps) {\n const bumpResult = maybeBump(bump, predicateFn)\n if (bumpResult !== null) {\n return bumpResult\n }\n }\n\n return null\n}\n\nfunction applySorting(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number {\n const a = aNode.item.value\n const b = bNode.item.value\n\n // Only customize sort for visible posts.\n if (aNode.type === 'post' && bNode.type === 'post') {\n const { sort } = opts\n\n if (sort === 'oldest') {\n return a.post.indexedAt.localeCompare(b.post.indexedAt)\n }\n if (sort === 'top') {\n const aLikes = a.post.likeCount ?? 0\n const bLikes = b.post.likeCount ?? 0\n const aTop = topSortValue(aLikes, aNode.hasOPLike)\n const bTop = topSortValue(bLikes, bNode.hasOPLike)\n if (aTop !== bTop) {\n return bTop - aTop\n }\n }\n }\n\n // Fallback to newest.\n return b.post.indexedAt.localeCompare(a.post.indexedAt)\n}\n\nfunction topSortValue(likeCount: number, hasOPLike: boolean): number {\n return Math.log(3 + likeCount) * (hasOPLike ? 1.45 : 1.0)\n}\n\nfunction flattenTree(tree: ThreadTree) {\n return [\n // All parents above.\n ...Array.from(\n flattenInDirection({\n tree,\n direction: 'up',\n }),\n ),\n\n // The anchor.\n // In the case of hidden replies, the anchor item itself is undefined.\n ...(tree.item.value ? [tree.item] : []),\n\n // All replies below.\n ...Array.from(\n flattenInDirection({\n tree,\n direction: 'down',\n }),\n ),\n ]\n}\n\nfunction* flattenInDirection({\n tree,\n direction,\n}: {\n tree: ThreadTree\n direction: 'up' | 'down'\n}) {\n if (tree.type === 'noUnauthenticated') {\n if (direction === 'up') {\n if (tree.parent) {\n // Unfold all parents above.\n yield* flattenTree(tree.parent)\n }\n }\n }\n\n if (tree.type === 'post') {\n if (direction === 'up') {\n if (tree.parent) {\n // Unfold all parents above.\n yield* flattenTree(tree.parent)\n }\n } else {\n // Unfold all replies below.\n if (tree.replies?.length) {\n for (const reply of tree.replies) {\n yield* flattenTree(reply)\n }\n }\n }\n }\n\n // For the first level of hidden replies, the items are undefined.\n if (tree.type === 'hiddenAnchor' || tree.type === 'hiddenPost') {\n if (direction === 'down') {\n // Unfold all replies below.\n if (tree.replies?.length) {\n for (const reply of tree.replies) {\n yield* flattenTree(reply)\n }\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"threads-v2.js","sourceRoot":"","sources":["../../src/views/threads-v2.ts"],"names":[],"mappings":"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","sourcesContent":["import { HydrateCtx } from '../hydration/hydrator'\nimport {\n ThreadItemBlocked,\n ThreadItemNoUnauthenticated,\n ThreadItemNotFound,\n ThreadItemPost,\n} from '../lexicon/types/app/bsky/unspecced/defs'\nimport { ThreadItem as ThreadOtherItem } from '../lexicon/types/app/bsky/unspecced/getPostThreadOtherV2'\nimport {\n QueryParams as GetPostThreadV2QueryParams,\n ThreadItem,\n} from '../lexicon/types/app/bsky/unspecced/getPostThreadV2'\nimport { $Typed } from '../lexicon/util'\n\ntype ThreadMaybeOtherPostNode = ThreadPostNode | ThreadOtherPostNode\ntype ThreadNodeWithReplies =\n | ThreadPostNode\n | ThreadOtherPostNode\n | ThreadOtherAnchorPostNode\n\ntype ThreadItemValue<T extends ThreadItem['value']> = Omit<\n ThreadItem,\n 'value'\n> & {\n value: T\n}\n\nexport type ThreadItemValueBlocked = ThreadItemValue<$Typed<ThreadItemBlocked>>\n\nexport type ThreadItemValueNoUnauthenticated = ThreadItemValue<\n $Typed<ThreadItemNoUnauthenticated>\n>\n\nexport type ThreadItemValueNotFound = ThreadItemValue<\n $Typed<ThreadItemNotFound>\n>\n\nexport type ThreadItemValuePost = ThreadItemValue<$Typed<ThreadItemPost>>\n\ntype ThreadBlockedNode = {\n type: 'blocked'\n item: ThreadItemValueBlocked\n}\ntype ThreadNoUnauthenticatedNode = {\n type: 'noUnauthenticated'\n parent: ThreadTree | undefined\n item: ThreadItemValueNoUnauthenticated\n}\n\ntype ThreadNotFoundNode = {\n type: 'notFound'\n item: ThreadItemValueNotFound\n}\n\ntype ThreadPostNode = {\n type: 'post'\n item: ThreadItemValuePost\n tags: Set<string>\n hasOPLike: boolean\n parent: ThreadTree | undefined\n replies: ThreadTree[] | undefined\n}\n\ntype ThreadOtherItemValue<T extends ThreadOtherItem['value']> = Omit<\n ThreadOtherItem,\n 'value'\n> & {\n value: T\n}\n\nexport type ThreadOtherItemValuePost = ThreadOtherItemValue<\n $Typed<ThreadItemPost>\n>\n\n// This is an intermediary type that doesn't map to the views.\n// It is useful to differentiate between the anchor post and the replies for the hidden case,\n// while also differentiating between hidden and visible cases.\nexport type ThreadOtherAnchorPostNode = {\n type: 'hiddenAnchor'\n item: Omit<ThreadOtherItem, 'value'> & { value: undefined }\n replies: ThreadOtherPostNode[] | undefined\n}\n\nexport type ThreadOtherPostNode = {\n type: 'hiddenPost'\n item: ThreadOtherItemValuePost\n tags: Set<string>\n replies: ThreadOtherPostNode[] | undefined\n}\n\nconst isNodeWithReplies = (node: ThreadTree): node is ThreadNodeWithReplies =>\n 'replies' in node && node.replies !== undefined\n\nconst isPostNode = (node: ThreadTree): node is ThreadMaybeOtherPostNode =>\n node.type === 'post' || node.type === 'hiddenPost'\n\nexport type ThreadTreeVisible =\n | ThreadBlockedNode\n | ThreadNoUnauthenticatedNode\n | ThreadNotFoundNode\n | ThreadPostNode\n\nexport type ThreadTreeOther = ThreadOtherAnchorPostNode | ThreadOtherPostNode\n\nexport type ThreadTree = ThreadTreeVisible | ThreadTreeOther\n\n/** This function mutates the tree parameter. */\nexport function sortTrimFlattenThreadTree(\n anchorTree: ThreadTree,\n options: SortTrimFlattenOptions,\n useExploration?: boolean,\n) {\n const sortedAnchorTree = useExploration\n ? sortTrimThreadTreeExploration(anchorTree, options)\n : sortTrimThreadTree(anchorTree, options)\n\n return flattenTree(sortedAnchorTree)\n}\n\ntype SortTrimFlattenOptions = {\n branchingFactor: GetPostThreadV2QueryParams['branchingFactor']\n opDid: string\n sort?: GetPostThreadV2QueryParams['sort']\n viewer: HydrateCtx['viewer']\n threadTagsBumpDown: readonly string[]\n threadTagsHide: readonly string[]\n visibilityTagRankPrefix: string\n}\n\n/** This function mutates the tree parameter. */\nfunction sortTrimThreadTree(\n n: ThreadTree,\n opts: SortTrimFlattenOptions,\n): ThreadTree {\n if (!isNodeWithReplies(n)) {\n return n\n }\n const node: ThreadNodeWithReplies = n\n\n if (node.replies) {\n node.replies.sort((an: ThreadTree, bn: ThreadTree) => {\n if (!isPostNode(an)) {\n return 1\n }\n if (!isPostNode(bn)) {\n return -1\n }\n const aNode: ThreadMaybeOtherPostNode = an\n const bNode: ThreadMaybeOtherPostNode = bn\n\n // First applies bumping.\n const bump = applyBumping(aNode, bNode, opts)\n if (bump !== null) {\n return bump\n }\n\n // Then applies sorting.\n return applySorting(aNode, bNode, opts)\n })\n\n // Trimming: after sorting, apply branching factor to all levels of replies except the anchor direct replies.\n if (node.item.depth !== 0) {\n node.replies = node.replies.slice(0, opts.branchingFactor)\n }\n\n node.replies.forEach((reply) => sortTrimThreadTree(reply, opts))\n }\n\n return node\n}\n\nfunction applyBumping(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number | null {\n if (!isPostNode(aNode)) {\n return null\n }\n if (!isPostNode(bNode)) {\n return null\n }\n\n const { opDid, viewer, threadTagsBumpDown, threadTagsHide } = opts\n\n type BumpDirection = 'up' | 'down'\n type BumpPredicateFn = (i: ThreadMaybeOtherPostNode) => boolean\n\n const maybeBump = (\n bump: BumpDirection,\n predicateFn: BumpPredicateFn,\n ): number | null => {\n const aPredicate = predicateFn(aNode)\n const bPredicate = predicateFn(bNode)\n if (aPredicate && bPredicate) {\n return applySorting(aNode, bNode, opts)\n } else if (aPredicate) {\n return bump === 'up' ? -1 : 1\n } else if (bPredicate) {\n return bump === 'up' ? 1 : -1\n }\n return null\n }\n\n // The order of the bumps determines the priority with which they are applied.\n // Bumps-up applied first make the item appear higher in the list than later bumps-up.\n // Bumps-down applied first make the item appear lower in the list than later bumps-down.\n const bumps: [BumpDirection, BumpPredicateFn][] = [\n /*\n General bumps.\n */\n // OP replies.\n ['up', (i) => i.item.value.post.author.did === opDid],\n // Viewer replies.\n ['up', (i) => i.item.value.post.author.did === viewer],\n\n /*\n Bumps within visible replies.\n */\n // Followers posts.\n [\n 'up',\n (i) => i.type === 'post' && !!i.item.value.post.author.viewer?.following,\n ],\n // Bump-down tags.\n [\n 'down',\n (i) => i.type === 'post' && threadTagsBumpDown.some((t) => i.tags.has(t)),\n ],\n\n /*\n Bumps within hidden replies.\n This determines the order of hidden replies:\n 1. hidden by threadgate.\n 2. hidden by tags.\n 3. muted by viewer.\n */\n // Muted account by the viewer.\n ['down', (i) => i.type === 'hiddenPost' && i.item.value.mutedByViewer],\n // Hidden by tags.\n [\n 'down',\n (i) =>\n i.type === 'hiddenPost' && threadTagsHide.some((t) => i.tags.has(t)),\n ],\n // Hidden by threadgate.\n ['down', (i) => i.type === 'hiddenPost' && i.item.value.hiddenByThreadgate],\n ]\n\n for (const [bump, predicateFn] of bumps) {\n const bumpResult = maybeBump(bump, predicateFn)\n if (bumpResult !== null) {\n return bumpResult\n }\n }\n\n return null\n}\n\nfunction applySorting(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number {\n const a = aNode.item.value\n const b = bNode.item.value\n\n // Only customize sort for visible posts.\n if (aNode.type === 'post' && bNode.type === 'post') {\n const { sort } = opts\n\n if (sort === 'oldest') {\n return a.post.indexedAt.localeCompare(b.post.indexedAt)\n }\n if (sort === 'top') {\n const aLikes = a.post.likeCount ?? 0\n const bLikes = b.post.likeCount ?? 0\n const aTop = topSortValue(aLikes, aNode.hasOPLike)\n const bTop = topSortValue(bLikes, bNode.hasOPLike)\n if (aTop !== bTop) {\n return bTop - aTop\n }\n }\n }\n\n // Fallback to newest.\n return b.post.indexedAt.localeCompare(a.post.indexedAt)\n}\n\nfunction topSortValue(likeCount: number, hasOPLike: boolean): number {\n return Math.log(3 + likeCount) * (hasOPLike ? 1.45 : 1.0)\n}\n\nfunction flattenTree(tree: ThreadTree) {\n return [\n // All parents above.\n ...Array.from(\n flattenInDirection({\n tree,\n direction: 'up',\n }),\n ),\n\n // The anchor.\n // In the case of hidden replies, the anchor item itself is undefined.\n ...(tree.item.value ? [tree.item] : []),\n\n // All replies below.\n ...Array.from(\n flattenInDirection({\n tree,\n direction: 'down',\n }),\n ),\n ]\n}\n\nfunction* flattenInDirection({\n tree,\n direction,\n}: {\n tree: ThreadTree\n direction: 'up' | 'down'\n}) {\n if (tree.type === 'noUnauthenticated') {\n if (direction === 'up') {\n if (tree.parent) {\n // Unfold all parents above.\n yield* flattenTree(tree.parent)\n }\n }\n }\n\n if (tree.type === 'post') {\n if (direction === 'up') {\n if (tree.parent) {\n // Unfold all parents above.\n yield* flattenTree(tree.parent)\n }\n } else {\n // Unfold all replies below.\n if (tree.replies?.length) {\n for (const reply of tree.replies) {\n yield* flattenTree(reply)\n }\n }\n }\n }\n\n // For the first level of hidden replies, the items are undefined.\n if (tree.type === 'hiddenAnchor' || tree.type === 'hiddenPost') {\n if (direction === 'down') {\n // Unfold all replies below.\n if (tree.replies?.length) {\n for (const reply of tree.replies) {\n yield* flattenTree(reply)\n }\n }\n }\n }\n}\n\nexport function sortTrimThreadTreeExploration(\n n: ThreadTree,\n opts: SortTrimFlattenOptions,\n): ThreadTree {\n if (!isNodeWithReplies(n)) {\n return n\n }\n const node: ThreadNodeWithReplies = n\n\n if (node.replies) {\n node.replies.sort((an: ThreadTree, bn: ThreadTree) => {\n if (!isPostNode(an)) {\n return 1\n }\n if (!isPostNode(bn)) {\n return -1\n }\n const aNode: ThreadMaybeOtherPostNode = an\n const bNode: ThreadMaybeOtherPostNode = bn\n\n // First applies bumping.\n const bump = applyBumpingExploration(aNode, bNode, opts)\n if (bump !== null) {\n return bump\n }\n\n // Then applies sorting.\n return applySortingExploration(aNode, bNode, opts)\n })\n\n // Trimming: after sorting, apply branching factor to all levels of replies except the anchor direct replies.\n if (node.item.depth !== 0) {\n node.replies = node.replies.slice(0, opts.branchingFactor)\n }\n\n node.replies.forEach((reply) => sortTrimThreadTreeExploration(reply, opts))\n }\n\n return node\n}\n\nfunction applyBumpingExploration(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number | null {\n if (!isPostNode(aNode)) {\n return null\n }\n if (!isPostNode(bNode)) {\n return null\n }\n\n const { opDid, viewer } = opts\n\n type BumpDirection = 'up' | 'down'\n type BumpPredicateFn = (i: ThreadMaybeOtherPostNode) => boolean\n\n const maybeBump = (\n bump: BumpDirection,\n predicateFn: BumpPredicateFn,\n ): number | null => {\n const aPredicate = predicateFn(aNode)\n const bPredicate = predicateFn(bNode)\n if (aPredicate && bPredicate) {\n return applySortingExploration(aNode, bNode, opts)\n } else if (aPredicate) {\n return bump === 'up' ? -1 : 1\n } else if (bPredicate) {\n return bump === 'up' ? 1 : -1\n }\n return null\n }\n\n // The order of the bumps determines the priority with which they are applied.\n // Bumps-up applied first make the item appear higher in the list than later bumps-up.\n // Bumps-down applied first make the item appear lower in the list than later bumps-down.\n const bumps: [BumpDirection, BumpPredicateFn][] = [\n /*\n General bumps.\n */\n // OP replies.\n ['up', (i) => i.item.value.post.author.did === opDid],\n // Viewer replies.\n ['up', (i) => i.item.value.post.author.did === viewer],\n ]\n\n for (const [bump, predicateFn] of bumps) {\n const bumpResult = maybeBump(bump, predicateFn)\n if (bumpResult !== null) {\n return bumpResult\n }\n }\n\n return null\n}\n\nfunction applySortingExploration(\n aNode: ThreadMaybeOtherPostNode,\n bNode: ThreadMaybeOtherPostNode,\n opts: SortTrimFlattenOptions,\n): number {\n const { visibilityTagRankPrefix: rp } = opts\n\n const a = aNode.item.value\n const ar = !rp ? 0 : parseRankFromTag(rp, findRankTag(aNode.tags, rp))\n const b = bNode.item.value\n const br = !rp ? 0 : parseRankFromTag(rp, findRankTag(bNode.tags, rp))\n\n // Only customize sort for visible posts.\n if (aNode.type === 'post' && bNode.type === 'post') {\n const { sort } = opts\n\n if (sort === 'oldest') {\n return a.post.indexedAt.localeCompare(b.post.indexedAt)\n }\n if (sort === 'top') {\n const aLikes = a.post.likeCount ?? 0\n const bLikes = b.post.likeCount ?? 0\n const aTop = topSortValue(aLikes, aNode.hasOPLike)\n const bTop = topSortValue(bLikes, bNode.hasOPLike)\n const aRank = aTop + ar\n const bRank = bTop + br\n if (aRank !== bRank) {\n return bRank - aRank\n }\n }\n }\n\n // Fallback to newest.\n return b.post.indexedAt.localeCompare(a.post.indexedAt)\n}\n\nfunction findRankTag(tags: Set<string>, prefix: string) {\n return Array.from(tags.values()).find((tag) => tag.startsWith(prefix))\n}\n\nfunction parseRankFromTag(prefix: string, tag?: string) {\n if (!tag) return 0\n\n try {\n const rank = parseInt(tag.slice(prefix.length), 10)\n if (typeof rank !== 'number' || isNaN(rank)) {\n return 0\n }\n return rank\n } catch (e) {\n return 0\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.194",
3
+ "version": "0.0.196",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -53,14 +53,14 @@
53
53
  "zod": "3.23.8",
54
54
  "@atproto-labs/fetch-node": "0.2.0",
55
55
  "@atproto-labs/xrpc-utils": "0.0.22",
56
- "@atproto/api": "^0.17.7",
57
- "@atproto/common": "^0.4.12",
58
- "@atproto/crypto": "^0.4.4",
56
+ "@atproto/api": "^0.18.0",
59
57
  "@atproto/did": "^0.2.1",
58
+ "@atproto/crypto": "^0.4.4",
59
+ "@atproto/common": "^0.4.12",
60
60
  "@atproto/identity": "^0.4.9",
61
61
  "@atproto/lexicon": "^0.5.1",
62
62
  "@atproto/repo": "^0.8.10",
63
- "@atproto/sync": "^0.1.35",
63
+ "@atproto/sync": "^0.1.36",
64
64
  "@atproto/syntax": "^0.4.1",
65
65
  "@atproto/xrpc-server": "^0.9.5"
66
66
  },
@@ -77,9 +77,9 @@
77
77
  "jest": "^28.1.2",
78
78
  "ts-node": "^10.8.2",
79
79
  "typescript": "^5.6.3",
80
- "@atproto/api": "^0.17.7",
80
+ "@atproto/api": "^0.18.0",
81
81
  "@atproto/lex-cli": "^0.9.6",
82
- "@atproto/pds": "^0.4.191",
82
+ "@atproto/pds": "^0.4.193",
83
83
  "@atproto/xrpc": "^0.7.5"
84
84
  },
85
85
  "scripts": {
package/proto/bsky.proto CHANGED
@@ -88,6 +88,8 @@ message PostRecordMeta {
88
88
 
89
89
  message GetPostRecordsRequest {
90
90
  repeated string uris = 1;
91
+ optional string process_dynamic_tags_for_view = 2;
92
+ optional string viewer_did = 3;
91
93
  }
92
94
 
93
95
  message GetPostRecordsResponse {
@@ -1,7 +1,13 @@
1
1
  import { AtpAgent } from '@atproto/api'
2
2
  import { mapDefined } from '@atproto/common'
3
+ import { ServerConfig } from '../../../../config'
3
4
  import { AppContext } from '../../../../context'
4
5
  import { DataPlaneClient } from '../../../../data-plane'
6
+ import {
7
+ PostSearchQuery,
8
+ parsePostSearchQuery,
9
+ } from '../../../../data-plane/server/util'
10
+ import { FeatureGateID } from '../../../../feature-gates'
5
11
  import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator'
6
12
  import { parseString } from '../../../../hydration/util'
7
13
  import { Server } from '../../../../lexicon'
@@ -21,16 +27,27 @@ export default function (server: Server, ctx: AppContext) {
21
27
  const searchPosts = createPipeline(
22
28
  skeleton,
23
29
  hydration,
24
- noBlocks,
30
+ noBlocksOrTagged,
25
31
  presentation,
26
32
  )
27
33
  server.app.bsky.feed.searchPosts({
28
34
  auth: ctx.authVerifier.standardOptional,
29
35
  handler: async ({ auth, params, req }) => {
30
- const viewer = auth.credentials.iss
36
+ const { viewer, isModService } = ctx.authVerifier.parseCreds(auth)
37
+
31
38
  const labelers = ctx.reqLabelers(req)
32
- const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer })
33
- const results = await searchPosts({ ...params, hydrateCtx }, ctx)
39
+ const hydrateCtx = await ctx.hydrator.createContext({
40
+ labelers,
41
+ viewer,
42
+ featureGates: ctx.featureGates.checkGates(
43
+ [ctx.featureGates.ids.SearchFilteringExploration],
44
+ ctx.featureGates.user({ did: viewer ?? '' }),
45
+ ),
46
+ })
47
+ const results = await searchPosts(
48
+ { ...params, hydrateCtx, isModService },
49
+ ctx,
50
+ )
34
51
  return {
35
52
  encoding: 'application/json',
36
53
  body: results,
@@ -42,6 +59,9 @@ export default function (server: Server, ctx: AppContext) {
42
59
 
43
60
  const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
44
61
  const { ctx, params } = inputs
62
+ const parsedQuery = parsePostSearchQuery(params.q, {
63
+ author: params.author,
64
+ })
45
65
 
46
66
  if (ctx.searchAgent) {
47
67
  // @NOTE cursors won't change on appview swap
@@ -64,6 +84,7 @@ const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
64
84
  return {
65
85
  posts: res.posts.map(({ uri }) => uri),
66
86
  cursor: parseString(res.cursor),
87
+ parsedQuery,
67
88
  }
68
89
  }
69
90
 
@@ -75,6 +96,7 @@ const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
75
96
  return {
76
97
  posts: res.uris,
77
98
  cursor: parseString(res.cursor),
99
+ parsedQuery,
78
100
  }
79
101
  }
80
102
 
@@ -85,14 +107,51 @@ const hydration = async (
85
107
  return ctx.hydrator.hydratePosts(
86
108
  skeleton.posts.map((uri) => ({ uri })),
87
109
  params.hydrateCtx,
110
+ undefined,
111
+ {
112
+ processDynamicTagsForView: params.hydrateCtx.featureGates.get(
113
+ FeatureGateID.SearchFilteringExploration,
114
+ )
115
+ ? 'search'
116
+ : undefined,
117
+ },
88
118
  )
89
119
  }
90
120
 
91
- const noBlocks = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
92
- const { ctx, skeleton, hydration } = inputs
121
+ const noBlocksOrTagged = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
122
+ const { ctx, params, skeleton, hydration } = inputs
123
+ const { parsedQuery } = skeleton
124
+
93
125
  skeleton.posts = skeleton.posts.filter((uri) => {
126
+ const post = hydration.posts?.get(uri)
127
+ if (!post) return
128
+
94
129
  const creator = creatorFromUri(uri)
95
- return !ctx.views.viewerBlockExists(creator, hydration)
130
+ const isCuratedSearch = params.sort === 'top'
131
+ const isPostByViewer = creator === params.hydrateCtx.viewer
132
+
133
+ // Cases to always show.
134
+ if (isPostByViewer) return true
135
+ if (params.isModService) return true
136
+
137
+ // Cases to never show.
138
+ if (ctx.views.viewerBlockExists(creator, hydration)) return false
139
+
140
+ let tagged = false
141
+ if (
142
+ params.hydrateCtx.featureGates.get(
143
+ FeatureGateID.SearchFilteringExploration,
144
+ )
145
+ ) {
146
+ tagged = post.tags.has(ctx.cfg.visibilityTagHide)
147
+ } else {
148
+ tagged = [...ctx.cfg.searchTagsHide].some((t) => post.tags.has(t))
149
+ }
150
+
151
+ // Cases to conditionally show based on tagging.
152
+ if (isCuratedSearch && tagged) return false
153
+ if (!parsedQuery.author && tagged) return false
154
+ return true
96
155
  })
97
156
  return skeleton
98
157
  }
@@ -101,9 +160,12 @@ const presentation = (
101
160
  inputs: PresentationFnInput<Context, Params, Skeleton>,
102
161
  ) => {
103
162
  const { ctx, skeleton, hydration } = inputs
104
- const posts = mapDefined(skeleton.posts, (uri) =>
105
- ctx.views.post(uri, hydration),
106
- )
163
+ const posts = mapDefined(skeleton.posts, (uri) => {
164
+ const post = hydration.posts?.get(uri)
165
+ if (!post) return
166
+
167
+ return ctx.views.post(uri, hydration)
168
+ })
107
169
  return {
108
170
  posts,
109
171
  cursor: skeleton.cursor,
@@ -112,16 +174,21 @@ const presentation = (
112
174
  }
113
175
 
114
176
  type Context = {
177
+ cfg: ServerConfig
115
178
  dataplane: DataPlaneClient
116
179
  hydrator: Hydrator
117
180
  views: Views
118
181
  searchAgent?: AtpAgent
119
182
  }
120
183
 
121
- type Params = QueryParams & { hydrateCtx: HydrateCtx }
184
+ type Params = QueryParams & {
185
+ hydrateCtx: HydrateCtx
186
+ isModService: boolean
187
+ }
122
188
 
123
189
  type Skeleton = {
124
190
  posts: string[]
125
191
  hitsTotal?: number
126
192
  cursor?: string
193
+ parsedQuery: PostSearchQuery
127
194
  }
@@ -93,11 +93,10 @@ const hydration = async (
93
93
  const presentation = (
94
94
  inputs: PresentationFnInput<Context, Params, Skeleton>,
95
95
  ) => {
96
- const { ctx, params, skeleton, hydration } = inputs
96
+ const { ctx, skeleton, hydration } = inputs
97
97
  const thread = ctx.views.threadOtherV2(skeleton, hydration, {
98
98
  below: BELOW,
99
99
  branchingFactor: BRANCHING_FACTOR,
100
- prioritizeFollowedUsers: params.prioritizeFollowedUsers,
101
100
  })
102
101
  return { thread }
103
102
  }
@@ -33,6 +33,10 @@ export default function (server: Server, ctx: AppContext) {
33
33
  viewer,
34
34
  includeTakedowns,
35
35
  include3pBlocks,
36
+ featureGates: ctx.featureGates.checkGates(
37
+ [ctx.featureGates.ids.ThreadsV2ReplyRankingExploration],
38
+ ctx.featureGates.user({ did: viewer ?? '' }),
39
+ ),
36
40
  })
37
41
 
38
42
  return {
@@ -89,7 +93,6 @@ const presentation = (
89
93
  above: calculateAbove(ctx, params),
90
94
  below: calculateBelow(ctx, skeleton.anchor, params),
91
95
  branchingFactor: params.branchingFactor,
92
- prioritizeFollowedUsers: params.prioritizeFollowedUsers,
93
96
  sort: params.sort,
94
97
  })
95
98
 
@@ -234,7 +234,15 @@ export class AuthVerifier {
234
234
  )
235
235
  })
236
236
 
237
- const { sub, aud, scope } = res.payload
237
+ const { sub, aud, scope, cnf } = res.payload
238
+ if (typeof cnf !== 'undefined') {
239
+ // Proof-of-Possession (PoP) tokens are not allowed here
240
+ // https://www.rfc-editor.org/rfc/rfc7800.html
241
+ throw new AuthRequiredError(
242
+ 'Malformed token: DPoP not supported',
243
+ 'InvalidToken',
244
+ )
245
+ }
238
246
  if (typeof sub !== 'string' || !sub.startsWith('did:')) {
239
247
  throw new AuthRequiredError('Malformed token', 'InvalidToken')
240
248
  } else if (
@@ -388,12 +396,17 @@ export class AuthVerifier {
388
396
  const canPerformTakedown =
389
397
  (creds.credentials.type === 'role' && creds.credentials.admin) ||
390
398
  creds.credentials.type === 'mod_service'
399
+ const isModService =
400
+ creds.credentials.type === 'mod_service' ||
401
+ (creds.credentials.type === 'standard' &&
402
+ this.isModService(creds.credentials.iss))
391
403
 
392
404
  return {
393
405
  viewer,
394
406
  includeTakedowns: includeTakedownsAnd3pBlocks,
395
407
  include3pBlocks: includeTakedownsAnd3pBlocks,
396
408
  canPerformTakedown,
409
+ isModService,
397
410
  }
398
411
  }
399
412
  }