@atproto/bsky 0.1.7 → 0.2.1

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 (143) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/api/app/bsky/actor/getProfile.js +1 -1
  3. package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
  4. package/dist/api/app/bsky/actor/getProfiles.js +1 -1
  5. package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
  6. package/dist/api/app/bsky/actor/getSuggestions.js +2 -2
  7. package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
  8. package/dist/api/app/bsky/embed/getEmbedExternalView.d.ts.map +1 -1
  9. package/dist/api/app/bsky/embed/getEmbedExternalView.js +132 -53
  10. package/dist/api/app/bsky/embed/getEmbedExternalView.js.map +1 -1
  11. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  12. package/dist/api/app/bsky/feed/getAuthorFeed.js +4 -2
  13. package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
  14. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +2 -2
  15. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
  16. package/dist/api/app/bsky/notification/constants.d.ts +4 -1
  17. package/dist/api/app/bsky/notification/constants.d.ts.map +1 -1
  18. package/dist/api/app/bsky/notification/constants.js +3 -0
  19. package/dist/api/app/bsky/notification/constants.js.map +1 -1
  20. package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts +1 -1
  21. package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
  22. package/dist/api/app/bsky/notification/getGroupedNotifications.js +44 -26
  23. package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
  24. package/dist/api/app/bsky/notification/grouping/spotlight.d.ts +1 -1
  25. package/dist/api/app/bsky/notification/grouping/spotlight.d.ts.map +1 -1
  26. package/dist/api/app/bsky/notification/grouping/spotlight.js +114 -80
  27. package/dist/api/app/bsky/notification/grouping/spotlight.js.map +1 -1
  28. package/dist/api/app/bsky/notification/listActivitySubscriptions.js +3 -2
  29. package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -1
  30. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.d.ts.map +1 -1
  31. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js +16 -148
  32. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js.map +1 -1
  33. package/dist/api/internal/bsky/actor/getProfiles.js +4 -1
  34. package/dist/api/internal/bsky/actor/getProfiles.js.map +1 -1
  35. package/dist/config.d.ts +10 -0
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +43 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/data-plane/server/indexing/index.d.ts +3 -1
  40. package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
  41. package/dist/data-plane/server/indexing/index.js +2 -0
  42. package/dist/data-plane/server/indexing/index.js.map +1 -1
  43. package/dist/data-plane/server/indexing/plugins/profile-link.d.ts +8 -0
  44. package/dist/data-plane/server/indexing/plugins/profile-link.d.ts.map +1 -0
  45. package/dist/data-plane/server/indexing/plugins/profile-link.js +30 -0
  46. package/dist/data-plane/server/indexing/plugins/profile-link.js.map +1 -0
  47. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  48. package/dist/data-plane/server/routes/records.js +1 -0
  49. package/dist/data-plane/server/routes/records.js.map +1 -1
  50. package/dist/hydration/actor.d.ts +4 -1
  51. package/dist/hydration/actor.d.ts.map +1 -1
  52. package/dist/hydration/actor.js +11 -0
  53. package/dist/hydration/actor.js.map +1 -1
  54. package/dist/hydration/external.d.ts +67 -64
  55. package/dist/hydration/external.d.ts.map +1 -1
  56. package/dist/hydration/external.js +79 -133
  57. package/dist/hydration/external.js.map +1 -1
  58. package/dist/hydration/hydrator.d.ts +53 -14
  59. package/dist/hydration/hydrator.d.ts.map +1 -1
  60. package/dist/hydration/hydrator.js +422 -105
  61. package/dist/hydration/hydrator.js.map +1 -1
  62. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +25 -0
  63. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  64. package/dist/lexicons/app/bsky/actor/defs.defs.js +16 -0
  65. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  66. package/dist/lexicons/app/bsky/actor/link.d.ts +3 -0
  67. package/dist/lexicons/app/bsky/actor/link.d.ts.map +1 -0
  68. package/dist/lexicons/app/bsky/actor/link.defs.d.ts +120 -0
  69. package/dist/lexicons/app/bsky/actor/link.defs.d.ts.map +1 -0
  70. package/dist/lexicons/app/bsky/actor/link.defs.js +39 -0
  71. package/dist/lexicons/app/bsky/actor/link.defs.js.map +1 -0
  72. package/dist/lexicons/app/bsky/actor/link.js +6 -0
  73. package/dist/lexicons/app/bsky/actor/link.js.map +1 -0
  74. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts +14 -0
  75. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
  76. package/dist/lexicons/app/bsky/actor/profile.defs.js +3 -0
  77. package/dist/lexicons/app/bsky/actor/profile.defs.js.map +1 -1
  78. package/dist/lexicons/app/bsky/actor.d.ts +1 -0
  79. package/dist/lexicons/app/bsky/actor.d.ts.map +1 -1
  80. package/dist/lexicons/app/bsky/actor.js +1 -0
  81. package/dist/lexicons/app/bsky/actor.js.map +1 -1
  82. package/dist/lexicons/app/bsky/authFullApp.defs.d.ts.map +1 -1
  83. package/dist/lexicons/app/bsky/authFullApp.defs.js +1 -0
  84. package/dist/lexicons/app/bsky/authFullApp.defs.js.map +1 -1
  85. package/dist/lexicons/app/bsky/authManageProfile.defs.d.ts.map +1 -1
  86. package/dist/lexicons/app/bsky/authManageProfile.defs.js +1 -0
  87. package/dist/lexicons/app/bsky/authManageProfile.defs.js.map +1 -1
  88. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts +13 -3
  89. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts.map +1 -1
  90. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js +11 -3
  91. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js.map +1 -1
  92. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts +62 -3
  93. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts.map +1 -1
  94. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js +53 -2
  95. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js.map +1 -1
  96. package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts +14 -1
  97. package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts.map +1 -1
  98. package/dist/lexicons/tools/ozone/inbox/defs.defs.js +6 -1
  99. package/dist/lexicons/tools/ozone/inbox/defs.defs.js.map +1 -1
  100. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts +1 -0
  101. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts.map +1 -1
  102. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js +1 -0
  103. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js.map +1 -1
  104. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts +7 -2
  105. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts.map +1 -1
  106. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js +4 -1
  107. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js.map +1 -1
  108. package/dist/proto/bsky_connect.d.ts +10 -1
  109. package/dist/proto/bsky_connect.d.ts.map +1 -1
  110. package/dist/proto/bsky_connect.js +10 -1
  111. package/dist/proto/bsky_connect.js.map +1 -1
  112. package/dist/proto/bsky_pb.d.ts +34 -0
  113. package/dist/proto/bsky_pb.d.ts.map +1 -1
  114. package/dist/proto/bsky_pb.js +60 -0
  115. package/dist/proto/bsky_pb.js.map +1 -1
  116. package/dist/telemetry/events.d.ts +35 -0
  117. package/dist/telemetry/events.d.ts.map +1 -1
  118. package/dist/telemetry/events.js +68 -0
  119. package/dist/telemetry/events.js.map +1 -1
  120. package/dist/util/external/index.d.ts +8 -0
  121. package/dist/util/external/index.d.ts.map +1 -0
  122. package/dist/util/external/index.js +50 -0
  123. package/dist/util/external/index.js.map +1 -0
  124. package/dist/util/{standard-site.d.ts → external/standard-site.d.ts} +30 -4
  125. package/dist/util/external/standard-site.d.ts.map +1 -0
  126. package/dist/util/{standard-site.js → external/standard-site.js} +73 -4
  127. package/dist/util/external/standard-site.js.map +1 -0
  128. package/dist/views/external.js +1 -1
  129. package/dist/views/external.js.map +1 -1
  130. package/dist/views/index.d.ts +11 -7
  131. package/dist/views/index.d.ts.map +1 -1
  132. package/dist/views/index.js +94 -43
  133. package/dist/views/index.js.map +1 -1
  134. package/dist/views/profile-link-rules.d.ts +10 -0
  135. package/dist/views/profile-link-rules.d.ts.map +1 -0
  136. package/dist/views/profile-link-rules.js +48 -0
  137. package/dist/views/profile-link-rules.js.map +1 -0
  138. package/dist/views/types.d.ts +2 -0
  139. package/dist/views/types.d.ts.map +1 -1
  140. package/dist/views/types.js.map +1 -1
  141. package/package.json +7 -7
  142. package/dist/util/standard-site.d.ts.map +0 -1
  143. package/dist/util/standard-site.js.map +0 -1
