diff --git a/Cargo.lock b/Cargo.lock index 44aef43..e2efbb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -431,6 +431,7 @@ dependencies = [ "serde 1.0.133", "serde_json", "simple_logger", + "strsim 0.10.0", "structopt", "tokio", "warp", @@ -1579,6 +1580,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.25" diff --git a/geoffrey_api/Cargo.toml b/geoffrey_api/Cargo.toml index 489fd03..1e4f2a3 100644 --- a/geoffrey_api/Cargo.toml +++ b/geoffrey_api/Cargo.toml @@ -19,6 +19,7 @@ log = "0.4.14" rand = "0.8.4" regex = "1.5.4" chrono = { version = "0.4.19", features = ["serde"] } +strsim = "0.10.0" # Doing this for now, as there seems to be an issue with using timestamps [dependencies.simple_logger] diff --git a/geoffrey_api/src/commands/info.rs b/geoffrey_api/src/commands/info.rs index 7144a73..dc67310 100644 --- a/geoffrey_api/src/commands/info.rs +++ b/geoffrey_api/src/commands/info.rs @@ -9,6 +9,7 @@ use geoffrey_models::models::player::Player; use geoffrey_models::models::response::api_error::GeoffreyAPIError; use geoffrey_models::models::CommandLevel; use std::sync::Arc; +use strsim::normalized_damerau_levenshtein; pub struct InfoCommand {} @@ -29,14 +30,24 @@ impl Command for InfoCommand { } fn run_command(ctx: Arc, req: &Self::Req, _: Option) -> Result { - let query = QueryBuilder::::default().with_name_regex(&req.location_name)?; + let query = QueryBuilder::::default() + .with_name_regex_case_insensitive(&req.location_name)?; - let location = ctx - .db - .run_query(query)? - .pop() - .ok_or(GeoffreyAPIError::EntryNotFound)?; + let mut locations = ctx.db.run_query(query)?; - Ok(load_location(&ctx.db, &location)?) + if locations.is_empty() { + Err(GeoffreyAPIError::EntryNotFound) + } else { + locations.sort_by(|a, b| { + let a = + (normalized_damerau_levenshtein(&a.name, &req.location_name) * 1000.0) as u32; + let b = + (normalized_damerau_levenshtein(&b.name, &req.location_name) * 1000.0) as u32; + + a.cmp(&b) + }); + + Ok(load_location(&ctx.db, &locations.pop().unwrap())?) + } } } diff --git a/geoffrey_api/src/commands/report_out_of_stock.rs b/geoffrey_api/src/commands/report_out_of_stock.rs index 96a7324..8b6979d 100644 --- a/geoffrey_api/src/commands/report_out_of_stock.rs +++ b/geoffrey_api/src/commands/report_out_of_stock.rs @@ -44,7 +44,9 @@ impl Command for ReportOutOfStock { .ok_or(GeoffreyAPIError::EntryNotFound)?; if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data { - let filter = regex::Regex::new(&req.item_name) + let filter = regex::RegexBuilder::new(&format!("^{}$", &req.item_name)) + .case_insensitive(true) + .build() .map_err(|e| GeoffreyAPIError::InvalidRegex(e.to_string()))?; let updated_items: HashSet = shop_data diff --git a/geoffrey_api/src/commands/restock.rs b/geoffrey_api/src/commands/restock.rs index 1581e80..b660cad 100644 --- a/geoffrey_api/src/commands/restock.rs +++ b/geoffrey_api/src/commands/restock.rs @@ -45,7 +45,9 @@ impl Command for Restock { .ok_or(GeoffreyAPIError::EntryNotFound)?; if let LocationDataDb::Shop(shop_data) = &mut shop.loc_data { - let filter = regex::Regex::new(&req.item_name) + let filter = regex::RegexBuilder::new(&req.item_name) + .case_insensitive(true) + .build() .map_err(|e| GeoffreyAPIError::InvalidRegex(e.to_string()))?; let updated_items: HashSet = shop_data diff --git a/geoffrey_bot/src/bot/commands/find.rs b/geoffrey_bot/src/bot/commands/find.rs index 1957fdc..588e1bf 100644 --- a/geoffrey_bot/src/bot/commands/find.rs +++ b/geoffrey_bot/src/bot/commands/find.rs @@ -64,10 +64,11 @@ impl BotCommand for FindCommand { formatter .push("The following locations match '") .push(¶ms.query) - .push("'"); + .push("':") + .push_new_line(); for loc in &resp { - formatter.push_loc(loc).push_new_line().push_new_line(); + formatter.push_loc(loc).push_new_line(); } formatter.build() diff --git a/geoffrey_bot/src/bot/commands/set_portal.rs b/geoffrey_bot/src/bot/commands/set_portal.rs index 22eafe1..3acdd6e 100644 --- a/geoffrey_bot/src/bot/commands/set_portal.rs +++ b/geoffrey_bot/src/bot/commands/set_portal.rs @@ -100,7 +100,6 @@ impl BotCommand for SetPortalCommand { portal.x, portal.z )) - .push_bold_safe(resp.name) .push_line("") .build() } diff --git a/geoffrey_bot/src/bot/formatters.rs b/geoffrey_bot/src/bot/formatters.rs index 332dc23..80b3b86 100644 --- a/geoffrey_bot/src/bot/formatters.rs +++ b/geoffrey_bot/src/bot/formatters.rs @@ -1,6 +1,6 @@ use chrono::{Duration, Utc}; use geoffrey_models::models::item::ItemListing; -use geoffrey_models::models::locations::{Location, LocationData}; +use geoffrey_models::models::locations::{Location, LocationData, LocationType}; use geoffrey_models::models::player::Player; use geoffrey_models::models::settings::GeoffreySettings; use geoffrey_models::models::Portal; @@ -94,7 +94,11 @@ impl GeoffreyFormatter { } }; - self.push_owners(&loc.owners) + let loc_type: LocationType = LocationType::from(&loc.loc_data); + + self.push_owners(&loc.owners).push(", "); + + self.push("Type: ").push(&loc_type.to_string()) } pub fn push_item_listing(&mut self, listing: &ItemListing) -> &mut Self { diff --git a/geoffrey_db/src/helper.rs b/geoffrey_db/src/helper.rs index 195da0d..58c8f3d 100644 --- a/geoffrey_db/src/helper.rs +++ b/geoffrey_db/src/helper.rs @@ -78,7 +78,7 @@ pub fn find_location_by_name_type( let location: LocationDb = db .filter(|_, loc: &LocationDb| { - let lt: LocationType = loc.loc_data.clone().into(); + let lt: LocationType = LocationType::from(&loc.loc_data); loc.owners().contains(&owner_id) && filter.is_match(loc.name.as_str()) && lt == loc_type })? .next() diff --git a/geoffrey_db/src/query/location_query.rs b/geoffrey_db/src/query/location_query.rs index 1a93d42..fa1ad96 100644 --- a/geoffrey_db/src/query/location_query.rs +++ b/geoffrey_db/src/query/location_query.rs @@ -32,7 +32,7 @@ impl QueryBuilder { pub fn with_type(self, loc_type: LocationType) -> Self { self.add_query_clause(Box::new(move |_: u64, loc: &LocationDb| { - let next_loc_type: LocationType = loc.loc_data.clone().into(); + let next_loc_type: LocationType = LocationType::from(&loc.loc_data); loc_type == next_loc_type })) diff --git a/geoffrey_models/src/models/locations/mod.rs b/geoffrey_models/src/models/locations/mod.rs index 4ff4ef4..3b4e72a 100644 --- a/geoffrey_models/src/models/locations/mod.rs +++ b/geoffrey_models/src/models/locations/mod.rs @@ -26,8 +26,8 @@ pub enum LocationType { Market, } -impl From for LocationType { - fn from(l: LocationDataDb) -> Self { +impl From<&LocationDataDb> for LocationType { + fn from(l: &LocationDataDb) -> Self { match l { LocationDataDb::Base => LocationType::Base, LocationDataDb::Shop(_) => LocationType::Shop, @@ -39,6 +39,19 @@ impl From for LocationType { } } +impl From<&LocationData> for LocationType { + fn from(l: &LocationData) -> Self { + match l { + LocationData::Base => LocationType::Base, + LocationData::Shop(_) => LocationType::Shop, + LocationData::Attraction => LocationType::Attraction, + LocationData::Town(_) => LocationType::Town, + LocationData::Farm(_) => LocationType::Farm, + LocationData::Market(_) => LocationType::Market, + } + } +} + impl Display for LocationType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = match self {