serenity 0.11.5

A Rust library for the Discord API.
Documentation
//! Models relating to Discord channels.

#[cfg(feature = "model")]
use std::fmt::Display;
#[cfg(all(feature = "cache", feature = "model"))]
use std::fmt::Write;

#[cfg(all(feature = "model", feature = "utils"))]
use crate::builder::{CreateEmbed, EditMessage};
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::Cache;
#[cfg(feature = "collector")]
use crate::client::bridge::gateway::ShardMessenger;
#[cfg(feature = "collector")]
use crate::collector::{
    CollectComponentInteraction,
    CollectModalInteraction,
    CollectReaction,
    ComponentInteractionCollectorBuilder,
    ModalInteractionCollectorBuilder,
    ReactionCollectorBuilder,
};
#[cfg(feature = "model")]
use crate::http::{CacheHttp, Http};
#[cfg(feature = "model")]
use crate::json;
use crate::json::prelude::*;
use crate::model::application::component::ActionRow;
use crate::model::application::interaction::MessageInteraction;
use crate::model::prelude::*;
#[cfg(feature = "model")]
use crate::{
    constants,
    model::{
        id::{ApplicationId, ChannelId, GuildId, MessageId},
        sticker::StickerItem,
        timestamp::Timestamp,
    },
};

/// A representation of a message over a guild's text channel, a group, or a
/// private channel.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Message {
    /// The unique Id of the message. Can be used to calculate the creation date
    /// of the message.
    pub id: MessageId,
    /// An vector of the files attached to a message.
    pub attachments: Vec<Attachment>,
    /// The user that sent the message.
    pub author: User,
    /// The Id of the [`Channel`] that the message was sent to.
    pub channel_id: ChannelId,
    /// The content of the message.
    pub content: String,
    /// The timestamp of the last time the message was updated, if it was.
    pub edited_timestamp: Option<Timestamp>,
    /// Array of embeds sent with the message.
    pub embeds: Vec<Embed>,
    /// The Id of the [`Guild`] that the message was sent in. This value will
    /// only be present if this message was received over the gateway.
    pub guild_id: Option<GuildId>,
    /// Indicator of the type of message this is, i.e. whether it is a regular
    /// message or a system message.
    #[serde(rename = "type")]
    pub kind: MessageType,
    /// A partial amount of data about the user's member data, if this message
    /// was sent in a guild.
    pub member: Option<PartialMember>,
    /// Indicator of whether the message mentions everyone.
    pub mention_everyone: bool,
    /// Array of [`Role`]s' Ids mentioned in the message.
    pub mention_roles: Vec<RoleId>,
    /// Channels specifically mentioned in this message.
    ///
    /// **Note**:
    /// Not all channel mentions in a message will appear in [`Self::mention_channels`]. Only textual
    /// channels that are visible to everyone in a lurkable guild will ever be included.
    ///
    /// A lurkable guild is one that allows users to read public channels in a server without
    /// actually joining the server. It also allows users to look at these channels without being
    /// logged in to Discord.
    ///
    /// Only crossposted messages (via Channel Following) currently include [`Self::mention_channels`] at
    /// all. If no mentions in the message meet these requirements, this field will not be sent.
    /// [Refer to Discord's documentation for more information][discord-docs].
    ///
    /// [discord-docs]: https://discord.com/developers/docs/resources/channel#message-object
    #[serde(default = "Vec::new")]
    pub mention_channels: Vec<ChannelMention>,
    /// Array of users mentioned in the message.
    pub mentions: Vec<User>,
    /// Non-repeating number used for ensuring message order.
    #[serde(default)]
    pub nonce: Value,
    /// Indicator of whether the message is pinned.
    pub pinned: bool,
    /// Array of reactions performed on the message.
    #[serde(default)]
    pub reactions: Vec<MessageReaction>,
    /// Initial message creation timestamp, calculated from its Id.
    pub timestamp: Timestamp,
    /// Indicator of whether the command is to be played back via
    /// text-to-speech.
    ///
    /// In the client, this is done via the `/tts` slash command.
    pub tts: bool,
    /// The Id of the webhook that sent this message, if one did.
    pub webhook_id: Option<WebhookId>,
    /// Sent with Rich Presence-related chat embeds.
    pub activity: Option<MessageActivity>,
    /// Sent with Rich Presence-related chat embeds.
    pub application: Option<MessageApplication>,
    /// Reference data sent with crossposted messages.
    pub message_reference: Option<MessageReference>,
    /// Bit flags describing extra features of the message.
    pub flags: Option<MessageFlags>,
    /// Array of message sticker item objects.
    #[serde(default)]
    pub sticker_items: Vec<StickerItem>,
    /// The message that was replied to using this message.
    pub referenced_message: Option<Box<Message>>, // Boxed to avoid recursion
    /// Sent if the message is a response to an [`Interaction`].
    ///
    /// [`Interaction`]: crate::model::application::interaction::Interaction
    pub interaction: Option<MessageInteraction>,
    /// The components of this message
    #[serde(default)]
    pub components: Vec<ActionRow>,
}

