@atproto/bsky 0.0.65 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/api/app/bsky/actor/getSuggestions.js +1 -1
- package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
- package/dist/api/app/bsky/feed/getLikes.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getLikes.js +6 -2
- package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
- package/dist/api/app/bsky/feed/getRepostedBy.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getRepostedBy.js +6 -2
- package/dist/api/app/bsky/feed/getRepostedBy.js.map +1 -1
- package/dist/api/app/bsky/graph/getLists.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getLists.js +10 -1
- package/dist/api/app/bsky/graph/getLists.js.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +37 -12
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
- package/dist/api/com/atproto/repo/getRecord.d.ts.map +1 -1
- package/dist/api/com/atproto/repo/getRecord.js +27 -19
- package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -1
- package/dist/feature-gates.d.ts +25 -0
- package/dist/feature-gates.d.ts.map +1 -0
- package/dist/feature-gates.js +82 -0
- package/dist/feature-gates.js.map +1 -0
- package/dist/hydration/hydrator.d.ts +3 -0
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +67 -44
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/hydration/util.d.ts +3 -0
- package/dist/hydration/util.d.ts.map +1 -1
- package/dist/hydration/util.js +25 -1
- package/dist/hydration/util.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +35 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +39 -1
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts +17 -1
- package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js +21 -1
- package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/record.d.ts +1 -1
- package/dist/lexicon/types/app/bsky/embed/record.d.ts.map +1 -1
- package/dist/lexicon/types/app/bsky/embed/record.js.map +1 -1
- package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts +2 -0
- package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.js.map +1 -1
- package/dist/views/index.d.ts +3 -3
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +41 -8
- package/dist/views/index.js.map +1 -1
- package/package.json +5 -4
- package/src/api/app/bsky/actor/getSuggestions.ts +1 -1
- package/src/api/app/bsky/feed/getLikes.ts +6 -2
- package/src/api/app/bsky/feed/getRepostedBy.ts +6 -2
- package/src/api/app/bsky/graph/getLists.ts +19 -2
- package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +55 -15
- package/src/api/com/atproto/repo/getRecord.ts +28 -19
- package/src/config.ts +20 -0
- package/src/context.ts +6 -0
- package/src/feature-gates.ts +66 -0
- package/src/hydration/hydrator.ts +58 -16
- package/src/hydration/util.ts +28 -0
- package/src/index.ts +9 -0
- package/src/lexicon/lexicons.ts +43 -1
- package/src/lexicon/types/app/bsky/actor/defs.ts +40 -0
- package/src/lexicon/types/app/bsky/embed/record.ts +1 -0
- package/src/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +2 -0
- package/src/logger.ts +2 -0
- package/src/views/index.ts +40 -12
- package/tests/__snapshots__/feed-generation.test.ts.snap +136 -0
- package/tests/feed-generation.test.ts +80 -0
- package/tests/hydration/util.test.ts +82 -0
- package/tests/seed/known-followers.ts +110 -0
- package/tests/views/__snapshots__/lists.test.ts.snap +42 -0
- package/tests/views/known-followers.test.ts +160 -0
- package/tests/views/lists.test.ts +62 -0
- package/tests/views/profile.test.ts +0 -35
package/src/lexicon/lexicons.ts
CHANGED
|
@@ -3343,7 +3343,7 @@ export const schemaDict = {
|
|
|
3343
3343
|
main: {
|
|
3344
3344
|
type: 'query',
|
|
3345
3345
|
description:
|
|
3346
|
-
'List blob
|
|
3346
|
+
'List blob CIDs for an account, since some repo revision. Does not require auth; implemented by PDS.',
|
|
3347
3347
|
parameters: {
|
|
3348
3348
|
type: 'params',
|
|
3349
3349
|
required: ['did'],
|
|
@@ -4166,6 +4166,8 @@ export const schemaDict = {
|
|
|
4166
4166
|
'lex:app.bsky.actor.defs#interestsPref',
|
|
4167
4167
|
'lex:app.bsky.actor.defs#mutedWordsPref',
|
|
4168
4168
|
'lex:app.bsky.actor.defs#hiddenPostsPref',
|
|
4169
|
+
'lex:app.bsky.actor.defs#bskyAppStatePref',
|
|
4170
|
+
'lex:app.bsky.actor.defs#labelersPref',
|
|
4169
4171
|
],
|
|
4170
4172
|
},
|
|
4171
4173
|
},
|
|
@@ -4407,6 +4409,39 @@ export const schemaDict = {
|
|
|
4407
4409
|
},
|
|
4408
4410
|
},
|
|
4409
4411
|
},
|
|
4412
|
+
bskyAppStatePref: {
|
|
4413
|
+
description:
|
|
4414
|
+
"A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this.",
|
|
4415
|
+
type: 'object',
|
|
4416
|
+
properties: {
|
|
4417
|
+
activeProgressGuide: {
|
|
4418
|
+
type: 'ref',
|
|
4419
|
+
ref: 'lex:app.bsky.actor.defs#bskyAppProgressGuide',
|
|
4420
|
+
},
|
|
4421
|
+
queuedNudges: {
|
|
4422
|
+
description:
|
|
4423
|
+
'An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user.',
|
|
4424
|
+
type: 'array',
|
|
4425
|
+
maxLength: 1000,
|
|
4426
|
+
items: {
|
|
4427
|
+
type: 'string',
|
|
4428
|
+
maxLength: 100,
|
|
4429
|
+
},
|
|
4430
|
+
},
|
|
4431
|
+
},
|
|
4432
|
+
},
|
|
4433
|
+
bskyAppProgressGuide: {
|
|
4434
|
+
description:
|
|
4435
|
+
'If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress.',
|
|
4436
|
+
type: 'object',
|
|
4437
|
+
required: ['guide'],
|
|
4438
|
+
properties: {
|
|
4439
|
+
guide: {
|
|
4440
|
+
type: 'string',
|
|
4441
|
+
maxLength: 100,
|
|
4442
|
+
},
|
|
4443
|
+
},
|
|
4444
|
+
},
|
|
4410
4445
|
},
|
|
4411
4446
|
},
|
|
4412
4447
|
AppBskyActorGetPreferences: {
|
|
@@ -4923,6 +4958,7 @@ export const schemaDict = {
|
|
|
4923
4958
|
'lex:app.bsky.feed.defs#generatorView',
|
|
4924
4959
|
'lex:app.bsky.graph.defs#listView',
|
|
4925
4960
|
'lex:app.bsky.labeler.defs#labelerView',
|
|
4961
|
+
'lex:app.bsky.graph.defs#starterPackViewBasic',
|
|
4926
4962
|
],
|
|
4927
4963
|
},
|
|
4928
4964
|
},
|
|
@@ -8676,6 +8712,12 @@ export const schemaDict = {
|
|
|
8676
8712
|
cursor: {
|
|
8677
8713
|
type: 'string',
|
|
8678
8714
|
},
|
|
8715
|
+
relativeToDid: {
|
|
8716
|
+
type: 'string',
|
|
8717
|
+
format: 'did',
|
|
8718
|
+
description:
|
|
8719
|
+
'DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer.',
|
|
8720
|
+
},
|
|
8679
8721
|
},
|
|
8680
8722
|
},
|
|
8681
8723
|
output: {
|
|
@@ -184,6 +184,8 @@ export type Preferences = (
|
|
|
184
184
|
| InterestsPref
|
|
185
185
|
| MutedWordsPref
|
|
186
186
|
| HiddenPostsPref
|
|
187
|
+
| BskyAppStatePref
|
|
188
|
+
| LabelersPref
|
|
187
189
|
| { $type: string; [k: string]: unknown }
|
|
188
190
|
)[]
|
|
189
191
|
|
|
@@ -456,3 +458,41 @@ export function isLabelerPrefItem(v: unknown): v is LabelerPrefItem {
|
|
|
456
458
|
export function validateLabelerPrefItem(v: unknown): ValidationResult {
|
|
457
459
|
return lexicons.validate('app.bsky.actor.defs#labelerPrefItem', v)
|
|
458
460
|
}
|
|
461
|
+
|
|
462
|
+
/** A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this. */
|
|
463
|
+
export interface BskyAppStatePref {
|
|
464
|
+
activeProgressGuide?: BskyAppProgressGuide
|
|
465
|
+
/** An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user. */
|
|
466
|
+
queuedNudges?: string[]
|
|
467
|
+
[k: string]: unknown
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function isBskyAppStatePref(v: unknown): v is BskyAppStatePref {
|
|
471
|
+
return (
|
|
472
|
+
isObj(v) &&
|
|
473
|
+
hasProp(v, '$type') &&
|
|
474
|
+
v.$type === 'app.bsky.actor.defs#bskyAppStatePref'
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function validateBskyAppStatePref(v: unknown): ValidationResult {
|
|
479
|
+
return lexicons.validate('app.bsky.actor.defs#bskyAppStatePref', v)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress. */
|
|
483
|
+
export interface BskyAppProgressGuide {
|
|
484
|
+
guide: string
|
|
485
|
+
[k: string]: unknown
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function isBskyAppProgressGuide(v: unknown): v is BskyAppProgressGuide {
|
|
489
|
+
return (
|
|
490
|
+
isObj(v) &&
|
|
491
|
+
hasProp(v, '$type') &&
|
|
492
|
+
v.$type === 'app.bsky.actor.defs#bskyAppProgressGuide'
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function validateBskyAppProgressGuide(v: unknown): ValidationResult {
|
|
497
|
+
return lexicons.validate('app.bsky.actor.defs#bskyAppProgressGuide', v)
|
|
498
|
+
}
|
|
@@ -14,6 +14,8 @@ export interface QueryParams {
|
|
|
14
14
|
viewer?: string
|
|
15
15
|
limit: number
|
|
16
16
|
cursor?: string
|
|
17
|
+
/** DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer. */
|
|
18
|
+
relativeToDid?: string
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export type InputSchema = undefined
|
package/src/logger.ts
CHANGED
|
@@ -12,6 +12,8 @@ export const labelerLogger: ReturnType<typeof subsystemLogger> =
|
|
|
12
12
|
subsystemLogger('bsky:labeler')
|
|
13
13
|
export const hydrationLogger: ReturnType<typeof subsystemLogger> =
|
|
14
14
|
subsystemLogger('bsky:hydration')
|
|
15
|
+
export const featureGatesLogger: ReturnType<typeof subsystemLogger> =
|
|
16
|
+
subsystemLogger('bsky:featuregates')
|
|
15
17
|
export const httpLogger: ReturnType<typeof subsystemLogger> =
|
|
16
18
|
subsystemLogger('bsky')
|
|
17
19
|
|
package/src/views/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax'
|
|
|
2
2
|
import { mapDefined } from '@atproto/common'
|
|
3
3
|
import { ImageUriBuilder } from '../image/uri'
|
|
4
4
|
import { HydrationState } from '../hydration/hydrator'
|
|
5
|
-
import { ProfileViewerState as HydratorProfileViewerState } from '../hydration/actor'
|
|
6
5
|
import { ids } from '../lexicon/lexicons'
|
|
7
6
|
import {
|
|
8
7
|
ProfileViewDetailed,
|
|
@@ -113,10 +112,7 @@ export class Views {
|
|
|
113
112
|
if (!actor) return
|
|
114
113
|
const baseView = this.profile(did, state)
|
|
115
114
|
if (!baseView) return
|
|
116
|
-
const
|
|
117
|
-
const knownFollowers = knownFollowersSkeleton
|
|
118
|
-
? this.knownFollowers(knownFollowersSkeleton, state)
|
|
119
|
-
: undefined
|
|
115
|
+
const knownFollowers = this.knownFollowers(did, state)
|
|
120
116
|
const profileAggs = state.profileAggs?.get(did)
|
|
121
117
|
return {
|
|
122
118
|
...baseView,
|
|
@@ -213,6 +209,26 @@ export class Views {
|
|
|
213
209
|
}
|
|
214
210
|
}
|
|
215
211
|
|
|
212
|
+
profileKnownFollowers(
|
|
213
|
+
did: string,
|
|
214
|
+
state: HydrationState,
|
|
215
|
+
): ProfileView | undefined {
|
|
216
|
+
const actor = state.actors?.get(did)
|
|
217
|
+
if (!actor) return
|
|
218
|
+
const baseView = this.profile(did, state)
|
|
219
|
+
if (!baseView) return
|
|
220
|
+
const knownFollowers = this.knownFollowers(did, state)
|
|
221
|
+
return {
|
|
222
|
+
...baseView,
|
|
223
|
+
viewer: baseView.viewer
|
|
224
|
+
? {
|
|
225
|
+
...baseView.viewer,
|
|
226
|
+
knownFollowers,
|
|
227
|
+
}
|
|
228
|
+
: undefined,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
216
232
|
profileViewer(
|
|
217
233
|
did: string,
|
|
218
234
|
state: HydrationState,
|
|
@@ -237,15 +253,22 @@ export class Views {
|
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
|
|
240
|
-
knownFollowers(
|
|
241
|
-
knownFollowers
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const followers = mapDefined(knownFollowers.followers, (
|
|
245
|
-
if (this.viewerBlockExists(
|
|
256
|
+
knownFollowers(did: string, state: HydrationState) {
|
|
257
|
+
const knownFollowers = state.knownFollowers?.get(did)
|
|
258
|
+
if (!knownFollowers) return
|
|
259
|
+
const blocks = state.bidirectionalBlocks?.get(did)
|
|
260
|
+
const followers = mapDefined(knownFollowers.followers, (followerDid) => {
|
|
261
|
+
if (this.viewerBlockExists(followerDid, state)) {
|
|
246
262
|
return undefined
|
|
247
263
|
}
|
|
248
|
-
|
|
264
|
+
if (blocks?.get(followerDid) === true) {
|
|
265
|
+
return undefined
|
|
266
|
+
}
|
|
267
|
+
if (this.actorIsNoHosted(followerDid, state)) {
|
|
268
|
+
// @TODO only needed right now to work around getProfile's { includeTakedowns: true }
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
|
+
return this.profileBasic(followerDid, state)
|
|
249
272
|
})
|
|
250
273
|
return { count: knownFollowers.count, followers }
|
|
251
274
|
}
|
|
@@ -936,6 +959,11 @@ export class Views {
|
|
|
936
959
|
if (!view) return this.embedNotFound(uri)
|
|
937
960
|
view.$type = 'app.bsky.labeler.defs#labelerView'
|
|
938
961
|
return this.recordEmbedWrapper(view, withTypeTag)
|
|
962
|
+
} else if (parsedUri.collection === ids.AppBskyGraphStarterpack) {
|
|
963
|
+
const view = this.starterPackBasic(uri, state)
|
|
964
|
+
if (!view) return this.embedNotFound(uri)
|
|
965
|
+
view.$type = 'app.bsky.graph.defs#starterPackViewBasic'
|
|
966
|
+
return this.recordEmbedWrapper(view, withTypeTag)
|
|
939
967
|
}
|
|
940
968
|
return this.embedNotFound(uri)
|
|
941
969
|
}
|
|
@@ -47,6 +47,53 @@ Object {
|
|
|
47
47
|
}
|
|
48
48
|
`;
|
|
49
49
|
|
|
50
|
+
exports[`feed generation does not embed taken-down starter pack records in posts 1`] = `
|
|
51
|
+
Object {
|
|
52
|
+
"author": Object {
|
|
53
|
+
"avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg",
|
|
54
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
55
|
+
"did": "user(0)",
|
|
56
|
+
"displayName": "bobby",
|
|
57
|
+
"handle": "bob.test",
|
|
58
|
+
"labels": Array [],
|
|
59
|
+
"viewer": Object {
|
|
60
|
+
"blockedBy": false,
|
|
61
|
+
"muted": false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"cid": "cids(0)",
|
|
65
|
+
"embed": Object {
|
|
66
|
+
"$type": "app.bsky.embed.record#view",
|
|
67
|
+
"record": Object {
|
|
68
|
+
"$type": "app.bsky.embed.record#viewNotFound",
|
|
69
|
+
"notFound": true,
|
|
70
|
+
"uri": "record(1)",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
"indexedAt": "1970-01-01T00:00:00.000Z",
|
|
74
|
+
"labels": Array [],
|
|
75
|
+
"likeCount": 0,
|
|
76
|
+
"record": Object {
|
|
77
|
+
"$type": "app.bsky.feed.post",
|
|
78
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
79
|
+
"embed": Object {
|
|
80
|
+
"$type": "app.bsky.embed.record",
|
|
81
|
+
"record": Object {
|
|
82
|
+
"cid": "cids(2)",
|
|
83
|
+
"uri": "record(1)",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"text": "annoying starter pack",
|
|
87
|
+
},
|
|
88
|
+
"replyCount": 0,
|
|
89
|
+
"repostCount": 0,
|
|
90
|
+
"uri": "record(0)",
|
|
91
|
+
"viewer": Object {
|
|
92
|
+
"threadMuted": false,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
50
97
|
exports[`feed generation embeds feed generator records in posts 1`] = `
|
|
51
98
|
Object {
|
|
52
99
|
"author": Object {
|
|
@@ -134,6 +181,95 @@ Object {
|
|
|
134
181
|
}
|
|
135
182
|
`;
|
|
136
183
|
|
|
184
|
+
exports[`feed generation embeds starter pack records in posts 1`] = `
|
|
185
|
+
Object {
|
|
186
|
+
"author": Object {
|
|
187
|
+
"avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg",
|
|
188
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
189
|
+
"did": "user(0)",
|
|
190
|
+
"displayName": "bobby",
|
|
191
|
+
"handle": "bob.test",
|
|
192
|
+
"labels": Array [],
|
|
193
|
+
"viewer": Object {
|
|
194
|
+
"blockedBy": false,
|
|
195
|
+
"muted": false,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
"cid": "cids(0)",
|
|
199
|
+
"embed": Object {
|
|
200
|
+
"$type": "app.bsky.embed.record#view",
|
|
201
|
+
"record": Object {
|
|
202
|
+
"$type": "app.bsky.graph.defs#starterPackViewBasic",
|
|
203
|
+
"cid": "cids(2)",
|
|
204
|
+
"creator": Object {
|
|
205
|
+
"avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg",
|
|
206
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
207
|
+
"did": "user(2)",
|
|
208
|
+
"displayName": "ali",
|
|
209
|
+
"handle": "alice.test",
|
|
210
|
+
"labels": Array [
|
|
211
|
+
Object {
|
|
212
|
+
"cid": "cids(3)",
|
|
213
|
+
"cts": "1970-01-01T00:00:00.000Z",
|
|
214
|
+
"src": "user(2)",
|
|
215
|
+
"uri": "record(5)",
|
|
216
|
+
"val": "self-label-a",
|
|
217
|
+
},
|
|
218
|
+
Object {
|
|
219
|
+
"cid": "cids(3)",
|
|
220
|
+
"cts": "1970-01-01T00:00:00.000Z",
|
|
221
|
+
"src": "user(2)",
|
|
222
|
+
"uri": "record(5)",
|
|
223
|
+
"val": "self-label-b",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
"viewer": Object {
|
|
227
|
+
"blockedBy": false,
|
|
228
|
+
"followedBy": "record(4)",
|
|
229
|
+
"following": "record(3)",
|
|
230
|
+
"muted": false,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
"indexedAt": "1970-01-01T00:00:00.000Z",
|
|
234
|
+
"joinedAllTimeCount": 0,
|
|
235
|
+
"joinedWeekCount": 0,
|
|
236
|
+
"labels": Array [],
|
|
237
|
+
"record": Object {
|
|
238
|
+
"$type": "app.bsky.graph.starterpack",
|
|
239
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
240
|
+
"description": "",
|
|
241
|
+
"descriptionFacets": Array [],
|
|
242
|
+
"feeds": Array [],
|
|
243
|
+
"list": "record(2)",
|
|
244
|
+
"name": "awesome starter pack!",
|
|
245
|
+
},
|
|
246
|
+
"uri": "record(1)",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
"indexedAt": "1970-01-01T00:00:00.000Z",
|
|
250
|
+
"labels": Array [],
|
|
251
|
+
"likeCount": 0,
|
|
252
|
+
"record": Object {
|
|
253
|
+
"$type": "app.bsky.feed.post",
|
|
254
|
+
"createdAt": "1970-01-01T00:00:00.000Z",
|
|
255
|
+
"embed": Object {
|
|
256
|
+
"$type": "app.bsky.embed.record",
|
|
257
|
+
"record": Object {
|
|
258
|
+
"cid": "cids(2)",
|
|
259
|
+
"uri": "record(1)",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
"text": "sick starter pack!",
|
|
263
|
+
},
|
|
264
|
+
"replyCount": 0,
|
|
265
|
+
"repostCount": 0,
|
|
266
|
+
"uri": "record(0)",
|
|
267
|
+
"viewer": Object {
|
|
268
|
+
"threadMuted": false,
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
|
|
137
273
|
exports[`feed generation getActorFeeds fetches feed generators by actor. 1`] = `
|
|
138
274
|
Array [
|
|
139
275
|
Object {
|
|
@@ -34,6 +34,7 @@ describe('feed generation', () => {
|
|
|
34
34
|
let feedUriPrime: string // Taken-down
|
|
35
35
|
let feedUriPrimeRef: RecordRef
|
|
36
36
|
let feedUriNeedsAuth: string
|
|
37
|
+
let starterPackRef: { uri: string; cid: string }
|
|
37
38
|
|
|
38
39
|
beforeAll(async () => {
|
|
39
40
|
network = await TestNetwork.create({
|
|
@@ -251,6 +252,85 @@ describe('feed generation', () => {
|
|
|
251
252
|
expect(forSnapshot(view.data.posts[0])).toMatchSnapshot()
|
|
252
253
|
})
|
|
253
254
|
|
|
255
|
+
it('embeds starter pack records in posts', async () => {
|
|
256
|
+
const listRes = await pdsAgent.api.app.bsky.graph.list.create(
|
|
257
|
+
{
|
|
258
|
+
repo: sc.dids.alice,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'awesome starter pack!',
|
|
262
|
+
description: '',
|
|
263
|
+
descriptionFacets: [],
|
|
264
|
+
avatar: undefined,
|
|
265
|
+
createdAt: new Date().toISOString(),
|
|
266
|
+
purpose: 'app.bsky.graph.defs#referencelist',
|
|
267
|
+
},
|
|
268
|
+
sc.getHeaders(sc.dids.alice),
|
|
269
|
+
)
|
|
270
|
+
const starterPackRes = await pdsAgent.api.app.bsky.graph.starterpack.create(
|
|
271
|
+
{
|
|
272
|
+
repo: sc.dids.alice,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'awesome starter pack!',
|
|
276
|
+
description: '',
|
|
277
|
+
descriptionFacets: [],
|
|
278
|
+
feeds: [],
|
|
279
|
+
list: listRes.uri,
|
|
280
|
+
createdAt: new Date().toISOString(),
|
|
281
|
+
},
|
|
282
|
+
sc.getHeaders(sc.dids.alice),
|
|
283
|
+
)
|
|
284
|
+
starterPackRef = {
|
|
285
|
+
uri: starterPackRes.uri,
|
|
286
|
+
cid: starterPackRes.cid,
|
|
287
|
+
}
|
|
288
|
+
const res = await pdsAgent.api.app.bsky.feed.post.create(
|
|
289
|
+
{ repo: sc.dids.bob },
|
|
290
|
+
{
|
|
291
|
+
text: 'sick starter pack!',
|
|
292
|
+
embed: {
|
|
293
|
+
$type: 'app.bsky.embed.record',
|
|
294
|
+
record: starterPackRef,
|
|
295
|
+
},
|
|
296
|
+
createdAt: new Date().toISOString(),
|
|
297
|
+
},
|
|
298
|
+
sc.getHeaders(sc.dids.bob),
|
|
299
|
+
)
|
|
300
|
+
await network.processAll()
|
|
301
|
+
const view = await agent.api.app.bsky.feed.getPosts(
|
|
302
|
+
{ uris: [res.uri] },
|
|
303
|
+
{ headers: await network.serviceHeaders(sc.dids.bob) },
|
|
304
|
+
)
|
|
305
|
+
expect(view.data.posts.length).toBe(1)
|
|
306
|
+
expect(forSnapshot(view.data.posts[0])).toMatchSnapshot()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('does not embed taken-down starter pack records in posts', async () => {
|
|
310
|
+
await network.bsky.ctx.dataplane.takedownRecord({
|
|
311
|
+
recordUri: starterPackRef.uri,
|
|
312
|
+
})
|
|
313
|
+
const res = await pdsAgent.api.app.bsky.feed.post.create(
|
|
314
|
+
{ repo: sc.dids.bob },
|
|
315
|
+
{
|
|
316
|
+
text: 'annoying starter pack',
|
|
317
|
+
embed: {
|
|
318
|
+
$type: 'app.bsky.embed.record',
|
|
319
|
+
record: starterPackRef,
|
|
320
|
+
},
|
|
321
|
+
createdAt: new Date().toISOString(),
|
|
322
|
+
},
|
|
323
|
+
sc.getHeaders(sc.dids.bob),
|
|
324
|
+
)
|
|
325
|
+
await network.processAll()
|
|
326
|
+
const view = await agent.api.app.bsky.feed.getPosts(
|
|
327
|
+
{ uris: [res.uri] },
|
|
328
|
+
{ headers: await network.serviceHeaders(sc.dids.bob) },
|
|
329
|
+
)
|
|
330
|
+
expect(view.data.posts.length).toBe(1)
|
|
331
|
+
expect(forSnapshot(view.data.posts[0])).toMatchSnapshot()
|
|
332
|
+
})
|
|
333
|
+
|
|
254
334
|
describe('getFeedGenerator', () => {
|
|
255
335
|
it('describes a feed gen & returns online status', async () => {
|
|
256
336
|
const resEven = await agent.api.app.bsky.feed.getFeedGenerator(
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HydrationMap,
|
|
3
|
+
mergeMaps,
|
|
4
|
+
mergeManyMaps,
|
|
5
|
+
mergeNestedMaps,
|
|
6
|
+
} from '../../src/hydration/util'
|
|
7
|
+
|
|
8
|
+
const mapToObj = (map: HydrationMap<any>) => {
|
|
9
|
+
const obj: Record<string, any> = {}
|
|
10
|
+
for (const [key, value] of map) {
|
|
11
|
+
obj[key] = value
|
|
12
|
+
}
|
|
13
|
+
return obj
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('hydration util', () => {
|
|
17
|
+
it(`mergeMaps: merges two maps`, () => {
|
|
18
|
+
const compare = new HydrationMap<string>()
|
|
19
|
+
compare.set('a', 'a')
|
|
20
|
+
compare.set('b', 'b')
|
|
21
|
+
|
|
22
|
+
const a = new HydrationMap<string>().set('a', 'a')
|
|
23
|
+
const b = new HydrationMap<string>().set('b', 'b')
|
|
24
|
+
const merged = mergeMaps(a, b)
|
|
25
|
+
|
|
26
|
+
expect(mapToObj(merged!)).toEqual(mapToObj(compare))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it(`mergeManyMaps: merges three maps`, () => {
|
|
30
|
+
const compare = new HydrationMap<string>()
|
|
31
|
+
compare.set('a', 'a')
|
|
32
|
+
compare.set('b', 'b')
|
|
33
|
+
compare.set('c', 'c')
|
|
34
|
+
|
|
35
|
+
const a = new HydrationMap<string>().set('a', 'a')
|
|
36
|
+
const b = new HydrationMap<string>().set('b', 'b')
|
|
37
|
+
const c = new HydrationMap<string>().set('c', 'c')
|
|
38
|
+
const merged = mergeManyMaps(a, b, c)
|
|
39
|
+
|
|
40
|
+
expect(mapToObj(merged!)).toEqual(mapToObj(compare))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it(`mergeNestedMaps: merges two nested maps`, () => {
|
|
44
|
+
const compare = new HydrationMap<HydrationMap<string>>()
|
|
45
|
+
const compareA = new HydrationMap<string>().set('a', 'a')
|
|
46
|
+
const compareB = new HydrationMap<string>().set('b', 'b')
|
|
47
|
+
compare.set('a', compareA)
|
|
48
|
+
compare.set('b', compareB)
|
|
49
|
+
|
|
50
|
+
const a = new HydrationMap<HydrationMap<string>>().set(
|
|
51
|
+
'a',
|
|
52
|
+
new HydrationMap<string>().set('a', 'a'),
|
|
53
|
+
)
|
|
54
|
+
const b = new HydrationMap<HydrationMap<string>>().set(
|
|
55
|
+
'b',
|
|
56
|
+
new HydrationMap<string>().set('b', 'b'),
|
|
57
|
+
)
|
|
58
|
+
const merged = mergeNestedMaps(a, b)
|
|
59
|
+
|
|
60
|
+
expect(mapToObj(merged!)).toEqual(mapToObj(compare))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it(`mergeNestedMaps: merges two nested maps with common keys`, () => {
|
|
64
|
+
const compare = new HydrationMap<HydrationMap<boolean>>()
|
|
65
|
+
const compareA = new HydrationMap<boolean>()
|
|
66
|
+
compareA.set('b', true)
|
|
67
|
+
compareA.set('c', true)
|
|
68
|
+
compare.set('a', compareA)
|
|
69
|
+
|
|
70
|
+
const a = new HydrationMap<HydrationMap<boolean>>().set(
|
|
71
|
+
'a',
|
|
72
|
+
new HydrationMap<boolean>().set('b', true),
|
|
73
|
+
)
|
|
74
|
+
const b = new HydrationMap<HydrationMap<boolean>>().set(
|
|
75
|
+
'a',
|
|
76
|
+
new HydrationMap<boolean>().set('c', true),
|
|
77
|
+
)
|
|
78
|
+
const merged = mergeNestedMaps(a, b)
|
|
79
|
+
|
|
80
|
+
expect(mapToObj(merged!)).toEqual(mapToObj(compare))
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { TestNetwork, SeedClient, TestNetworkNoAppView } from '@atproto/dev-env'
|
|
2
|
+
|
|
3
|
+
export type User = {
|
|
4
|
+
email: string
|
|
5
|
+
handle: string
|
|
6
|
+
password: string
|
|
7
|
+
displayName: string
|
|
8
|
+
description: string
|
|
9
|
+
selfLabels: undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createUser(name: string): User {
|
|
13
|
+
return {
|
|
14
|
+
email: `${name}@test.com`,
|
|
15
|
+
handle: `${name}.test`,
|
|
16
|
+
password: `${name}-pass`,
|
|
17
|
+
displayName: name,
|
|
18
|
+
description: `hi im ${name} label_me`,
|
|
19
|
+
selfLabels: undefined,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const users = {
|
|
24
|
+
/*
|
|
25
|
+
* Base test. One known follower.
|
|
26
|
+
*/
|
|
27
|
+
base_sub: createUser('base-sub'),
|
|
28
|
+
base_res_1: createUser('base-res-1'),
|
|
29
|
+
base_view: createUser('base-view'),
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* First-part block of a single known follower.
|
|
33
|
+
*/
|
|
34
|
+
fp_block_sub: createUser('fp-block-sub'),
|
|
35
|
+
fp_block_res_1: createUser('fp-block-res-1'),
|
|
36
|
+
fp_block_view: createUser('fp-block-view'),
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Second-party block of a single known follower.
|
|
40
|
+
*/
|
|
41
|
+
sp_block_sub: createUser('sp-block-sub'),
|
|
42
|
+
sp_block_res_1: createUser('sp-block-res-1'),
|
|
43
|
+
sp_block_view: createUser('sp-block-view'),
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* Mix of known followers and blocks.
|
|
47
|
+
*/
|
|
48
|
+
mix_sub_1: createUser('mix-sub-1'),
|
|
49
|
+
mix_sub_2: createUser('mix-sub-2'),
|
|
50
|
+
mix_sub_3: createUser('mix-sub-3'),
|
|
51
|
+
mix_res: createUser('mix-res'),
|
|
52
|
+
mix_fp_block_res: createUser('mix-fp-block-res'),
|
|
53
|
+
mix_sp_block_res: createUser('mix-sp-block-res'),
|
|
54
|
+
mix_view: createUser('mix-view'),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function knownFollowersSeed(
|
|
58
|
+
sc: SeedClient<TestNetwork | TestNetworkNoAppView>,
|
|
59
|
+
) {
|
|
60
|
+
await sc.createAccount('base_sub', users.base_sub)
|
|
61
|
+
await sc.createAccount('base_res_1', users.base_res_1)
|
|
62
|
+
await sc.createAccount('base_view', users.base_view)
|
|
63
|
+
|
|
64
|
+
await sc.createAccount('fp_block_sub', users.fp_block_sub)
|
|
65
|
+
await sc.createAccount('fp_block_res_1', users.fp_block_res_1)
|
|
66
|
+
await sc.createAccount('fp_block_view', users.fp_block_view)
|
|
67
|
+
|
|
68
|
+
await sc.createAccount('sp_block_sub', users.sp_block_sub)
|
|
69
|
+
await sc.createAccount('sp_block_res_1', users.sp_block_res_1)
|
|
70
|
+
await sc.createAccount('sp_block_view', users.sp_block_view)
|
|
71
|
+
|
|
72
|
+
await sc.createAccount('mix_sub_1', users.mix_sub_1)
|
|
73
|
+
await sc.createAccount('mix_sub_2', users.mix_sub_2)
|
|
74
|
+
await sc.createAccount('mix_sub_3', users.mix_sub_3)
|
|
75
|
+
await sc.createAccount('mix_res', users.mix_res)
|
|
76
|
+
await sc.createAccount('mix_fp_block_res', users.mix_fp_block_res)
|
|
77
|
+
await sc.createAccount('mix_sp_block_res', users.mix_sp_block_res)
|
|
78
|
+
await sc.createAccount('mix_view', users.mix_view)
|
|
79
|
+
|
|
80
|
+
const dids = sc.dids
|
|
81
|
+
|
|
82
|
+
await sc.follow(dids.base_res_1, dids.base_sub)
|
|
83
|
+
await sc.follow(dids.base_view, dids.base_res_1)
|
|
84
|
+
|
|
85
|
+
await sc.follow(dids.fp_block_res_1, dids.fp_block_sub)
|
|
86
|
+
await sc.follow(dids.fp_block_view, dids.fp_block_res_1)
|
|
87
|
+
|
|
88
|
+
await sc.follow(dids.sp_block_res_1, dids.sp_block_sub)
|
|
89
|
+
await sc.follow(dids.sp_block_view, dids.sp_block_res_1)
|
|
90
|
+
|
|
91
|
+
await sc.follow(dids.mix_res, dids.mix_sub_1)
|
|
92
|
+
await sc.follow(dids.mix_res, dids.mix_sub_2)
|
|
93
|
+
await sc.follow(dids.mix_res, dids.mix_sub_3)
|
|
94
|
+
await sc.follow(dids.mix_fp_block_res, dids.mix_sub_1)
|
|
95
|
+
await sc.follow(dids.mix_fp_block_res, dids.mix_sub_2)
|
|
96
|
+
await sc.follow(dids.mix_fp_block_res, dids.mix_sub_3)
|
|
97
|
+
await sc.follow(dids.mix_sp_block_res, dids.mix_sub_1)
|
|
98
|
+
await sc.follow(dids.mix_sp_block_res, dids.mix_sub_2)
|
|
99
|
+
await sc.follow(dids.mix_sp_block_res, dids.mix_sub_3)
|
|
100
|
+
await sc.follow(dids.mix_view, dids.mix_res)
|
|
101
|
+
await sc.follow(dids.mix_view, dids.mix_fp_block_res)
|
|
102
|
+
await sc.follow(dids.mix_view, dids.mix_sp_block_res)
|
|
103
|
+
|
|
104
|
+
await sc.network.processAll()
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
users,
|
|
108
|
+
seedClient: sc,
|
|
109
|
+
}
|
|
110
|
+
}
|