serenity/utils/argument_convert/
channel.rs

1use std::fmt;
2
3use super::ArgumentConvert;
4use crate::model::prelude::*;
5use crate::prelude::*;
6
7/// Error that can be returned from [`Channel::convert`].
8#[non_exhaustive]
9#[derive(Debug)]
10pub enum ChannelParseError {
11    /// When channel retrieval via HTTP failed
12    Http(SerenityError),
13    /// The provided channel string failed to parse, or the parsed result cannot be found in the
14    /// cache.
15    NotFoundOrMalformed,
16}
17
18impl std::error::Error for ChannelParseError {
19    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
20        match self {
21            Self::Http(e) => Some(e),
22            Self::NotFoundOrMalformed => None,
23        }
24    }
25}
26
27impl fmt::Display for ChannelParseError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Http(_) => f.write_str("Failed to request channel via HTTP"),
31            Self::NotFoundOrMalformed => f.write_str("Channel not found or unknown format"),
32        }
33    }
34}
35
36fn channel_belongs_to_guild(channel: &Channel, guild: GuildId) -> bool {
37    match channel {
38        Channel::Guild(channel) => channel.guild_id == guild,
39        Channel::Private(_channel) => false,
40    }
41}
42
43async fn lookup_channel_global(
44    ctx: impl CacheHttp,
45    guild_id: Option<GuildId>,
46    s: &str,
47) -> Result<Channel, ChannelParseError> {
48    if let Some(channel_id) = s.parse().ok().or_else(|| crate::utils::parse_channel_mention(s)) {
49        return channel_id.to_channel(&ctx).await.map_err(ChannelParseError::Http);
50    }
51
52    let guild_id = guild_id.ok_or(ChannelParseError::NotFoundOrMalformed)?;
53
54    #[cfg(feature = "cache")]
55    if let Some(cache) = ctx.cache() {
56        if let Some(guild) = cache.guild(guild_id) {
57            let channel = guild.channels.values().find(|c| c.name.eq_ignore_ascii_case(s));
58            if let Some(channel) = channel {
59                return Ok(Channel::Guild(channel.clone()));
60            }
61        }
62
63        return Err(ChannelParseError::NotFoundOrMalformed);
64    }
65
66    let channels = ctx.http().get_channels(guild_id).await.map_err(ChannelParseError::Http)?;
67    if let Some(channel) = channels.into_iter().find(|c| c.name.eq_ignore_ascii_case(s)) {
68        Ok(Channel::Guild(channel))
69    } else {
70        Err(ChannelParseError::NotFoundOrMalformed)
71    }
72}
73
74/// Look up a Channel by a string case-insensitively.
75///
76/// Lookup are done via local guild. If in DMs, the global cache is used instead.
77///
78/// The cache feature needs to be enabled.
79///
80/// The lookup strategy is as follows (in order):
81/// 1. Lookup by ID.
82/// 2. [Lookup by mention](`crate::utils::parse_channel`).
83/// 3. Lookup by name.
84#[async_trait::async_trait]
85impl ArgumentConvert for Channel {
86    type Err = ChannelParseError;
87
88    async fn convert(
89        ctx: impl CacheHttp,
90        guild_id: Option<GuildId>,
91        _channel_id: Option<ChannelId>,
92        s: &str,
93    ) -> Result<Self, Self::Err> {
94        let channel = lookup_channel_global(&ctx, guild_id, s).await?;
95
96        // Don't yield for other guilds' channels
97        if let Some(guild_id) = guild_id {
98            if !channel_belongs_to_guild(&channel, guild_id) {
99                return Err(ChannelParseError::NotFoundOrMalformed);
100            }
101        };
102
103        Ok(channel)
104    }
105}
106
107/// Error that can be returned from [`GuildChannel::convert`].
108#[non_exhaustive]
109#[derive(Debug)]
110pub enum GuildChannelParseError {
111    /// When channel retrieval via HTTP failed
112    Http(SerenityError),
113    /// The provided channel string failed to parse, or the parsed result cannot be found in the
114    /// cache.
115    NotFoundOrMalformed,
116    /// When the referenced channel is not a guild channel
117    NotAGuildChannel,
118}
119
120impl std::error::Error for GuildChannelParseError {
121    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
122        match self {
123            Self::Http(e) => Some(e),
124            Self::NotFoundOrMalformed | Self::NotAGuildChannel => None,
125        }
126    }
127}
128
129impl fmt::Display for GuildChannelParseError {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        match self {
132            Self::Http(_) => f.write_str("Failed to request channel via HTTP"),
133            Self::NotFoundOrMalformed => f.write_str("Channel not found or unknown format"),
134            Self::NotAGuildChannel => f.write_str("Channel is not a guild channel"),
135        }
136    }
137}
138
139/// Look up a GuildChannel by a string case-insensitively.
140///
141/// Lookup is done by the global cache, hence the cache feature needs to be enabled.
142///
143/// For more information, see the ArgumentConvert implementation for [`Channel`]
144#[async_trait::async_trait]
145impl ArgumentConvert for GuildChannel {
146    type Err = GuildChannelParseError;
147
148    async fn convert(
149        ctx: impl CacheHttp,
150        guild_id: Option<GuildId>,
151        channel_id: Option<ChannelId>,
152        s: &str,
153    ) -> Result<Self, Self::Err> {
154        match Channel::convert(&ctx, guild_id, channel_id, s).await {
155            Ok(Channel::Guild(channel)) => Ok(channel),
156            Ok(_) => Err(GuildChannelParseError::NotAGuildChannel),
157            Err(ChannelParseError::Http(e)) => Err(GuildChannelParseError::Http(e)),
158            Err(ChannelParseError::NotFoundOrMalformed) => {
159                Err(GuildChannelParseError::NotFoundOrMalformed)
160            },
161        }
162    }
163}