aboutsummaryrefslogtreecommitdiff
path: root/notes/app.bsky.feed.go
blob: 21b94f9a03990be7e8c55d04c41171ece52e8615 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
}