serenity/model/
mention.rs

1#[cfg(all(feature = "model", feature = "utils"))]
2use std::error::Error as StdError;
3use std::fmt;
4#[cfg(all(feature = "model", feature = "utils"))]
5use std::str::FromStr;
6
7use super::prelude::*;
8#[cfg(all(feature = "model", feature = "utils"))]
9use crate::utils;
10
11/// Allows something - such as a channel or role - to be mentioned in a message.
12pub trait Mentionable {
13    /// Creates a [`Mention`] that will be able to notify or create a link to the item.
14    ///
15    /// [`Mention`] implements [`Display`], so [`ToString::to_string()`] can be called on it, or
16    /// inserted directly into a [`format_args!`] type of macro.
17    ///
18    /// [`Display`]: fmt::Display
19    ///
20    /// # Examples
21    ///
22    /// ```
23    /// # #[cfg(feature = "client")] {
24    /// # use serenity::builder::CreateMessage;
25    /// # use serenity::model::guild::Member;
26    /// # use serenity::model::channel::GuildChannel;
27    /// # use serenity::model::id::ChannelId;
28    /// # use serenity::prelude::Context;
29    /// # use serenity::Error;
30    /// use serenity::model::mention::Mentionable;
31    /// async fn greet(
32    ///     ctx: Context,
33    ///     member: Member,
34    ///     to_channel: GuildChannel,
35    ///     rules_channel: ChannelId,
36    /// ) -> Result<(), Error> {
37    ///     let builder = CreateMessage::new().content(format!(
38    ///         "Hi {member}, welcome to the server! \
39    ///         Please refer to {rules} for our code of conduct, \
40    ///         and enjoy your stay.",
41    ///         member = member.mention(),
42    ///         rules = rules_channel.mention(),
43    ///     ));
44    ///     to_channel.id.send_message(ctx, builder).await?;
45    ///     Ok(())
46    /// }
47    /// # }
48    /// ```
49    /// ```
50    /// # use serenity::model::id::{RoleId, ChannelId, UserId};
51    /// use serenity::model::mention::Mentionable;
52    /// let user = UserId::new(1);
53    /// let channel = ChannelId::new(2);
54    /// let role = RoleId::new(3);
55    /// assert_eq!(
56    ///     "<@1> <#2> <@&3>",
57    ///     format!("{} {} {}", user.mention(), channel.mention(), role.mention(),),
58    /// )
59    /// ```
60    fn mention(&self) -> Mention;
61}
62
63/// A struct that represents some way to insert a notification, link, or emoji into a message.
64///
65/// [`Display`] is the primary way of utilizing a [`Mention`], either in a [`format_args!`] type of
66/// macro or with [`ToString::to_string()`]. A [`Mention`] is created using
67/// [`Mentionable::mention()`], or with [`From`]/[`Into`].
68///
69/// [`Display`]: fmt::Display
70///
71/// # Examples
72///
73/// ```
74/// # use serenity::model::id::{RoleId, ChannelId, UserId};
75/// use serenity::model::mention::Mention;
76/// let user = UserId::new(1);
77/// let channel = ChannelId::new(2);
78/// let role = RoleId::new(3);
79/// assert_eq!(
80///     "<@1> <#2> <@&3>",
81///     format!("{} {} {}", Mention::from(user), Mention::from(channel), Mention::from(role),),
82/// )
83/// ```
84#[derive(Clone, Copy, Debug)]
85pub enum Mention {
86    Channel(ChannelId),
87    Role(RoleId),
88    User(UserId),
89}
90
91macro_rules! mention {
92    ($i:ident: $($t:ty, $e:expr;)*) => {$(
93        impl From<$t> for Mention {
94            #[inline(always)]
95            fn from($i: $t) -> Self {
96                $e
97            }
98        }
99    )*};
100}
101
102mention!(value:
103    ChannelId, Mention::Channel(value);
104    RoleId, Mention::Role(value);
105    UserId, Mention::User(value);
106);
107
108impl fmt::Display for Mention {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match *self {
111            Mention::Channel(id) => f.write_fmt(format_args!("<#{id}>")),
112            Mention::Role(id) => f.write_fmt(format_args!("<@&{id}>")),
113            Mention::User(id) => f.write_fmt(format_args!("<@{id}>")),
114        }
115    }
116}
117
118#[cfg(all(feature = "model", feature = "utils"))]
119#[derive(Debug)]
120pub enum MentionParseError {
121    InvalidMention,
122}
123
124#[cfg(all(feature = "model", feature = "utils"))]
125impl fmt::Display for MentionParseError {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.write_str("invalid mention")
128    }
129}
130
131#[cfg(all(feature = "model", feature = "utils"))]
132impl StdError for MentionParseError {}
133
134#[cfg(all(feature = "model", feature = "utils"))]
135impl FromStr for Mention {
136    type Err = MentionParseError;
137
138    fn from_str(s: &str) -> StdResult<Self, Self::Err> {
139        let m = if let Some(id) = utils::parse_channel_mention(s) {
140            id.mention()
141        } else if let Some(id) = utils::parse_role_mention(s) {
142            id.mention()
143        } else if let Some(id) = utils::parse_user_mention(s) {
144            id.mention()
145        } else {
146            return Err(MentionParseError::InvalidMention);
147        };
148
149        Ok(m)
150    }
151}
152
153impl<T> Mentionable for T
154where
155    T: Into<Mention> + Copy,
156{
157    fn mention(&self) -> Mention {
158        (*self).into()
159    }
160}
161
162macro_rules! mentionable {
163    ($i:ident: $t:ty, $e:expr) => {
164        impl Mentionable for $t {
165            #[inline(always)]
166            fn mention(&self) -> Mention {
167                let $i = self;
168                $e.into()
169            }
170        }
171    };
172}
173
174#[cfg(feature = "model")]
175mentionable!(value: Channel, value.id());
176
177mentionable!(value: GuildChannel, value.id);
178mentionable!(value: PrivateChannel, value.id);
179mentionable!(value: Member, value.user.id);
180mentionable!(value: CurrentUser, value.id);
181mentionable!(value: User, value.id);
182mentionable!(value: Role, value.id);
183
184#[cfg(feature = "utils")]
185#[cfg(test)]
186mod test {
187    use crate::model::prelude::*;
188
189    #[test]
190    fn test_mention() {
191        let channel = Channel::Guild(GuildChannel {
192            id: ChannelId::new(4),
193            ..Default::default()
194        });
195        let role = Role {
196            id: RoleId::new(2),
197            ..Default::default()
198        };
199        let user = User {
200            id: UserId::new(6),
201            ..Default::default()
202        };
203        let member = Member {
204            user: user.clone(),
205            ..Default::default()
206        };
207
208        assert_eq!(ChannelId::new(1).mention().to_string(), "<#1>");
209        #[cfg(feature = "model")]
210        assert_eq!(channel.mention().to_string(), "<#4>");
211        assert_eq!(member.mention().to_string(), "<@6>");
212        assert_eq!(role.mention().to_string(), "<@&2>");
213        assert_eq!(role.id.mention().to_string(), "<@&2>");
214        assert_eq!(user.mention().to_string(), "<@6>");
215        assert_eq!(user.id.mention().to_string(), "<@6>");
216    }
217}