Added global settings
ci/woodpecker/push/woodpecker Pipeline was successful Details

+ Usedful for the impls to retrieve settings without having to redefine them in each project
+ Start of models api
+ Bunch of small tweaks
+ clippy + fmt
main
Joey Hines 2022-01-08 12:35:55 -07:00
parent 99cb470e6e
commit a8f5f5d87b
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
34 changed files with 433 additions and 285 deletions

View File

@ -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()));
}

View File

@ -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)
}
}

View File

@ -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<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp>;
fn validate_parameters(_: &Self::Req, _: &GeoffreyAPIConfig) -> Result<()> {
fn validate_parameters(_: &Self::Req, _: &GeoffreySettings) -> Result<()> {
Ok(())
}
@ -102,7 +104,7 @@ pub fn handle_command<T: 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::<SetPortal>(ctx)),
)
}
pub fn model_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("model").and(create_command_filter::<Settings>(ctx))
}

View File

@ -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 {
server_config: ServerConfig {
db_path: PathBuf::from("test_db/"),
host: "".to_string(),
max_str_len: 64,
},
geoffrey_settings: GeoffreySettings::default(),
};
let ctx = Context::new(config.clone(), Args::default()).unwrap();

View File

@ -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<Context>, _: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
Ok(ctx.cfg.geoffrey_settings.clone())
}
}

View File

@ -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 {

View File

@ -11,7 +11,7 @@ pub struct Context {
impl Context {
pub fn new(cfg: GeoffreyAPIConfig, args: Args) -> Result<Arc<Self>> {
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,
};

View File

@ -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<Context>) {
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;
}

View File

@ -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<T: GeoffreyParam, U: DeserializeOwned>(
ctx: &GeoffreyContext,
param: &CommandRequest<T>,
method: Method,
endpoint: &str,
) -> Result<U, BotError> {
let resp: APIResponse<U> = 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()),
}
}

View File

@ -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<i64, CommandError> {
) -> Result<i64, BotError> {
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<String, CommandError> {
) -> Result<String, BotError> {
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<LocationType, CommandError> {
) -> Result<LocationType, BotError> {
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<Dimension, CommandError> {
) -> Result<Dimension, BotError> {
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<ItemSort, CommandError> {
) -> Result<ItemSort, BotError> {
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<Order, CommandError> {
) -> Result<Order, BotError> {
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(

View File

@ -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<String> {
fn custom_err_resp(e: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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)
)
}
}

View File

@ -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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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)
)
}
}

View File

@ -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<String> {
fn custom_err_resp(err: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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

View File

@ -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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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,)
}
}

View File

@ -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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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)
}
}

View File

@ -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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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
}

View File

@ -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<String> {
fn custom_err_resp(err: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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)
}
}

View File

@ -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<Box<dyn Future<Output = String> + 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<GeoffreyAPIError> for CommandError {
fn from(err: GeoffreyAPIError) -> Self {
Self::GeoffreyApi(err)
}
}
impl From<SerenityError> for CommandError {
fn from(err: SerenityError) -> Self {
Self::Serenity(err)
}
}
impl From<reqwest::Error> 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<Self::ApiParams>,
) -> Result<APIResponse<Self::ApiResp>, CommandError> {
let command_url = Self::command_url(&ctx.cfg.api.base_url);
let resp: APIResponse<Self::ApiResp> = ctx
.http_client
.request(Self::request_type(), command_url)
.json(&params)
.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<String> {
fn custom_err_resp(_: &BotError) -> Option<String> {
None
}
@ -159,13 +94,13 @@ pub trait BotCommand: Send + 'static {
async fn process_arguments(
command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError>;
) -> Result<Self::ApiParams, BotError>;
async fn run_command(
ctx: GeoffreyContext,
user_id: UserID,
command_interact: ApplicationCommandInteraction,
) -> Result<String, CommandError> {
) -> Result<String, BotError> {
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;
}

View File

@ -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<String> {
fn custom_err_resp(err: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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

View File

@ -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<String> {
fn custom_err_resp(e: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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

View File

@ -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<String> {
fn custom_err_resp(e: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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

View File

@ -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<String> {
fn custom_err_resp(e: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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

View File

@ -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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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 {

View File

@ -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<String> {
fn custom_err_resp(err: &BotError) -> Option<String> {
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<Self::ApiParams, CommandError> {
) -> Result<Self::ApiParams, BotError> {
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,

View File

@ -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)
}

View File

@ -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<T: BotCommand>(
&mut self,
ctx: &Context,
) -> Result<(), CommandError> {
async fn register_app_command<T: BotCommand>(&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<T: BotCommand>(
&mut self,
ctx: &Context,
) -> Result<&mut Self, CommandError> {
) -> Result<&mut Self, BotError> {
self.add_command_to_lookup::<T>();
self.register_app_command::<T>(ctx).await?;
Ok(self)
@ -67,11 +67,11 @@ impl CommandRunner {
geoffrey_ctx: GeoffreyContext,
user_id: UserID,
interaction: ApplicationCommandInteraction,
) -> Result<String, CommandError> {
) -> Result<String, BotError> {
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::<AddItemCommand>(ctx)
.await?

View File

@ -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 {

View File

@ -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<GeoffreyAPIError> for BotError {
fn from(err: GeoffreyAPIError) -> Self {
Self::GeoffreyApi(err)
}
}
impl From<SerenityError> for BotError {
fn from(err: SerenityError) -> Self {
Self::Serenity(err)
}
}
impl From<reqwest::Error> for BotError {
fn from(err: Error) -> Self {
Self::Reqwest(err)
}
}

View File

@ -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::<GeoffreyContext>().expect("Unable");
match run_api_query::<EmptyRequest, GeoffreySettings>(
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::<CommandRunner>()
.expect("Unable to open command runner!");
@ -154,6 +192,7 @@ async fn main() {
data.insert::<GeoffreyContext>(GeoffreyContext {
http_client: reqwest::Client::new(),
cfg,
settings: GeoffreySettings::default(),
});
data.insert::<CommandRunner>(CommandRunner::default());

View File

@ -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;

View File

@ -1,23 +0,0 @@
use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Meta {
id: Option<u64>,
version: u64,
database_version: u64,
}
impl GeoffreyDatabaseModel for Meta {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"meta".to_string()
}
}

View File

@ -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)]

View File

@ -24,3 +24,8 @@ pub struct CommandRequest<T> {
pub user_id: Option<UserID>,
pub params: T,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EmptyRequest {}
impl GeoffreyParam for EmptyRequest {}

View File

@ -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<String>,
}
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,
}
}
}