Skip to main content

stoat/commands/
converter.rs

1use std::{fmt::Debug, sync::LazyLock};
2
3use async_trait::async_trait;
4use regex::Regex;
5use stoat_models::v0::{Channel, Emoji, Member, Role, User};
6
7use crate::{Error, commands::Context};
8
9static ID_REGEX: LazyLock<Regex> =
10    LazyLock::new(|| Regex::new("^([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})$").unwrap());
11static USER_REGEX: LazyLock<Regex> =
12    LazyLock::new(|| Regex::new("^<@([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
13static CHANNEL_REGEX: LazyLock<Regex> =
14    LazyLock::new(|| Regex::new("^<#([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
15static ROLE_REGEX: LazyLock<Regex> =
16    LazyLock::new(|| Regex::new("^<%([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
17static EMOJI_REGEX: LazyLock<Regex> =
18    LazyLock::new(|| Regex::new("^:([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}):$").unwrap());
19
20#[async_trait]
21pub trait Converter<
22    E: From<Error> + Clone + Debug + Send + Sync + 'static,
23    S: Debug + Clone + Send + Sync + 'static,
24>: Sized
25{
26    async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
27        let input = context.words.next().ok_or(Error::MissingParameter)?;
28
29        Self::convert(context, input).await
30    }
31
32    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E>;
33}
34
35macro_rules! impl_parse_converter {
36    ($ty:ty) => {
37        #[async_trait]
38        impl<
39            E: From<Error> + Clone + Debug + Send + Sync + 'static,
40            S: Debug + Clone + Send + Sync + 'static,
41        > Converter<E, S> for $ty
42        {
43            async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
44                input
45                    .parse::<$ty>()
46                    .map_err(|e| Error::ConverterError(e.to_string()).into())
47            }
48        }
49    };
50}
51
52impl_parse_converter!(u8);
53impl_parse_converter!(u16);
54impl_parse_converter!(u32);
55impl_parse_converter!(u64);
56impl_parse_converter!(u128);
57impl_parse_converter!(i8);
58impl_parse_converter!(i16);
59impl_parse_converter!(i32);
60impl_parse_converter!(i64);
61impl_parse_converter!(i128);
62impl_parse_converter!(f32);
63impl_parse_converter!(f64);
64
65#[async_trait]
66impl<
67    E: From<Error> + Clone + Debug + Send + Sync + 'static,
68    S: Debug + Clone + Send + Sync + 'static,
69> Converter<E, S> for String
70{
71    async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
72        Ok(input)
73    }
74}
75
76#[async_trait]
77impl<
78    E: From<Error> + Clone + Debug + Send + Sync + 'static,
79    S: Debug + Clone + Send + Sync + 'static,
80> Converter<E, S> for bool
81{
82    async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
83        match input.to_lowercase().as_str() {
84            "yes" | "y" | "true" | "t" | "1" | "enable" | "enabled" | "on" => Ok(true),
85            "no" | "n" | "false" | "f" | "0" | "disable" | "disabled" | "off" => Ok(false),
86            _ => Err(Error::ConverterError("Bad boolean value".to_string()).into()),
87        }
88    }
89}
90
91#[async_trait]
92impl<
93    E: From<Error> + Clone + Debug + Send + Sync + 'static,
94    S: Debug + Clone + Send + Sync + 'static,
95> Converter<E, S> for User
96{
97    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
98        if let Some(captures) = USER_REGEX
99            .captures(&input)
100            .or_else(|| ID_REGEX.captures(&input))
101        {
102            let id = captures.get(1).unwrap().as_str();
103
104            let user = context.cache.get_user(id);
105
106            if let Some(user) = user {
107                return Ok(user.clone());
108            } else if let Ok(user) = context.http.fetch_user(id).await {
109                return Ok(user);
110            };
111        };
112
113        Err(Error::ConverterError("User not found".to_string()).into())
114    }
115}
116
117#[async_trait]
118impl<
119    E: From<Error> + Clone + Debug + Send + Sync + 'static,
120    S: Debug + Clone + Send + Sync + 'static,
121> Converter<E, S> for Channel
122{
123    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
124        if let Some(captures) = CHANNEL_REGEX
125            .captures(&input)
126            .or_else(|| ID_REGEX.captures(&input))
127        {
128            let id = captures.get(1).unwrap().as_str();
129
130            if let Some(channel) = context.cache.get_channel(id) {
131                return Ok(channel);
132            }
133        };
134
135        Err(Error::ConverterError("Channel not found".to_string()).into())
136    }
137}
138
139#[async_trait]
140impl<
141    E: From<Error> + Clone + Debug + Send + Sync + 'static,
142    S: Debug + Clone + Send + Sync + 'static,
143> Converter<E, S> for Role
144{
145    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
146        let Ok(server) = context.get_current_server() else {
147            return Err(Error::ConverterError("Role not found".to_string()).into());
148        };
149
150        if let Some(captures) = ROLE_REGEX
151            .captures(&input)
152            .or_else(|| ID_REGEX.captures(&input))
153        {
154            let id = captures.get(1).unwrap().as_str();
155
156            if let Some(role) = server.roles.get(id) {
157                return Ok(role.clone());
158            }
159        };
160
161        Err(Error::ConverterError("Role not found".to_string()).into())
162    }
163}
164
165#[async_trait]
166impl<
167    E: From<Error> + Clone + Debug + Send + Sync + 'static,
168    S: Debug + Clone + Send + Sync + 'static,
169> Converter<E, S> for Member
170{
171    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
172        if let Ok(server) = context.get_current_server() {
173            let user = <User as Converter<E, S>>::convert(context, input).await?;
174
175            if let Some(member) = context.cache.get_member(&server.id, &user.id) {
176                return Ok(member);
177            } else if let Ok(member) = context.http.fetch_member(&server.id, &user.id).await {
178                return Ok(member);
179            };
180        };
181
182        Err(Error::ConverterError("Member not found".to_string()).into())
183    }
184}
185
186#[async_trait]
187impl<
188    E: From<Error> + Clone + Debug + Send + Sync + 'static,
189    S: Debug + Clone + Send + Sync + 'static,
190> Converter<E, S> for Emoji
191{
192    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
193        if let Some(captures) = EMOJI_REGEX
194            .captures(&input)
195            .or_else(|| ID_REGEX.captures(&input))
196        {
197            let id = captures.get(1).unwrap().as_str();
198
199            if let Some(emoji) = context.cache.get_emoji(id) {
200                return Ok(emoji);
201            }
202        } else {
203            if let Some(emoji) = context
204                .cache
205                .emojis
206                .any_sync(|_, emoji| &emoji.name == &input)
207            {
208                return Ok(emoji.get().clone());
209            }
210        };
211
212        Err(Error::ConverterError("Emoji not found".to_string()).into())
213    }
214}
215
216pub struct ConsumeRest(pub String);
217
218#[async_trait]
219impl<
220    E: From<Error> + Clone + Debug + Send + Sync + 'static,
221    S: Debug + Clone + Send + Sync + 'static,
222> Converter<E, S> for ConsumeRest
223{
224    async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
225        let words = context.words.rest();
226
227        Ok(Self(words.join(" ")))
228    }
229
230    async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
231        unreachable!()
232    }
233}
234
235#[cfg(feature = "either")]
236#[async_trait]
237impl<
238    E: From<Error> + Clone + Debug + Send + Sync + 'static,
239    S: Debug + Clone + Send + Sync + 'static,
240    L: Converter<E, S>,
241    R: Converter<E, S>,
242> Converter<E, S> for either::Either<L, R>
243{
244    async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
245        if let Ok(left) = L::convert(context, input.clone()).await {
246            Ok(either::Either::Left(left))
247        } else {
248            R::convert(context, input).await.map(either::Either::Right)
249        }
250    }
251}
252
253#[async_trait]
254impl<
255    E: From<Error> + Clone + Debug + Send + Sync + 'static,
256    S: Debug + Clone + Send + Sync + 'static,
257    T: Converter<E, S>,
258> Converter<E, S> for Option<T>
259{
260    async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
261        let Some(input) = context.words.next() else {
262            return Ok(None);
263        };
264
265        Ok(T::convert(context, input).await.ok())
266    }
267
268    async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
269        unreachable!()
270    }
271}
272
273pub struct Greedy<T>(pub Vec<T>);
274
275#[async_trait]
276impl<
277    E: From<Error> + Clone + Debug + Send + Sync + 'static,
278    S: Debug + Clone + Send + Sync + 'static,
279    T: Converter<E, S> + Send + Sync,
280> Converter<E, S> for Greedy<T>
281{
282    async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
283        let mut converted = Vec::new();
284
285        while let Some(arg) = context.words.next() {
286            if let Ok(value) = T::convert(context, arg).await {
287                converted.push(value);
288            } else {
289                context.words.undo();
290                break;
291            }
292        }
293
294        Ok(Self(converted))
295    }
296
297    async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
298        unreachable!()
299    }
300}
301
302#[async_trait]
303impl<
304    E: From<Error> + Clone + Debug + Send + Sync + 'static,
305    S: Debug + Clone + Send + Sync + 'static,
306    T: Converter<E, S> + Send + Sync,
307> Converter<E, S> for Vec<T>
308{
309    async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
310        let mut converted = Vec::new();
311
312        while let Some(arg) = context.words.next() {
313            converted.push(T::convert(context, arg).await?);
314        }
315
316        Ok(converted)
317    }
318
319    async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
320        unreachable!()
321    }
322}