package server import ( "time" "github.com/bluesky-social/indigo/api/bsky" "github.com/nacorid/naco-api/internal/activitypub" "github.com/nacorid/naco-api/internal/clustering" "github.com/nacorid/naco-api/internal/nostr" "github.com/nacorid/naco-api/internal/twitter" ) func bskyPostViewToResponse(p *bsky.FeedDefs_PostView) *GetPostResponsePost { author := "" if p.Author != nil { author = p.Author.Did } content := "" created := "" if fp, ok := p.Record.Val.(*bsky.FeedPost); ok { content = fp.Text created = fp.CreatedAt } likes, reposts, quotes, replies := int64(0), int64(0), int64(0), int64(0) if p.LikeCount != nil { likes = *p.LikeCount } if p.RepostCount != nil { reposts = *p.RepostCount } if p.QuoteCount != nil { quotes = *p.QuoteCount } if p.ReplyCount != nil { replies = *p.ReplyCount } return &GetPostResponsePost{ Author: author, Content: content, Likes: likes, Reposts: reposts, Quotes: quotes, Replies: replies, CreatedAt: created, } } func apPostToResponse(p *activitypub.Post) *GetPostResponsePost { created := "" if !p.CreatedAt.IsZero() { created = p.CreatedAt.Format(time.RFC3339) } return &GetPostResponsePost{ Author: p.Author, Content: p.Content, Likes: int64(p.FavouritesCount), Reposts: int64(p.ReblogsCount), Replies: int64(p.RepliesCount), CreatedAt: created, } } func nostrPostToResponse(p *nostr.Post) *GetPostResponsePost { created := "" if !p.CreatedAt.IsZero() { created = p.CreatedAt.Format(time.RFC3339) } return &GetPostResponsePost{ Author: p.Author, Content: p.Content, CreatedAt: created, } } func twitterPostToResponse(p *twitter.Post) *GetPostResponsePost { created := "" if !p.CreatedAt.IsZero() { created = p.CreatedAt.Format(time.RFC3339) } return &GetPostResponsePost{ Author: p.Author, Content: p.Content, Likes: int64(p.Likes), Reposts: int64(p.Reposts), Quotes: int64(p.Quotes), Replies: int64(p.Replies), CreatedAt: created, } } // ---- Batch converters for GetPostResponsePost ---- func blueskyFeedViewsToResponses(views []*bsky.FeedDefs_FeedViewPost) ([]*GetPostResponsePost, string) { posts := make([]*GetPostResponsePost, 0, len(views)) var aid string for _, p := range views { if p.Post == nil { continue } posts = append(posts, bskyPostViewToResponse(p.Post)) if p.Post.Author != nil { aid = p.Post.Author.Did } } return posts, aid } func apPostsPtrToResponses(posts []*activitypub.Post) ([]*GetPostResponsePost, string) { out := make([]*GetPostResponsePost, 0, len(posts)) var aid string for _, p := range posts { out = append(out, apPostToResponse(p)) aid = p.Author } return out, aid } func apPostsToResponses(posts []*activitypub.Post) ([]*GetPostResponsePost, string) { out := make([]*GetPostResponsePost, 0, len(posts)) var aid string for _, p := range posts { out = append(out, apPostToResponse(p)) aid = p.Author } return out, aid } func nostrPostsToResponses(posts []*nostr.Post) ([]*GetPostResponsePost, string) { out := make([]*GetPostResponsePost, 0, len(posts)) var aid string for _, p := range posts { out = append(out, nostrPostToResponse(p)) aid = p.Author } return out, aid } func twitterPostsToResponses(posts []*twitter.Post) ([]*GetPostResponsePost, string) { out := make([]*GetPostResponsePost, 0, len(posts)) var aid string for _, p := range posts { out = append(out, twitterPostToResponse(p)) aid = p.Author } return out, aid } // ---- Batch converters for clustering.Item (style & analytics) ---- func blueskyFeedViewsToItems(views []*bsky.FeedDefs_FeedViewPost) []clustering.Item { out := make([]clustering.Item, 0, len(views)) for _, p := range views { fp, ok := p.Post.Record.Val.(*bsky.FeedPost) if !ok { continue } if fp.Text == "" { continue } out = append(out, clustering.Item{ID: p.Post.Uri, Text: fp.Text}) } return out } func apPostsPtrToItems(posts []*activitypub.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content != "" { out = append(out, clustering.Item{ID: p.ID, Text: p.Content}) } } return out } func nostrPostsToItems(posts []*nostr.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content != "" { out = append(out, clustering.Item{ID: p.ID, Text: p.Content}) } } return out } func twitterPostsToItems(posts []*twitter.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content != "" { out = append(out, clustering.Item{ID: p.ID, Text: p.Content}) } } return out } // analytics-specific converters that preserve engagement + timestamps func blueskyFeedViewsToAnalyticsItems(views []*bsky.FeedDefs_FeedViewPost) []clustering.Item { out := make([]clustering.Item, 0, len(views)) for _, p := range views { if p.Post == nil || p.Post.Record == nil { continue } fp, ok := p.Post.Record.Val.(*bsky.FeedPost) if !ok { continue } item := clustering.Item{ID: p.Post.Uri, Text: fp.Text} if ts, err := time.Parse(time.RFC3339, p.Post.IndexedAt); err == nil { item.Timestamp = ts } item.Likes = int64Val(p.Post.LikeCount) item.Reposts = int64Val(p.Post.RepostCount) item.Replies = int64Val(p.Post.ReplyCount) item.Quotes = int64Val(p.Post.QuoteCount) out = append(out, item) } return out } func apPostsToAnalyticsItems(posts []*activitypub.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content != "" { out = append(out, clustering.Item{ ID: p.ID, Text: p.Content, Timestamp: p.CreatedAt, Likes: int64(p.FavouritesCount), Reposts: int64(p.ReblogsCount), Replies: int64(p.RepliesCount), }) } } return out } func nostrPostsToAnalyticsItems(posts []*nostr.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content != "" { out = append(out, clustering.Item{ID: p.ID, Text: p.Content, Timestamp: p.CreatedAt}) } } return out } func twitterPostsToAnalyticsItems(posts []*twitter.Post) []clustering.Item { out := make([]clustering.Item, 0, len(posts)) for _, p := range posts { if p.Content == "" { continue } out = append(out, clustering.Item{ ID: p.ID, Text: p.Content, Timestamp: p.CreatedAt, Likes: int64(p.Likes), Reposts: int64(p.Reposts), Replies: int64(p.Replies), Quotes: int64(p.Quotes), }) } return out }