diff --git a/Cargo.lock b/Cargo.lock index 6ad69ab..0e27366 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,7 @@ name = "geoffrey_bot" version = "0.1.0" dependencies = [ "async-trait", + "chrono", "config", "geoffrey_models", "log", diff --git a/geoffrey_api/src/commands/mod.rs b/geoffrey_api/src/commands/mod.rs index 0e7a9f8..4c73b9d 100644 --- a/geoffrey_api/src/commands/mod.rs +++ b/geoffrey_api/src/commands/mod.rs @@ -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::(ctx.clone())) .or(create_command_filter::(ctx.clone())) .or(create_command_filter::(ctx.clone())) + .or(create_command_filter::(ctx.clone())) .or(create_command_filter::(ctx)), ) } diff --git a/geoffrey_api/src/commands/restock.rs b/geoffrey_api/src/commands/restock.rs new file mode 100644 index 0000000..5c13271 --- /dev/null +++ b/geoffrey_api/src/commands/restock.rs @@ -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, req: &Self::Req, user: Option) -> Result { + 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 = 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) + } + } +} diff --git a/geoffrey_bot/Cargo.toml b/geoffrey_bot/Cargo.toml index db202b6..38b8fa6 100644 --- a/geoffrey_bot/Cargo.toml +++ b/geoffrey_bot/Cargo.toml @@ -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] diff --git a/geoffrey_bot/src/bot/commands/mod.rs b/geoffrey_bot/src/bot/commands/mod.rs index 74ff59b..e402bb9 100644 --- a/geoffrey_bot/src/bot/commands/mod.rs +++ b/geoffrey_bot/src/bot/commands/mod.rs @@ -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; diff --git a/geoffrey_bot/src/bot/commands/remove_item.rs b/geoffrey_bot/src/bot/commands/remove_item.rs index 13eac06..0b4cd0b 100644 --- a/geoffrey_bot/src/bot/commands/remove_item.rs +++ b/geoffrey_bot/src/bot/commands/remove_item.rs @@ -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) }) diff --git a/geoffrey_bot/src/bot/commands/restock.rs b/geoffrey_bot/src/bot/commands/restock.rs new file mode 100644 index 0000000..2e84ebf --- /dev/null +++ b/geoffrey_bot/src/bot/commands/restock.rs @@ -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 { + 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 { + 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) + ) + } +} diff --git a/geoffrey_bot/src/bot/formatters.rs b/geoffrey_bot/src/bot/formatters.rs index 571a035..21e693c 100644 --- a/geoffrey_bot/src/bot/formatters.rs +++ b/geoffrey_bot/src/bot/formatters.rs @@ -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::>() .join("\n") ) diff --git a/geoffrey_bot/src/bot/mod.rs b/geoffrey_bot/src/bot/mod.rs index bd6100f..2b3c0ff 100644 --- a/geoffrey_bot/src/bot/mod.rs +++ b/geoffrey_bot/src/bot/mod.rs @@ -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::(ctx) .await? .add_command::(ctx) + .await? + .add_command::(ctx) .await?; Ok(()) diff --git a/geoffrey_models/src/models/parameters/mod.rs b/geoffrey_models/src/models/parameters/mod.rs index 5c3a86f..e6a7056 100644 --- a/geoffrey_models/src/models/parameters/mod.rs +++ b/geoffrey_models/src/models/parameters/mod.rs @@ -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; diff --git a/geoffrey_models/src/models/parameters/restock_params.rs b/geoffrey_models/src/models/parameters/restock_params.rs new file mode 100644 index 0000000..2c695c5 --- /dev/null +++ b/geoffrey_models/src/models/parameters/restock_params.rs @@ -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 {}