diff --git a/geoffrey_api/src/commands/add_item.rs b/geoffrey_api/src/commands/add_item.rs index 8bdd82c..882b95e 100644 --- a/geoffrey_api/src/commands/add_item.rs +++ b/geoffrey_api/src/commands/add_item.rs @@ -1,5 +1,4 @@ use crate::commands::{Command, RequestType}; -use crate::config::GeoffreyAPIConfig; use crate::context::Context; use crate::helper::validate_string_parameter; use crate::Result; @@ -10,6 +9,7 @@ use geoffrey_models::models::locations::{Location, LocationDataDb, LocationDb, L use geoffrey_models::models::parameters::add_item_params::AddItemParams; use geoffrey_models::models::player::Player; use geoffrey_models::models::response::api_error::GeoffreyAPIError; +use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::CommandLevel; use std::sync::Arc; @@ -60,7 +60,7 @@ impl Command for AddItem { } } - fn validate_parameters(req: &Self::Req, cfg: &GeoffreyAPIConfig) -> Result<()> { + fn validate_parameters(req: &Self::Req, cfg: &GeoffreySettings) -> Result<()> { if req.quantity == 0 { return Err(GeoffreyAPIError::ParameterInvalid("quantity".to_string())); } diff --git a/geoffrey_api/src/commands/add_location.rs b/geoffrey_api/src/commands/add_location.rs index 8a4647d..f6988eb 100644 --- a/geoffrey_api/src/commands/add_location.rs +++ b/geoffrey_api/src/commands/add_location.rs @@ -1,5 +1,4 @@ use crate::commands::{Command, RequestType}; -use crate::config::GeoffreyAPIConfig; use crate::context::Context; use crate::helper::validate_string_parameter; use crate::Result; @@ -7,6 +6,7 @@ use geoffrey_db::helper::load_location; use geoffrey_models::models::locations::{Location, LocationDb}; use geoffrey_models::models::parameters::add_location_params::AddLocationParams; use geoffrey_models::models::player::Player; +use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::CommandLevel; use std::sync::Arc; @@ -44,7 +44,7 @@ impl Command for AddLocation { load_location(&ctx.db, &location).map_err(|err| err.into()) } - fn validate_parameters(req: &Self::Req, cfg: &GeoffreyAPIConfig) -> Result<()> { + fn validate_parameters(req: &Self::Req, cfg: &GeoffreySettings) -> Result<()> { validate_string_parameter("name", &req.name, cfg.max_str_len) } } diff --git a/geoffrey_api/src/commands/mod.rs b/geoffrey_api/src/commands/mod.rs index 440cb22..0427cee 100644 --- a/geoffrey_api/src/commands/mod.rs +++ b/geoffrey_api/src/commands/mod.rs @@ -11,7 +11,7 @@ use crate::commands::report_out_of_stock::ReportOutOfStock; use crate::commands::restock::Restock; use crate::commands::selling::Selling; use crate::commands::set_portal::SetPortal; -use crate::config::GeoffreyAPIConfig; +use crate::commands::settings::Settings; use crate::context::Context; use crate::helper::{get_player_from_req, get_token_from_req}; use crate::Result; @@ -19,6 +19,7 @@ use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam}; use geoffrey_models::models::player::Player; use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::response::APIResponse; +use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::token::{Permissions, Token}; use geoffrey_models::models::CommandLevel; use serde::de::DeserializeOwned; @@ -42,6 +43,7 @@ pub mod report_out_of_stock; pub mod restock; pub mod selling; pub mod set_portal; +pub mod settings; #[derive(Debug, Clone, PartialEq)] #[allow(clippy::upper_case_acronyms)] @@ -59,7 +61,7 @@ pub trait Command { fn command_level() -> CommandLevel; fn run_command(ctx: Arc, req: &Self::Req, user: Option) -> Result; - fn validate_parameters(_: &Self::Req, _: &GeoffreyAPIConfig) -> Result<()> { + fn validate_parameters(_: &Self::Req, _: &GeoffreySettings) -> Result<()> { Ok(()) } @@ -102,7 +104,7 @@ pub fn handle_command( match T::user_is_authorized(&token, &user) { Ok(_) => { - T::validate_parameters(&req.params, &ctx.cfg)?; + T::validate_parameters(&req.params, &ctx.cfg.geoffrey_settings)?; T::run_command(ctx, &req.params, user) } Err(e) => Err(e), @@ -153,3 +155,9 @@ pub fn command_filter( .or(create_command_filter::(ctx)), ) } + +pub fn model_filter( + ctx: Arc, +) -> impl Filter + Clone { + warp::path("model").and(create_command_filter::(ctx)) +} diff --git a/geoffrey_api/src/commands/selling.rs b/geoffrey_api/src/commands/selling.rs index 8e3d49c..b335d22 100644 --- a/geoffrey_api/src/commands/selling.rs +++ b/geoffrey_api/src/commands/selling.rs @@ -101,22 +101,25 @@ impl Command for Selling { mod test { use crate::commands::selling::Selling; use crate::commands::Command; - use crate::config::GeoffreyAPIConfig; + use crate::config::{GeoffreyAPIConfig, ServerConfig}; use crate::context::Context; use crate::Args; use geoffrey_models::models::item::ItemListing; use geoffrey_models::models::locations::shop::Shop; use geoffrey_models::models::locations::{LocationDataDb, LocationDb}; use geoffrey_models::models::parameters::selling_params::SellingParams; + use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::Position; use std::path::PathBuf; use std::time::Instant; fn test_selling_lookup_speed(iter: i32, shop_count: i32, items_for_sale: i32) -> f32 { let config = GeoffreyAPIConfig { - db_path: PathBuf::from("test_db/"), - host: "".to_string(), - max_str_len: 64, + server_config: ServerConfig { + db_path: PathBuf::from("test_db/"), + host: "".to_string(), + }, + geoffrey_settings: GeoffreySettings::default(), }; let ctx = Context::new(config.clone(), Args::default()).unwrap(); diff --git a/geoffrey_api/src/commands/settings.rs b/geoffrey_api/src/commands/settings.rs new file mode 100644 index 0000000..66c5cd7 --- /dev/null +++ b/geoffrey_api/src/commands/settings.rs @@ -0,0 +1,31 @@ +use crate::commands::{Command, RequestType}; +use crate::context::Context; +use crate::Result; +use geoffrey_models::models::parameters::EmptyRequest; +use geoffrey_models::models::player::Player; +use geoffrey_models::models::settings::GeoffreySettings; +use geoffrey_models::models::CommandLevel; +use std::sync::Arc; + +pub struct Settings {} + +impl Command for Settings { + type Req = EmptyRequest; + type Resp = GeoffreySettings; + + fn command_name() -> String { + "settings".to_string() + } + + fn request_type() -> RequestType { + RequestType::GET + } + + fn command_level() -> CommandLevel { + CommandLevel::ALL + } + + fn run_command(ctx: Arc, _: &Self::Req, _: Option) -> Result { + Ok(ctx.cfg.geoffrey_settings.clone()) + } +} diff --git a/geoffrey_api/src/config.rs b/geoffrey_api/src/config.rs index 0e420a3..c09e31d 100644 --- a/geoffrey_api/src/config.rs +++ b/geoffrey_api/src/config.rs @@ -1,12 +1,18 @@ use config::{Config, ConfigError, File}; +use geoffrey_models::models::settings::GeoffreySettings; use serde::Deserialize; use std::path::{Path, PathBuf}; #[derive(Debug, Deserialize, Clone)] pub struct GeoffreyAPIConfig { + pub server_config: ServerConfig, + pub geoffrey_settings: GeoffreySettings, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct ServerConfig { pub db_path: PathBuf, pub host: String, - pub max_str_len: usize, } impl GeoffreyAPIConfig { diff --git a/geoffrey_api/src/context.rs b/geoffrey_api/src/context.rs index 0d5a487..8e772c3 100644 --- a/geoffrey_api/src/context.rs +++ b/geoffrey_api/src/context.rs @@ -11,7 +11,7 @@ pub struct Context { impl Context { pub fn new(cfg: GeoffreyAPIConfig, args: Args) -> Result> { let ctx = Self { - db: Database::new(cfg.db_path.as_path(), args.force_migration).unwrap(), + db: Database::new(cfg.server_config.db_path.as_path(), args.force_migration).unwrap(), cfg, }; diff --git a/geoffrey_api/src/main.rs b/geoffrey_api/src/main.rs index 4904cce..3363bcb 100644 --- a/geoffrey_api/src/main.rs +++ b/geoffrey_api/src/main.rs @@ -4,6 +4,8 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use warp::Filter; + use structopt::StructOpt; use geoffrey_models::logging::LogLevel; @@ -12,7 +14,7 @@ use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::token::Permissions; use crate::commands::add_token::AddToken; -use crate::commands::{command_filter, Command}; +use crate::commands::{command_filter, model_filter, Command}; use crate::config::GeoffreyAPIConfig; use crate::context::Context; use crate::logging::init_logging; @@ -65,15 +67,19 @@ pub struct CreateTokenCommand { } async fn run_server(ctx: Arc) { - let socket_addr = match SocketAddr::from_str(ctx.cfg.host.as_str()) { + let socket_addr = match SocketAddr::from_str(ctx.cfg.server_config.host.as_str()) { Ok(socket_addr) => socket_addr, Err(e) => { - log::warn!("Error parsing {} as address: {}", ctx.cfg.host, e); + log::warn!( + "Error parsing {} as address: {}", + ctx.cfg.server_config.host, + e + ); return; } }; - let api = command_filter(ctx.clone()); + let api = command_filter(ctx.clone()).or(model_filter(ctx.clone())); warp::serve(api).run(socket_addr).await; } diff --git a/geoffrey_bot/src/api/mod.rs b/geoffrey_bot/src/api/mod.rs new file mode 100644 index 0000000..31ad128 --- /dev/null +++ b/geoffrey_bot/src/api/mod.rs @@ -0,0 +1,33 @@ +use crate::context::GeoffreyContext; +use crate::error::BotError; +use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam}; +use geoffrey_models::models::response::APIResponse; +use reqwest::Method; +use serde::de::DeserializeOwned; + +pub fn build_url(base_str: &str, end_point: &str) -> String { + let slash = if !base_str.ends_with('/') { "/" } else { "" }; + + format!("{}{}{}", base_str, slash, end_point) +} + +pub async fn run_api_query( + ctx: &GeoffreyContext, + param: &CommandRequest, + method: Method, + endpoint: &str, +) -> Result { + let resp: APIResponse = ctx + .http_client + .request(method, build_url(&ctx.cfg.api.base_url, endpoint)) + .json(param) + .send() + .await? + .json() + .await?; + + match resp { + APIResponse::Response(resp) => Ok(resp), + APIResponse::Error { error: e, .. } => Err(e.into()), + } +} diff --git a/geoffrey_bot/src/bot/arg_parse.rs b/geoffrey_bot/src/bot/arg_parse.rs index 84d8efd..90fd33a 100644 --- a/geoffrey_bot/src/bot/arg_parse.rs +++ b/geoffrey_bot/src/bot/arg_parse.rs @@ -1,4 +1,4 @@ -use crate::bot::commands::CommandError; +use crate::error::BotError; use geoffrey_models::models::locations::LocationType; use geoffrey_models::models::parameters::selling_params::{ItemSort, Order}; use geoffrey_models::models::Dimension; @@ -12,7 +12,7 @@ use std::str::FromStr; pub fn option_to_i64( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { if let Some(option) = option { let option = option.resolved.as_ref(); @@ -20,13 +20,13 @@ pub fn option_to_i64( return Ok(*i); } } - Err(CommandError::ArgumentParse(field.to_string())) + Err(BotError::ArgumentParse(field.to_string())) } pub fn option_to_string( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { if let Some(option) = option { let option = option.resolved.as_ref(); @@ -34,43 +34,43 @@ pub fn option_to_string( return Ok(s.clone()); } } - Err(CommandError::ArgumentParse(field.to_string())) + Err(BotError::ArgumentParse(field.to_string())) } pub fn option_to_loc_type( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { let loc_type = option_to_string(option, field)?; - LocationType::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string())) + LocationType::from_str(&loc_type).map_err(|_| BotError::ArgumentParse(field.to_string())) } pub fn option_to_dim( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { let loc_type = option_to_string(option, field)?; - Dimension::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string())) + Dimension::from_str(&loc_type).map_err(|_| BotError::ArgumentParse(field.to_string())) } pub fn option_to_sort( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { let loc_type = option_to_string(option, field)?; - ItemSort::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string())) + ItemSort::from_str(&loc_type).map_err(|_| BotError::ArgumentParse(field.to_string())) } pub fn option_to_order( option: Option<&ApplicationCommandInteractionDataOption>, field: &str, -) -> Result { +) -> Result { let loc_type = option_to_string(option, field)?; - Order::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string())) + Order::from_str(&loc_type).map_err(|_| BotError::ArgumentParse(field.to_string())) } pub fn add_x_position_argument( diff --git a/geoffrey_bot/src/bot/commands/add_item.rs b/geoffrey_bot/src/bot/commands/add_item.rs index e75e802..cacea95 100644 --- a/geoffrey_bot/src/bot/commands/add_item.rs +++ b/geoffrey_bot/src/bot/commands/add_item.rs @@ -1,18 +1,20 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::add_item_params::AddItemParams; +use geoffrey_models::models::response::api_error::GeoffreyAPIError; use crate::bot::arg_parse::{option_to_i64, option_to_string}; -use crate::bot::commands::{BotCommand, CommandError}; +use crate::bot::commands::BotCommand; use crate::bot::formatters::display_loc_full; use crate::bot::lang::{PLAYER_ALREADY_SELLS_ITEM, PLAYER_DOES_NOT_HAVE_MATCHING_SHOP}; -use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct AddItemCommand; @@ -29,12 +31,12 @@ impl BotCommand for AddItemCommand { Method::POST } - fn custom_err_resp(e: &CommandError) -> Option { + fn custom_err_resp(e: &BotError) -> Option { match e { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(PLAYER_DOES_NOT_HAVE_MATCHING_SHOP.to_string()) } - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotUnique) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotUnique) => { Some(PLAYER_ALREADY_SELLS_ITEM.to_string()) } _ => None, @@ -79,7 +81,7 @@ impl BotCommand for AddItemCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; Ok(Self::ApiParams::new( @@ -90,12 +92,12 @@ impl BotCommand for AddItemCommand { )) } - fn build_response(resp: Self::ApiResp, req: Self::ApiParams) -> String { + fn build_response(ctx: &GeoffreyContext, resp: Self::ApiResp, req: Self::ApiParams) -> String { format!( "**{}** has been added to {} :\n{}", req.item_name, resp.name, - display_loc_full(&resp) + display_loc_full(&resp, &ctx.settings) ) } } diff --git a/geoffrey_bot/src/bot/commands/add_location.rs b/geoffrey_bot/src/bot/commands/add_location.rs index ff0bfa3..106273e 100644 --- a/geoffrey_bot/src/bot/commands/add_location.rs +++ b/geoffrey_bot/src/bot/commands/add_location.rs @@ -1,19 +1,22 @@ use async_trait::async_trait; -use geoffrey_models::models::locations::{Location, LocationType}; -use geoffrey_models::models::parameters::add_location_params::AddLocationParams; -use geoffrey_models::models::{Dimension, Position}; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; +use geoffrey_models::models::locations::{Location, LocationType}; +use geoffrey_models::models::parameters::add_location_params::AddLocationParams; +use geoffrey_models::models::{Dimension, Position}; + use crate::bot::arg_parse::{ add_dimension_argument, add_x_position_argument, add_y_position_argument, add_z_position_argument, option_to_dim, option_to_i64, option_to_loc_type, option_to_string, }; -use crate::bot::commands::{BotCommand, CommandError}; +use crate::bot::commands::BotCommand; use crate::bot::formatters::display_loc_full; -use serenity::builder::CreateApplicationCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct AddLocationCommand; @@ -62,7 +65,7 @@ impl BotCommand for AddLocationCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let loc_type = option_to_loc_type(options.get(0), "loc_type")?; let name = option_to_string(options.get(1), "name")?; @@ -76,11 +79,11 @@ impl BotCommand for AddLocationCommand { Ok(Self::ApiParams::new(name, position, loc_type, None)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(ctx: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { format!( "**{}** has been added to Geoffrey:\n{}", resp.name, - display_loc_full(&resp) + display_loc_full(&resp, &ctx.settings) ) } } diff --git a/geoffrey_bot/src/bot/commands/delete.rs b/geoffrey_bot/src/bot/commands/delete.rs index 7b418c7..e812618 100644 --- a/geoffrey_bot/src/bot/commands/delete.rs +++ b/geoffrey_bot/src/bot/commands/delete.rs @@ -1,16 +1,19 @@ use async_trait::async_trait; -use geoffrey_models::models::locations::Location; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_LOC; +use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::delete_params::DeleteParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_LOC; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct DeleteCommand; @@ -27,9 +30,9 @@ impl BotCommand for DeleteCommand { Method::POST } - fn custom_err_resp(err: &CommandError) -> Option { + fn custom_err_resp(err: &BotError) -> Option { match err { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(PLAYER_DOES_NOT_HAVE_MATCHING_LOC.to_string()) } _ => None, @@ -51,14 +54,14 @@ impl BotCommand for DeleteCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let loc_name = option_to_string(options.get(0), "loc_name")?; Ok(Self::ApiParams::new(loc_name)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { format!( "**{}** has been been removed from Geoffrey, good riddance!", resp.name diff --git a/geoffrey_bot/src/bot/commands/edit_name.rs b/geoffrey_bot/src/bot/commands/edit_name.rs index 49dee64..e1cae7d 100644 --- a/geoffrey_bot/src/bot/commands/edit_name.rs +++ b/geoffrey_bot/src/bot/commands/edit_name.rs @@ -1,14 +1,17 @@ use async_trait::async_trait; -use geoffrey_models::models::locations::Location; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; +use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::edit_params::EditParams; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; //TODO: Combine edit commands into one class once I figure out why subcommand are not working pub struct EditNameCommand; @@ -22,7 +25,7 @@ impl BotCommand for EditNameCommand { "edit_name".to_string() } - fn endpoint_name() -> String { + fn endpoint() -> String { "edit".to_string() } @@ -52,14 +55,14 @@ impl BotCommand for EditNameCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let name = option_to_string(options.get(0), "loc_name")?; let new_name = option_to_string(options.get(1), "new_name")?; Ok(Self::ApiParams::new(name, None, Some(new_name))) } - fn build_response(resp: Self::ApiResp, args: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, args: Self::ApiParams) -> String { format!("**{}** has been renamed to {}", args.loc_name, resp.name,) } } diff --git a/geoffrey_bot/src/bot/commands/edit_pos.rs b/geoffrey_bot/src/bot/commands/edit_pos.rs index 125f091..70357f1 100644 --- a/geoffrey_bot/src/bot/commands/edit_pos.rs +++ b/geoffrey_bot/src/bot/commands/edit_pos.rs @@ -1,18 +1,21 @@ use async_trait::async_trait; -use geoffrey_models::models::locations::Location; -use geoffrey_models::models::{Dimension, Position}; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; +use geoffrey_models::models::locations::Location; +use geoffrey_models::models::parameters::edit_params::EditParams; +use geoffrey_models::models::{Dimension, Position}; + use crate::bot::arg_parse::{ add_dimension_argument, add_x_position_argument, add_y_position_argument, add_z_position_argument, option_to_dim, option_to_i64, option_to_string, }; -use crate::bot::commands::{BotCommand, CommandError}; -use geoffrey_models::models::parameters::edit_params::EditParams; -use serenity::builder::CreateApplicationCommand; +use crate::bot::commands::BotCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; //TODO: Combine edit commands into one class once I figure out why subcommand are not working pub struct EditPosCommand; @@ -26,7 +29,7 @@ impl BotCommand for EditPosCommand { "edit_pos".to_string() } - fn endpoint_name() -> String { + fn endpoint() -> String { "edit".to_string() } @@ -53,7 +56,7 @@ impl BotCommand for EditPosCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let name = option_to_string(options.get(0), "loc_name")?; @@ -66,7 +69,7 @@ impl BotCommand for EditPosCommand { Ok(Self::ApiParams::new(name, Some(position), None)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { format!("**{}** has been moved to {}", resp.name, resp.position) } } diff --git a/geoffrey_bot/src/bot/commands/find.rs b/geoffrey_bot/src/bot/commands/find.rs index e32c77b..95dbc8b 100644 --- a/geoffrey_bot/src/bot/commands/find.rs +++ b/geoffrey_bot/src/bot/commands/find.rs @@ -1,17 +1,20 @@ +use std::fmt::Write; + use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; -use std::fmt::Write; use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::find_params::FindParams; use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; +use crate::bot::commands::BotCommand; use crate::bot::formatters::display_loc; -use serenity::builder::CreateApplicationCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct FindCommand; @@ -43,21 +46,21 @@ impl BotCommand for FindCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let query = option_to_string(options.get(0), "query")?; Ok(FindParams::new(query)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(ctx: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { if resp.is_empty() { "No locations match that query, try better next time ding dong".to_string() } else { let mut resp_str = String::new(); writeln!(resp_str, "The following locations match:").unwrap(); for loc in resp { - writeln!(resp_str, "{}", display_loc(&loc)).unwrap(); + writeln!(resp_str, "{}", display_loc(&loc, &ctx.settings)).unwrap(); } resp_str } diff --git a/geoffrey_bot/src/bot/commands/info.rs b/geoffrey_bot/src/bot/commands/info.rs index 0190acd..89e322e 100644 --- a/geoffrey_bot/src/bot/commands/info.rs +++ b/geoffrey_bot/src/bot/commands/info.rs @@ -1,18 +1,20 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; use geoffrey_models::models::locations::Location; - -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::formatters::display_loc_full; -use crate::bot::lang::NO_LOCATION_FOUND; use geoffrey_models::models::parameters::info_params::InfoParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::bot::formatters::display_loc_full; +use crate::bot::lang::NO_LOCATION_FOUND; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct InfoCommand; @@ -29,9 +31,9 @@ impl BotCommand for InfoCommand { Method::GET } - fn custom_err_resp(err: &CommandError) -> Option { + fn custom_err_resp(err: &BotError) -> Option { match err { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(NO_LOCATION_FOUND.to_string()) } _ => None, @@ -53,14 +55,14 @@ impl BotCommand for InfoCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let location_name = option_to_string(options.get(0), "loc_name")?; Ok(Self::ApiParams::new(location_name)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { - display_loc_full(&resp) + fn build_response(ctx: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { + display_loc_full(&resp, &ctx.settings) } } diff --git a/geoffrey_bot/src/bot/commands/mod.rs b/geoffrey_bot/src/bot/commands/mod.rs index f30a732..0fb761e 100644 --- a/geoffrey_bot/src/bot/commands/mod.rs +++ b/geoffrey_bot/src/bot/commands/mod.rs @@ -1,21 +1,20 @@ -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; +use std::future::Future; +use std::pin::Pin; use async_trait::async_trait; -use reqwest::Error; use serde::de::DeserializeOwned; use serde::Serialize; +use serenity::builder::CreateApplicationCommand; 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::api::run_api_query; use crate::context::GeoffreyContext; -use serenity::builder::CreateApplicationCommand; -use std::future::Future; -use std::pin::Pin; +use crate::error::BotError; pub mod add_item; pub mod add_location; @@ -39,47 +38,6 @@ pub type GeoffreyCommandFn = Box< ) -> 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; @@ -87,56 +45,33 @@ pub trait BotCommand: Send + 'static { fn command_name() -> String; - fn endpoint_name() -> String { - Self::command_name() + fn endpoint() -> String { + format!("command/{}/", Self::command_name()) } 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::endpoint_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 { + fn get_err_resp(err: BotError) -> String { if let Some(resp) = Self::custom_err_resp(&err) { resp } else { match err { - CommandError::GeoffreyApi(GeoffreyAPIError::PlayerNotRegistered) => { + BotError::GeoffreyApi(GeoffreyAPIError::PlayerNotRegistered) => { "You need to register before using this command!".to_string() } - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { "Couldn't find that, maybe look for something that exists?".to_string() } - CommandError::GeoffreyApi(GeoffreyAPIError::PermissionInsufficient) => { + BotError::GeoffreyApi(GeoffreyAPIError::PermissionInsufficient) => { "Looks like you don't have permission for that.".to_string() } - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotUnique) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotUnique) => { "Slow down, I already know that thing. Try a new name.".to_string() } - CommandError::GeoffreyApi(GeoffreyAPIError::MultipleLocationsMatch) => { + BotError::GeoffreyApi(GeoffreyAPIError::MultipleLocationsMatch) => { "I couldn't match a single location, narrow down your search".to_string() } - CommandError::GeoffreyApi(GeoffreyAPIError::ParameterInvalid(err)) => { + BotError::GeoffreyApi(GeoffreyAPIError::ParameterInvalid(err)) => { format!( "Welp, you some how messed up the {} parameter, great job", err @@ -151,7 +86,7 @@ pub trait BotCommand: Send + 'static { } } - fn custom_err_resp(_: &CommandError) -> Option { + fn custom_err_resp(_: &BotError) -> Option { None } @@ -159,13 +94,13 @@ pub trait BotCommand: Send + 'static { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result; + ) -> Result; async fn run_command( ctx: GeoffreyContext, user_id: UserID, command_interact: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let args = Self::process_arguments(command_interact).await?; log::info!( @@ -180,12 +115,9 @@ pub trait BotCommand: Send + 'static { params: args.clone(), }; - let resp = Self::run_api_query(ctx, request).await?; + let resp = run_api_query(&ctx, &request, Self::request_type(), &Self::endpoint()).await?; - match resp { - APIResponse::Response(resp) => Ok(Self::build_response(resp, args)), - APIResponse::Error { error: err, .. } => Err(CommandError::GeoffreyApi(err)), - } + Ok(Self::build_response(&ctx, resp, args)) } async fn command( @@ -199,5 +131,5 @@ pub trait BotCommand: Send + 'static { } } - fn build_response(resp: Self::ApiResp, req: Self::ApiParams) -> String; + fn build_response(ctx: &GeoffreyContext, resp: Self::ApiResp, req: Self::ApiParams) -> String; } diff --git a/geoffrey_bot/src/bot/commands/register.rs b/geoffrey_bot/src/bot/commands/register.rs index 313dbd9..ba80bd9 100644 --- a/geoffrey_bot/src/bot/commands/register.rs +++ b/geoffrey_bot/src/bot/commands/register.rs @@ -1,16 +1,19 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::lang::ACCOUNT_LINK_INVALID; use geoffrey_models::models::parameters::register_params::RegisterParameters; use geoffrey_models::models::player::{Player, UserID}; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::bot::lang::ACCOUNT_LINK_INVALID; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct RegisterCommand; @@ -27,9 +30,9 @@ impl BotCommand for RegisterCommand { Method::POST } - fn custom_err_resp(err: &CommandError) -> Option { + fn custom_err_resp(err: &BotError) -> Option { match err { - CommandError::GeoffreyApi(GeoffreyAPIError::AccountLinkInvalid) => { + BotError::GeoffreyApi(GeoffreyAPIError::AccountLinkInvalid) => { Some(ACCOUNT_LINK_INVALID.to_string()) } _ => None, @@ -51,7 +54,7 @@ impl BotCommand for RegisterCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let link_code = option_to_string(options.get(0), "link_code")?; @@ -68,7 +71,7 @@ impl BotCommand for RegisterCommand { Ok(register) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { format!( "**{}**, you have been registered for the Geoffrey bot!", resp.name diff --git a/geoffrey_bot/src/bot/commands/remove_item.rs b/geoffrey_bot/src/bot/commands/remove_item.rs index f33562a..a88d6ae 100644 --- a/geoffrey_bot/src/bot/commands/remove_item.rs +++ b/geoffrey_bot/src/bot/commands/remove_item.rs @@ -1,17 +1,19 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::item_command_params::ItemCommandParams; +use geoffrey_models::models::response::api_error::GeoffreyAPIError; use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; +use crate::bot::commands::BotCommand; use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_SHOP; -use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct RemoveItemCommand; @@ -28,9 +30,9 @@ impl BotCommand for RemoveItemCommand { Method::POST } - fn custom_err_resp(e: &CommandError) -> Option { + fn custom_err_resp(e: &BotError) -> Option { match e { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(PLAYER_DOES_NOT_HAVE_MATCHING_SHOP.to_string()) } _ => None, @@ -59,7 +61,7 @@ impl BotCommand for RemoveItemCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; Ok(Self::ApiParams::new( @@ -68,7 +70,7 @@ impl BotCommand for RemoveItemCommand { )) } - fn build_response(resp: Self::ApiResp, args: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, args: Self::ApiParams) -> String { format!( "**{}** has been removed from **{}**", args.item_name, resp.name diff --git a/geoffrey_bot/src/bot/commands/report_out_of_stock.rs b/geoffrey_bot/src/bot/commands/report_out_of_stock.rs index 09dcc98..12fc68f 100644 --- a/geoffrey_bot/src/bot/commands/report_out_of_stock.rs +++ b/geoffrey_bot/src/bot/commands/report_out_of_stock.rs @@ -1,17 +1,19 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; use geoffrey_models::models::locations::Location; - -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::lang::NO_LOCATION_FOUND; use geoffrey_models::models::parameters::item_command_params::ItemCommandParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::bot::lang::NO_LOCATION_FOUND; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct ReportOutOfStockCommand; @@ -28,9 +30,9 @@ impl BotCommand for ReportOutOfStockCommand { Method::POST } - fn custom_err_resp(e: &CommandError) -> Option { + fn custom_err_resp(e: &BotError) -> Option { match e { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(NO_LOCATION_FOUND.to_string()) } _ => None, @@ -59,7 +61,7 @@ impl BotCommand for ReportOutOfStockCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; Ok(Self::ApiParams::new( @@ -68,7 +70,7 @@ impl BotCommand for ReportOutOfStockCommand { )) } - fn build_response(resp: Self::ApiResp, args: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, args: Self::ApiParams) -> String { format!( "**{}** has been reported out of stock at {}", args.item_name, resp.name diff --git a/geoffrey_bot/src/bot/commands/restock.rs b/geoffrey_bot/src/bot/commands/restock.rs index 2066f27..3459b0d 100644 --- a/geoffrey_bot/src/bot/commands/restock.rs +++ b/geoffrey_bot/src/bot/commands/restock.rs @@ -1,17 +1,19 @@ use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; use geoffrey_models::models::locations::Location; - -use crate::bot::arg_parse::option_to_string; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_SHOP; use geoffrey_models::models::parameters::item_command_params::ItemCommandParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; + +use crate::bot::arg_parse::option_to_string; +use crate::bot::commands::BotCommand; +use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_SHOP; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct RestockCommand; @@ -28,9 +30,9 @@ impl BotCommand for RestockCommand { Method::POST } - fn custom_err_resp(e: &CommandError) -> Option { + fn custom_err_resp(e: &BotError) -> Option { match e { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(PLAYER_DOES_NOT_HAVE_MATCHING_SHOP.to_string()) } _ => None, @@ -59,7 +61,7 @@ impl BotCommand for RestockCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; Ok(Self::ApiParams::new( @@ -68,7 +70,7 @@ impl BotCommand for RestockCommand { )) } - fn build_response(resp: Self::ApiResp, args: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, args: Self::ApiParams) -> String { format!( "**{}** has been restocked at **{}**", args.item_name, resp.name diff --git a/geoffrey_bot/src/bot/commands/selling.rs b/geoffrey_bot/src/bot/commands/selling.rs index ab8db52..1e36278 100644 --- a/geoffrey_bot/src/bot/commands/selling.rs +++ b/geoffrey_bot/src/bot/commands/selling.rs @@ -2,6 +2,7 @@ use std::fmt::Write; use async_trait::async_trait; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; @@ -10,9 +11,10 @@ use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, Selli use geoffrey_models::models::response::selling_listing::SellingListing; use crate::bot::arg_parse::{option_to_order, option_to_sort, option_to_string}; -use crate::bot::commands::{BotCommand, CommandError}; +use crate::bot::commands::BotCommand; use crate::bot::formatters::display_item_listing; -use serenity::builder::CreateApplicationCommand; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct SellingCommand; @@ -62,7 +64,7 @@ impl BotCommand for SellingCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let query = option_to_string(options.get(0), "query")?; let sort = option_to_sort(options.get(1), "sort").ok(); @@ -71,7 +73,7 @@ impl BotCommand for SellingCommand { Ok(SellingParams::new(query, sort, order)) } - fn build_response(resp: Self::ApiResp, args: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, args: Self::ApiParams) -> String { if resp.is_empty() { "No shops were found selling that, maybe I should start selling it...".to_string() } else { diff --git a/geoffrey_bot/src/bot/commands/set_portal.rs b/geoffrey_bot/src/bot/commands/set_portal.rs index 6b88694..6d8faad 100644 --- a/geoffrey_bot/src/bot/commands/set_portal.rs +++ b/geoffrey_bot/src/bot/commands/set_portal.rs @@ -1,17 +1,20 @@ use async_trait::async_trait; -use geoffrey_models::models::locations::Location; -use geoffrey_models::models::Portal; use reqwest::Method; +use serenity::builder::CreateApplicationCommand; use serenity::model::interactions::application_command::{ ApplicationCommandInteraction, ApplicationCommandOptionType, }; -use crate::bot::arg_parse::{option_to_i64, option_to_string}; -use crate::bot::commands::{BotCommand, CommandError}; -use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_LOC; +use geoffrey_models::models::locations::Location; use geoffrey_models::models::parameters::set_portal_params::SetPortalParams; use geoffrey_models::models::response::api_error::GeoffreyAPIError; -use serenity::builder::CreateApplicationCommand; +use geoffrey_models::models::Portal; + +use crate::bot::arg_parse::{option_to_i64, option_to_string}; +use crate::bot::commands::BotCommand; +use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_LOC; +use crate::context::GeoffreyContext; +use crate::error::BotError; pub struct SetPortalCommand; @@ -28,9 +31,9 @@ impl BotCommand for SetPortalCommand { Method::POST } - fn custom_err_resp(err: &CommandError) -> Option { + fn custom_err_resp(err: &BotError) -> Option { match err { - CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { + BotError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => { Some(PLAYER_DOES_NOT_HAVE_MATCHING_LOC.to_string()) } _ => None, @@ -70,7 +73,7 @@ impl BotCommand for SetPortalCommand { async fn process_arguments( command_interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let options = command_interaction.data.options; let loc_name = option_to_string(options.get(0), "name")?; let x_portal = option_to_i64(options.get(1), "x_portal")?; @@ -81,7 +84,7 @@ impl BotCommand for SetPortalCommand { Ok(Self::ApiParams::new(loc_name, portal)) } - fn build_response(resp: Self::ApiResp, _: Self::ApiParams) -> String { + fn build_response(_: &GeoffreyContext, resp: Self::ApiResp, _: Self::ApiParams) -> String { let portal = match resp.portal { None => return "Portal could not be set, try again!".to_string(), Some(p) => p, diff --git a/geoffrey_bot/src/bot/formatters.rs b/geoffrey_bot/src/bot/formatters.rs index aa04403..2463855 100644 --- a/geoffrey_bot/src/bot/formatters.rs +++ b/geoffrey_bot/src/bot/formatters.rs @@ -2,9 +2,10 @@ use chrono::{Duration, Utc}; use geoffrey_models::models::item::ItemListing; use geoffrey_models::models::locations::{Location, LocationData}; use geoffrey_models::models::player::Player; +use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::Portal; -pub fn display_owners(owners: &[Player], limit: usize) -> String { +pub fn display_owners(owners: &[Player], settings: &GeoffreySettings) -> String { let mut plural = ""; let mut ellipses = ""; @@ -12,9 +13,9 @@ pub fn display_owners(owners: &[Player], limit: usize) -> String { plural = "s" } - let range = if owners.len() > limit { + let range = if owners.len() > settings.max_owners_to_display as usize { ellipses = "..."; - limit + settings.max_owners_to_display as usize } else { owners.len() }; @@ -41,7 +42,7 @@ pub fn display_portal(portal: &Portal) -> String { ) } -pub fn display_loc(loc: &Location) -> String { +pub fn display_loc(loc: &Location, settings: &GeoffreySettings) -> String { let portal_str = match &loc.portal { None => "".to_string(), Some(p) => format!("{}, ", display_portal(p)), @@ -52,7 +53,7 @@ pub fn display_loc(loc: &Location) -> String { loc.name, loc.position, portal_str, - display_owners(&loc.owners, 3), + display_owners(&loc.owners, settings), ) } @@ -79,7 +80,7 @@ pub fn display_item_listing(listing: &ItemListing) -> String { } } -pub fn display_loc_full(loc: &Location) -> String { +pub fn display_loc_full(loc: &Location, settings: &GeoffreySettings) -> String { let info = match &loc.loc_data { LocationData::Shop(shop) => { if !shop.item_listings.is_empty() { @@ -98,5 +99,5 @@ pub fn display_loc_full(loc: &Location) -> String { _ => "".to_string(), }; - format!("{}\n{}", display_loc(loc), info) + format!("{}\n{}", display_loc(loc, settings), info) } diff --git a/geoffrey_bot/src/bot/mod.rs b/geoffrey_bot/src/bot/mod.rs index b6fedde..48cb88c 100644 --- a/geoffrey_bot/src/bot/mod.rs +++ b/geoffrey_bot/src/bot/mod.rs @@ -1,6 +1,17 @@ +use std::collections::HashMap; + use serenity::model::interactions::application_command::ApplicationCommand; +use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::*; +use commands::add_item::AddItemCommand; +use commands::add_location::AddLocationCommand; +use commands::find::FindCommand; +use commands::selling::SellingCommand; +use commands::set_portal::SetPortalCommand; +use commands::BotCommand; +use geoffrey_models::models::player::UserID; + use crate::bot::commands::delete::DeleteCommand; use crate::bot::commands::edit_name::EditNameCommand; use crate::bot::commands::edit_pos::EditPosCommand; @@ -11,15 +22,7 @@ use crate::bot::commands::report_out_of_stock::ReportOutOfStockCommand; use crate::bot::commands::restock::RestockCommand; use crate::bot::commands::GeoffreyCommandFn; use crate::context::GeoffreyContext; -use commands::add_item::AddItemCommand; -use commands::add_location::AddLocationCommand; -use commands::find::FindCommand; -use commands::selling::SellingCommand; -use commands::set_portal::SetPortalCommand; -use commands::{BotCommand, CommandError}; -use geoffrey_models::models::player::UserID; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use std::collections::HashMap; +use crate::error::BotError; pub mod arg_parse; pub mod commands; @@ -33,10 +36,7 @@ pub struct CommandRunner { } impl CommandRunner { - async fn register_app_command( - &mut self, - ctx: &Context, - ) -> Result<(), CommandError> { + async fn register_app_command(&mut self, ctx: &Context) -> Result<(), BotError> { let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| { T::create_app_command(command) }) @@ -55,7 +55,7 @@ impl CommandRunner { pub async fn add_command( &mut self, ctx: &Context, - ) -> Result<&mut Self, CommandError> { + ) -> Result<&mut Self, BotError> { self.add_command_to_lookup::(); self.register_app_command::(ctx).await?; Ok(self) @@ -67,11 +67,11 @@ impl CommandRunner { geoffrey_ctx: GeoffreyContext, user_id: UserID, interaction: ApplicationCommandInteraction, - ) -> Result { + ) -> Result { let command_fn = self .commands .get(command_name) - .ok_or_else(|| CommandError::CommandNotFound(command_name.to_string()))?; + .ok_or_else(|| BotError::CommandNotFound(command_name.to_string()))?; Ok(command_fn(geoffrey_ctx, user_id, interaction).await) } @@ -80,7 +80,7 @@ impl CommandRunner { pub async fn build_commands( ctx: &Context, command_runner: &mut CommandRunner, -) -> Result<(), CommandError> { +) -> Result<(), BotError> { command_runner .add_command::(ctx) .await? diff --git a/geoffrey_bot/src/context.rs b/geoffrey_bot/src/context.rs index e4f869d..9d56846 100644 --- a/geoffrey_bot/src/context.rs +++ b/geoffrey_bot/src/context.rs @@ -1,10 +1,12 @@ use crate::configs::GeoffreyBotConfig; +use geoffrey_models::models::settings::GeoffreySettings; use serenity::prelude::TypeMapKey; #[derive(Debug, Clone)] pub struct GeoffreyContext { pub http_client: reqwest::Client, pub cfg: GeoffreyBotConfig, + pub settings: GeoffreySettings, } impl TypeMapKey for GeoffreyContext { diff --git a/geoffrey_bot/src/error.rs b/geoffrey_bot/src/error.rs new file mode 100644 index 0000000..5b9fc5e --- /dev/null +++ b/geoffrey_bot/src/error.rs @@ -0,0 +1,47 @@ +use std::fmt::{Display, Formatter}; + +use reqwest::Error; +use serenity::Error as SerenityError; + +use geoffrey_models::models::response::api_error::GeoffreyAPIError; + +#[derive(Debug)] +pub enum BotError { + ArgumentParse(String), + GeoffreyApi(GeoffreyAPIError), + Serenity(serenity::Error), + Reqwest(reqwest::Error), + CommandNotFound(String), +} + +impl Display for BotError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let s = match self { + BotError::ArgumentParse(s) => format!("Unable to parse argument '{}'", s), + BotError::GeoffreyApi(err) => format!("Got error from GeoffreyAPI: {}", err), + BotError::Serenity(err) => format!("Serenity Error: {}", err), + BotError::Reqwest(err) => format!("Reqwest Error: {}", err), + BotError::CommandNotFound(err) => format!("'{}' not found!", err), + }; + + write!(f, "{}", s) + } +} + +impl From for BotError { + fn from(err: GeoffreyAPIError) -> Self { + Self::GeoffreyApi(err) + } +} + +impl From for BotError { + fn from(err: SerenityError) -> Self { + Self::Serenity(err) + } +} + +impl From for BotError { + fn from(err: Error) -> Self { + Self::Reqwest(err) + } +} diff --git a/geoffrey_bot/src/main.rs b/geoffrey_bot/src/main.rs index f75bb3f..ca61e74 100644 --- a/geoffrey_bot/src/main.rs +++ b/geoffrey_bot/src/main.rs @@ -1,14 +1,6 @@ -mod bot; -mod configs; -mod context; -mod logging; +use std::path::PathBuf; -use crate::bot::{build_commands, CommandRunner}; -use crate::configs::GeoffreyBotConfig; -use crate::context::GeoffreyContext; -use crate::logging::init_logging; -use geoffrey_models::logging::LogLevel; -use geoffrey_models::models::player::UserID; +use reqwest::Method; use serenity::utils::{content_safe, ContentSafeOptions}; use serenity::{ async_trait, @@ -18,9 +10,26 @@ use serenity::{ }, prelude::*, }; -use std::path::PathBuf; use structopt::StructOpt; +use geoffrey_models::logging::LogLevel; +use geoffrey_models::models::parameters::{CommandRequest, EmptyRequest}; +use geoffrey_models::models::player::UserID; +use geoffrey_models::models::settings::GeoffreySettings; + +use crate::api::run_api_query; +use crate::bot::{build_commands, CommandRunner}; +use crate::configs::GeoffreyBotConfig; +use crate::context::GeoffreyContext; +use crate::logging::init_logging; + +mod api; +mod bot; +mod configs; +mod context; +mod error; +mod logging; + #[derive(Debug, StructOpt, Clone)] #[structopt(name = "GeoffreyBot", about = "Geoffrey Discord Bot")] struct Args { @@ -42,6 +51,12 @@ impl TypeMapKey for HttpClient { type Value = serenity::client::Client; } +struct Settings; + +impl TypeMapKey for Settings { + type Value = GeoffreySettings; +} + struct Handler; #[async_trait] @@ -51,6 +66,29 @@ impl EventHandler for Handler { let mut data = ctx.data.write().await; + let mut geoffrey_ctx = data.get_mut::().expect("Unable"); + + match run_api_query::( + geoffrey_ctx, + &CommandRequest { + token: geoffrey_ctx.cfg.api.token.clone(), + user_id: None, + params: EmptyRequest {}, + }, + Method::GET, + "model/settings/", + ) + .await + { + Ok(settings) => geoffrey_ctx.settings = settings, + Err(err) => { + log::warn!( + "Unable to retrieve Geoffrey global settings, using defaults. Error: {}", + err + ) + } + } + let command_runner = data .get_mut::() .expect("Unable to open command runner!"); @@ -154,6 +192,7 @@ async fn main() { data.insert::(GeoffreyContext { http_client: reqwest::Client::new(), cfg, + settings: GeoffreySettings::default(), }); data.insert::(CommandRunner::default()); diff --git a/geoffrey_models/src/lib.rs b/geoffrey_models/src/lib.rs index 15c0b43..439e798 100644 --- a/geoffrey_models/src/lib.rs +++ b/geoffrey_models/src/lib.rs @@ -1,7 +1,9 @@ #![allow(dead_code)] + +use std::fmt::Debug; + use serde::de::DeserializeOwned; use serde::Serialize; -use std::fmt::Debug; pub mod logging; pub mod models; diff --git a/geoffrey_models/src/models/meta.rs b/geoffrey_models/src/models/meta.rs deleted file mode 100644 index b60ae08..0000000 --- a/geoffrey_models/src/models/meta.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::GeoffreyDatabaseModel; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct Meta { - id: Option, - version: u64, - database_version: u64, -} - -impl GeoffreyDatabaseModel for Meta { - fn id(&self) -> Option { - self.id - } - - fn set_id(&mut self, id: u64) { - self.id = Some(id); - } - - fn tree() -> String { - "meta".to_string() - } -} diff --git a/geoffrey_models/src/models/mod.rs b/geoffrey_models/src/models/mod.rs index 52909d8..e13ae09 100644 --- a/geoffrey_models/src/models/mod.rs +++ b/geoffrey_models/src/models/mod.rs @@ -1,15 +1,16 @@ -use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::str::FromStr; +use serde::{Deserialize, Serialize}; + pub mod db_metadata; pub mod item; pub mod link; pub mod locations; -pub mod meta; pub mod parameters; pub mod player; pub mod response; +pub mod settings; pub mod token; #[derive(Serialize, Deserialize, Debug, Copy, Clone)] diff --git a/geoffrey_models/src/models/parameters/mod.rs b/geoffrey_models/src/models/parameters/mod.rs index ef425c2..6473208 100644 --- a/geoffrey_models/src/models/parameters/mod.rs +++ b/geoffrey_models/src/models/parameters/mod.rs @@ -24,3 +24,8 @@ pub struct CommandRequest { pub user_id: Option, pub params: T, } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EmptyRequest {} + +impl GeoffreyParam for EmptyRequest {} diff --git a/geoffrey_models/src/models/settings.rs b/geoffrey_models/src/models/settings.rs new file mode 100644 index 0000000..7baad19 --- /dev/null +++ b/geoffrey_models/src/models/settings.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GeoffreySettings { + pub min_out_of_stock_votes: u32, + pub max_owners_to_display: u32, + pub max_item_listings_to_display: u32, + pub max_str_len: usize, + pub dynmap_base_link: Option, +} + +impl Default for GeoffreySettings { + fn default() -> Self { + Self { + min_out_of_stock_votes: 1, + max_owners_to_display: 3, + max_item_listings_to_display: 10, + max_str_len: 25, + dynmap_base_link: None, + } + } +}