serenity/utils/argument_convert/
user.rs

1use std::fmt;
2
3use super::ArgumentConvert;
4use crate::model::prelude::*;
5use crate::prelude::*;
6
7/// Error that can be returned from [`User::convert`].
8#[non_exhaustive]
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub enum UserParseError {
11    /// The provided user string failed to parse, or the parsed result cannot be found in the guild
12    /// cache data.
13    NotFoundOrMalformed,
14}
15
16impl std::error::Error for UserParseError {}
17
18impl fmt::Display for UserParseError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::NotFoundOrMalformed => f.write_str("User not found or unknown format"),
22        }
23    }
24}
25
26#[cfg(feature = "cache")]
27fn lookup_by_global_cache(ctx: impl CacheHttp, s: &str) -> Option<User> {
28    let users = &ctx.cache()?.users;
29
30    let lookup_by_id = || users.get(&s.parse().ok()?).map(|u| u.clone());
31
32    let lookup_by_mention = || users.get(&crate::utils::parse_user_mention(s)?).map(|u| u.clone());
33
34    let lookup_by_name_and_discrim = || {
35        let (name, discrim) = crate::utils::parse_user_tag(s)?;
36        users.iter().find_map(|m| {
37            let user = m.value();
38            (user.discriminator == discrim && user.name.eq_ignore_ascii_case(name))
39                .then(|| user.clone())
40        })
41    };
42
43    let lookup_by_name = || {
44        users.iter().find_map(|m| {
45            let user = m.value();
46            (user.name == s).then(|| user.clone())
47        })
48    };
49
50    lookup_by_id()
51        .or_else(lookup_by_mention)
52        .or_else(lookup_by_name_and_discrim)
53        .or_else(lookup_by_name)
54}
55
56/// Look up a user by a string case-insensitively.
57///
58/// Requires the cache feature to be enabled. If a user is not in cache, they will not be found!
59///
60/// The lookup strategy is as follows (in order):
61/// 1. Lookup by ID.
62/// 2. [Lookup by mention](`crate::utils::parse_username`).
63/// 3. [Lookup by name#discrim](`crate::utils::parse_user_tag`).
64/// 4. Lookup by name
65#[async_trait::async_trait]
66impl ArgumentConvert for User {
67    type Err = UserParseError;
68
69    async fn convert(
70        ctx: impl CacheHttp,
71        guild_id: Option<GuildId>,
72        channel_id: Option<ChannelId>,
73        s: &str,
74    ) -> Result<Self, Self::Err> {
75        // Try to look up in global user cache via a variety of methods
76        #[cfg(feature = "cache")]
77        if let Some(user) = lookup_by_global_cache(&ctx, s) {
78            return Ok(user);
79        }
80
81        // If not successful, convert as a Member which uses HTTP endpoints instead of cache
82        if let Ok(member) = Member::convert(&ctx, guild_id, channel_id, s).await {
83            return Ok(member.user);
84        }
85
86        // If string is a raw user ID or a mention
87        if let Some(user_id) = s.parse().ok().or_else(|| crate::utils::parse_user_mention(s)) {
88            // Now, we can still try UserId::to_user because it works for all users from all guilds
89            // the bot is joined
90            if let Ok(user) = user_id.to_user(&ctx).await {
91                return Ok(user);
92            }
93        }
94
95        Err(UserParseError::NotFoundOrMalformed)
96    }
97}