@atproto/bsky 0.0.96 → 0.0.98

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 (37) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/api/app/bsky/actor/getProfile.js +3 -1
  3. package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
  4. package/dist/api/app/bsky/actor/getProfiles.d.ts.map +1 -1
  5. package/dist/api/app/bsky/actor/getProfiles.js +6 -2
  6. package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
  7. package/dist/api/app/bsky/feed/getLikes.d.ts.map +1 -1
  8. package/dist/api/app/bsky/feed/getLikes.js +14 -7
  9. package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
  10. package/dist/api/app/bsky/notification/listNotifications.js +45 -3
  11. package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
  12. package/dist/auth-verifier.d.ts.map +1 -1
  13. package/dist/auth-verifier.js.map +1 -1
  14. package/dist/hydration/hydrator.d.ts +6 -1
  15. package/dist/hydration/hydrator.d.ts.map +1 -1
  16. package/dist/hydration/hydrator.js +28 -4
  17. package/dist/hydration/hydrator.js.map +1 -1
  18. package/dist/lexicon/lexicons.d.ts +17795 -7841
  19. package/dist/lexicon/lexicons.d.ts.map +1 -1
  20. package/dist/lexicon/lexicons.js +8 -0
  21. package/dist/lexicon/lexicons.js.map +1 -1
  22. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +2 -0
  23. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts.map +1 -1
  24. package/dist/lexicon/types/app/bsky/notification/listNotifications.js.map +1 -1
  25. package/package.json +11 -11
  26. package/src/api/app/bsky/actor/getProfile.ts +3 -1
  27. package/src/api/app/bsky/actor/getProfiles.ts +6 -2
  28. package/src/api/app/bsky/feed/getLikes.ts +24 -12
  29. package/src/api/app/bsky/notification/listNotifications.ts +57 -3
  30. package/src/auth-verifier.ts +1 -8
  31. package/src/hydration/hydrator.ts +33 -4
  32. package/src/lexicon/lexicons.ts +12 -2
  33. package/src/lexicon/types/app/bsky/notification/listNotifications.ts +2 -0
  34. package/tests/views/__snapshots__/notifications.test.ts.snap +257 -0
  35. package/tests/views/labels-takedown.test.ts +31 -0
  36. package/tests/views/likes.test.ts +76 -0
  37. package/tests/views/notifications.test.ts +78 -0
@@ -7,6 +7,8 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server';
7
7
  import * as AppBskyActorDefs from '../actor/defs';
8
8
  import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs';
9
9
  export interface QueryParams {
10
+ /** Notification reasons to include in response. */
11
+ reasons?: string[];
10
12
  limit: number;
11
13
  priority?: boolean;
12
14
  cursor?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"listNotifications.d.ts","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/notification/listNotifications.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAW,MAAM,kBAAkB,CAAA;AAI5D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,KAAK,gBAAgB,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,mBAAmB,MAAM,iCAAiC,CAAA;AAEtE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,CAAA;AAEpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,kBAAkB,CAAA;AAC9E,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,WAAW,GAAG,KAAK,IAAI;IAC1D,IAAI,EAAE,EAAE,CAAA;IACR,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,YAAY,CAAA;IACnB,GAAG,EAAE,OAAO,CAAC,OAAO,CAAA;IACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAA;CACtB,CAAA;AACD,MAAM,MAAM,OAAO,CAAC,EAAE,SAAS,WAAW,GAAG,KAAK,IAAI,CACpD,GAAG,EAAE,aAAa,CAAC,EAAE,CAAC,KACnB,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,gBAAgB,CAAC,WAAW,CAAA;IACpC,6GAA6G;IAC7G,MAAM,EACF,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,OAAO,GACP,OAAO,GACP,oBAAoB,GACpB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,EAAE,CAAA;IACV,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,mBAAmB,CAAC,KAAK,EAAE,CAAA;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,YAAY,CAM5D;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAKjE"}
