diff --git a/packages/bsky/src/hydration/actor.ts b/packages/bsky/src/hydration/actor.ts index 0c501f87f66..8191f25786d 100644 --- a/packages/bsky/src/hydration/actor.ts +++ b/packages/bsky/src/hydration/actor.ts @@ -1,6 +1,6 @@ import { DataPlaneClient } from '../data-plane/client' import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile' -import { HydrationMap } from './util' +import { HydrationMap, parseRecord } from './util' export type Profile = { did: string @@ -13,8 +13,9 @@ export type Profiles = HydrationMap export type ProfileViewerState = { muted?: boolean mutedByList?: string - blockedBy?: boolean + blockedBy?: string blocking?: string + blockedByList?: string blockingByList?: string following?: string followedBy?: string @@ -34,17 +35,63 @@ export class ActorHydrator { constructor(public dataplane: DataPlaneClient) {} async getProfiles(dids: string[]): Promise { - throw new Error('unimplemented') + const [handles, profiles] = await Promise.all([ + this.dataplane.getHandles({ dids }), + this.dataplane.getProfiles({ dids }), + ]) + return dids.reduce((acc, did, i) => { + const handle = handles[i] ?? null + const record = parseRecord(profiles.records[i]) + return acc.set(did, { + did, + handle, + record, + }) + }, new HydrationMap()) } async getProfileViewerStates( dids: string[], viewer: string, ): Promise { - throw new Error('unimplemented') + const res = await this.dataplane.getRelationships({ + actorDid: viewer, + targetDids: dids, + }) + return dids.reduce((acc, did, i) => { + const rels = res.relationships[i] + return acc.set(did, { + muted: rels.muted, + mutedByList: rels.mutedByList.length > 0 ? rels.mutedByList : undefined, + blockedBy: rels.blockedBy.length > 0 ? rels.blockedBy : undefined, + blocking: rels.blocking.length > 0 ? rels.blocking : undefined, + blockedByList: + rels.blockedByList.length > 0 ? rels.blockedByList : undefined, + blockingByList: + rels.blockingByList.length > 0 ? rels.blockingByList : undefined, + following: rels.following.length > 0 ? rels.following : undefined, + followedBy: rels.followedBy.length > 0 ? rels.followedBy : undefined, + }) + }, new HydrationMap()) } async getProfileAggregates(dids: string[]): Promise { - throw new Error('unimplemented') + const aggs = await Promise.all(dids.map((did) => this.getAggsForDid(did))) + return dids.reduce((acc, did, i) => { + return acc.set(did, aggs[i]) + }, new HydrationMap()) + } + + private async getAggsForDid(actorDid: string) { + const [followers, follows, posts] = await Promise.all([ + this.dataplane.getFollowersCount({ actorDid }), + this.dataplane.getFollowsCount({ actorDid }), + { count: 0 }, // @TODO need getPostsCount function + ]) + return { + followers: followers.count, + follows: follows.count, + posts: posts.count, + } } } diff --git a/packages/bsky/src/hydration/graph.ts b/packages/bsky/src/hydration/graph.ts index 40eb4b1cd1d..7f38a5b3cf9 100644 --- a/packages/bsky/src/hydration/graph.ts +++ b/packages/bsky/src/hydration/graph.ts @@ -1,8 +1,6 @@ -import * as ui8 from 'uint8arrays' import { Record as ListRecord } from '../lexicon/types/app/bsky/graph/list' import { DataPlaneClient } from '../data-plane/client' -import { jsonToLex } from '@atproto/lexicon' -import { HydrationMap } from './util' +import { HydrationMap, parseRecord } from './util' export type List = ListRecord export type Lists = HydrationMap @@ -33,17 +31,15 @@ export class Blocks { export class GraphHydrator { constructor(public dataplane: DataPlaneClient) {} - async getListRecords(uris: string[]): Promise { + async getLists(uris: string[]): Promise { const res = await this.dataplane.getLists({ uris }) return uris.reduce((acc, uri, i) => { - const list = res.records[i] - const parsed = JSON.parse(ui8.toString(list, 'utf8')) - const record = parsed ? (jsonToLex(parsed) as ListRecord) : null + const record = parseRecord(res.records[i]) return acc.set(uri, record) - }, new Map() as Lists) + }, new HydrationMap() as Lists) } - async getListsViewerState( + async getListViewerStates( uris: string[], viewer: string, ): Promise { @@ -60,11 +56,7 @@ export class GraphHydrator { viewerListBlockUri: mutesAndBlocks[i].listBlockUri, viewerInList: listMemberships.listitemUris[i], }) - }, new Map() as ListViewerStates) - } - - async getBidirectionalBlocks(pairs: RelationshipPair[]): Promise { - throw new Error('unimplemented') + }, new HydrationMap() as ListViewerStates) } private async getMutesAndBlocks(uri: string, viewer: string) { @@ -83,4 +75,8 @@ export class GraphHydrator { listBlockUri: listBlockUri.listblockUri, } } + + async getBidirectionalBlocks(pairs: RelationshipPair[]): Promise { + throw new Error('unimplemented') + } } diff --git a/packages/bsky/src/hydration/util.ts b/packages/bsky/src/hydration/util.ts index 5d47b538108..5e64eab03c9 100644 --- a/packages/bsky/src/hydration/util.ts +++ b/packages/bsky/src/hydration/util.ts @@ -1,5 +1,17 @@ +import { jsonToLex } from '@atproto/lexicon' +import * as ui8 from 'uint8arrays' + export class HydrationMap extends Map { merge(map: HydrationMap): HydrationMap { - throw new Error('unimplemented') + map.forEach((val, key) => { + this.set(key, val) + }) + return this } } + +export const parseRecord = (bytes: Uint8Array): T | null => { + if (bytes.byteLength === 0) return null + const parsed = JSON.parse(ui8.toString(bytes, 'utf8')) + return parsed ? (jsonToLex(parsed) as T) : null +}