diff --git a/munite-core/src/audio/music/pro_studio_masters.rs b/munite-core/src/audio/music/pro_studio_masters.rs index a42b2c5..69165b9 100644 --- a/munite-core/src/audio/music/pro_studio_masters.rs +++ b/munite-core/src/audio/music/pro_studio_masters.rs @@ -70,6 +70,7 @@ impl MusicSearchStore for ProStudioMasters { }, }).into(), price: StorePrice { cents: 0, currency: StoreCurrency::Unknown }, + stock: crate::StoreStock::Available, }); } } diff --git a/munite-core/src/audio/music/seven_digital.rs b/munite-core/src/audio/music/seven_digital.rs index 48b4784..6d7c76e 100644 --- a/munite-core/src/audio/music/seven_digital.rs +++ b/munite-core/src/audio/music/seven_digital.rs @@ -161,7 +161,8 @@ impl std::convert::Into for ArtistSearchResult { image: Some(self.image) }).into(), store: "7digital".to_owned(), - price: StorePrice { cents: 0, currency: StoreCurrency::Unknown } + price: StorePrice { cents: 0, currency: StoreCurrency::Unknown }, + stock: crate::StoreStock::Available, } } } @@ -200,7 +201,8 @@ impl std::convert::Into for ReleaseSearchResult { } }).into(), store: "7digital".to_owned(), - price: StorePrice { cents: 0, currency: StoreCurrency::Unknown } + price: StorePrice { cents: 0, currency: StoreCurrency::Unknown }, + stock: crate::StoreStock::Available, } } } @@ -255,7 +257,8 @@ impl std::convert::Into for TrackSearchResult { }), }).into(), store: "7digital".to_owned(), - price: StorePrice { cents: 0, currency: StoreCurrency::Unknown } + price: StorePrice { cents: 0, currency: StoreCurrency::Unknown }, + stock: crate::StoreStock::Available, } } } diff --git a/munite-core/src/entry.rs b/munite-core/src/entry.rs index cdbf74f..5106839 100644 --- a/munite-core/src/entry.rs +++ b/munite-core/src/entry.rs @@ -31,6 +31,8 @@ pub struct StoreEntry { pub extra: StoreExtra, /// Price information pub price: StorePrice, + /// Stock information + pub stock: StoreStock, } /// A store identifier @@ -61,6 +63,50 @@ impl std::fmt::Display for StoreId { } } +/// Stock of a search result +pub enum StoreStock { + /// Out of stock + SoldOut, + /// In stock, but amount unknown or not applicable (for digital items) + Available, + /// In stock with available + Count(u64), + /// Stock is unknown + Unknown, +} + +impl StoreStock { + /// Store definitely has at least 1 of the item + pub fn has_stock(&self) -> bool { + match self { + Self::Available => true, + Self::Count(n) => *n > 0, + _ => false, + } + } + + /// Store is definitely out of stock of the item + pub fn is_oos(&self) -> bool { + matches!(self, Self::SoldOut) + } + + /// Store does not know the stock of the item + pub fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown) + } +} + +impl std::fmt::Display for StoreStock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SoldOut => write!(f, "OOS"), + Self::Available => write!(f, "InStock"), + Self::Count(n) => write!(f, "InStock:{}", n), + Self::Unknown => write!(f, "???"), + } + } +} + /// Store price information pub struct StorePrice { /// Price of store result, in 100ths of a dollar diff --git a/munite-core/src/general/best_buy_ca.rs b/munite-core/src/general/best_buy_ca.rs index 4f03c36..07b0b73 100644 --- a/munite-core/src/general/best_buy_ca.rs +++ b/munite-core/src/general/best_buy_ca.rs @@ -195,6 +195,8 @@ impl std::convert::Into for ProductInfo { cents: (self.salePrice * 100.0) as _, currency: StoreCurrency::Cad, }, + stock: crate::StoreStock::Unknown, // TODO do query to API endpoint that provides this info + // e.g. https://www.bestbuy.ca/ecomm-api/availability/products?accept=application/vnd.bestbuy.simpleproduct.v1+json&accept-language=en-CA&locations=940|928|972|639|627|224|975&postalCode=K1R&skus=M2234719 } } } diff --git a/munite-core/src/general/sunrise_records.rs b/munite-core/src/general/sunrise_records.rs index 683f4dd..de11d01 100644 --- a/munite-core/src/general/sunrise_records.rs +++ b/munite-core/src/general/sunrise_records.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use super::GeneralSearchStore; use crate::audio::{AudioExtra, music::{MusicExtra, MusicMediumType}}; use crate::video::{VideoExtra, VideoFormat}; -use crate::{MuniteError, StoreEntry, StorePrice, StoreCurrency, StoreType, StoreId, StoreExtra}; +use crate::{MuniteError, StoreEntry, StorePrice, StoreCurrency, StoreType, StoreId, StoreExtra, StoreStock}; /// BestBuy Canada store client pub struct SunriseRecords { @@ -24,11 +24,12 @@ impl SunriseRecords { .query(&[ ("page", &page.to_string() as &str), ("limit", &page_size.to_string() as &str), - (&format!("controls[{}]", page), query), + ("controls[1]", query), ("sortBy", sort_by), ]) //.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:108.0) Gecko/20100101 Firefox/108.0") .header("Accept", "application/json, text/javascript, */*; q=0.01") + .header("DNT", "1") } fn search_store(&self, query: &str, page: u64, page_size: u64, sort_by: &str) -> RequestBuilder { @@ -54,9 +55,9 @@ const MAX_PAGE_SIZE: u64 = 48; // Anything larger is clamped back down to this v impl GeneralSearchStore for SunriseRecords { async fn search(&self, s: String, category: StoreType) -> crate::MuniteResult> { let result = match category { - StoreType::General => self.search_store(&s, 1, MAX_PAGE_SIZE, "stock_desc").send().await, - StoreType::Video(_) => self.search_store(&s, 1, MAX_PAGE_SIZE, "stock_desc").send().await, - StoreType::Audio(_) => self.search_store(&s, 1, MAX_PAGE_SIZE, "stock_desc").send().await, + StoreType::General => self.search_store(&s, 1, MAX_PAGE_SIZE, "relevance_desc").send().await, + StoreType::Video(_) => self.search_store(&s, 1, MAX_PAGE_SIZE, "relevance_desc").send().await, + StoreType::Audio(_) => self.search_store(&s, 1, MAX_PAGE_SIZE, "relevance_desc").send().await, }.map_err(|e| MuniteError::Http(e))?; let json_data: SearchResultsResponse = result.json().await.map_err(|e| MuniteError::Http(e))?; let mut output = Vec::with_capacity(json_data.items.len()); @@ -130,7 +131,7 @@ struct ProductInfo { #[derive(Deserialize)] struct StockInfo { isBackordered: bool, - available: u64, + available: i64, } #[allow(non_snake_case, dead_code)] @@ -186,6 +187,7 @@ impl std::convert::Into for ProductInfo { cents: (dollar_price * 100.0) as _, currency: StoreCurrency::Cad, }, + stock: if self.stock.available <= 0 || self.stock.isBackordered { StoreStock::SoldOut } else { StoreStock::Count(self.stock.available as _) } } } } diff --git a/munite-core/src/lib.rs b/munite-core/src/lib.rs index 557d85f..3eac66d 100644 --- a/munite-core/src/lib.rs +++ b/munite-core/src/lib.rs @@ -10,7 +10,7 @@ mod entry; mod errors; mod search; -pub use entry::{StoreEntry, StoreExtra, StoreType, StorePrice, StoreCurrency, StoreId}; +pub use entry::{StoreEntry, StoreExtra, StoreType, StorePrice, StoreCurrency, StoreId, StoreStock}; pub use errors::{MuniteError, MuniteResult}; pub use search::StoreSearch; diff --git a/munite-core/src/video/cinema1.rs b/munite-core/src/video/cinema1.rs index 30804b5..f59c7e1 100644 --- a/munite-core/src/video/cinema1.rs +++ b/munite-core/src/video/cinema1.rs @@ -49,6 +49,7 @@ impl CinemaOne { if let Some(format) = formats.tag("li").class("active").find() { let format_info = match format.text().trim() as &str { "4K" => VideoFormat::BlurayUhd, + "UHD" => VideoFormat::BlurayUhd, // idk why there's a distinction "3D BLU-RAY" => VideoFormat::Bluray3d, "BLU-RAY" => VideoFormat::Bluray, "DVD" => VideoFormat::Dvd, @@ -74,6 +75,7 @@ impl CinemaOne { info: None, // TODO }.into(), price: price_info, + stock: crate::StoreStock::Available, }); } }