···11package views
2233import (
44+ "fmt"
55+46 "github.com/bluesky-social/indigo/api/bsky"
57 "github.com/bluesky-social/indigo/lex/util"
68 "github.com/whyrusleeping/konbini/hydration"
···4042 }
4143 }
42444343- // TODO: Add embed handling - need to convert embed types to proper views
4444- // if post.Post.Embed != nil {
4545- // view.Embed = formatEmbed(post.Post.Embed)
4646- // }
4545+ // Add embed handling
4646+ if post.Post.Embed != nil {
4747+ view.Embed = formatEmbed(post.Post.Embed, post.Author)
4848+ }
47494850 return view
4951}
···5658}
57595860// ThreadViewPost builds a thread view post (app.bsky.feed.defs#threadViewPost)
5959-func ThreadViewPost(post *hydration.PostInfo, author *hydration.ActorInfo, parent, replies interface{}) *bsky.FeedDefs_ThreadViewPost {
6161+func ThreadViewPost(post *hydration.PostInfo, author *hydration.ActorInfo, parent, replies any) *bsky.FeedDefs_ThreadViewPost {
6062 view := &bsky.FeedDefs_ThreadViewPost{
6163 LexiconTypeID: "app.bsky.feed.defs#threadViewPost",
6264 Post: PostView(post, author),
···67696870 return view
6971}
7272+7373+func formatEmbed(embed *bsky.FeedPost_Embed, authorDID string) *bsky.FeedDefs_PostView_Embed {
7474+ if embed == nil {
7575+ return nil
7676+ }
7777+7878+ result := &bsky.FeedDefs_PostView_Embed{}
7979+8080+ // Handle images
8181+ if embed.EmbedImages != nil {
8282+ viewImages := make([]*bsky.EmbedImages_ViewImage, len(embed.EmbedImages.Images))
8383+ for i, img := range embed.EmbedImages.Images {
8484+ // Convert blob to CDN URLs
8585+ fullsize := ""
8686+ thumb := ""
8787+ if img.Image != nil {
8888+ // CDN URL format for feed images
8989+ cid := img.Image.Ref.String()
9090+ fullsize = fmt.Sprintf("https://cdn.bsky.app/img/feed_fullsize/plain/%s/%s@jpeg", authorDID, cid)
9191+ thumb = fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
9292+ }
9393+9494+ viewImages[i] = &bsky.EmbedImages_ViewImage{
9595+ Alt: img.Alt,
9696+ AspectRatio: img.AspectRatio,
9797+ Fullsize: fullsize,
9898+ Thumb: thumb,
9999+ }
100100+ }
101101+ result.EmbedImages_View = &bsky.EmbedImages_View{
102102+ LexiconTypeID: "app.bsky.embed.images#view",
103103+ Images: viewImages,
104104+ }
105105+ return result
106106+ }
107107+108108+ // Handle external links
109109+ if embed.EmbedExternal != nil && embed.EmbedExternal.External != nil {
110110+ // Convert blob thumb to CDN URL if present
111111+ var thumbURL *string
112112+ if embed.EmbedExternal.External.Thumb != nil {
113113+ // CDN URL for external link thumbnails
114114+ cid := embed.EmbedExternal.External.Thumb.Ref.String()
115115+ url := fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
116116+ thumbURL = &url
117117+ }
118118+119119+ result.EmbedExternal_View = &bsky.EmbedExternal_View{
120120+ LexiconTypeID: "app.bsky.embed.external#view",
121121+ External: &bsky.EmbedExternal_ViewExternal{
122122+ Uri: embed.EmbedExternal.External.Uri,
123123+ Title: embed.EmbedExternal.External.Title,
124124+ Description: embed.EmbedExternal.External.Description,
125125+ Thumb: thumbURL,
126126+ },
127127+ }
128128+ return result
129129+ }
130130+131131+ // Handle video
132132+ if embed.EmbedVideo != nil {
133133+ // TODO: Implement video embed view
134134+ // This would require converting video blob to CDN URLs and playlist URLs
135135+ return nil
136136+ }
137137+138138+ // Handle record (quote posts, etc.)
139139+ if embed.EmbedRecord != nil {
140140+ // TODO: Implement record embed view
141141+ // This requires hydrating the embedded record, which is complex
142142+ // For now, return nil to skip these embeds
143143+ return nil
144144+ }
145145+146146+ // Handle record with media (quote post with images/external)
147147+ if embed.EmbedRecordWithMedia != nil {
148148+ // TODO: Implement record with media embed view
149149+ // This combines record hydration with media conversion
150150+ return nil
151151+ }
152152+153153+ return nil
154154+}
155155+156156+// GeneratorView builds a feed generator view (app.bsky.feed.defs#generatorView)
157157+func GeneratorView(uri, cid string, record *bsky.FeedGenerator, creator *hydration.ActorInfo, likeCount int64, viewerLike string, indexedAt string) *bsky.FeedDefs_GeneratorView {
158158+ view := &bsky.FeedDefs_GeneratorView{
159159+ LexiconTypeID: "app.bsky.feed.defs#generatorView",
160160+ Uri: uri,
161161+ Cid: cid,
162162+ Did: record.Did,
163163+ Creator: ProfileView(creator),
164164+ DisplayName: record.DisplayName,
165165+ Description: record.Description,
166166+ IndexedAt: indexedAt,
167167+ }
168168+169169+ // Add optional fields
170170+ if record.Avatar != nil {
171171+ avatarURL := fmt.Sprintf("https://cdn.bsky.app/img/avatar/plain/%s/%s@jpeg", creator.DID, record.Avatar.Ref.String())
172172+ view.Avatar = &avatarURL
173173+ }
174174+175175+ if record.DescriptionFacets != nil && len(record.DescriptionFacets) > 0 {
176176+ view.DescriptionFacets = record.DescriptionFacets
177177+ }
178178+179179+ if record.AcceptsInteractions != nil {
180180+ view.AcceptsInteractions = record.AcceptsInteractions
181181+ }
182182+183183+ if record.ContentMode != nil {
184184+ view.ContentMode = record.ContentMode
185185+ }
186186+187187+ // Add like count if present
188188+ if likeCount > 0 {
189189+ view.LikeCount = &likeCount
190190+ }
191191+192192+ // Add viewer state if viewer has liked
193193+ if viewerLike != "" {
194194+ view.Viewer = &bsky.FeedDefs_GeneratorViewerState{
195195+ Like: &viewerLike,
196196+ }
197197+ }
198198+199199+ return view
200200+}
+2-15
xrpc/actor/getProfiles.go
···33import (
44 "net/http"
5566+ "github.com/bluesky-social/indigo/api/bsky"
67 "github.com/labstack/echo/v4"
78 "github.com/whyrusleeping/konbini/hydration"
89 "github.com/whyrusleeping/konbini/views"
···2829 ctx := c.Request().Context()
29303031 // Resolve all actors to DIDs and hydrate profiles
3131- profiles := make([]interface{}, 0)
3232+ profiles := make([]*bsky.ActorDefs_ProfileViewDetailed, 0, len(actors))
3233 for _, actor := range actors {
3334 // Resolve actor to DID
3435 did, err := hydrator.ResolveDID(ctx, actor)
···4344 // Skip actors that can't be hydrated
4445 continue
4546 }
4646-4747- // Get counts for the profile
4848- type counts struct {
4949- Followers int
5050- Follows int
5151- Posts int
5252- }
5353- var c counts
5454- db.Raw(`
5555- SELECT
5656- (SELECT COUNT(*) FROM follows WHERE subject = (SELECT id FROM repos WHERE did = ?)) as followers,
5757- (SELECT COUNT(*) FROM follows WHERE author = (SELECT id FROM repos WHERE did = ?)) as follows,
5858- (SELECT COUNT(*) FROM posts WHERE author = (SELECT id FROM repos WHERE did = ?)) as posts
5959- `, did, did, did).Scan(&c)
60476148 profiles = append(profiles, views.ProfileViewDetailed(actorInfo))
6249 }