package blueskyclient import ( "encoding/json" "fmt" "sort" "strings" "time" ) type Post struct { Type string `json:"$type"` Text string `json:"text,omitempty"` Embed *Embed `json:"embed,omitempty"` Langs []string `json:"langs,omitempty"` Labels *Labels `json:"labels,omitempty"` Reply *Reply `json:"reply,omitempty"` Facets *[]Facets `json:"facets,omitempty"` CreatedAt time.Time `json:"createdAt"` Subject *Record `json:"subject,omitempty"` Avatar *Avatar `json:"avatar,omitempty"` Banner *Banner `json:"banner,omitempty"` Description string `json:"description"` DisplayName string `json:"displayName"` PinnedPost *Record `json:"pinnedPost,omitempty"` Allow []struct { Type string `json:"$type"` } `json:"allow,omitempty"` HiddenReplies []any `json:"hiddenReplies,omitempty"` Post string `json:"post,omitempty"` } type Banner struct { Type string `json:"$type"` Ref struct { Link string `json:"$link"` } `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } type Avatar struct { Type string `json:"$type"` Ref struct { Link string `json:"$link"` } `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } type Ref struct { Link string `json:"$link"` } type Thumb struct { Type string `json:"$type"` Ref *Ref `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } type External struct { URI string `json:"uri"` Thumb *Thumb `json:"thumb"` Title string `json:"title"` Description string `json:"description"` } type Video struct { Type string `json:"$type"` Ref *Ref `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } type Image struct { Type string `json:"$type"` Ref *Ref `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } type AspectRatio struct { Width int `json:"width"` Height int `json:"height"` } type Images struct { Alt string `json:"alt,omitempty"` Image *Image `json:"image"` AspectRatio *AspectRatio `json:"aspectRatio"` } type Media struct { Type string `json:"$type"` External *External `json:"external,omitempty"` Video *Video `json:"video,omitempty"` Images *[]Images `json:"images,omitempty"` AspectRatio *AspectRatio `json:"aspectRatio,omitempty"` } type Record struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } func (r *Record) GetDID() string { return strings.Split(r.URI, "/")[2] } func (r *Record) GetCollection() string { return strings.Split(r.URI, "/")[3] } func (r *Record) GetRKey() string { return strings.Split(r.URI, "/")[4] } type PostRecord struct { Type string `json:"$type"` Cid string `json:"cid"` URI string `json:"uri"` Record *Record `json:"record"` } func (r *PostRecord) GetDID() string { return strings.Split(r.URI, "/")[2] } func (r *PostRecord) GetCollection() string { return strings.Split(r.URI, "/")[3] } func (r *PostRecord) GetRKey() string { return strings.Split(r.URI, "/")[4] } type Embed struct { Type string `json:"$type"` Media *Media `json:"media,omitempty"` Images *[]Images `json:"images,omitempty"` Video *Video `json:"video,omitempty"` Record *PostRecord `json:"record,omitempty"` External *External `json:"external,omitempty"` } type Values struct { Val string `json:"val"` } type Labels struct { Type string `json:"$type"` Values *[]Values `json:"values"` } type Reply struct { Root *Record `json:"root,omitempty"` Parent *Record `json:"parent,omitempty"` } type Index struct { ByteEnd int `json:"byteEnd"` ByteStart int `json:"byteStart"` } type Features struct { Did string `json:"did"` URI string `json:"uri"` Tag string `json:"tag"` Type string `json:"$type"` } type Facets struct { Type string `json:"$type"` Index *Index `json:"index"` Features *[]Features `json:"features"` } type ParsedEmbeds struct { Type string MimeType string Ref string URI string Width int64 Height int64 } type Author struct { Did string `json:"did"` Handle string `json:"handle"` DisplayName string `json:"displayName"` Avatar string `json:"avatar"` Associated struct { Chat struct { AllowIncoming string `json:"allowIncoming"` } `json:"chat"` } `json:"associated"` Labels []interface{} `json:"labels"` CreatedAt time.Time `json:"createdAt"` } type FetchedPost struct { URI string `json:"uri"` Cid string `json:"cid"` Author *Author `json:"author"` Record *Post `json:"record"` Embed *Embed `json:"embed,omitempty"` ReplyCount int `json:"replyCount"` RepostCount int `json:"repostCount"` LikeCount int `json:"likeCount"` QuoteCount int `json:"quoteCount"` IndexedAt time.Time `json:"indexedAt"` Labels []struct { Src string `json:"src"` URI string `json:"uri"` Cid string `json:"cid"` Val string `json:"val"` Cts time.Time `json:"cts"` } `json:"labels"` } func (b *BSky) ParsePost(post []byte) (*Post, error) { var p = &Post{} err := json.Unmarshal(post, &p) if err != nil { return nil, err } return p, nil } func (post *Post) ProcessFacets() string { if post == nil { return "" } if post.Facets == nil { return post.Text } sort.Slice((*post.Facets), func(i, j int) bool { return (*post.Facets)[i].Index.ByteStart < (*post.Facets)[j].Index.ByteStart }) var result strings.Builder lastIndex := 0 // post.Text = html.EscapeString(post.Text) for _, facet := range *post.Facets { start := facet.Index.ByteStart end := facet.Index.ByteEnd result.WriteString(post.Text[lastIndex:start]) for _, feature := range *facet.Features { switch feature.Type { case "app.bsky.richtext.facet#mention": link := fmt.Sprintf(`%s`, feature.Did, post.Text[start:end]) result.WriteString(link) case "app.bsky.richtext.facet#link": link := fmt.Sprintf(`%s`, feature.URI, post.Text[start:end]) result.WriteString(link) case "app.bsky.richtext.facet#tag": link := fmt.Sprintf(`%s`, feature.Tag, post.Text[start:end]) result.WriteString(link) default: result.WriteString(post.Text[start:end]) } } lastIndex = end } result.WriteString(post.Text[lastIndex:]) return result.String() } func (p *Post) GetEmbeds() *[]ParsedEmbeds { var parsedEmbeds = &[]ParsedEmbeds{} if p.Embed != nil { if p.Embed.Video != nil { parsedEmbed := ParsedEmbeds{ URI: p.Embed.Video.Ref.Link, Type: "video", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } if p.Embed.External != nil { if strings.Contains(p.Embed.External.URI, "media.tenor.com") { parsedEmbed := ParsedEmbeds{ URI: p.Embed.External.URI, Type: "external", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } } if p.Embed.Media != nil { if p.Embed.Media.Images != nil { for _, image := range *p.Embed.Media.Images { parsedEmbed := ParsedEmbeds{ URI: image.Image.Ref.Link, Type: "image", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } } if p.Embed.Media.Video != nil { parsedEmbed := ParsedEmbeds{ URI: p.Embed.Media.Video.Ref.Link, Type: "video", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } if p.Embed.Media.External != nil { parsedEmbed := ParsedEmbeds{ URI: p.Embed.Media.External.URI, Type: "external", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } } if p.Embed.Images != nil { for _, image := range *p.Embed.Images { parsedEmbed := ParsedEmbeds{ URI: image.Image.Ref.Link, Type: "image", } *parsedEmbeds = append(*parsedEmbeds, parsedEmbed) } } } return parsedEmbeds } func (p *Post) GetMedia() *Media { if p.GetEmbeds() != nil { if p.Embed.Media != nil { return p.Embed.Media } } return nil } func (p *Post) GetMediaImages() *[]Images { if p.GetMedia() != nil { return p.GetMedia().Images } return nil } func (p *Post) GetExternal() *External { if p.GetMedia() != nil { if p.GetMedia().External != nil { return p.GetMedia().External } } return nil } func (p *Post) IsReply() bool { return p.Reply != nil } func (p *Post) IsQuotePost() bool { if p.Embed != nil { if p.Embed.Record != nil { return true } } return false }