Bot code cleanup

+ Added functions for parsing Application Command options into different types
+ Added formatters for common message formats
+ Clippy + Fmt
main
Joey Hines 2021-12-03 20:42:57 -07:00
parent 8d858bdfd3
commit a655146c81
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
9 changed files with 161 additions and 173 deletions

View File

@ -0,0 +1,72 @@
use crate::bot::commands::CommandError;
use geoffrey_models::models::locations::LocationType;
use geoffrey_models::models::parameters::selling_params::{ItemSort, Order};
use geoffrey_models::models::Dimension;
use serenity::model::prelude::application_command::{
ApplicationCommandInteractionDataOption, ApplicationCommandInteractionDataOptionValue,
};
use std::str::FromStr;
pub fn option_to_i64(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<i64, CommandError> {
if let Some(option) = option {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::Integer(i)) = option {
return Ok(*i);
}
}
Err(CommandError::ArgumentParse(field.to_string()))
}
pub fn option_to_string(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<String, CommandError> {
if let Some(option) = option {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
return Ok(s.clone());
}
}
Err(CommandError::ArgumentParse(field.to_string()))
}
pub fn option_to_loc_type(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<LocationType, CommandError> {
let loc_type = option_to_string(option, field)?;
LocationType::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string()))
}
pub fn option_to_dim(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<Dimension, CommandError> {
let loc_type = option_to_string(option, field)?;
Dimension::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string()))
}
pub fn option_to_sort(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<ItemSort, CommandError> {
let loc_type = option_to_string(option, field)?;
ItemSort::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string()))
}
pub fn option_to_order(
option: Option<&ApplicationCommandInteractionDataOption>,
field: &str,
) -> Result<Order, CommandError> {
let loc_type = option_to_string(option, field)?;
Order::from_str(&loc_type).map_err(|_| CommandError::ArgumentParse(field.to_string()))
}

View File

@ -4,12 +4,13 @@ use serenity::client::Context;
use serenity::model::interactions::application_command::{ use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType, ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
}; };
use serenity::model::prelude::application_command::ApplicationCommandInteractionDataOptionValue;
use geoffrey_models::models::locations::Location; use geoffrey_models::models::locations::Location;
use geoffrey_models::models::parameters::add_item_params::AddItemParams; use geoffrey_models::models::parameters::add_item_params::AddItemParams;
use crate::bot::arg_parse::{option_to_i64, option_to_string};
use crate::bot::commands::{BotCommand, CommandError}; use crate::bot::commands::{BotCommand, CommandError};
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
pub struct AddItemCommand; pub struct AddItemCommand;
@ -72,60 +73,25 @@ impl BotCommand for AddItemCommand {
) -> Result<Self::ApiParams, CommandError> { ) -> Result<Self::ApiParams, CommandError> {
let options = command_interaction.data.options; let options = command_interaction.data.options;
let mut item_name = String::default();
let mut price = 0;
let mut quanity = 0;
let mut shop = String::default();
if let Some(option) = options.get(0) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
item_name = s.clone();
} else {
return Err(CommandError::ArgumentParse("item_name".to_string()));
}
}
if let Some(option) = options.get(1) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::Integer(p)) = option {
price = *p;
} else {
return Err(CommandError::ArgumentParse("price".to_string()));
}
}
if let Some(option) = options.get(2) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::Integer(q)) = option {
quanity = *q;
} else {
return Err(CommandError::ArgumentParse("quantity".to_string()));
}
}
if let Some(option) = options.get(3) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
shop = s.clone();
} else {
return Err(CommandError::ArgumentParse("shop".to_string()));
}
}
Ok(Self::ApiParams::new( Ok(Self::ApiParams::new(
item_name, option_to_string(options.get(0), "item_name")?,
price as u32, option_to_i64(options.get(1), "price")? as u32,
quanity as u32, option_to_i64(options.get(2), "quantity")? as u32,
shop, option_to_string(options.get(3), "shop")?,
)) ))
} }
fn build_response(resp: Self::ApiResp) -> String { fn build_response(resp: Self::ApiResp) -> String {
format!("{} has been updated", resp.name) format!("{} has been updated", resp.name)
} }
fn custom_err_resp(e: &CommandError) -> Option<String> {
if let CommandError::GeoffreyApi(err) = e {
if matches!(err, GeoffreyAPIError::EntryNotFound) {
return Some("You don't have a shop by that name ding dong!".to_string());
}
}
None
}
} }

View File

