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 + Fmtmain
parent
2ff4d14e3f
commit
3aaaf39913
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use log::LevelFilter;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum LogLevel {
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue