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, ulid::Ulid};
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 Ulid
96{
97 async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
98 Ulid::from_string(input)
99 .map_err(|_| Error::ConverterError("Bad id value".to_string()).into())
100 }
101}
102
103#[async_trait]
104impl<
105 E: From<Error> + Clone + Debug + Send + Sync + 'static,
106 S: Debug + Clone + Send + Sync + 'static,
107> Converter<E, S> for User
108{
109 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
110 if let Some(captures) = USER_REGEX
111 .captures(&input)
112 .or_else(|| ID_REGEX.captures(&input))
113 {
114 let id = captures.get(1).unwrap().as_str();
115
116 let user = context.cache.get_user(id);
117
118 if let Some(user) = user {
119 return Ok(user.clone());
120 } else if let Ok(user) = context.http.fetch_user(id).await {
121 return Ok(user);
122 };
123 };
124
125 Err(Error::ConverterError("User not found".to_string()).into())
126 }
127}
128
129#[async_trait]
130impl<
131 E: From<Error> + Clone + Debug + Send + Sync + 'static,
132 S: Debug + Clone + Send + Sync + 'static,
133> Converter<E, S> for Channel
134{
135 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
136 if let Some(captures) = CHANNEL_REGEX
137 .captures(&input)
138 .or_else(|| ID_REGEX.captures(&input))
139 {
140 let id = captures.get(1).unwrap().as_str();
141
142 if let Some(channel) = context.cache.get_channel(id) {
143 return Ok(channel);
144 }
145 } else if let Some(entry) = context
146 .cache
147 .channels
148 .any_async(|_, channel| match channel {
149 Channel::TextChannel { name, .. } => name == &input,
150 _ => false,
151 })
152 .await
153 {
154 return Ok(entry.get().clone());
155 }
156
157 Err(Error::ConverterError("Channel not found".to_string()).into())
158 }
159}
160
161#[async_trait]
162impl<
163 E: From<Error> + Clone + Debug + Send + Sync + 'static,
164 S: Debug + Clone + Send + Sync + 'static,
165> Converter<E, S> for Role
166{
167 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
168 let Ok(server) = context.get_current_server() else {
169 return Err(Error::ConverterError("Role not found".to_string()).into());
170 };
171
172 if let Some(captures) = ROLE_REGEX
173 .captures(&input)
174 .or_else(|| ID_REGEX.captures(&input))
175 {
176 let id = captures.get(1).unwrap().as_str();
177
178 if let Some(role) = server.roles.get(id) {
179 return Ok(role.clone());
180 }
181 };
182
183 Err(Error::ConverterError("Role not found".to_string()).into())
184 }
185}
186
187#[async_trait]
188impl<
189 E: From<Error> + Clone + Debug + Send + Sync + 'static,
190 S: Debug + Clone + Send + Sync + 'static,
191> Converter<E, S> for Member
192{
193 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
194 if let Ok(server) = context.get_current_server() {
195 let user = <User as Converter<E, S>>::convert(context, input).await?;
196
197 if let Some(member) = context.cache.get_member(&server.id, &user.id) {
198 return Ok(member);
199 } else if let Ok(member) = context.http.fetch_member(&server.id, &user.id).await {
200 return Ok(member);
201 };
202 };
203
204 Err(Error::ConverterError("Member not found".to_string()).into())
205 }
206}
207
208#[async_trait]
209impl<
210 E: From<Error> + Clone + Debug + Send + Sync + 'static,
211 S: Debug + Clone + Send + Sync + 'static,
212> Converter<E, S> for Emoji
213{
214 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
215 if let Some(captures) = EMOJI_REGEX
216 .captures(&input)
217 .or_else(|| ID_REGEX.captures(&input))
218 {
219 let id = captures.get(1).unwrap().as_str();
220
221 if let Some(emoji) = context.cache.get_emoji(id) {
222 return Ok(emoji);
223 }
224 } else {
225 if let Some(emoji) = context
226 .cache
227 .emojis
228 .any_sync(|_, emoji| &emoji.name == &input)
229 {
230 return Ok(emoji.get().clone());
231 }
232 };
233
234 Err(Error::ConverterError("Emoji not found".to_string()).into())
235 }
236}
237
238pub struct ConsumeRest<T = String>(pub T);
239
240#[async_trait]
241impl<
242 T: Converter<E, S>,
243 E: From<Error> + Clone + Debug + Send + Sync + 'static,
244 S: Debug + Clone + Send + Sync + 'static,
245> Converter<E, S> for ConsumeRest<T>
246{
247 async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
248 let words = context.words.rest().join(" ");
249
250 Self::convert(context, words).await
251 }
252
253 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
254 T::convert(context, input).await.map(Self)
255 }
256}
257
258#[cfg(feature = "either")]
259#[async_trait]
260impl<
261 E: From<Error> + Clone + Debug + Send + Sync + 'static,
262 S: Debug + Clone + Send + Sync + 'static,
263 L: Converter<E, S>,
264 R: Converter<E, S>,
265> Converter<E, S> for either::Either<L, R>
266{
267 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
268 if let Ok(left) = L::convert(context, input.clone()).await {
269 Ok(either::Either::Left(left))
270 } else {
271 R::convert(context, input).await.map(either::Either::Right)
272 }
273 }
274}
275
276#[async_trait]
277impl<
278 E: From<Error> + Clone + Debug + Send + Sync + 'static,
279 S: Debug + Clone + Send + Sync + 'static,
280 T: Converter<E, S>,
281> Converter<E, S> for Option<T>
282{
283 async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
284 let Some(input) = context.words.next() else {
285 return Ok(None);
286 };
287
288 Self::convert(context, input).await
289 }
290
291 async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
292 Ok(T::convert(context, input).await.ok())
293 }
294}
295
296pub struct Greedy<T>(pub Vec<T>);
297
298#[async_trait]
299impl<
300 E: From<Error> + Clone + Debug + Send + Sync + 'static,
301 S: Debug + Clone + Send + Sync + 'static,
302 T: Converter<E, S> + Send + Sync,
303> Converter<E, S> for Greedy<T>
304{
305 async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
306 let mut converted = Vec::new();
307
308 while let Some(arg) = context.words.next() {
309 if let Ok(value) = T::convert(context, arg).await {
310 converted.push(value);
311 } else {
312 context.words.undo();
313 break;
314 }
315 }
316
317 Ok(Self(converted))
318 }
319
320 async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
321 unreachable!("Cannot use Greedy inside another converter")
322 }
323}
324
325#[async_trait]
326impl<
327 E: From<Error> + Clone + Debug + Send + Sync + 'static,
328 S: Debug + Clone + Send + Sync + 'static,
329 T: Converter<E, S> + Send + Sync,
330> Converter<E, S> for Vec<T>
331{
332 async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
333 let mut converted = Vec::new();
334
335 while let Some(arg) = context.words.next() {
336 converted.push(T::convert(context, arg).await?);
337 }
338
339 Ok(converted)
340 }
341
342 async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
343 unreachable!("Cannot use Vec inside another converter")
344 }
345}