Improved bot command handling

+ Created CommandRunner struct to house all the bot commands
+ Streamlines registering app commands and dispatching commands
+ Bit of hecky rust that may need to be cleaned up
+ Clippy + Fmt
main
Joey Hines 2021-12-12 19:30:38 -07:00
parent 2ff4d14e3f
commit 3aaaf39913
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
14 changed files with 330 additions and 267 deletions

View File

@ -11,8 +11,8 @@ use geoffrey_models::models::parameters::add_token_params::AddTokenParams;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::token::Permissions;
use crate::commands::{Command, command_filter};
use crate::commands::add_token::AddToken;
use crate::commands::{command_filter, Command};
use crate::config::GeoffreyAPIConfig;
use crate::context::Context;
use crate::logging::init_logging;

View File

@ -1,8 +1,7 @@
use async_trait::async_trait;
use reqwest::Method;
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
use geoffrey_models::models::locations::Location;
@ -11,6 +10,7 @@ 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 geoffrey_models::models::response::api_error::GeoffreyAPIError;
use serenity::builder::CreateApplicationCommand;
pub struct AddItemCommand;
@ -27,45 +27,50 @@ impl BotCommand for AddItemCommand {
Method::POST
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
command
.name(Self::command_name())
.description("Add a item to a shop.")
.create_option(|option| {
option
.name("item_name")
.description("Name of the item to sell.")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("price")
.description("Price to list them item at.")
.kind(ApplicationCommandOptionType::Integer)
.required(true)
.min_int_value(0)
})
.create_option(|option| {
option
.name("quantity")
.description("Number of items to sell for price")
.kind(ApplicationCommandOptionType::Integer)
.required(true)
.min_int_value(1)
})
.create_option(|option| {
option
.name("shop")
.description("Shop to list the item at")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
})
.await?;
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());
}
}
Ok(command)
None
}
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Add a item to a shop.")
.create_option(|option| {
option
.name("item_name")
.description("Name of the item to sell.")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("price")
.description("Price to list them item at.")
.kind(ApplicationCommandOptionType::Integer)
.required(true)
.min_int_value(0)
})
.create_option(|option| {
option
.name("quantity")
.description("Number of items to sell for price")
.kind(ApplicationCommandOptionType::Integer)
.required(true)
.min_int_value(1)
})
.create_option(|option| {
option
.name("shop")
.description("Shop to list the item at")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
}
async fn process_arguments(
@ -84,14 +89,4 @@ impl BotCommand for AddItemCommand {
fn build_response(resp: Self::ApiResp) -> String {
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

@ -3,13 +3,13 @@ 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::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
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 serenity::builder::CreateApplicationCommand;
pub struct AddLocationCommand;
@ -26,72 +26,67 @@ impl BotCommand for AddLocationCommand {
Method::POST
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
command
.name(Self::command_name())
.description("Add a location to Geoffrey.")
.create_option(|option| {
option
.name("type")
.description("Location type")
.kind(ApplicationCommandOptionType::String)
.required(true)
.add_string_choice(LocationType::Base, LocationType::Base)
.add_string_choice(LocationType::Shop, LocationType::Shop)
.add_string_choice(LocationType::Attraction, LocationType::Attraction)
.add_string_choice(LocationType::Town, LocationType::Town)
.add_string_choice(LocationType::Farm, LocationType::Farm)
.add_string_choice(LocationType::Market, LocationType::Market)
})
.create_option(|option| {
option
.name("name")
.description("Name of the location")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("x")
.description("X coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("y")
.description("Y coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("z")
.description("Z coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("dimension")
.description("Dimension of the shop, default is Overworld")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(Dimension::Overworld, Dimension::Overworld)
.add_string_choice(Dimension::Nether, Dimension::Nether)
.add_string_choice(Dimension::TheEnd, Dimension::TheEnd)
.required(false)
})
})
.await?;
Ok(command)
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Add a location to Geoffrey.")
.create_option(|option| {
option
.name("type")
.description("Location type")
.kind(ApplicationCommandOptionType::String)
.required(true)
.add_string_choice(LocationType::Base, LocationType::Base)
.add_string_choice(LocationType::Shop, LocationType::Shop)
.add_string_choice(LocationType::Attraction, LocationType::Attraction)
.add_string_choice(LocationType::Town, LocationType::Town)
.add_string_choice(LocationType::Farm, LocationType::Farm)
.add_string_choice(LocationType::Market, LocationType::Market)
})
.create_option(|option| {
option
.name("name")
.description("Name of the location")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("x")
.description("X coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("y")
.description("Y coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("z")
.description("Z coordinate of the location")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("dimension")
.description("Dimension of the shop, default is Overworld")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(Dimension::Overworld, Dimension::Overworld)
.add_string_choice(Dimension::Nether, Dimension::Nether)
.add_string_choice(Dimension::TheEnd, Dimension::TheEnd)
.required(false)
})
}
async fn process_arguments(

View File

@ -1,8 +1,7 @@
use async_trait::async_trait;
use reqwest::Method;
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
use std::fmt::Write;
@ -12,6 +11,7 @@ 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::formatters::display_loc;
use serenity::builder::CreateApplicationCommand;
pub struct FindCommand;
@ -28,22 +28,17 @@ impl BotCommand for FindCommand {
Method::GET
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
command
.name(Self::command_name())
.description("Find a location in Geoffrey.")
.create_option(|option| {
option
.name("query")
.description("The location name or player to lookup")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
})
.await?;
Ok(command)
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Find a location in Geoffrey.")
.create_option(|option| {
option
.name("query")
.description("The location name or player to lookup")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
}
async fn process_arguments(

View File

@ -4,10 +4,7 @@ use async_trait::async_trait;
use reqwest::Error;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction,
};
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
use serenity::Error as SerenityError;
use geoffrey_models::models::parameters::CommandRequest;
@ -16,6 +13,9 @@ use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
use crate::context::GeoffreyContext;
use serenity::builder::CreateApplicationCommand;
use std::future::Future;
use std::pin::Pin;
pub mod add_item;
pub mod add_location;
@ -23,12 +23,21 @@ pub mod find;
pub mod selling;
pub mod set_portal;
pub type GeoffreyCommandFn = Box<
fn(
GeoffreyContext,
UserID,
ApplicationCommandInteraction,
) -> 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 {
@ -38,6 +47,7 @@ impl Display for CommandError {
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)
@ -63,7 +73,7 @@ impl From<reqwest::Error> for CommandError {
}
#[async_trait]
pub trait BotCommand {
pub trait BotCommand: Send + 'static {
type ApiParams: CommandRequest;
type ApiResp: Serialize + DeserializeOwned + Send;
@ -78,7 +88,7 @@ pub trait BotCommand {
}
async fn run_api_query(
ctx: &GeoffreyContext,
ctx: GeoffreyContext,
params: Self::ApiParams,
) -> Result<APIResponse<Self::ApiResp>, CommandError> {
let command_url = Self::command_url(&ctx.cfg.api.base_url);
@ -103,7 +113,7 @@ pub trait BotCommand {
"You need to register before using this command!".to_string()
}
CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => {
"Couldn't find that, maybe look for something that exists?".to_string()
"Couldn't find that, maybe look for something that exists?".to_string()
}
CommandError::GeoffreyApi(GeoffreyAPIError::PermissionInsufficient) => {
"Looks like you don't have permission for that.".to_string()
@ -133,20 +143,24 @@ pub trait BotCommand {
None
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError>;
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand;
async fn process_arguments(
command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError>;
async fn run_command(
ctx: &GeoffreyContext,
ctx: GeoffreyContext,
user_id: UserID,
command_interact: ApplicationCommandInteraction,
) -> Result<String, CommandError> {
let mut args = Self::process_arguments(command_interact).await?;
log::info!("Running command {}, with args {:?}", Self::command_name(), args);
log::info!(
"Running command {}, with args {:?}",
Self::command_name(),
args
);
args.set_token(ctx.cfg.api.token.clone());
args.set_user_id(user_id);
@ -160,7 +174,7 @@ pub trait BotCommand {
}
async fn command(
ctx: &GeoffreyContext,
ctx: GeoffreyContext,
user_id: UserID,
command_interact: ApplicationCommandInteraction,
) -> String {

View File

@ -2,9 +2,8 @@ use std::fmt::Write;
use async_trait::async_trait;
use reqwest::Method;
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams};
@ -12,6 +11,7 @@ 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 serenity::builder::CreateApplicationCommand;
pub struct SellingCommand;
@ -28,40 +28,35 @@ impl BotCommand for SellingCommand {
Method::GET
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
command
.name(Self::command_name())
.description("Find items for sale.")
.create_option(|option| {
option
.name("query")
.description("Item to find")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("sort")
.description("How to sort items")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(ItemSort::Price, ItemSort::Price)
.add_string_choice(ItemSort::Restock, ItemSort::Restock)
.required(false)
})
.create_option(|option| {
option
.name("order")
.description("Order of the item Search")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(Order::Low, Order::Low)
.add_string_choice(Order::High, Order::High)
.required(false)
})
})
.await?;
Ok(command)
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Find items for sale.")
.create_option(|option| {
option
.name("query")
.description("Item to find")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("sort")
.description("How to sort items")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(ItemSort::Price, ItemSort::Price)
.add_string_choice(ItemSort::Restock, ItemSort::Restock)
.required(false)
})
.create_option(|option| {
option
.name("order")
.description("Order of the item Search")
.kind(ApplicationCommandOptionType::String)
.add_string_choice(Order::Low, Order::Low)
.add_string_choice(Order::High, Order::High)
.required(false)
})
}
async fn process_arguments(

View File

@ -2,14 +2,14 @@ use async_trait::async_trait;
use geoffrey_models::models::locations::Location;
use geoffrey_models::models::Portal;
use reqwest::Method;
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
use crate::bot::arg_parse::{option_to_i64, option_to_string};
use crate::bot::commands::{BotCommand, CommandError};
use geoffrey_models::models::parameters::set_portal_params::SetPortalParams;
use serenity::builder::CreateApplicationCommand;
pub struct SetPortalCommand;
@ -26,40 +26,35 @@ impl BotCommand for SetPortalCommand {
Method::POST
}
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
command
.name(Self::command_name())
.description("Set a portal for a location")
.create_option(|option| {
option
.name("loc_name")
.description("Name of the location")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("x_portal")
.description("X coordinate of the portal in the nether")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("z_portal")
.description("Z coordinate of the portal in the nether")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
})
.await?;
Ok(command)
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Set a portal for a location")
.create_option(|option| {
option
.name("loc_name")
.description("Name of the location")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("x_portal")
.description("X coordinate of the portal in the nether")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
.create_option(|option| {
option
.name("z_portal")
.description("Z coordinate of the portal in the nether")
.kind(ApplicationCommandOptionType::Integer)
.max_int_value(i32::MAX)
.min_int_value(i32::MIN)
.required(true)
})
}
async fn process_arguments(
@ -78,4 +73,4 @@ impl BotCommand for SetPortalCommand {
fn build_response(resp: Self::ApiResp) -> String {
format!("{} has been been updated.", resp.name)
}
}
}

View File

@ -30,13 +30,19 @@ pub fn display_owners(owners: Vec<Player>, limit: usize) -> String {
}
pub fn display_portal(portal: Portal) -> String {
format!("Portal: {} {} (x={}, z={})", portal.direction(), portal.tunnel_addr(), portal.x, portal.z)
format!(
"Portal: {} {} (x={}, z={})",
portal.direction(),
portal.tunnel_addr(),
portal.x,
portal.z
)
}
pub fn display_loc(loc: Location) -> String {
let portal_str = match loc.portal {
None => "".to_string(),
Some(p) => format!("{}, ", display_portal(p))
Some(p) => format!("{}, ", display_portal(p)),
};
format!(

View File

@ -1,25 +1,86 @@
use serenity::model::interactions::application_command::ApplicationCommand;
use serenity::prelude::*;
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;
pub mod arg_parse;
pub mod commands;
pub mod formatters;
pub async fn create_commands(ctx: &Context) -> Result<Vec<ApplicationCommand>, CommandError> {
let mut commands: Vec<ApplicationCommand> = Vec::new();
commands.push(FindCommand::create_app_command(ctx).await?);
commands.push(SellingCommand::create_app_command(ctx).await?);
commands.push(AddLocationCommand::create_app_command(ctx).await?);
commands.push(AddItemCommand::create_app_command(ctx).await?);
commands.push( SetPortalCommand::create_app_command(ctx).await?);
Ok(commands)
#[derive(Default)]
pub struct CommandRunner {
commands: HashMap<String, GeoffreyCommandFn>,
}
impl CommandRunner {
async fn register_app_command<T: BotCommand>(ctx: &Context) -> Result<(), CommandError> {
ApplicationCommand::create_global_application_command(&ctx.http, |command| {
T::create_app_command(command)
})
.await?;
Ok(())
}
fn add_command_to_lookup<T: BotCommand>(&mut self) {
self.commands
.insert(T::command_name(), Box::new(T::command));
}
pub async fn add_command<T: BotCommand>(
&mut self,
ctx: &Context,
) -> Result<&mut Self, CommandError> {
self.add_command_to_lookup::<T>();
Self::register_app_command::<T>(ctx).await?;
Ok(self)
}
pub async fn run_command<'r>(
&self,
command_name: &str,
geoffrey_ctx: GeoffreyContext,
user_id: UserID,
interaction: ApplicationCommandInteraction,
) -> Result<String, CommandError> {
let command_fn = self
.commands
.get(command_name)
.ok_or_else(|| CommandError::CommandNotFound(command_name.to_string()))?;
Ok(command_fn(geoffrey_ctx, user_id, interaction).await)
}
}
pub async fn build_commands(
ctx: &Context,
command_runner: &mut CommandRunner,
) -> Result<(), CommandError> {
command_runner
.add_command::<AddItemCommand>(ctx)
.await?
.add_command::<AddLocationCommand>(ctx)
.await?
.add_command::<FindCommand>(ctx)
.await?
.add_command::<SellingCommand>(ctx)
.await?
.add_command::<SetPortalCommand>(ctx)
.await?;
Ok(())
}
impl TypeMapKey for CommandRunner {
type Value = CommandRunner;
}

View File

@ -8,4 +8,4 @@ pub fn init_logging(log_level: LogLevel) -> Result<(), SetLoggerError> {
.with_level(LevelFilter::Warn)
.with_module_level("geoffrey_bot", log_level.into())
.init()
}
}

View File

@ -3,14 +3,11 @@ mod configs;
mod context;
mod logging;
use crate::bot::create_commands;
use crate::bot::{build_commands, CommandRunner};
use crate::configs::GeoffreyBotConfig;
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 crate::logging::init_logging;
use geoffrey_models::logging::LogLevel;
use geoffrey_models::models::player::UserID;
use serenity::utils::{content_safe, ContentSafeOptions};
use serenity::{
@ -23,9 +20,6 @@ use serenity::{
};
use std::path::PathBuf;
use structopt::StructOpt;
use geoffrey_models::logging::LogLevel;
use crate::logging::init_logging;
use crate::bot::commands::set_portal::SetPortalCommand;
#[derive(Debug, StructOpt, Clone)]
#[structopt(name = "GeoffreyBot", about = "Geoffrey Discord Bot")]
@ -33,11 +27,11 @@ struct Args {
#[structopt(env = "GEOFFREY_BOT_CONFIG", parse(from_os_str))]
config: PathBuf,
#[structopt(
short,
long,
env = "GEOFFREY_LOG_LEVEL",
parse(from_str),
default_value = "Info"
short,
long,
env = "GEOFFREY_LOG_LEVEL",
parse(from_str),
default_value = "Info"
)]
log_level: LogLevel,
}
@ -54,36 +48,47 @@ struct Handler;
impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) {
log::info!("{} is connected!", ready.user.name);
let commands = create_commands(&ctx).await.unwrap();
log::debug!("The following bot have been registered:");
let mut data = ctx.data.write().await;
for command in commands {
log::debug!(
"{}: {} - {:?}",
command.name, command.description, command.options
);
let command_runner = data
.get_mut::<CommandRunner>()
.expect("Unable to open command runner!");
match build_commands(&ctx, command_runner).await {
Ok(_) => {}
Err(e) => {
log::warn!("Error registering commands: {:?}", e)
}
}
}
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
let data = ctx.data.read().await;
let geoffrey_ctx = data.get::<GeoffreyContext>().unwrap();
let command_runner = data.get::<CommandRunner>().unwrap();
if let Interaction::ApplicationCommand(command) = interaction {
let user_id = UserID::DiscordUUID {
discord_uuid: command.user.id.0,
};
let msg = match command.data.name.as_str() {
"find" => FindCommand::command(geoffrey_ctx, user_id, command.clone()).await,
"selling" => SellingCommand::command(geoffrey_ctx, user_id, command.clone()).await,
"add_location" => {
AddLocationCommand::command(geoffrey_ctx, user_id, command.clone()).await
let command_name = command.data.name.clone();
let msg = match command_runner
.run_command(
&command_name,
geoffrey_ctx.clone(),
user_id,
command.clone(),
)
.await
{
Ok(msg) => msg,
Err(e) => {
log::warn!("Error running command '{}': {:?}", command_name, e);
return;
}
"add_item" => AddItemCommand::command(geoffrey_ctx, user_id, command.clone()).await,
"set_portal" => SetPortalCommand::command(geoffrey_ctx, user_id, command.clone()).await,
_ => "not implemented :(".to_string(),
};
let msg = content_safe(&ctx.cache, &msg, &ContentSafeOptions::default()).await;
@ -143,7 +148,9 @@ async fn main() {
data.insert::<GeoffreyContext>(GeoffreyContext {
http_client: reqwest::Client::new(),
cfg,
})
});
data.insert::<CommandRunner>(CommandRunner::default());
}
if let Err(e) = client.start().await {

View File

@ -3,8 +3,8 @@ use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::Debug;
pub mod models;
pub mod logging;
pub mod models;
pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned + Debug {
fn id(&self) -> Option<u64>;

View File

@ -1,5 +1,5 @@
use log::LevelFilter;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum LogLevel {

View File

@ -122,7 +122,7 @@ impl Portal {
pub fn tunnel_addr(&self) -> i32 {
match self.direction() {
Direction::North | Direction::South => self.z.abs(),
Direction::East | Direction::West => self.x.abs()
Direction::East | Direction::West => self.x.abs(),
}
}
}