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}