@atproto/bsky 0.0.195 → 0.0.197
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.
- package/CHANGELOG.md +29 -0
- package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/searchPosts.js +18 -3
- package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js +1 -0
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +6 -1
- package/dist/auth-verifier.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/feature-gates.d.ts +17 -6
- package/dist/feature-gates.d.ts.map +1 -1
- package/dist/feature-gates.js +24 -13
- package/dist/feature-gates.js.map +1 -1
- package/dist/hydration/feed.d.ts +4 -1
- package/dist/hydration/feed.d.ts.map +1 -1
- package/dist/hydration/feed.js +10 -2
- package/dist/hydration/feed.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +5 -2
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +17 -3
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +8 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +20 -0
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/views/index.d.ts +4 -0
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +31 -8
- package/dist/views/index.js.map +1 -1
- package/dist/views/threads-v2.d.ts +3 -1
- package/dist/views/threads-v2.d.ts.map +1 -1
- package/dist/views/threads-v2.js +122 -12
- package/dist/views/threads-v2.js.map +1 -1
- package/package.json +7 -7
- package/proto/bsky.proto +2 -0
- package/src/api/app/bsky/feed/searchPosts.ts +28 -2
- package/src/api/app/bsky/unspecced/getPostThreadV2.ts +4 -0
- package/src/auth-verifier.ts +9 -1
- package/src/config.ts +15 -0
- package/src/feature-gates.ts +28 -9
- package/src/hydration/feed.ts +17 -1
- package/src/hydration/hydrator.ts +17 -1
- package/src/index.ts +2 -0
- package/src/proto/bsky_pb.ts +12 -0
- package/src/views/index.ts +52 -22
- package/src/views/threads-v2.ts +156 -13
|
@@ -52,7 +52,7 @@ 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;
|
|
@@ -60,6 +60,8 @@ type SortTrimFlattenOptions = {
|
|
|
60
60
|
viewer: HydrateCtx['viewer'];
|
|
61
61
|
threadTagsBumpDown: readonly string[];
|
|
62
62
|
threadTagsHide: readonly string[];
|
|
63
|
+
visibilityTagRankPrefix: string;
|
|
63
64
|
};
|
|
65
|
+
export declare function sortTrimThreadTreeExploration(n: ThreadTree, opts: SortTrimFlattenOptions): ThreadTree;
|
|
64
66
|
export {};
|
|
65
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":"
|
|
1
|
+
{"version":3,"file":"threads-v2.d.ts","sourceRoot":"","sources":["../../src/views/threads-v2.ts"],"names":[],"mappings":"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"}
|
package/dist/views/threads-v2.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sortTrimFlattenThreadTree = sortTrimFlattenThreadTree;
|
|
4
|
-
|
|
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 =
|
|
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)) {
|
|
@@ -89,13 +89,6 @@ function applyBumping(aNode, bNode, opts) {
|
|
|
89
89
|
'down',
|
|
90
90
|
(i) => i.type === 'post' && threadTagsBumpDown.some((t) => i.tags.has(t)),
|
|
91
91
|
],
|
|
92
|
-
// Pushpin-only.
|
|
93
|
-
[
|
|
94
|
-
'down',
|
|
95
|
-
(i) => i.type === 'post' &&
|
|
96
|
-
isPostRecord(i.item.value.post.record) &&
|
|
97
|
-
i.item.value.post.record.text.trim() === '📌',
|
|
98
|
-
],
|
|
99
92
|
/*
|
|
100
93
|
Bumps within hidden replies.
|
|
101
94
|
This determines the order of hidden replies:
|
|
@@ -200,4 +193,121 @@ function* flattenInDirection({ tree, direction, }) {
|
|
|
200
193
|
}
|
|
201
194
|
}
|
|
202
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
|
+
}
|
|
203
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 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 { 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 // 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.
|
|
3
|
+
"version": "0.0.197",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Reference implementation of app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
"undici": "^6.19.8",
|
|
53
53
|
"zod": "3.23.8",
|
|
54
54
|
"@atproto-labs/fetch-node": "0.2.0",
|
|
55
|
-
"@atproto-labs/xrpc-utils": "0.0.
|
|
56
|
-
"@atproto/api": "^0.18.
|
|
55
|
+
"@atproto-labs/xrpc-utils": "0.0.23",
|
|
56
|
+
"@atproto/api": "^0.18.1",
|
|
57
57
|
"@atproto/common": "^0.4.12",
|
|
58
58
|
"@atproto/crypto": "^0.4.4",
|
|
59
59
|
"@atproto/did": "^0.2.1",
|
|
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.
|
|
63
|
+
"@atproto/sync": "^0.1.37",
|
|
64
64
|
"@atproto/syntax": "^0.4.1",
|
|
65
|
-
"@atproto/xrpc-server": "^0.9.
|
|
65
|
+
"@atproto/xrpc-server": "^0.9.6"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@bufbuild/buf": "^1.28.1",
|
|
@@ -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.18.
|
|
80
|
+
"@atproto/api": "^0.18.1",
|
|
81
81
|
"@atproto/lex-cli": "^0.9.6",
|
|
82
|
-
"@atproto/pds": "^0.4.
|
|
82
|
+
"@atproto/pds": "^0.4.194",
|
|
83
83
|
"@atproto/xrpc": "^0.7.5"
|
|
84
84
|
},
|
|
85
85
|
"scripts": {
|
package/proto/bsky.proto
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
PostSearchQuery,
|
|
8
8
|
parsePostSearchQuery,
|
|
9
9
|
} from '../../../../data-plane/server/util'
|
|
10
|
+
import { FeatureGateID } from '../../../../feature-gates'
|
|
10
11
|
import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator'
|
|
11
12
|
import { parseString } from '../../../../hydration/util'
|
|
12
13
|
import { Server } from '../../../../lexicon'
|
|
@@ -35,7 +36,14 @@ export default function (server: Server, ctx: AppContext) {
|
|
|
35
36
|
const { viewer, isModService } = ctx.authVerifier.parseCreds(auth)
|
|
36
37
|
|
|
37
38
|
const labelers = ctx.reqLabelers(req)
|
|
38
|
-
const hydrateCtx = await ctx.hydrator.createContext({
|
|
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
|
+
})
|
|
39
47
|
const results = await searchPosts(
|
|
40
48
|
{ ...params, hydrateCtx, isModService },
|
|
41
49
|
ctx,
|
|
@@ -99,6 +107,14 @@ const hydration = async (
|
|
|
99
107
|
return ctx.hydrator.hydratePosts(
|
|
100
108
|
skeleton.posts.map((uri) => ({ uri })),
|
|
101
109
|
params.hydrateCtx,
|
|
110
|
+
undefined,
|
|
111
|
+
{
|
|
112
|
+
processDynamicTagsForView: params.hydrateCtx.featureGates.get(
|
|
113
|
+
FeatureGateID.SearchFilteringExploration,
|
|
114
|
+
)
|
|
115
|
+
? 'search'
|
|
116
|
+
: undefined,
|
|
117
|
+
},
|
|
102
118
|
)
|
|
103
119
|
}
|
|
104
120
|
|
|
@@ -121,8 +137,18 @@ const noBlocksOrTagged = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
|
|
|
121
137
|
// Cases to never show.
|
|
122
138
|
if (ctx.views.viewerBlockExists(creator, hydration)) return false
|
|
123
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
|
+
|
|
124
151
|
// Cases to conditionally show based on tagging.
|
|
125
|
-
const tagged = [...ctx.cfg.searchTagsHide].some((t) => post.tags.has(t))
|
|
126
152
|
if (isCuratedSearch && tagged) return false
|
|
127
153
|
if (!parsedQuery.author && tagged) return false
|
|
128
154
|
return true
|
|
@@ -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 {
|
package/src/auth-verifier.ts
CHANGED
|
@@ -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 (
|
package/src/config.ts
CHANGED
|
@@ -71,6 +71,8 @@ export interface ServerConfigValues {
|
|
|
71
71
|
maxThreadParents: number
|
|
72
72
|
threadTagsHide: Set<string>
|
|
73
73
|
threadTagsBumpDown: Set<string>
|
|
74
|
+
visibilityTagHide: string
|
|
75
|
+
visibilityTagRankPrefix: string
|
|
74
76
|
// notifications
|
|
75
77
|
notificationsDelayMs?: number
|
|
76
78
|
// client config
|
|
@@ -216,6 +218,9 @@ export class ServerConfig {
|
|
|
216
218
|
const threadTagsBumpDown = new Set(
|
|
217
219
|
envList(process.env.BSKY_THREAD_TAGS_BUMP_DOWN),
|
|
218
220
|
)
|
|
221
|
+
const visibilityTagHide = process.env.BSKY_VISIBILITY_TAG_HIDE || ''
|
|
222
|
+
const visibilityTagRankPrefix =
|
|
223
|
+
process.env.BSKY_VISIBILITY_TAG_RANK_PREFIX || ''
|
|
219
224
|
|
|
220
225
|
const notificationsDelayMs = process.env.BSKY_NOTIFICATIONS_DELAY_MS
|
|
221
226
|
? parseInt(process.env.BSKY_NOTIFICATIONS_DELAY_MS || '', 10)
|
|
@@ -333,6 +338,8 @@ export class ServerConfig {
|
|
|
333
338
|
maxThreadParents,
|
|
334
339
|
threadTagsHide,
|
|
335
340
|
threadTagsBumpDown,
|
|
341
|
+
visibilityTagHide,
|
|
342
|
+
visibilityTagRankPrefix,
|
|
336
343
|
notificationsDelayMs,
|
|
337
344
|
disableSsrfProtection,
|
|
338
345
|
proxyAllowHTTP2,
|
|
@@ -551,6 +558,14 @@ export class ServerConfig {
|
|
|
551
558
|
return this.cfg.threadTagsBumpDown
|
|
552
559
|
}
|
|
553
560
|
|
|
561
|
+
get visibilityTagHide() {
|
|
562
|
+
return this.cfg.visibilityTagHide
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
get visibilityTagRankPrefix() {
|
|
566
|
+
return this.cfg.visibilityTagRankPrefix
|
|
567
|
+
}
|
|
568
|
+
|
|
554
569
|
get notificationsDelayMs() {
|
|
555
570
|
return this.cfg.notificationsDelayMs ?? 0
|
|
556
571
|
}
|
package/src/feature-gates.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Statsig, { StatsigUser } from 'statsig-node'
|
|
2
|
-
import { sha256Hex } from '@atproto/crypto'
|
|
3
2
|
import { featureGatesLogger } from './logger'
|
|
4
3
|
|
|
5
4
|
export type Config = {
|
|
@@ -7,21 +6,28 @@ export type Config = {
|
|
|
7
6
|
env?: 'development' | 'staging' | 'production' | string
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export enum
|
|
9
|
+
export enum FeatureGateID {
|
|
11
10
|
/**
|
|
12
11
|
* Left here ensure this is interpreted as a string enum and therefore
|
|
13
12
|
* appease TS
|
|
14
13
|
*/
|
|
15
14
|
_ = '',
|
|
15
|
+
ThreadsV2ReplyRankingExploration = 'threads_v2_reply_ranking_exploration',
|
|
16
|
+
SearchFilteringExploration = 'search_filtering_exploration',
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Pre-evaluated feature gates map, the result of `FeatureGates.checkGates()`
|
|
21
|
+
*/
|
|
22
|
+
export type CheckedFeatureGatesMap = Map<FeatureGateID, boolean>
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* @see https://docs.statsig.com/server/nodejsServerSDK
|
|
20
26
|
*/
|
|
21
27
|
export class FeatureGates {
|
|
22
28
|
ready = false
|
|
23
29
|
private statsig = Statsig
|
|
24
|
-
ids =
|
|
30
|
+
ids = FeatureGateID
|
|
25
31
|
|
|
26
32
|
constructor(private config: Config) {}
|
|
27
33
|
|
|
@@ -54,15 +60,28 @@ export class FeatureGates {
|
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
user({ did }: { did?: string }): StatsigUser | undefined {
|
|
64
|
+
return did
|
|
65
|
+
? {
|
|
66
|
+
userID: did,
|
|
67
|
+
}
|
|
68
|
+
: undefined
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
check(
|
|
71
|
+
check(gate: FeatureGateID, user?: StatsigUser): boolean {
|
|
65
72
|
if (!this.ready) return false
|
|
73
|
+
if (!user) return false
|
|
66
74
|
return this.statsig.checkGateSync(user, gate)
|
|
67
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pre-evaluate multiple feature gates for a given user, returning a map of
|
|
79
|
+
* gate ID to boolean result.
|
|
80
|
+
*/
|
|
81
|
+
checkGates(
|
|
82
|
+
gates: FeatureGateID[],
|
|
83
|
+
user?: StatsigUser,
|
|
84
|
+
): CheckedFeatureGatesMap {
|
|
85
|
+
return new Map(gates.map((g) => [g, this.check(g, user)]))
|
|
86
|
+
}
|
|
68
87
|
}
|
package/src/hydration/feed.ts
CHANGED
|
@@ -102,6 +102,10 @@ export type FeedItem = {
|
|
|
102
102
|
authorPinned?: boolean
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export type GetPostsHydrationOptions = {
|
|
106
|
+
processDynamicTagsForView?: 'thread' | 'search'
|
|
107
|
+
}
|
|
108
|
+
|
|
105
109
|
export class FeedHydrator {
|
|
106
110
|
constructor(public dataplane: DataPlaneClient) {}
|
|
107
111
|
|
|
@@ -109,6 +113,8 @@ export class FeedHydrator {
|
|
|
109
113
|
uris: string[],
|
|
110
114
|
includeTakedowns = false,
|
|
111
115
|
given = new HydrationMap<Post>(),
|
|
116
|
+
viewer?: string | null,
|
|
117
|
+
options: GetPostsHydrationOptions = {},
|
|
112
118
|
): Promise<Posts> {
|
|
113
119
|
const [have, need] = split(uris, (uri) => given.has(uri))
|
|
114
120
|
const base = have.reduce(
|
|
@@ -116,7 +122,17 @@ export class FeedHydrator {
|
|
|
116
122
|
new HydrationMap<Post>(),
|
|
117
123
|
)
|
|
118
124
|
if (!need.length) return base
|
|
119
|
-
const res = await this.dataplane.getPostRecords(
|
|
125
|
+
const res = await this.dataplane.getPostRecords(
|
|
126
|
+
options.processDynamicTagsForView
|
|
127
|
+
? {
|
|
128
|
+
uris: need,
|
|
129
|
+
viewerDid: viewer ?? undefined,
|
|
130
|
+
processDynamicTagsForView: options.processDynamicTagsForView,
|
|
131
|
+
}
|
|
132
|
+
: {
|
|
133
|
+
uris: need,
|
|
134
|
+
},
|
|
135
|
+
)
|
|
120
136
|
return need.reduce((acc, uri, i) => {
|
|
121
137
|
const record = parseRecord<PostRecord>(res.records[i], includeTakedowns)
|
|
122
138
|
const violatesThreadGate = res.meta[i].violatesThreadGate
|
|
@@ -2,6 +2,7 @@ import assert from 'node:assert'
|
|
|
2
2
|
import { mapDefined } from '@atproto/common'
|
|
3
3
|
import { AtUri } from '@atproto/syntax'
|
|
4
4
|
import { DataPlaneClient } from '../data-plane/client'
|
|
5
|
+
import { type CheckedFeatureGatesMap, FeatureGateID } from '../feature-gates'
|
|
5
6
|
import { ids } from '../lexicon/lexicons'
|
|
6
7
|
import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile'
|
|
7
8
|
import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record'
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
FeedGens,
|
|
32
33
|
FeedHydrator,
|
|
33
34
|
FeedItem,
|
|
35
|
+
type GetPostsHydrationOptions,
|
|
34
36
|
Likes,
|
|
35
37
|
Post,
|
|
36
38
|
PostAggs,
|
|
@@ -81,6 +83,7 @@ export class HydrateCtx {
|
|
|
81
83
|
includeActorTakedowns = this.vals.includeActorTakedowns
|
|
82
84
|
include3pBlocks = this.vals.include3pBlocks
|
|
83
85
|
includeDebugField = this.vals.includeDebugField
|
|
86
|
+
featureGates: CheckedFeatureGatesMap = this.vals.featureGates || new Map()
|
|
84
87
|
constructor(private vals: HydrateCtxVals) {}
|
|
85
88
|
// Convenience with use with dataplane.getActors cache control
|
|
86
89
|
get skipCacheForViewer() {
|
|
@@ -99,6 +102,7 @@ export type HydrateCtxVals = {
|
|
|
99
102
|
includeActorTakedowns?: boolean
|
|
100
103
|
include3pBlocks?: boolean
|
|
101
104
|
includeDebugField?: boolean
|
|
105
|
+
featureGates?: CheckedFeatureGatesMap
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
export type HydrationState = {
|
|
@@ -444,6 +448,7 @@ export class Hydrator {
|
|
|
444
448
|
refs: ItemRef[],
|
|
445
449
|
ctx: HydrateCtx,
|
|
446
450
|
state: HydrationState = {},
|
|
451
|
+
options: Pick<GetPostsHydrationOptions, 'processDynamicTagsForView'> = {},
|
|
447
452
|
): Promise<HydrationState> {
|
|
448
453
|
const uris = refs.map((ref) => ref.uri)
|
|
449
454
|
|
|
@@ -460,6 +465,10 @@ export class Hydrator {
|
|
|
460
465
|
uris,
|
|
461
466
|
ctx.includeTakedowns,
|
|
462
467
|
state.posts,
|
|
468
|
+
ctx.viewer,
|
|
469
|
+
{
|
|
470
|
+
processDynamicTagsForView: options.processDynamicTagsForView,
|
|
471
|
+
},
|
|
463
472
|
)
|
|
464
473
|
addPostsToHydrationState(postsLayer0)
|
|
465
474
|
|
|
@@ -731,7 +740,13 @@ export class Hydrator {
|
|
|
731
740
|
refs: ItemRef[],
|
|
732
741
|
ctx: HydrateCtx,
|
|
733
742
|
): Promise<HydrationState> {
|
|
734
|
-
const postsState = await this.hydratePosts(refs, ctx
|
|
743
|
+
const postsState = await this.hydratePosts(refs, ctx, undefined, {
|
|
744
|
+
processDynamicTagsForView: ctx.featureGates.get(
|
|
745
|
+
FeatureGateID.ThreadsV2ReplyRankingExploration,
|
|
746
|
+
)
|
|
747
|
+
? 'thread'
|
|
748
|
+
: undefined,
|
|
749
|
+
})
|
|
735
750
|
|
|
736
751
|
const { posts } = postsState
|
|
737
752
|
const postsList = posts ? Array.from(posts.entries()) : []
|
|
@@ -1304,6 +1319,7 @@ export class Hydrator {
|
|
|
1304
1319
|
includeTakedowns: vals.includeTakedowns,
|
|
1305
1320
|
include3pBlocks: vals.include3pBlocks,
|
|
1306
1321
|
includeDebugField,
|
|
1322
|
+
featureGates: vals.featureGates,
|
|
1307
1323
|
})
|
|
1308
1324
|
}
|
|
1309
1325
|
|