#[cfg(feature = "model")]
impl Message {
    /// Crossposts this message.
    ///
    /// Requires either to be the message author or to have manage [Manage Messages] permissions on this channel.
    ///
    /// **Note**: Only available on news channels.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// Returns a [`ModelError::MessageAlreadyCrossposted`] if the message has already been crossposted.
    ///
    /// Returns a [`ModelError::CannotCrosspostMessage`] if the message cannot be crossposted.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn crosspost(&self, cache_http: impl CacheHttp) -> Result<Message> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.author.id != cache.current_user_id() && self.guild_id.is_some() {
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::MANAGE_MESSAGES,
                    )?;
                }
            }
        }

        if let Some(flags) = self.flags {
            if flags.contains(MessageFlags::CROSSPOSTED) {
                return Err(Error::Model(ModelError::MessageAlreadyCrossposted));
            } else if flags.contains(MessageFlags::IS_CROSSPOST)
                || self.kind != MessageType::Regular
            {
                return Err(Error::Model(ModelError::CannotCrosspostMessage));
            }
        }

        self.channel_id.crosspost(cache_http.http(), self.id.0).await
    }

    /// First attempts to find a [`Channel`] by its Id in the cache,
    /// upon failure requests it via the REST API.
    ///
    /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon
    /// owning the required permissions the HTTP-request will be issued.
    ///
    /// # Errors
    ///
    /// Can return an error if the HTTP request fails.
    #[inline]
    pub async fn channel(&self, cache_http: impl CacheHttp) -> Result<Channel> {
        self.channel_id.to_channel(cache_http).await
    }

    /// A util function for determining whether this message was sent by someone else, or the
    /// bot.
    #[cfg(feature = "cache")]
    pub fn is_own(&self, cache: impl AsRef<Cache>) -> bool {
        self.author.id == cache.as_ref().current_user().id
    }

    /// Deletes the message.
    ///
    /// **Note**: The logged in user must either be the author of the message or
    /// have the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` feature is enabled, then returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.author.id != cache.current_user_id() {
                    if self.is_private() {
                        return Err(Error::Model(ModelError::NotAuthor));
                    }
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::MANAGE_MESSAGES,
                    )?;
                }
            }
        }

        self.channel_id.delete_message(&cache_http.http(), self.id).await
    }

    /// Deletes all of the [`Reaction`]s associated with the message.
    ///
    /// **Note**: Requires the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` feature is enabled, then returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn delete_reactions(&self, cache_http: impl CacheHttp) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                utils::user_has_perms_cache(
                    cache,
                    self.channel_id,
                    self.guild_id,
                    Permissions::MANAGE_MESSAGES,
                )?;
            }
        }

        cache_http.http().as_ref().delete_message_reactions(self.channel_id.0, self.id.0).await
    }

    /// Deletes all of the [`Reaction`]s of a given emoji associated with the message.
    ///
    /// **Note**: Requires the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` feature is enabled, then returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn delete_reaction_emoji(
        &self,
        cache_http: impl CacheHttp,
        reaction_type: impl Into<ReactionType>,
    ) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                utils::user_has_perms_cache(
                    cache,
                    self.channel_id,
                    self.guild_id,
                    Permissions::MANAGE_MESSAGES,
                )?;
            }
        }

        cache_http
            .http()
            .as_ref()
            .delete_message_reaction_emoji(self.channel_id.0, self.id.0, &reaction_type.into())
            .await
    }

    /// Edits this message, replacing the original content with new content.
    ///
    /// Message editing preserves all unchanged message data.
    ///
    /// Refer to the documentation for [`EditMessage`] for more information
    /// regarding message restrictions and requirements.
    ///
    /// **Note**: Requires that the current user be the author of the message.
    ///
    /// # Examples
    ///
    /// Edit a message with new content:
    ///
    /// ```rust,ignore
    /// // assuming a `message` has already been bound
    ///
    /// message.edit(&context, |m| m.content("new content"));
    /// ```
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a [`ModelError::InvalidUser`] if the
    /// current user is not the author.
    ///
    /// Returns a [`ModelError::MessageTooLong`] if the content of the message
    /// is over [`the limit`], containing the number of unicode code points
    /// over the limit.
    ///
    /// [`the limit`]: crate::builder::EditMessage::content
    pub async fn edit<'a, F>(&mut self, cache_http: impl CacheHttp, f: F) -> Result<()>
    where
        F: for<'b> FnOnce(&'b mut EditMessage<'a>) -> &'b mut EditMessage<'a>,
    {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.author.id != cache.current_user_id() {
                    return Err(Error::Model(ModelError::InvalidUser));
                }
            }
        }
        let mut builder = self._prepare_edit_builder();
        f(&mut builder);
        self._send_edit(cache_http.http(), builder).await
    }

    fn _prepare_edit_builder<'a>(&self) -> EditMessage<'a> {
        let mut builder = EditMessage::default();

        if !self.content.is_empty() {
            builder.content(&self.content);
        }

        let embeds: Vec<_> = self.embeds.iter().map(|e| CreateEmbed::from(e.clone())).collect();
        builder.set_embeds(embeds);

        for attachment in &self.attachments {
            builder.add_existing_attachment(attachment.id);
        }
        builder
    }

    async fn _send_edit<'a>(&mut self, http: &Http, builder: EditMessage<'a>) -> Result<()> {
        let map = json::hashmap_to_json_map(builder.0);

        *self = http
            .edit_message_and_attachments(
                self.channel_id.0,
                self.id.0,
                &Value::from(map),
                builder.1,
            )
            .await?;
        Ok(())
    }

    pub(crate) fn transform_content(&mut self) {
        match self.kind {
            MessageType::PinsAdd => {
                self.content =
                    format!("{} pinned a message to this channel. See all the pins.", self.author);
            },
            MessageType::MemberJoin => {
                let sec = self.timestamp.unix_timestamp() as usize;
                let chosen = constants::JOIN_MESSAGES[sec % constants::JOIN_MESSAGES.len()];

                self.content = if chosen.contains("$user") {
                    chosen.replace("$user", &self.author.mention().to_string())
                } else {
                    chosen.to_string()
                };
            },
            _ => {},
        }
    }

    /// Returns message content, but with user and role mentions replaced with
    /// names and everyone/here mentions cancelled.
    #[cfg(feature = "cache")]
    pub fn content_safe(&self, cache: impl AsRef<Cache>) -> String {
        let mut result = self.content.clone();

        // First replace all user mentions.
        for u in &self.mentions {
            let mut at_distinct = String::with_capacity(38);
            at_distinct.push('@');
            at_distinct.push_str(&u.name);
            at_distinct.push('#');
            write!(at_distinct, "{:04}", u.discriminator).unwrap();

            let mut m = u.mention().to_string();
            // Check whether we're replacing a nickname mention or a normal mention.
            // `UserId::mention` returns a normal mention. If it isn't present in the message, it's a nickname mention.
            if !result.contains(&m) {
                m.insert(2, '!');
            }

            result = result.replace(&m, &at_distinct);
        }

        // Then replace all role mentions.
        for id in &self.mention_roles {
            let mention = id.mention().to_string();

            if let Some(role) = id.to_role_cached(&cache) {
                result = result.replace(&mention, &format!("@{}", role.name));
            } else {
                result = result.replace(&mention, "@deleted-role");
            }
        }

        // And finally replace everyone and here mentions.
        result.replace("@everyone", "@\u{200B}everyone").replace("@here", "@\u{200B}here")
    }

    /// Gets the list of [`User`]s who have reacted to a [`Message`] with a
    /// certain [`Emoji`].
    ///
    /// The default `limit` is `50` - specify otherwise to receive a different
    /// maximum number of users. The maximum that may be retrieve at a time is
    /// `100`, if a greater number is provided then it is automatically reduced.
    ///
    /// The optional `after` attribute is to retrieve the users after a certain
    /// user. This is useful for pagination.
    ///
    /// **Note**: Requires the [Read Message History] permission.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Http`] if the current user lacks permission.
    ///
    /// [Read Message History]: Permissions::READ_MESSAGE_HISTORY
    #[inline]
    pub async fn reaction_users(
        &self,
        http: impl AsRef<Http>,
        reaction_type: impl Into<ReactionType>,
        limit: Option<u8>,
        after: impl Into<Option<UserId>>,
    ) -> Result<Vec<User>> {
        self.channel_id.reaction_users(&http, self.id, reaction_type, limit, after).await
    }

    /// Returns the associated [`Guild`] for the message if one is in the cache.
    ///
    /// Returns [`None`] if the guild's Id could not be found via [`Self::guild_id`] or
    /// if the Guild itself is not cached.
    ///
    /// Requires the `cache` feature be enabled.
    #[cfg(feature = "cache")]
    pub fn guild(&self, cache: impl AsRef<Cache>) -> Option<Guild> {
        cache.as_ref().guild(self.guild_id?)
    }

    /// Returns a field to the [`Guild`] for the message if one is in the cache.
    /// The field can be selected via the `field_accessor`.
    ///
    /// Returns [`None`] if the guild's ID could not be found via [`Self::guild_id`] or
    /// if the Guild itself is not cached.
    ///
    /// Requires the `cache` feature be enabled.
    #[cfg(feature = "cache")]
    pub fn guild_field<Ret, Fun>(
        &self,
        cache: impl AsRef<Cache>,
        field_accessor: Fun,
    ) -> Option<Ret>
    where
        Ret: Clone,
        Fun: FnOnce(&Guild) -> Ret,
    {
        cache.as_ref().guild_field(self.guild_id?, field_accessor)
    }

    /// True if message was sent using direct messages.
    #[inline]
    #[must_use]
    pub fn is_private(&self) -> bool {
        self.guild_id.is_none()
    }

    /// Retrieves a clone of the author's Member instance, if this message was
    /// sent in a guild.
    ///
    /// If the instance cannot be found in the cache, or the `cache` feature is
    /// disabled, a HTTP request is performed to retrieve it from Discord's API.
    ///
    /// # Errors
    ///
    /// [`ModelError::ItemMissing`] is returned if [`Self::guild_id`] is [`None`].
    pub async fn member(&self, cache_http: impl CacheHttp) -> Result<Member> {
        let guild_id = match self.guild_id {
            Some(guild_id) => guild_id,
            None => return Err(Error::Model(ModelError::ItemMissing)),
        };

        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if let Some(member) = cache.member(guild_id, self.author.id) {
                    return Ok(member);
                }
            }
        }

        cache_http.http().get_member(guild_id.0, self.author.id.0).await
    }

    /// Checks the length of a string to ensure that it is within Discord's
    /// maximum message length limit.
    ///
    /// Returns [`None`] if the message is within the limit, otherwise returns
    /// [`Some`] with an inner value of how many unicode code points the message
    /// is over.
    #[must_use]
    pub fn overflow_length(content: &str) -> Option<usize> {
        // Check if the content is over the maximum number of unicode code
        // points.
        let count = content.chars().count();

        if count > constants::MESSAGE_CODE_LIMIT {
            Some(count - constants::MESSAGE_CODE_LIMIT)
        } else {
            None
        }
    }

    /// Pins this message to its channel.
    ///
    /// **Note**: Requires the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn pin(&self, cache_http: impl CacheHttp) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.guild_id.is_some() {
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::MANAGE_MESSAGES,
                    )?;
                }
            }
        }

        self.channel_id.pin(cache_http.http(), self.id.0).await
    }

    /// React to the message with a custom [`Emoji`] or unicode character.
    ///
    /// **Note**: Requires the [Add Reactions] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have the
    /// required [permissions].
    ///
    /// [Add Reactions]: Permissions::ADD_REACTIONS
    /// [permissions]: super::permissions
    #[inline]
    pub async fn react(
        &self,
        cache_http: impl CacheHttp,
        reaction_type: impl Into<ReactionType>,
    ) -> Result<Reaction> {
        self._react(cache_http, reaction_type.into()).await
    }

    async fn _react(
        &self,
        cache_http: impl CacheHttp,
        reaction_type: ReactionType,
    ) -> Result<Reaction> {
        #[allow(unused_mut)]
        let mut user_id = None;

        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.guild_id.is_some() {
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::ADD_REACTIONS,
                    )?;
                }

                user_id = Some(cache.current_user_id());
            }
        }

        cache_http.http().create_reaction(self.channel_id.0, self.id.0, &reaction_type).await?;

        Ok(Reaction {
            channel_id: self.channel_id,
            emoji: reaction_type,
            message_id: self.id,
            user_id,
            guild_id: self.guild_id,
            member: self.member.clone(),
        })
    }

    /// Uses Discord's inline reply to a user without pinging them.
    ///
    /// User mentions are generally around 20 or 21 characters long.
    ///
    /// **Note**: Requires the [Send Messages] permission.
    ///
    /// **Note**: Message contents must be under 2000 unicode code points.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// Returns a [`ModelError::MessageTooLong`] if the content of the message
    /// is over the above limit, containing the number of unicode code points
    /// over the limit.
    ///
    /// [Send Messages]: Permissions::SEND_MESSAGES
    #[inline]
    pub async fn reply(
        &self,
        cache_http: impl CacheHttp,
        content: impl Display,
    ) -> Result<Message> {
        self._reply(cache_http, content, Some(false)).await
    }

    /// Uses Discord's inline reply to a user with a ping.
    ///
    /// **Note**: Requires the [Send Messages] permission.
    ///
    /// **Note**: Message contents must be under 2000 unicode code points.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// Returns a [`ModelError::MessageTooLong`] if the content of the message
    /// is over the above limit, containing the number of unicode code points
    /// over the limit.
    ///
    /// [Send Messages]: Permissions::SEND_MESSAGES
    #[inline]
    pub async fn reply_ping(
        &self,
        cache_http: impl CacheHttp,
        content: impl Display,
    ) -> Result<Message> {
        self._reply(cache_http, content, Some(true)).await
    }

    /// Replies to the user, mentioning them prior to the content in the form
    /// of: `@<USER_ID> YOUR_CONTENT`.
    ///
    /// User mentions are generally around 20 or 21 characters long.
    ///
    /// **Note**: Requires the [Send Messages] permission.
    ///
    /// **Note**: Message contents must be under 2000 unicode code points.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// Returns a [`ModelError::MessageTooLong`] if the content of the message
    /// is over the above limit, containing the number of unicode code points
    /// over the limit.
    ///
    /// [Send Messages]: Permissions::SEND_MESSAGES
    #[inline]
    pub async fn reply_mention(
        &self,
        cache_http: impl CacheHttp,
        content: impl Display,
    ) -> Result<Message> {
        self._reply(cache_http, format!("{} {}", self.author.mention(), content), None).await
    }

    /// `inlined` decides whether this reply is inlined and whether it pings.
    async fn _reply(
        &self,
        cache_http: impl CacheHttp,
        content: impl Display,
        inlined: Option<bool>,
    ) -> Result<Message> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.guild_id.is_some() {
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::SEND_MESSAGES,
                    )?;
                }
            }
        }

        self.channel_id
            .send_message(cache_http.http(), |builder| {
                if let Some(ping_user) = inlined {
                    builder.reference_message(self).allowed_mentions(|f| {
                        f.replied_user(ping_user)
                            // By providing allowed_mentions, Discord disabled _all_ pings by
                            // default so we need to re-enable them
                            .parse(crate::builder::ParseValue::Everyone)
                            .parse(crate::builder::ParseValue::Users)
                            .parse(crate::builder::ParseValue::Roles)
                    });
                }

                builder.content(content)
            })
            .await
    }

    /// Delete all embeds in this message
    /// **Note**: The logged in user must either be the author of the message or
    /// have the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` feature is enabled, then returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// Otherwise returns [`Error::Http`] if the current user lacks permission.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn suppress_embeds(&mut self, cache_http: impl CacheHttp) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                utils::user_has_perms_cache(
                    cache,
                    self.channel_id,
                    self.guild_id,
                    Permissions::MANAGE_MESSAGES,
                )?;

                if self.author.id != cache.current_user_id() {
                    return Err(Error::Model(ModelError::NotAuthor));
                }
            }
        }

        let mut suppress = EditMessage::default();
        suppress.suppress_embeds(true);

        let map = json::hashmap_to_json_map(suppress.0);

        *self =
            cache_http.http().edit_message(self.channel_id.0, self.id.0, &Value::from(map)).await?;

        Ok(())
    }

    /// Checks whether the message mentions passed [`UserId`].
    #[inline]
    pub fn mentions_user_id(&self, id: impl Into<UserId>) -> bool {
        let id = id.into();
        self.mentions.iter().any(|mentioned_user| mentioned_user.id.0 == id.0)
    }

    /// Checks whether the message mentions passed [`User`].
    #[inline]
    #[must_use]
    pub fn mentions_user(&self, user: &User) -> bool {
        self.mentions_user_id(user.id)
    }

    /// Checks whether the message mentions the current user.
    ///
    /// # Errors
    ///
    /// May return [`Error::Http`] if the `cache` feature is not enabled,
    /// or if the cache is otherwise unavailable.
    pub async fn mentions_me(&self, cache_http: impl CacheHttp) -> Result<bool> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                return Ok(self.mentions_user_id(cache.current_user_id()));
            }
        }

        let current_user = cache_http.http().get_current_user().await?;
        Ok(self.mentions_user_id(current_user.id))
    }

    /// Unpins the message from its channel.
    ///
    /// **Note**: Requires the [Manage Messages] permission.
    ///
    /// # Errors
    ///
    /// If the `cache` is enabled, returns a
    /// [`ModelError::InvalidPermissions`] if the current user does not have
    /// the required permissions.
    ///
    /// [Manage Messages]: Permissions::MANAGE_MESSAGES
    pub async fn unpin(&self, cache_http: impl CacheHttp) -> Result<()> {
        #[cfg(feature = "cache")]
        {
            if let Some(cache) = cache_http.cache() {
                if self.guild_id.is_some() {
                    utils::user_has_perms_cache(
                        cache,
                        self.channel_id,
                        self.guild_id,
                        Permissions::MANAGE_MESSAGES,
                    )?;
                }
            }
        }

        cache_http.http().unpin_message(self.channel_id.0, self.id.0, None).await
    }

    /// Tries to return author's nickname in the current channel's guild.
    ///
    /// Refer to [`User::nick_in()`] inside and [`None`] outside of a guild.
    #[inline]
    pub async fn author_nick(&self, cache_http: impl CacheHttp) -> Option<String> {
        self.author.nick_in(cache_http, self.guild_id?).await
    }

    /// Returns a link referencing this message. When clicked, users will jump to the message.
    /// The link will be valid for messages in either private channels or guilds.
    #[inline]
    #[must_use]
    pub fn link(&self) -> String {
        self.id.link(self.channel_id, self.guild_id)
    }

    /// Same as [`Self::link`] but tries to find the [`GuildId`]
    /// if Discord does not provide it.
    ///
    /// [`guild_id`]: Self::guild_id
    #[inline]
    pub async fn link_ensured(&self, cache_http: impl CacheHttp) -> String {
        self.id.link_ensured(cache_http, self.channel_id, self.guild_id).await
    }

    /// Await a single reaction on this message.
    #[cfg(feature = "collector")]
    pub fn await_reaction(&self, shard_messenger: impl AsRef<ShardMessenger>) -> CollectReaction {
        CollectReaction::new(shard_messenger).message_id(self.id.0)
    }

    /// Returns a stream builder which can be awaited to obtain a stream of reactions on this message.
    #[cfg(feature = "collector")]
    pub fn await_reactions(
        &self,
        shard_messenger: impl AsRef<ShardMessenger>,
    ) -> ReactionCollectorBuilder {
        ReactionCollectorBuilder::new(shard_messenger).message_id(self.id.0)
    }

    /// Await a single component interaction on this message.
    #[cfg(feature = "collector")]
    pub fn await_component_interaction(
        &self,
        shard_messenger: impl AsRef<ShardMessenger>,
    ) -> CollectComponentInteraction {
        CollectComponentInteraction::new(shard_messenger).message_id(self.id.0)
    }

    /// Returns a stream builder which can be awaited to obtain a stream of component interactions on this message.
    #[cfg(feature = "collector")]
    pub fn await_component_interactions(
        &self,
        shard_messenger: impl AsRef<ShardMessenger>,
    ) -> ComponentInteractionCollectorBuilder {
        ComponentInteractionCollectorBuilder::new(shard_messenger).message_id(self.id.0)
    }

    /// Await a single modal submit interaction on this message.
    #[cfg(feature = "collector")]
    pub fn await_modal_interaction(
        &self,
        shard_messenger: impl AsRef<ShardMessenger>,
    ) -> CollectModalInteraction {
        CollectModalInteraction::new(shard_messenger).message_id(self.id.0)
    }

    /// Returns a stream builder which can be awaited to obtain a stream of modal submit interactions on this message.
    #[cfg(feature = "collector")]
    pub fn await_modal_interactions(
        &self,
        shard_messenger: impl AsRef<ShardMessenger>,
    ) -> ModalInteractionCollectorBuilder {
        ModalInteractionCollectorBuilder::new(shard_messenger).message_id(self.id.0)
    }

    /// Retrieves the message channel's category ID if the channel has one.
    #[cfg(feature = "cache")]
    pub fn category_id(&self, cache: impl AsRef<Cache>) -> Option<ChannelId> {
        cache.as_ref().channel_category_id(self.channel_id)
    }

    pub(crate) fn check_lengths(map: &JsonMap) -> Result<()> {
        Self::check_content_length(map)?;
        Self::check_embed_length(map)?;
        Self::check_sticker_ids_length(map)?;

        Ok(())
    }

    pub(crate) fn check_content_length(map: &JsonMap) -> Result<()> {
        if let Some(Value::String(content)) = map.get("content") {
            if let Some(length_over) = Message::overflow_length(content) {
                return Err(Error::Model(ModelError::MessageTooLong(length_over)));
            }
        }

        Ok(())
    }

    pub(crate) fn check_embed_length(map: &JsonMap) -> Result<()> {
        let embeds = match map.get("embeds") {
            Some(&Value::Array(ref value)) => value,
            _ => return Ok(()),
        };

        if embeds.len() > 10 {
            return Err(Error::Model(ModelError::EmbedAmount));
        }

        for embed in embeds {
            let mut total: usize = 0;

            if let Some(&Value::Object(ref author)) = embed.get("author") {
                if let Some(&Value::Object(ref name)) = author.get("name") {
                    total += name.len();
                }
            }

            if let Some(&Value::String(ref description)) = embed.get("description") {
                total += description.len();
            }

            if let Some(&Value::Array(ref fields)) = embed.get("fields") {
                for field_as_value in fields {
                    if let Value::Object(ref field) = *field_as_value {
                        if let Some(&Value::String(ref field_name)) = field.get("name") {
                            total += field_name.len();
                        }

                        if let Some(&Value::String(ref field_value)) = field.get("value") {
                            total += field_value.len();
                        }
                    }
                }
            }

            if let Some(&Value::Object(ref footer)) = embed.get("footer") {
                if let Some(&Value::String(ref text)) = footer.get("text") {
                    total += text.len();
                }
            }

            if let Some(&Value::String(ref title)) = embed.get("title") {
                total += title.len();
            }

            if total > constants::EMBED_MAX_LENGTH {
                let overflow = total - constants::EMBED_MAX_LENGTH;
                return Err(Error::Model(ModelError::EmbedTooLarge(overflow)));
            }
        }

        Ok(())
    }

    pub(crate) fn check_sticker_ids_length(map: &JsonMap) -> Result<()> {
        if let Some(Value::Array(sticker_ids)) = map.get("sticker_ids") {
            if sticker_ids.len() > constants::STICKER_MAX_COUNT {
                return Err(Error::Model(ModelError::StickerAmount));
            }
        }

        Ok(())
    }
}

