serenity/builder/
create_embed.rs

1//! Developer note:
2//!
3//! This is a set of embed builders for rich embeds.
4//!
5//! These are used in the [`ChannelId::send_message`] and [`ExecuteWebhook::embeds`] methods, both
6//! as part of builders.
7//!
8//! The only builder that should be exposed is [`CreateEmbed`]. The rest of these have no real
9//! reason for being exposed, but are for completeness' sake.
10//!
11//! Documentation for embeds can be found [here].
12//!
13//! [`ChannelId::send_message`]: crate::model::id::ChannelId::send_message
14//! [`ExecuteWebhook::embeds`]: crate::builder::ExecuteWebhook::embeds
15//! [here]: https://discord.com/developers/docs/resources/channel#embed-object
16
17#[cfg(feature = "http")]
18use crate::internal::prelude::*;
19use crate::model::prelude::*;
20
21/// A builder to create an embed in a message
22///
23/// [Discord docs](https://discord.com/developers/docs/resources/channel#embed-object)
24#[derive(Clone, Debug, Serialize, PartialEq)]
25#[must_use]
26pub struct CreateEmbed(Embed);
27
28impl CreateEmbed {
29    /// Equivalent to [`Self::default`].
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Set the author of the embed.
35    ///
36    /// Refer to the documentation for [`CreateEmbedAuthor`] for more information.
37    pub fn author(mut self, author: CreateEmbedAuthor) -> Self {
38        self.0.author = Some(author.0);
39        self
40    }
41
42    /// Set the colour of the left-hand side of the embed.
43    ///
44    /// This is an alias of [`Self::colour`].
45    #[inline]
46    pub fn color<C: Into<Colour>>(self, colour: C) -> Self {
47        self.colour(colour)
48    }
49
50    /// Set the colour of the left-hand side of the embed.
51    #[inline]
52    pub fn colour<C: Into<Colour>>(mut self, colour: C) -> Self {
53        self.0.colour = Some(colour.into());
54        self
55    }
56
57    /// Set the description of the embed.
58    ///
59    /// **Note**: This can't be longer than 4096 characters.
60    #[inline]
61    pub fn description(mut self, description: impl Into<String>) -> Self {
62        self.0.description = Some(description.into());
63        self
64    }
65
66    /// Set a field. Note that this will not overwrite other fields, and will add to them.
67    ///
68    /// **Note**: Maximum amount of characters you can put is 256 in a field name and 1024 in a
69    /// field value.
70    #[inline]
71    pub fn field(
72        mut self,
73        name: impl Into<String>,
74        value: impl Into<String>,
75        inline: bool,
76    ) -> Self {
77        self.0.fields.push(EmbedField::new(name, value, inline));
78        self
79    }
80
81    /// Adds multiple fields at once.
82    ///
83    /// This is sugar to reduce the need of calling [`Self::field`] manually multiple times.
84    pub fn fields<N, V>(mut self, fields: impl IntoIterator<Item = (N, V, bool)>) -> Self
85    where
86        N: Into<String>,
87        V: Into<String>,
88    {
89        let fields =
90            fields.into_iter().map(|(name, value, inline)| EmbedField::new(name, value, inline));
91        self.0.fields.extend(fields);
92        self
93    }
94
95    /// Set the footer of the embed.
96    ///
97    /// Refer to the documentation for [`CreateEmbedFooter`] for more information.
98    pub fn footer(mut self, footer: CreateEmbedFooter) -> Self {
99        self.0.footer = Some(footer.0);
100        self
101    }
102
103    /// Set the image associated with the embed.
104    ///
105    /// Refer [Discord Documentation](https://discord.com/developers/docs/reference#uploading-files)
106    /// for rules on naming local attachments.
107    #[inline]
108    pub fn image(mut self, url: impl Into<String>) -> Self {
109        self.0.image = Some(EmbedImage {
110            url: url.into(),
111            proxy_url: None,
112            height: None,
113            width: None,
114        });
115        self
116    }
117
118    /// Set the thumbnail of the embed.
119    #[inline]
120    pub fn thumbnail(mut self, url: impl Into<String>) -> Self {
121        self.0.thumbnail = Some(EmbedThumbnail {
122            url: url.into(),
123            proxy_url: None,
124            height: None,
125            width: None,
126        });
127        self
128    }
129
130    /// Set the timestamp.
131    ///
132    /// See the documentation of [`Timestamp`] for more information.
133    ///
134    /// # Examples
135    ///
136    /// Passing a string timestamp:
137    ///
138    /// ```rust
139    /// # use serenity::builder::CreateEmbed;
140    /// # use serenity::model::Timestamp;
141    /// let timestamp: Timestamp = "2004-06-08T16:04:23Z".parse().expect("Invalid timestamp!");
142    /// let embed = CreateEmbed::new().title("hello").timestamp(timestamp);
143    /// ```
144    #[inline]
145    pub fn timestamp<T: Into<Timestamp>>(mut self, timestamp: T) -> Self {
146        self.0.timestamp = Some(timestamp.into());
147        self
148    }
149
150    /// Set the title of the embed.
151    #[inline]
152    pub fn title(mut self, title: impl Into<String>) -> Self {
153        self.0.title = Some(title.into());
154        self
155    }
156
157    /// Set the URL to direct to when clicking on the title.
158    #[inline]
159    pub fn url(mut self, url: impl Into<String>) -> Self {
160        self.0.url = Some(url.into());
161        self
162    }
163
164    /// Same as calling [`Self::image`] with "attachment://filename.(jpg, png)".
165    ///
166    /// Note however, you have to be sure you set an attachment (with [`ChannelId::send_files`])
167    /// with the provided filename. Or else this won't work.
168    ///
169    /// Refer [`Self::image`] for rules on naming local attachments.
170    ///
171    /// [`ChannelId::send_files`]: crate::model::id::ChannelId::send_files
172    #[inline]
173    pub fn attachment(self, filename: impl Into<String>) -> Self {
174        let mut filename = filename.into();
175        filename.insert_str(0, "attachment://");
176        self.image(filename)
177    }
178
179    #[cfg(feature = "http")]
180    pub(super) fn check_length(&self) -> Result<()> {
181        let mut length = 0;
182        if let Some(ref author) = self.0.author {
183            length += author.name.chars().count();
184        }
185
186        if let Some(ref description) = self.0.description {
187            length += description.chars().count();
188        }
189
190        for field in &self.0.fields {
191            length += field.name.chars().count();
192            length += field.value.chars().count();
193        }
194
195        if let Some(ref footer) = self.0.footer {
196            length += footer.text.chars().count();
197        }
198
199        if let Some(ref title) = self.0.title {
200            length += title.chars().count();
201        }
202
203        super::check_overflow(length, crate::constants::EMBED_MAX_LENGTH)
204            .map_err(|overflow| Error::Model(ModelError::EmbedTooLarge(overflow)))
205    }
206}
207
208impl Default for CreateEmbed {
209    /// Creates a builder with default values, setting the `type` to `rich`.
210    fn default() -> Self {
211        Self(Embed {
212            fields: Vec::new(),
213            description: None,
214            thumbnail: None,
215            timestamp: None,
216            kind: Some("rich".into()),
217            author: None,
218            colour: None,
219            footer: None,
220            image: None,
221            title: None,
222            url: None,
223            video: None,
224            provider: None,
225        })
226    }
227}
228
229impl From<Embed> for CreateEmbed {
230    fn from(embed: Embed) -> Self {
231        Self(embed)
232    }
233}
234
235/// A builder to create the author data of an embed. See [`CreateEmbed::author`]
236#[derive(Clone, Debug, Serialize)]
237#[must_use]
238pub struct CreateEmbedAuthor(EmbedAuthor);
239
240impl CreateEmbedAuthor {
241    /// Creates an author object with the given name, leaving all other fields empty.
242    pub fn new(name: impl Into<String>) -> Self {
243        Self(EmbedAuthor {
244            name: name.into(),
245            icon_url: None,
246            url: None,
247            // Has no builder method because I think this field is only relevant when receiving (?)
248            proxy_icon_url: None,
249        })
250    }
251
252    /// Set the author's name, replacing the current value as set in [`Self::new`].
253    pub fn name(mut self, name: impl Into<String>) -> Self {
254        self.0.name = name.into();
255        self
256    }
257
258    /// Set the URL of the author's icon.
259    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
260        self.0.icon_url = Some(icon_url.into());
261        self
262    }
263
264    /// Set the author's URL.
265    pub fn url(mut self, url: impl Into<String>) -> Self {
266        self.0.url = Some(url.into());
267        self
268    }
269}
270
271impl From<EmbedAuthor> for CreateEmbedAuthor {
272    fn from(author: EmbedAuthor) -> Self {
273        Self(author)
274    }
275}
276
277#[cfg(feature = "model")]
278impl From<User> for CreateEmbedAuthor {
279    fn from(user: User) -> Self {
280        let avatar_icon = user.face();
281        Self::new(user.name).icon_url(avatar_icon)
282    }
283}
284
285#[cfg(feature = "model")]
286impl From<&User> for CreateEmbedAuthor {
287    fn from(user: &User) -> Self {
288        let avatar_icon = user.face();
289        Self::new(user.name.clone()).icon_url(avatar_icon)
290    }
291}
292
293/// A builder to create the footer data for an embed. See [`CreateEmbed::footer`]
294#[derive(Clone, Debug, Serialize)]
295#[must_use]
296pub struct CreateEmbedFooter(EmbedFooter);
297
298impl CreateEmbedFooter {
299    /// Creates a new footer object with the given text, leaving all other fields empty.
300    pub fn new(text: impl Into<String>) -> Self {
301        Self(EmbedFooter {
302            text: text.into(),
303            icon_url: None,
304            // Has no builder method because I think this field is only relevant when receiving (?)
305            proxy_icon_url: None,
306        })
307    }
308
309    /// Set the footer's text, replacing the current value as set in [`Self::new`].
310    pub fn text(mut self, text: impl Into<String>) -> Self {
311        self.0.text = text.into();
312        self
313    }
314
315    /// Set the icon URL's value.
316    ///
317    /// Refer [`CreateEmbed::image`] for rules on naming local attachments.
318    pub fn icon_url(mut self, icon_url: impl Into<String>) -> Self {
319        self.0.icon_url = Some(icon_url.into());
320        self
321    }
322}
323
324impl From<EmbedFooter> for CreateEmbedFooter {
325    fn from(footer: EmbedFooter) -> Self {
326        Self(footer)
327    }
328}