import assert from 'node:assert' import {type AppBskyGraphDefs, AtUri} from '@atproto/api' import resvg from '@resvg/resvg-js' import {type Express} from 'express' import satori from 'satori' import { StarterPack, STARTERPACK_HEIGHT, STARTERPACK_WIDTH, } from '../components/StarterPack.js' import {type AppContext} from '../context.js' import {httpLogger} from '../logger.js' import {loadEmojiAsSvg} from '../util.js' import { getImage, handler, hideAvatarLabels, originVerifyMiddleware, } from './util.js' import * as bsky from '../types/bsky/index.js' export default function (ctx: AppContext, app: Express) { return app.get( '/start/:actor/:rkey', originVerifyMiddleware(ctx), handler(async (req, res) => { const {actor, rkey} = req.params const uri = AtUri.make(actor, 'app.bsky.graph.starterpack', rkey) let starterPack: AppBskyGraphDefs.StarterPackView try { const result = await ctx.appviewAgent.app.bsky.graph.getStarterPack({ starterPack: uri.toString(), }) starterPack = result.data.starterPack } catch (err) { httpLogger.warn( {err, uri: uri.toString()}, 'could not fetch starter pack', ) return res.status(404).end('not found') } const imageEntries = await Promise.all( ([starterPack.creator] as Array) .concat((starterPack.listItemsSample ?? []).map(li => li.subject)) // has avatar .filter(p => p.avatar) // no sensitive labels .filter(p => !p.labels?.some(l => hideAvatarLabels.has(l.val))) .map(async p => { try { assert(p.avatar) const image = await getImage(p.avatar) return [p.did, image] as const } catch (err) { httpLogger.warn( {err, uri: uri.toString(), did: p.did}, 'could not fetch image', ) return [p.did, null] as const } }), ) const images = new Map( imageEntries .filter( (entry): entry is readonly [string, Buffer] => entry[1] !== null, ) .slice(0, 7), ) const svg = await satori( , { fonts: ctx.fonts, height: STARTERPACK_HEIGHT, width: STARTERPACK_WIDTH, loadAdditionalAsset: async (code, text) => { if (code === 'emoji') { return (await loadEmojiAsSvg(text)) ?? '' } return '' }, }, ) const output = await resvg.renderAsync(svg) res.statusCode = 200 res.setHeader('content-type', 'image/png') res.setHeader('cdn-tag', [...images.keys()].join(',')) return res.end(output.asPng()) }), ) }