serenity/builder/edit_message.rs
1#[cfg(feature = "http")]
2use super::{check_overflow, Builder};
3use super::{
4 CreateActionRow,
5 CreateAllowedMentions,
6 CreateAttachment,
7 CreateEmbed,
8 EditAttachments,
9};
10#[cfg(feature = "http")]
11use crate::constants;
12#[cfg(feature = "http")]
13use crate::http::CacheHttp;
14#[cfg(feature = "http")]
15use crate::internal::prelude::*;
16use crate::model::prelude::*;
17
18/// A builder to specify the fields to edit in an existing message.
19///
20/// # Examples
21///
22/// Editing the content of a [`Message`] to `"hello"`:
23///
24/// ```rust,no_run
25/// # use serenity::builder::EditMessage;
26/// # use serenity::model::channel::Message;
27/// # use serenity::model::id::ChannelId;
28/// # use serenity::http::CacheHttp;
29///
30/// # async fn example(ctx: impl CacheHttp, mut message: Message) -> Result<(), Box<dyn std::error::Error>> {
31/// let builder = EditMessage::new().content("hello");
32/// message.edit(ctx, builder).await?;
33/// # Ok(())
34/// # }
35/// ```
36///
37/// [Discord docs](https://discord.com/developers/docs/resources/channel#edit-message)
38#[derive(Clone, Debug, Default, Serialize, PartialEq)]
39#[must_use]
40pub struct EditMessage {
41 #[serde(skip_serializing_if = "Option::is_none")]
42 content: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 embeds: Option<Vec<CreateEmbed>>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 flags: Option<MessageFlags>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 allowed_mentions: Option<CreateAllowedMentions>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 components: Option<Vec<CreateActionRow>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 attachments: Option<EditAttachments>,
53}
54
55impl EditMessage {
56 /// Equivalent to [`Self::default`].
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 #[cfg(feature = "http")]
62 fn check_length(&self) -> Result<()> {
63 if let Some(content) = &self.content {
64 check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT)
65 .map_err(|overflow| Error::Model(ModelError::MessageTooLong(overflow)))?;
66 }
67
68 if let Some(embeds) = &self.embeds {
69 check_overflow(embeds.len(), constants::EMBED_MAX_COUNT)
70 .map_err(|_| Error::Model(ModelError::EmbedAmount))?;
71 for embed in embeds {
72 embed.check_length()?;
73 }
74 }
75
76 Ok(())
77 }
78
79 /// Set the content of the message.
80 ///
81 /// **Note**: Message contents must be under 2000 unicode code points.
82 #[inline]
83 pub fn content(mut self, content: impl Into<String>) -> Self {
84 self.content = Some(content.into());
85 self
86 }
87
88 /// Add an embed for the message.
89 ///
90 /// **Note**: This will keep all existing embeds. Use [`Self::embed()`] to replace existing
91 /// embeds.
92 pub fn add_embed(mut self, embed: CreateEmbed) -> Self {
93 self.embeds.get_or_insert_with(Vec::new).push(embed);
94 self
95 }
96
97 /// Add multiple embeds for the message.
98 ///
99 /// **Note**: This will keep all existing embeds. Use [`Self::embeds()`] to replace existing
100 /// embeds.
101 pub fn add_embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
102 self.embeds.get_or_insert_with(Vec::new).extend(embeds);
103 self
104 }
105
106 /// Set an embed for the message.
107 ///
108 /// **Note**: This will replace all existing embeds. Use [`Self::add_embed()`] to keep existing
109 /// embeds.
110 pub fn embed(self, embed: CreateEmbed) -> Self {
111 self.embeds(vec![embed])
112 }
113
114 /// Set multiple embeds for the message.
115 ///
116 /// **Note**: This will replace all existing embeds. Use [`Self::add_embeds()`] to keep existing
117 /// embeds.
118 pub fn embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
119 self.embeds = Some(embeds);
120 self
121 }
122
123 /// Suppress or unsuppress embeds in the message, this includes those generated by Discord
124 /// themselves.
125 ///
126 /// If this is sent directly after posting the message, there is a small chance Discord hasn't
127 /// yet fully parsed the contained links and generated the embeds, so this embed suppression
128 /// request has no effect. To mitigate this, you can defer the embed suppression until the
129 /// embeds have loaded:
130 ///
131 /// ```rust,no_run
132 /// # use serenity::all::*;
133 /// # #[cfg(feature = "collector")]
134 /// # async fn test(ctx: &Context, channel_id: ChannelId) -> Result<(), Error> {
135 /// use std::time::Duration;
136 ///
137 /// use futures::StreamExt;
138 ///
139 /// let mut msg = channel_id.say(ctx, "<link that spawns an embed>").await?;
140 ///
141 /// // When the embed appears, a MessageUpdate event is sent and we suppress the embed.
142 /// // No MessageUpdate event is sent if the message contains no embeddable link or if the link
143 /// // has been posted before and is still cached in Discord's servers (in which case the
144 /// // embed appears immediately), no MessageUpdate event is sent. To not wait forever in those
145 /// // cases, a timeout of 2000ms was added.
146 /// let msg_id = msg.id;
147 /// let mut message_updates = serenity::collector::collect(&ctx.shard, move |ev| match ev {
148 /// Event::MessageUpdate(x) if x.id == msg_id => Some(()),
149 /// _ => None,
150 /// });
151 /// let _ = tokio::time::timeout(Duration::from_millis(2000), message_updates.next()).await;
152 /// msg.edit(&ctx, EditMessage::new().suppress_embeds(true)).await?;
153 /// # Ok(()) }
154 /// ```
155 pub fn suppress_embeds(mut self, suppress: bool) -> Self {
156 // At time of writing, only `SUPPRESS_EMBEDS` can be set/unset when editing messages. See
157 // for details: https://discord.com/developers/docs/resources/channel#edit-message-jsonform-params
158 let flags =
159 suppress.then_some(MessageFlags::SUPPRESS_EMBEDS).unwrap_or_else(MessageFlags::empty);
160
161 self.flags = Some(flags);
162 self
163 }
164
165 /// Set the allowed mentions for the message.
166 pub fn allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
167 self.allowed_mentions = Some(allowed_mentions);
168 self
169 }
170
171 /// Sets the components of this message.
172 pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
173 self.components = Some(components);
174 self
175 }
176 super::button_and_select_menu_convenience_methods!(self.components);
177
178 /// Sets the flags for the message.
179 pub fn flags(mut self, flags: MessageFlags) -> Self {
180 self.flags = Some(flags);
181 self
182 }
183
184 /// Sets attachments, see [`EditAttachments`] for more details.
185 pub fn attachments(mut self, attachments: EditAttachments) -> Self {
186 self.attachments = Some(attachments);
187 self
188 }
189
190 /// Adds a new attachment to the message.
191 ///
192 /// Resets existing attachments. See the documentation for [`EditAttachments`] for details.
193 pub fn new_attachment(mut self, attachment: CreateAttachment) -> Self {
194 let attachments = self.attachments.get_or_insert_with(Default::default);
195 self.attachments = Some(std::mem::take(attachments).add(attachment));
196 self
197 }
198
199 /// Shorthand for [`EditAttachments::keep`].
200 pub fn keep_existing_attachment(mut self, id: AttachmentId) -> Self {
201 let attachments = self.attachments.get_or_insert_with(Default::default);
202 self.attachments = Some(std::mem::take(attachments).keep(id));
203 self
204 }
205
206 /// Shorthand for [`EditAttachments::remove`].
207 pub fn remove_existing_attachment(mut self, id: AttachmentId) -> Self {
208 if let Some(attachments) = self.attachments {
209 self.attachments = Some(attachments.remove(id));
210 }
211 self
212 }
213
214 /// Shorthand for calling [`Self::attachments`] with [`EditAttachments::new`].
215 pub fn remove_all_attachments(mut self) -> Self {
216 self.attachments = Some(EditAttachments::new());
217 self
218 }
219}
220
221#[cfg(feature = "http")]
222#[async_trait::async_trait]
223impl Builder for EditMessage {
224 type Context<'ctx> = (ChannelId, MessageId, Option<UserId>);
225 type Built = Message;
226
227 /// Edits a message in the channel.
228 ///
229 /// **Note**: Message contents must be under 2000 unicode code points, and embeds must be under
230 /// 6000 code points.
231 ///
232 /// **Note**: Requires that the current user be the author of the message. Other users can only
233 /// call [`Self::suppress_embeds`], but additionally require the [Manage Messages] permission
234 /// to do so.
235 ///
236 /// **Note**: If any embeds or attachments are set, they will overwrite the existing contents
237 /// of the message, deleting existing embeds and attachments. Preserving them requires calling
238 /// [`Self::keep_existing_attachment`] in the case of attachments. In the case of embeds,
239 /// duplicate copies of the existing embeds must be sent. Luckily, [`CreateEmbed`] implements
240 /// [`From<Embed>`], so one can simply call `embed.into()`.
241 ///
242 /// # Errors
243 ///
244 /// Returns a [`ModelError::MessageTooLong`] if the message contents are over the above limits.
245 ///
246 /// Returns [`Error::Http`] if the user lacks permission, as well as if invalid data is given.
247 ///
248 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
249 /// [`From<Embed>`]: CreateEmbed#impl-From<Embed>
250 async fn execute(
251 mut self,
252 cache_http: impl CacheHttp,
253 ctx: Self::Context<'_>,
254 ) -> Result<Self::Built> {
255 self.check_length()?;
256
257 #[cfg(feature = "cache")]
258 if let Some(user_id) = ctx.2 {
259 if let Some(cache) = cache_http.cache() {
260 let reference_builder = EditMessage::new().suppress_embeds(true);
261
262 if user_id != cache.current_user().id && self != reference_builder {
263 return Err(Error::Model(ModelError::InvalidUser));
264 }
265 }
266 }
267
268 let files = self.attachments.as_mut().map_or(Vec::new(), |a| a.take_files());
269
270 let http = cache_http.http();
271 if self.allowed_mentions.is_none() {
272 self.allowed_mentions.clone_from(&http.default_allowed_mentions);
273 }
274
275 http.edit_message(ctx.0, ctx.1, &self, files).await
276 }
277}