Added restock command
continuous-integration/woodpecker the build was successful Details

+ Discord command has not been tested
+ Clippy + Fmt
main
Joey Hines 2021-12-26 21:36:32 -06:00
parent ec8926d693
commit 928e59a700
Signed by: joeyahines
GPG Key ID: 0C681E6AED894AE4
11 changed files with 196 additions and 7 deletions

1
Cargo.lock generated
View File

@ -441,6 +441,7 @@ name = "geoffrey_bot"
version = "0.1.0"
dependencies = [
"async-trait",
"chrono",
"config",
"geoffrey_models",
"log",

View File

@ -7,6 +7,7 @@ use crate::commands::info::InfoCommand;
use crate::commands::link::LinkCommand;
use crate::commands::register::Register;
use crate::commands::remove_item::RemoveItem;
use crate::commands::restock::Restock;
use crate::commands::selling::Selling;
use crate::commands::set_portal::SetPortal;
use crate::config::GeoffreyAPIConfig;
@ -36,6 +37,7 @@ pub mod info;
pub mod link;
pub mod register;
pub mod remove_item;
pub mod restock;
pub mod selling;
pub mod set_portal;
@ -144,6 +146,7 @@ pub fn command_filter(
.or(create_command_filter::<Edit>(ctx.clone()))
.or(create_command_filter::<RemoveItem>(ctx.clone()))
.or(create_command_filter::<InfoCommand>(ctx.clone()))
.or(create_command_filter::<Restock>(ctx.clone()))
.or(create_command_filter::<SetPortal>(ctx)),
)
}

View File

@ -0,0 +1,67 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use geoffrey_db::helper::{find_location_by_name_type, load_location};
use geoffrey_models::models::item::ItemListing;
use geoffrey_models::models::locations::{Location, LocationDataDb, LocationType};
use geoffrey_models::models::parameters::restock_params::RestockParameters;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::collections::HashSet;
use std::sync::Arc;
pub struct Restock {}
impl Command for Restock {
type Req = RestockParameters;
type Resp = Location;
fn command_name() -> String {
"restock".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED
}
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap();
let mut shop = find_location_by_name_type(
&ctx.db,
&req.shop_name,
user.id.unwrap(),
LocationType::Shop,
)?;
if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data {
let filter = regex::Regex::new(&req.item_name)
.map_err(|_| GeoffreyAPIError::ParameterInvalid("item_name".to_string()))?;
let updated_items: HashSet<ItemListing> = shop_data
.item_listings
.iter()
.map(|item| {
let mut item = item.clone();
if filter.is_match(&item.item.name) {
item.restock();
}
item
})
.collect();
shop_data.item_listings = updated_items;
let shop = ctx.db.insert(shop)?;
load_location(&ctx.db, &shop).map_err(|err| err.into())
} else {
Err(GeoffreyAPIError::EntryNotFound)
}
}
}

View File

@ -17,6 +17,7 @@ async-trait = "0.1.51"
config = "0.11.0"
structopt = "0.3.21"
log = "0.4.14"
chrono = "0.4.19"
# Doing this for now, as there seems to be an issue with using timestamps
[dependencies.simple_logger]

View File

@ -26,6 +26,7 @@ pub mod find;
pub mod info;
pub mod register;
pub mod remove_item;
pub mod restock;
pub mod selling;
pub mod set_portal;

View File

