@atproto/bsky 0.0.194 → 0.0.195

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 (73) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
  3. package/dist/api/app/bsky/feed/searchPosts.js +43 -12
  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.js +0 -1
  8. package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  9. package/dist/auth-verifier.d.ts +1 -0
  10. package/dist/auth-verifier.d.ts.map +1 -1
  11. package/dist/auth-verifier.js +4 -0
  12. package/dist/auth-verifier.js.map +1 -1
  13. package/dist/config.d.ts +2 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +5 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/data-plane/server/routes/search.d.ts.map +1 -1
  18. package/dist/data-plane/server/routes/search.js +15 -1
  19. package/dist/data-plane/server/routes/search.js.map +1 -1
  20. package/dist/data-plane/server/util.d.ts +7 -0
  21. package/dist/data-plane/server/util.d.ts.map +1 -1
  22. package/dist/data-plane/server/util.js +38 -1
  23. package/dist/data-plane/server/util.js.map +1 -1
  24. package/dist/lexicon/index.d.ts +2 -0
  25. package/dist/lexicon/index.d.ts.map +1 -1
  26. package/dist/lexicon/index.js +4 -0
  27. package/dist/lexicon/index.js.map +1 -1
  28. package/dist/lexicon/lexicons.d.ts +98 -28
  29. package/dist/lexicon/lexicons.d.ts.map +1 -1
  30. package/dist/lexicon/lexicons.js +52 -14
  31. package/dist/lexicon/lexicons.js.map +1 -1
  32. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +0 -2
  33. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  34. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  35. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.d.ts +0 -2
  36. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -1
  37. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -1
  38. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts +0 -2
  39. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
  40. package/dist/lexicon/types/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  41. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.d.ts +28 -0
  42. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.d.ts.map +1 -0
  43. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.js +7 -0
  44. package/dist/lexicon/types/com/atproto/lexicon/resolveLexicon.js.map +1 -0
  45. package/dist/views/index.d.ts +2 -4
  46. package/dist/views/index.d.ts.map +1 -1
  47. package/dist/views/index.js +8 -14
  48. package/dist/views/index.js.map +1 -1
  49. package/dist/views/threads-v2.d.ts +0 -1
  50. package/dist/views/threads-v2.d.ts.map +1 -1
  51. package/dist/views/threads-v2.js +2 -4
  52. package/dist/views/threads-v2.js.map +1 -1
  53. package/package.json +5 -5
  54. package/src/api/app/bsky/feed/searchPosts.ts +51 -10
  55. package/src/api/app/bsky/unspecced/getPostThreadOtherV2.ts +1 -2
  56. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +0 -1
  57. package/src/auth-verifier.ts +5 -0
  58. package/src/config.ts +8 -0
  59. package/src/data-plane/server/routes/search.ts +18 -1
  60. package/src/data-plane/server/util.ts +51 -0
  61. package/src/lexicon/index.ts +13 -0
  62. package/src/lexicon/lexicons.ts +52 -16
  63. package/src/lexicon/types/app/bsky/actor/defs.ts +0 -2
  64. package/src/lexicon/types/app/bsky/unspecced/getPostThreadOtherV2.ts +0 -2
  65. package/src/lexicon/types/app/bsky/unspecced/getPostThreadV2.ts +0 -2
  66. package/src/lexicon/types/com/atproto/lexicon/resolveLexicon.ts +46 -0
  67. package/src/views/index.ts +3 -23
  68. package/src/views/threads-v2.ts +2 -12
  69. package/tests/utils.test.ts +45 -0
  70. package/tests/views/post-search.test.ts +221 -0
  71. package/tests/views/thread-v2.test.ts +2 -109
  72. package/tsconfig.build.tsbuildinfo +1 -1
  73. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -56,7 +56,6 @@ export declare function sortTrimFlattenThreadTree(anchorTree: ThreadTree, option
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[];
@@ -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":"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"}
@@ -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,9 +82,7 @@ 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
  [
@@ -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 { 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.194",
3
+ "version": "0.0.195",
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",
56
+ "@atproto/api": "^0.18.0",
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.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.192",
83
83
  "@atproto/xrpc": "^0.7.5"
84
84
  },
85
85
  "scripts": {
@@ -1,7 +1,12 @@
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'
5
10
  import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator'
6
11
  import { parseString } from '../../../../hydration/util'
7
12
  import { Server } from '../../../../lexicon'
@@ -21,16 +26,20 @@ export default function (server: Server, ctx: AppContext) {
21
26
  const searchPosts = createPipeline(
22
27
  skeleton,
23
28
  hydration,
24
- noBlocks,
29
+ noBlocksOrTagged,
25
30
  presentation,
26
31
  )
27
32
  server.app.bsky.feed.searchPosts({
28
33
  auth: ctx.authVerifier.standardOptional,
29
34
  handler: async ({ auth, params, req }) => {
30
- const viewer = auth.credentials.iss
35
+ const { viewer, isModService } = ctx.authVerifier.parseCreds(auth)
36
+
31
37
  const labelers = ctx.reqLabelers(req)
32
38
  const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer })
33
- const results = await searchPosts({ ...params, hydrateCtx }, ctx)
39
+ const results = await searchPosts(
40
+ { ...params, hydrateCtx, isModService },
41
+ ctx,
42
+ )
34
43
  return {
35
44
  encoding: 'application/json',
36
45
  body: results,
@@ -42,6 +51,9 @@ export default function (server: Server, ctx: AppContext) {
42
51
 
43
52
  const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
44
53
  const { ctx, params } = inputs
54
+ const parsedQuery = parsePostSearchQuery(params.q, {
55
+ author: params.author,
56
+ })
45
57
 
46
58
  if (ctx.searchAgent) {
47
59
  // @NOTE cursors won't change on appview swap
@@ -64,6 +76,7 @@ const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
64
76
  return {
65
77
  posts: res.posts.map(({ uri }) => uri),
66
78
  cursor: parseString(res.cursor),
79
+ parsedQuery,
67
80
  }
68
81
  }
69
82
 
@@ -75,6 +88,7 @@ const skeleton = async (inputs: SkeletonFnInput<Context, Params>) => {
75
88
  return {
76
89
  posts: res.uris,
77
90
  cursor: parseString(res.cursor),
91
+ parsedQuery,
78
92
  }
79
93
  }
80
94
 
@@ -88,11 +102,30 @@ const hydration = async (
88
102
  )
89
103
  }
90
104
 
91
- const noBlocks = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
92
- const { ctx, skeleton, hydration } = inputs
105
+ const noBlocksOrTagged = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
106
+ const { ctx, params, skeleton, hydration } = inputs
107
+ const { parsedQuery } = skeleton
108
+
93
109
  skeleton.posts = skeleton.posts.filter((uri) => {
110
+ const post = hydration.posts?.get(uri)
111
+ if (!post) return
112
+
94
113
  const creator = creatorFromUri(uri)
95
- return !ctx.views.viewerBlockExists(creator, hydration)
114
+ const isCuratedSearch = params.sort === 'top'
115
+ const isPostByViewer = creator === params.hydrateCtx.viewer
116
+
117
+ // Cases to always show.
118
+ if (isPostByViewer) return true
119
+ if (params.isModService) return true
120
+
121
+ // Cases to never show.
122
+ if (ctx.views.viewerBlockExists(creator, hydration)) return false
123
+
124
+ // Cases to conditionally show based on tagging.
125
+ const tagged = [...ctx.cfg.searchTagsHide].some((t) => post.tags.has(t))
126
+ if (isCuratedSearch && tagged) return false
127
+ if (!parsedQuery.author && tagged) return false
128
+ return true
96
129
  })
97
130
  return skeleton
98
131
  }
@@ -101,9 +134,12 @@ const presentation = (
101
134
  inputs: PresentationFnInput<Context, Params, Skeleton>,
102
135
  ) => {
103
136
  const { ctx, skeleton, hydration } = inputs
104
- const posts = mapDefined(skeleton.posts, (uri) =>
105
- ctx.views.post(uri, hydration),
106
- )
137
+ const posts = mapDefined(skeleton.posts, (uri) => {
138
+ const post = hydration.posts?.get(uri)
139
+ if (!post) return
140
+
141
+ return ctx.views.post(uri, hydration)
142
+ })
107
143
  return {
108
144
  posts,
109
145
  cursor: skeleton.cursor,
@@ -112,16 +148,21 @@ const presentation = (
112
148
  }
113
149
 
114
150
  type Context = {
151
+ cfg: ServerConfig
115
152
  dataplane: DataPlaneClient
116
153
  hydrator: Hydrator
117
154
  views: Views
118
155
  searchAgent?: AtpAgent
119
156
  }
120
157
 
121
- type Params = QueryParams & { hydrateCtx: HydrateCtx }
158
+ type Params = QueryParams & {
159
+ hydrateCtx: HydrateCtx
160
+ isModService: boolean
161
+ }
122
162
 
123
163
  type Skeleton = {
124
164
  posts: string[]
125
165
  hitsTotal?: number
126
166
  cursor?: string
167
+ parsedQuery: PostSearchQuery
127
168
  }
@@ -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
  }
@@ -89,7 +89,6 @@ const presentation = (
89
89
  above: calculateAbove(ctx, params),
90
90
  below: calculateBelow(ctx, skeleton.anchor, params),
91
91
  branchingFactor: params.branchingFactor,
92
- prioritizeFollowedUsers: params.prioritizeFollowedUsers,
93
92
  sort: params.sort,
94
93
  })
95
94
 
@@ -388,12 +388,17 @@ export class AuthVerifier {
388
388
  const canPerformTakedown =
389
389
  (creds.credentials.type === 'role' && creds.credentials.admin) ||
390
390
  creds.credentials.type === 'mod_service'
391
+ const isModService =
392
+ creds.credentials.type === 'mod_service' ||
393
+ (creds.credentials.type === 'standard' &&
394
+ this.isModService(creds.credentials.iss))
391
395
 
392
396
  return {
393
397
  viewer,
394
398
  includeTakedowns: includeTakedownsAnd3pBlocks,
395
399
  include3pBlocks: includeTakedownsAnd3pBlocks,
396
400
  canPerformTakedown,
401
+ isModService,
397
402
  }
398
403
  }
399
404
  }
package/src/config.ts CHANGED
@@ -42,6 +42,7 @@ export interface ServerConfigValues {
42
42
  courierHttpVersion?: '1.1' | '2'
43
43
  courierIgnoreBadTls?: boolean
44
44
  searchUrl?: string
45
+ searchTagsHide: Set<string>
45
46
  suggestionsUrl?: string
46
47
  suggestionsApiKey?: string
47
48
  topicsUrl?: string
@@ -133,6 +134,7 @@ export class ServerConfig {
133
134
  process.env.BSKY_SEARCH_URL ||
134
135
  process.env.BSKY_SEARCH_ENDPOINT ||
135
136
  undefined
137
+ const searchTagsHide = new Set(envList(process.env.BSKY_SEARCH_TAGS_HIDE))
136
138
  const suggestionsUrl = process.env.BSKY_SUGGESTIONS_URL || undefined
137
139
  const suggestionsApiKey = process.env.BSKY_SUGGESTIONS_API_KEY || undefined
138
140
  const topicsUrl = process.env.BSKY_TOPICS_URL || undefined
@@ -296,6 +298,7 @@ export class ServerConfig {
296
298
  dataplaneHttpVersion,
297
299
  dataplaneIgnoreBadTls,
298
300
  searchUrl,
301
+ searchTagsHide,
299
302
  suggestionsUrl,
300
303
  suggestionsApiKey,
301
304
  topicsUrl,
@@ -440,6 +443,10 @@ export class ServerConfig {
440
443
  return this.cfg.searchUrl
441
444
  }
442
445
 
446
+ get searchTagsHide() {
447
+ return this.cfg.searchTagsHide
448
+ }
449
+
443
450
  get suggestionsUrl() {
444
451
  return this.cfg.suggestionsUrl
445
452
  }
@@ -539,6 +546,7 @@ export class ServerConfig {
539
546
  get threadTagsHide() {
540
547
  return this.cfg.threadTagsHide
541
548
  }
549
+
542
550
  get threadTagsBumpDown() {
543
551
  return this.cfg.threadTagsBumpDown
544
552
  }
@@ -2,6 +2,7 @@ import { ServiceImpl } from '@connectrpc/connect'
2
2
  import { Service } from '../../../proto/bsky_connect'
3
3
  import { Database } from '../db'
4
4
  import { IndexedAtDidKeyset, TimeCidKeyset, paginate } from '../db/pagination'
5
+ import { parsePostSearchQuery } from '../util'
5
6
 
6
7
  export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
7
8
  // @TODO actor search endpoints still fall back to search service
@@ -35,12 +36,28 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
35
36
  // @TODO post search endpoint still falls back to search service
36
37
  async searchPosts(req) {
37
38
  const { term, limit, cursor } = req
39
+ const { q, author } = parsePostSearchQuery(term)
40
+
41
+ let authorDid = author
42
+ if (author && !author?.startsWith('did:')) {
43
+ const res = await db.db
44
+ .selectFrom('actor')
45
+ .where('handle', '=', author)
46
+ .selectAll()
47
+ .executeTakeFirst()
48
+ authorDid = res?.did
49
+ }
50
+
38
51
  const { ref } = db.db.dynamic
39
52
  let builder = db.db
40
53
  .selectFrom('post')
41
- .where('post.text', 'like', `%${term}%`)
54
+ .where('post.text', 'like', `%${q}%`)
42
55
  .selectAll()
43
56
 
57
+ if (authorDid) {
58
+ builder = builder.where('post.creator', '=', authorDid)
59
+ }
60
+
44
61
  const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid'))
45
62
  builder = paginate(builder, {
46
63
  limit,
@@ -153,3 +153,54 @@ export const violatesThreadGate = async (
153
153
 
154
154
  return true
155
155
  }
156
+
157
+ // @NOTE: This type is not complete with all supported options.
158
+ // Only the ones that we needed to apply custom logic on are currently present.
159
+ export type PostSearchQuery = {
160
+ q: string
161
+ author: string | undefined
162
+ }
163
+
164
+ export const parsePostSearchQuery = (
165
+ qParam: string,
166
+ params?: {
167
+ author?: string
168
+ },
169
+ ): PostSearchQuery => {
170
+ // Accept individual params, but give preference to options embedded in `q`.
171
+ let author = params?.author
172
+
173
+ const parts: string[] = []
174
+ let curr = ''
175
+ let quoted = false
176
+ for (const c of qParam) {
177
+ if (c === ' ' && !quoted) {
178
+ curr.trim() && parts.push(curr)
179
+ curr = ''
180
+ continue
181
+ }
182
+
183
+ if (c === '"') {
184
+ quoted = !quoted
185
+ }
186
+ curr += c
187
+ }
188
+ curr.trim() && parts.push(curr)
189
+
190
+ const qParts: string[] = []
191
+ for (const p of parts) {
192
+ const tokens = p.split(':')
193
+ if (tokens[0] === 'did') {
194
+ author = p
195
+ } else if (tokens[0] === 'author' || tokens[0] === 'from') {
196
+ author = tokens[1]
197
+ } else {
198
+ qParts.push(p)
199
+ }
200
+ }
201
+
202
+ return {
203
+ q: qParts.join(' '),
204
+ author,
205
+ }
206
+ }
@@ -145,6 +145,7 @@ import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/ident
145
145
  import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle.js'
146
146
  import * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels.js'
147
147
  import * as ComAtprotoLabelSubscribeLabels from './types/com/atproto/label/subscribeLabels.js'
148
+ import * as ComAtprotoLexiconResolveLexicon from './types/com/atproto/lexicon/resolveLexicon.js'
148
149
  import * as ComAtprotoModerationCreateReport from './types/com/atproto/moderation/createReport.js'
149
150
  import * as ComAtprotoRepoApplyWrites from './types/com/atproto/repo/applyWrites.js'
150
151
  import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createRecord.js'
@@ -2108,6 +2109,18 @@ export class ComAtprotoLexiconNS {
2108
2109
  constructor(server: Server) {
2109
2110
  this._server = server
2110
2111
  }
2112
+
2113
+ resolveLexicon<A extends Auth = void>(
2114
+ cfg: MethodConfigOrHandler<
2115
+ A,
2116
+ ComAtprotoLexiconResolveLexicon.QueryParams,
2117
+ ComAtprotoLexiconResolveLexicon.HandlerInput,
2118
+ ComAtprotoLexiconResolveLexicon.HandlerOutput
2119
+ >,
2120
+ ) {
2121
+ const nsid = 'com.atproto.lexicon.resolveLexicon' // @ts-ignore
2122
+ return this._server.xrpc.method(nsid, cfg)
2123
+ }
2111
2124
  }
2112
2125
 
2113
2126
  export class ComAtprotoModerationNS {
@@ -555,10 +555,6 @@ export const schemaDict = {
555
555
  'hotness',
556
556
  ],
557
557
  },
558
- prioritizeFollowedUsers: {
559
- type: 'boolean',
560
- description: 'Show followed users at the top of all replies.',
561
- },
562
558
  },
563
559
  },
564
560
  interestsPref: {
@@ -6799,12 +6795,6 @@ export const schemaDict = {
6799
6795
  description:
6800
6796
  'Reference (AT-URI) to post record. This is the anchor post.',
6801
6797
  },
6802
- prioritizeFollowedUsers: {
6803
- type: 'boolean',
6804
- description:
6805
- 'Whether to prioritize posts from followed users. It only has effect when the user is authenticated.',
6806
- default: false,
6807
- },
6808
6798
  },
6809
6799
  },
6810
6800
  output: {
@@ -6886,12 +6876,6 @@ export const schemaDict = {
6886
6876
  minimum: 0,
6887
6877
  maximum: 100,
6888
6878
  },
6889
- prioritizeFollowedUsers: {
6890
- type: 'boolean',
6891
- description:
6892
- 'Whether to prioritize posts from followed users. It only has effect when the user is authenticated.',
6893
- default: false,
6894
- },
6895
6879
  sort: {
6896
6880
  type: 'string',
6897
6881
  description: 'Sorting for the thread replies.',
@@ -10531,6 +10515,57 @@ export const schemaDict = {
10531
10515
  },
10532
10516
  },
10533
10517
  },
10518
+ ComAtprotoLexiconResolveLexicon: {
10519
+ lexicon: 1,
10520
+ id: 'com.atproto.lexicon.resolveLexicon',
10521
+ defs: {
10522
+ main: {
10523
+ type: 'query',
10524
+ description: 'Resolves an atproto lexicon (NSID) to a schema.',
10525
+ parameters: {
10526
+ type: 'params',
10527
+ properties: {
10528
+ nsid: {
10529
+ format: 'nsid',
10530
+ type: 'string',
10531
+ description: 'The lexicon NSID to resolve.',
10532
+ },
10533
+ },
10534
+ required: ['nsid'],
10535
+ },
10536
+ output: {
10537
+ encoding: 'application/json',
10538
+ schema: {
10539
+ type: 'object',
10540
+ properties: {
10541
+ cid: {
10542
+ type: 'string',
10543
+ format: 'cid',
10544
+ description: 'The CID of the lexicon schema record.',
10545
+ },
10546
+ schema: {
10547
+ type: 'ref',
10548
+ ref: 'lex:com.atproto.lexicon.schema#main',
10549
+ description: 'The resolved lexicon schema record.',
10550
+ },
10551
+ uri: {
10552
+ type: 'string',
10553
+ format: 'at-uri',
10554
+ description: 'The AT-URI of the lexicon schema record.',
10555
+ },
10556
+ },
10557
+ required: ['uri', 'cid', 'schema'],
10558
+ },
10559
+ },
10560
+ errors: [
10561
+ {
10562
+ description: 'No lexicon was resolved for the NSID.',
10563
+ name: 'LexiconNotFound',
10564
+ },
10565
+ ],
10566
+ },
10567
+ },
10568
+ },
10534
10569
  ComAtprotoLexiconSchema: {
10535
10570
  lexicon: 1,
10536
10571
  id: 'com.atproto.lexicon.schema',
@@ -14177,6 +14212,7 @@ export const ids = {
14177
14212
  ComAtprotoLabelDefs: 'com.atproto.label.defs',
14178
14213
  ComAtprotoLabelQueryLabels: 'com.atproto.label.queryLabels',
14179
14214
  ComAtprotoLabelSubscribeLabels: 'com.atproto.label.subscribeLabels',
14215
+ ComAtprotoLexiconResolveLexicon: 'com.atproto.lexicon.resolveLexicon',
14180
14216
  ComAtprotoLexiconSchema: 'com.atproto.lexicon.schema',
14181
14217
  ComAtprotoModerationCreateReport: 'com.atproto.moderation.createReport',
14182
14218
  ComAtprotoModerationDefs: 'com.atproto.moderation.defs',
@@ -406,8 +406,6 @@ export interface ThreadViewPref {
406
406
  | 'random'
407
407
  | 'hotness'
408
408
  | (string & {})
409
- /** Show followed users at the top of all replies. */
410
- prioritizeFollowedUsers?: boolean
411
409
  }
412
410
 
413
411
  const hashThreadViewPref = 'threadViewPref'
@@ -18,8 +18,6 @@ const id = 'app.bsky.unspecced.getPostThreadOtherV2'
18
18
  export type QueryParams = {
19
19
  /** Reference (AT-URI) to post record. This is the anchor post. */
20
20
  anchor: string
21
- /** Whether to prioritize posts from followed users. It only has effect when the user is authenticated. */
22
- prioritizeFollowedUsers: boolean
23
21
  }
24
22
  export type InputSchema = undefined
25
23
 
@@ -25,8 +25,6 @@ export type QueryParams = {
25
25
  below: number
26
26
  /** Maximum of replies to include at each level of the thread, except for the direct replies to the anchor, which are (NOTE: currently, during unspecced phase) all returned (NOTE: later they might be paginated). */
27
27
  branchingFactor: number
28
- /** Whether to prioritize posts from followed users. It only has effect when the user is authenticated. */
29
- prioritizeFollowedUsers: boolean
30
28
  /** Sorting for the thread replies. */
31
29
  sort: 'newest' | 'oldest' | 'top' | (string & {})
32
30
  }