discord_webhook_rs/
embed.rs

1use super::discord::Error;
2use serde_json::{Value, json};
3
4pub struct Author {
5    name: Option<String>,
6    url: Option<String>,
7    icon_url: Option<String>,
8}
9
10pub struct Field {
11    name: Option<String>,
12    value: Option<String>,
13    inline: Option<bool>,
14}
15
16pub struct Footer {
17    text: Option<String>,
18    icon_url: Option<String>,
19}
20
21pub struct Embed {
22    author: Option<Author>,
23    title: Option<String>,
24    url: Option<String>,
25    description: Option<String>,
26    color: Option<u32>,
27    fields: Vec<Field>,
28    thumbnail: Option<String>,
29    image: Option<String>,
30    footer: Option<Footer>,
31}
32
33impl Author {
34    pub fn new() -> Self {
35        Author {
36            name: None,
37            url: None,
38            icon_url: None,
39        }
40    }
41
42    pub fn name(mut self, name: impl Into<String>) -> Self {
43        self.name = Some(name.into());
44        self
45    }
46
47    pub fn url(mut self, url: impl Into<String>) -> Self {
48        self.url = Some(url.into());
49        self
50    }
51
52    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
53        self.icon_url = Some(icon_url.into());
54        self
55    }
56
57    fn verify(&self) -> Result<(), Error> {
58        if let Some(name) = &self.name {
59            if name.len() > 256 {
60                return Err(Error::MaxContent);
61            }
62        }
63
64        Ok(())
65    }
66
67    pub fn build(&self) -> Result<Value, Error> {
68        self.verify()?;
69
70        let mut obj = serde_json::Map::new();
71
72        if let Some(name) = &self.name {
73            obj.insert("name".into(), Value::String(name.clone()));
74        };
75
76        if let Some(url) = &self.url {
77            obj.insert("url".into(), Value::String(url.clone()));
78        };
79
80        Ok(Value::Object(obj))
81    }
82}
83
84impl Field {
85    pub fn new() -> Self {
86        Field {
87            name: None,
88            value: None,
89            inline: None,
90        }
91    }
92
93    pub fn name(mut self, name: impl Into<String>) -> Self {
94        self.name = Some(name.into());
95        self
96    }
97
98    pub fn value(mut self, value: impl Into<String>) -> Self {
99        self.value = Some(value.into());
100        self
101    }
102
103    pub fn inline(mut self, inline: bool) -> Self {
104        self.inline = Some(inline);
105        self
106    }
107
108    fn verify(&self) -> Result<(), Error> {
109        if let Some(name) = &self.name {
110            if name.len() > 256 {
111                return Err(Error::MaxContent);
112            }
113        }
114
115        if let Some(value) = &self.value {
116            if value.len() > 1024 {
117                return Err(Error::MaxContent);
118            }
119        }
120
121        Ok(())
122    }
123
124    pub fn build(&self) -> Result<Value, Error> {
125        self.verify()?;
126
127        let mut obj = serde_json::Map::new();
128
129        if let Some(name) = &self.name {
130            obj.insert("name".into(), Value::String(name.clone()));
131        } else {
132            return Err(Error::MissingNameField);
133        };
134
135        if let Some(value) = &self.value {
136            obj.insert("value".into(), Value::String(value.clone()));
137        } else {
138            return Err(Error::MissingValueField);
139        };
140
141        if let Some(inline) = &self.inline {
142            obj.insert("inline".into(), Value::Bool(inline.clone()));
143        };
144
145        Ok(Value::Object(obj))
146    }
147}
148
149impl Footer {
150    pub fn new() -> Self {
151        Footer {
152            text: None,
153            icon_url: None,
154        }
155    }
156
157    pub fn text(mut self, text: impl Into<String>) -> Self {
158        self.text = Some(text.into());
159        self
160    }
161
162    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
163        self.icon_url = Some(icon_url.into());
164        self
165    }
166
167    fn verify(&self) -> Result<(), Error> {
168        if let Some(text) = &self.text {
169            if text.len() > 2048 {
170                return Err(Error::MaxContent);
171            }
172        }
173
174        Ok(())
175    }
176
177    pub fn build(&self) -> Result<Value, Error> {
178        self.verify()?;
179
180        let mut obj = serde_json::Map::new();
181
182        if let Some(text) = &self.text {
183            obj.insert("text".into(), Value::String(text.clone()));
184        };
185
186        if let Some(icon_url) = &self.icon_url {
187            obj.insert("icon_url".into(), Value::String(icon_url.clone()));
188        };
189
190        Ok(Value::Object(obj))
191    }
192}
193
194impl Embed {
195    pub fn new() -> Self {
196        Embed {
197            author: None,
198            title: None,
199            url: None,
200            description: None,
201            color: None,
202            fields: Vec::new(),
203            thumbnail: None,
204            image: None,
205            footer: None,
206        }
207    }
208
209    pub fn author(mut self, author: Author) -> Self {
210        self.author = Some(author);
211        self
212    }
213
214    pub fn title(mut self, title: impl Into<String>) -> Self {
215        self.title = Some(title.into());
216        self
217    }
218
219    pub fn url(mut self, url: impl Into<String>) -> Self {
220        self.url = Some(url.into());
221        self
222    }
223
224    pub fn description(mut self, description: impl Into<String>) -> Self {
225        self.description = Some(description.into());
226        self
227    }
228
229    pub fn color(mut self, color: u32) -> Self {
230        self.color = Some(color);
231        self
232    }
233
234    pub fn add_field(mut self, field: Field) -> Self {
235        self.fields.push(field);
236        self
237    }
238
239    pub fn thumbnail(mut self, thumbnail: impl Into<String>) -> Self {
240        self.thumbnail = Some(thumbnail.into());
241        self
242    }
243
244    pub fn image(mut self, image: impl Into<String>) -> Self {
245        self.image = Some(image.into());
246        self
247    }
248
249    pub fn footer(mut self, footer: Footer) -> Self {
250        self.footer = Some(footer);
251        self
252    }
253
254    fn verify(&self) -> Result<(), Error> {
255        if let Some(title) = &self.title {
256            if title.len() > 256 {
257                return Err(Error::MaxContent);
258            }
259        }
260
261        if let Some(description) = &self.description {
262            if description.len() > 4096 {
263                return Err(Error::MaxContent);
264            }
265        }
266
267        if let Some(color) = self.color {
268            // Max decimal color (R: 255, G: 255, B: 255)
269            if color > (1 << 8 * 3) - 1 {
270                return Err(Error::InvalidColor);
271            }
272        }
273
274        if self.fields.len() > 25 {
275            return Err(Error::MaxField);
276        }
277
278        Ok(())
279    }
280
281    pub fn build(&self) -> Result<Value, Error> {
282        self.verify()?;
283
284        let mut obj = serde_json::Map::new();
285
286        if let Some(author) = &self.author {
287            obj.insert("author".into(), author.build()?);
288        }
289
290        if let Some(footer) = &self.footer {
291            obj.insert("footer".into(), footer.build()?);
292        }
293
294        if let Some(title) = &self.title {
295            obj.insert("title".into(), Value::String(title.clone()));
296        }
297
298        if let Some(url) = &self.url {
299            obj.insert("url".into(), Value::String(url.clone()));
300        }
301
302        if let Some(description) = &self.description {
303            obj.insert("description".into(), Value::String(description.clone()));
304        }
305
306        if let Some(color) = &self.color {
307            obj.insert("color".into(), json!(self.color));
308        }
309
310        if let Some(thumbnail) = &self.thumbnail {
311            obj.insert("thumbnail".into(), json!({"url": thumbnail}));
312        }
313
314        if let Some(image) = &self.image {
315            obj.insert("image".into(), json!({"url": image}));
316        }
317
318        obj.insert("fields".into(), Value::Array(Vec::new()));
319        if let Value::Array(ref mut fields) = obj["fields"] {
320            for field in &self.fields {
321                fields.push(field.build()?);
322            }
323        };
324
325        // An embed can be sent if it contains at least one of these key
326        if obj.contains_key("author")
327            || obj.contains_key("footer")
328            || obj.contains_key("title")
329            || obj.contains_key("description")
330            || obj.contains_key("thumbnail")
331            || obj.contains_key("image")
332            || self.fields.len() > 0
333        {
334            Ok(Value::Object(obj))
335        } else {
336            Err(Error::InvalidEmbed)
337        }
338    }
339}