@ -45,7 +45,7 @@ impl BotCommand for RemoveItemCommand {
.create_option(|option| {
option
.name("shop_name")
.description("Shop to list the item at")
.description("Shop remove an item")
.kind(ApplicationCommandOptionType::String)
.required(true)
})

View File

@ -0,0 +1,79 @@
use async_trait::async_trait;
use reqwest::Method;
use serenity::model::interactions::application_command::{
ApplicationCommandInteraction, ApplicationCommandOptionType,
};
use geoffrey_models::models::locations::Location;
use crate::bot::arg_parse::option_to_string;
use crate::bot::commands::{BotCommand, CommandError};
use crate::bot::formatters::display_loc_full;
use crate::bot::lang::PLAYER_DOES_NOT_HAVE_MATCHING_SHOP;
use geoffrey_models::models::parameters::restock_params::RestockParameters;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use serenity::builder::CreateApplicationCommand;
pub struct RestockCommand;
#[async_trait]
impl BotCommand for RestockCommand {
type ApiParams = RestockParameters;
type ApiResp = Location;
fn command_name() -> String {
"restock".to_string()
}
fn request_type() -> Method {
Method::POST
}
fn custom_err_resp(e: &CommandError) -> Option<String> {
match e {
CommandError::GeoffreyApi(GeoffreyAPIError::EntryNotFound) => {
Some(PLAYER_DOES_NOT_HAVE_MATCHING_SHOP.to_string())
}
_ => None,
}
}
fn create_app_command(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(Self::command_name())
.description("Restock an item from a shop")
.create_option(|option| {
option
.name("shop_name")
.description("Shop to restock the item at")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("item_name")
.description("Name of the item to restock")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
}
async fn process_arguments(
command_interaction: ApplicationCommandInteraction,
) -> Result<Self::ApiParams, CommandError> {
let options = command_interaction.data.options;
Ok(Self::ApiParams::new(
option_to_string(options.get(0), "shop_name")?,
option_to_string(options.get(1), "item_name")?,
))
}
fn build_response(resp: Self::ApiResp) -> String {
format!(
"**{}** has been updated:\n{}",
resp.name,
display_loc_full(&resp)
)
}
}

View File

@ -1,3 +1,5 @@
use chrono::{Duration, Utc};
use geoffrey_models::models::item::ItemListing;
use geoffrey_models::models::locations::{Location, LocationData};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::Portal;
@ -54,6 +56,23 @@ pub fn display_loc(loc: &Location) -> String {
)
}
pub fn display_item_listing(listing: &ItemListing) -> String {
let stocked_diff = Utc::now() - listing.restocked_time;
let time_str = if stocked_diff < Duration::days(1) {
"today".to_string()
} else if stocked_diff < Duration::days(2) {
"1 day ago".to_string()
} else {
format!("{} days ago", stocked_diff.num_days())
};
format!(
"**{}**, {} for {}D. Restocked {}.",
listing.item.name, listing.count_per_price, listing.price, time_str
)
}
pub fn display_loc_full(loc: &Location) -> String {
let info = match &loc.loc_data {
LocationData::Shop(shop) => {
@ -62,12 +81,7 @@ pub fn display_loc_full(loc: &Location) -> String {
"\n**Inventory**:\n{}",
shop.item_listings
.iter()
.map(|item| {
format!(
"**{}**, {} for {}D",
item.item.name, item.count_per_price, item.price
)
})
.map(display_item_listing)
.collect::<Vec<String>>()
.join("\n")
)

View File

@ -7,6 +7,7 @@ use crate::bot::commands::edit_pos::EditPosCommand;
use crate::bot::commands::info::InfoCommand;
use crate::bot::commands::register::RegisterCommand;
use crate::bot::commands::remove_item::RemoveItemCommand;
use crate::bot::commands::restock::RestockCommand;
use crate::bot::commands::GeoffreyCommandFn;
use crate::context::GeoffreyContext;
use commands::add_item::AddItemCommand;
@ -101,6 +102,8 @@ pub async fn build_commands(
.add_command::<RemoveItemCommand>(ctx)
.await?
.add_command::<InfoCommand>(ctx)
.await?
.add_command::<RestockCommand>(ctx)
.await?;
Ok(())

View File

@ -8,6 +8,7 @@ pub mod info_params;
pub mod link_params;
pub mod register_params;
pub mod remove_item_params;
pub mod restock_params;
pub mod selling_params;
pub mod set_portal_params;

View File

@ -0,0 +1,19 @@
use crate::models::parameters::GeoffreyParam;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RestockParameters {
pub shop_name: String,
pub item_name: String,
}
impl RestockParameters {
pub fn new(shop_name: String, item_name: String) -> Self {
Self {
shop_name,
item_name,
}
}
}
impl GeoffreyParam for RestockParameters {}