serenity/builder/
create_message.rs

1use super::create_poll::Ready;
2#[cfg(feature = "http")]
3use super::{check_overflow, Builder};
4use super::{
5    CreateActionRow,
6    CreateAllowedMentions,
7    CreateAttachment,
8    CreateEmbed,
9    CreatePoll,
10    EditAttachments,
11};
12#[cfg(feature = "http")]
13use crate::constants;
14#[cfg(feature = "http")]
15use crate::http::CacheHttp;
16#[cfg(feature = "http")]
17use crate::internal::prelude::*;
18use crate::model::prelude::*;
19
20/// A builder to specify the contents of an send message request, primarily meant for use
21/// through [`ChannelId::send_message`].
22///
23/// There are three situations where different field requirements are present:
24///
25/// 1. When sending a message without embeds or stickers, [`Self::content`] is the only required
26///    field that is required to be set.
27/// 2. When sending an [`Self::embed`], no other field is required.
28/// 3. When sending stickers with [`Self::sticker_id`] or other sticker methods, no other field is
29///    required.
30///
31/// Note that if you only need to send the content of a message, without specifying other fields,
32/// then [`ChannelId::say`] may be a more preferable option.
33///
34/// # Examples
35///
36/// Sending a message with a content of `"test"` and applying text-to-speech:
37///
38/// ```rust,no_run
39/// use serenity::builder::{CreateEmbed, CreateMessage};
40/// use serenity::model::id::ChannelId;
41/// # use serenity::http::Http;
42/// # use std::sync::Arc;
43/// #
44/// # async fn run() {
45/// # let http: Arc<Http> = unimplemented!();
46/// # let channel_id = ChannelId::new(7);
47/// let embed = CreateEmbed::new().title("This is an embed").description("With a description");
48/// let builder = CreateMessage::new().content("test").tts(true).embed(embed);
49/// let _ = channel_id.send_message(&http, builder).await;
50/// # }
51/// ```
52///
53/// [Discord docs](https://discord.com/developers/docs/resources/channel#create-message)
54#[derive(Clone, Debug, Default, Serialize)]
55#[must_use]
56pub struct CreateMessage {
57    #[serde(skip_serializing_if = "Option::is_none")]
58    content: Option<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    nonce: Option<Nonce>,
61    tts: bool,
62    embeds: Vec<CreateEmbed>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    allowed_mentions: Option<CreateAllowedMentions>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    message_reference: Option<MessageReference>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    components: Option<Vec<CreateActionRow>>,
69    sticker_ids: Vec<StickerId>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    flags: Option<MessageFlags>,
72    pub(crate) attachments: EditAttachments,
73    enforce_nonce: bool,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    poll: Option<CreatePoll<super::create_poll::Ready>>,
76
77    // The following fields are handled separately.
78    #[serde(skip)]
79    reactions: Vec<ReactionType>,
80}
81
82impl CreateMessage {
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    #[cfg(feature = "http")]
88    fn check_length(&self) -> Result<()> {
89        if let Some(content) = &self.content {
90            check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT)
91                .map_err(|overflow| Error::Model(ModelError::MessageTooLong(overflow)))?;
92        }
93
94        check_overflow(self.embeds.len(), constants::EMBED_MAX_COUNT)
95            .map_err(|_| Error::Model(ModelError::EmbedAmount))?;
96        for embed in &self.embeds {
97            embed.check_length()?;
98        }
99
100        check_overflow(self.sticker_ids.len(), constants::STICKER_MAX_COUNT)
101            .map_err(|_| Error::Model(ModelError::StickerAmount))?;
102
103        Ok(())
104    }
105
106    /// Set the content of the message.
107    ///
108    /// **Note**: Message contents must be under 2000 unicode code points.
109    #[inline]
110    pub fn content(mut self, content: impl Into<String>) -> Self {
111        self.content = Some(content.into());
112        self
113    }
114
115    /// Add an embed for the message.
116    ///
117    /// **Note**: This will keep all existing embeds. Use [`Self::embed()`] to replace existing
118    /// embeds.
119    pub fn add_embed(mut self, embed: CreateEmbed) -> Self {
120        self.embeds.push(embed);
121        self
122    }
123
124    /// Add multiple embeds for the message.
125    ///
126    /// **Note**: This will keep all existing embeds. Use [`Self::embeds()`] to replace existing
127    /// embeds.
128    pub fn add_embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
129        self.embeds.extend(embeds);
130        self
131    }
132
133    /// Set an embed for the message.
134    ///
135    /// **Note**: This will replace all existing embeds. Use [`Self::add_embed()`] to keep existing
136    /// embeds.
137    pub fn embed(self, embed: CreateEmbed) -> Self {
138        self.embeds(vec![embed])
139    }
140
141    /// Set multiple embeds for the message.
142    ///
143    /// **Note**: This will replace all existing embeds. Use [`Self::add_embeds()`] to keep existing
144    /// embeds.
145    pub fn embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
146        self.embeds = embeds;
147        self
148    }
149
150    /// Set whether the message is text-to-speech.
151    ///
152    /// Think carefully before setting this to `true`.
153    ///
154    /// Defaults to `false`.
155    pub fn tts(mut self, tts: bool) -> Self {
156        self.tts = tts;
157        self
158    }
159
160    /// Adds a list of reactions to create after the message's sent.
161    #[inline]
162    pub fn reactions<R: Into<ReactionType>>(
163        mut self,
164        reactions: impl IntoIterator<Item = R>,
165    ) -> Self {
166        self.reactions = reactions.into_iter().map(Into::into).collect();
167        self
168    }
169
170    /// Appends a file to the message.
171    ///
172    /// **Note**: Requires the [Attach Files] permission.
173    ///
174    /// [Attach Files]: Permissions::ATTACH_FILES
175    pub fn add_file(mut self, file: CreateAttachment) -> Self {
176        self.attachments = self.attachments.add(file);
177        self
178    }
179
180    /// Appends a list of files to the message.
181    ///
182    /// **Note**: Requires the [Attach Files] permission.
183    ///
184    /// [Attach Files]: Permissions::ATTACH_FILES
185    pub fn add_files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
186        for file in files {
187            self.attachments = self.attachments.add(file);
188        }
189        self
190    }
191
192    /// Sets a list of files to include in the message.
193    ///
194    /// Calling this multiple times will overwrite the file list. To append files, call
195    /// [`Self::add_file`] or [`Self::add_files`] instead.
196    ///
197    /// **Note**: Requires the [Attach Files] permission.
198    ///
199    /// [Attach Files]: Permissions::ATTACH_FILES
200    pub fn files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
201        self.attachments = EditAttachments::new();
202        self.add_files(files)
203    }
204
205    /// Set the allowed mentions for the message.
206    pub fn allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
207        self.allowed_mentions = Some(allowed_mentions);
208        self
209    }
210
211    /// Set the message this reply or forward is referring to.
212    pub fn reference_message(mut self, reference: impl Into<MessageReference>) -> Self {
213        self.message_reference = Some(reference.into());
214        self
215    }
216
217    /// Sets the components of this message.
218    pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
219        self.components = Some(components);
220        self
221    }
222    super::button_and_select_menu_convenience_methods!(self.components);
223
224    /// Sets the flags for the message.
225    pub fn flags(mut self, flags: MessageFlags) -> Self {
226        self.flags = Some(flags);
227        self
228    }
229
230    /// Sets a single sticker ID to include in the message.
231    ///
232    /// **Note**: This will replace all existing stickers. Use [`Self::add_sticker_id()`] to keep
233    /// existing stickers.
234    pub fn sticker_id(self, sticker_id: impl Into<StickerId>) -> Self {
235        self.sticker_ids(vec![sticker_id.into()])
236    }
237
238    /// Sets a list of sticker IDs to include in the message.
239    ///
240    /// **Note**: There can be a maximum of 3 stickers in a message.
241    ///
242    /// **Note**: This will replace all existing stickers. Use [`Self::add_sticker_id()`] or
243    /// [`Self::add_sticker_ids()`] to keep existing stickers.
244    pub fn sticker_ids<T: Into<StickerId>>(
245        mut self,
246        sticker_ids: impl IntoIterator<Item = T>,
247    ) -> Self {
248        self.sticker_ids = sticker_ids.into_iter().map(Into::into).collect();
249        self
250    }
251
252    /// Add a sticker ID for the message.
253    ///
254    /// **Note**: There can be a maximum of 3 stickers in a message.
255    ///
256    /// **Note**: This will keep all existing stickers. Use [`Self::sticker_id()`] to replace
257    /// existing sticker.
258    pub fn add_sticker_id(mut self, sticker_id: impl Into<StickerId>) -> Self {
259        self.sticker_ids.push(sticker_id.into());
260        self
261    }
262
263    /// Add multiple sticker IDs for the message.
264    ///
265    /// **Note**: There can be a maximum of 3 stickers in a message.
266    ///
267    /// **Note**: This will keep all existing stickers. Use [`Self::sticker_ids()`] to replace
268    /// existing stickers.
269    pub fn add_sticker_ids<T: Into<StickerId>>(
270        mut self,
271        sticker_ids: impl IntoIterator<Item = T>,
272    ) -> Self {
273        for sticker_id in sticker_ids {
274            self = self.add_sticker_id(sticker_id);
275        }
276        self
277    }
278
279    /// Can be used to verify a message was sent (up to 25 characters). Value will appear in
280    /// [`Message::nonce`]
281    ///
282    /// See [`Self::enforce_nonce`] if you would like discord to perform de-duplication.
283    pub fn nonce(mut self, nonce: Nonce) -> Self {
284        self.nonce = Some(nonce);
285        self
286    }
287
288    /// If true and [`Self::nonce`] is provided, it will be checked for uniqueness in the past few
289    /// minutes. If another message was created by the same author with the same nonce, that
290    /// message will be returned and no new message will be created.
291    pub fn enforce_nonce(mut self, enforce_nonce: bool) -> Self {
292        self.enforce_nonce = enforce_nonce;
293        self
294    }
295
296    /// Sets the [`Poll`] for this message.
297    pub fn poll(mut self, poll: CreatePoll<Ready>) -> Self {
298        self.poll = Some(poll);
299        self
300    }
301}
302
303#[cfg(feature = "http")]
304#[async_trait::async_trait]
305impl Builder for CreateMessage {
306    type Context<'ctx> = (ChannelId, Option<GuildId>);
307    type Built = Message;
308
309    /// Send a message to the channel.
310    ///
311    /// **Note**: Requires the [Send Messages] permission. Additionally, attaching files requires
312    /// the [Attach Files] permission.
313    ///
314    /// **Note**: Message contents must be under 2000 unicode code points, and embeds must be under
315    /// 6000 code points.
316    ///
317    /// # Errors
318    ///
319    /// Returns a [`ModelError::MessageTooLong`] if the message contents are over the above limits.
320    ///
321    /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
322    /// lacks permission. Otherwise returns [`Error::Http`], as well as if invalid data is given.
323    ///
324    /// [Send Messages]: Permissions::SEND_MESSAGES
325    /// [Attach Files]: Permissions::ATTACH_FILES
326    async fn execute(
327        mut self,
328        cache_http: impl CacheHttp,
329        (channel_id, guild_id): Self::Context<'_>,
330    ) -> Result<Self::Built> {
331        #[cfg(feature = "cache")]
332        {
333            let mut req = Permissions::SEND_MESSAGES;
334            if !self.attachments.is_empty() {
335                req |= Permissions::ATTACH_FILES;
336            }
337            if let Some(cache) = cache_http.cache() {
338                crate::utils::user_has_perms_cache(cache, channel_id, req)?;
339            }
340        }
341
342        self.check_length()?;
343
344        let http = cache_http.http();
345
346        let files = self.attachments.take_files();
347        if self.allowed_mentions.is_none() {
348            self.allowed_mentions.clone_from(&http.default_allowed_mentions);
349        }
350
351        #[cfg_attr(not(feature = "cache"), allow(unused_mut))]
352        let mut message = http.send_message(channel_id, files, &self).await?;
353
354        for reaction in self.reactions {
355            http.create_reaction(channel_id, message.id, &reaction).await?;
356        }
357
358        // HTTP sent Messages don't have guild_id set, so we fill it in ourselves by best effort
359        if message.guild_id.is_none() {
360            // If we were called from GuildChannel, we can fill in the GuildId ourselves.
361            message.guild_id = guild_id;
362        }
363
364        Ok(message)
365    }
366}