impl AsRef<MessageId> for Message {
    fn as_ref(&self) -> &MessageId {
        &self.id
    }
}

impl From<Message> for MessageId {
    /// Gets the Id of a [`Message`].
    fn from(message: Message) -> MessageId {
        message.id
    }
}

impl<'a> From<&'a Message> for MessageId {
    /// Gets the Id of a [`Message`].
    fn from(message: &Message) -> MessageId {
        message.id
    }
}

/// A representation of a reaction to a message.
///
/// Multiple of the same [reaction type] are sent into one [`MessageReaction`],
/// with an associated [`Self::count`].
///
/// [reaction type]: ReactionType
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReaction {
    /// The amount of the type of reaction that have been sent for the
    /// associated message.
    pub count: u64,
    /// Indicator of whether the current user has sent the type of reaction.
    pub me: bool,
    /// The type of reaction.
    #[serde(rename = "emoji")]
    pub reaction_type: ReactionType,
}

/// Differentiates between regular and different types of system messages.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MessageType {
    /// A regular message.
    Regular = 0,
    /// An indicator that a recipient was added by the author.
    GroupRecipientAddition = 1,
    /// An indicator that a recipient was removed by the author.
    GroupRecipientRemoval = 2,
    /// An indicator that a call was started by the author.
    GroupCallCreation = 3,
    /// An indicator that the group name was modified by the author.
    GroupNameUpdate = 4,
    /// An indicator that the group icon was modified by the author.
    GroupIconUpdate = 5,
    /// An indicator that a message was pinned by the author.
    PinsAdd = 6,
    /// An indicator that a member joined the guild.
    MemberJoin = 7,
    /// An indicator that someone has boosted the guild.
    NitroBoost = 8,
    /// An indicator that the guild has reached nitro tier 1
    NitroTier1 = 9,
    /// An indicator that the guild has reached nitro tier 2
    NitroTier2 = 10,
    /// An indicator that the guild has reached nitro tier 3
    NitroTier3 = 11,
    /// An indicator that the channel is now following a news channel
    ChannelFollowAdd = 12,
    /// An indicator that the guild is disqualified for Discovery Feature
    GuildDiscoveryDisqualified = 14,
    /// An indicator that the guild is requalified for Discovery Feature
    GuildDiscoveryRequalified = 15,
    /// The first warning before guild discovery removal.
    GuildDiscoveryGracePeriodInitialWarning = 16,
    /// The last warning before guild discovery removal.
    GuildDiscoveryGracePeriodFinalWarning = 17,
    /// Message sent to inform users that a thread was created.
    ThreadCreated = 18,
    /// A message reply.
    InlineReply = 19,
    /// A slash command.
    ChatInputCommand = 20,
    /// A thread start message.
    ThreadStarterMessage = 21,
    /// Server setup tips.
    GuildInviteReminder = 22,
    /// A context menu command.
    ContextMenuCommand = 23,
    /// A message from an auto moderation action.
    AutoModerationAction = 24,
    /// An indicator that the message is of unknown type.
    Unknown = !0,
}