@ -1,32 +1,18 @@
use std::str::FromStr;
use async_trait::async_trait; 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 reqwest::Method;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::interactions::application_command::{ use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandInteractionDataOption, ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandOptionType,
}; };
use serenity::model::prelude::application_command::ApplicationCommandInteractionDataOptionValue;
use geoffrey_models::models::{Dimension, Position};
use geoffrey_models::models::locations::{Location, LocationType};
use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
use crate::bot::arg_parse::{option_to_dim, option_to_i64, option_to_loc_type, option_to_string};
use crate::bot::commands::{BotCommand, CommandError}; use crate::bot::commands::{BotCommand, CommandError};
pub struct AddLocationCommand; pub struct AddLocationCommand;
fn option_to_i64(option: &ApplicationCommandInteractionDataOption) -> Option<i64> {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::Integer(s)) = option {
Some(*s)
} else {
None
}
}
#[async_trait] #[async_trait]
impl BotCommand for AddLocationCommand { impl BotCommand for AddLocationCommand {
type ApiParams = AddLocationParams; type ApiParams = AddLocationParams;
@ -120,55 +106,16 @@ impl BotCommand for AddLocationCommand {
command_interaction: ApplicationCommandInteraction, command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError> { ) -> Result<Self::ApiParams, CommandError> {
let options = command_interaction.data.options; let options = command_interaction.data.options;
let mut name = String::new(); let name = option_to_string(options.get(0), "name")?;
let mut loc_type = LocationType::Base; let loc_type = option_to_loc_type(options.get(1), "loc_type")?;
let x; let x = option_to_i64(options.get(2), "x")?;
let _y; let _y = option_to_i64(options.get(3), "x")?;
let z; let z = option_to_i64(options.get(4), "x")?;
let dimension; let dim = option_to_dim(options.get(5), "dimension").unwrap_or(Dimension::Overworld);
if let Some(option) = options.get(0) { let position = Position::new(x as i32, z as i32, dim);
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option { Ok(Self::ApiParams::new(name, position, loc_type, None))
loc_type = LocationType::from_str(s).unwrap();
} else {
return Err(CommandError::ArgumentParse("loc_type".to_string()));
}
}
if let Some(option) = options.get(1) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
name = s.clone();
} else {
return Err(CommandError::ArgumentParse("name".to_string()));
}
}
x = option_to_i64(&options[2]).unwrap() as i32;
_y = option_to_i64(&options[3]).unwrap() as i32;
z = option_to_i64(&options[4]).unwrap() as i32;
if let Some(option) = options.get(5) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
dimension = Dimension::from_str(s.as_str()).unwrap();
} else {
return Err(CommandError::ArgumentParse("dimension".to_string()));
}
} else {
dimension = Dimension::default();
}
Ok(Self::ApiParams::new(
name,
Position::new(x, z, dimension),
loc_type,
None,
))
} }
fn build_response(resp: Self::ApiResp) -> String { fn build_response(resp: Self::ApiResp) -> String {

View File

@ -1,17 +1,17 @@
use std::fmt::Write;
use async_trait::async_trait; use async_trait::async_trait;
use reqwest::Method; use reqwest::Method;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::interactions::application_command::{ use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType, ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
}; };
use serenity::model::prelude::application_command::ApplicationCommandInteractionDataOptionValue; use std::fmt::Write;
use geoffrey_models::models::locations::Location; use geoffrey_models::models::locations::Location;
use geoffrey_models::models::parameters::find_params::FindParams; 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, CommandError};
use crate::bot::formatters::display_loc;
pub struct FindCommand; pub struct FindCommand;
@ -50,16 +50,9 @@ impl BotCommand for FindCommand {
command_interaction: ApplicationCommandInteraction, command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError> { ) -> Result<Self::ApiParams, CommandError> {
let options = command_interaction.data.options; let options = command_interaction.data.options;
let query = option_to_string(options.get(0), "query")?;
if let Some(option) = options.get(0) { Ok(FindParams::new(query))
let query = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = query {
return Ok(FindParams::new(s.clone()));
}
}
Err(CommandError::ArgumentParse("query".to_string()))
} }
fn build_response(resp: Self::ApiResp) -> String { fn build_response(resp: Self::ApiResp) -> String {
@ -69,7 +62,7 @@ impl BotCommand for FindCommand {
let mut resp_str = String::new(); let mut resp_str = String::new();
writeln!(resp_str, "The following locations match:").unwrap(); writeln!(resp_str, "The following locations match:").unwrap();
for loc in resp { for loc in resp {
writeln!(resp_str, "**{}**, {}", loc.name, loc.position).unwrap(); writeln!(display_loc(loc)).unwrap();
} }
resp_str resp_str
} }

View File

@ -5,8 +5,10 @@ use reqwest::Error;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction,
};
use serenity::Error as SerenityError; use serenity::Error as SerenityError;
use serenity::model::interactions::application_command::{ApplicationCommand, ApplicationCommandInteraction};
use geoffrey_models::models::parameters::CommandRequest; use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::player::UserID; use geoffrey_models::models::player::UserID;
@ -17,7 +19,6 @@ use crate::context::GeoffreyContext;
pub mod add_item; pub mod add_item;
pub mod add_location; pub mod add_location;
mod arg_parse;
pub mod find; pub mod find;
pub mod selling; pub mod selling;

View File

