twilight_model/channel/message/
mention.rs

1use crate::{
2    guild::PartialMember,
3    id::{marker::UserMarker, Id},
4    user::{self, DiscriminatorDisplay, UserFlags},
5    util::image_hash::ImageHash,
6};
7use serde::{Deserialize, Serialize};
8
9/// Mention of a user in a message.
10#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
11pub struct Mention {
12    /// Hash of the user's avatar, if any.
13    pub avatar: Option<ImageHash>,
14    /// Whether the user is a bot.
15    #[serde(default)]
16    pub bot: bool,
17    /// Discriminator used to differentiate people with the same username.
18    ///
19    /// # serde
20    ///
21    /// The discriminator field can be deserialized from either a string or an
22    /// integer. The field will always serialize into a string due to that being
23    /// the type Discord's API uses.
24    #[serde(with = "user::discriminator")]
25    pub discriminator: u16,
26    /// Unique ID of the user.
27    pub id: Id<UserMarker>,
28    /// Member object for the user in the guild, if available.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub member: Option<PartialMember>,
31    #[serde(rename = "username")]
32    /// Username of the user.
33    pub name: String,
34    /// Public flags on the user's account.
35    pub public_flags: UserFlags,
36}
37
38impl Mention {
39    /// Create a [`Display`] formatter for a user discriminator.
40    ///
41    /// [`Display`]: core::fmt::Display
42    pub const fn discriminator(&self) -> DiscriminatorDisplay {
43        DiscriminatorDisplay::new(self.discriminator)
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::{Mention, PartialMember, UserFlags};
50    use crate::{
51        guild::MemberFlags,
52        id::Id,
53        util::datetime::{Timestamp, TimestampParseError},
54    };
55    use serde_test::Token;
56    use std::str::FromStr;
57
58    #[test]
59    fn mention_without_member() {
60        let value = Mention {
61            avatar: None,
62            bot: false,
63            discriminator: 1,
64            id: Id::new(1),
65            member: None,
66            name: "foo".to_owned(),
67            public_flags: UserFlags::empty(),
68        };
69
70        serde_test::assert_tokens(
71            &value,
72            &[
73                Token::Struct {
74                    name: "Mention",
75                    len: 6,
76                },
77                Token::Str("avatar"),
78                Token::None,
79                Token::Str("bot"),
80                Token::Bool(false),
81                Token::Str("discriminator"),
82                Token::Str("0001"),
83                Token::Str("id"),
84                Token::NewtypeStruct { name: "Id" },
85                Token::Str("1"),
86                Token::Str("username"),
87                Token::Str("foo"),
88                Token::Str("public_flags"),
89                Token::U64(0),
90                Token::StructEnd,
91            ],
92        );
93    }
94
95    #[test]
96    fn mention_with_member() -> Result<(), TimestampParseError> {
97        let joined_at = Some(Timestamp::from_str("2015-04-26T06:26:56.936000+00:00")?);
98        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
99
100        let value = Mention {
101            avatar: None,
102            bot: false,
103            discriminator: 1,
104            id: Id::new(1),
105            member: Some(PartialMember {
106                avatar: None,
107                communication_disabled_until: None,
108                deaf: false,
109                flags,
110                joined_at,
111                mute: true,
112                nick: Some("bar".to_owned()),
113                permissions: None,
114                premium_since: None,
115                roles: Vec::new(),
116                user: None,
117            }),
118            name: "foo".to_owned(),
119            public_flags: UserFlags::empty(),
120        };
121
122        serde_test::assert_tokens(
123            &value,
124            &[
125                Token::Struct {
126                    name: "Mention",
127                    len: 7,
128                },
129                Token::Str("avatar"),
130                Token::None,
131                Token::Str("bot"),
132                Token::Bool(false),
133                Token::Str("discriminator"),
134                Token::Str("0001"),
135                Token::Str("id"),
136                Token::NewtypeStruct { name: "Id" },
137                Token::Str("1"),
138                Token::Str("member"),
139                Token::Some,
140                Token::Struct {
141                    name: "PartialMember",
142                    len: 8,
143                },
144                Token::Str("communication_disabled_until"),
145                Token::None,
146                Token::Str("deaf"),
147                Token::Bool(false),
148                Token::Str("flags"),
149                Token::U64(flags.bits()),
150                Token::Str("joined_at"),
151                Token::Some,
152                Token::Str("2015-04-26T06:26:56.936000+00:00"),
153                Token::Str("mute"),
154                Token::Bool(true),
155                Token::Str("nick"),
156                Token::Some,
157                Token::Str("bar"),
158                Token::Str("roles"),
159                Token::Seq { len: Some(0) },
160                Token::SeqEnd,
161                Token::Str("user"),
162                Token::None,
163                Token::StructEnd,
164                Token::Str("username"),
165                Token::Str("foo"),
166                Token::Str("public_flags"),
167                Token::U64(0),
168                Token::StructEnd,
169            ],
170        );
171        Ok(())
172    }
173}