use std::fmt::{Display, Formatter}; use async_trait::async_trait; use reqwest::Error; use serde::de::DeserializeOwned; use serde::Serialize; use serenity::client::Context; use serenity::model::interactions::application_command::{ ApplicationCommand, ApplicationCommandInteraction, }; use serenity::Error as SerenityError; use geoffrey_models::models::parameters::CommandRequest; use geoffrey_models::models::player::UserID; use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::response::APIResponse; use crate::context::GeoffreyContext; pub mod add_item; pub mod add_location; pub mod find; pub mod selling; pub mod set_portal; #[derive(Debug)] pub enum CommandError { ArgumentParse(String), GeoffreyApi(GeoffreyAPIError), Serenity(serenity::Error), Reqwest(reqwest::Error), } 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), }; 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 { type ApiParams: CommandRequest; type ApiResp: Serialize + DeserializeOwned + Send; 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: Self::ApiParams, ) -> 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 } async fn create_app_command(ctx: &Context) -> Result; async fn process_arguments( command_interaction: ApplicationCommandInteraction, ) -> Result; async fn run_command( ctx: &GeoffreyContext, user_id: UserID, command_interact: ApplicationCommandInteraction, ) -> Result { let mut args = Self::process_arguments(command_interact).await?; log::info!("Running command {}, with args {:?}", Self::command_name(), args); args.set_token(ctx.cfg.api.token.clone()); args.set_user_id(user_id); let resp = Self::run_api_query(ctx, args).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; }