use crate::{Client, RoboatError};
use request_types::AvatarSearchQueryResponse;
use catalog_types::QueryLimit;
pub use catalog_types::{
AssetType, AvatarSearchQuery, AvatarSearchQueryBuilder, BundleType, CatalogQueryLimit,
Category, CreatorType, Genre, Item, ItemDetails, ItemRestriction, ItemStatus, ItemType,
PriceStatus, QueryGenre, SalesTypeFilter, SortAggregation, SortType, Subcategory,
};
pub mod catalog_types;
mod request_types;
const ITEM_DETAILS_API: &str = "https://catalog.roblox.com/v1/catalog/items/details";
const QUERY_LIMIT: QueryLimit = QueryLimit::Thirty;
impl Client {
pub async fn item_details(&self, items: Vec<Item>) -> Result<Vec<ItemDetails>, RoboatError> {
match self.item_details_internal(items.clone()).await {
Ok(x) => Ok(x),
Err(e) => match e {
RoboatError::InvalidXcsrf(new_xcsrf) => {
self.set_xcsrf(new_xcsrf).await;
self.item_details_internal(items).await
}
_ => Err(e),
},
}
}
pub async fn product_id(&self, item_id: u64) -> Result<u64, RoboatError> {
let item = Item {
item_type: ItemType::Asset,
id: item_id,
};
let details = self.item_details(vec![item]).await?;
details
.first()
.ok_or(RoboatError::MalformedResponse)?
.product_id
.ok_or(RoboatError::MalformedResponse)
}
pub async fn product_id_bulk(&self, item_ids: Vec<u64>) -> Result<Vec<u64>, RoboatError> {
let item_ids_len = item_ids.len();
let mut items = Vec::new();
for item_id in item_ids {
let item = Item {
item_type: ItemType::Asset,
id: item_id,
};
items.push(item);
}
let details = self.item_details(items).await?;
let product_ids = details
.iter()
.filter_map(|x| x.product_id)
.collect::<Vec<u64>>();
if product_ids.len() != item_ids_len {
return Err(RoboatError::MalformedResponse);
}
Ok(product_ids)
}
pub async fn collectible_item_id(&self, item_id: u64) -> Result<String, RoboatError> {
let item = Item {
item_type: ItemType::Asset,
id: item_id,
};
let details = self.item_details(vec![item]).await?;
details
.first()
.ok_or(RoboatError::MalformedResponse)?
.collectible_item_id
.clone()
.ok_or(RoboatError::MalformedResponse)
}
pub async fn collectible_item_id_bulk(
&self,
item_ids: Vec<u64>,
) -> Result<Vec<String>, RoboatError> {
let item_ids_len = item_ids.len();
let mut items = Vec::new();
for item_id in item_ids {
let item = Item {
item_type: ItemType::Asset,
id: item_id,
};
items.push(item);
}
let details = self.item_details(items).await?;
let collectible_item_ids = details
.iter()
.filter_map(|x| x.collectible_item_id.clone())
.collect::<Vec<String>>();
if collectible_item_ids.len() != item_ids_len {
return Err(RoboatError::MalformedResponse);
}
Ok(collectible_item_ids)
}
pub async fn avatar_catalog_search(
&self,
query: &AvatarSearchQuery,
cursor: Option<String>,
) -> Result<(Vec<Item>, Option<String>), RoboatError> {
let formatted_url = format!(
"{}&limit={}&cursor={}",
query.to_url(),
QUERY_LIMIT.as_u8(),
cursor.unwrap_or_default()
);
let request_result = self.reqwest_client.get(formatted_url).send().await;
let response = Self::validate_request_result(request_result).await?;
let raw = Self::parse_to_raw::<AvatarSearchQueryResponse>(response).await?;
let items = raw.items;
let next_cursor = raw.next_page_cursor;
Ok((items, next_cursor))
}
}
mod internal {
use super::{request_types, sort_items_by_argument_order, Item, ItemDetails, ITEM_DETAILS_API};
use crate::XCSRF_HEADER;
use crate::{Client, RoboatError};
impl Client {
pub(super) async fn item_details_internal(
&self,
items: Vec<Item>,
) -> Result<Vec<ItemDetails>, RoboatError> {
let request_body = request_types::ItemDetailsReqBody {
items: items
.iter()
.map(|x| request_types::ItemReq::from(*x))
.collect(),
};
let request_result = self
.reqwest_client
.post(ITEM_DETAILS_API)
.header(XCSRF_HEADER, self.xcsrf().await)
.json(&request_body)
.send()
.await;
let response = Self::validate_request_result(request_result).await?;
let raw = Self::parse_to_raw::<request_types::ItemDetailsResponse>(response).await?;
let mut item_details = Vec::new();
for raw_details in raw.data {
let details = ItemDetails::try_from(raw_details)?;
item_details.push(details);
}
sort_items_by_argument_order(&mut item_details, &items);
Ok(item_details)
}
}
}
fn sort_items_by_argument_order(items: &mut [ItemDetails], arguments: &[Item]) {
items.sort_by(|a, b| {
let a_index = arguments
.iter()
.position(|item_args| item_args.id == a.id)
.unwrap_or(usize::MAX);
let b_index = arguments
.iter()
.position(|item_args| item_args.id == b.id)
.unwrap_or(usize::MAX);
a_index.cmp(&b_index)
});
}