import { type $Typed, AppBskyActorDefs, AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyEmbedVideo, AppBskyFeedPost, AtUri, } from "@atproto/api"; import { useAtom } from "jotai"; import { useMemo } from "react"; import { imgCDNAtom, videoCDNAtom } from "./atoms"; import { useQueryIdentity, useQueryPost, useQueryProfile } from "./useQuery"; type QueryResultData any> = ReturnType extends { data: infer D } | undefined ? D : never; function asTyped(obj: T): $Typed { return obj as $Typed; } export function hydrateEmbedImages( embed: AppBskyEmbedImages.Main, did: string, cdn: string ): $Typed { return asTyped({ $type: "app.bsky.embed.images#view" as const, images: embed.images .map((img) => { const link = img.image.ref?.["$link"]; if (!link) return null; return { thumb: `https://${cdn}/img/feed_thumbnail/plain/${did}/${link}@jpeg`, fullsize: `https://${cdn}/img/feed_fullsize/plain/${did}/${link}@jpeg`, alt: img.alt || "", aspectRatio: img.aspectRatio, }; }) .filter(Boolean) as AppBskyEmbedImages.ViewImage[], }); } export function hydrateEmbedExternal( embed: AppBskyEmbedExternal.Main, did: string, cdn: string ): $Typed { return asTyped({ $type: "app.bsky.embed.external#view" as const, external: { uri: embed.external.uri, title: embed.external.title, description: embed.external.description, thumb: embed.external.thumb?.ref?.$link ? `https://${cdn}/img/feed_thumbnail/plain/${did}/${embed.external.thumb.ref.$link}@jpeg` : undefined, }, }); } export function hydrateEmbedVideo( embed: AppBskyEmbedVideo.Main, did: string, videocdn: string ): $Typed { const videoLink = embed.video.ref.$link; return asTyped({ $type: "app.bsky.embed.video#view" as const, playlist: `https://${videocdn}/watch/${did}/${videoLink}/playlist.m3u8`, thumbnail: `https://${videocdn}/watch/${did}/${videoLink}/thumbnail.jpg`, aspectRatio: embed.aspectRatio, cid: videoLink, }); } function hydrateEmbedRecord( embed: AppBskyEmbedRecord.Main, quotedPost: QueryResultData, quotedProfile: QueryResultData, quotedIdentity: QueryResultData, cdn: string ): $Typed | undefined { if (!quotedPost || !quotedProfile || !quotedIdentity) { return undefined; } const author: $Typed = asTyped({ $type: "app.bsky.actor.defs#profileViewBasic" as const, did: quotedIdentity.did, handle: quotedIdentity.handle, displayName: quotedProfile.value.displayName ?? quotedIdentity.handle, avatar: quotedProfile.value.avatar?.ref?.$link ? `https://${cdn}/img/avatar/plain/${quotedIdentity.did}/${quotedProfile.value.avatar.ref.$link}@jpeg` : undefined, viewer: {}, labels: [], }); const viewRecord: $Typed = asTyped({ $type: "app.bsky.embed.record#viewRecord" as const, uri: quotedPost.uri, cid: quotedPost.cid, author, value: quotedPost.value, indexedAt: quotedPost.value.createdAt, embeds: quotedPost.value.embed ? [quotedPost.value.embed] : undefined, }); return asTyped({ $type: "app.bsky.embed.record#view" as const, record: viewRecord, }); } function hydrateEmbedRecordWithMedia( embed: AppBskyEmbedRecordWithMedia.Main, mediaHydratedEmbed: | $Typed | $Typed | $Typed, quotedPost: QueryResultData, quotedProfile: QueryResultData, quotedIdentity: QueryResultData, cdn: string ): $Typed | undefined { const hydratedRecord = hydrateEmbedRecord( embed.record, quotedPost, quotedProfile, quotedIdentity, cdn ); if (!hydratedRecord) return undefined; return asTyped({ $type: "app.bsky.embed.recordWithMedia#view" as const, record: hydratedRecord, media: mediaHydratedEmbed, }); } type HydratedEmbedView = | $Typed | $Typed | $Typed | $Typed | $Typed; export function useHydratedEmbed( embed: AppBskyFeedPost.Record["embed"], postAuthorDid: string | undefined ) { const recordInfo = useMemo(() => { if (AppBskyEmbedRecordWithMedia.isMain(embed)) { const recordUri = embed.record.record.uri; const quotedAuthorDid = new AtUri(recordUri).hostname; return { recordUri, quotedAuthorDid, isRecordType: true }; } else if (AppBskyEmbedRecord.isMain(embed)) { const recordUri = embed.record.uri; const quotedAuthorDid = new AtUri(recordUri).hostname; return { recordUri, quotedAuthorDid, isRecordType: true }; } return { recordUri: undefined, quotedAuthorDid: undefined, isRecordType: false, }; }, [embed]); const { isRecordType, recordUri, quotedAuthorDid } = recordInfo; const usequerypostresults = useQueryPost(recordUri); const profileUri = quotedAuthorDid ? `at://${quotedAuthorDid}/app.bsky.actor.profile/self` : undefined; const { data: quotedProfile, isLoading: isLoadingProfile, error: profileError, } = useQueryProfile(profileUri); const [imgcdn] = useAtom(imgCDNAtom); const [videocdn] = useAtom(videoCDNAtom); const queryidentityresult = useQueryIdentity(quotedAuthorDid); const hydratedEmbed: HydratedEmbedView | undefined = (() => { if (!embed || !postAuthorDid) return undefined; if ( isRecordType && (!usequerypostresults?.data || !quotedProfile || !queryidentityresult?.data) ) { return undefined; } try { if (AppBskyEmbedImages.isMain(embed)) { return hydrateEmbedImages(embed, postAuthorDid, imgcdn); } else if (AppBskyEmbedExternal.isMain(embed)) { return hydrateEmbedExternal(embed, postAuthorDid, imgcdn); } else if (AppBskyEmbedVideo.isMain(embed)) { return hydrateEmbedVideo(embed, postAuthorDid, videocdn); } else if (AppBskyEmbedRecord.isMain(embed)) { return hydrateEmbedRecord( embed, usequerypostresults?.data, quotedProfile, queryidentityresult?.data, imgcdn ); } else if (AppBskyEmbedRecordWithMedia.isMain(embed)) { let hydratedMedia: | $Typed | $Typed | $Typed | undefined; if (AppBskyEmbedImages.isMain(embed.media)) { hydratedMedia = hydrateEmbedImages( embed.media, postAuthorDid, imgcdn ); } else if (AppBskyEmbedExternal.isMain(embed.media)) { hydratedMedia = hydrateEmbedExternal( embed.media, postAuthorDid, imgcdn ); } else if (AppBskyEmbedVideo.isMain(embed.media)) { hydratedMedia = hydrateEmbedVideo( embed.media, postAuthorDid, videocdn ); } if (hydratedMedia) { return hydrateEmbedRecordWithMedia( embed, hydratedMedia, usequerypostresults?.data, quotedProfile, queryidentityresult?.data, imgcdn ); } } } catch (e) { console.error("Error hydrating embed", e); return undefined; } })(); const isLoading = isRecordType ? usequerypostresults?.isLoading || isLoadingProfile || queryidentityresult?.isLoading : false; const error = usequerypostresults?.error || profileError || queryidentityresult?.error; return { data: hydratedEmbed, isLoading, error }; }