···66import * as TID from '@atcute/tid';
7788/**
99+ * Resolve a batch of DIDs to their minimal profile view (handle + display
1010+ * name + avatar). Uses the public appview so it works for signed-out
1111+ * viewers too. Returns a map keyed by DID; missing or failed lookups are
1212+ * simply absent from the result.
1313+ */
1414+export const resolveProfiles = command(
1515+ v.object({
1616+ dids: v.array(v.string())
1717+ }),
1818+ async (input) => {
1919+ const out: Record<
2020+ string,
2121+ { handle: string; displayName: string | null; avatar: string | null }
2222+ > = {};
2323+ if (input.dids.length === 0) return { profiles: out };
2424+2525+ // Dedupe before hitting the network — a feed with N posts often has
2626+ // far fewer distinct submitters than N. Uses the public appview,
2727+ // unauthenticated, so there are no scope concerns.
2828+ const unique = Array.from(new Set(input.dids));
2929+ const publicClient = new Client({
3030+ handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' })
3131+ });
3232+3333+ for (let i = 0; i < unique.length; i += 25) {
3434+ const batch = unique.slice(i, i + 25);
3535+ try {
3636+ const res = await publicClient.get('app.bsky.actor.getProfiles', {
3737+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3838+ params: { actors: batch as any }
3939+ });
4040+ if (res.ok) {
4141+ for (const p of res.data.profiles) {
4242+ out[p.did] = {
4343+ handle: p.handle,
4444+ displayName: p.displayName ?? null,
4545+ avatar: p.avatar ?? null
4646+ };
4747+ }
4848+ }
4949+ } catch (e) {
5050+ console.error('[resolveProfiles] batch failed', e);
5151+ }
5252+ }
5353+5454+ return { profiles: out };
5555+ }
5656+);
5757+5858+/**
959 * Fetch viewer-specific state for a batch of post URIs. Returns a map
1060 * `{ [uri]: { likeUri: string | null } }` so the caller can render
1161 * per-post "liked by me" UI. Requires an authenticated viewer — returns