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