use std::{fmt::Debug, sync::LazyLock};
use async_trait::async_trait;
use regex::Regex;
use stoat_models::v0::{Channel, Emoji, Member, Role, User};
use crate::{Error, commands::Context, ulid::Ulid};
static ID_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})$").unwrap());
static USER_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^<@([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
static CHANNEL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^<#([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
static ROLE_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^<%([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>$").unwrap());
static EMOJI_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^:([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}):$").unwrap());
#[async_trait]
pub trait Converter<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
>: Sized
{
async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
let input = context.words.next().ok_or(Error::MissingParameter)?;
Self::convert(context, input).await
}
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E>;
}
macro_rules! impl_parse_converter {
($ty:ty) => {
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for $ty
{
async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
input
.parse::<$ty>()
.map_err(|e| Error::ConverterError(e.to_string()).into())
}
}
};
}
impl_parse_converter!(u8);
impl_parse_converter!(u16);
impl_parse_converter!(u32);
impl_parse_converter!(u64);
impl_parse_converter!(u128);
impl_parse_converter!(i8);
impl_parse_converter!(i16);
impl_parse_converter!(i32);
impl_parse_converter!(i64);
impl_parse_converter!(i128);
impl_parse_converter!(f32);
impl_parse_converter!(f64);
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for String
{
async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
Ok(input)
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for bool
{
async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
match input.to_lowercase().as_str() {
"yes" | "y" | "true" | "t" | "1" | "enable" | "enabled" | "on" => Ok(true),
"no" | "n" | "false" | "f" | "0" | "disable" | "disabled" | "off" => Ok(false),
_ => Err(Error::ConverterError("Bad boolean value".to_string()).into()),
}
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for Ulid
{
async fn convert(_context: &Context<E, S>, input: String) -> Result<Self, E> {
Ulid::from_string(input)
.map_err(|_| Error::ConverterError("Bad id value".to_string()).into())
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for User
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
if let Some(captures) = USER_REGEX
.captures(&input)
.or_else(|| ID_REGEX.captures(&input))
{
let id = captures.get(1).unwrap().as_str();
let user = context.cache.get_user(id);
if let Some(user) = user {
return Ok(user.clone());
} else if let Ok(user) = context.http.fetch_user(id).await {
return Ok(user);
};
};
Err(Error::ConverterError("User not found".to_string()).into())
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for Channel
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
if let Some(captures) = CHANNEL_REGEX
.captures(&input)
.or_else(|| ID_REGEX.captures(&input))
{
let id = captures.get(1).unwrap().as_str();
if let Some(channel) = context.cache.get_channel(id) {
return Ok(channel);
}
} else if let Some(entry) = context
.cache
.channels
.any_async(|_, channel| match channel {
Channel::TextChannel { name, .. } => name == &input,
_ => false,
})
.await
{
return Ok(entry.get().clone());
}
Err(Error::ConverterError("Channel not found".to_string()).into())
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for Role
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
let Ok(server) = context.get_current_server() else {
return Err(Error::ConverterError("Role not found".to_string()).into());
};
if let Some(captures) = ROLE_REGEX
.captures(&input)
.or_else(|| ID_REGEX.captures(&input))
{
let id = captures.get(1).unwrap().as_str();
if let Some(role) = server.roles.get(id) {
return Ok(role.clone());
}
};
Err(Error::ConverterError("Role not found".to_string()).into())
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for Member
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
if let Ok(server) = context.get_current_server() {
let user = <User as Converter<E, S>>::convert(context, input).await?;
if let Some(member) = context.cache.get_member(&server.id, &user.id) {
return Ok(member);
} else if let Ok(member) = context.http.fetch_member(&server.id, &user.id).await {
return Ok(member);
};
};
Err(Error::ConverterError("Member not found".to_string()).into())
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for Emoji
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
if let Some(captures) = EMOJI_REGEX
.captures(&input)
.or_else(|| ID_REGEX.captures(&input))
{
let id = captures.get(1).unwrap().as_str();
if let Some(emoji) = context.cache.get_emoji(id) {
return Ok(emoji);
}
} else {
if let Some(emoji) = context
.cache
.emojis
.any_sync(|_, emoji| &emoji.name == &input)
{
return Ok(emoji.get().clone());
}
};
Err(Error::ConverterError("Emoji not found".to_string()).into())
}
}
pub struct ConsumeRest<T = String>(pub T);
#[async_trait]
impl<
T: Converter<E, S>,
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
> Converter<E, S> for ConsumeRest<T>
{
async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
let words = context.words.rest().join(" ");
Self::convert(context, words).await
}
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
T::convert(context, input).await.map(Self)
}
}
#[cfg(feature = "either")]
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
L: Converter<E, S>,
R: Converter<E, S>,
> Converter<E, S> for either::Either<L, R>
{
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
if let Ok(left) = L::convert(context, input.clone()).await {
Ok(either::Either::Left(left))
} else {
R::convert(context, input).await.map(either::Either::Right)
}
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
T: Converter<E, S>,
> Converter<E, S> for Option<T>
{
async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
let Some(input) = context.words.next() else {
return Ok(None);
};
Self::convert(context, input).await
}
async fn convert(context: &Context<E, S>, input: String) -> Result<Self, E> {
Ok(T::convert(context, input).await.ok())
}
}
pub struct Greedy<T>(pub Vec<T>);
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
T: Converter<E, S> + Send + Sync,
> Converter<E, S> for Greedy<T>
{
async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
let mut converted = Vec::new();
while let Some(arg) = context.words.next() {
if let Ok(value) = T::convert(context, arg).await {
converted.push(value);
} else {
context.words.undo();
break;
}
}
Ok(Self(converted))
}
async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
unreachable!("Cannot use Greedy inside another converter")
}
}
#[async_trait]
impl<
E: From<Error> + Clone + Debug + Send + Sync + 'static,
S: Debug + Clone + Send + Sync + 'static,
T: Converter<E, S> + Send + Sync,
> Converter<E, S> for Vec<T>
{
async fn from_context(context: &Context<E, S>) -> Result<Self, E> {
let mut converted = Vec::new();
while let Some(arg) = context.words.next() {
converted.push(T::convert(context, arg).await?);
}
Ok(converted)
}
async fn convert(_context: &Context<E, S>, _input: String) -> Result<Self, E> {
unreachable!("Cannot use Vec inside another converter")
}
}