@@ -74,7 +74,7 @@ const hydration = async (input) => {
74
74
  const { ctx, params, skeleton } = input;
75
75
  const { suggestedDids } = skeleton;
76
76
  if (params.hydrateCtx.features.checkGate(params.hydrateCtx.features.Gate.SuggestedUsersSocialProofEnable)) {
77
- return ctx.hydrator.hydrateProfilesDetailed(suggestedDids, params.hydrateCtx);
77
+ return ctx.hydrator.hydrateProfilesDetailed(suggestedDids, params.hydrateCtx, { knownFollowersDids: suggestedDids, activitySubscriptions: true });
78
78
  }
79
79
  else {
80
80
  return ctx.hydrator.hydrateProfiles(suggestedDids, params.hydrateCtx);
@@ -89,7 +89,7 @@ const noBlocksOrMutes = (input) => {
89
89
  const presentation = (input) => {
90
90
  const { ctx, hydration, skeleton } = input;
91
91
  const { suggestedDids, contentLanguage } = skeleton;
92
- const suggestions = mapDefined(suggestedDids, (did) => ctx.views.profileKnownFollowers(did, hydration));
92
+ const suggestions = mapDefined(suggestedDids, (did) => ctx.views.profile(did, hydration));
93
93
  return {
94
94
  recIdStr: skeleton.recIdStr,
95
95
  contentLanguage,
@@ -1 +1 @@
1
- {"version":3,"file":"getSuggestedFollowsByActor.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/graph/getSuggestedFollowsByActor.ts"],"names":[],"mappings":"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","sourcesContent":["import { mapDefined, noUndefinedVals } from '@atproto/common'\nimport type { Client, DidString } from '@atproto/lex'\nimport {\n type Headers as HeadersMap,\n InternalServerError,\n InvalidRequestError,\n type Server,\n} from '@atproto/xrpc-server'\nimport type { AppContext } from '../../../../context.js'\nimport type { HydrateCtx, Hydrator } from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { getAtprotoPassthroughHeaders } from '../../../../util/headers.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\n\nexport default function (server: Server, ctx: AppContext) {\n const getSuggestedFollowsByActor = createPipeline(\n skeleton,\n hydration,\n noBlocksOrMutes,\n presentation,\n )\n server.add(app.bsky.graph.getSuggestedFollowsByActor, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ auth, params, req, signal }) => {\n const viewer = auth.credentials.iss\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features: ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({\n viewer,\n req,\n }),\n ),\n })\n\n const useIris = hydrateCtx.features.checkGate(\n hydrateCtx.features.Gate.SuggestedUsersIrisEnable,\n )\n const client = useIris ? ctx.irisClient : ctx.suggestionsClient\n\n if (!client) {\n return {\n encoding: 'application/json',\n body: { suggestions: [] },\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n }\n }\n\n const headers = noUndefinedVals({\n 'accept-language': req.headers['accept-language'],\n ...getAtprotoPassthroughHeaders(req),\n })\n\n const { contentLanguage, ...body } = await getSuggestedFollowsByActor(\n { ...params, hydrateCtx, headers, signal },\n ctx,\n )\n\n return {\n encoding: 'application/json',\n body,\n headers: {\n ...(contentLanguage ? { 'content-language': contentLanguage } : null),\n ...resHeaders({ labelers: hydrateCtx.labelers }),\n },\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { params, ctx } = input\n\n const useIris = params.hydrateCtx.features.checkGate(\n params.hydrateCtx.features.Gate.SuggestedUsersIrisEnable,\n )\n const client = useIris ? ctx.irisClient : ctx.suggestionsClient\n\n // handled above already, this branch should not be reached\n if (!client) {\n throw new InternalServerError('Suggestions service not configured')\n }\n\n const [relativeToDid] = await ctx.hydrator.actor.getDids([params.actor])\n if (!relativeToDid) {\n throw new InvalidRequestError('Actor not found')\n }\n\n const res = await client.xrpc(app.bsky.unspecced.getSuggestionsSkeleton, {\n params: {\n viewer: params.hydrateCtx.viewer ?? undefined,\n relativeToDid,\n },\n headers: params.headers,\n signal: params.signal,\n })\n\n return {\n recIdStr: res.body.recIdStr,\n suggestedDids: res.body.actors.map((a) => a.did),\n contentLanguage: res.headers.get('content-language') ?? undefined,\n }\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const { suggestedDids } = skeleton\n if (\n params.hydrateCtx.features.checkGate(\n params.hydrateCtx.features.Gate.SuggestedUsersSocialProofEnable,\n )\n ) {\n return ctx.hydrator.hydrateProfilesDetailed(\n suggestedDids,\n params.hydrateCtx,\n )\n } else {\n return ctx.hydrator.hydrateProfiles(suggestedDids, params.hydrateCtx)\n }\n}\n\nconst noBlocksOrMutes = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, skeleton, hydration } = input\n skeleton.suggestedDids = skeleton.suggestedDids.filter(\n (did) =>\n !ctx.views.viewerBlockExists(did, hydration) &&\n !ctx.views.viewerMuteExists(did, hydration),\n )\n return skeleton\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, hydration, skeleton } = input\n const { suggestedDids, contentLanguage } = skeleton\n const suggestions = mapDefined(suggestedDids, (did) =>\n ctx.views.profileKnownFollowers(did, hydration),\n )\n return {\n recIdStr: skeleton.recIdStr,\n contentLanguage,\n suggestions,\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n suggestionsClient: Client | undefined\n irisClient: Client | undefined\n}\n\ntype Params = app.bsky.graph.getSuggestedFollowsByActor.$Params & {\n hydrateCtx: HydrateCtx\n headers: HeadersMap\n signal: AbortSignal\n}\n\ntype SkeletonState = {\n suggestedDids: DidString[]\n recIdStr?: string\n contentLanguage?: string\n}\n"]}
1
+ {"version":3,"file":"getSuggestedFollowsByActor.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/graph/getSuggestedFollowsByActor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAEL,mBAAmB,EACnB,mBAAmB,GAEpB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EAAE,GAAG,EAAE,MAAM,+BAA+B,CAAA;AACnD,OAAO,EAKL,cAAc,GACf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAA;AAE1E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,MAAM,CAAC,OAAO,WAAW,MAAc,EAAE,GAAe;IACtD,MAAM,0BAA0B,GAAG,cAAc,CAC/C,QAAQ,EACR,SAAS,EACT,eAAe,EACf,YAAY,CACb,CAAA;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,0BAA0B,EAAE;QACpD,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,gBAAgB;QACvC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAA;YACnC,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;YACrC,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAClD,QAAQ;gBACR,MAAM;gBACN,QAAQ,EAAE,GAAG,CAAC,kBAAkB,CAAC,KAAK,CACpC,GAAG,CAAC,kBAAkB,CAAC,2BAA2B,CAAC;oBACjD,MAAM;oBACN,GAAG;iBACJ,CAAC,CACH;aACF,CAAC,CAAA;YAEF,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,SAAS,CAC3C,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CAClD,CAAA;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAA;YAE/D,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACL,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;oBACzB,OAAO,EAAE,UAAU,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC;iBACvD,CAAA;YACH,CAAC;YAED,MAAM,OAAO,GAAG,eAAe,CAAC;gBAC9B,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC;gBACjD,GAAG,4BAA4B,CAAC,GAAG,CAAC;aACrC,CAAC,CAAA;YAEF,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,0BAA0B,CACnE,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,EAC1C,GAAG,CACJ,CAAA;YAED,OAAO;gBACL,QAAQ,EAAE,kBAAkB;gBAC5B,IAAI;gBACJ,OAAO,EAAE;oBACP,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;oBACrE,GAAG,UAAU,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC;iBACjD;aACF,CAAA;QACH,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,QAAQ,GAAG,KAAK,EACpB,KAAuC,EACf,EAAE;IAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;IAE7B,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAClD,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CACzD,CAAA;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAA;IAE/D,2DAA2D;IAC3D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,mBAAmB,CAAC,oCAAoC,CAAC,CAAA;IACrE,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACxE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,sBAAsB,EAAE;QACvE,MAAM,EAAE;YACN,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,IAAI,SAAS;YAC7C,aAAa;SACd;QACD,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CAAA;IAEF,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ;QAC3B,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAChD,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,SAAS;KAClE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,KAAK,EACrB,KAAuD,EACvD,EAAE;IACF,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IACvC,MAAM,EAAE,aAAa,EAAE,GAAG,QAAQ,CAAA;IAClC,IACE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAClC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,+BAA+B,CAChE,EACD,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,CAAC,uBAAuB,CACzC,aAAa,EACb,MAAM,CAAC,UAAU,EACjB,EAAE,kBAAkB,EAAE,aAAa,EAAE,qBAAqB,EAAE,IAAI,EAAE,CACnE,CAAA;IACH,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IACvE,CAAC;AACH,CAAC,CAAA;AAED,MAAM,eAAe,GAAG,CACtB,KAAmD,EACnD,EAAE;IACF,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IAC1C,QAAQ,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CACpD,CAAC,GAAG,EAAE,EAAE,CACN,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC;QAC5C,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,EAAE,SAAS,CAAC,CAC9C,CAAA;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CACnB,KAA0D,EAC1D,EAAE;IACF,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAC1C,MAAM,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAA;IACnD,MAAM,WAAW,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CACpD,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAClC,CAAA;IACD,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,eAAe;QACf,WAAW;KACZ,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { mapDefined, noUndefinedVals } from '@atproto/common'\nimport type { Client, DidString } from '@atproto/lex'\nimport {\n type Headers as HeadersMap,\n InternalServerError,\n InvalidRequestError,\n type Server,\n} from '@atproto/xrpc-server'\nimport type { AppContext } from '../../../../context.js'\nimport type { HydrateCtx, Hydrator } from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { getAtprotoPassthroughHeaders } from '../../../../util/headers.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\n\nexport default function (server: Server, ctx: AppContext) {\n const getSuggestedFollowsByActor = createPipeline(\n skeleton,\n hydration,\n noBlocksOrMutes,\n presentation,\n )\n server.add(app.bsky.graph.getSuggestedFollowsByActor, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ auth, params, req, signal }) => {\n const viewer = auth.credentials.iss\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features: ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({\n viewer,\n req,\n }),\n ),\n })\n\n const useIris = hydrateCtx.features.checkGate(\n hydrateCtx.features.Gate.SuggestedUsersIrisEnable,\n )\n const client = useIris ? ctx.irisClient : ctx.suggestionsClient\n\n if (!client) {\n return {\n encoding: 'application/json',\n body: { suggestions: [] },\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n }\n }\n\n const headers = noUndefinedVals({\n 'accept-language': req.headers['accept-language'],\n ...getAtprotoPassthroughHeaders(req),\n })\n\n const { contentLanguage, ...body } = await getSuggestedFollowsByActor(\n { ...params, hydrateCtx, headers, signal },\n ctx,\n )\n\n return {\n encoding: 'application/json',\n body,\n headers: {\n ...(contentLanguage ? { 'content-language': contentLanguage } : null),\n ...resHeaders({ labelers: hydrateCtx.labelers }),\n },\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { params, ctx } = input\n\n const useIris = params.hydrateCtx.features.checkGate(\n params.hydrateCtx.features.Gate.SuggestedUsersIrisEnable,\n )\n const client = useIris ? ctx.irisClient : ctx.suggestionsClient\n\n // handled above already, this branch should not be reached\n if (!client) {\n throw new InternalServerError('Suggestions service not configured')\n }\n\n const [relativeToDid] = await ctx.hydrator.actor.getDids([params.actor])\n if (!relativeToDid) {\n throw new InvalidRequestError('Actor not found')\n }\n\n const res = await client.xrpc(app.bsky.unspecced.getSuggestionsSkeleton, {\n params: {\n viewer: params.hydrateCtx.viewer ?? undefined,\n relativeToDid,\n },\n headers: params.headers,\n signal: params.signal,\n })\n\n return {\n recIdStr: res.body.recIdStr,\n suggestedDids: res.body.actors.map((a) => a.did),\n contentLanguage: res.headers.get('content-language') ?? undefined,\n }\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const { suggestedDids } = skeleton\n if (\n params.hydrateCtx.features.checkGate(\n params.hydrateCtx.features.Gate.SuggestedUsersSocialProofEnable,\n )\n ) {\n return ctx.hydrator.hydrateProfilesDetailed(\n suggestedDids,\n params.hydrateCtx,\n { knownFollowersDids: suggestedDids, activitySubscriptions: true },\n )\n } else {\n return ctx.hydrator.hydrateProfiles(suggestedDids, params.hydrateCtx)\n }\n}\n\nconst noBlocksOrMutes = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, skeleton, hydration } = input\n skeleton.suggestedDids = skeleton.suggestedDids.filter(\n (did) =>\n !ctx.views.viewerBlockExists(did, hydration) &&\n !ctx.views.viewerMuteExists(did, hydration),\n )\n return skeleton\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, hydration, skeleton } = input\n const { suggestedDids, contentLanguage } = skeleton\n const suggestions = mapDefined(suggestedDids, (did) =>\n ctx.views.profile(did, hydration),\n )\n return {\n recIdStr: skeleton.recIdStr,\n contentLanguage,\n suggestions,\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n suggestionsClient: Client | undefined\n irisClient: Client | undefined\n}\n\ntype Params = app.bsky.graph.getSuggestedFollowsByActor.$Params & {\n hydrateCtx: HydrateCtx\n headers: HeadersMap\n signal: AbortSignal\n}\n\ntype SkeletonState = {\n suggestedDids: DidString[]\n recIdStr?: string\n contentLanguage?: string\n}\n"]}
@@ -31,7 +31,10 @@ export declare const APPVIEW_NOTIFICATION_REASON: {
31
31
  readonly UNVERIFIED: 'unverified';
32
32
  readonly VERIFIED: 'verified';
33
33
  readonly MULTI_POST_LIKE: 'multi-post-like';
34
+ readonly MULTI_POST_REPOST: 'multi-post-repost';
35
+ readonly MULTI_POST_LIKE_VIA_REPOST: 'multi-post-like-via-repost';
36
+ readonly MULTI_POST_REPOST_VIA_REPOST: 'multi-post-repost-via-repost';
34
37
  };
35
- export type AppviewNotificationReason = DataplaneNotificationReason | typeof APPVIEW_NOTIFICATION_REASON.MULTI_POST_LIKE;
38
+ export type AppviewNotificationReason = (typeof APPVIEW_NOTIFICATION_REASON)[keyof typeof APPVIEW_NOTIFICATION_REASON];
36
39
  export declare const MARK_READ_GENERIC = "mark-read-generic";
37
40
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,mBAAmB;aAC9B,aAAa,EAAE,eAAe;aAC9B,MAAM,EAAE,QAAQ;aAChB,WAAW,EAAE,aAAa;aAC1B,IAAI,EAAE,MAAM;aACZ,eAAe,EAAE,iBAAiB;aAClC,OAAO,EAAE,SAAS;aAClB,KAAK,EAAE,OAAO;aACd,KAAK,EAAE,OAAO;aACd,MAAM,EAAE,QAAQ;aAChB,iBAAiB,EAAE,mBAAmB;aACtC,kBAAkB,EAAE,oBAAoB;aACxC,eAAe,EAAE,iBAAiB;aAClC,UAAU,EAAE,YAAY;aACxB,QAAQ,EAAE,UAAU;CACZ,CAAA;AAEV,MAAM,MAAM,2BAA2B,GACrC,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAA;AAEhE,eAAO,MAAM,2BAA2B;4BAnBvB,eAAe;qBACtB,QAAQ;0BACH,aAAa;mBACpB,MAAM;8BACK,iBAAiB;sBACzB,SAAS;oBACX,OAAO;oBACP,OAAO;qBACN,QAAQ;gCACG,mBAAmB;iCAClB,oBAAoB;8BACvB,iBAAiB;yBACtB,YAAY;uBACd,UAAU;8BAQH,iBAAiB;CAC1B,CAAA;AAEV,MAAM,MAAM,yBAAyB,GACjC,2BAA2B,GAC3B,OAAO,2BAA2B,CAAC,eAAe,CAAA;AAGtD,eAAO,MAAM,iBAAiB,sBAAsB,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,mBAAmB;aAC9B,aAAa,EAAE,eAAe;aAC9B,MAAM,EAAE,QAAQ;aAChB,WAAW,EAAE,aAAa;aAC1B,IAAI,EAAE,MAAM;aACZ,eAAe,EAAE,iBAAiB;aAClC,OAAO,EAAE,SAAS;aAClB,KAAK,EAAE,OAAO;aACd,KAAK,EAAE,OAAO;aACd,MAAM,EAAE,QAAQ;aAChB,iBAAiB,EAAE,mBAAmB;aACtC,kBAAkB,EAAE,oBAAoB;aACxC,eAAe,EAAE,iBAAiB;aAClC,UAAU,EAAE,YAAY;aACxB,QAAQ,EAAE,UAAU;CACZ,CAAA;AAEV,MAAM,MAAM,2BAA2B,GACrC,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAA;AAEhE,eAAO,MAAM,2BAA2B;4BAnBvB,eAAe;qBACtB,QAAQ;0BACH,aAAa;mBACpB,MAAM;8BACK,iBAAiB;sBACzB,SAAS;oBACX,OAAO;oBACP,OAAO;qBACN,QAAQ;gCACG,mBAAmB;iCAClB,oBAAoB;8BACvB,iBAAiB;yBACtB,YAAY;uBACd,UAAU;8BAQH,iBAAiB;gCACf,mBAAmB;yCACV,4BAA4B;2CAC1B,8BAA8B;CACpD,CAAA;AAEV,MAAM,MAAM,yBAAyB,GACnC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,OAAO,2BAA2B,CAAC,CAAA;AAGhF,eAAO,MAAM,iBAAiB,sBAAsB,CAAA"}
@@ -18,6 +18,9 @@ export const NOTIFICATION_REASON = {
18
18
  export const APPVIEW_NOTIFICATION_REASON = {
19
19
  ...NOTIFICATION_REASON,
20
20
  MULTI_POST_LIKE: 'multi-post-like',
21
+ MULTI_POST_REPOST: 'multi-post-repost',
22
+ MULTI_POST_LIKE_VIA_REPOST: 'multi-post-like-via-repost',
23
+ MULTI_POST_REPOST_VIA_REPOST: 'multi-post-repost-via-repost',
21
24
  };
22
25
  // Not a real notification the users see, but used to mark as read across user devices.
23
26
  export const MARK_READ_GENERIC = 'mark-read-generic';
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/constants.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,aAAa,EAAE,eAAe;IAC9B,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;IAC1B,IAAI,EAAE,MAAM;IACZ,eAAe,EAAE,iBAAiB;IAClC,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;IAChB,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,UAAU;CACZ,CAAA;AAKV,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,GAAG,mBAAmB;IACtB,eAAe,EAAE,iBAAiB;CAC1B,CAAA;AAMV,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAA","sourcesContent":["// As they come from the dataplane.\nexport const NOTIFICATION_REASON = {\n CONTACT_MATCH: 'contact-match',\n FOLLOW: 'follow',\n FOLLOW_BACK: 'follow-back',\n LIKE: 'like',\n LIKE_VIA_REPOST: 'like-via-repost',\n MENTION: 'mention',\n QUOTE: 'quote',\n REPLY: 'reply',\n REPOST: 'repost',\n REPOST_VIA_REPOST: 'repost-via-repost',\n STARTERPACK_JOINED: 'starterpack-joined',\n SUBSCRIBED_POST: 'subscribed-post',\n UNVERIFIED: 'unverified',\n VERIFIED: 'verified',\n} as const\n\nexport type DataplaneNotificationReason =\n (typeof NOTIFICATION_REASON)[keyof typeof NOTIFICATION_REASON]\n\nexport const APPVIEW_NOTIFICATION_REASON = {\n ...NOTIFICATION_REASON,\n MULTI_POST_LIKE: 'multi-post-like',\n} as const\n\nexport type AppviewNotificationReason =\n | DataplaneNotificationReason\n | typeof APPVIEW_NOTIFICATION_REASON.MULTI_POST_LIKE\n\n// Not a real notification the users see, but used to mark as read across user devices.\nexport const MARK_READ_GENERIC = 'mark-read-generic'\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/constants.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,aAAa,EAAE,eAAe;IAC9B,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;IAC1B,IAAI,EAAE,MAAM;IACZ,eAAe,EAAE,iBAAiB;IAClC,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;IAChB,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,UAAU;CACZ,CAAA;AAKV,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,GAAG,mBAAmB;IACtB,eAAe,EAAE,iBAAiB;IAClC,iBAAiB,EAAE,mBAAmB;IACtC,0BAA0B,EAAE,4BAA4B;IACxD,4BAA4B,EAAE,8BAA8B;CACpD,CAAA;AAKV,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAA","sourcesContent":["// As they come from the dataplane.\nexport const NOTIFICATION_REASON = {\n CONTACT_MATCH: 'contact-match',\n FOLLOW: 'follow',\n FOLLOW_BACK: 'follow-back',\n LIKE: 'like',\n LIKE_VIA_REPOST: 'like-via-repost',\n MENTION: 'mention',\n QUOTE: 'quote',\n REPLY: 'reply',\n REPOST: 'repost',\n REPOST_VIA_REPOST: 'repost-via-repost',\n STARTERPACK_JOINED: 'starterpack-joined',\n SUBSCRIBED_POST: 'subscribed-post',\n UNVERIFIED: 'unverified',\n VERIFIED: 'verified',\n} as const\n\nexport type DataplaneNotificationReason =\n (typeof NOTIFICATION_REASON)[keyof typeof NOTIFICATION_REASON]\n\nexport const APPVIEW_NOTIFICATION_REASON = {\n ...NOTIFICATION_REASON,\n MULTI_POST_LIKE: 'multi-post-like',\n MULTI_POST_REPOST: 'multi-post-repost',\n MULTI_POST_LIKE_VIA_REPOST: 'multi-post-like-via-repost',\n MULTI_POST_REPOST_VIA_REPOST: 'multi-post-repost-via-repost',\n} as const\n\nexport type AppviewNotificationReason =\n (typeof APPVIEW_NOTIFICATION_REASON)[keyof typeof APPVIEW_NOTIFICATION_REASON]\n\n// Not a real notification the users see, but used to mark as read across user devices.\nexport const MARK_READ_GENERIC = 'mark-read-generic'\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Server } from '@atproto/xrpc-server';
2
2
  import type { AppContext } from '../../../../context.js';
3
- export declare const getNextRawLimit: (itemsFetched: number, groupsBuilt: number, groupsNeeded: number) => number;
3
+ export declare const getNextRawLimit: (itemsFetched: number, groupsBuilt: number, groupsNeeded: number, minRawLimit: number, maxRawItems: number) => number;
4
4
  export default function (server: Server, ctx: AppContext): void;
5
5
  //# sourceMappingURL=getGroupedNotifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getGroupedNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AA2CxD,eAAO,MAAM,eAAe,iBACZ,MAAM,eACP,MAAM,gBACL,MAAM,KACnB,MAoBF,CAAA;AAED,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAqCvD"}
1
+ {"version":3,"file":"getGroupedNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAuCxD,eAAO,MAAM,eAAe,iBACZ,MAAM,eACP,MAAM,gBACL,MAAM,eACP,MAAM,eACN,MAAM,KAClB,MAoBF,CAAA;AAED,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAqCvD"}
@@ -1,10 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { mapDefined } from '@atproto/common';
2
+ import { dedupeStrs, mapDefined } from '@atproto/common';
3
3
  import { asDatetimeString, } from '@atproto/lex';
4
4
  import { AtUri } from '@atproto/syntax';
5
5
  import { app } from '../../../../lexicons/index.js';
6
6
  import { createPipeline, } from '../../../../pipeline.js';
7
7
  import { NotificationFeed } from '../../../../proto/bsky_pb.js';
8
+ import { events } from '../../../../telemetry/events.js';
8
9
  import { uriToDid } from '../../../../util/uris.js';
9
10
  import { resHeaders } from '../../../util.js';
10
11
  import { NOTIFICATION_REASON } from './constants.js';
@@ -12,27 +13,23 @@ import { buildGroups, parseRawNotification, } from './grouping/grouping.js';
12
13
  import { compareNotificationGroupsNewestFirst, isNotificationRead, } from './grouping/shared.js';
13
14
  import { buildSpotlight } from './grouping/spotlight.js';
14
15
  import { delayCursor, shouldFilterForNeedsReview, shouldFilterHiddenThreadTag, shouldFilterReplyByThreadgate, } from './util.js';
15
- const MIN_RAW_LIMIT = 300;
16
- const MAX_RAW_ITEMS = 3000;
17
- const MAX_READS = 3;
18
- const MAX_GROUP_ITEMS_WITH_RELATED_VIEWS = 10;
19
- export const getNextRawLimit = (itemsFetched, groupsBuilt, groupsNeeded) => {
16
+ export const getNextRawLimit = (itemsFetched, groupsBuilt, groupsNeeded, minRawLimit, maxRawItems) => {
20
17
  // How many raw items we still can fetch until hit the max.
21
- const remainingItems = MAX_RAW_ITEMS - itemsFetched;
18
+ const remainingItems = maxRawItems - itemsFetched;
22
19
  if (remainingItems <= 0)
23
20
  return 0;
24
21
  // Defensive fallback to avoid a division by 0 later.
25
22
  // This is for the scenario where fetched items but couldn't build any groups with them
26
23
  // (let's say all notifications are missing data and are filtered out, shouldn't happen in practice).
27
24
  if (groupsBuilt === 0)
28
- return Math.min(MIN_RAW_LIMIT, remainingItems);
25
+ return Math.min(minRawLimit, remainingItems);
29
26
  // How many groups still to be built - note that we might still fit items into existing groups.
30
27
  const groupsMissing = Math.max(0, groupsNeeded - groupsBuilt);
31
28
  const averagePerGroup = itemsFetched / groupsBuilt;
32
29
  // Estimate additional raw notifications from the average group size.
33
30
  const estimatedRawItems = Math.ceil(averagePerGroup * groupsMissing);
34
31
  // Keep the estimate above the minimum batch and within the total raw-item budget.
35
- return Math.min(remainingItems, Math.max(MIN_RAW_LIMIT, estimatedRawItems));
32
+ return Math.min(remainingItems, Math.max(minRawLimit, estimatedRawItems));
36
33
  };
37
34
  export default function (server, ctx) {
38
35
  const getGroupedNotifications = createPipeline(skeleton, hydration, rules, presentation);
@@ -76,8 +73,8 @@ const skeleton = async (input) => {
76
73
  let groups = [];
77
74
  let nextCursor;
78
75
  let itemsFetched = 0;
79
- let rawLimit = MIN_RAW_LIMIT;
80
- for (let read = 0; read < MAX_READS; read++) {
76
+ let rawLimit = ctx.cfg.notificationsV2MinRawLimit;
77
+ for (let read = 0; read < ctx.cfg.notificationsV2MaxReads; read++) {
81
78
  const response = await ctx.hydrator.dataplane.getNotificationsV2({
82
79
  actorDid: params.hydrateCtx.viewer,
83
80
  feed,
@@ -124,16 +121,27 @@ const skeleton = async (input) => {
124
121
  if (!shouldContinueBuildingPage)
125
122
  break;
126
123
  cursor = response.cursor;
127
- rawLimit = getNextRawLimit(itemsFetched, groups.length, params.limit);
124
+ rawLimit = getNextRawLimit(itemsFetched, groups.length, params.limit, ctx.cfg.notificationsV2MinRawLimit, ctx.cfg.notificationsV2MaxRawItems);
128
125
  if (rawLimit === 0)
129
126
  break;
130
127
  }
131
- return buildSpotlight(items, groups, nextCursor, params.limit, seenAt);
128
+ return {
129
+ ...buildSpotlight(items, groups, nextCursor, params.limit, seenAt),
130
+ itemsFetched,
131
+ };
132
132
  };
133
133
  const hydration = async (input) => {
134
134
  const { ctx, params, skeleton } = input;
135
135
  const notifs = skeleton.groups.flatMap((group) => group.items.map((item) => item.raw));
136
- return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx);
136
+ // Optimization: instead of fetching known followers for all users in all notifications,
137
+ // only fetch for the notifications type that want it (follow type), and only for the ones
138
+ // that appear before truncation.
139
+ const knownFollowersDids = dedupeStrs(skeleton.groups.flatMap((group) => group.kind === NOTIFICATION_REASON.FOLLOW
140
+ ? truncateRelatedViewsItems(group.items, ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews).map((item) => item.actorDid)
141
+ : []));
142
+ return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx, {
143
+ knownFollowersDids,
144
+ });
137
145
  };
138
146
  const rules = (input) => {
139
147
  const { ctx, params, skeleton, hydration } = input;
@@ -240,6 +248,8 @@ const presentation = (input) => {
240
248
  const { ctx, params, skeleton, hydration } = input;
241
249
  const defs = app.bsky.notification.getGroupedNotifications;
242
250
  const groups = mapDefined(skeleton.groups, (group) => ctx.views.notificationGroup(group, hydration));
251
+ const maxGroupItems = ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews;
252
+ // Build related profiles, records.
243
253
  const profileDids = new Set();
244
254
  const recordUris = new Set();
245
255
  for (const { kind } of groups) {
@@ -248,31 +258,34 @@ const presentation = (input) => {
248
258
  defs.likeViaRepostGroup.$isTypeOf(kind) ||
249
259
  defs.repostViaRepostGroup.$isTypeOf(kind)) {
250
260
  recordUris.add(kind.post);
251
- for (const item of truncateRelatedViewsItems(kind.items)) {
261
+ for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {
252
262
  profileDids.add(item.actor);
253
263
  }
254
264
  }
255
- else if (defs.multiPostLikeGroup.$isTypeOf(kind)) {
265
+ else if (defs.multiPostLikeGroup.$isTypeOf(kind) ||
266
+ defs.multiPostRepostGroup.$isTypeOf(kind) ||
267
+ defs.multiPostLikeViaRepostGroup.$isTypeOf(kind) ||
268
+ defs.multiPostRepostViaRepostGroup.$isTypeOf(kind)) {
256
269
  profileDids.add(kind.actor);
257
- for (const item of truncateRelatedViewsItems(kind.items)) {
270
+ for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {
258
271
  recordUris.add(item.post);
259
272
  }
260
273
  }
261
274
  else if (defs.generatorLikeGroup.$isTypeOf(kind)) {
262
275
  recordUris.add(kind.generator);
263
- for (const item of truncateRelatedViewsItems(kind.items)) {
276
+ for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {
264
277
  profileDids.add(item.actor);
265
278
  }
266
279
  }
267
280
  else if (defs.followGroup.$isTypeOf(kind)) {
268
- for (const item of truncateRelatedViewsItems(kind.items)) {
281
+ for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {
269
282
  profileDids.add(item.actor);
270
283
  if (item.starterPack)
271
284
  recordUris.add(item.starterPack);
272
285
  }
273
286
  }
274
287
  else if (defs.subscribedPostGroup.$isTypeOf(kind)) {
275
- for (const item of truncateRelatedViewsItems(kind.items)) {
288
+ for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {
276
289
  profileDids.add(item.actor);
277
290
  recordUris.add(item.post);
278
291
  }
@@ -296,14 +309,14 @@ const presentation = (input) => {
296
309
  profileDids.add(kind.actor);
297
310
  }
298
311
  }
299
- return {
312
+ const body = {
300
313
  groups,
301
314
  relatedViews: [
302
315
  ...mapDefined([...profileDids], (did) => {
303
- const view = ctx.views.profileDetailed(did, hydration);
316
+ const view = ctx.views.profileBasic(did, hydration);
304
317
  if (!view)
305
318
  return;
306
- return app.bsky.actor.defs.profileViewDetailed.$build(view);
319
+ return app.bsky.actor.defs.profileViewBasic.$build(view);
307
320
  }),
308
321
  ...mapDefined([...recordUris], (uri) => {
309
322
  const collection = new AtUri(uri).collection;
@@ -314,10 +327,10 @@ const presentation = (input) => {
314
327
  return view;
315
328
  }
316
329
  else if (collection === app.bsky.graph.starterpack.$type) {
317
- const view = ctx.views.starterPack(uri, hydration);
330
+ const view = ctx.views.starterPackBasic(uri, hydration);
318
331
  if (!view)
319
332
  return;
320
- return app.bsky.graph.defs.starterPackView.$build(view);
333
+ return app.bsky.graph.defs.starterPackViewBasic.$build(view);
321
334
  }
322
335
  else if (collection === app.bsky.feed.generator.$type) {
323
336
  const view = ctx.views.feedGenerator(uri, hydration);
@@ -330,8 +343,13 @@ const presentation = (input) => {
330
343
  cursor: skeleton.cursor,
331
344
  seenAt: params.seenAt,
332
345
  };
346
+ events.groupedNotifications({
347
+ itemsFetched: skeleton.itemsFetched,
348
+ itemsReturned: groups.reduce((count, group) => count + group.count, 0),
349
+ });
350
+ return body;
333
351
  };
334
- const truncateRelatedViewsItems = (items) => items.slice(0, MAX_GROUP_ITEMS_WITH_RELATED_VIEWS);
352
+ const truncateRelatedViewsItems = (items, maxItems) => items.slice(0, maxItems);
335
353
  const normalizeFeed = (feed) => {
336
354
  switch (feed) {
337
355
  case 'people-i-follow':
@@ -1 +1 @@
1
- {"version":3,"file":"getGroupedNotifications.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { mapDefined } from '@atproto/common'\nimport {\n type AtUriString,\n type DatetimeString,\n type DidString,\n asDatetimeString,\n} from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type {\n HydrateCtxWithViewer,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { NotificationFeed } from '../../../../proto/bsky_pb.js'\nimport { uriToDid } from '../../../../util/uris.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\nimport { NOTIFICATION_REASON } from './constants.js'\nimport {\n type NotificationGroup,\n type NotificationItem,\n type RawNotification,\n buildGroups,\n parseRawNotification,\n} from './grouping/grouping.js'\nimport {\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './grouping/shared.js'\nimport { buildSpotlight } from './grouping/spotlight.js'\nimport {\n delayCursor,\n shouldFilterForNeedsReview,\n shouldFilterHiddenThreadTag,\n shouldFilterReplyByThreadgate,\n} from './util.js'\n\nconst MIN_RAW_LIMIT = 300\nconst MAX_RAW_ITEMS = 3000\nconst MAX_READS = 3\nconst MAX_GROUP_ITEMS_WITH_RELATED_VIEWS = 10\n\nexport const getNextRawLimit = (\n itemsFetched: number,\n groupsBuilt: number,\n groupsNeeded: number,\n): number => {\n // How many raw items we still can fetch until hit the max.\n const remainingItems = MAX_RAW_ITEMS - itemsFetched\n if (remainingItems <= 0) return 0\n\n // Defensive fallback to avoid a division by 0 later.\n // This is for the scenario where fetched items but couldn't build any groups with them\n // (let's say all notifications are missing data and are filtered out, shouldn't happen in practice).\n if (groupsBuilt === 0) return Math.min(MIN_RAW_LIMIT, remainingItems)\n\n // How many groups still to be built - note that we might still fit items into existing groups.\n const groupsMissing = Math.max(0, groupsNeeded - groupsBuilt)\n\n const averagePerGroup = itemsFetched / groupsBuilt\n\n // Estimate additional raw notifications from the average group size.\n const estimatedRawItems = Math.ceil(averagePerGroup * groupsMissing)\n\n // Keep the estimate above the minimum batch and within the total raw-item budget.\n return Math.min(remainingItems, Math.max(MIN_RAW_LIMIT, estimatedRawItems))\n}\n\nexport default function (server: Server, ctx: AppContext) {\n const getGroupedNotifications = createPipeline(\n skeleton,\n hydration,\n rules,\n presentation,\n )\n server.add(app.bsky.notification.getGroupedNotifications, {\n auth: ctx.authVerifier.standard,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const features = ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n )\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features,\n })\n const lastSeenRes = await ctx.hydrator.dataplane.getNotificationSeen({\n actorDid: viewer,\n })\n const seenAt = lastSeenRes.timestamp\n ? asDatetimeString(lastSeenRes.timestamp.toJson() as string)\n : undefined\n const body = await getGroupedNotifications(\n { ...params, hydrateCtx, seenAt },\n ctx,\n )\n return {\n encoding: 'application/json',\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n body,\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { ctx, params } = input\n const now = Date.now()\n const feed = normalizeFeed(params.feed)\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const delayedCursor = delayCursor(undefined, ctx.cfg.notificationsDelayMs)\n let cursor = params.cursor\n if (!cursor || Date.parse(cursor) > Date.parse(delayedCursor)) {\n cursor = delayedCursor\n }\n\n const items: NotificationItem[] = []\n let groups: NotificationGroup[] = []\n let nextCursor: string | undefined\n let itemsFetched = 0\n let rawLimit = MIN_RAW_LIMIT\n\n for (let read = 0; read < MAX_READS; read++) {\n const response = await ctx.hydrator.dataplane.getNotificationsV2({\n actorDid: params.hydrateCtx.viewer,\n feed,\n cursor,\n limit: rawLimit,\n })\n itemsFetched += response.notifications.length\n\n for (const notification of response.notifications) {\n const notif = parseRawNotification(notification)\n if (!notif) continue\n const uri = notif.uri\n const actorDid = uriToDid(uri)\n const indexedAt = notif.indexedAt\n // Hashing keeps IDs within the lexicon's length limit for long record URIs.\n const id = createHash('sha256')\n .update(`${indexedAt}\\0${uri}`)\n .digest('base64url')\n\n let groupingKey: string = notif.reasonSubject || uri\n if (notif.reason === NOTIFICATION_REASON.FOLLOW) {\n groupingKey = NOTIFICATION_REASON.FOLLOW\n }\n if (notif.reason === NOTIFICATION_REASON.SUBSCRIBED_POST) {\n groupingKey = actorDid\n }\n\n items.push({\n raw: notif,\n id,\n groupingKey,\n actorDid,\n })\n }\n\n const groupingResult = buildGroups(\n items,\n params.limit,\n now,\n seenAt,\n ctx.cfg.notificationsV2Algorithm,\n feed,\n )\n groups = groupingResult.groups\n\n // After fetching a page from the dataplane and attempting to group, 2 situations can happen:\n // 1. The first unconsumed notification would create an additional group beyond params.limit.\n // result.cursor points before that notification; use it so the next request processes it.\n // 2. Every fetched notification was consumed into the groups.\n // result.cursor is undefined, so use response.cursor to fetch the next dataplane page.\n nextCursor = groupingResult.cursor ?? (response.cursor || undefined)\n\n const groupingFoundUnconsumedNotification =\n groupingResult.cursor !== undefined\n const dataplaneHasMoreNotifications = !!response.cursor\n const shouldContinueBuildingPage =\n !groupingFoundUnconsumedNotification && dataplaneHasMoreNotifications\n if (!shouldContinueBuildingPage) break\n\n cursor = response.cursor\n rawLimit = getNextRawLimit(itemsFetched, groups.length, params.limit)\n if (rawLimit === 0) break\n }\n\n return buildSpotlight(items, groups, nextCursor, params.limit, seenAt)\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const notifs = skeleton.groups.flatMap((group) =>\n group.items.map((item) => item.raw),\n )\n return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx)\n}\n\nconst rules = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n): SkeletonState => {\n const { ctx, params, skeleton, hydration } = input\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const groups = mapDefined(\n skeleton.groups,\n (group): NotificationGroup | undefined => {\n const items = group.items.filter((item) => {\n const notif = item.raw\n const uri = notif.uri\n const did = item.actorDid\n if (\n ctx.views.viewerBlockExists(did, hydration) ||\n ctx.views.viewerMuteExists(did, hydration)\n ) {\n return false\n }\n if (\n shouldFilterReplyByThreadgate(\n notif.reason,\n uri,\n params.hydrateCtx.viewer,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n if (\n shouldFilterHiddenThreadTag(\n notif.reason,\n uri,\n did,\n hydration,\n ctx.cfg.threadTagsHide,\n )\n ) {\n return false\n }\n if (\n shouldFilterForNeedsReview(\n notif.reason,\n did,\n uri,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n\n // After hydration, notificationAvailable checks whether each notification’s required records exist.\n // If its actor, post, repost, follow, or starter pack is missing or blocked, rules removes that notification from the returned group.\n return notificationAvailable(notif, hydration, ctx.views)\n })\n\n const newest = items[0]\n const oldest = items.at(-1)\n\n // If it has no more elements, drop this group.\n if (!newest || !oldest) return\n\n // Filtering may remove notifications from this group; build a new group with only the remaining items and their updated metadata.\n return {\n ...group,\n id: newest.id,\n items,\n actorDids: items.map((item) => item.actorDid),\n itemCount: items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n }\n },\n )\n\n groups.sort(compareNotificationGroupsNewestFirst)\n\n return { ...skeleton, groups }\n}\n\n// Checks that the notification’s actor and required records exist and referenced posts aren’t viewer-blocked.\nconst notificationAvailable = (\n notif: RawNotification,\n state: HydrationState,\n views: Views,\n): boolean => {\n const uri = notif.uri\n const actor = state.actors?.get(uriToDid(uri))\n if (!actor) return false\n\n const postAvailable = (postUri: AtUriString) =>\n !!state.posts?.get(postUri) &&\n !!state.actors?.get(uriToDid(postUri)) &&\n !views.viewerBlockExists(uriToDid(postUri), state)\n\n const starterPackAvailable = (starterPackUri: AtUriString) =>\n !!state.starterPacks?.get(starterPackUri) &&\n !!state.actors?.get(uriToDid(starterPackUri))\n\n switch (notif.reason) {\n case NOTIFICATION_REASON.LIKE: {\n const subjectUri = notif.reasonSubject\n if (!state.likes?.get(uri)) return false\n if (new AtUri(subjectUri).collection === app.bsky.feed.generator.$type) {\n return (\n !!state.feedgens?.get(subjectUri) &&\n !!state.actors?.get(uriToDid(subjectUri))\n )\n }\n return postAvailable(subjectUri)\n }\n case NOTIFICATION_REASON.REPOST:\n return !!state.reposts?.get(uri) && postAvailable(notif.reasonSubject)\n case NOTIFICATION_REASON.LIKE_VIA_REPOST:\n case NOTIFICATION_REASON.REPOST_VIA_REPOST: {\n const recordInfo =\n notif.reason === NOTIFICATION_REASON.LIKE_VIA_REPOST\n ? state.likes?.get(uri)\n : state.reposts?.get(uri)\n const postUri = state.reposts?.get(notif.reasonSubject)?.record.subject\n .uri\n return !!recordInfo && !!postUri && postAvailable(postUri)\n }\n case NOTIFICATION_REASON.FOLLOW:\n case NOTIFICATION_REASON.FOLLOW_BACK:\n return !!state.follows?.get(uri)\n case NOTIFICATION_REASON.REPLY:\n return (\n postAvailable(uri) && !!state.posts?.get(uri)?.record.reply?.parent.uri\n )\n case NOTIFICATION_REASON.QUOTE:\n case NOTIFICATION_REASON.MENTION:\n case NOTIFICATION_REASON.SUBSCRIBED_POST:\n return postAvailable(uri)\n case NOTIFICATION_REASON.VERIFIED:\n case NOTIFICATION_REASON.UNVERIFIED:\n // Both notifications survive deletion of the verification record.\n return true\n case NOTIFICATION_REASON.STARTERPACK_JOINED:\n return (\n !!actor.profile &&\n !!actor.profileCid &&\n starterPackAvailable(notif.reasonSubject)\n )\n case NOTIFICATION_REASON.CONTACT_MATCH:\n return !!actor.profile && !!actor.profileCid\n default:\n return false\n }\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n): app.bsky.notification.getGroupedNotifications.$OutputBody => {\n const { ctx, params, skeleton, hydration } = input\n const defs = app.bsky.notification.getGroupedNotifications\n const groups = mapDefined(skeleton.groups, (group) =>\n ctx.views.notificationGroup(group, hydration),\n )\n const profileDids = new Set<DidString>()\n const recordUris = new Set<AtUriString>()\n for (const { kind } of groups) {\n if (\n defs.likeGroup.$isTypeOf(kind) ||\n defs.repostGroup.$isTypeOf(kind) ||\n defs.likeViaRepostGroup.$isTypeOf(kind) ||\n defs.repostViaRepostGroup.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n for (const item of truncateRelatedViewsItems(kind.items)) {\n profileDids.add(item.actor)\n }\n } else if (defs.multiPostLikeGroup.$isTypeOf(kind)) {\n profileDids.add(kind.actor)\n for (const item of truncateRelatedViewsItems(kind.items)) {\n recordUris.add(item.post)\n }\n } else if (defs.generatorLikeGroup.$isTypeOf(kind)) {\n recordUris.add(kind.generator)\n for (const item of truncateRelatedViewsItems(kind.items)) {\n profileDids.add(item.actor)\n }\n } else if (defs.followGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items)) {\n profileDids.add(item.actor)\n if (item.starterPack) recordUris.add(item.starterPack)\n }\n } else if (defs.subscribedPostGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items)) {\n profileDids.add(item.actor)\n recordUris.add(item.post)\n }\n } else if (\n defs.replyNotification.$isTypeOf(kind) ||\n defs.quoteNotification.$isTypeOf(kind) ||\n defs.mentionNotification.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n if (kind.parent) recordUris.add(kind.parent)\n } else if (\n defs.followBackNotification.$isTypeOf(kind) ||\n defs.starterPackJoinedNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n if (kind.starterPack) recordUris.add(kind.starterPack)\n } else if (\n defs.verifiedNotification.$isTypeOf(kind) ||\n defs.unverifiedNotification.$isTypeOf(kind) ||\n defs.contactMatchNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n }\n }\n return {\n groups,\n relatedViews: [\n ...mapDefined([...profileDids], (did) => {\n const view = ctx.views.profileDetailed(did, hydration)\n if (!view) return\n return app.bsky.actor.defs.profileViewDetailed.$build(view)\n }),\n ...mapDefined([...recordUris], (uri) => {\n const collection = new AtUri(uri).collection\n if (collection === app.bsky.feed.post.$type) {\n const view = ctx.views.maybePost(uri, hydration)\n if (!view) return\n return view\n } else if (collection === app.bsky.graph.starterpack.$type) {\n const view = ctx.views.starterPack(uri, hydration)\n if (!view) return\n return app.bsky.graph.defs.starterPackView.$build(view)\n } else if (collection === app.bsky.feed.generator.$type) {\n const view = ctx.views.feedGenerator(uri, hydration)\n if (!view) return\n return app.bsky.feed.defs.generatorView.$build(view)\n }\n }),\n ],\n cursor: skeleton.cursor,\n seenAt: params.seenAt,\n }\n}\n\nconst truncateRelatedViewsItems = <T extends unknown[]>(\n items: T,\n): T[number][] => items.slice(0, MAX_GROUP_ITEMS_WITH_RELATED_VIEWS)\n\nconst normalizeFeed = (feed: string): NotificationFeed => {\n switch (feed) {\n case 'people-i-follow':\n return NotificationFeed.PEOPLE_I_FOLLOW\n case 'conversations':\n return NotificationFeed.CONVERSATIONS\n case 'followers':\n return NotificationFeed.FOLLOWERS\n case 'activity':\n return NotificationFeed.ACTIVITY\n default:\n return NotificationFeed.ALL\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: ServerConfig\n}\n\ntype Params = app.bsky.notification.getGroupedNotifications.$Params & {\n hydrateCtx: HydrateCtxWithViewer\n seenAt?: DatetimeString\n}\n\ntype SkeletonState = {\n groups: NotificationGroup[]\n cursor?: string\n}\n"]}
1
+ {"version":3,"file":"getGroupedNotifications.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { dedupeStrs, mapDefined } from '@atproto/common'\nimport {\n type AtUriString,\n type DatetimeString,\n type DidString,\n asDatetimeString,\n} from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type {\n HydrateCtxWithViewer,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { NotificationFeed } from '../../../../proto/bsky_pb.js'\nimport { events } from '../../../../telemetry/events.js'\nimport { uriToDid } from '../../../../util/uris.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\nimport { NOTIFICATION_REASON } from './constants.js'\nimport {\n type NotificationGroup,\n type NotificationItem,\n type RawNotification,\n buildGroups,\n parseRawNotification,\n} from './grouping/grouping.js'\nimport {\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './grouping/shared.js'\nimport { buildSpotlight } from './grouping/spotlight.js'\nimport {\n delayCursor,\n shouldFilterForNeedsReview,\n shouldFilterHiddenThreadTag,\n shouldFilterReplyByThreadgate,\n} from './util.js'\n\nexport const getNextRawLimit = (\n itemsFetched: number,\n groupsBuilt: number,\n groupsNeeded: number,\n minRawLimit: number,\n maxRawItems: number,\n): number => {\n // How many raw items we still can fetch until hit the max.\n const remainingItems = maxRawItems - itemsFetched\n if (remainingItems <= 0) return 0\n\n // Defensive fallback to avoid a division by 0 later.\n // This is for the scenario where fetched items but couldn't build any groups with them\n // (let's say all notifications are missing data and are filtered out, shouldn't happen in practice).\n if (groupsBuilt === 0) return Math.min(minRawLimit, remainingItems)\n\n // How many groups still to be built - note that we might still fit items into existing groups.\n const groupsMissing = Math.max(0, groupsNeeded - groupsBuilt)\n\n const averagePerGroup = itemsFetched / groupsBuilt\n\n // Estimate additional raw notifications from the average group size.\n const estimatedRawItems = Math.ceil(averagePerGroup * groupsMissing)\n\n // Keep the estimate above the minimum batch and within the total raw-item budget.\n return Math.min(remainingItems, Math.max(minRawLimit, estimatedRawItems))\n}\n\nexport default function (server: Server, ctx: AppContext) {\n const getGroupedNotifications = createPipeline(\n skeleton,\n hydration,\n rules,\n presentation,\n )\n server.add(app.bsky.notification.getGroupedNotifications, {\n auth: ctx.authVerifier.standard,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const features = ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n )\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features,\n })\n const lastSeenRes = await ctx.hydrator.dataplane.getNotificationSeen({\n actorDid: viewer,\n })\n const seenAt = lastSeenRes.timestamp\n ? asDatetimeString(lastSeenRes.timestamp.toJson() as string)\n : undefined\n const body = await getGroupedNotifications(\n { ...params, hydrateCtx, seenAt },\n ctx,\n )\n return {\n encoding: 'application/json',\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n body,\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { ctx, params } = input\n const now = Date.now()\n const feed = normalizeFeed(params.feed)\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const delayedCursor = delayCursor(undefined, ctx.cfg.notificationsDelayMs)\n let cursor = params.cursor\n if (!cursor || Date.parse(cursor) > Date.parse(delayedCursor)) {\n cursor = delayedCursor\n }\n\n const items: NotificationItem[] = []\n let groups: NotificationGroup[] = []\n let nextCursor: string | undefined\n let itemsFetched = 0\n let rawLimit = ctx.cfg.notificationsV2MinRawLimit\n\n for (let read = 0; read < ctx.cfg.notificationsV2MaxReads; read++) {\n const response = await ctx.hydrator.dataplane.getNotificationsV2({\n actorDid: params.hydrateCtx.viewer,\n feed,\n cursor,\n limit: rawLimit,\n })\n itemsFetched += response.notifications.length\n\n for (const notification of response.notifications) {\n const notif = parseRawNotification(notification)\n if (!notif) continue\n const uri = notif.uri\n const actorDid = uriToDid(uri)\n const indexedAt = notif.indexedAt\n // Hashing keeps IDs within the lexicon's length limit for long record URIs.\n const id = createHash('sha256')\n .update(`${indexedAt}\\0${uri}`)\n .digest('base64url')\n\n let groupingKey: string = notif.reasonSubject || uri\n if (notif.reason === NOTIFICATION_REASON.FOLLOW) {\n groupingKey = NOTIFICATION_REASON.FOLLOW\n }\n if (notif.reason === NOTIFICATION_REASON.SUBSCRIBED_POST) {\n groupingKey = actorDid\n }\n\n items.push({\n raw: notif,\n id,\n groupingKey,\n actorDid,\n })\n }\n\n const groupingResult = buildGroups(\n items,\n params.limit,\n now,\n seenAt,\n ctx.cfg.notificationsV2Algorithm,\n feed,\n )\n groups = groupingResult.groups\n\n // After fetching a page from the dataplane and attempting to group, 2 situations can happen:\n // 1. The first unconsumed notification would create an additional group beyond params.limit.\n // result.cursor points before that notification; use it so the next request processes it.\n // 2. Every fetched notification was consumed into the groups.\n // result.cursor is undefined, so use response.cursor to fetch the next dataplane page.\n nextCursor = groupingResult.cursor ?? (response.cursor || undefined)\n\n const groupingFoundUnconsumedNotification =\n groupingResult.cursor !== undefined\n const dataplaneHasMoreNotifications = !!response.cursor\n const shouldContinueBuildingPage =\n !groupingFoundUnconsumedNotification && dataplaneHasMoreNotifications\n if (!shouldContinueBuildingPage) break\n\n cursor = response.cursor\n rawLimit = getNextRawLimit(\n itemsFetched,\n groups.length,\n params.limit,\n ctx.cfg.notificationsV2MinRawLimit,\n ctx.cfg.notificationsV2MaxRawItems,\n )\n if (rawLimit === 0) break\n }\n\n return {\n ...buildSpotlight(items, groups, nextCursor, params.limit, seenAt),\n itemsFetched,\n }\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const notifs = skeleton.groups.flatMap((group) =>\n group.items.map((item) => item.raw),\n )\n\n // Optimization: instead of fetching known followers for all users in all notifications,\n // only fetch for the notifications type that want it (follow type), and only for the ones\n // that appear before truncation.\n const knownFollowersDids = dedupeStrs(\n skeleton.groups.flatMap((group) =>\n group.kind === NOTIFICATION_REASON.FOLLOW\n ? truncateRelatedViewsItems(\n group.items,\n ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews,\n ).map((item) => item.actorDid)\n : [],\n ),\n )\n\n return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx, {\n knownFollowersDids,\n })\n}\n\nconst rules = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n): SkeletonState => {\n const { ctx, params, skeleton, hydration } = input\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const groups = mapDefined(\n skeleton.groups,\n (group): NotificationGroup | undefined => {\n const items = group.items.filter((item) => {\n const notif = item.raw\n const uri = notif.uri\n const did = item.actorDid\n if (\n ctx.views.viewerBlockExists(did, hydration) ||\n ctx.views.viewerMuteExists(did, hydration)\n ) {\n return false\n }\n if (\n shouldFilterReplyByThreadgate(\n notif.reason,\n uri,\n params.hydrateCtx.viewer,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n if (\n shouldFilterHiddenThreadTag(\n notif.reason,\n uri,\n did,\n hydration,\n ctx.cfg.threadTagsHide,\n )\n ) {\n return false\n }\n if (\n shouldFilterForNeedsReview(\n notif.reason,\n did,\n uri,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n\n // After hydration, notificationAvailable checks whether each notification’s required records exist.\n // If its actor, post, repost, follow, or starter pack is missing or blocked, rules removes that notification from the returned group.\n return notificationAvailable(notif, hydration, ctx.views)\n })\n\n const newest = items[0]\n const oldest = items.at(-1)\n\n // If it has no more elements, drop this group.\n if (!newest || !oldest) return\n\n // Filtering may remove notifications from this group; build a new group with only the remaining items and their updated metadata.\n return {\n ...group,\n id: newest.id,\n items,\n actorDids: items.map((item) => item.actorDid),\n itemCount: items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n }\n },\n )\n\n groups.sort(compareNotificationGroupsNewestFirst)\n\n return { ...skeleton, groups }\n}\n\n// Checks that the notification’s actor and required records exist and referenced posts aren’t viewer-blocked.\nconst notificationAvailable = (\n notif: RawNotification,\n state: HydrationState,\n views: Views,\n): boolean => {\n const uri = notif.uri\n const actor = state.actors?.get(uriToDid(uri))\n if (!actor) return false\n\n const postAvailable = (postUri: AtUriString) =>\n !!state.posts?.get(postUri) &&\n !!state.actors?.get(uriToDid(postUri)) &&\n !views.viewerBlockExists(uriToDid(postUri), state)\n\n const starterPackAvailable = (starterPackUri: AtUriString) =>\n !!state.starterPacks?.get(starterPackUri) &&\n !!state.actors?.get(uriToDid(starterPackUri))\n\n switch (notif.reason) {\n case NOTIFICATION_REASON.LIKE: {\n const subjectUri = notif.reasonSubject\n if (!state.likes?.get(uri)) return false\n if (new AtUri(subjectUri).collection === app.bsky.feed.generator.$type) {\n return (\n !!state.feedgens?.get(subjectUri) &&\n !!state.actors?.get(uriToDid(subjectUri))\n )\n }\n return postAvailable(subjectUri)\n }\n case NOTIFICATION_REASON.REPOST:\n return !!state.reposts?.get(uri) && postAvailable(notif.reasonSubject)\n case NOTIFICATION_REASON.LIKE_VIA_REPOST:\n case NOTIFICATION_REASON.REPOST_VIA_REPOST: {\n const recordInfo =\n notif.reason === NOTIFICATION_REASON.LIKE_VIA_REPOST\n ? state.likes?.get(uri)\n : state.reposts?.get(uri)\n const postUri = state.reposts?.get(notif.reasonSubject)?.record.subject\n .uri\n return !!recordInfo && !!postUri && postAvailable(postUri)\n }\n case NOTIFICATION_REASON.FOLLOW:\n case NOTIFICATION_REASON.FOLLOW_BACK:\n return !!state.follows?.get(uri)\n case NOTIFICATION_REASON.REPLY:\n return (\n postAvailable(uri) && !!state.posts?.get(uri)?.record.reply?.parent.uri\n )\n case NOTIFICATION_REASON.QUOTE:\n case NOTIFICATION_REASON.MENTION:\n case NOTIFICATION_REASON.SUBSCRIBED_POST:\n return postAvailable(uri)\n case NOTIFICATION_REASON.VERIFIED:\n case NOTIFICATION_REASON.UNVERIFIED:\n // Both notifications survive deletion of the verification record.\n return true\n case NOTIFICATION_REASON.STARTERPACK_JOINED:\n return (\n !!actor.profile &&\n !!actor.profileCid &&\n starterPackAvailable(notif.reasonSubject)\n )\n case NOTIFICATION_REASON.CONTACT_MATCH:\n return !!actor.profile && !!actor.profileCid\n default:\n return false\n }\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n): app.bsky.notification.getGroupedNotifications.$OutputBody => {\n const { ctx, params, skeleton, hydration } = input\n const defs = app.bsky.notification.getGroupedNotifications\n const groups = mapDefined(skeleton.groups, (group) =>\n ctx.views.notificationGroup(group, hydration),\n )\n\n const maxGroupItems = ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews\n\n // Build related profiles, records.\n const profileDids = new Set<DidString>()\n const recordUris = new Set<AtUriString>()\n for (const { kind } of groups) {\n if (\n defs.likeGroup.$isTypeOf(kind) ||\n defs.repostGroup.$isTypeOf(kind) ||\n defs.likeViaRepostGroup.$isTypeOf(kind) ||\n defs.repostViaRepostGroup.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (\n defs.multiPostLikeGroup.$isTypeOf(kind) ||\n defs.multiPostRepostGroup.$isTypeOf(kind) ||\n defs.multiPostLikeViaRepostGroup.$isTypeOf(kind) ||\n defs.multiPostRepostViaRepostGroup.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n recordUris.add(item.post)\n }\n } else if (defs.generatorLikeGroup.$isTypeOf(kind)) {\n recordUris.add(kind.generator)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (defs.followGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n if (item.starterPack) recordUris.add(item.starterPack)\n }\n } else if (defs.subscribedPostGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n recordUris.add(item.post)\n }\n } else if (\n defs.replyNotification.$isTypeOf(kind) ||\n defs.quoteNotification.$isTypeOf(kind) ||\n defs.mentionNotification.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n if (kind.parent) recordUris.add(kind.parent)\n } else if (\n defs.followBackNotification.$isTypeOf(kind) ||\n defs.starterPackJoinedNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n if (kind.starterPack) recordUris.add(kind.starterPack)\n } else if (\n defs.verifiedNotification.$isTypeOf(kind) ||\n defs.unverifiedNotification.$isTypeOf(kind) ||\n defs.contactMatchNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n }\n }\n\n const body = {\n groups,\n relatedViews: [\n ...mapDefined([...profileDids], (did) => {\n const view = ctx.views.profileBasic(did, hydration)\n if (!view) return\n return app.bsky.actor.defs.profileViewBasic.$build(view)\n }),\n ...mapDefined([...recordUris], (uri) => {\n const collection = new AtUri(uri).collection\n if (collection === app.bsky.feed.post.$type) {\n const view = ctx.views.maybePost(uri, hydration)\n if (!view) return\n return view\n } else if (collection === app.bsky.graph.starterpack.$type) {\n const view = ctx.views.starterPackBasic(uri, hydration)\n if (!view) return\n return app.bsky.graph.defs.starterPackViewBasic.$build(view)\n } else if (collection === app.bsky.feed.generator.$type) {\n const view = ctx.views.feedGenerator(uri, hydration)\n if (!view) return\n return app.bsky.feed.defs.generatorView.$build(view)\n }\n }),\n ],\n cursor: skeleton.cursor,\n seenAt: params.seenAt,\n }\n\n events.groupedNotifications({\n itemsFetched: skeleton.itemsFetched,\n itemsReturned: groups.reduce((count, group) => count + group.count, 0),\n })\n\n return body\n}\n\nconst truncateRelatedViewsItems = <T extends unknown[]>(\n items: T,\n maxItems: number,\n): T[number][] => items.slice(0, maxItems)\n\nconst normalizeFeed = (feed: string): NotificationFeed => {\n switch (feed) {\n case 'people-i-follow':\n return NotificationFeed.PEOPLE_I_FOLLOW\n case 'conversations':\n return NotificationFeed.CONVERSATIONS\n case 'followers':\n return NotificationFeed.FOLLOWERS\n case 'activity':\n return NotificationFeed.ACTIVITY\n default:\n return NotificationFeed.ALL\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: ServerConfig\n}\n\ntype Params = app.bsky.notification.getGroupedNotifications.$Params & {\n hydrateCtx: HydrateCtxWithViewer\n seenAt?: DatetimeString\n}\n\ntype SkeletonState = {\n groups: NotificationGroup[]\n cursor?: string\n itemsFetched: number\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  import type { GroupingResult, NotificationGroup, NotificationItem } from './grouping.js';
2
- export declare const buildSpotlight: (items: NotificationItem[], groups: NotificationGroup[], cursor: string | undefined, limit: number, seenAt: number | undefined) => GroupingResult;
2
+ export declare function buildSpotlight(items: NotificationItem[], groups: NotificationGroup[], cursor: string | undefined, limit: number, seenAt: number | undefined): GroupingResult;
3
3
  //# sourceMappingURL=spotlight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/spotlight.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AAsBtB,eAAO,MAAM,cAAc,UAClB,gBAAgB,EAAE,UACjB,iBAAiB,EAAE,UACnB,MAAM,GAAG,SAAS,SACnB,MAAM,UACL,MAAM,GAAG,SAAS,KACzB,cAuEF,CAAA"}
1
+ {"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/spotlight.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AAiCtB,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,EAAE,EACzB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,cAAc,CAyGhB"}