@atproto/bsky 0.0.239 → 0.0.241
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 +18 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/internal/bsky/actor/getProfiles.d.ts +4 -0
- package/dist/api/internal/bsky/actor/getProfiles.d.ts.map +1 -0
- package/dist/api/internal/bsky/actor/getProfiles.js +39 -0
- package/dist/api/internal/bsky/actor/getProfiles.js.map +1 -0
- package/dist/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.js +17 -0
- package/dist/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +1 -0
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/tables/post-embed.d.ts +8 -0
- package/dist/data-plane/server/db/tables/post-embed.d.ts.map +1 -1
- package/dist/data-plane/server/db/tables/post-embed.js +1 -0
- package/dist/data-plane/server/db/tables/post-embed.js.map +1 -1
- package/dist/data-plane/server/indexing/index.d.ts +1 -1
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js +8 -0
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/indexing/plugins/post.d.ts +2 -1
- package/dist/data-plane/server/indexing/plugins/post.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/plugins/post.js +39 -1
- package/dist/data-plane/server/indexing/plugins/post.js.map +1 -1
- package/dist/data-plane/server/routes/feeds.d.ts.map +1 -1
- package/dist/data-plane/server/routes/feeds.js +7 -2
- package/dist/data-plane/server/routes/feeds.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +3 -1
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +20 -18
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +4 -0
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.js +1 -0
- package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.d.ts +3 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.defs.d.ts +19 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.defs.js +16 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.js +6 -0
- package/dist/lexicons/chat/bsky/convo/getUnreadCounts.js.map +1 -0
- package/dist/lexicons/chat/bsky/convo/unlockConvo.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/convo/unlockConvo.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/unlockConvo.defs.js +1 -0
- package/dist/lexicons/chat/bsky/convo/unlockConvo.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo.d.ts +1 -0
- package/dist/lexicons/chat/bsky/convo.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo.js +1 -0
- package/dist/lexicons/chat/bsky/convo.js.map +1 -1
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.js +5 -1
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.js +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/defs.defs.d.ts +18 -1
- package/dist/lexicons/chat/bsky/group/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/defs.defs.js +10 -1
- package/dist/lexicons/chat/bsky/group/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts +3 -3
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js +6 -2
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js.map +1 -1
- package/dist/lexicons/index.d.ts +1 -0
- package/dist/lexicons/index.d.ts.map +1 -1
- package/dist/lexicons/index.js +1 -0
- package/dist/lexicons/index.js.map +1 -1
- package/dist/lexicons/internal/bsky/actor/getProfiles.d.ts +3 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.d.ts.map +1 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.defs.d.ts +38 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.defs.d.ts.map +1 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.defs.js +26 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.defs.js.map +1 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.js +6 -0
- package/dist/lexicons/internal/bsky/actor/getProfiles.js.map +1 -0
- package/dist/lexicons/internal/bsky/actor.d.ts +2 -0
- package/dist/lexicons/internal/bsky/actor.d.ts.map +1 -0
- package/dist/lexicons/internal/bsky/actor.js +5 -0
- package/dist/lexicons/internal/bsky/actor.js.map +1 -0
- package/dist/lexicons/internal/bsky.d.ts +2 -0
- package/dist/lexicons/internal/bsky.d.ts.map +1 -0
- package/dist/lexicons/internal/bsky.js +5 -0
- package/dist/lexicons/internal/bsky.js.map +1 -0
- package/dist/lexicons/internal.d.ts +2 -0
- package/dist/lexicons/internal.d.ts.map +1 -0
- package/dist/lexicons/internal.js +5 -0
- package/dist/lexicons/internal.js.map +1 -0
- package/package.json +7 -7
- package/src/api/index.ts +3 -0
- package/src/api/internal/bsky/actor/getProfiles.ts +87 -0
- package/src/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.ts +19 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/tables/post-embed.ts +9 -0
- package/src/data-plane/server/indexing/index.ts +8 -0
- package/src/data-plane/server/indexing/plugins/post.ts +49 -1
- package/src/data-plane/server/routes/feeds.ts +17 -4
- package/src/hydration/hydrator.ts +29 -28
- package/tests/views/author-feed.test.ts +47 -0
- package/tests/views/internal-actor.test.ts +129 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -28,6 +28,7 @@ type PostEmbedImage = DatabaseSchemaType['post_embed_image']
|
|
|
28
28
|
type PostEmbedExternal = DatabaseSchemaType['post_embed_external']
|
|
29
29
|
type PostEmbedRecord = DatabaseSchemaType['post_embed_record']
|
|
30
30
|
type PostEmbedVideo = DatabaseSchemaType['post_embed_video']
|
|
31
|
+
type PostEmbedGalleryImage = DatabaseSchemaType['post_embed_gallery_image']
|
|
31
32
|
type PostAncestor = {
|
|
32
33
|
uri: string
|
|
33
34
|
height: number
|
|
@@ -47,6 +48,7 @@ type IndexedPost = {
|
|
|
47
48
|
| PostEmbedExternal
|
|
48
49
|
| PostEmbedRecord
|
|
49
50
|
| PostEmbedVideo
|
|
51
|
+
| PostEmbedGalleryImage[]
|
|
50
52
|
)[]
|
|
51
53
|
ancestors?: PostAncestor[]
|
|
52
54
|
descendents?: PostDescendent[]
|
|
@@ -144,6 +146,7 @@ const insertFn = async (
|
|
|
144
146
|
| PostEmbedExternal
|
|
145
147
|
| PostEmbedRecord
|
|
146
148
|
| PostEmbedVideo
|
|
149
|
+
| PostEmbedGalleryImage[]
|
|
147
150
|
)[] = []
|
|
148
151
|
const postEmbeds = separateEmbeds(obj.embed)
|
|
149
152
|
for (const postEmbed of postEmbeds) {
|
|
@@ -238,6 +241,27 @@ const insertFn = async (
|
|
|
238
241
|
embeds.push(videoEmbed)
|
|
239
242
|
|
|
240
243
|
await db.insertInto('post_embed_video').values(videoEmbed).execute()
|
|
244
|
+
} else if (app.bsky.embed.gallery.$matches(postEmbed)) {
|
|
245
|
+
// Gallery items are a union; today only `#image` exists, but we
|
|
246
|
+
// defensively skip unknown variants for forward-compat.
|
|
247
|
+
const galleryImages: PostEmbedGalleryImage[] = []
|
|
248
|
+
postEmbed.items.forEach((item, i) => {
|
|
249
|
+
if (app.bsky.embed.gallery.image.$matches(item)) {
|
|
250
|
+
galleryImages.push({
|
|
251
|
+
postUri: uri.toString(),
|
|
252
|
+
position: i,
|
|
253
|
+
imageCid: getBlobCidString(item.image),
|
|
254
|
+
alt: item.alt,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
if (galleryImages.length > 0) {
|
|
259
|
+
embeds.push(galleryImages)
|
|
260
|
+
await db
|
|
261
|
+
.insertInto('post_embed_gallery_image')
|
|
262
|
+
.values(galleryImages)
|
|
263
|
+
.execute()
|
|
264
|
+
}
|
|
241
265
|
}
|
|
242
266
|
}
|
|
243
267
|
|
|
@@ -381,8 +405,16 @@ const deleteFn = async (
|
|
|
381
405
|
| PostEmbedImage[]
|
|
382
406
|
| PostEmbedExternal
|
|
383
407
|
| PostEmbedRecord
|
|
408
|
+
| PostEmbedVideo
|
|
409
|
+
| PostEmbedGalleryImage[]
|
|
384
410
|
)[] = []
|
|
385
|
-
const [
|
|
411
|
+
const [
|
|
412
|
+
deletedImgs,
|
|
413
|
+
deletedExternals,
|
|
414
|
+
deletedPosts,
|
|
415
|
+
deletedVideo,
|
|
416
|
+
deletedGalleryImgs,
|
|
417
|
+
] = await Promise.all([
|
|
386
418
|
db
|
|
387
419
|
.deleteFrom('post_embed_image')
|
|
388
420
|
.where('postUri', '=', uriStr)
|
|
@@ -398,6 +430,16 @@ const deleteFn = async (
|
|
|
398
430
|
.where('postUri', '=', uriStr)
|
|
399
431
|
.returningAll()
|
|
400
432
|
.executeTakeFirst(),
|
|
433
|
+
db
|
|
434
|
+
.deleteFrom('post_embed_video')
|
|
435
|
+
.where('postUri', '=', uriStr)
|
|
436
|
+
.returningAll()
|
|
437
|
+
.executeTakeFirst(),
|
|
438
|
+
db
|
|
439
|
+
.deleteFrom('post_embed_gallery_image')
|
|
440
|
+
.where('postUri', '=', uriStr)
|
|
441
|
+
.returningAll()
|
|
442
|
+
.execute(),
|
|
401
443
|
])
|
|
402
444
|
if (deletedImgs.length) {
|
|
403
445
|
deletedEmbeds.push(deletedImgs)
|
|
@@ -405,6 +447,12 @@ const deleteFn = async (
|
|
|
405
447
|
if (deletedExternals) {
|
|
406
448
|
deletedEmbeds.push(deletedExternals)
|
|
407
449
|
}
|
|
450
|
+
if (deletedVideo) {
|
|
451
|
+
deletedEmbeds.push(deletedVideo)
|
|
452
|
+
}
|
|
453
|
+
if (deletedGalleryImgs.length) {
|
|
454
|
+
deletedEmbeds.push(deletedGalleryImgs)
|
|
455
|
+
}
|
|
408
456
|
if (deletedPosts) {
|
|
409
457
|
const embedUri = new AtUri(deletedPosts.embedUri)
|
|
410
458
|
deletedEmbeds.push(deletedPosts)
|
|
@@ -21,11 +21,24 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
21
21
|
// only your own posts
|
|
22
22
|
.where('type', '=', 'post')
|
|
23
23
|
// only posts with media
|
|
24
|
-
.
|
|
24
|
+
.where((qb) =>
|
|
25
25
|
qb
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
.whereExists((iqb) =>
|
|
27
|
+
iqb
|
|
28
|
+
.selectFrom('post_embed_image')
|
|
29
|
+
.select('post_embed_image.postUri')
|
|
30
|
+
.whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'),
|
|
31
|
+
)
|
|
32
|
+
.orWhereExists((iqb) =>
|
|
33
|
+
iqb
|
|
34
|
+
.selectFrom('post_embed_gallery_image')
|
|
35
|
+
.select('post_embed_gallery_image.postUri')
|
|
36
|
+
.whereRef(
|
|
37
|
+
'post_embed_gallery_image.postUri',
|
|
38
|
+
'=',
|
|
39
|
+
'feed_item.postUri',
|
|
40
|
+
),
|
|
41
|
+
),
|
|
29
42
|
)
|
|
30
43
|
} else if (feedType === FeedType.POSTS_WITH_VIDEO) {
|
|
31
44
|
builder = builder
|
|
@@ -334,29 +334,31 @@ export class Hydrator {
|
|
|
334
334
|
async hydrateProfilesDetailed(
|
|
335
335
|
dids: DidString[],
|
|
336
336
|
ctx: HydrateCtx,
|
|
337
|
+
opts?: {
|
|
338
|
+
// when set, restricts known followers hydration to this subset of dids
|
|
339
|
+
knownFollowersDids?: DidString[]
|
|
340
|
+
},
|
|
337
341
|
): Promise<HydrationState> {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
}
|
|
342
|
+
const [knownFollowers, activitySubscriptions] = await Promise.all([
|
|
343
|
+
this.actor
|
|
344
|
+
.getKnownFollowers(opts?.knownFollowersDids ?? dids, ctx.viewer)
|
|
345
|
+
.catch((err): KnownFollowersStates => {
|
|
346
|
+
hydrationLogger.error(
|
|
347
|
+
{ err },
|
|
348
|
+
'Failed to get known followers for profiles',
|
|
349
|
+
)
|
|
350
|
+
return new HydrationMap()
|
|
351
|
+
}),
|
|
352
|
+
this.actor
|
|
353
|
+
.getActivitySubscriptions(dids, ctx.viewer)
|
|
354
|
+
.catch((err): ActivitySubscriptionStates => {
|
|
355
|
+
hydrationLogger.error(
|
|
356
|
+
{ err },
|
|
357
|
+
'Failed to get activity subscriptions state for profiles',
|
|
358
|
+
)
|
|
359
|
+
return new HydrationMap()
|
|
360
|
+
}),
|
|
361
|
+
])
|
|
360
362
|
|
|
361
363
|
const subjectsToKnownFollowersMap = new Map<DidString, DidString[]>()
|
|
362
364
|
|
|
@@ -1051,12 +1053,11 @@ export class Hydrator {
|
|
|
1051
1053
|
)
|
|
1052
1054
|
},
|
|
1053
1055
|
)
|
|
1054
|
-
const blocks = await
|
|
1055
|
-
pairsToMap(listCreatorMemberPairs),
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const listMemberAggs = await this.actor.getProfileAggregates(listMemberDids)
|
|
1056
|
+
const [blocks, listMemberAggs] = await Promise.all([
|
|
1057
|
+
this.hydrateBidirectionalBlocks(pairsToMap(listCreatorMemberPairs), ctx),
|
|
1058
|
+
// sample top list items per starter pack based on their follows
|
|
1059
|
+
this.actor.getProfileAggregates(listMemberDids),
|
|
1060
|
+
])
|
|
1060
1061
|
const listItemUris: AtUriString[] = []
|
|
1061
1062
|
uris.forEach((uri) => {
|
|
1062
1063
|
const sp = starterPackState.starterPacks?.get(uri)
|
|
@@ -2,6 +2,7 @@ import assert from 'node:assert'
|
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
3
3
|
import {
|
|
4
4
|
AppBskyActorProfile,
|
|
5
|
+
AppBskyEmbedGallery,
|
|
5
6
|
AppBskyEmbedImages,
|
|
6
7
|
AppBskyEmbedRecordWithMedia,
|
|
7
8
|
AppBskyEmbedVideo,
|
|
@@ -395,6 +396,52 @@ describe('pds author feed views', () => {
|
|
|
395
396
|
expect(danFeed.feed.length).toEqual(0)
|
|
396
397
|
})
|
|
397
398
|
|
|
399
|
+
it('includes gallery posts in posts_with_media', async () => {
|
|
400
|
+
const { data: blob1 } = await pdsAgent.api.com.atproto.repo.uploadBlob(
|
|
401
|
+
Buffer.from('gallery-image-1'),
|
|
402
|
+
{
|
|
403
|
+
headers: sc.getHeaders(sc.dids.dan),
|
|
404
|
+
encoding: 'image/jpeg',
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
const { data: blob2 } = await pdsAgent.api.com.atproto.repo.uploadBlob(
|
|
408
|
+
Buffer.from('gallery-image-2'),
|
|
409
|
+
{
|
|
410
|
+
headers: sc.getHeaders(sc.dids.dan),
|
|
411
|
+
encoding: 'image/jpeg',
|
|
412
|
+
},
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
await sc.post(dan, 'gallery post', undefined, undefined, undefined, {
|
|
416
|
+
embed: {
|
|
417
|
+
$type: 'app.bsky.embed.gallery',
|
|
418
|
+
items: [
|
|
419
|
+
{
|
|
420
|
+
$type: 'app.bsky.embed.gallery#image',
|
|
421
|
+
image: blob1.blob,
|
|
422
|
+
alt: 'first',
|
|
423
|
+
aspectRatio: { height: 1, width: 1 },
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
$type: 'app.bsky.embed.gallery#image',
|
|
427
|
+
image: blob2.blob,
|
|
428
|
+
alt: 'second',
|
|
429
|
+
aspectRatio: { height: 1, width: 1 },
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
})
|
|
434
|
+
await network.processAll()
|
|
435
|
+
|
|
436
|
+
const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
|
|
437
|
+
actor: dan,
|
|
438
|
+
filter: 'posts_with_media',
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
expect(danFeed.feed.length).toEqual(1)
|
|
442
|
+
assert(AppBskyEmbedGallery.isView(danFeed.feed[0].post.embed))
|
|
443
|
+
})
|
|
444
|
+
|
|
398
445
|
it('filters by posts_no_replies', async () => {
|
|
399
446
|
const { data: carolFeed } = await agent.api.app.bsky.feed.getAuthorFeed({
|
|
400
447
|
actor: carol,
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { AppBskyActorDefs, AtpAgent } from '@atproto/api'
|
|
3
|
+
import { SeedClient, TestNetwork } from '@atproto/dev-env'
|
|
4
|
+
import { knownFollowersSeed } from '../seed/known-followers.js'
|
|
5
|
+
|
|
6
|
+
describe('internal actor views', () => {
|
|
7
|
+
let network: TestNetwork
|
|
8
|
+
let pdsAgent: AtpAgent
|
|
9
|
+
let seedClient: SeedClient
|
|
10
|
+
|
|
11
|
+
let dids: Record<string, string>
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
network = await TestNetwork.create({
|
|
15
|
+
dbPostgresSchema: 'bsky_internal_actor',
|
|
16
|
+
})
|
|
17
|
+
pdsAgent = network.pds.getAgent()
|
|
18
|
+
seedClient = network.getSeedClient()
|
|
19
|
+
|
|
20
|
+
await knownFollowersSeed(seedClient)
|
|
21
|
+
|
|
22
|
+
dids = seedClient.dids
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Mix of blocks and non, mirroring the known-followers test setup so the
|
|
26
|
+
* social proof results exercise block filtering too.
|
|
27
|
+
*/
|
|
28
|
+
await pdsAgent.api.app.bsky.graph.block.create(
|
|
29
|
+
{ repo: dids.mix_view },
|
|
30
|
+
{ createdAt: new Date().toISOString(), subject: dids.mix_fp_block_res },
|
|
31
|
+
seedClient.getHeaders(dids.mix_view),
|
|
32
|
+
)
|
|
33
|
+
await pdsAgent.api.app.bsky.graph.block.create(
|
|
34
|
+
{ repo: dids.mix_sub_1 },
|
|
35
|
+
{ createdAt: new Date().toISOString(), subject: dids.mix_sp_block_res },
|
|
36
|
+
seedClient.getHeaders(dids.mix_sub_1),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
await network.processAll()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterAll(async () => {
|
|
43
|
+
await network.close()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('getProfiles', () => {
|
|
47
|
+
const getProfiles = async (
|
|
48
|
+
params: { dids: string[]; socialProof?: string[]; viewer?: string },
|
|
49
|
+
headers: Record<string, string> = network.bsky.adminAuthHeaders(),
|
|
50
|
+
) => {
|
|
51
|
+
const search = new URLSearchParams()
|
|
52
|
+
params.dids.forEach((did) => search.append('dids', did))
|
|
53
|
+
params.socialProof?.forEach((did) => search.append('socialProof', did))
|
|
54
|
+
if (params.viewer) search.append('viewer', params.viewer)
|
|
55
|
+
const res = await fetch(
|
|
56
|
+
`${network.bsky.url}/xrpc/internal.bsky.actor.getProfiles?${search.toString()}`,
|
|
57
|
+
{ headers },
|
|
58
|
+
)
|
|
59
|
+
return {
|
|
60
|
+
status: res.status,
|
|
61
|
+
body: (await res.json()) as {
|
|
62
|
+
profiles: AppBskyActorDefs.ProfileViewDetailed[]
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
it('requires role auth, rejecting standard user auth', async () => {
|
|
68
|
+
const userHeaders = await network.serviceHeaders(
|
|
69
|
+
dids.mix_view,
|
|
70
|
+
'internal.bsky.actor.getProfiles',
|
|
71
|
+
)
|
|
72
|
+
const { status } = await getProfiles(
|
|
73
|
+
{ dids: [dids.mix_sub_1] },
|
|
74
|
+
userHeaders,
|
|
75
|
+
)
|
|
76
|
+
expect(status).toBe(401)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('returns all profiles, with social proof only for the requested subset', async () => {
|
|
80
|
+
const { status, body } = await getProfiles({
|
|
81
|
+
dids: [dids.mix_sub_1, dids.mix_sub_2, dids.mix_sub_3],
|
|
82
|
+
socialProof: [dids.mix_sub_1],
|
|
83
|
+
viewer: dids.mix_view,
|
|
84
|
+
})
|
|
85
|
+
expect(status).toBe(200)
|
|
86
|
+
expect(body.profiles).toHaveLength(3)
|
|
87
|
+
|
|
88
|
+
const [sub_1, sub_2, sub_3] = body.profiles
|
|
89
|
+
expect(sub_1.viewer?.knownFollowers?.count).toBe(3)
|
|
90
|
+
expect(sub_1.viewer?.knownFollowers?.followers).toHaveLength(1)
|
|
91
|
+
expect(sub_2.viewer?.knownFollowers).toBeUndefined()
|
|
92
|
+
expect(sub_3.viewer?.knownFollowers).toBeUndefined()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('ignores socialProof dids that are not in dids', async () => {
|
|
96
|
+
const { status, body } = await getProfiles({
|
|
97
|
+
dids: [dids.mix_sub_1],
|
|
98
|
+
socialProof: [dids.mix_sub_1, dids.mix_sub_2],
|
|
99
|
+
viewer: dids.mix_view,
|
|
100
|
+
})
|
|
101
|
+
expect(status).toBe(200)
|
|
102
|
+
expect(body.profiles).toHaveLength(1)
|
|
103
|
+
expect(body.profiles[0].did).toBe(dids.mix_sub_1)
|
|
104
|
+
expect(body.profiles[0].viewer?.knownFollowers?.count).toBe(3)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('returns no social proof when socialProof is omitted', async () => {
|
|
108
|
+
const { status, body } = await getProfiles({
|
|
109
|
+
dids: [dids.mix_sub_1, dids.mix_sub_2],
|
|
110
|
+
viewer: dids.mix_view,
|
|
111
|
+
})
|
|
112
|
+
expect(status).toBe(200)
|
|
113
|
+
expect(body.profiles).toHaveLength(2)
|
|
114
|
+
for (const profile of body.profiles) {
|
|
115
|
+
expect(profile.viewer?.knownFollowers).toBeUndefined()
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('returns no viewer state when viewer is omitted', async () => {
|
|
120
|
+
const { status, body } = await getProfiles({
|
|
121
|
+
dids: [dids.mix_sub_1],
|
|
122
|
+
socialProof: [dids.mix_sub_1],
|
|
123
|
+
})
|
|
124
|
+
expect(status).toBe(200)
|
|
125
|
+
expect(body.profiles).toHaveLength(1)
|
|
126
|
+
expect(body.profiles[0].viewer).toBeUndefined()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|