package bsky import ( "encoding/json" "fmt" "sort" "strings" "time" ) type Post struct { Text string `json:"text,omitempty"` Type string `json:"$type,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"` } type Ref struct { Link string `json:"$link,omitempty"` } type Thumb struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } type External struct { URI string `json:"uri,omitempty"` Thumb *Thumb `json:"thumb,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` } type Video struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } type Image struct { Type string `json:"$type,omitempty"` Ref *Ref `json:"ref,omitempty"` MimeType string `json:"mimeType,omitempty"` Size int `json:"size,omitempty"` } type AspectRatio struct { Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` } type Images struct { Alt string `json:"alt,omitempty"` Image *Image `json:"image,omitempty"` AspectRatio *AspectRatio `json:"aspectRatio,omitempty"` } type Media struct { Type string `json:"$type,omitempty"` 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"` } type PostRecord struct { Type string `json:"$type,omitempty"` Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` Record *Record `json:"record,omitempty"` } type Embed struct { Type string `json:"$type,omitempty"` 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,omitempty"` } type Labels struct { Type string `json:"$type,omitempty"` Values *[]Values `json:"values,omitempty"` } type Root struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } func (r *Root) GetDID() string { return strings.Split(r.URI, "/")[2] } func (r *Root) GetRKey() string { return strings.Split(r.URI, "/")[4] } type Parent struct { Cid string `json:"cid,omitempty"` URI string `json:"uri,omitempty"` } func (p *Parent) GetDID() string { return strings.Split(p.URI, "/")[2] } func (p *Parent) GetRKey() string { return strings.Split(p.URI, "/")[4] } type Reply struct { Root *Root `json:"root,omitempty"` Parent *Parent `json:"parent,omitempty"` } type Index struct { ByteEnd int `json:"byteEnd,omitempty"` ByteStart int `json:"byteStart,omitempty"` } type Features struct { Did string `json:"did,omitempty"` URI string `json:"uri,omitempty"` Tag string `json:"tag,omitempty"` Type string `json:"$type,omitempty"` } type Facets struct { Type string `json:"$type"` Index *Index `json:"index,omitempty"` Features *[]Features `json:"features,omitempty"` } type ParsedEmbeds struct { Type string MimeType string Ref string Cid string URI string Width int64 Height int64 } type FetchedPost struct { URI string `json:"uri"` Cid string `json:"cid"` 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"` } `json:"author"` Record *Post `json:"record"` // Record struct { // Type string `json:"$type"` // CreatedAt time.Time `json:"createdAt"` // Embed struct { // Type string `json:"$type"` // Media struct { // Type string `json:"$type"` // Images []struct { // Alt string `json:"alt"` // AspectRatio struct { // Height int `json:"height"` // Width int `json:"width"` // } `json:"aspectRatio"` // Image struct { // Type string `json:"$type"` // Ref struct { // Link string `json:"$link"` // } `json:"ref"` // MimeType string `json:"mimeType"` // Size int `json:"size"` // } `json:"image"` // } `json:"images"` // } `json:"media"` // Record struct { // Type string `json:"$type"` // Record struct { // Cid string `json:"cid"` // URI string `json:"uri"` // } `json:"record"` // } `json:"record"` // } `json:"embed"` // Labels struct { // Type string `json:"$type"` // Values []struct { // Val string `json:"val"` // } `json:"values"` // } `json:"labels"` // Langs []string `json:"langs"` // Text string `json:"text"` // } `json:"record"` Embed struct { Type string `json:"$type"` Media struct { Type string `json:"$type"` Images []struct { Thumb string `json:"thumb"` Fullsize string `json:"fullsize"` Alt string `json:"alt"` AspectRatio struct { Height int `json:"height"` Width int `json:"width"` } `json:"aspectRatio"` } `json:"images"` } `json:"media"` Record struct { Record struct { Type string `json:"$type"` URI string `json:"uri"` Cid string `json:"cid"` 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"` } `json:"author"` Value struct { Type string `json:"$type"` CreatedAt time.Time `json:"createdAt"` Embed struct { Type string `json:"$type"` AspectRatio struct { Height int `json:"height"` Width int `json:"width"` } `json:"aspectRatio"` Video struct { Type string `json:"$type"` Ref struct { Link string `json:"$link"` } `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } `json:"video"` } `json:"embed"` Facets []struct { Type string `json:"$type"` Features []struct { Type string `json:"$type"` Did string `json:"did"` } `json:"features"` Index struct { ByteEnd int `json:"byteEnd"` ByteStart int `json:"byteStart"` } `json:"index"` } `json:"facets"` Langs []string `json:"langs"` Text string `json:"text"` } `json:"value"` Labels []interface{} `json:"labels"` LikeCount int `json:"likeCount"` ReplyCount int `json:"replyCount"` RepostCount int `json:"repostCount"` QuoteCount int `json:"quoteCount"` IndexedAt time.Time `json:"indexedAt"` Embeds []struct { Type string `json:"$type"` Cid string `json:"cid"` Playlist string `json:"playlist"` Thumbnail string `json:"thumbnail"` AspectRatio struct { Height int `json:"height"` Width int `json:"width"` } `json:"aspectRatio"` } `json:"embeds"` } `json:"record"` } `json:"record"` } `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(aliases []Records) 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]) for _, alias := range aliases { if alias.Value.Subject == feature.Did { link = fmt.Sprintf(`%s`, strings.SplitN(alias.Value.Target, "#", 2)[0], strings.SplitN(alias.Value.Target, "#", 2)[1]) } } 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 }