@atproto/bsky 0.0.112 → 0.0.113
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 +14 -0
- package/dist/api/app/bsky/actor/getSuggestions.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.js +4 -2
- package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
- package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getFeed.js +1 -0
- package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
- package/dist/api/blob-resolver.d.ts.map +1 -1
- package/dist/api/blob-resolver.js +2 -0
- package/dist/api/blob-resolver.js.map +1 -1
- package/dist/api/util.d.ts +1 -0
- package/dist/api/util.d.ts.map +1 -1
- package/dist/api/util.js +2 -1
- package/dist/api/util.js.map +1 -1
- package/dist/data-plane/server/routes/relationships.d.ts.map +1 -1
- package/dist/data-plane/server/routes/relationships.js +51 -31
- package/dist/data-plane/server/routes/relationships.js.map +1 -1
- package/dist/hydration/graph.d.ts +7 -4
- package/dist/hydration/graph.d.ts.map +1 -1
- package/dist/hydration/graph.js +17 -19
- package/dist/hydration/graph.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 +49 -17
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/views/index.d.ts +8 -3
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +39 -18
- package/dist/views/index.js.map +1 -1
- package/package.json +11 -11
- package/src/api/app/bsky/actor/getSuggestions.ts +4 -3
- package/src/api/app/bsky/feed/getAuthorFeed.ts +8 -2
- package/src/api/app/bsky/feed/getFeed.ts +2 -1
- package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +3 -2
- package/src/api/blob-resolver.ts +3 -0
- package/src/api/util.ts +1 -0
- package/src/data-plane/server/routes/relationships.ts +64 -40
- package/src/hydration/graph.ts +23 -23
- package/src/hydration/hydrator.ts +66 -19
- package/src/views/index.ts +57 -26
- package/tests/label-hydration.test.ts +5 -2
- package/tests/views/author-feed.test.ts +1 -1
- package/tests/views/labels-takedown.test.ts +29 -0
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
mergeNestedMaps,
|
|
44
44
|
mergeManyMaps,
|
|
45
45
|
} from './util'
|
|
46
|
-
import { uriToDid as didFromUri } from '../util/uris'
|
|
46
|
+
import { uriToDid as didFromUri, uriToDid } from '../util/uris'
|
|
47
47
|
import {
|
|
48
48
|
FeedGenAggs,
|
|
49
49
|
FeedGens,
|
|
@@ -279,10 +279,11 @@ export class Hydrator {
|
|
|
279
279
|
// - profile basic
|
|
280
280
|
async hydrateLists(uris: string[], ctx: HydrateCtx): Promise<HydrationState> {
|
|
281
281
|
const [listsState, profilesState] = await Promise.all([
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
this.hydrateListsBasic(uris, ctx, {
|
|
283
|
+
skipAuthors: true, // handled via author profile hydration
|
|
284
|
+
}),
|
|
285
|
+
this.hydrateProfilesBasic(uris.map(didFromUri), ctx),
|
|
284
286
|
])
|
|
285
|
-
|
|
286
287
|
return mergeStates(listsState, profilesState)
|
|
287
288
|
}
|
|
288
289
|
|
|
@@ -291,19 +292,26 @@ export class Hydrator {
|
|
|
291
292
|
async hydrateListsBasic(
|
|
292
293
|
uris: string[],
|
|
293
294
|
ctx: HydrateCtx,
|
|
295
|
+
opts?: { skipAuthors: boolean },
|
|
294
296
|
): Promise<HydrationState> {
|
|
295
|
-
const
|
|
297
|
+
const includeAuthorDids = opts?.skipAuthors ? [] : uris.map(uriToDid)
|
|
298
|
+
const [lists, listAggs, listViewers, labels, actors] = await Promise.all([
|
|
296
299
|
this.graph.getLists(uris, ctx.includeTakedowns),
|
|
297
300
|
this.graph.getListAggregates(uris.map((uri) => ({ uri }))),
|
|
298
301
|
ctx.viewer ? this.graph.getListViewerStates(uris, ctx.viewer) : undefined,
|
|
299
|
-
this.label.getLabelsForSubjects(
|
|
302
|
+
this.label.getLabelsForSubjects(
|
|
303
|
+
[...uris, ...includeAuthorDids],
|
|
304
|
+
ctx.labelers,
|
|
305
|
+
),
|
|
306
|
+
this.actor.getActors(includeAuthorDids, ctx.includeTakedowns),
|
|
300
307
|
])
|
|
301
308
|
|
|
302
309
|
if (!ctx.includeTakedowns) {
|
|
303
310
|
actionTakedownLabels(uris, lists, labels)
|
|
311
|
+
actionTakedownLabels(includeAuthorDids, actors, labels)
|
|
304
312
|
}
|
|
305
313
|
|
|
306
|
-
return { lists, listAggs, listViewers, labels, ctx }
|
|
314
|
+
return { lists, listAggs, listViewers, labels, actors, ctx }
|
|
307
315
|
}
|
|
308
316
|
|
|
309
317
|
// app.bsky.graph.defs#listItemView
|
|
@@ -533,12 +541,14 @@ export class Hydrator {
|
|
|
533
541
|
}
|
|
534
542
|
}
|
|
535
543
|
// replace embed/parent/root pairs with block state
|
|
536
|
-
const blocks = await this.
|
|
544
|
+
const blocks = await this.hydrateBidirectionalBlocks(
|
|
545
|
+
pairsToMap(relationships),
|
|
546
|
+
)
|
|
537
547
|
for (const [uri, { embed, parent, root }] of postBlocksPairs) {
|
|
538
548
|
postBlocks.set(uri, {
|
|
539
|
-
embed: !!embed &&
|
|
540
|
-
parent: !!parent &&
|
|
541
|
-
root: !!root &&
|
|
549
|
+
embed: !!embed && !!isBlocked(blocks, embed),
|
|
550
|
+
parent: !!parent && !!isBlocked(blocks, parent),
|
|
551
|
+
root: !!root && !!isBlocked(blocks, root),
|
|
542
552
|
})
|
|
543
553
|
}
|
|
544
554
|
return postBlocks
|
|
@@ -756,8 +766,8 @@ export class Hydrator {
|
|
|
756
766
|
)
|
|
757
767
|
},
|
|
758
768
|
)
|
|
759
|
-
const blocks = await this.
|
|
760
|
-
listCreatorMemberPairs,
|
|
769
|
+
const blocks = await this.hydrateBidirectionalBlocks(
|
|
770
|
+
pairsToMap(listCreatorMemberPairs),
|
|
761
771
|
)
|
|
762
772
|
// sample top list items per starter pack based on their follows
|
|
763
773
|
const listMemberAggs = await this.actor.getProfileAggregates(listMemberDids)
|
|
@@ -772,7 +782,8 @@ export class Hydrator {
|
|
|
772
782
|
// update aggregation with list items for top 12 most followed members
|
|
773
783
|
agg.listItemSampleUris = [
|
|
774
784
|
...members.listitems.filter(
|
|
775
|
-
(li) =>
|
|
785
|
+
(li) =>
|
|
786
|
+
ctx.viewer === creator || !isBlocked(blocks, [creator, li.did]),
|
|
776
787
|
),
|
|
777
788
|
]
|
|
778
789
|
.sort((li1, li2) => {
|
|
@@ -814,11 +825,11 @@ export class Hydrator {
|
|
|
814
825
|
pairs.push([authorDid, didFromUri(uri)])
|
|
815
826
|
}
|
|
816
827
|
}
|
|
817
|
-
const blocks = await this.
|
|
828
|
+
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs))
|
|
818
829
|
const likeBlocks = new HydrationMap<LikeBlock>()
|
|
819
830
|
for (const [uri, like] of likes) {
|
|
820
831
|
if (like) {
|
|
821
|
-
likeBlocks.set(uri,
|
|
832
|
+
likeBlocks.set(uri, isBlocked(blocks, [authorDid, didFromUri(uri)]))
|
|
822
833
|
} else {
|
|
823
834
|
likeBlocks.set(uri, null)
|
|
824
835
|
}
|
|
@@ -898,13 +909,13 @@ export class Hydrator {
|
|
|
898
909
|
pairs.push([didFromUri(uri), follow.record.subject])
|
|
899
910
|
}
|
|
900
911
|
}
|
|
901
|
-
const blocks = await this.
|
|
912
|
+
const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs))
|
|
902
913
|
const followBlocks = new HydrationMap<FollowBlock>()
|
|
903
914
|
for (const [uri, follow] of follows) {
|
|
904
915
|
if (follow) {
|
|
905
916
|
followBlocks.set(
|
|
906
917
|
uri,
|
|
907
|
-
|
|
918
|
+
isBlocked(blocks, [didFromUri(uri), follow.record.subject]),
|
|
908
919
|
)
|
|
909
920
|
} else {
|
|
910
921
|
followBlocks.set(uri, null)
|
|
@@ -926,10 +937,32 @@ export class Hydrator {
|
|
|
926
937
|
const result = new HydrationMap<HydrationMap<boolean>>()
|
|
927
938
|
const blocks = await this.graph.getBidirectionalBlocks(pairs)
|
|
928
939
|
|
|
940
|
+
// lookup list authors to apply takedown status to blocklists
|
|
941
|
+
const listAuthorDids = new Set<string>()
|
|
942
|
+
for (const [source, targets] of didMap) {
|
|
943
|
+
for (const target of targets) {
|
|
944
|
+
const block = blocks.get(source, target)
|
|
945
|
+
if (block?.blockListUri) {
|
|
946
|
+
listAuthorDids.add(uriToDid(block.blockListUri))
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const activeListAuthors = await this.actor.getActors(
|
|
952
|
+
[...listAuthorDids],
|
|
953
|
+
false,
|
|
954
|
+
)
|
|
955
|
+
|
|
929
956
|
for (const [source, targets] of didMap) {
|
|
930
957
|
const didBlocks = new HydrationMap<boolean>()
|
|
931
958
|
for (const target of targets) {
|
|
932
|
-
|
|
959
|
+
const block = blocks.get(source, target)
|
|
960
|
+
const isBlocked = !!(
|
|
961
|
+
block?.blockUri ||
|
|
962
|
+
(block?.blockListUri &&
|
|
963
|
+
activeListAuthors.get(uriToDid(block.blockListUri)))
|
|
964
|
+
)
|
|
965
|
+
didBlocks.set(target, isBlocked)
|
|
933
966
|
}
|
|
934
967
|
result.set(source, didBlocks)
|
|
935
968
|
}
|
|
@@ -1198,6 +1231,20 @@ const getListUrisFromThreadgates = (gates: Threadgates) => {
|
|
|
1198
1231
|
return uris
|
|
1199
1232
|
}
|
|
1200
1233
|
|
|
1234
|
+
const isBlocked = (blocks: BidirectionalBlocks, [a, b]: RelationshipPair) => {
|
|
1235
|
+
return blocks.get(a)?.get(b) ?? null
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const pairsToMap = (pairs: RelationshipPair[]): Map<string, string[]> => {
|
|
1239
|
+
const map = new Map<string, string[]>()
|
|
1240
|
+
for (const [a, b] of pairs) {
|
|
1241
|
+
const list = map.get(a) ?? []
|
|
1242
|
+
list.push(b)
|
|
1243
|
+
map.set(a, list)
|
|
1244
|
+
}
|
|
1245
|
+
return map
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1201
1248
|
export const mergeStates = (
|
|
1202
1249
|
stateA: HydrationState,
|
|
1203
1250
|
stateB: HydrationState,
|
package/src/views/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
ProfileViewDetailed,
|
|
8
8
|
ProfileView,
|
|
9
9
|
ProfileViewBasic,
|
|
10
|
-
ViewerState as
|
|
10
|
+
ViewerState as ProfileViewer,
|
|
11
11
|
} from '../lexicon/types/app/bsky/actor/defs'
|
|
12
12
|
import {
|
|
13
13
|
BlockedPost,
|
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
VideoUriBuilder,
|
|
36
36
|
parsePostgate,
|
|
37
37
|
} from './util'
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
uriToDid as creatorFromUri,
|
|
40
|
+
safePinnedPost,
|
|
41
|
+
uriToDid,
|
|
42
|
+
} from '../util/uris'
|
|
39
43
|
import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate'
|
|
40
44
|
import { isSelfLabels } from '../lexicon/types/com/atproto/label/defs'
|
|
41
45
|
import {
|
|
@@ -73,6 +77,7 @@ import {
|
|
|
73
77
|
} from '../lexicon/types/app/bsky/labeler/defs'
|
|
74
78
|
import { Notification } from '../proto/bsky_pb'
|
|
75
79
|
import { postUriToThreadgateUri, postUriToPostgateUri } from '../util/uris'
|
|
80
|
+
import { ProfileViewerState } from '../hydration/actor'
|
|
76
81
|
|
|
77
82
|
export class Views {
|
|
78
83
|
public imgUriBuilder: ImageUriBuilder = this.opts.imgUriBuilder
|
|
@@ -101,9 +106,10 @@ export class Views {
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
actorIsTakendown(did: string, state: HydrationState): boolean {
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
if (
|
|
109
|
+
const actor = state.actors?.get(did)
|
|
110
|
+
if (actor?.takedownRef) return true
|
|
111
|
+
if (actor?.upstreamStatus === 'takendown') return true
|
|
112
|
+
if (actor?.upstreamStatus === 'suspended') return true
|
|
107
113
|
if (state.labels?.get(did)?.isTakendown) return true
|
|
108
114
|
return false
|
|
109
115
|
}
|
|
@@ -111,18 +117,45 @@ export class Views {
|
|
|
111
117
|
viewerBlockExists(did: string, state: HydrationState): boolean {
|
|
112
118
|
const actor = state.profileViewers?.get(did)
|
|
113
119
|
if (!actor) return false
|
|
114
|
-
return (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
return !!(
|
|
121
|
+
actor.blockedBy ||
|
|
122
|
+
actor.blocking ||
|
|
123
|
+
this.blockedByList(actor, state) ||
|
|
124
|
+
this.blockingByList(actor, state)
|
|
119
125
|
)
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
viewerMuteExists(did: string, state: HydrationState): boolean {
|
|
123
129
|
const actor = state.profileViewers?.get(did)
|
|
124
130
|
if (!actor) return false
|
|
125
|
-
return actor.muted ||
|
|
131
|
+
return !!(actor.muted || this.mutedByList(actor, state))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
blockingByList(viewer: ProfileViewerState, state: HydrationState) {
|
|
135
|
+
return (
|
|
136
|
+
viewer.blockingByList && this.recordActive(viewer.blockingByList, state)
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
blockedByList(viewer: ProfileViewerState, state: HydrationState) {
|
|
141
|
+
return (
|
|
142
|
+
viewer.blockedByList && this.recordActive(viewer.blockedByList, state)
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
mutedByList(viewer: ProfileViewerState, state: HydrationState) {
|
|
147
|
+
return viewer.mutedByList && this.recordActive(viewer.mutedByList, state)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
recordActive(uri: string, state: HydrationState) {
|
|
151
|
+
const did = uriToDid(uri)
|
|
152
|
+
const actor = state.actors?.get(did)
|
|
153
|
+
if (!actor || this.actorIsTakendown(did, state)) {
|
|
154
|
+
// actor may not be present when takedowns are eagerly applied during hydration.
|
|
155
|
+
// so it's important to _try_ to hydrate the actor for records checked this way.
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
return uri
|
|
126
159
|
}
|
|
127
160
|
|
|
128
161
|
viewerSeesNeedsReview(did: string, state: HydrationState): boolean {
|
|
@@ -276,24 +309,22 @@ export class Views {
|
|
|
276
309
|
}
|
|
277
310
|
}
|
|
278
311
|
|
|
279
|
-
profileViewer(
|
|
280
|
-
did: string,
|
|
281
|
-
state: HydrationState,
|
|
282
|
-
): ProfileViewerState | undefined {
|
|
312
|
+
profileViewer(did: string, state: HydrationState): ProfileViewer | undefined {
|
|
283
313
|
const viewer = state.profileViewers?.get(did)
|
|
284
314
|
if (!viewer) return
|
|
285
|
-
const
|
|
286
|
-
const
|
|
315
|
+
const blockedByList = this.blockedByList(viewer, state)
|
|
316
|
+
const blockedByUri = viewer.blockedBy || blockedByList
|
|
317
|
+
const blockingByList = this.blockingByList(viewer, state)
|
|
318
|
+
const blockingUri = viewer.blocking || blockingByList
|
|
287
319
|
const block = !!blockedByUri || !!blockingUri
|
|
320
|
+
const mutedByList = this.mutedByList(viewer, state)
|
|
288
321
|
return {
|
|
289
|
-
muted: viewer.muted ||
|
|
290
|
-
mutedByList:
|
|
291
|
-
? this.listBasic(viewer.mutedByList, state)
|
|
292
|
-
: undefined,
|
|
322
|
+
muted: !!(viewer.muted || mutedByList),
|
|
323
|
+
mutedByList: mutedByList ? this.listBasic(mutedByList, state) : undefined,
|
|
293
324
|
blockedBy: !!blockedByUri,
|
|
294
325
|
blocking: blockingUri,
|
|
295
|
-
blockingByList:
|
|
296
|
-
? this.listBasic(
|
|
326
|
+
blockingByList: blockingByList
|
|
327
|
+
? this.listBasic(blockingByList, state)
|
|
297
328
|
: undefined,
|
|
298
329
|
following: viewer.following && !block ? viewer.following : undefined,
|
|
299
330
|
followedBy: viewer.followedBy && !block ? viewer.followedBy : undefined,
|
|
@@ -323,11 +354,11 @@ export class Views {
|
|
|
323
354
|
blockedProfileViewer(
|
|
324
355
|
did: string,
|
|
325
356
|
state: HydrationState,
|
|
326
|
-
):
|
|
357
|
+
): ProfileViewer | undefined {
|
|
327
358
|
const viewer = state.profileViewers?.get(did)
|
|
328
359
|
if (!viewer) return
|
|
329
|
-
const blockedByUri = viewer.blockedBy ||
|
|
330
|
-
const blockingUri = viewer.blocking ||
|
|
360
|
+
const blockedByUri = viewer.blockedBy || this.blockedByList(viewer, state)
|
|
361
|
+
const blockingUri = viewer.blocking || this.blockingByList(viewer, state)
|
|
331
362
|
return {
|
|
332
363
|
blockedBy: !!blockedByUri,
|
|
333
364
|
blocking: blockingUri,
|
|
@@ -73,8 +73,11 @@ describe('label hydration', () => {
|
|
|
73
73
|
expect(res.data.labels?.find((l) => l.src === labelerDid)?.val).toEqual(
|
|
74
74
|
'misleading',
|
|
75
75
|
)
|
|
76
|
-
const labelerHeaderDids = res.headers['atproto-content-labelers']
|
|
77
|
-
|
|
76
|
+
const labelerHeaderDids = res.headers['atproto-content-labelers']
|
|
77
|
+
?.split(',')
|
|
78
|
+
.sort()
|
|
79
|
+
|
|
80
|
+
expect(labelerHeaderDids).toEqual(
|
|
78
81
|
[alice, `${bob};redact`, labelerDid].sort(),
|
|
79
82
|
)
|
|
80
83
|
})
|
|
@@ -487,7 +487,7 @@ describe('pds author feed views', () => {
|
|
|
487
487
|
await sc.post(alice, 'not pinned post')
|
|
488
488
|
const post = await createAndPinPost()
|
|
489
489
|
await sc.post(alice, 'not pinned post')
|
|
490
|
-
|
|
490
|
+
await network.processAll()
|
|
491
491
|
const { data } = await agent.api.app.bsky.feed.getAuthorFeed(
|
|
492
492
|
{ actor: sc.accounts[alice].handle, includePins: true },
|
|
493
493
|
{
|
|
@@ -37,6 +37,16 @@ describe('bsky takedown labels', () => {
|
|
|
37
37
|
sc.getHeaders(sc.dids.carol),
|
|
38
38
|
)
|
|
39
39
|
carolListRef = await sc.createList(sc.dids.carol, 'carol list', 'mod')
|
|
40
|
+
// alice blocks dan via carol's list, and carol is takendown
|
|
41
|
+
await sc.addToList(sc.dids.carol, sc.dids.dan, carolListRef)
|
|
42
|
+
await pdsAgent.app.bsky.graph.listblock.create(
|
|
43
|
+
{ repo: sc.dids.alice },
|
|
44
|
+
{
|
|
45
|
+
subject: carolListRef.uriStr,
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
},
|
|
48
|
+
sc.getHeaders(sc.dids.alice),
|
|
49
|
+
)
|
|
40
50
|
aliceGenRef = await sc.createFeedGen(
|
|
41
51
|
sc.dids.alice,
|
|
42
52
|
'did:web:example.com',
|
|
@@ -190,6 +200,25 @@ describe('bsky takedown labels', () => {
|
|
|
190
200
|
expect(profile.viewer?.blockingByList).toBeUndefined()
|
|
191
201
|
})
|
|
192
202
|
|
|
203
|
+
it('author takedown halts application of mod lists', async () => {
|
|
204
|
+
const { data: profile } = await agent.app.bsky.actor.getProfile(
|
|
205
|
+
{
|
|
206
|
+
actor: sc.dids.dan, // blocked via carol's list, and carol is takendown
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
headers: await network.serviceHeaders(
|
|
210
|
+
sc.dids.alice,
|
|
211
|
+
ids.AppBskyActorGetProfile,
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
expect(profile.did).toBe(sc.dids.dan)
|
|
216
|
+
expect(profile.viewer).not.toBeUndefined()
|
|
217
|
+
expect(profile.viewer?.blockedBy).toBe(false)
|
|
218
|
+
expect(profile.viewer?.blocking).toBeUndefined()
|
|
219
|
+
expect(profile.viewer?.blockingByList).toBeUndefined()
|
|
220
|
+
})
|
|
221
|
+
|
|
193
222
|
it('takesdown feed generators', async () => {
|
|
194
223
|
const res = await agent.api.app.bsky.feed.getFeedGenerators({
|
|
195
224
|
feeds: [aliceGenRef.uriStr, bobGenRef.uriStr, carolGenRef.uriStr],
|