1
+ {"version":3,"file":"listNotifications.d.ts","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/notification/listNotifications.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAW,MAAM,kBAAkB,CAAA;AAI5D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,KAAK,gBAAgB,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,mBAAmB,MAAM,iCAAiC,CAAA;AAEtE,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,CAAA;AAEpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,kBAAkB,CAAA;AAC9E,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,WAAW,GAAG,KAAK,IAAI;IAC1D,IAAI,EAAE,EAAE,CAAA;IACR,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,YAAY,CAAA;IACnB,GAAG,EAAE,OAAO,CAAC,OAAO,CAAA;IACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAA;CACtB,CAAA;AACD,MAAM,MAAM,OAAO,CAAC,EAAE,SAAS,WAAW,GAAG,KAAK,IAAI,CACpD,GAAG,EAAE,aAAa,CAAC,EAAE,CAAC,KACnB,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,gBAAgB,CAAC,WAAW,CAAA;IACpC,6GAA6G;IAC7G,MAAM,EACF,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,OAAO,GACP,OAAO,GACP,oBAAoB,GACpB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,EAAE,CAAA;IACV,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,mBAAmB,CAAC,KAAK,EAAE,CAAA;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,YAAY,CAM5D;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAKjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"listNotifications.js","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/notification/listNotifications.ts"],"names":[],"mappings":";;AA4EA,wCAMC;AAED,oDAKC;AApFD,mDAA+C;AAC/C,2CAAiD;AAsEjD,SAAgB,cAAc,CAAC,CAAU;IACvC,OAAO,CACL,IAAA,YAAK,EAAC,CAAC,CAAC;QACR,IAAA,cAAO,EAAC,CAAC,EAAE,OAAO,CAAC;QACnB,CAAC,CAAC,KAAK,KAAK,sDAAsD,CACnE,CAAA;AACH,CAAC;AAED,SAAgB,oBAAoB,CAAC,CAAU;IAC7C,OAAO,mBAAQ,CAAC,QAAQ,CACtB,sDAAsD,EACtD,CAAC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"listNotifications.js","sourceRoot":"","sources":["../../../../../../src/lexicon/types/app/bsky/notification/listNotifications.ts"],"names":[],"mappings":";;AA8EA,wCAMC;AAED,oDAKC;AAtFD,mDAA+C;AAC/C,2CAAiD;AAwEjD,SAAgB,cAAc,CAAC,CAAU;IACvC,OAAO,CACL,IAAA,YAAK,EAAC,CAAC,CAAC;QACR,IAAA,cAAO,EAAC,CAAC,EAAE,OAAO,CAAC;QACnB,CAAC,CAAC,KAAK,KAAK,sDAAsD,CACnE,CAAA;AACH,CAAC;AAED,SAAgB,oBAAoB,CAAC,CAAU;IAC7C,OAAO,mBAAQ,CAAC,QAAQ,CACtB,sDAAsD,EACtD,CAAC,CACF,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -42,15 +42,15 @@
42
42
  "structured-headers": "^1.0.1",
43
43
  "typed-emitter": "^2.1.0",
44
44
  "uint8arrays": "3.0.0",
45
- "@atproto/api": "^0.13.18",
46
- "@atproto/common": "^0.4.4",
45
+ "@atproto/api": "^0.13.20",
46
+ "@atproto/common": "^0.4.5",
47
47
  "@atproto/crypto": "^0.4.2",
48
48
  "@atproto/identity": "^0.4.3",
49
- "@atproto/lexicon": "^0.4.3",
50
- "@atproto/repo": "^0.5.5",
51
- "@atproto/sync": "^0.1.6",
49
+ "@atproto/lexicon": "^0.4.4",
50
+ "@atproto/repo": "^0.6.0",
51
+ "@atproto/sync": "^0.1.7",
52
52
  "@atproto/syntax": "^0.3.1",
53
- "@atproto/xrpc-server": "^0.7.3"
53
+ "@atproto/xrpc-server": "^0.7.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@bufbuild/buf": "^1.28.1",
@@ -66,10 +66,10 @@
66
66
  "jest": "^28.1.2",
67
67
  "ts-node": "^10.8.2",
68
68
  "typescript": "^5.6.3",
69
- "@atproto/api": "^0.13.18",
70
- "@atproto/lex-cli": "^0.5.2",
71
- "@atproto/pds": "^0.4.73",
72
- "@atproto/xrpc": "^0.6.4"
69
+ "@atproto/api": "^0.13.20",
70
+ "@atproto/lex-cli": "^0.5.3",
71
+ "@atproto/pds": "^0.4.76",
72
+ "@atproto/xrpc": "^0.6.5"
73
73
  },
