use chrono::{Duration, Utc}; use geoffrey_models::models::item::ItemListing; 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; use serenity::client::Cache; use serenity::utils::{content_safe, ContentSafeOptions, MessageBuilder}; pub struct GeoffreyFormatter { msg: String, settings: GeoffreySettings, } impl GeoffreyFormatter { pub fn new(settings: GeoffreySettings) -> Self { Self { msg: String::new(), settings, } } pub fn build(&mut self) -> String { self.msg.clone() } pub fn push(&mut self, string: &str) -> &mut Self { self.msg.push_str(string); self } pub fn push_new_line(&mut self) -> &mut Self { self.push("\n") } pub fn push_owners(&mut self, owners: &[Player]) -> &mut Self { let mut plural = ""; let mut ellipses = ""; if owners.len() > 1 { plural = "s" } let range = if owners.len() > self.settings.max_owners_to_display as usize { ellipses = "..."; self.settings.max_owners_to_display as usize } else { owners.len() }; self.msg = MessageBuilder::new() .push(self.msg.clone()) .push("Owner") .push(plural) .push(": ") .push( owners[0..range] .iter() .map(|owner| owner.name.clone()) .collect::>() .join(", "), ) .push(ellipses) .build(); self } pub fn push_portal(&mut self, portal: &Portal) -> &mut Self { self.push(&format!( "Portal: {} {} (x={}, z={})", portal.direction(), portal.tunnel_addr(), portal.x, portal.z )) } pub fn push_loc(&mut self, loc: &Location) -> &mut Self { self.push( &MessageBuilder::new() .push_bold_safe(&loc.name) .push(", ") .push(&loc.position) .push(", ") .build(), ); match &loc.portal { None => {} Some(p) => { self.push_portal(p).push(", "); } }; 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 { 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()) }; let msg = MessageBuilder::new() .push_bold_safe(&listing.item.name) .push(", ") .push(&listing.count_per_price) .push(" for ") .push(listing.price) .push("D. Restocked ") .push(time_str) .build(); let msg = if listing.is_out_of_stock(self.settings.min_out_of_stock_votes) { MessageBuilder::new().push_strike_safe(msg).to_string() } else { msg }; self.push(&msg) } pub fn push_loc_full(&mut self, loc: &Location) -> &mut Self { self.push_loc(loc); match &loc.loc_data { LocationData::Shop(shop) => { if !shop.item_listings.is_empty() { self.push_new_line() .push(&MessageBuilder::new().push_bold("Inventory:").to_string()) .push_new_line(); for listing in &shop.item_listings { self.push_item_listing(listing).push_new_line(); } self } else { self } } _ => self, } } } pub async fn clean_message(cache: impl AsRef, msg: &str) -> String { content_safe( cache, msg, &ContentSafeOptions::new() .clean_channel(true) .clean_user(true) .clean_everyone(true) .clean_here(true) .clean_role(true), ) .await }