MCPcopy
hub / github.com/npmx-dev/npmx.dev / fetchBlueskyAvatars

Function fetchBlueskyAvatars

modules/blog.ts:26–72  ·  view source on GitHub ↗

* Fetches Bluesky avatars for a set of authors at build time. * Returns a map of handle → avatar URL.

(
  imagesDir: string,
  handles: string[],
)

Source from the content-addressed store, hash-verified

24 * Returns a map of handle → avatar URL.
25 */
26async function fetchBlueskyAvatars(
27 imagesDir: string,
28 handles: string[],
29): Promise<Map<string, string>> {
30 const avatarMap = new Map<string, string>()
31 if (handles.length === 0) return avatarMap
32
33 try {
34 const params = new URLSearchParams()
35 for (const handle of handles) {
36 params.append('actors', handle)
37 }
38
39 const response = await fetch(
40 `${BLUESKY_API}/xrpc/app.bsky.actor.getProfiles?${params.toString()}`,
41 )
42
43 if (!response.ok) {
44 console.warn(`[blog] Failed to fetch Bluesky profiles: ${response.status}`)
45 return avatarMap
46 }
47
48 const data = (await response.json()) as { profiles: Array<{ handle: string; avatar?: string }> }
49
50 for (const profile of data.profiles) {
51 if (profile.avatar) {
52 const hash = crypto.createHash('sha256').update(profile.avatar).digest('hex')
53 const dest = join(imagesDir, `${hash}.png`)
54
55 if (!existsSync(dest)) {
56 const res = await fetch(`${profile.avatar}@png`)
57 if (!res.ok || !res.body) {
58 console.warn(`[blog] Failed to fetch Bluesky avatar: ${profile.avatar}@png`)
59 continue
60 }
61 await writeFile(join(imagesDir, `${hash}.png`), res.body)
62 }
63
64 avatarMap.set(profile.handle, `/blog/avatar/${hash}.png`)
65 }
66 }
67 } catch (error) {
68 console.warn(`[blog] Failed to fetch Bluesky avatars:`, error)
69 }
70
71 return avatarMap
72}
73
74/**
75 * Resolves authors with their Bluesky avatars and profile URLs.

Callers 1

loadBlogPostsFunction · 0.85

Calls 1

setMethod · 0.65

Tested by

no test coverage detected