discord_webhook_rs/
embed.rs

1use super::Author;
2use super::Error;
3use super::Field;
4use super::Footer;
5
6use serde_json::{Value, json};
7
8pub struct Embed {
9    author: Option<Author>,
10    title: Option<String>,
11    url: Option<String>,
12    description: Option<String>,
13    color: Option<u32>,
14    fields: Vec<Field>,
15    thumbnail: Option<String>,
16    image: Option<String>,
17    footer: Option<Footer>,
18}
19
20impl Embed {
21    pub fn new() -> Self {
22        Embed {
23            author: None,
24            title: None,
25            url: None,
26            description: None,
27            color: None,
28            fields: Vec::new(),
29            thumbnail: None,
30            image: None,
31            footer: None,
32        }
33    }
34
35    pub fn author(mut self, author: Author) -> Self {
36        self.author = Some(author);
37        self
38    }
39
40    pub fn title(mut self, title: impl Into<String>) -> Self {
41        self.title = Some(title.into());
42        self
43    }
44
45    pub fn url(mut self, url: impl Into<String>) -> Self {
46        self.url = Some(url.into());
47        self
48    }
49
50    pub fn description(mut self, description: impl Into<String>) -> Self {
51        self.description = Some(description.into());
52        self
53    }
54
55    pub fn color(mut self, color: u32) -> Self {
56        self.color = Some(color);
57        self
58    }
59
60    pub fn add_field(mut self, field: Field) -> Self {
61        self.fields.push(field);
62        self
63    }
64
65    pub fn thumbnail(mut self, thumbnail: impl Into<String>) -> Self {
66        self.thumbnail = Some(thumbnail.into());
67        self
68    }
69
70    pub fn image(mut self, image: impl Into<String>) -> Self {
71        self.image = Some(image.into());
72        self
73    }
74
75    pub fn footer(mut self, footer: Footer) -> Self {
76        self.footer = Some(footer);
77        self
78    }
79
80    fn verify(&self) -> Result<(), Error> {
81        if let Some(title) = &self.title {
82            if title.len() > 256 {
83                return Err(Error::MaxContent);
84            }
85        }
86
87        if let Some(description) = &self.description {
88            if description.len() > 4096 {
89                return Err(Error::MaxContent);
90            }
91        }
92
93        if let Some(color) = self.color {
94            // Max decimal color (R: 255, G: 255, B: 255)
95            if color > (1 << 8 * 3) - 1 {
96                return Err(Error::InvalidColor);
97            }
98        }
99
100        if self.fields.len() > 25 {
101            return Err(Error::MaxField);
102        }
103
104        Ok(())
105    }
106
107    pub fn build(&self) -> Result<Value, Error> {
108        self.verify()?;
109
110        let mut obj = serde_json::Map::new();
111
112        if let Some(author) = &self.author {
113            obj.insert("author".into(), author.build()?);
114        }
115
116        if let Some(footer) = &self.footer {
117            obj.insert("footer".into(), footer.build()?);
118        }
119
120        if let Some(title) = &self.title {
121            obj.insert("title".into(), Value::String(title.clone()));
122        }
123
124        if let Some(url) = &self.url {
125            obj.insert("url".into(), Value::String(url.clone()));
126        }
127
128        if let Some(description) = &self.description {
129            obj.insert("description".into(), Value::String(description.clone()));
130        }
131
132        if let Some(color) = &self.color {
133            obj.insert("color".into(), json!(color));
134        }
135
136        if let Some(thumbnail) = &self.thumbnail {
137            obj.insert("thumbnail".into(), json!({"url": thumbnail}));
138        }
139
140        if let Some(image) = &self.image {
141            obj.insert("image".into(), json!({"url": image}));
142        }
143
144        obj.insert("fields".into(), Value::Array(Vec::new()));
145        if let Value::Array(ref mut fields) = obj["fields"] {
146            for field in &self.fields {
147                fields.push(field.build()?);
148            }
149        };
150
151        // An embed can be sent if it contains at least one of these key
152        if obj.contains_key("author")
153            || obj.contains_key("footer")
154            || obj.contains_key("title")
155            || obj.contains_key("description")
156            || obj.contains_key("thumbnail")
157            || obj.contains_key("image")
158            || self.fields.len() > 0
159        {
160            Ok(Value::Object(obj))
161        } else {
162            Err(Error::InvalidEmbed)
163        }
164    }
165}