enum_number!(MessageType {
    Regular,
    GroupRecipientAddition,
    GroupRecipientRemoval,
    GroupCallCreation,
    GroupNameUpdate,
    GroupIconUpdate,
    PinsAdd,
    MemberJoin,
    NitroBoost,
    NitroTier1,
    NitroTier2,
    NitroTier3,
    ChannelFollowAdd,
    GuildDiscoveryDisqualified,
    GuildDiscoveryRequalified,
    GuildDiscoveryGracePeriodInitialWarning,
    GuildDiscoveryGracePeriodFinalWarning
    ThreadCreated,
    InlineReply,
    ChatInputCommand,
    ThreadStarterMessage,
    GuildInviteReminder,
    ContextMenuCommand,
    AutoModerationAction,
});

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MessageActivityKind {
    #[allow(clippy::upper_case_acronyms)]
    JOIN = 1,
    #[allow(clippy::upper_case_acronyms)]
    SPECTATE = 2,
    #[allow(clippy::upper_case_acronyms)]
    LISTEN = 3,
    #[allow(non_camel_case_types)]
    JOIN_REQUEST = 5,
    Unknown = !0,
}

enum_number!(MessageActivityKind {
    JOIN,
    SPECTATE,
    LISTEN,
    JOIN_REQUEST
});

