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
#[cfg(feature = "http")]
use super::{check_overflow, Builder};
use super::{
CreateActionRow,
CreateAllowedMentions,
CreateAttachment,
CreateEmbed,
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 fields to edit in an existing message.
///
/// # Examples
///
/// Editing the content of a [`Message`] to `"hello"`:
///
/// ```rust,no_run
/// # use serenity::builder::EditMessage;
/// # use serenity::model::channel::Message;
/// # use serenity::model::id::ChannelId;
/// # use serenity::http::CacheHttp;
///
/// # async fn example(ctx: impl CacheHttp, mut message: Message) -> Result<(), Box<dyn std::error::Error>> {
/// let builder = EditMessage::new().content("hello");
/// message.edit(ctx, builder).await?;
/// # Ok(())
/// # }
/// ```
///
/// [Discord docs](https://discord.com/developers/docs/resources/channel#edit-message)
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[must_use]
pub struct EditMessage {
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
embeds: Option<Vec<CreateEmbed>>,
#[serde(skip_serializing_if = "Option::is_none")]
flags: Option<MessageFlags>,
#[serde(skip_serializing_if = "Option::is_none")]
allowed_mentions: Option<CreateAllowedMentions>,
#[serde(skip_serializing_if = "Option::is_none")]
components: Option<Vec<CreateActionRow>>,
#[serde(skip_serializing_if = "Option::is_none")]
attachments: Option<EditAttachments>,
}
impl EditMessage {
/// Equivalent to [`Self::default`].
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)))?;
}
if let Some(embeds) = &self.embeds {
check_overflow(embeds.len(), constants::EMBED_MAX_COUNT)
.map_err(|_| Error::Model(ModelError::EmbedAmount))?;
for embed in embeds {
embed.check_length()?;
}
}
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.get_or_insert_with(Vec::new).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.get_or_insert_with(Vec::new).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 = Some(embeds);
self
}
/// Suppress or unsuppress embeds in the message, this includes those generated by Discord
/// themselves.
///
/// If this is sent directly after posting the message, there is a small chance Discord hasn't
/// yet fully parsed the contained links and generated the embeds, so this embed suppression
/// request has no effect. To mitigate this, you can defer the embed suppression until the
/// embeds have loaded:
///
/// ```rust,no_run
/// # use serenity::all::*;
/// # #[cfg(feature = "collector")]
/// # async fn test(ctx: &Context, channel_id: ChannelId) -> Result<(), Error> {
/// use std::time::Duration;
///
/// use futures::StreamExt;
///
/// let mut msg = channel_id.say(ctx, "<link that spawns an embed>").await?;
///
/// // When the embed appears, a MessageUpdate event is sent and we suppress the embed.
/// // No MessageUpdate event is sent if the message contains no embeddable link or if the link
/// // has been posted before and is still cached in Discord's servers (in which case the
/// // embed appears immediately), no MessageUpdate event is sent. To not wait forever in those
/// // cases, a timeout of 2000ms was added.
/// let msg_id = msg.id;
/// let mut message_updates = serenity::collector::collect(&ctx.shard, move |ev| match ev {
/// Event::MessageUpdate(x) if x.id == msg_id => Some(()),
/// _ => None,
/// });
/// let _ = tokio::time::timeout(Duration::from_millis(2000), message_updates.next()).await;
/// msg.edit(&ctx, EditMessage::new().suppress_embeds(true)).await?;
/// # Ok(()) }
/// ```
pub fn suppress_embeds(mut self, suppress: bool) -> Self {
// See for details: https://discord.com/developers/docs/resources/message#edit-message-jsonform-params
self.flags
.get_or_insert(MessageFlags::empty())
.set(MessageFlags::SUPPRESS_EMBEDS, suppress);
self
}
/// Set the allowed mentions for the message.
pub fn allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
self.allowed_mentions = Some(allowed_mentions);
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 attachments, see [`EditAttachments`] for more details.
pub fn attachments(mut self, attachments: EditAttachments) -> Self {
self.attachments = Some(attachments);
self
}
/// Adds a new attachment to the message.
///
/// Resets existing attachments. See the documentation for [`EditAttachments`] for details.
pub fn new_attachment(mut self, attachment: CreateAttachment) -> Self {
let attachments = self.attachments.get_or_insert_with(Default::default);
self.attachments = Some(std::mem::take(attachments).add(attachment));
self
}
/// Shorthand for [`EditAttachments::keep`].
pub fn keep_existing_attachment(mut self, id: AttachmentId) -> Self {
let attachments = self.attachments.get_or_insert_with(Default::default);
self.attachments = Some(std::mem::take(attachments).keep(id));
self
}
/// Shorthand for [`EditAttachments::remove`].
pub fn remove_existing_attachment(mut self, id: AttachmentId) -> Self {
if let Some(attachments) = self.attachments {
self.attachments = Some(attachments.remove(id));
}
self
}
/// Shorthand for calling [`Self::attachments`] with [`EditAttachments::new`].
pub fn remove_all_attachments(mut self) -> Self {
self.attachments = Some(EditAttachments::new());
self
}
}
#[cfg(feature = "http")]
#[async_trait::async_trait]
impl Builder for EditMessage {
type Context<'ctx> = (ChannelId, MessageId, Option<UserId>);
type Built = Message;
/// Edits a message in the channel.
///
/// **Note**: Message contents must be under 2000 unicode code points, and embeds must be under
/// 6000 code points.
///
/// **Note**: Requires that the current user be the author of the message. Other users can only
/// call [`Self::suppress_embeds`], but additionally require the [Manage Messages] permission
/// to do so.
///
/// **Note**: If any embeds or attachments are set, they will overwrite the existing contents
/// of the message, deleting existing embeds and attachments. Preserving them requires calling
/// [`Self::keep_existing_attachment`] in the case of attachments. In the case of embeds,
/// duplicate copies of the existing embeds must be sent. Luckily, [`CreateEmbed`] implements
/// [`From<Embed>`], so one can simply call `embed.into()`.
///
/// # Errors
///
/// Returns a [`ModelError::MessageTooLong`] if the message contents are over the above limits.
///
/// Returns [`Error::Http`] if the user lacks permission, as well as if invalid data is given.
///
/// [Manage Messages]: Permissions::MANAGE_MESSAGES
/// [`From<Embed>`]: CreateEmbed#impl-From<Embed>
async fn execute(
mut self,
cache_http: impl CacheHttp,
ctx: Self::Context<'_>,
) -> Result<Self::Built> {
self.check_length()?;
#[cfg(feature = "cache")]
if let Some(user_id) = ctx.2 {
if let Some(cache) = cache_http.cache() {
let reference_builder = EditMessage::new().suppress_embeds(true);
if user_id != cache.current_user().id && self != reference_builder {
return Err(Error::Model(ModelError::InvalidUser));
}
}
}
let files = self.attachments.as_mut().map_or(Vec::new(), |a| a.take_files());
let http = cache_http.http();
if self.allowed_mentions.is_none() {
self.allowed_mentions.clone_from(&http.default_allowed_mentions);
}
http.edit_message(ctx.0, ctx.1, &self, files).await
}
}