@atproto/bsky 0.0.97 → 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.
@@ -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.97",
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.19",
45
+ "@atproto/api": "^0.13.20",
46
+ "@atproto/common": "^0.4.5",
46
47
  "@atproto/crypto": "^0.4.2",
47
48
  "@atproto/identity": "^0.4.3",
48
- "@atproto/common": "^0.4.4",
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.19",
70
- "@atproto/pds": "^0.4.75",
71
- "@atproto/lex-cli": "^0.5.2",
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/*/*",
@@ -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
 
@@ -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
@@ -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,
@@ -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 {
@@ -417,6 +417,84 @@ describe('notification views', () => {
417
417
  ),
418
418
  ).toBe(true)
419
419
  expect(forSnapshot(notifs.data)).toMatchSnapshot()
420
+ await agent.api.app.bsky.notification.putPreferences(
421
+ { priority: false },
422
+ {
423
+ encoding: 'application/json',
424
+ headers: await network.serviceHeaders(
425
+ sc.dids.carol,
426
+ ids.AppBskyNotificationPutPreferences,
427
+ ),
428
+ },
429
+ )
430
+ await network.processAll()
431
+ })
432
+
433
+ it('filters notifications by reason', async () => {
434
+ const res = await agent.app.bsky.notification.listNotifications(
435
+ {
436
+ reasons: ['mention'],
437
+ },
438
+ {
439
+ headers: await network.serviceHeaders(
440
+ sc.dids.alice,
441
+ ids.AppBskyNotificationListNotifications,
442
+ ),
443
+ },
444
+ )
445
+ expect(res.data.notifications.length).toBe(1)
446
+ expect(forSnapshot(res.data)).toMatchSnapshot()
447
+ })
448
+
449
+ it('filters notifications by multiple reasons', async () => {
450
+ const res = await agent.app.bsky.notification.listNotifications(
451
+ {
452
+ reasons: ['mention', 'reply'],
453
+ },
454
+ {
455
+ headers: await network.serviceHeaders(
456
+ sc.dids.alice,
457
+ ids.AppBskyNotificationListNotifications,
458
+ ),
459
+ },
460
+ )
461
+ expect(res.data.notifications.length).toBe(4)
462
+ expect(forSnapshot(res.data)).toMatchSnapshot()
463
+ })
464
+
465
+ it('paginates filtered notifications', async () => {
466
+ const results = (results) =>
467
+ sort(results.flatMap((res) => res.notifications))
468
+ const paginator = async (cursor?: string) => {
469
+ const res = await agent.app.bsky.notification.listNotifications(
470
+ { reasons: ['mention', 'reply'], cursor, limit: 2 },
471
+ {
472
+ headers: await network.serviceHeaders(
473
+ alice,
474
+ ids.AppBskyNotificationListNotifications,
475
+ ),
476
+ },
477
+ )
478
+ return res.data
479
+ }
480
+
481
+ const paginatedAll = await paginateAll(paginator)
482
+ paginatedAll.forEach((res) =>
483
+ expect(res.notifications.length).toBeLessThanOrEqual(2),
484
+ )
485
+
486
+ const full = await agent.app.bsky.notification.listNotifications(
487
+ { reasons: ['mention', 'reply'] },
488
+ {
489
+ headers: await network.serviceHeaders(
490
+ alice,
491
+ ids.AppBskyNotificationListNotifications,
492
+ ),
493
+ },
494
+ )
495
+
496
+ expect(full.data.notifications.length).toBe(4)
497
+ expect(results(paginatedAll)).toEqual(results([full.data]))
420
498
  })
421
499
 
422
500
  it('fails open on clearly bad cursor.', async () => {