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::response::api_error::GeoffreyAPIError;
|
||||||
use geoffrey_models::models::token::Permissions;
|
use geoffrey_models::models::token::Permissions;
|
||||||
|
|
||||||
use crate::commands::{Command, command_filter};
|
|
||||||
use crate::commands::add_token::AddToken;
|
use crate::commands::add_token::AddToken;
|
||||||
|
use crate::commands::{command_filter, Command};
|
||||||
use crate::config::GeoffreyAPIConfig;
|
use crate::config::GeoffreyAPIConfig;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::logging::init_logging;
|
use crate::logging::init_logging;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serenity::client::Context;
|
|
||||||
use serenity::model::interactions::application_command::{
|
use serenity::model::interactions::application_command::{
|
||||||
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
|
ApplicationCommandInteraction, ApplicationCommandOptionType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use geoffrey_models::models::locations::Location;
|
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::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;
|
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
|
||||||
pub struct AddItemCommand;
|
pub struct AddItemCommand;
|
||||||
|
|
||||||
|
@ -27,45 +27,50 @@ impl BotCommand for AddItemCommand {
|
||||||
Method::POST
|
Method::POST
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
|
fn custom_err_resp(e: &CommandError) -> Option<String> {
|
||||||
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
if let CommandError::GeoffreyApi(err) = e {
|
||||||
command
|
if matches!(err, GeoffreyAPIError::EntryNotFound) {
|
||||||
.name(Self::command_name())
|
return Some("You don't have a shop by that name ding dong!".to_string());
|
||||||
.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?;
|
|
||||||
|
|
||||||
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(
|
async fn process_arguments(
|
||||||
|
@ -84,14 +89,4 @@ impl BotCommand for AddItemCommand {
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ use geoffrey_models::models::locations::{Location, LocationType};
|
||||||
use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
|
use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
|
||||||
use geoffrey_models::models::{Dimension, Position};
|
use geoffrey_models::models::{Dimension, Position};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serenity::client::Context;
|
|
||||||
use serenity::model::interactions::application_command::{
|
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::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};
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
|
||||||
pub struct AddLocationCommand;
|
pub struct AddLocationCommand;
|
||||||
|
|
||||||
|
@ -26,72 +26,67 @@ impl BotCommand for AddLocationCommand {
|
||||||
Method::POST
|
Method::POST
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
|
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
command
|
||||||
command
|
.name(Self::command_name())
|
||||||
.name(Self::command_name())
|
.description("Add a location to Geoffrey.")
|
||||||
.description("Add a location to Geoffrey.")
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("type")
|
||||||
.name("type")
|
.description("Location type")
|
||||||
.description("Location type")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.required(true)
|
||||||
.required(true)
|
.add_string_choice(LocationType::Base, LocationType::Base)
|
||||||
.add_string_choice(LocationType::Base, LocationType::Base)
|
.add_string_choice(LocationType::Shop, LocationType::Shop)
|
||||||
.add_string_choice(LocationType::Shop, LocationType::Shop)
|
.add_string_choice(LocationType::Attraction, LocationType::Attraction)
|
||||||
.add_string_choice(LocationType::Attraction, LocationType::Attraction)
|
.add_string_choice(LocationType::Town, LocationType::Town)
|
||||||
.add_string_choice(LocationType::Town, LocationType::Town)
|
.add_string_choice(LocationType::Farm, LocationType::Farm)
|
||||||
.add_string_choice(LocationType::Farm, LocationType::Farm)
|
.add_string_choice(LocationType::Market, LocationType::Market)
|
||||||
.add_string_choice(LocationType::Market, LocationType::Market)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("name")
|
||||||
.name("name")
|
.description("Name of the location")
|
||||||
.description("Name of the location")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("x")
|
||||||
.name("x")
|
.description("X coordinate of the location")
|
||||||
.description("X coordinate of the location")
|
.kind(ApplicationCommandOptionType::Integer)
|
||||||
.kind(ApplicationCommandOptionType::Integer)
|
.max_int_value(i32::MAX)
|
||||||
.max_int_value(i32::MAX)
|
.min_int_value(i32::MIN)
|
||||||
.min_int_value(i32::MIN)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("y")
|
||||||
.name("y")
|
.description("Y coordinate of the location")
|
||||||
.description("Y coordinate of the location")
|
.kind(ApplicationCommandOptionType::Integer)
|
||||||
.kind(ApplicationCommandOptionType::Integer)
|
.max_int_value(i32::MAX)
|
||||||
.max_int_value(i32::MAX)
|
.min_int_value(i32::MIN)
|
||||||
.min_int_value(i32::MIN)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("z")
|
||||||
.name("z")
|
.description("Z coordinate of the location")
|
||||||
.description("Z coordinate of the location")
|
.kind(ApplicationCommandOptionType::Integer)
|
||||||
.kind(ApplicationCommandOptionType::Integer)
|
.max_int_value(i32::MAX)
|
||||||
.max_int_value(i32::MAX)
|
.min_int_value(i32::MIN)
|
||||||
.min_int_value(i32::MIN)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("dimension")
|
||||||
.name("dimension")
|
.description("Dimension of the shop, default is Overworld")
|
||||||
.description("Dimension of the shop, default is Overworld")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.add_string_choice(Dimension::Overworld, Dimension::Overworld)
|
||||||
.add_string_choice(Dimension::Overworld, Dimension::Overworld)
|
.add_string_choice(Dimension::Nether, Dimension::Nether)
|
||||||
.add_string_choice(Dimension::Nether, Dimension::Nether)
|
.add_string_choice(Dimension::TheEnd, Dimension::TheEnd)
|
||||||
.add_string_choice(Dimension::TheEnd, Dimension::TheEnd)
|
.required(false)
|
||||||
.required(false)
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_arguments(
|
async fn process_arguments(
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serenity::client::Context;
|
|
||||||
use serenity::model::interactions::application_command::{
|
use serenity::model::interactions::application_command::{
|
||||||
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
|
ApplicationCommandInteraction, ApplicationCommandOptionType,
|
||||||
};
|
};
|
||||||
use std::fmt::Write;
|
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::arg_parse::option_to_string;
|
||||||
use crate::bot::commands::{BotCommand, CommandError};
|
use crate::bot::commands::{BotCommand, CommandError};
|
||||||
use crate::bot::formatters::display_loc;
|
use crate::bot::formatters::display_loc;
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
|
||||||
pub struct FindCommand;
|
pub struct FindCommand;
|
||||||
|
|
||||||
|
@ -28,22 +28,17 @@ impl BotCommand for FindCommand {
|
||||||
Method::GET
|
Method::GET
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
|
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
command
|
||||||
command
|
.name(Self::command_name())
|
||||||
.name(Self::command_name())
|
.description("Find a location in Geoffrey.")
|
||||||
.description("Find a location in Geoffrey.")
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("query")
|
||||||
.name("query")
|
.description("The location name or player to lookup")
|
||||||
.description("The location name or player to lookup")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_arguments(
|
async fn process_arguments(
|
||||||
|
|
|
@ -4,10 +4,7 @@ use async_trait::async_trait;
|
||||||
use reqwest::Error;
|
use reqwest::Error;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serenity::client::Context;
|
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
|
||||||
use serenity::model::interactions::application_command::{
|
|
||||||
ApplicationCommand, ApplicationCommandInteraction,
|
|
||||||
};
|
|
||||||
use serenity::Error as SerenityError;
|
use serenity::Error as SerenityError;
|
||||||
|
|
||||||
use geoffrey_models::models::parameters::CommandRequest;
|
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 geoffrey_models::models::response::APIResponse;
|
||||||
|
|
||||||
use crate::context::GeoffreyContext;
|
use crate::context::GeoffreyContext;
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
pub mod add_item;
|
pub mod add_item;
|
||||||
pub mod add_location;
|
pub mod add_location;
|
||||||
|
@ -23,12 +23,21 @@ pub mod find;
|
||||||
pub mod selling;
|
pub mod selling;
|
||||||
pub mod set_portal;
|
pub mod set_portal;
|
||||||
|
|
||||||
|
pub type GeoffreyCommandFn = Box<
|
||||||
|
fn(
|
||||||
|
GeoffreyContext,
|
||||||
|
UserID,
|
||||||
|
ApplicationCommandInteraction,
|
||||||
|
) -> Pin<Box<dyn Future<Output = String> + Send>>,
|
||||||
|
>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CommandError {
|
pub enum CommandError {
|
||||||
ArgumentParse(String),
|
ArgumentParse(String),
|
||||||
GeoffreyApi(GeoffreyAPIError),
|
GeoffreyApi(GeoffreyAPIError),
|
||||||
Serenity(serenity::Error),
|
Serenity(serenity::Error),
|
||||||
Reqwest(reqwest::Error),
|
Reqwest(reqwest::Error),
|
||||||
|
CommandNotFound(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for CommandError {
|
impl Display for CommandError {
|
||||||
|
@ -38,6 +47,7 @@ impl Display for CommandError {
|
||||||
CommandError::GeoffreyApi(err) => format!("Got error from GeoffreyAPI: {}", err),
|
CommandError::GeoffreyApi(err) => format!("Got error from GeoffreyAPI: {}", err),
|
||||||
CommandError::Serenity(err) => format!("Serenity Error: {}", err),
|
CommandError::Serenity(err) => format!("Serenity Error: {}", err),
|
||||||
CommandError::Reqwest(err) => format!("Reqwest Error: {}", err),
|
CommandError::Reqwest(err) => format!("Reqwest Error: {}", err),
|
||||||
|
CommandError::CommandNotFound(err) => format!("'{}' not found!", err),
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
|
@ -63,7 +73,7 @@ impl From<reqwest::Error> for CommandError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BotCommand {
|
pub trait BotCommand: Send + 'static {
|
||||||
type ApiParams: CommandRequest;
|
type ApiParams: CommandRequest;
|
||||||
type ApiResp: Serialize + DeserializeOwned + Send;
|
type ApiResp: Serialize + DeserializeOwned + Send;
|
||||||
|
|
||||||
|
@ -78,7 +88,7 @@ pub trait BotCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_api_query(
|
async fn run_api_query(
|
||||||
ctx: &GeoffreyContext,
|
ctx: GeoffreyContext,
|
||||||
params: Self::ApiParams,
|
params: Self::ApiParams,
|
||||||
) -> Result<APIResponse<Self::ApiResp>, CommandError> {
|
) -> Result<APIResponse<Self::ApiResp>, CommandError> {
|
||||||
let command_url = Self::command_url(&ctx.cfg.api.base_url);
|
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()
|
"You need to register before using this command!".to_string()
|
||||||
}
|
}
|
||||||
CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => {
|
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) => {
|
CommandError::GeoffreyApi(GeoffreyAPIError::PermissionInsufficient) => {
|
||||||
"Looks like you don't have permission for that.".to_string()
|
"Looks like you don't have permission for that.".to_string()
|
||||||
|
@ -133,20 +143,24 @@ pub trait BotCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError>;
|
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand;
|
||||||
|
|
||||||
async fn process_arguments(
|
async fn process_arguments(
|
||||||
command_interaction: ApplicationCommandInteraction,
|
command_interaction: ApplicationCommandInteraction,
|
||||||
) -> Result<Self::ApiParams, CommandError>;
|
) -> Result<Self::ApiParams, CommandError>;
|
||||||
|
|
||||||
async fn run_command(
|
async fn run_command(
|
||||||
ctx: &GeoffreyContext,
|
ctx: GeoffreyContext,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
command_interact: ApplicationCommandInteraction,
|
command_interact: ApplicationCommandInteraction,
|
||||||
) -> Result<String, CommandError> {
|
) -> Result<String, CommandError> {
|
||||||
let mut args = Self::process_arguments(command_interact).await?;
|
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_token(ctx.cfg.api.token.clone());
|
||||||
args.set_user_id(user_id);
|
args.set_user_id(user_id);
|
||||||
|
@ -160,7 +174,7 @@ pub trait BotCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn command(
|
async fn command(
|
||||||
ctx: &GeoffreyContext,
|
ctx: GeoffreyContext,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
command_interact: ApplicationCommandInteraction,
|
command_interact: ApplicationCommandInteraction,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
|
|
@ -2,9 +2,8 @@ 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::model::interactions::application_command::{
|
use serenity::model::interactions::application_command::{
|
||||||
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
|
ApplicationCommandInteraction, ApplicationCommandOptionType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use geoffrey_models::models::parameters::selling_params::{ItemSort, Order, SellingParams};
|
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::arg_parse::{option_to_order, option_to_sort, option_to_string};
|
||||||
use crate::bot::commands::{BotCommand, CommandError};
|
use crate::bot::commands::{BotCommand, CommandError};
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
|
||||||
pub struct SellingCommand;
|
pub struct SellingCommand;
|
||||||
|
|
||||||
|
@ -28,40 +28,35 @@ impl BotCommand for SellingCommand {
|
||||||
Method::GET
|
Method::GET
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
|
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
command
|
||||||
command
|
.name(Self::command_name())
|
||||||
.name(Self::command_name())
|
.description("Find items for sale.")
|
||||||
.description("Find items for sale.")
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("query")
|
||||||
.name("query")
|
.description("Item to find")
|
||||||
.description("Item to find")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("sort")
|
||||||
.name("sort")
|
.description("How to sort items")
|
||||||
.description("How to sort items")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.add_string_choice(ItemSort::Price, ItemSort::Price)
|
||||||
.add_string_choice(ItemSort::Price, ItemSort::Price)
|
.add_string_choice(ItemSort::Restock, ItemSort::Restock)
|
||||||
.add_string_choice(ItemSort::Restock, ItemSort::Restock)
|
.required(false)
|
||||||
.required(false)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("order")
|
||||||
.name("order")
|
.description("Order of the item Search")
|
||||||
.description("Order of the item Search")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.add_string_choice(Order::Low, Order::Low)
|
||||||
.add_string_choice(Order::Low, Order::Low)
|
.add_string_choice(Order::High, Order::High)
|
||||||
.add_string_choice(Order::High, Order::High)
|
.required(false)
|
||||||
.required(false)
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_arguments(
|
async fn process_arguments(
|
||||||
|
|
|
@ -2,14 +2,14 @@ use async_trait::async_trait;
|
||||||
use geoffrey_models::models::locations::Location;
|
use geoffrey_models::models::locations::Location;
|
||||||
use geoffrey_models::models::Portal;
|
use geoffrey_models::models::Portal;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serenity::client::Context;
|
|
||||||
use serenity::model::interactions::application_command::{
|
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::arg_parse::{option_to_i64, option_to_string};
|
||||||
use crate::bot::commands::{BotCommand, CommandError};
|
use crate::bot::commands::{BotCommand, CommandError};
|
||||||
use geoffrey_models::models::parameters::set_portal_params::SetPortalParams;
|
use geoffrey_models::models::parameters::set_portal_params::SetPortalParams;
|
||||||
|
use serenity::builder::CreateApplicationCommand;
|
||||||
|
|
||||||
pub struct SetPortalCommand;
|
pub struct SetPortalCommand;
|
||||||
|
|
||||||
|
@ -26,40 +26,35 @@ impl BotCommand for SetPortalCommand {
|
||||||
Method::POST
|
Method::POST
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_app_command(ctx: &Context) -> Result<ApplicationCommand, CommandError> {
|
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
let command = ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
command
|
||||||
command
|
.name(Self::command_name())
|
||||||
.name(Self::command_name())
|
.description("Set a portal for a location")
|
||||||
.description("Set a portal for a location")
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("loc_name")
|
||||||
.name("loc_name")
|
.description("Name of the location")
|
||||||
.description("Name of the location")
|
.kind(ApplicationCommandOptionType::String)
|
||||||
.kind(ApplicationCommandOptionType::String)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("x_portal")
|
||||||
.name("x_portal")
|
.description("X coordinate of the portal in the nether")
|
||||||
.description("X coordinate of the portal in the nether")
|
.kind(ApplicationCommandOptionType::Integer)
|
||||||
.kind(ApplicationCommandOptionType::Integer)
|
.max_int_value(i32::MAX)
|
||||||
.max_int_value(i32::MAX)
|
.min_int_value(i32::MIN)
|
||||||
.min_int_value(i32::MIN)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
.create_option(|option| {
|
||||||
.create_option(|option| {
|
option
|
||||||
option
|
.name("z_portal")
|
||||||
.name("z_portal")
|
.description("Z coordinate of the portal in the nether")
|
||||||
.description("Z coordinate of the portal in the nether")
|
.kind(ApplicationCommandOptionType::Integer)
|
||||||
.kind(ApplicationCommandOptionType::Integer)
|
.max_int_value(i32::MAX)
|
||||||
.max_int_value(i32::MAX)
|
.min_int_value(i32::MIN)
|
||||||
.min_int_value(i32::MIN)
|
.required(true)
|
||||||
.required(true)
|
})
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_arguments(
|
async fn process_arguments(
|
||||||
|
|
|
@ -30,13 +30,19 @@ pub fn display_owners(owners: Vec<Player>, limit: usize) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_portal(portal: Portal) -> 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 {
|
pub fn display_loc(loc: Location) -> String {
|
||||||
let portal_str = match loc.portal {
|
let portal_str = match loc.portal {
|
||||||
None => "".to_string(),
|
None => "".to_string(),
|
||||||
Some(p) => format!("{}, ", display_portal(p))
|
Some(p) => format!("{}, ", display_portal(p)),
|
||||||
};
|
};
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -1,25 +1,86 @@
|
||||||
use serenity::model::interactions::application_command::ApplicationCommand;
|
use serenity::model::interactions::application_command::ApplicationCommand;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
|
|
||||||
|
use crate::bot::commands::GeoffreyCommandFn;
|
||||||
|
use crate::context::GeoffreyContext;
|
||||||
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::set_portal::SetPortalCommand;
|
use commands::set_portal::SetPortalCommand;
|
||||||
use commands::{BotCommand, CommandError};
|
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 arg_parse;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod formatters;
|
pub mod formatters;
|
||||||
|
|
||||||
pub async fn create_commands(ctx: &Context) -> Result<Vec<ApplicationCommand>, CommandError> {
|
#[derive(Default)]
|
||||||
let mut commands: Vec<ApplicationCommand> = Vec::new();
|
pub struct CommandRunner {
|
||||||
|
commands: HashMap<String, GeoffreyCommandFn>,
|
||||||
commands.push(FindCommand::create_app_command(ctx).await?);
|
}
|
||||||
commands.push(SellingCommand::create_app_command(ctx).await?);
|
|
||||||
commands.push(AddLocationCommand::create_app_command(ctx).await?);
|
impl CommandRunner {
|
||||||
commands.push(AddItemCommand::create_app_command(ctx).await?);
|
async fn register_app_command<T: BotCommand>(ctx: &Context) -> Result<(), CommandError> {
|
||||||
commands.push( SetPortalCommand::create_app_command(ctx).await?);
|
ApplicationCommand::create_global_application_command(&ctx.http, |command| {
|
||||||
|
T::create_app_command(command)
|
||||||
Ok(commands)
|
})
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,11 @@ mod configs;
|
||||||
mod context;
|
mod context;
|
||||||
mod logging;
|
mod logging;
|
||||||
|
|
||||||
use crate::bot::create_commands;
|
use crate::bot::{build_commands, CommandRunner};
|
||||||
use crate::configs::GeoffreyBotConfig;
|
use crate::configs::GeoffreyBotConfig;
|
||||||
use crate::context::GeoffreyContext;
|
use crate::context::GeoffreyContext;
|
||||||
use bot::commands::add_item::AddItemCommand;
|
use crate::logging::init_logging;
|
||||||
use bot::commands::add_location::AddLocationCommand;
|
use geoffrey_models::logging::LogLevel;
|
||||||
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::{
|
||||||
|
@ -23,9 +20,6 @@ use serenity::{
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use geoffrey_models::logging::LogLevel;
|
|
||||||
use crate::logging::init_logging;
|
|
||||||
use crate::bot::commands::set_portal::SetPortalCommand;
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt, Clone)]
|
#[derive(Debug, StructOpt, Clone)]
|
||||||
#[structopt(name = "GeoffreyBot", about = "Geoffrey Discord Bot")]
|
#[structopt(name = "GeoffreyBot", about = "Geoffrey Discord Bot")]
|
||||||
|
@ -33,11 +27,11 @@ struct Args {
|
||||||
#[structopt(env = "GEOFFREY_BOT_CONFIG", parse(from_os_str))]
|
#[structopt(env = "GEOFFREY_BOT_CONFIG", parse(from_os_str))]
|
||||||
config: PathBuf,
|
config: PathBuf,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
short,
|
short,
|
||||||
long,
|
long,
|
||||||
env = "GEOFFREY_LOG_LEVEL",
|
env = "GEOFFREY_LOG_LEVEL",
|
||||||
parse(from_str),
|
parse(from_str),
|
||||||
default_value = "Info"
|
default_value = "Info"
|
||||||
)]
|
)]
|
||||||
log_level: LogLevel,
|
log_level: LogLevel,
|
||||||
}
|
}
|
||||||
|
@ -54,36 +48,47 @@ struct Handler;
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
log::info!("{} is connected!", ready.user.name);
|
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 {
|
let command_runner = data
|
||||||
log::debug!(
|
.get_mut::<CommandRunner>()
|
||||||
"{}: {} - {:?}",
|
.expect("Unable to open command runner!");
|
||||||
command.name, command.description, command.options
|
|
||||||
);
|
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) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let geoffrey_ctx = data.get::<GeoffreyContext>().unwrap();
|
let geoffrey_ctx = data.get::<GeoffreyContext>().unwrap();
|
||||||
|
let command_runner = data.get::<CommandRunner>().unwrap();
|
||||||
|
|
||||||
if let Interaction::ApplicationCommand(command) = interaction {
|
if let Interaction::ApplicationCommand(command) = interaction {
|
||||||
let user_id = UserID::DiscordUUID {
|
let user_id = UserID::DiscordUUID {
|
||||||
discord_uuid: command.user.id.0,
|
discord_uuid: command.user.id.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg = match command.data.name.as_str() {
|
let command_name = command.data.name.clone();
|
||||||
"find" => FindCommand::command(geoffrey_ctx, user_id, command.clone()).await,
|
|
||||||
"selling" => SellingCommand::command(geoffrey_ctx, user_id, command.clone()).await,
|
let msg = match command_runner
|
||||||
"add_location" => {
|
.run_command(
|
||||||
AddLocationCommand::command(geoffrey_ctx, user_id, command.clone()).await
|
&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;
|
let msg = content_safe(&ctx.cache, &msg, &ContentSafeOptions::default()).await;
|
||||||
|
@ -143,7 +148,9 @@ async fn main() {
|
||||||
data.insert::<GeoffreyContext>(GeoffreyContext {
|
data.insert::<GeoffreyContext>(GeoffreyContext {
|
||||||
http_client: reqwest::Client::new(),
|
http_client: reqwest::Client::new(),
|
||||||
cfg,
|
cfg,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
data.insert::<CommandRunner>(CommandRunner::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = client.start().await {
|
if let Err(e) = client.start().await {
|
||||||
|
|
|
@ -3,8 +3,8 @@ use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned + Debug {
|
pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned + Debug {
|
||||||
fn id(&self) -> Option<u64>;
|
fn id(&self) -> Option<u64>;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
|
|
|
@ -122,7 +122,7 @@ impl Portal {
|
||||||
pub fn tunnel_addr(&self) -> i32 {
|
pub fn tunnel_addr(&self) -> i32 {
|
||||||
match self.direction() {
|
match self.direction() {
|
||||||
Direction::North | Direction::South => self.z.abs(),
|
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