74
74
  "scripts": {
75
75
  "codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*",
@@ -60,7 +60,9 @@ const hydration = async (input: {
60
60
  const { ctx, params, skeleton } = input
61
61
  return ctx.hydrator.hydrateProfilesDetailed(
62
62
  [skeleton.did],
63
- params.hydrateCtx.copy({ includeTakedowns: true }),
63
+ params.hydrateCtx.copy({
64
+ includeActorTakedowns: true,
65
+ }),
64
66
  )
65
67
  }
66
68
 
@@ -25,9 +25,13 @@ export default function (server: Server, ctx: AppContext) {
25
25
  },
26
26
  }),
27
27
  handler: async ({ auth, params, req }) => {
28
- const viewer = auth.credentials.iss
28
+ const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth)
29
29
  const labelers = ctx.reqLabelers(req)
30
- const hydrateCtx = await ctx.hydrator.createContext({ viewer, labelers })
30
+ const hydrateCtx = await ctx.hydrator.createContext({
31
+ viewer,
32
+ labelers,
33
+ includeTakedowns,
34
+ })
31
35
 
32
36
  const result = await getProfile({ ...params, hydrateCtx }, ctx)
33
37
 
@@ -3,11 +3,12 @@ import { normalizeDatetimeAlways } from '@atproto/syntax'
3
3
  import { Server } from '../../../../lexicon'
4
4
  import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getLikes'
5
5
  import AppContext from '../../../../context'
6
- import { createPipeline } from '../../../../pipeline'
6
+ import { createPipeline, RulesFnInput } from '../../../../pipeline'
7
7
  import {
8
8
  HydrateCtx,
9
9
  HydrationState,
10
10
  Hydrator,
11
+ mergeStates,
11
12
  } from '../../../../hydration/hydrator'
12
13
  import { Views } from '../../../../views'
13
14
  import { parseString } from '../../../../hydration/util'
