use std::fmt::{Debug, Display, Formatter}; use async_trait::async_trait; use reqwest::Error; use serde::de::DeserializeOwned; use serde::Serialize; use serenity::model::interactions::application_command::ApplicationCommandInteraction; use serenity::Error as SerenityError; use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam}; use geoffrey_models::models::player::UserID; use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::response::APIResponse; use crate::context::GeoffreyContext; use serenity::builder::CreateApplicationCommand; use std::future::Future; use std::pin::Pin; pub mod add_item; pub mod add_location; pub mod find; pub mod selling; pub mod set_portal; pub type GeoffreyCommandFn = Box< fn( GeoffreyContext, UserID, ApplicationCommandInteraction, ) -> Pin + Send>>, >; #[derive(Debug)] pub enum CommandError { ArgumentParse(String), GeoffreyApi(GeoffreyAPIError), Serenity(serenity::Error), Reqwest(reqwest::Error), CommandNotFound(String), } impl Display for CommandError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let s = match self { CommandError::ArgumentParse(s) => format!("Unable to parse argument '{}'", s), CommandError::GeoffreyApi(err) => format!("Got error from GeoffreyAPI: {}", err), CommandError::Serenity(err) => format!("Serenity Error: {}", err), CommandError::Reqwest(err) => format!("Reqwest Error: {}", err), CommandError::CommandNotFound(err) => format!("'{}' not found!", err), }; write!(f, "{}", s) } } impl From for CommandError { fn from(err: GeoffreyAPIError) -> Self { Self::GeoffreyApi(err) } } impl From for CommandError { fn from(err: SerenityError) -> Self { Self::Serenity(err) } } impl From for CommandError { fn from(err: Error) -> Self { Self::Reqwest(err) } } #[async_trait] pub trait BotCommand: Send + 'static { type ApiParams: GeoffreyParam; type ApiResp: Serialize + DeserializeOwned + Send + Debug; fn command_name() -> String; fn request_type() -> reqwest::Method; fn command_url(base_string: &str) -> String { let slash = if !base_string.ends_with('/') { "/" } else { "" }; format!("{}{}command/{}/", base_string, slash, Self::command_name()) } async fn run_api_query( ctx: GeoffreyContext, params: CommandRequest, ) -> Result, CommandError> { let command_url = Self::command_url(&ctx.cfg.api.base_url); let resp: APIResponse = ctx .http_client .request(Self::request_type(), command_url) .json(¶ms) .send() .await? .json() .await?; Ok(resp) } fn get_err_resp(err: CommandError) -> String { if let Some(resp) = Self::custom_err_resp(&err) { resp } else { match err { CommandError::GeoffreyApi(GeoffreyAPIError::PlayerNotRegistered) => { "You need to register before using this command!".to_string() } CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { "Couldn't find that, maybe look for something that exists?".to_string() } CommandError::GeoffreyApi(GeoffreyAPIError::PermissionInsufficient) => { "Looks like you don't have permission for that.".to_string() } CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotUnique) => { "Slow down, I already know that thing. Try a new name.".to_string() } CommandError::GeoffreyApi(GeoffreyAPIError::MultipleLocationsMatch) => { "I couldn't match a single location, narrow down your search".to_string() } CommandError::GeoffreyApi(GeoffreyAPIError::ParameterInvalid(err)) => { format!( "Welp, you some how messed up the {} parameter, great job", err ) } _ => { log::warn!("GeoffreyBot got an unhandled error: {}", err); format!("OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our \ headquarters are working VEWY HAWD to fix this! (Error in command {})", Self::command_name()) } } } } fn custom_err_resp(_: &CommandError) -> Option { None } fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand; async fn process_arguments( command_interaction: ApplicationCommandInteraction, ) -> Result; async fn run_command( ctx: GeoffreyContext, user_id: UserID, command_interact: ApplicationCommandInteraction, ) -> Result { let args = Self::process_arguments(command_interact).await?; log::info!( "Running command {}, with args {:?}", Self::command_name(), args ); let request = CommandRequest { token: ctx.cfg.api.token.clone(), user_id: Some(user_id), params: args, }; let resp = Self::run_api_query(ctx, request).await?; match resp { APIResponse::Response(resp) => Ok(Self::build_response(resp)), APIResponse::Error { error: err, .. } => Err(CommandError::GeoffreyApi(err)), } } async fn command( ctx: GeoffreyContext, user_id: UserID, command_interact: ApplicationCommandInteraction, ) -> String { match Self::run_command(ctx, user_id, command_interact).await { Ok(msg) => msg, Err(e) => Self::get_err_resp(e), } } fn build_response(resp: Self::ApiResp) -> String; }