/// Rich Presence application information.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageApplication {
    /// ID of the application.
    pub id: ApplicationId,
    /// ID of the embed's image asset.
    pub cover_image: Option<String>,
    /// Application's description.
    pub description: String,
    /// ID of the application's icon.
    pub icon: Option<String>,
    /// Name of the application.
    pub name: String,
}

/// Rich Presence activity information.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageActivity {
    /// Kind of message activity.
    #[serde(rename = "type")]
    pub kind: MessageActivityKind,
    /// `party_id` from a Rich Presence event.
    pub party_id: Option<String>,
}

/// Reference data sent with crossposted messages.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReference {
    /// ID of the originating message.
    pub message_id: Option<MessageId>,
    /// ID of the originating message's channel.
    pub channel_id: ChannelId,
    /// ID of the originating message's guild.
    pub guild_id: Option<GuildId>,
}

impl From<&Message> for MessageReference {
    fn from(m: &Message) -> Self {
        Self {
            message_id: Some(m.id),
            channel_id: m.channel_id,
            guild_id: m.guild_id,
        }
    }
}

impl From<(ChannelId, MessageId)> for MessageReference {
    fn from(pair: (ChannelId, MessageId)) -> Self {
        Self {
            message_id: Some(pair.1),
            channel_id: pair.0,
            guild_id: None,
        }
    }
}