@@ -43,8 +44,10 @@ const skeleton = async (inputs: {
43
44
  params: Params
44
45
  }): Promise<Skeleton> => {
45
46
  const { ctx, params } = inputs
47
+ const authorDid = creatorFromUri(params.uri)
48
+
46
49
  if (clearlyBadCursor(params.cursor)) {
47
- return { likes: [] }
50
+ return { authorDid, likes: [] }
48
51
  }
49
52
  if (looksLikeNonSortedCursor(params.cursor)) {
50
53
  throw new InvalidRequestError(
@@ -57,6 +60,7 @@ const skeleton = async (inputs: {
57
60
  limit: params.limit,
58
61
  })
59
62
  return {
63
+ authorDid,
60
64
  likes: likesRes.uris,
61
65
  cursor: parseString(likesRes.cursor),
62
66
  }
@@ -68,18 +72,25 @@ const hydration = async (inputs: {
68
72
  skeleton: Skeleton
69
73
  }) => {
70
74
  const { ctx, params, skeleton } = inputs
71
- return await ctx.hydrator.hydrateLikes(skeleton.likes, params.hydrateCtx)
75
+ const likesState = await ctx.hydrator.hydrateLikes(
76
+ skeleton.authorDid,
77
+ skeleton.likes,
78
+ params.hydrateCtx,
79
+ )
80
+ return likesState
72
81
  }
73
82
 
74
- const noBlocks = (inputs: {
75
- ctx: Context
76
- skeleton: Skeleton
77
- hydration: HydrationState
78
- }) => {
79
- const { ctx, skeleton, hydration } = inputs
80
- skeleton.likes = skeleton.likes.filter((uri) => {
81
- const creator = creatorFromUri(uri)
82
- return !ctx.views.viewerBlockExists(creator, hydration)
83
+ const noBlocks = (input: RulesFnInput<Context, Params, Skeleton>) => {
84
+ const { ctx, skeleton, hydration } = input
85
+
86
+ skeleton.likes = skeleton.likes.filter((likeUri) => {
87
+ const like = hydration.likes?.get(likeUri)
88
+ if (!like) return false
89
+ const likerDid = creatorFromUri(likeUri)
90
+ return (
91
+ !hydration.likeBlocks?.get(likeUri) &&
92
+ !ctx.views.viewerBlockExists(likerDid, hydration)
93
+ )
83
94
  })
84
95
  return skeleton
85
96
  }
@@ -123,6 +134,7 @@ type Context = {
123
134
  type Params = QueryParams & { hydrateCtx: HydrateCtx }
124
135
 
125
136
  type Skeleton = {
137
+ authorDid: string
126
138
  likes: string[]
127
139
  cursor?: string
128
140
  }
@@ -43,6 +43,56 @@ export default function (server: Server, ctx: AppContext) {
43
43
  })
44
44
  }
45
45
 
46
+ const paginateNotifications = async (opts: {
47
+ ctx: Context
48
+ priority: boolean
49
+ reasons?: string[]
50
+ cursor?: string
51
+ limit: number
52
+ viewer: string
53
+ }) => {
54
+ const { ctx, priority, reasons, limit, viewer } = opts
55
+
56
+ // if not filtering, then just pass through the response from dataplane
57
+ if (!reasons) {
58
+ const res = await ctx.hydrator.dataplane.getNotifications({
59
+ actorDid: viewer,
60
+ priority,
61
+ cursor: opts.cursor,
62
+ limit,
63
+ })
64
+ return {
65
+ notifications: res.notifications,
66
+ cursor: res.cursor,
67
+ }
68
+ }
69
+
70
+ let nextCursor: string | undefined = opts.cursor
71
+ let toReturn: Notification[] = []
72
+ const maxAttempts = 10
73
+ const attemptSize = Math.ceil(limit / 2)
74
+ for (let i = 0; i < maxAttempts; i++) {
75
+ const res = await ctx.hydrator.dataplane.getNotifications({
76
+ actorDid: viewer,
77
+ priority,
78
+ cursor: nextCursor,
79
+ limit,
80
+ })
81
+ const filtered = res.notifications.filter((notif) =>
82
+ reasons.includes(notif.reason),
83
+ )
84
+ toReturn = [...toReturn, ...filtered]
85
+ nextCursor = res.cursor ?? undefined
86
+ if (toReturn.length >= attemptSize || !nextCursor) {
87
+ break
88
+ }
89
+ }
90
+ return {
91
+ notifications: toReturn,
92
+ cursor: nextCursor,
93
+ }
94
+ }
95
+
46
96
  const skeleton = async (
47
97
  input: SkeletonFnInput<Context, Params>,
48
98
  ): Promise<SkeletonState> => {
@@ -56,11 +106,13 @@ const skeleton = async (
56
106
  return { notifs: [], priority }
57
107
  }
58
108
  const [res, lastSeenRes] = await Promise.all([
59
- ctx.hydrator.dataplane.getNotifications({
60
- actorDid: viewer,
109
+ paginateNotifications({
110
+ ctx,
61
111
  priority,
112
+ reasons: params.reasons,
62
113
  cursor: params.cursor,
63
114
  limit: params.limit,
115
+ viewer,
64
116
  }),
65
117
  ctx.hydrator.dataplane.getNotificationSeen({
66
118
  actorDid: viewer,
@@ -126,7 +178,9 @@ const noBlockOrMutesOrNeedsReview = (
126
178
  if (
127
179
  item.reason === 'reply' ||
128
180
  item.reason === 'quote' ||
129
- item.reason === 'mention'
181
+ item.reason === 'mention' ||
182
+ item.reason === 'like' ||
183
+ item.reason === 'follow'
130
184
  ) {
131
185
  if (!ctx.views.viewerSeesNeedsReview(did, hydration)) {
132
186
  return false
@@ -18,14 +18,7 @@ import {
18
18
  unpackIdentityKeys,
19
19
  } from './data-plane'
20
20
  import { GetIdentityByDidResponse } from './proto/bsky_pb'
21
- import {
22
- extractMultikey,
23
- extractPrefixedBytes,
24
- hasPrefix,
25
- parseDidKey,
26
- SECP256K1_DID_PREFIX,
27
- SECP256K1_JWT_ALG,
28
- } from '@atproto/crypto'
21
+ import { parseDidKey, SECP256K1_JWT_ALG } from '@atproto/crypto'
29
22
 
30
23
  type ReqCtx = {
31
24
  req: express.Request
@@ -65,6 +65,7 @@ export class HydrateCtx {
65
65
  labelers = this.vals.labelers
66
66
  viewer = this.vals.viewer !== null ? serviceRefToDid(this.vals.viewer) : null
67
67
  includeTakedowns = this.vals.includeTakedowns
68
+ includeActorTakedowns = this.vals.includeActorTakedowns
68
69
  include3pBlocks = this.vals.include3pBlocks
69
70
  constructor(private vals: HydrateCtxVals) {}
70
71
  copy<V extends Partial<HydrateCtxVals>>(vals?: V): HydrateCtx & V {
@@ -76,6 +77,7 @@ export type HydrateCtxVals = {
76
77
  labelers: ParsedLabelers
77
78
  viewer: string | null
78
79
  includeTakedowns?: boolean
80
+ includeActorTakedowns?: boolean
79
81
  include3pBlocks?: boolean
80
82
  }
81
83
 
@@ -98,6 +100,7 @@ export type HydrationState = {
98
100
  listViewers?: ListViewerStates
99
101
  listItems?: ListItems
100
102
  likes?: Likes
103
+ likeBlocks?: LikeBlocks
101
104
  labels?: Labels
102
105
  feedgens?: FeedGens
103
106
  feedgenViewers?: FeedGenViewerStates
@@ -119,6 +122,9 @@ type PostBlockPairs = {
119
122
  root?: RelationshipPair
120
123
  }
121
124
 
125
+ export type LikeBlock = boolean
126
+ export type LikeBlocks = HydrationMap<LikeBlock>
127
+
122
128
  export type FollowBlock = boolean
123
129
  export type FollowBlocks = HydrationMap<FollowBlock>
124
130
 
@@ -179,12 +185,13 @@ export class Hydrator {
179
185
  dids: string[],
180
186
  ctx: HydrateCtx,
181
187
  ): Promise<HydrationState> {
188
+ const includeTakedowns = ctx.includeTakedowns || ctx.includeActorTakedowns
182
189
  const [actors, labels, profileViewersState] = await Promise.all([
183
- this.actor.getActors(dids, ctx.includeTakedowns),
190
+ this.actor.getActors(dids, includeTakedowns),
184
191
  this.label.getLabelsForSubjects(labelSubjectsForDid(dids), ctx.labelers),
185
192
  this.hydrateProfileViewers(dids, ctx),
186
193
  ])
187
- if (!ctx.includeTakedowns) {
194
+ if (!includeTakedowns) {
188
195
  actionTakedownLabels(dids, actors, labels)
189
196
  }
190
197
  return mergeStates(profileViewersState ?? {}, {
@@ -743,12 +750,33 @@ export class Hydrator {
743
750
  // - like
744
751
  // - profile
745
752
  // - list basic
746
- async hydrateLikes(uris: string[], ctx: HydrateCtx): Promise<HydrationState> {
753
+ async hydrateLikes(
754
+ authorDid: string,
755
+ uris: string[],
756
+ ctx: HydrateCtx,
757
+ ): Promise<HydrationState> {
747
758
  const [likes, profileState] = await Promise.all([
748
759
  this.feed.getLikes(uris, ctx.includeTakedowns),
749
760
  this.hydrateProfiles(uris.map(didFromUri), ctx),
750
761
  ])
751
- return mergeStates(profileState, { likes, ctx })
762
+
763
+ const pairs: RelationshipPair[] = []
764
+ for (const [uri, like] of likes) {
765
+ if (like) {
766
+ pairs.push([authorDid, didFromUri(uri)])
767
+ }
768
+ }
769
+ const blocks = await this.graph.getBidirectionalBlocks(pairs)
770
+ const likeBlocks = new HydrationMap<LikeBlock>()
771
+ for (const [uri, like] of likes) {
772
+ if (like) {
773
+ likeBlocks.set(uri, blocks.isBlocked(authorDid, didFromUri(uri)))
774
+ } else {
775
+ likeBlocks.set(uri, null)
776
+ }
777
+ }
778
+
779
+ return mergeStates(profileState, { likes, likeBlocks, ctx })
752
780
  }
753
781
 
754
782
  // app.bsky.feed.getRepostedBy#repostedBy
@@ -1151,6 +1179,7 @@ export const mergeStates = (
1151
1179
  listViewers: mergeMaps(stateA.listViewers, stateB.listViewers),
1152
1180
  listItems: mergeMaps(stateA.listItems, stateB.listItems),
1153
1181
  likes: mergeMaps(stateA.likes, stateB.likes),
1182
+ likeBlocks: mergeMaps(stateA.likeBlocks, stateB.likeBlocks),
1154
1183
  labels: mergeMaps(stateA.labels, stateB.labels),
1155
1184
  feedgens: mergeMaps(stateA.feedgens, stateB.feedgens),
1156
1185
  feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs),
@@ -8944,6 +8944,15 @@ export const schemaDict = {
8944
8944
  parameters: {
8945
8945
  type: 'params',
8946
8946
  properties: {
8947
+ reasons: {
8948
+ description: 'Notification reasons to include in response.',
8949
+ type: 'array',
8950
+ items: {
8951
+ type: 'string',
8952
+ description:
8953
+ 'A reason that matches the reason property of #notification.',
8954
+ },
8955
+ },
8947
8956
  limit: {
8948
8957
  type: 'integer',
8949
8958
  minimum: 1,
@@ -10728,8 +10737,9 @@ export const schemaDict = {
10728
10737
  },
10729
10738
  },
10730
10739
  },
10731
- }
10732
- export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
10740
+ } as const satisfies Record<string, LexiconDoc>
10741
+
10742
+ export const schemas = Object.values(schemaDict)
10733
10743
  export const lexicons: Lexicons = new Lexicons(schemas)
10734
10744
  export const ids = {
10735
10745
  ComAtprotoAdminDefs: 'com.atproto.admin.defs',
@@ -11,6 +11,8 @@ import * as AppBskyActorDefs from '../actor/defs'
11
11
  import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
12
12
 
13
13
  export interface QueryParams {
14
+ /** Notification reasons to include in response. */
15
+ reasons?: string[]
14
16
  limit: number
15
17
  priority?: boolean
16
18
  cursor?: string
@@ -1263,6 +1263,263 @@ Array [
1263
1263
  ]
1264
1264
  `;
1265
1265
 
1266
+ exports[`notification views filters notifications by multiple reasons 1`] = `
1267
+ Object {
1268
+ "notifications": Array [
1269
+ Object {
1270
+ "author": Object {
1271
+ "did": "user(0)",
1272
+ "handle": "carol.test",
1273
+ "labels": Array [],
1274
+ "viewer": Object {
1275
+ "blockedBy": false,
1276
+ "followedBy": "record(2)",
1277
+ "following": "record(1)",
1278
+ "muted": false,
1279
+ },
1280
+ },
1281
+ "cid": "cids(0)",
1282
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1283
+ "isRead": false,
1284
+ "labels": Array [],
1285
+ "reason": "reply",
1286
+ "reasonSubject": "record(3)",
1287
+ "record": Object {
1288
+ "$type": "app.bsky.feed.post",
1289
+ "createdAt": "1970-01-01T00:00:00.000Z",
1290
+ "reply": Object {
1291
+ "parent": Object {
1292
+ "cid": "cids(2)",
1293
+ "uri": "record(3)",
1294
+ },
1295
+ "root": Object {
1296
+ "cid": "cids(1)",
1297
+ "uri": "record(4)",
1298
+ },
1299
+ },
1300
+ "text": "indeed",
1301
+ },
1302
+ "uri": "record(0)",
1303
+ },
1304
+ Object {
1305
+ "author": Object {
1306
+ "did": "user(0)",
1307
+ "handle": "carol.test",
1308
+ "labels": Array [],
1309
+ "viewer": Object {
1310
+ "blockedBy": false,
1311
+ "followedBy": "record(2)",
1312
+ "following": "record(1)",
1313
+ "muted": false,
1314
+ },
1315
+ },
1316
+ "cid": "cids(3)",
1317
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1318
+ "isRead": false,
1319
+ "labels": Array [],
1320
+ "reason": "reply",
1321
+ "reasonSubject": "record(4)",
1322
+ "record": Object {
1323
+ "$type": "app.bsky.feed.post",
1324
+ "createdAt": "1970-01-01T00:00:00.000Z",
1325
+ "reply": Object {
1326
+ "parent": Object {
1327
+ "cid": "cids(1)",
1328
+ "uri": "record(4)",
1329
+ },
1330
+ "root": Object {
1331
+ "cid": "cids(1)",
1332
+ "uri": "record(4)",
1333
+ },
1334
+ },
1335
+ "text": "of course",
1336
+ },
1337
+ "uri": "record(5)",
1338
+ },
1339
+ Object {
1340
+ "author": Object {
1341
+ "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(5)@jpeg",
1342
+ "createdAt": "1970-01-01T00:00:00.000Z",
1343
+ "description": "hi im bob label_me",
1344
+ "did": "user(1)",
1345
+ "displayName": "bobby",
1346
+ "handle": "bob.test",
1347
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1348
+ "labels": Array [],
1349
+ "viewer": Object {
1350
+ "blockedBy": false,
1351
+ "followedBy": "record(8)",
1352
+ "following": "record(7)",
1353
+ "muted": false,
1354
+ },
1355
+ },
1356
+ "cid": "cids(4)",
1357
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1358
+ "isRead": false,
1359
+ "labels": Array [
1360
+ Object {
1361
+ "cid": "cids(4)",
1362
+ "cts": "1970-01-01T00:00:00.000Z",
1363
+ "src": "did:example:labeler",
1364
+ "uri": "record(6)",
1365
+ "val": "test-label",
1366
+ },
1367
+ Object {
1368
+ "cid": "cids(4)",
1369
+ "cts": "1970-01-01T00:00:00.000Z",
1370
+ "src": "did:example:labeler",
1371
+ "uri": "record(6)",
1372
+ "val": "test-label-2",
1373
+ },
1374
+ ],
1375
+ "reason": "reply",
1376
+ "reasonSubject": "record(4)",
1377
+ "record": Object {
1378
+ "$type": "app.bsky.feed.post",
1379
+ "createdAt": "1970-01-01T00:00:00.000Z",
1380
+ "embed": Object {
1381
+ "$type": "app.bsky.embed.images",
1382
+ "images": Array [
1383
+ Object {
1384
+ "alt": "../dev-env/src/seed/img/key-landscape-small.jpg",
1385
+ "image": Object {
1386
+ "$type": "blob",
1387
+ "mimeType": "image/jpeg",
1388
+ "ref": Object {
1389
+ "$link": "cids(6)",
1390
+ },
1391
+ "size": 4114,
1392
+ },
1393
+ },
1394
+ ],
1395
+ },
1396
+ "reply": Object {
1397
+ "parent": Object {
1398
+ "cid": "cids(1)",
1399
+ "uri": "record(4)",
1400
+ },
1401
+ "root": Object {
1402
+ "cid": "cids(1)",
1403
+ "uri": "record(4)",
1404
+ },
1405
+ },
1406
+ "text": "hear that label_me label_me_2",
1407
+ },
1408
+ "uri": "record(6)",
1409
+ },
1410
+ Object {
1411
+ "author": Object {
1412
+ "associated": Object {
1413
+ "chat": Object {
1414
+ "allowIncoming": "none",
1415
+ },
1416
+ },
1417
+ "did": "user(3)",
1418
+ "handle": "dan.test",
1419
+ "labels": Array [],
1420
+ "viewer": Object {
1421
+ "blockedBy": false,
1422
+ "following": "record(10)",
1423
+ "muted": false,
1424
+ },
1425
+ },
1426
+ "cid": "cids(7)",
1427
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1428
+ "isRead": false,
1429
+ "labels": Array [],
1430
+ "reason": "mention",
1431
+ "record": Object {
1432
+ "$type": "app.bsky.feed.post",
1433
+ "createdAt": "1970-01-01T00:00:00.000Z",
1434
+ "embed": Object {
1435
+ "$type": "app.bsky.embed.record",
1436
+ "record": Object {
1437
+ "cid": "cids(8)",
1438
+ "uri": "record(11)",
1439
+ },
1440
+ },
1441
+ "facets": Array [
1442
+ Object {
1443
+ "features": Array [
1444
+ Object {
1445
+ "$type": "app.bsky.richtext.facet#mention",
1446
+ "did": "user(4)",
1447
+ },
1448
+ ],
1449
+ "index": Object {
1450
+ "byteEnd": 18,
1451
+ "byteStart": 0,
1452
+ },
1453
+ },
1454
+ ],
1455
+ "text": "@alice.bluesky.xyz is the best",
1456
+ },
1457
+ "uri": "record(9)",
1458
+ },
1459
+ ],
1460
+ "priority": false,
1461
+ "seenAt": "1970-01-01T00:00:00.000Z",
1462
+ }
1463
+ `;
1464
+
1465
+ exports[`notification views filters notifications by reason 1`] = `
1466
+ Object {
1467
+ "notifications": Array [
1468
+ Object {
1469
+ "author": Object {
1470
+ "associated": Object {
1471
+ "chat": Object {
1472
+ "allowIncoming": "none",
1473
+ },
1474
+ },
1475
+ "did": "user(0)",
1476
+ "handle": "dan.test",
1477
+ "labels": Array [],
1478
+ "viewer": Object {
1479
+ "blockedBy": false,
1480
+ "following": "record(1)",
1481
+ "muted": false,
1482
+ },
1483
+ },
1484
+ "cid": "cids(0)",
1485
+ "indexedAt": "1970-01-01T00:00:00.000Z",
1486
+ "isRead": false,
1487
+ "labels": Array [],
1488
+ "reason": "mention",
1489
+ "record": Object {
1490
+ "$type": "app.bsky.feed.post",
1491
+ "createdAt": "1970-01-01T00:00:00.000Z",
1492
+ "embed": Object {
1493
+ "$type": "app.bsky.embed.record",
1494
+ "record": Object {
1495
+ "cid": "cids(1)",
1496
+ "uri": "record(2)",
1497
+ },
1498
+ },
1499
+ "facets": Array [
1500
+ Object {
1501
+ "features": Array [
1502
+ Object {
1503
+ "$type": "app.bsky.richtext.facet#mention",
1504
+ "did": "user(1)",
1505
+ },
1506
+ ],
1507
+ "index": Object {
1508
+ "byteEnd": 18,
1509
+ "byteStart": 0,
1510
+ },
1511
+ },
1512
+ ],
1513
+ "text": "@alice.bluesky.xyz is the best",
1514
+ },
1515
+ "uri": "record(0)",
1516
+ },
1517
+ ],
1518
+ "priority": false,
1519
+ "seenAt": "1970-01-01T00:00:00.000Z",
1520
+ }
1521
+ `;
1522
+
1266
1523
  exports[`notification views generates notifications for quotes 1`] = `
1267
1524
  Array [
1268
1525
  Object {