small SPA gleam experiment to fetch and render a single bsky post
1import gleam/dynamic/decode.{field, int, list, string, success}
2import gleam/option.{type Option, None}
3
4pub type StrongRef {
5 StrongRef(uri: String, cid: String)
6}
7
8pub fn decode_strong_ref() -> decode.Decoder(StrongRef) {
9 use uri <- field("uri", string)
10 use cid <- field("cid", string)
11 success(StrongRef(uri:, cid:))
12}
13
14pub type Label {
15 Label(val: String)
16}
17
18pub fn decode_label() -> decode.Decoder(Label) {
19 use val <- field("val", string)
20 success(Label(val:))
21}
22
23pub type ByteSlice {
24 ByteSlice(byte_start: Int, byte_end: Int)
25}
26
27pub fn decode_byte_slice() -> decode.Decoder(ByteSlice) {
28 use byte_start <- field("byteStart", int)
29 use byte_end <- field("byteEnd", int)
30 success(ByteSlice(byte_start:, byte_end:))
31}
32
33pub type FacetFeature {
34 Mention(did: String)
35 Link(uri: String)
36 Tag(tag: String)
37}
38
39fn decode_facet_feature() -> decode.Decoder(FacetFeature) {
40 use type_ <- field("$type", string)
41 case type_ {
42 "app.bsky.richtext.facet#mention" -> {
43 use did <- field("did", string)
44 success(Mention(did:))
45 }
46 "app.bsky.richtext.facet#link" -> {
47 use uri <- field("uri", string)
48 success(Link(uri:))
49 }
50 "app.bsky.richtext.facet#tag" -> {
51 use tag <- field("tag", string)
52 success(Tag(tag:))
53 }
54 _ -> decode.failure(Mention(""), expected: "FacetFeature")
55 }
56}
57
58pub type Facet {
59 Facet(index: ByteSlice, features: List(FacetFeature))
60}
61
62pub fn decode_facet() -> decode.Decoder(Facet) {
63 use index <- field("index", decode_byte_slice())
64 use features <- field("features", list(of: decode_facet_feature()))
65 success(Facet(index:, features:))
66}
67
68pub type AspectRatio {
69 AspectRatio(width: Int, height: Int)
70}
71
72pub fn decode_aspect_ratio() -> decode.Decoder(AspectRatio) {
73 use width <- field("width", int)
74 use height <- field("height", int)
75 success(AspectRatio(width:, height:))
76}
77
78pub type Image {
79 Image(
80 alt: String,
81 thumb: Option(String),
82 fullsize: Option(String),
83 aspect_ratio: Option(AspectRatio),
84 )
85}
86
87pub fn decode_image() -> decode.Decoder(Image) {
88 use alt <- field("alt", string)
89 success(Image(alt:, thumb: None, fullsize: None, aspect_ratio: None))
90}
91
92pub type External {
93 External(
94 uri: String,
95 title: String,
96 description: String,
97 thumb: Option(String),
98 )
99}
100
101pub fn decode_external() -> decode.Decoder(External) {
102 use uri <- field("uri", string)
103 use title <- field("title", string)
104 use description <- field("description", string)
105 success(External(uri:, title:, description:, thumb: None))
106}
107
108pub type Embed {
109 Images(images: List(Image))
110 ExternalLink(external: External)
111 Record(StrongRef)
112}
113
114pub fn decode_embed() -> decode.Decoder(Embed) {
115 use type_ <- field("$type", string)
116 case type_ {
117 "app.bsky.embed.images" -> {
118 use images <- field("images", list(of: decode_image()))
119 success(Images(images:))
120 }
121 "app.bsky.embed.external" -> {
122 use external <- field("external", decode_external())
123 success(ExternalLink(external:))
124 }
125 "app.bsky.embed.record" -> {
126 use record <- field("record", decode_strong_ref())
127 success(Record(record))
128 }
129 _ -> decode.failure(Images(images: []), expected: "Embed type")
130 }
131}
132
133pub type Post {
134 Post(
135 text: String,
136 facets: List(Facet),
137 embed: Option(Embed),
138 langs: List(String),
139 labels: List(Label),
140 tags: List(String),
141 created_at: String,
142 )
143}
144
145pub fn decode_post() -> decode.Decoder(Post) {
146 use text <- field("text", string)
147 use created_at <- field("createdAt", string)
148 success(Post(
149 text:,
150 facets: [],
151 embed: None,
152 langs: [],
153 labels: [],
154 tags: [],
155 created_at:,
156 ))
157}