/// Channel Mention Object
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ChannelMention {
    /// ID of the channel.
    pub id: ChannelId,
    /// ID of the guild containing the channel.
    pub guild_id: GuildId,
    /// The kind of channel
    #[serde(rename = "type")]
    pub kind: ChannelType,
    /// The name of the channel
    pub name: String,
}

bitflags! {
    /// Describes extra features of the message.
    #[derive(Default)]
    pub struct MessageFlags: u64 {
        /// This message has been published to subscribed channels (via Channel Following).
        const CROSSPOSTED = 1 << 0;
        /// This message originated from a message in another channel (via Channel Following).
        const IS_CROSSPOST = 1 << 1;
        /// Do not include any embeds when serializing this message.
        const SUPPRESS_EMBEDS = 1 << 2;
        /// The source message for this crosspost has been deleted (via Channel Following).
        const SOURCE_MESSAGE_DELETED = 1 << 3;
        /// This message came from the urgent message system.
        const URGENT = 1 << 4;
        /// This message has an associated thread, with the same id as the message.
        const HAS_THREAD = 1 << 5;
        /// This message is only visible to the user who invoked the Interaction.
        const EPHEMERAL = 1 << 6;
        /// This message is an Interaction Response and the bot is "thinking".
        const LOADING = 1 << 7;
        /// This message failed to mention some roles and add their members to the thread.
        const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
    }
}

#[cfg(feature = "model")]
impl MessageId {
    /// Returns a link referencing this message. When clicked, users will jump to the message.
    /// The link will be valid for messages in either private channels or guilds.
    #[must_use]
    pub fn link(&self, channel_id: ChannelId, guild_id: Option<GuildId>) -> String {
        if let Some(guild_id) = guild_id {
            format!("https://discord.com/channels/{}/{}/{}", guild_id, channel_id, self)
        } else {
            format!("https://discord.com/channels/@me/{}/{}", channel_id, self)
        }
    }

    /// Same as [`Self::link`] but tries to find the [`GuildId`]
    /// if it is not provided.
    pub async fn link_ensured(
        &self,
        cache_http: impl CacheHttp,
        channel_id: ChannelId,
        mut guild_id: Option<GuildId>,
    ) -> String {
        if guild_id.is_none() {
            let found_channel = channel_id.to_channel(cache_http).await;

            if let Ok(channel) = found_channel {
                if let Some(c) = channel.guild() {
                    guild_id = Some(c.guild_id);
                }
            }
        }

        self.link(channel_id, guild_id)
    }
}