serenity/model/channel/message.rs
1//! Models relating to Discord channels.
2
3#[cfg(feature = "model")]
4use std::fmt::Display;
5#[cfg(all(feature = "cache", feature = "model"))]
6use std::fmt::Write;
7
8#[cfg(all(feature = "model", feature = "utils"))]
9use crate::builder::{Builder, CreateAllowedMentions, CreateMessage, EditMessage};
10#[cfg(all(feature = "cache", feature = "model"))]
11use crate::cache::{Cache, GuildRef};
12#[cfg(feature = "collector")]
13use crate::collector::{
14 ComponentInteractionCollector,
15 ModalInteractionCollector,
16 ReactionCollector,
17};
18#[cfg(feature = "model")]
19use crate::constants;
20#[cfg(feature = "collector")]
21use crate::gateway::ShardMessenger;
22#[cfg(feature = "model")]
23use crate::http::{CacheHttp, Http};
24use crate::model::prelude::*;
25use crate::model::utils::{deserialize_components, discord_colours, StrOrInt};
26#[cfg(all(feature = "model", feature = "cache"))]
27use crate::utils;
28
29/// A representation of a message over a guild's text channel, a group, or a private channel.
30///
31/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object) with some
32/// [extra fields](https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields).
33#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
34#[derive(Clone, Debug, Default, Deserialize, Serialize)]
35#[non_exhaustive]
36pub struct Message {
37 /// The unique Id of the message. Can be used to calculate the creation date of the message.
38 pub id: MessageId,
39 /// The Id of the [`Channel`] that the message was sent to.
40 pub channel_id: ChannelId,
41 /// The user that sent the message.
42 pub author: User,
43 /// The content of the message.
44 pub content: String,
45 /// Initial message creation timestamp, calculated from its Id.
46 pub timestamp: Timestamp,
47 /// The timestamp of the last time the message was updated, if it was.
48 pub edited_timestamp: Option<Timestamp>,
49 /// Indicator of whether the command is to be played back via text-to-speech.
50 ///
51 /// In the client, this is done via the `/tts` slash command.
52 pub tts: bool,
53 /// Indicator of whether the message mentions everyone.
54 pub mention_everyone: bool,
55 /// Array of users mentioned in the message.
56 pub mentions: Vec<User>,
57 /// Array of [`Role`]s' Ids mentioned in the message.
58 pub mention_roles: Vec<RoleId>,
59 /// Channels specifically mentioned in this message.
60 ///
61 /// **Note**: Not all channel mentions in a message will appear in [`Self::mention_channels`].
62 /// Only textual channels that are visible to everyone in a lurkable guild will ever be
63 /// included.
64 ///
65 /// A lurkable guild is one that allows users to read public channels in a server without
66 /// actually joining the server. It also allows users to look at these channels without being
67 /// logged in to Discord.
68 ///
69 /// Only crossposted messages (via Channel Following) currently include
70 /// [`Self::mention_channels`] at all. If no mentions in the message meet these requirements,
71 /// this field will not be sent.
72 ///
73 /// [Refer to Discord's documentation for more information][discord-docs].
74 ///
75 /// [discord-docs]: https://discord.com/developers/docs/resources/channel#message-object
76 #[serde(default = "Vec::new")]
77 pub mention_channels: Vec<ChannelMention>,
78 /// An vector of the files attached to a message.
79 pub attachments: Vec<Attachment>,
80 /// Array of embeds sent with the message.
81 pub embeds: Vec<Embed>,
82 /// Array of reactions performed on the message.
83 #[serde(default)]
84 pub reactions: Vec<MessageReaction>,
85 /// Non-repeating number used for ensuring message order.
86 #[serde(default)]
87 pub nonce: Option<Nonce>,
88 /// Indicator of whether the message is pinned.
89 pub pinned: bool,
90 /// The Id of the webhook that sent this message, if one did.
91 pub webhook_id: Option<WebhookId>,
92 /// Indicator of the type of message this is, i.e. whether it is a regular message or a system
93 /// message.
94 #[serde(rename = "type")]
95 pub kind: MessageType,
96 /// Sent with Rich Presence-related chat embeds.
97 pub activity: Option<MessageActivity>,
98 /// Sent with Rich Presence-related chat embeds.
99 pub application: Option<MessageApplication>,
100 /// If the message is an Interaction or application-owned webhook, this is the id of the
101 /// application.
102 pub application_id: Option<ApplicationId>,
103 /// Reference data sent with crossposted messages.
104 pub message_reference: Option<MessageReference>,
105 /// Bit flags describing extra features of the message.
106 pub flags: Option<MessageFlags>,
107 /// The message that was replied to using this message.
108 pub referenced_message: Option<Box<Message>>, // Boxed to avoid recursion
109 /// An array of message snapshots, known as forwarded messages.
110 #[serde(default, deserialize_with = "deserialize_snapshots")]
111 pub message_snapshots: Vec<MessageSnapshot>,
112 #[cfg_attr(not(ignore_serenity_deprecated), deprecated = "Use interaction_metadata")]
113 pub interaction: Option<Box<MessageInteraction>>,
114 /// Sent if the message is a response to an [`Interaction`].
115 ///
116 /// [`Interaction`]: crate::model::application::Interaction
117 pub interaction_metadata: Option<Box<MessageInteractionMetadata>>,
118 /// The thread that was started from this message, includes thread member object.
119 pub thread: Option<GuildChannel>,
120 /// The components of this message
121 #[serde(default, deserialize_with = "deserialize_components")]
122 pub components: Vec<ActionRow>,
123 /// Array of message sticker item objects.
124 #[serde(default)]
125 pub sticker_items: Vec<StickerItem>,
126 /// A generally increasing integer (there may be gaps or duplicates) that represents the
127 /// approximate position of the message in a thread, it can be used to estimate the relative
128 /// position of the message in a thread in company with total_message_sent on parent thread.
129 pub position: Option<u64>,
130 /// Data of the role subscription purchase or renewal that prompted this
131 /// [`MessageType::RoleSubscriptionPurchase`] message.
132 pub role_subscription_data: Option<RoleSubscriptionData>,
133 // Field omitted: stickers (it's deprecated by Discord)
134 /// The Id of the [`Guild`] that the message was sent in. This value will only be present if
135 /// this message was received over the gateway, therefore **do not use this to check if message
136 /// is in DMs**, it is not a reliable method.
137 // TODO: maybe introduce an `enum MessageLocation { Dm, Guild(GuildId) }` and store
138 // `Option<MessageLocation` here. Instead of None being ambiguous (is it in DMs? Or do we just
139 // not know because HTTP retrieved Messages don't have guild ID?), we'd set
140 // Some(MessageLocation::Dm) in gateway and None in HTTP.
141 pub guild_id: Option<GuildId>,
142 /// A partial amount of data about the user's member data
143 ///
144 /// Only present in [`MessageCreateEvent`].
145 pub member: Option<Box<PartialMember>>,
146 /// A poll that may be attached to a message.
147 ///
148 /// This is often omitted, so is boxed to improve memory usage.
149 ///
150 /// Only present in [`MessageCreateEvent`].
151 pub poll: Option<Box<Poll>>,
152}
153
154#[cfg(feature = "model")]
155impl Message {
156 /// Crossposts this message.
157 ///
158 /// Requires either to be the message author or to have manage [Manage Messages] permissions on
159 /// this channel.
160 ///
161 /// **Note**: Only available on news channels.
162 ///
163 /// # Errors
164 ///
165 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
166 /// does not have the required permissions.
167 ///
168 /// Returns a [`ModelError::MessageAlreadyCrossposted`] if the message has already been
169 /// crossposted.
170 ///
171 /// Returns a [`ModelError::CannotCrosspostMessage`] if the message cannot be crossposted.
172 ///
173 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
174 pub async fn crosspost(&self, cache_http: impl CacheHttp) -> Result<Message> {
175 #[cfg(feature = "cache")]
176 {
177 if let Some(cache) = cache_http.cache() {
178 if self.author.id != cache.current_user().id && self.guild_id.is_some() {
179 utils::user_has_perms_cache(
180 cache,
181 self.channel_id,
182 Permissions::MANAGE_MESSAGES,
183 )?;
184 }
185 }
186 }
187
188 if let Some(flags) = self.flags {
189 if flags.contains(MessageFlags::CROSSPOSTED) {
190 return Err(Error::Model(ModelError::MessageAlreadyCrossposted));
191 } else if flags.contains(MessageFlags::IS_CROSSPOST)
192 || self.kind != MessageType::Regular
193 {
194 return Err(Error::Model(ModelError::CannotCrosspostMessage));
195 }
196 }
197
198 self.channel_id.crosspost(cache_http.http(), self.id).await
199 }
200
201 /// First attempts to find a [`Channel`] by its Id in the cache, upon failure requests it via
202 /// the REST API.
203 ///
204 /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon owning the
205 /// required permissions the HTTP-request will be issued.
206 ///
207 /// # Errors
208 ///
209 /// Can return an error if the HTTP request fails.
210 #[inline]
211 pub async fn channel(&self, cache_http: impl CacheHttp) -> Result<Channel> {
212 self.channel_id.to_channel(cache_http).await
213 }
214
215 /// A util function for determining whether this message was sent by someone else, or the bot.
216 #[cfg(feature = "cache")]
217 #[deprecated = "Check Message::author is equal to Cache::current_user"]
218 pub fn is_own(&self, cache: impl AsRef<Cache>) -> bool {
219 self.author.id == cache.as_ref().current_user().id
220 }
221
222 /// Calculates the permissions of the message author in the current channel.
223 ///
224 /// This handles the [`Permissions::SEND_MESSAGES_IN_THREADS`] permission for threads, setting
225 /// [`Permissions::SEND_MESSAGES`] accordingly if this message was sent in a thread.
226 ///
227 /// This may return `None` if:
228 /// - The [`Cache`] does not have the current [`Guild`]
229 /// - The [`Guild`] does not have the current channel cached (should never happen).
230 /// - This message is not from [`MessageCreateEvent`] and the author's [`Member`] cannot be
231 /// found in [`Guild#structfield.members`].
232 #[cfg(feature = "cache")]
233 pub fn author_permissions(&self, cache: impl AsRef<Cache>) -> Option<Permissions> {
234 let Some(guild_id) = self.guild_id else {
235 return Some(Permissions::dm_permissions());
236 };
237
238 let guild = cache.as_ref().guild(guild_id)?;
239 let (channel, is_thread) = if let Some(channel) = guild.channels.get(&self.channel_id) {
240 (channel, false)
241 } else if let Some(thread) = guild.threads.iter().find(|th| th.id == self.channel_id) {
242 (thread, true)
243 } else {
244 return None;
245 };
246
247 let mut permissions = if let Some(member) = &self.member {
248 guild.partial_member_permissions_in(channel, self.author.id, member)
249 } else {
250 guild.user_permissions_in(channel, guild.members.get(&self.author.id)?)
251 };
252
253 if is_thread {
254 permissions.set(Permissions::SEND_MESSAGES, permissions.send_messages_in_threads());
255 }
256
257 Some(permissions)
258 }
259
260 /// Deletes the message.
261 ///
262 /// **Note**: The logged in user must either be the author of the message or have the [Manage
263 /// Messages] permission.
264 ///
265 /// # Errors
266 ///
267 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
268 /// current user does not have the required permissions.
269 ///
270 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
271 pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
272 #[cfg(feature = "cache")]
273 {
274 if let Some(cache) = cache_http.cache() {
275 if self.author.id != cache.current_user().id {
276 utils::user_has_perms_cache(
277 cache,
278 self.channel_id,
279 Permissions::MANAGE_MESSAGES,
280 )?;
281 }
282 }
283 }
284
285 self.channel_id.delete_message(cache_http.http(), self.id).await
286 }
287
288 /// Deletes all of the [`Reaction`]s associated with the message.
289 ///
290 /// **Note**: Requires the [Manage Messages] permission.
291 ///
292 /// # Errors
293 ///
294 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
295 /// current user does not have the required permissions.
296 ///
297 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
298 pub async fn delete_reactions(&self, cache_http: impl CacheHttp) -> Result<()> {
299 #[cfg(feature = "cache")]
300 {
301 if let Some(cache) = cache_http.cache() {
302 utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
303 }
304 }
305
306 self.channel_id.delete_reactions(cache_http.http(), self.id).await
307 }
308
309 /// Deletes the given [`Reaction`] from the message.
310 ///
311 /// **Note**: Requires the [Manage Messages] permission, _if_ the current user did not perform
312 /// the reaction.
313 ///
314 /// # Errors
315 ///
316 /// Returns [`Error::Http`] if the current user did not perform the reaction, or lacks
317 /// permission.
318 ///
319 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
320 #[inline]
321 pub async fn delete_reaction(
322 &self,
323 http: impl AsRef<Http>,
324 user_id: Option<UserId>,
325 reaction_type: impl Into<ReactionType>,
326 ) -> Result<()> {
327 self.channel_id.delete_reaction(http, self.id, user_id, reaction_type).await
328 }
329
330 /// Deletes all of the [`Reaction`]s of a given emoji associated with the message.
331 ///
332 /// **Note**: Requires the [Manage Messages] permission.
333 ///
334 /// # Errors
335 ///
336 /// If the `cache` feature is enabled, then returns a [`ModelError::InvalidPermissions`] if the
337 /// current user does not have the required permissions.
338 ///
339 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
340 pub async fn delete_reaction_emoji(
341 &self,
342 cache_http: impl CacheHttp,
343 reaction_type: impl Into<ReactionType>,
344 ) -> Result<()> {
345 #[cfg(feature = "cache")]
346 {
347 if let Some(cache) = cache_http.cache() {
348 utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
349 }
350 }
351
352 cache_http
353 .http()
354 .as_ref()
355 .delete_message_reaction_emoji(self.channel_id, self.id, &reaction_type.into())
356 .await
357 }
358
359 /// Edits this message, replacing the original content with new content.
360 ///
361 /// Message editing preserves all unchanged message data, with some exceptions for embeds and
362 /// attachments.
363 ///
364 /// **Note**: In most cases requires that the current user be the author of the message.
365 ///
366 /// Refer to the documentation for [`EditMessage`] for information regarding content
367 /// restrictions and requirements.
368 ///
369 /// # Examples
370 ///
371 /// Edit a message with new content:
372 ///
373 /// ```rust,no_run
374 /// # use serenity::builder::EditMessage;
375 /// # use serenity::model::channel::Message;
376 /// # use serenity::model::id::ChannelId;
377 /// # use serenity::http::Http;
378 /// #
379 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
380 /// # let http: Http = unimplemented!();
381 /// # let mut message: Message = unimplemented!();
382 /// // assuming a `message` has already been bound
383 /// let builder = EditMessage::new().content("new content");
384 /// message.edit(&http, builder).await?;
385 /// # Ok(())
386 /// # }
387 /// ```
388 ///
389 /// # Errors
390 ///
391 /// If the `cache` is enabled, returns a [`ModelError::InvalidUser`] if the current user is not
392 /// the author. Otherwise returns [`Error::Http`] if the user lacks permission, as well as if
393 /// invalid data is given.
394 ///
395 /// Returns a [`ModelError::MessageTooLong`] if the message contents are too long.
396 ///
397 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
398 pub async fn edit(&mut self, cache_http: impl CacheHttp, builder: EditMessage) -> Result<()> {
399 if let Some(flags) = self.flags {
400 if flags.contains(MessageFlags::IS_VOICE_MESSAGE) {
401 return Err(Error::Model(ModelError::CannotEditVoiceMessage));
402 }
403 }
404
405 *self =
406 builder.execute(cache_http, (self.channel_id, self.id, Some(self.author.id))).await?;
407 Ok(())
408 }
409
410 /// Returns message content, but with user and role mentions replaced with
411 /// names and everyone/here mentions cancelled.
412 #[cfg(feature = "cache")]
413 pub fn content_safe(&self, cache: impl AsRef<Cache>) -> String {
414 let mut result = self.content.clone();
415
416 // First replace all user mentions.
417 for u in &self.mentions {
418 let mut at_distinct = String::with_capacity(38);
419 at_distinct.push('@');
420 at_distinct.push_str(&u.name);
421 if let Some(discriminator) = u.discriminator {
422 at_distinct.push('#');
423 write!(at_distinct, "{:04}", discriminator.get())
424 .expect("writing to a string should never fail");
425 }
426
427 let mut m = u.mention().to_string();
428 // Check whether we're replacing a nickname mention or a normal mention.
429 // `UserId::mention` returns a normal mention. If it isn't present in the message, it's
430 // a nickname mention.
431 if !result.contains(&m) {
432 m.insert(2, '!');
433 }
434
435 result = result.replace(&m, &at_distinct);
436 }
437
438 // Then replace all role mentions.
439 if let Some(guild_id) = self.guild_id {
440 for id in &self.mention_roles {
441 let mention = id.mention().to_string();
442
443 if let Some(guild) = cache.as_ref().guild(guild_id) {
444 if let Some(role) = guild.roles.get(id) {
445 result = result.replace(&mention, &format!("@{}", role.name));
446 continue;
447 }
448 }
449
450 result = result.replace(&mention, "@deleted-role");
451 }
452 }
453
454 // And finally replace everyone and here mentions.
455 result.replace("@everyone", "@\u{200B}everyone").replace("@here", "@\u{200B}here")
456 }
457
458 /// Gets the list of [`User`]s who have reacted to a [`Message`] with a certain [`Emoji`].
459 ///
460 /// The default `limit` is `50` - specify otherwise to receive a different maximum number of
461 /// users. The maximum that may be retrieve at a time is `100`, if a greater number is provided
462 /// then it is automatically reduced.
463 ///
464 /// The optional `after` attribute is to retrieve the users after a certain user. This is
465 /// useful for pagination.
466 ///
467 /// **Note**: Requires the [Read Message History] permission.
468 ///
469 /// **Note**: If the passed reaction_type is a custom guild emoji, it must contain the name.
470 /// So, [`Emoji`] or [`EmojiIdentifier`] will always work, [`ReactionType`] only if
471 /// [`ReactionType::Custom::name`] is Some, and **[`EmojiId`] will never work**.
472 ///
473 /// # Errors
474 ///
475 /// Returns [`Error::Http`] if the current user lacks permission.
476 ///
477 /// [Read Message History]: Permissions::READ_MESSAGE_HISTORY
478 #[inline]
479 pub async fn reaction_users(
480 &self,
481 http: impl AsRef<Http>,
482 reaction_type: impl Into<ReactionType>,
483 limit: Option<u8>,
484 after: impl Into<Option<UserId>>,
485 ) -> Result<Vec<User>> {
486 self.channel_id.reaction_users(http, self.id, reaction_type, limit, after).await
487 }
488
489 /// Returns the associated [`Guild`] for the message if one is in the cache.
490 ///
491 /// Returns [`None`] if the guild's Id could not be found via [`Self::guild_id`] or if the
492 /// Guild itself is not cached.
493 ///
494 /// Requires the `cache` feature be enabled.
495 #[cfg(feature = "cache")]
496 pub fn guild<'a>(&self, cache: &'a Cache) -> Option<GuildRef<'a>> {
497 cache.guild(self.guild_id?)
498 }
499
500 /// True if message was sent using direct messages.
501 ///
502 /// **Only use this for messages from the gateway (event handler)!** Not for returned Message
503 /// objects from HTTP requests, like [`ChannelId::send_message`], because [`Self::guild_id`] is
504 /// never set for those, which this method relies on.
505 #[inline]
506 #[must_use]
507 #[deprecated = "Check if guild_id is None if the message is received from the gateway."]
508 pub fn is_private(&self) -> bool {
509 self.guild_id.is_none()
510 }
511
512 /// Retrieves a clone of the author's Member instance, if this message was sent in a guild.
513 ///
514 /// If the instance cannot be found in the cache, or the `cache` feature is disabled, a HTTP
515 /// request is performed to retrieve it from Discord's API.
516 ///
517 /// # Errors
518 ///
519 /// [`ModelError::ItemMissing`] is returned if [`Self::guild_id`] is [`None`].
520 pub async fn member(&self, cache_http: impl CacheHttp) -> Result<Member> {
521 match self.guild_id {
522 Some(guild_id) => guild_id.member(cache_http, self.author.id).await,
523 None => Err(Error::Model(ModelError::ItemMissing)),
524 }
525 }
526
527 /// Checks the length of a message to ensure that it is within Discord's maximum length limit.
528 ///
529 /// Returns [`None`] if the message is within the limit, otherwise returns [`Some`] with an
530 /// inner value of how many unicode code points the message is over.
531 #[must_use]
532 pub fn overflow_length(content: &str) -> Option<usize> {
533 crate::builder::check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT).err()
534 }
535
536 /// Pins this message to its channel.
537 ///
538 /// **Note**: Requires the [Manage Messages] permission.
539 ///
540 /// # Errors
541 ///
542 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
543 /// does not have the required permissions.
544 ///
545 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
546 pub async fn pin(&self, cache_http: impl CacheHttp) -> Result<()> {
547 #[cfg(feature = "cache")]
548 {
549 if let Some(cache) = cache_http.cache() {
550 if self.guild_id.is_some() {
551 utils::user_has_perms_cache(
552 cache,
553 self.channel_id,
554 Permissions::MANAGE_MESSAGES,
555 )?;
556 }
557 }
558 }
559
560 self.channel_id.pin(cache_http.http(), self.id).await
561 }
562
563 /// React to the message with a custom [`Emoji`] or unicode character.
564 ///
565 /// **Note**: Requires the [Add Reactions] permission.
566 ///
567 /// # Errors
568 ///
569 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
570 /// does not have the required [permissions].
571 ///
572 /// [Add Reactions]: Permissions::ADD_REACTIONS
573 /// [permissions]: crate::model::permissions
574 #[inline]
575 pub async fn react(
576 &self,
577 cache_http: impl CacheHttp,
578 reaction_type: impl Into<ReactionType>,
579 ) -> Result<Reaction> {
580 self.react_(cache_http, reaction_type.into()).await
581 }
582
583 async fn react_(
584 &self,
585 cache_http: impl CacheHttp,
586 reaction_type: ReactionType,
587 ) -> Result<Reaction> {
588 #[cfg_attr(not(feature = "cache"), allow(unused_mut))]
589 let mut user_id = None;
590
591 #[cfg(feature = "cache")]
592 {
593 if let Some(cache) = cache_http.cache() {
594 if self.guild_id.is_some() {
595 utils::user_has_perms_cache(
596 cache,
597 self.channel_id,
598 Permissions::ADD_REACTIONS,
599 )?;
600 }
601
602 user_id = Some(cache.current_user().id);
603 }
604 }
605
606 cache_http.http().create_reaction(self.channel_id, self.id, &reaction_type).await?;
607
608 Ok(Reaction {
609 channel_id: self.channel_id,
610 emoji: reaction_type,
611 message_id: self.id,
612 user_id,
613 guild_id: self.guild_id,
614 member: self.member.as_deref().map(|member| member.clone().into()),
615 message_author_id: None,
616 burst: false,
617 burst_colours: None,
618 reaction_type: ReactionTypes::Normal,
619 })
620 }
621
622 /// Uses Discord's inline reply to a user without pinging them.
623 ///
624 /// User mentions are generally around 20 or 21 characters long.
625 ///
626 /// **Note**: Requires the [Send Messages] permission.
627 ///
628 /// **Note**: Message contents must be under 2000 unicode code points.
629 ///
630 /// # Errors
631 ///
632 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
633 /// does not have the required permissions.
634 ///
635 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
636 /// limit, containing the number of unicode code points over the limit.
637 ///
638 /// [Send Messages]: Permissions::SEND_MESSAGES
639 #[inline]
640 pub async fn reply(
641 &self,
642 cache_http: impl CacheHttp,
643 content: impl Into<String>,
644 ) -> Result<Message> {
645 self.reply_(cache_http, content, Some(false)).await
646 }
647
648 /// Uses Discord's inline reply to a user with a ping.
649 ///
650 /// **Note**: Requires the [Send Messages] permission.
651 ///
652 /// **Note**: Message contents must be under 2000 unicode code points.
653 ///
654 /// # Errors
655 ///
656 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
657 /// does not have the required permissions.
658 ///
659 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
660 /// limit, containing the number of unicode code points over the limit.
661 ///
662 /// [Send Messages]: Permissions::SEND_MESSAGES
663 #[inline]
664 pub async fn reply_ping(
665 &self,
666 cache_http: impl CacheHttp,
667 content: impl Into<String>,
668 ) -> Result<Message> {
669 self.reply_(cache_http, content, Some(true)).await
670 }
671
672 /// Replies to the user, mentioning them prior to the content in the form of: `@<USER_ID>
673 /// YOUR_CONTENT`.
674 ///
675 /// User mentions are generally around 20 or 21 characters long.
676 ///
677 /// **Note**: Requires the [Send Messages] permission.
678 ///
679 /// **Note**: Message contents must be under 2000 unicode code points.
680 ///
681 /// # Errors
682 ///
683 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
684 /// does not have the required permissions.
685 ///
686 /// Returns a [`ModelError::MessageTooLong`] if the content of the message is over the above
687 /// limit, containing the number of unicode code points over the limit.
688 ///
689 /// [Send Messages]: Permissions::SEND_MESSAGES
690 #[inline]
691 pub async fn reply_mention(
692 &self,
693 cache_http: impl CacheHttp,
694 content: impl Display,
695 ) -> Result<Message> {
696 self.reply_(cache_http, format!("{} {content}", self.author.mention()), None).await
697 }
698
699 /// `inlined` decides whether this reply is inlined and whether it pings.
700 async fn reply_(
701 &self,
702 cache_http: impl CacheHttp,
703 content: impl Into<String>,
704 inlined: Option<bool>,
705 ) -> Result<Message> {
706 #[cfg(feature = "cache")]
707 {
708 if let Some(cache) = cache_http.cache() {
709 if self.guild_id.is_some() {
710 utils::user_has_perms_cache(
711 cache,
712 self.channel_id,
713 Permissions::SEND_MESSAGES,
714 )?;
715 }
716 }
717 }
718
719 let mut builder = CreateMessage::new().content(content);
720 if let Some(ping_user) = inlined {
721 let allowed_mentions = CreateAllowedMentions::new()
722 .replied_user(ping_user)
723 // By providing allowed_mentions, Discord disabled _all_ pings by default so we
724 // need to re-enable them
725 .everyone(true)
726 .all_users(true)
727 .all_roles(true);
728 builder = builder.reference_message(self).allowed_mentions(allowed_mentions);
729 }
730 self.channel_id.send_message(cache_http, builder).await
731 }
732
733 /// Checks whether the message mentions passed [`UserId`].
734 #[inline]
735 pub fn mentions_user_id(&self, id: impl Into<UserId>) -> bool {
736 let id = id.into();
737 self.mentions.iter().any(|mentioned_user| mentioned_user.id == id)
738 }
739
740 /// Checks whether the message mentions passed [`User`].
741 #[inline]
742 #[must_use]
743 pub fn mentions_user(&self, user: &User) -> bool {
744 self.mentions_user_id(user.id)
745 }
746
747 /// Checks whether the message mentions the current user.
748 ///
749 /// # Errors
750 ///
751 /// May return [`Error::Http`] if the `cache` feature is not enabled, or if the cache is
752 /// otherwise unavailable.
753 pub async fn mentions_me(&self, cache_http: impl CacheHttp) -> Result<bool> {
754 #[cfg(feature = "cache")]
755 {
756 if let Some(cache) = cache_http.cache() {
757 return Ok(self.mentions_user_id(cache.current_user().id));
758 }
759 }
760
761 let current_user = cache_http.http().get_current_user().await?;
762 Ok(self.mentions_user_id(current_user.id))
763 }
764
765 /// Unpins the message from its channel.
766 ///
767 /// **Note**: Requires the [Manage Messages] permission.
768 ///
769 /// # Errors
770 ///
771 /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
772 /// does not have the required permissions.
773 ///
774 /// [Manage Messages]: Permissions::MANAGE_MESSAGES
775 pub async fn unpin(&self, cache_http: impl CacheHttp) -> Result<()> {
776 #[cfg(feature = "cache")]
777 {
778 if let Some(cache) = cache_http.cache() {
779 if self.guild_id.is_some() {
780 utils::user_has_perms_cache(
781 cache,
782 self.channel_id,
783 Permissions::MANAGE_MESSAGES,
784 )?;
785 }
786 }
787 }
788
789 cache_http.http().unpin_message(self.channel_id, self.id, None).await
790 }
791
792 /// Ends the [`Poll`] on this message, if there is one.
793 ///
794 /// # Errors
795 ///
796 /// See [`ChannelId::end_poll`] for more information.
797 pub async fn end_poll(&self, http: impl AsRef<Http>) -> Result<Self> {
798 self.channel_id.end_poll(http, self.id).await
799 }
800
801 /// Tries to return author's nickname in the current channel's guild.
802 ///
803 /// Refer to [`User::nick_in()`] inside and [`None`] outside of a guild.
804 #[inline]
805 pub async fn author_nick(&self, cache_http: impl CacheHttp) -> Option<String> {
806 self.author.nick_in(cache_http, self.guild_id?).await
807 }
808
809 /// Returns a link referencing this message. When clicked, users will jump to the message. The
810 /// link will be valid for messages in either private channels or guilds.
811 #[inline]
812 #[must_use]
813 pub fn link(&self) -> String {
814 self.id.link(self.channel_id, self.guild_id)
815 }
816
817 /// Same as [`Self::link`] but tries to find the [`GuildId`] if Discord does not provide it.
818 ///
819 /// [`guild_id`]: Self::guild_id
820 #[inline]
821 #[allow(deprecated)]
822 #[deprecated = "Use Self::link if Message was recieved via an event, otherwise use MessageId::link to provide the guild_id yourself."]
823 pub async fn link_ensured(&self, cache_http: impl CacheHttp) -> String {
824 self.id.link_ensured(cache_http, self.channel_id, self.guild_id).await
825 }
826
827 /// Returns a builder which can be awaited to obtain a reaction or stream of reactions on this
828 /// message.
829 #[cfg(feature = "collector")]
830 pub fn await_reaction(&self, shard_messenger: impl AsRef<ShardMessenger>) -> ReactionCollector {
831 ReactionCollector::new(shard_messenger).message_id(self.id)
832 }
833
834 /// Same as [`Self::await_reaction`].
835 #[cfg(feature = "collector")]
836 pub fn await_reactions(
837 &self,
838 shard_messenger: impl AsRef<ShardMessenger>,
839 ) -> ReactionCollector {
840 self.await_reaction(shard_messenger)
841 }
842
843 /// Returns a builder which can be awaited to obtain a single component interactions or a
844 /// stream of component interactions on this message.
845 #[cfg(feature = "collector")]
846 pub fn await_component_interaction(
847 &self,
848 shard_messenger: impl AsRef<ShardMessenger>,
849 ) -> ComponentInteractionCollector {
850 ComponentInteractionCollector::new(shard_messenger).message_id(self.id)
851 }
852
853 /// Same as [`Self::await_component_interaction`].
854 #[cfg(feature = "collector")]
855 pub fn await_component_interactions(
856 &self,
857 shard_messenger: impl AsRef<ShardMessenger>,
858 ) -> ComponentInteractionCollector {
859 self.await_component_interaction(shard_messenger)
860 }
861
862 /// Returns a builder which can be awaited to obtain a model submit interaction or stream of
863 /// modal submit interactions on this message.
864 #[cfg(feature = "collector")]
865 pub fn await_modal_interaction(
866 &self,
867 shard_messenger: impl AsRef<ShardMessenger>,
868 ) -> ModalInteractionCollector {
869 ModalInteractionCollector::new(shard_messenger).message_id(self.id)
870 }
871
872 /// Same as [`Self::await_modal_interaction`].
873 #[cfg(feature = "collector")]
874 pub fn await_modal_interactions(
875 &self,
876 shard_messenger: impl AsRef<ShardMessenger>,
877 ) -> ModalInteractionCollector {
878 self.await_modal_interaction(shard_messenger)
879 }
880
881 /// Retrieves the message channel's category ID if the channel has one.
882 pub async fn category_id(&self, cache_http: impl CacheHttp) -> Option<ChannelId> {
883 #[cfg(feature = "cache")]
884 if let Some(cache) = cache_http.cache() {
885 if let Some(guild) = cache.guild(self.guild_id?) {
886 let channel = guild.channels.get(&self.channel_id)?;
887 return if channel.thread_metadata.is_some() {
888 let thread_parent = guild.channels.get(&channel.parent_id?)?;
889 thread_parent.parent_id
890 } else {
891 channel.parent_id
892 };
893 }
894 }
895
896 let channel = self.channel_id.to_channel(&cache_http).await.ok()?.guild()?;
897 if channel.thread_metadata.is_some() {
898 let thread_parent = channel.parent_id?.to_channel(cache_http).await.ok()?.guild()?;
899 thread_parent.parent_id
900 } else {
901 channel.parent_id
902 }
903 }
904}
905
906impl AsRef<MessageId> for Message {
907 fn as_ref(&self) -> &MessageId {
908 &self.id
909 }
910}
911
912impl From<Message> for MessageId {
913 /// Gets the Id of a [`Message`].
914 fn from(message: Message) -> MessageId {
915 message.id
916 }
917}
918
919impl From<&Message> for MessageId {
920 /// Gets the Id of a [`Message`].
921 fn from(message: &Message) -> MessageId {
922 message.id
923 }
924}
925
926/// A representation of a reaction to a message.
927///
928/// Multiple of the same [reaction type] are sent into one [`MessageReaction`], with an associated
929/// [`Self::count`].
930///
931/// [Discord docs](https://discord.com/developers/docs/resources/channel#reaction-object).
932///
933/// [reaction type]: ReactionType
934#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
935#[derive(Clone, Debug, Deserialize, Serialize)]
936#[non_exhaustive]
937pub struct MessageReaction {
938 /// The amount of the type of reaction that have been sent for the associated message
939 /// including super reactions.
940 pub count: u64,
941 /// A breakdown of what reactions were from regular reactions and super reactions.
942 pub count_details: CountDetails,
943 /// Indicator of whether the current user has sent this type of reaction.
944 pub me: bool,
945 /// Indicator of whether the current user has sent the type of super-reaction.
946 pub me_burst: bool,
947 /// The type of reaction.
948 #[serde(rename = "emoji")]
949 pub reaction_type: ReactionType,
950 // The colours used for super reactions.
951 #[serde(rename = "burst_colors", deserialize_with = "discord_colours")]
952 pub burst_colours: Vec<Colour>,
953}
954
955/// A representation of reaction count details.
956///
957/// [Discord docs](https://discord.com/developers/docs/resources/channel#reaction-count-details-object).
958#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
959#[derive(Clone, Debug, Deserialize, Serialize)]
960#[non_exhaustive]
961pub struct CountDetails {
962 pub burst: u64,
963 pub normal: u64,
964}
965
966enum_number! {
967 /// Differentiates between regular and different types of system messages.
968 ///
969 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-types).
970 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
971 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
972 #[serde(from = "u8", into = "u8")]
973 #[non_exhaustive]
974 pub enum MessageType {
975 /// A regular message.
976 #[default]
977 Regular = 0,
978 /// An indicator that a recipient was added by the author.
979 GroupRecipientAddition = 1,
980 /// An indicator that a recipient was removed by the author.
981 GroupRecipientRemoval = 2,
982 /// An indicator that a call was started by the author.
983 GroupCallCreation = 3,
984 /// An indicator that the group name was modified by the author.
985 GroupNameUpdate = 4,
986 /// An indicator that the group icon was modified by the author.
987 GroupIconUpdate = 5,
988 /// An indicator that a message was pinned by the author.
989 PinsAdd = 6,
990 /// An indicator that a member joined the guild.
991 MemberJoin = 7,
992 /// An indicator that someone has boosted the guild.
993 NitroBoost = 8,
994 /// An indicator that the guild has reached nitro tier 1
995 NitroTier1 = 9,
996 /// An indicator that the guild has reached nitro tier 2
997 NitroTier2 = 10,
998 /// An indicator that the guild has reached nitro tier 3
999 NitroTier3 = 11,
1000 /// An indicator that the channel is now following a news channel
1001 ChannelFollowAdd = 12,
1002 /// An indicator that the guild is disqualified for Discovery Feature
1003 GuildDiscoveryDisqualified = 14,
1004 /// An indicator that the guild is requalified for Discovery Feature
1005 GuildDiscoveryRequalified = 15,
1006 /// The first warning before guild discovery removal.
1007 GuildDiscoveryGracePeriodInitialWarning = 16,
1008 /// The last warning before guild discovery removal.
1009 GuildDiscoveryGracePeriodFinalWarning = 17,
1010 /// Message sent to inform users that a thread was created.
1011 ThreadCreated = 18,
1012 /// A message reply.
1013 InlineReply = 19,
1014 /// A slash command.
1015 ChatInputCommand = 20,
1016 /// A thread start message.
1017 ThreadStarterMessage = 21,
1018 /// Server setup tips.
1019 GuildInviteReminder = 22,
1020 /// A context menu command.
1021 ContextMenuCommand = 23,
1022 /// A message from an auto moderation action.
1023 AutoModAction = 24,
1024 RoleSubscriptionPurchase = 25,
1025 InteractionPremiumUpsell = 26,
1026 StageStart = 27,
1027 StageEnd = 28,
1028 StageSpeaker = 29,
1029 StageTopic = 31,
1030 GuildApplicationPremiumSubscription = 32,
1031 GuildIncidentAlertModeEnabled = 36,
1032 GuildIncidentAlertModeDisabled = 37,
1033 GuildIncidentReportRaid = 38,
1034 GuildIncidentReportFalseAlarm = 39,
1035 PurchaseNotification = 44,
1036 _ => Unknown(u8),
1037 }
1038}
1039
1040enum_number! {
1041 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-activity-types).
1042 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1043 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1044 #[serde(from = "u8", into = "u8")]
1045 #[non_exhaustive]
1046 pub enum MessageActivityKind {
1047 Join = 1,
1048 Spectate = 2,
1049 Listen = 3,
1050 JoinRequest = 5,
1051 _ => Unknown(u8),
1052 }
1053}
1054
1055/// Rich Presence application information.
1056///
1057/// [Discord docs](https://discord.com/developers/docs/resources/application#application-object),
1058/// [subset undocumented](https://discord.com/developers/docs/resources/channel#message-object-message-structure).
1059#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1060#[derive(Clone, Debug, Deserialize, Serialize)]
1061#[non_exhaustive]
1062pub struct MessageApplication {
1063 /// ID of the application.
1064 pub id: ApplicationId,
1065 /// ID of the embed's image asset.
1066 pub cover_image: Option<ImageHash>,
1067 /// Application's description.
1068 pub description: String,
1069 /// ID of the application's icon.
1070 pub icon: Option<ImageHash>,
1071 /// Name of the application.
1072 pub name: String,
1073}
1074
1075/// Rich Presence activity information.
1076///
1077/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure).
1078#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1079#[derive(Clone, Debug, Deserialize, Serialize)]
1080#[non_exhaustive]
1081pub struct MessageActivity {
1082 /// Kind of message activity.
1083 #[serde(rename = "type")]
1084 pub kind: MessageActivityKind,
1085 /// `party_id` from a Rich Presence event.
1086 pub party_id: Option<String>,
1087}
1088
1089enum_number! {
1090 /// Message Reference Type information
1091 ///
1092 /// [Discord docs](https://discord.com/developers/docs/resources/message#message-reference-types)
1093 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1094 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1095 #[serde(from = "u8", into = "u8")]
1096 #[non_exhaustive]
1097 pub enum MessageReferenceKind {
1098 #[default]
1099 Default = 0,
1100 Forward = 1,
1101 _ => Unknown(u8),
1102 }
1103}
1104
1105/// Reference data sent with crossposted messages.
1106///
1107/// [Discord docs](https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-structure).
1108#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1109#[derive(Clone, Debug, Deserialize, Serialize)]
1110#[non_exhaustive]
1111pub struct MessageReference {
1112 /// The Type of Message Reference
1113 #[serde(rename = "type", default = "MessageReferenceKind::default")]
1114 pub kind: MessageReferenceKind,
1115 /// ID of the originating message.
1116 pub message_id: Option<MessageId>,
1117 /// ID of the originating message's channel.
1118 pub channel_id: ChannelId,
1119 /// ID of the originating message's guild.
1120 pub guild_id: Option<GuildId>,
1121 /// When sending, whether to error if the referenced message doesn't exist instead of sending
1122 /// as a normal (non-reply) message, default true.
1123 pub fail_if_not_exists: Option<bool>,
1124}
1125
1126impl MessageReference {
1127 #[must_use]
1128 pub fn new(kind: MessageReferenceKind, channel_id: ChannelId) -> Self {
1129 Self {
1130 kind,
1131 channel_id,
1132 message_id: None,
1133 guild_id: None,
1134 fail_if_not_exists: None,
1135 }
1136 }
1137
1138 #[must_use]
1139 pub fn message_id(mut self, message_id: MessageId) -> Self {
1140 self.message_id = Some(message_id);
1141 self
1142 }
1143
1144 #[must_use]
1145 pub fn guild_id(mut self, guild_id: GuildId) -> Self {
1146 self.guild_id = Some(guild_id);
1147 self
1148 }
1149
1150 #[must_use]
1151 pub fn fail_if_not_exists(mut self, fail_if_not_exists: bool) -> Self {
1152 self.fail_if_not_exists = Some(fail_if_not_exists);
1153 self
1154 }
1155}
1156
1157impl From<&Message> for MessageReference {
1158 fn from(m: &Message) -> Self {
1159 Self {
1160 kind: MessageReferenceKind::default(),
1161 message_id: Some(m.id),
1162 channel_id: m.channel_id,
1163 guild_id: m.guild_id,
1164 fail_if_not_exists: None,
1165 }
1166 }
1167}
1168
1169impl From<(ChannelId, MessageId)> for MessageReference {
1170 // TODO(next): Remove this
1171 fn from(pair: (ChannelId, MessageId)) -> Self {
1172 Self {
1173 kind: MessageReferenceKind::default(),
1174 message_id: Some(pair.1),
1175 channel_id: pair.0,
1176 guild_id: None,
1177 fail_if_not_exists: None,
1178 }
1179 }
1180}
1181
1182/// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-mention-object).
1183#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1184#[derive(Clone, Debug, Deserialize, Serialize)]
1185#[non_exhaustive]
1186pub struct ChannelMention {
1187 /// ID of the channel.
1188 pub id: ChannelId,
1189 /// ID of the guild containing the channel.
1190 pub guild_id: GuildId,
1191 /// The kind of channel
1192 #[serde(rename = "type")]
1193 pub kind: ChannelType,
1194 /// The name of the channel
1195 pub name: String,
1196}
1197
1198/// [Discord docs](https://discord.com/developers/docs/resources/message#message-snapshot-structure)
1199///
1200/// For field documentation, see [`Message`].
1201#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1202#[derive(Clone, Debug, Serialize, Deserialize)]
1203#[non_exhaustive]
1204pub struct MessageSnapshot {
1205 pub content: String,
1206 pub timestamp: Timestamp,
1207 pub edited_timestamp: Option<Timestamp>,
1208 pub mentions: Vec<User>,
1209 #[serde(default)]
1210 pub mention_roles: Vec<RoleId>,
1211 pub attachments: Vec<Attachment>,
1212 pub embeds: Vec<Embed>,
1213 #[serde(rename = "type")]
1214 pub kind: MessageType,
1215 pub flags: Option<MessageFlags>,
1216 #[serde(default, deserialize_with = "deserialize_components")]
1217 pub components: Vec<ActionRow>,
1218 #[serde(default)]
1219 pub sticker_items: Vec<StickerItem>,
1220}
1221
1222/// Custom deserialization function to handle the nested "message" field
1223fn deserialize_snapshots<'de, D>(deserializer: D) -> Result<Vec<MessageSnapshot>, D::Error>
1224where
1225 D: Deserializer<'de>,
1226{
1227 #[derive(Deserialize)]
1228 struct MessageSnapshotWrapper {
1229 pub message: MessageSnapshot,
1230 }
1231
1232 let snapshots: Vec<MessageSnapshotWrapper> = Deserialize::deserialize(deserializer)?;
1233
1234 let result = snapshots.into_iter().map(|wrapper| wrapper.message).collect();
1235
1236 Ok(result)
1237}
1238
1239bitflags! {
1240 /// Describes extra features of the message.
1241 ///
1242 /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-flags).
1243 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1244 #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
1245 pub struct MessageFlags: u64 {
1246 /// This message has been published to subscribed channels (via Channel Following).
1247 const CROSSPOSTED = 1 << 0;
1248 /// This message originated from a message in another channel (via Channel Following).
1249 const IS_CROSSPOST = 1 << 1;
1250 /// Do not include any embeds when serializing this message.
1251 const SUPPRESS_EMBEDS = 1 << 2;
1252 /// The source message for this crosspost has been deleted (via Channel Following).
1253 const SOURCE_MESSAGE_DELETED = 1 << 3;
1254 /// This message came from the urgent message system.
1255 const URGENT = 1 << 4;
1256 /// This message has an associated thread, with the same id as the message.
1257 const HAS_THREAD = 1 << 5;
1258 /// This message is only visible to the user who invoked the Interaction.
1259 const EPHEMERAL = 1 << 6;
1260 /// This message is an Interaction Response and the bot is "thinking".
1261 const LOADING = 1 << 7;
1262 /// This message failed to mention some roles and add their members to the thread.
1263 const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
1264 /// This message will not trigger push and desktop notifications.
1265 const SUPPRESS_NOTIFICATIONS = 1 << 12;
1266 /// This message is a voice message.
1267 ///
1268 /// Voice messages have the following properties:
1269 /// - They cannot be edited.
1270 /// - Only a single audio attachment is allowed. No content, stickers, etc...
1271 /// - The [`Attachment`] has additional fields: `duration_secs` and `waveform`.
1272 ///
1273 /// As of 2023-04-14, clients upload a 1 channel, 48000 Hz, 32kbps Opus stream in an OGG container.
1274 /// The encoding is a Discord implementation detail and may change without warning or documentation.
1275 ///
1276 /// As of 2023-04-20, bots are currently not able to send voice messages
1277 /// ([source](https://github.com/discord/discord-api-docs/pull/6082)).
1278 const IS_VOICE_MESSAGE = 1 << 13;
1279 }
1280}
1281
1282#[cfg(feature = "model")]
1283impl MessageId {
1284 /// Returns a link referencing this message. When clicked, users will jump to the message. The
1285 /// link will be valid for messages in either private channels or guilds.
1286 #[must_use]
1287 pub fn link(&self, channel_id: ChannelId, guild_id: Option<GuildId>) -> String {
1288 if let Some(guild_id) = guild_id {
1289 format!("https://discord.com/channels/{guild_id}/{channel_id}/{self}")
1290 } else {
1291 format!("https://discord.com/channels/@me/{channel_id}/{self}")
1292 }
1293 }
1294
1295 /// Same as [`Self::link`] but tries to find the [`GuildId`] if it is not provided.
1296 #[deprecated = "Use GuildChannel::guild_id if you have no GuildId"]
1297 pub async fn link_ensured(
1298 &self,
1299 cache_http: impl CacheHttp,
1300 channel_id: ChannelId,
1301 mut guild_id: Option<GuildId>,
1302 ) -> String {
1303 if guild_id.is_none() {
1304 let found_channel = channel_id.to_channel(cache_http).await;
1305
1306 if let Ok(channel) = found_channel {
1307 if let Some(c) = channel.guild() {
1308 guild_id = Some(c.guild_id);
1309 }
1310 }
1311 }
1312
1313 self.link(channel_id, guild_id)
1314 }
1315}
1316
1317#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1318#[derive(Clone, Debug, Serialize)]
1319#[serde(untagged)]
1320pub enum Nonce {
1321 String(String),
1322 Number(u64),
1323}
1324
1325impl<'de> serde::Deserialize<'de> for Nonce {
1326 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
1327 Ok(StrOrInt::deserialize(deserializer)?.into_enum(Self::String, Self::Number))
1328 }
1329}
1330
1331/// [Discord docs](https://discord.com/developers/docs/resources/channel#role-subscription-data-object)
1332#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1333#[derive(Clone, Debug, Deserialize, Serialize)]
1334pub struct RoleSubscriptionData {
1335 /// The id of the sku and listing that the user is subscribed to.
1336 pub role_subscription_listing_id: SkuId,
1337 /// The name of the tier that the user is subscribed to.
1338 pub tier_name: String,
1339 /// The cumulative number of months that the user has been subscribed for.
1340 pub total_months_subscribed: u16,
1341 /// Whether this notification is for a renewal rather than a new purchase.
1342 pub is_renewal: bool,
1343}
1344
1345/// A poll that has been attached to a [`Message`].
1346///
1347/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-object)
1348#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1349#[derive(Clone, Debug, Deserialize, Serialize)]
1350#[non_exhaustive]
1351pub struct Poll {
1352 pub question: PollMedia,
1353 pub answers: Vec<PollAnswer>,
1354 pub expiry: Option<Timestamp>,
1355 pub allow_multiselect: bool,
1356 pub layout_type: PollLayoutType,
1357 /// The results of the Poll.
1358 ///
1359 /// None does **not** mean that there are no results, simply that Discord has not provide them.
1360 /// See the discord docs for a more detailed explaination.
1361 pub results: Option<PollResults>,
1362}
1363
1364/// A piece of data used in mutliple parts of the [`Poll`] structure.
1365///
1366/// Currently holds text and an optional emoji, but this is expected to change in future
1367///
1368/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-media-object)
1369#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1370#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1371#[non_exhaustive]
1372pub struct PollMedia {
1373 pub text: Option<String>,
1374 pub emoji: Option<PollMediaEmoji>,
1375}
1376
1377/// The "Partial Emoji" attached to a [`PollMedia`] model.
1378///
1379/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-media-object)
1380#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1381#[derive(Clone, Debug, Serialize)]
1382#[serde(rename_all = "lowercase")]
1383pub enum PollMediaEmoji {
1384 Name(String),
1385 Id(EmojiId),
1386}
1387
1388impl<'de> serde::Deserialize<'de> for PollMediaEmoji {
1389 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
1390 #[derive(serde::Deserialize)]
1391 struct RawPollMediaEmoji {
1392 name: Option<String>,
1393 id: Option<EmojiId>,
1394 }
1395
1396 let raw = RawPollMediaEmoji::deserialize(deserializer)?;
1397 if let Some(name) = raw.name {
1398 Ok(PollMediaEmoji::Name(name))
1399 } else if let Some(id) = raw.id {
1400 Ok(PollMediaEmoji::Id(id))
1401 } else {
1402 Err(serde::de::Error::duplicate_field("emoji"))
1403 }
1404 }
1405}
1406
1407impl From<String> for PollMediaEmoji {
1408 fn from(value: String) -> Self {
1409 Self::Name(value)
1410 }
1411}
1412
1413impl From<EmojiId> for PollMediaEmoji {
1414 fn from(value: EmojiId) -> Self {
1415 Self::Id(value)
1416 }
1417}
1418
1419/// A possible answer for a [`Poll`].
1420///
1421/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-answer-object)
1422#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1423#[derive(Clone, Debug, Deserialize, Serialize)]
1424#[non_exhaustive]
1425pub struct PollAnswer {
1426 pub answer_id: AnswerId,
1427 pub poll_media: PollMedia,
1428}
1429
1430enum_number! {
1431 /// Represents the different layouts that a [`Poll`] may have.
1432 ///
1433 /// Currently, there is only the one option.
1434 ///
1435 /// [Discord docs](https://discord.com/developers/docs/resources/poll#layout-type)
1436 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
1437 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1438 #[serde(from = "u8", into = "u8")]
1439 #[non_exhaustive]
1440 pub enum PollLayoutType {
1441 #[default]
1442 Default = 1,
1443 _ => Unknown(u8),
1444 }
1445}
1446
1447/// The model for the results of a [`Poll`].
1448///
1449/// If `is_finalized` is `false`, `answer_counts` will be inaccurate due to Discord's scale.
1450///
1451/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure)
1452#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1453#[derive(Clone, Debug, Deserialize, Serialize)]
1454#[non_exhaustive]
1455pub struct PollResults {
1456 pub is_finalized: bool,
1457 pub answer_counts: Vec<PollAnswerCount>,
1458}
1459
1460/// The count of a single [`PollAnswer`]'s results.
1461///
1462/// [Discord docs](https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure)
1463#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
1464#[derive(Clone, Debug, Deserialize, Serialize)]
1465#[non_exhaustive]
1466pub struct PollAnswerCount {
1467 pub id: AnswerId,
1468 pub count: u64,
1469 pub me_voted: bool,
1470}
1471
1472// all tests here require cache, move if non-cache test is added
1473#[cfg(all(test, feature = "cache"))]
1474mod tests {
1475 use std::collections::HashMap;
1476
1477 use dashmap::DashMap;
1478
1479 use super::{
1480 Guild,
1481 GuildChannel,
1482 Member,
1483 Message,
1484 PermissionOverwrite,
1485 PermissionOverwriteType,
1486 Permissions,
1487 User,
1488 UserId,
1489 };
1490 use crate::cache::wrappers::MaybeMap;
1491 use crate::cache::Cache;
1492
1493 /// Test that author_permissions checks the permissions in a channel, not just the guild.
1494 #[test]
1495 fn author_permissions_respects_overwrites() {
1496 // Author of the message, with a random ID that won't collide with defaults.
1497 let author = User {
1498 id: UserId::new(50778944701071),
1499 ..Default::default()
1500 };
1501
1502 // Channel with the message, with SEND_MESSAGES on.
1503 let channel = GuildChannel {
1504 permission_overwrites: vec![PermissionOverwrite {
1505 allow: Permissions::SEND_MESSAGES,
1506 deny: Permissions::default(),
1507 kind: PermissionOverwriteType::Member(author.id),
1508 }],
1509 ..Default::default()
1510 };
1511 let channel_id = channel.id;
1512
1513 // Guild with the author and channel cached, default (empty) permissions.
1514 let guild = Guild {
1515 channels: HashMap::from([(channel.id, channel)]),
1516 members: HashMap::from([(author.id, Member {
1517 user: author.clone(),
1518 ..Default::default()
1519 })]),
1520 ..Default::default()
1521 };
1522
1523 // Message, tied to the guild and the channel.
1524 let message = Message {
1525 author,
1526 channel_id,
1527 guild_id: Some(guild.id),
1528 ..Default::default()
1529 };
1530
1531 // Cache, with the guild setup.
1532 let mut cache = Cache::new();
1533 cache.guilds = MaybeMap(Some({
1534 let guilds = DashMap::default();
1535 guilds.insert(guild.id, guild);
1536 guilds
1537 }));
1538
1539 // The author should only have the one permission, SEND_MESSAGES.
1540 assert_eq!(message.author_permissions(&cache), Some(Permissions::SEND_MESSAGES));
1541 }
1542}