@@ -41,7 +41,25 @@ interface RecordData {
4141 color : string ;
4242}
4343
44- async function fetchAvatarDataUri ( url : string ) : Promise < string > {
44+ async function resolveAvatarUrl ( did : string ) : Promise < string > {
45+ if ( ! did ) return "" ;
46+ try {
47+ const res = await fetch (
48+ `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${ encodeURIComponent ( did ) } ` ,
49+ ) ;
50+ if ( ! res . ok ) return "" ;
51+ const profile = await res . json ( ) ;
52+ const avatar = profile . avatar || "" ;
53+ if ( ! avatar ) return "" ;
54+ return avatar . replace ( / @ [ a - z ] + $ / , "@jpeg" ) +
55+ ( / @ [ a - z ] + $ / . test ( avatar ) ? "" : "@jpeg" ) ;
56+ } catch {
57+ return "" ;
58+ }
59+ }
60+
61+ async function fetchAvatarDataUri ( did : string ) : Promise < string > {
62+ const url = await resolveAvatarUrl ( did ) ;
4563 if ( ! url ) return "" ;
4664 try {
4765 const res = await fetch ( url , { headers : { "User-Agent" : "margin.at/og" } } ) ;
@@ -67,7 +85,7 @@ async function fetchRecordData(uri: string): Promise<RecordData | null> {
6785 const did = author . did || "" ;
6886 const authorName = handle ? `@${ handle } ` : did || "someone" ;
6987 const displayName = author . displayName || handle || did || "someone" ;
70- const avatarURL = await fetchAvatarDataUri ( author . avatar || "" ) ;
88+ const avatarURL = await fetchAvatarDataUri ( author . did || "" ) ;
7189 const targetSource = item . target ?. source || item . url || item . source || "" ;
7290 const domain = targetSource
7391 ? ( ( ) => {
@@ -149,7 +167,7 @@ async function fetchRecordData(uri: string): Promise<RecordData | null> {
149167 const did = author . did || "" ;
150168 const authorName = handle ? `@${ handle } ` : did || "someone" ;
151169 const displayName = author . displayName || handle || did || "someone" ;
152- const avatarURL = await fetchAvatarDataUri ( author . avatar || "" ) ;
170+ const avatarURL = await fetchAvatarDataUri ( author . did || "" ) ;
153171
154172 return {
155173 type : "collection" ,
@@ -810,64 +828,72 @@ export const GET: APIRoute = async ({ url }) => {
810828 return new Response ( "uri parameter required" , { status : 400 } ) ;
811829 }
812830
813- const data = await fetchRecordData ( uri ) ;
814- if ( ! data ) {
815- return new Response ( "Record not found" , { status : 404 } ) ;
816- }
831+ try {
832+ const data = await fetchRecordData ( uri ) ;
833+ if ( ! data ) {
834+ return new Response ( "Record not found" , { status : 404 } ) ;
835+ }
817836
818- const fonts = loadFonts ( ) ;
819-
820- let element : unknown ;
821- switch ( data . type ) {
822- case "collection" :
823- element = buildCollectionImage ( data ) ;
824- break ;
825- case "bookmark" :
826- element = buildBookmarkImage ( data ) ;
827- break ;
828- case "highlight" :
829- element = buildHighlightImage ( data ) ;
830- break ;
831- case "annotation" :
832- default :
833- element = buildAnnotationImage ( data ) ;
834- break ;
835- }
837+ const fonts = loadFonts ( ) ;
838+
839+ let element : unknown ;
840+ switch ( data . type ) {
841+ case "collection" :
842+ element = buildCollectionImage ( data ) ;
843+ break ;
844+ case "bookmark" :
845+ element = buildBookmarkImage ( data ) ;
846+ break ;
847+ case "highlight" :
848+ element = buildHighlightImage ( data ) ;
849+ break ;
850+ case "annotation" :
851+ default :
852+ element = buildAnnotationImage ( data ) ;
853+ break ;
854+ }
836855
837- const svg = await satori ( element as React . ReactNode , {
838- width : 1200 ,
839- height : 630 ,
840- fonts : [
841- { name : "Inter" , data : fonts . regular , weight : 400 , style : "normal" } ,
842- { name : "Inter" , data : fonts . bold , weight : 700 , style : "normal" } ,
843- ] ,
844- loadAdditionalAsset : async ( code : string , segment : string ) => {
845- if ( code === "emoji" ) {
846- const codepoints = [ ...segment ]
847- . map ( ( c ) => c . codePointAt ( 0 ) ! . toString ( 16 ) )
848- . join ( "-" ) ;
849- const emojiUrl = `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/${ codepoints } .svg` ;
850- try {
851- const res = await fetch ( emojiUrl ) ;
852- if ( res . ok )
853- return `data:image/svg+xml,${ encodeURIComponent ( await res . text ( ) ) } ` ;
854- } catch {
855- // ignore
856+ const svg = await satori ( element as React . ReactNode , {
857+ width : 1200 ,
858+ height : 630 ,
859+ fonts : [
860+ { name : "Inter" , data : fonts . regular , weight : 400 , style : "normal" } ,
861+ { name : "Inter" , data : fonts . bold , weight : 700 , style : "normal" } ,
862+ ] ,
863+ loadAdditionalAsset : async ( code : string , segment : string ) => {
864+ if ( code === "emoji" ) {
865+ const codepoints = [ ...segment ]
866+ . map ( ( c ) => c . codePointAt ( 0 ) ! . toString ( 16 ) )
867+ . join ( "-" ) ;
868+ const emojiUrl = `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/${ codepoints } .svg` ;
869+ try {
870+ const res = await fetch ( emojiUrl ) ;
871+ if ( res . ok )
872+ return `data:image/svg+xml,${ encodeURIComponent ( await res . text ( ) ) } ` ;
873+ } catch {
874+ // ignore
875+ }
856876 }
857- }
858- return "" ;
859- } ,
860- } ) ;
877+ return "" ;
878+ } ,
879+ } ) ;
861880
862- const resvg = new Resvg ( svg , {
863- fitTo : { mode : "width" , value : 1200 } ,
864- } ) ;
865- const png = resvg . render ( ) . asPng ( ) ;
881+ const resvg = new Resvg ( svg , {
882+ fitTo : { mode : "width" , value : 1200 } ,
883+ } ) ;
884+ const png = resvg . render ( ) . asPng ( ) ;
866885
867- return new Response ( new Uint8Array ( png ) , {
868- headers : {
869- "Content-Type" : "image/png" ,
870- "Cache-Control" : "public, max-age=86400" ,
871- } ,
872- } ) ;
886+ return new Response ( new Uint8Array ( png ) , {
887+ headers : {
888+ "Content-Type" : "image/png" ,
889+ "Cache-Control" : "public, max-age=86400" ,
890+ } ,
891+ } ) ;
892+ } catch ( e ) {
893+ console . error ( "[og-image] Error generating image:" , e ) ;
894+ console . error ( "[og-image] cwd:" , process . cwd ( ) ) ;
895+ console . error ( "[og-image] publicDir:" , getPublicDir ( ) ) ;
896+ const BASE_URL = process . env . BASE_URL || "https://margin.at" ;
897+ return Response . redirect ( `${ BASE_URL } /og.png` , 302 ) ;
898+ }
873899} ;
0 commit comments