diff options
Diffstat (limited to 'notes/app.bsky.feed.go')
| -rw-r--r-- | notes/app.bsky.feed.go | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/notes/app.bsky.feed.go b/notes/app.bsky.feed.go new file mode 100644 index 0000000..21b94f9 --- /dev/null +++ b/notes/app.bsky.feed.go @@ -0,0 +1,150 @@ +package notes + +import ( + "fmt" + "llostre/general" + "time" +) + +type EmbedImage struct { + Alt string `json:"alt"` + Image struct { + CID string `json:"$link"` + } `json:"image"` + AspectRatio *struct { + Width int `json:"width"` + Height int `json:"height"` + } `json:"aspectRatio,omitempty"` +} + +type EmbedImages struct { + Type string `json:"$type"` + Images []EmbedImage `json:"images,omitempty"` +} + +type AppBskyFeedPostEmbed struct { + Type string `json:"$type"` + Images []EmbedImage `json:"images,omitempty"` + Media *EmbedImages `json:"media,omitempty"` +} + +type AppBskyFeedPostRecord struct { + URI string `json:"uri"` + Value struct { + Text string `json:"text"` + CreatedAt string `json:"createdAt"` + Reply *struct { + Parent struct { + URI string `json:"uri"` + } `json:"parent"` + } `json:"reply,omitempty"` + Facets []struct { + Index struct { + ByteStart int `json:"byteStart"` + ByteEnd int `json:"byteEnd"` + } `json:"index"` + Features []struct { + Type string `json:"$type"` + URI string `json:"uri,omitempty"` + DID string `json:"did,omitempty"` + Tag string `json:"tag,omitempty"` + } `json:"features"` + } `json:"facets,omitempty"` + Embed *AppBskyFeedPostEmbed `json:"embed,omitempty"` + } `json:"value"` +} + +func extractImages(embed *AppBskyFeedPostEmbed, did, pdsURL string) []Media { + if embed == nil { + return nil + } + images := embed.Images + if embed.Media != nil { + images = embed.Media.Images + } + var media []Media + for _, img := range images { + m := Media{ + URL: fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob?did=%s&cid=%s", pdsURL, did, img.Image.CID), + Alt: img.Alt, + } + if img.AspectRatio != nil { + m.Width = img.AspectRatio.Width + m.Height = img.AspectRatio.Height + } + media = append(media, m) + } + return media +} + +func NoteFromAppBskyFeedPostRecord(record AppBskyFeedPostRecord, author *Author) (*Note, error) { + if record.URI == "" { + return nil, fmt.Errorf("missing required field: uri") + } + + createdAt, err := time.Parse(time.RFC3339, record.Value.CreatedAt) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp: %w", err) + } + + var replyTo *string + if record.Value.Reply != nil { + replyTo = &record.Value.Reply.Parent.URI + } + + var facets []Facet + for _, f := range record.Value.Facets { + for _, feature := range f.Features { + var value string + var facetType string + + switch feature.Type { + case "app.bsky.richtext.facet#link": + facetType = "link" + value = feature.URI + case "app.bsky.richtext.facet#mention": + facetType = "mention" + value = feature.DID + case "app.bsky.richtext.facet#tag": + facetType = "tag" + value = feature.Tag + default: + continue + } + + facets = append(facets, Facet{ + ByteStart: f.Index.ByteStart, + ByteEnd: f.Index.ByteEnd, + Type: facetType, + Value: value, + }) + } + } + + var handle, displayName, avatarURL string + if author != nil { + handle = author.Handle + displayName = author.DisplayName + avatarURL = author.AvatarURL + } else { + handle = "handle.invalid" + displayName = "" + avatarURL = "" + } + + did, _, _, err := general.ParseURI(record.URI) + + return &Note{ + URI: record.URI, + AuthorDID: did, + AuthorHandle: handle, + AuthorName: displayName, + AuthorNoUnauth: author.NoUnauth, + AvatarURL: avatarURL, + CreatedAt: createdAt, + Text: record.Value.Text, + ReplyTo: replyTo, + Facets: facets, + Media: extractImages(record.Value.Embed, did, author.PDS), + }, nil +} |
