serenity/utils/argument_convert/
member.rs

1use std::fmt;
2
3use super::ArgumentConvert;
4use crate::model::prelude::*;
5use crate::prelude::*;
6
7/// Error that can be returned from [`Member::convert`].
8#[non_exhaustive]
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub enum MemberParseError {
11    /// Parser was invoked outside a guild.
12    OutsideGuild,
13    /// The guild in which the parser was invoked is not in cache.
14    GuildNotInCache,
15    /// The provided member string failed to parse, or the parsed result cannot be found in the
16    /// guild cache data.
17    NotFoundOrMalformed,
18}
19
20impl std::error::Error for MemberParseError {}
21
22impl fmt::Display for MemberParseError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            Self::OutsideGuild => f.write_str("Tried to find member outside a guild"),
26            Self::GuildNotInCache => f.write_str("Guild is not in cache"),
27            Self::NotFoundOrMalformed => f.write_str("Member not found or unknown format"),
28        }
29    }
30}
31
32/// Look up a guild member by a string case-insensitively.
33///
34/// Requires the cache feature to be enabled.
35///
36/// The lookup strategy is as follows (in order):
37/// 1. Lookup by ID.
38/// 2. [Lookup by mention](`crate::utils::parse_username`).
39/// 3. [Lookup by name#discrim](`crate::utils::parse_user_tag`).
40/// 4. Lookup by name
41/// 5. Lookup by nickname
42#[async_trait::async_trait]
43impl ArgumentConvert for Member {
44    type Err = MemberParseError;
45
46    async fn convert(
47        ctx: impl CacheHttp,
48        guild_id: Option<GuildId>,
49        _channel_id: Option<ChannelId>,
50        s: &str,
51    ) -> Result<Self, Self::Err> {
52        let guild_id = guild_id.ok_or(MemberParseError::OutsideGuild)?;
53
54        // DON'T use guild.members: it's only populated when guild presences intent is enabled!
55
56        // If string is a raw user ID or a mention
57        if let Some(user_id) = s.parse().ok().or_else(|| crate::utils::parse_user_mention(s)) {
58            if let Ok(member) = guild_id.member(&ctx, user_id).await {
59                return Ok(member);
60            }
61        }
62
63        // Following code is inspired by discord.py's MemberConvert::query_member_named
64
65        // If string is a username+discriminator
66        if let Some((name, discrim)) = crate::utils::parse_user_tag(s) {
67            if let Ok(member_results) = guild_id.search_members(ctx.http(), name, Some(100)).await {
68                if let Some(member) = member_results.into_iter().find(|m| {
69                    m.user.name.eq_ignore_ascii_case(name) && m.user.discriminator == discrim
70                }) {
71                    return Ok(member);
72                }
73            }
74        }
75
76        // If string is username or nickname
77        if let Ok(member_results) = guild_id.search_members(ctx.http(), s, Some(100)).await {
78            if let Some(member) = member_results.into_iter().find(|m| {
79                m.user.name.eq_ignore_ascii_case(s)
80                    || m.nick.as_ref().is_some_and(|nick| nick.eq_ignore_ascii_case(s))
81            }) {
82                return Ok(member);
83            }
84        }
85
86        Err(MemberParseError::NotFoundOrMalformed)
87    }
88}