@ -1,5 +1,4 @@
use std::fmt::Write; use std::fmt::Write;
use std::str::FromStr;
use async_trait::async_trait; use async_trait::async_trait;
use reqwest::Method; use reqwest::Method;
@ -7,11 +6,11 @@ use serenity::client::Context;
use serenity::model::interactions::application_command::{ use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType, ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
}; };
use serenity::model::prelude::application_command::ApplicationCommandInteractionDataOptionValue;
use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams}; use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams};
use geoffrey_models::models::response::selling_listing::SellingListing; 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, CommandError};
pub struct SellingCommand; pub struct SellingCommand;
@ -69,39 +68,9 @@ impl BotCommand for SellingCommand {
command_interaction: ApplicationCommandInteraction, command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError> { ) -> Result<Self::ApiParams, CommandError> {
let options = command_interaction.data.options; let options = command_interaction.data.options;
let mut query = String::new(); let query = option_to_string(options.get(0), "query")?;
let mut sort = None; let sort = option_to_sort(options.get(1), "sort").ok();
let mut order = None; let order = option_to_order(options.get(2), "order").ok();
if let Some(option) = options.get(0) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
query = s.clone();
} else {
return Err(CommandError::ArgumentParse("query".to_string()));
}
}
if let Some(option) = options.get(1) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
sort = Some(ItemSort::from_str(s).unwrap());
} else {
return Err(CommandError::ArgumentParse("sort".to_string()));
}
}
if let Some(option) = options.get(2) {
let option = option.resolved.as_ref();
if let Some(ApplicationCommandInteractionDataOptionValue::String(s)) = option {
order = Some(Order::from_str(s).unwrap());
} else {
return Err(CommandError::ArgumentParse("order".to_string()));
}
}
Ok(SellingParams::new(query, sort, order)) Ok(SellingParams::new(query, sort, order))
} }
@ -111,7 +80,7 @@ impl BotCommand for SellingCommand {
"No shops were found selling that, maybe I should start selling it...".to_string() "No shops were found selling that, maybe I should start selling it...".to_string()
} else { } else {
let mut resp_str = String::new(); let mut resp_str = String::new();
writeln!(resp_str, "The items match:").unwrap(); writeln!(resp_str, "The following items match:").unwrap();
for item in resp { for item in resp {
writeln!( writeln!(
resp_str, resp_str,

View File

@ -0,0 +1,38 @@
use geoffrey_models::models::locations::Location;
use geoffrey_models::models::player::Player;
pub fn display_owners(owners: Vec<Player>, limit: usize) -> String {
let mut plural = "";
let mut ellipses = "";
if owners.len() > 1 {
plural = "s"
}
let range = if owners.len() > limit {
ellipses = "...";
limit
} else {
owners.len()
};
format!(
"Owner{}: {}{}",
plural,
owners[0..range]
.iter()
.map(|owner| owner.name.clone())
.collect::<Vec<String>>()
.join(", "),
ellipses
)
}
pub fn display_loc(loc: Location) -> String {
format!(
"**{}**, {}, Owner(s): **{}**",
loc.name,
loc.position,
display_owners(loc.owners, 3)
)
}

View File

@ -1,13 +1,15 @@
use serenity::model::interactions::application_command::ApplicationCommand; use serenity::model::interactions::application_command::ApplicationCommand;
use serenity::prelude::*; use serenity::prelude::*;
use commands::{BotCommand, CommandError};
use commands::add_item::AddItemCommand; use commands::add_item::AddItemCommand;
use commands::add_location::AddLocationCommand; use commands::add_location::AddLocationCommand;
use commands::find::FindCommand; use commands::find::FindCommand;
use commands::selling::SellingCommand; use commands::selling::SellingCommand;
use commands::{BotCommand, CommandError};
pub mod arg_parse;
pub mod commands; pub mod commands;
pub mod formatters;
pub async fn create_commands(ctx: &Context) -> Result<Vec<ApplicationCommand>, CommandError> { pub async fn create_commands(ctx: &Context) -> Result<Vec<ApplicationCommand>, CommandError> {
let mut commands: Vec<ApplicationCommand> = Vec::new(); let mut commands: Vec<ApplicationCommand> = Vec::new();

View File

@ -2,14 +2,14 @@ mod bot;
mod configs; mod configs;
mod context; mod context;
use bot::commands::add_item::AddItemCommand;
use bot::commands::add_location::AddLocationCommand;
use bot::commands::BotCommand;
use crate::bot::create_commands; use crate::bot::create_commands;
use bot::commands::find::FindCommand;
use bot::commands::selling::SellingCommand;
use crate::configs::GeoffreyBotConfig; use crate::configs::GeoffreyBotConfig;
use crate::context::GeoffreyContext; use crate::context::GeoffreyContext;
use bot::commands::add_item::AddItemCommand;
use bot::commands::add_location::AddLocationCommand;
use bot::commands::find::FindCommand;
use bot::commands::selling::SellingCommand;
use bot::commands::BotCommand;
use geoffrey_models::models::player::UserID; use geoffrey_models::models::player::UserID;
use serenity::utils::{content_safe, ContentSafeOptions}; use serenity::utils::{content_safe, ContentSafeOptions};
use serenity::{ use serenity::{