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}