diff --git a/Cargo.toml b/Cargo.toml index 9bd95d0..9567ebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "roboat" readme = "README.md" repository = "https://github.com/Chloe-Woahie/roboat" -version = "0.12.0" +version = "0.13.0" [dependencies] reqwest = { version = "0.11.14", default-features=false, features = ["rustls-tls", "json"] } diff --git a/README.md b/README.md index 4ccb923..53f0f8b 100644 --- a/README.md +++ b/README.md @@ -12,29 +12,37 @@ A high performance interface for the Roblox API. This library is designed to be high-performance capable, meaning that it supports proxies and is capable of making requests in parallel. +Note that this crate is currently economy-focused, meaning that endpoints related to items and trades are currently prioritized. + # Documentation Extensive documentation is used throughout this crate. All public methods in this crate are documented and have at least one corresponding example. Documentation can be found [here](https://docs.rs/roboat/). -# Covered Endpoints +# Coverage * Catalog API - [`catalog.roblox.com/*`] - - Item Details - `/v1/catalog/items/details` + - Fetch Item Details - [`Client::item_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.item_details) + - Fetch Product ID - [`Client::product_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id) + - Fetch Product ID Bulk - [`Client::product_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id_bulk) + - Fetch Collectible Item ID - [`Client::collectible_item_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id) + - Fetch Collectible Item ID Bulk - [`Client::collectible_item_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id_bulk) * Economy API - [`economy.roblox.com/*`] - - Robux Balance - `/v1/users/{user_id}/currency` - - Resellers - `/v1/assets/{item_id}/resellers` - - User Sales - `/v2/users/{user_id}/transactions?transactionType=Sale` - - Put Limited On Sale - `/v1/assets/{item_id}/resellable-copies/{uaid}` - - Take Limited Off Sale - `/v1/assets/{item_id}/resellable-copies/{uaid}` - - Purchase Limited - `/v1/purchases/products/{product_id}` + - Fetch Robux Balance - [`Client::robux`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.robux) + - Fetch Resellers - [`Client::resellers`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.resellers) + - Fetch User Sales - [`Client::user_sales`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_sales) + - Put Limited On Sale - [`Client::put_limited_on_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.put_limited_on_sale) + - Take Limited Off Sale - [`Client::take_limited_off_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.take_limited_off_sale) + - Purchase Tradable Limited - [`Client::purchase_tradable_limited`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.purchase_tradable_limited) * Users API - [`users.roblox.com/*`] - - User Information - `/v1/users/authenticated` - - User Search - `/v1/users/search` + - Fetch User ID - [`Client::user_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_id) + - Fetch Username - [`Client::username`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.username) + - Fetch Display Name - [`Client::display_name`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.display_name) + - User Search - [`Client::user_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_search) * Presence API - [`presence.roblox.com/*`] - - Register Presence - `/v1/presence/register-app-presence` + - Register Presence - [`Client::register_presence`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.register_presence) * Trades API - [`trades.roblox.com/*`] - - Trades List - `/v1/trades/{trade_type}` + - Fetch Trades List - [`Client::trades`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trades) # Setup You can add the latest version of roboat to your project by running: @@ -46,7 +54,7 @@ Alternatively, you can add a specific version of roboat to your project by addin ```toml [dependencies] -roboat = "0.12" +roboat = "0.13.0" ``` # Quick Start Examples diff --git a/examples/get_product_id.rs b/examples/get_product_id.rs new file mode 100644 index 0000000..046adff --- /dev/null +++ b/examples/get_product_id.rs @@ -0,0 +1,12 @@ +use roboat::ClientBuilder; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = ClientBuilder::new().build(); + + let product_id = client.product_id(1365767).await?; + + println!("Ugc Limited Product ID: {}", product_id); + + Ok(()) +} diff --git a/examples/get_ugc_limited_collectible_item_id.rs b/examples/get_ugc_limited_collectible_item_id.rs new file mode 100644 index 0000000..d9f0d8f --- /dev/null +++ b/examples/get_ugc_limited_collectible_item_id.rs @@ -0,0 +1,12 @@ +use roboat::ClientBuilder; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = ClientBuilder::new().build(); + + let collectible_item_id = client.collectible_item_id(13032232281).await?; + + println!("Ugc Limited Product ID: {}", collectible_item_id); + + Ok(()) +} diff --git a/examples/purchase_limited.rs b/examples/purchase_limited.rs index d38e050..5b1a837 100644 --- a/examples/purchase_limited.rs +++ b/examples/purchase_limited.rs @@ -41,7 +41,7 @@ async fn main() -> Result<(), Box> { .expect("Item cannot be a \"new\" limited. Run purchase_ugc_limited instead."); let result = client - .purchase_limited(product_id, seller_id, uaid, price) + .purchase_tradable_limited(product_id, seller_id, uaid, price) .await; match result { diff --git a/src/catalog/avatar_catalog/mod.rs b/src/catalog/avatar_catalog/mod.rs index 17cc3d4..095c859 100644 --- a/src/catalog/avatar_catalog/mod.rs +++ b/src/catalog/avatar_catalog/mod.rs @@ -254,7 +254,9 @@ pub struct PremiumPricing { pub premium_price_in_robux: u64, } -/// The details of an item. Retrieved from . +/// A struct containing all the fields possibly returned from . +/// +/// This struct can be parsed into details structs. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)] pub struct ItemDetails { /// Either the asset id, or the bundle id, depending on the [`Self::item_type`]. @@ -282,8 +284,8 @@ pub struct ItemDetails { pub creator_has_verified_badge: bool, /// The type of creator that created the item (User or Group). pub creator_type: CreatorType, - /// The id of the creator. The value is 1 if the creator is Roblox. - pub creator_user_id: u64, + /// The id (group or user) of the creator. The value is 1 if the creator is Roblox. + pub creator_id: u64, /// The name of the creator. The value is "Roblox" if the creator is Roblox. pub creator_name: String, /// Coincides with price if the item is a non-limited, @@ -410,7 +412,7 @@ impl TryFrom for ItemDetails { .creator_has_verified_badge .ok_or(RoboatError::MalformedResponse)?; - let creator_user_id = value + let creator_id = value .creator_target_id .ok_or(RoboatError::MalformedResponse)?; @@ -449,7 +451,7 @@ impl TryFrom for ItemDetails { item_restrictions, creator_has_verified_badge, creator_type, - creator_user_id, + creator_id, creator_name, price, favorite_count, @@ -476,6 +478,10 @@ impl Client { /// If the `item_type` is [`ItemType::Asset`], then `id` is the item ID. /// Otherwise, if the `item_type` is [`ItemType::Bundle`], then `id` is the bundle ID. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// /// # Examples /// /// ```no_run @@ -529,15 +535,230 @@ impl Client { }, } } + + /// Fetches the product ID of an item (must be an asset). Uses [`Client::item_details`] internally + /// (which fetches from ) + /// + /// # Notes + /// * Does not require a valid roblosecurity. + /// * Will repeat once if the x-csrf-token is invalid. + /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// + /// # Example + /// ```no_run + /// use roboat::ClientBuilder; + /// + /// const ROBLOSECURITY: &str = "roblosecurity"; + /// + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// let client = ClientBuilder::new().build(); + /// + /// let item_id = 12345679; + /// + /// let product_id = client.product_id(item_id).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn product_id(&self, item_id: u64) -> Result { + let item = ItemArgs { + item_type: ItemType::Asset, + id: item_id, + }; + + let details = self.item_details(vec![item]).await?; + + details + .get(0) + .ok_or(RoboatError::MalformedResponse)? + .product_id + .ok_or(RoboatError::MalformedResponse) + } + + /// Fetches the product ID of multiple items (must be an asset). More efficient than calling [`Client::product_id`] repeatedly. + /// Uses [`Client::item_details`] internally + /// (which fetches from ). + /// + /// # Notes + /// * Does not require a valid roblosecurity. + /// * This endpoint will accept up to 120 items at a time. + /// * Will repeat once if the x-csrf-token is invalid. + /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// + /// # Example + /// ```no_run + /// use roboat::ClientBuilder; + /// + /// const ROBLOSECURITY: &str = "roblosecurity"; + /// + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// let client = ClientBuilder::new().build(); + /// + /// let item_id_1 = 12345679; + /// let item_id_2 = 987654321; + /// + /// let product_ids = client.product_id_bulk(vec![item_id_1, item_id_2]).await?; + /// + /// let product_id_1 = product_ids.get(0).ok_or("No product ID 1")?; + /// let product_id_2 = product_ids.get(1).ok_or("No product ID 2")?; + /// + /// # Ok(()) + /// # } + /// ``` + pub async fn product_id_bulk(&self, item_ids: Vec) -> Result, RoboatError> { + let item_ids_len = item_ids.len(); + + let mut items = Vec::new(); + + for item_id in item_ids { + let item = ItemArgs { + 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::>(); + + if product_ids.len() != item_ids_len { + return Err(RoboatError::MalformedResponse); + } + + Ok(product_ids) + } + + /// Fetches the collectible item id of a multiple non-tradeable limited (including ugc limiteds). + /// More efficient than calling [`Client::product_id`] repeatedly. + /// Uses [`Client::item_details`] internally + /// (which fetches from ). + /// + /// # Notes + /// * Does not require a valid roblosecurity. + /// * Will repeat once if the x-csrf-token is invalid. + /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// + /// # Example + /// ```no_run + /// use roboat::ClientBuilder; + /// + /// const ROBLOSECURITY: &str = "roblosecurity"; + /// + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// let client = ClientBuilder::new().build(); + /// + /// let item_id = 12345679; + /// + /// let collectible_item_id = client.collectible_item_id(item_id).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn collectible_item_id(&self, item_id: u64) -> Result { + let item = ItemArgs { + item_type: ItemType::Asset, + id: item_id, + }; + + let details = self.item_details(vec![item]).await?; + + details + .get(0) + .ok_or(RoboatError::MalformedResponse)? + .collectible_item_id + .clone() + .ok_or(RoboatError::MalformedResponse) + } + + /// Fetches the collectible item ids of multiple non-tradeable limiteds (including ugc limiteds). + /// More efficient than calling [`Client::collectible_item_id`] repeatedly. + /// Uses [`Client::item_details`] internally + /// (which fetches from ). + /// + /// # Notes + /// * Does not require a valid roblosecurity. + /// * This endpoint will accept up to 120 items at a time. + /// * Will repeat once if the x-csrf-token is invalid. + /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// + /// # Example + /// ```no_run + /// use roboat::ClientBuilder; + /// + /// const ROBLOSECURITY: &str = "roblosecurity"; + /// + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// let client = ClientBuilder::new().build(); + /// + /// let item_id_1 = 12345679; + /// let item_id_2 = 987654321; + /// + /// let collectible_item_ids = client.collectible_item_id_bulk(vec![item_id_1, item_id_2]).await?; + /// + /// let collectible_item_id_1 = collectible_item_ids.get(0).ok_or("No collectible item ID 1")?; + /// let collectible_item_id_2 = collectible_item_ids.get(1).ok_or("No collectible item ID 2")?; + /// + /// # Ok(()) + /// # } + /// ``` + pub async fn collectible_item_id_bulk( + &self, + item_ids: Vec, + ) -> Result, RoboatError> { + let item_ids_len = item_ids.len(); + + let mut items = Vec::new(); + + for item_id in item_ids { + let item = ItemArgs { + 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::>(); + + if collectible_item_ids.len() != item_ids_len { + return Err(RoboatError::MalformedResponse); + } + + Ok(collectible_item_ids) + } } mod internal { use super::{request_types, ItemArgs, ItemDetails, ITEM_DETAILS_API}; use crate::XCSRF_HEADER; use crate::{Client, RoboatError}; - use std::convert::TryFrom; impl Client { + /// Used internally to fetch the details of one or more items from . pub(super) async fn item_details_internal( &self, items: Vec, diff --git a/src/economy/mod.rs b/src/economy/mod.rs index cb9d697..ce0bfea 100644 --- a/src/economy/mod.rs +++ b/src/economy/mod.rs @@ -18,7 +18,7 @@ const TOGGLE_SALE_API_PART_2: &str = "/resellable-copies/"; const USER_SALES_TRANSACTION_TYPE: &str = "Sale"; -/// Custom Roblox errors that occur when using [`Client::purchase_limited`]. +/// Custom Roblox errors that occur when using [`Client::purchase_tradable_limited`]. #[derive( thiserror::Error, Debug, @@ -112,6 +112,10 @@ impl Client { /// # Notes /// * Requires a valid roblosecurity. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// /// # Example /// ```no_run /// use roboat::ClientBuilder; @@ -159,6 +163,10 @@ impl Client { /// * The first value is a vector of reseller listings. /// * The second value is the cursor for the next page of results. If there are no more pages, this will be `None`. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// /// # Example /// ```no_run /// use roboat::Limit; @@ -239,6 +247,10 @@ impl Client { /// * The first value is a vector of user sales. /// * The second value is the cursor for the next page of results. If there are no more pages, this will be `None`. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// /// # Example /// ```no_run /// use roboat::Limit; @@ -335,6 +347,11 @@ impl Client { /// # Return Value Notes /// * Will return `Ok(())` if the item was successfully put on sale. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// /// # Example /// ```no_run /// use roboat::ClientBuilder; @@ -388,6 +405,11 @@ impl Client { /// # Return Value Notes /// * Will return `Ok(())` if the item was successfully taken off sale. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// /// # Example /// ```no_run /// use roboat::ClientBuilder; @@ -423,7 +445,8 @@ impl Client { } // todo: add manual xcsrf refreshing and talk about it here - /// Purchases a limited (including limited u) using . + /// Purchases a limited using . + /// Only works on tradeable (legacy) limiteds. /// /// # Notes /// * Requires a valid roblosecurity. @@ -461,7 +484,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn purchase_limited( + pub async fn purchase_tradable_limited( &self, product_id: u64, seller_id: u64, diff --git a/src/lib.rs b/src/lib.rs index db722c2..7ed16ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,24 +8,29 @@ //! Extensive documentation is used throughout this crate. //! All public methods in this crate are documented and have at least one corresponding example. //! -//! # Covered Endpoints +//! # Coverage //! * Catalog API -//! - Item Details - [`Client::item_details`] +//! - Fetch Item Details - [`Client::item_details`] +//! - Fetch Product ID - [`Client::product_id`] +//! - Fetch Product ID Bulk - [`Client::product_id_bulk`] +//! - Fetch Collectible Item ID - [`Client::collectible_item_id`] +//! - Fetch Collectible Item ID Bulk - [`Client::collectible_item_id_bulk`] //! * Economy API -//! - Robux Balance - [`Client::robux`] -//! - Resellers - [`Client::resellers`] -//! - User Sales - [`Client::user_sales`] +//! - Fetch Robux Balance - [`Client::robux`] +//! - Fetch Resellers - [`Client::resellers`] +//! - Fetch User Sales - [`Client::user_sales`] //! - Put Limited On Sale - [`Client::put_limited_on_sale`] //! - Take Limited Off Sale - [`Client::take_limited_off_sale`] -//! - Purchase Limited - [`Client::purchase_limited`] +//! - Purchase Tradable Limited - [`Client::purchase_tradable_limited`] //! * Users API -//! - User Details - [`Client::user_id`], [`Client::username`], and [`Client::display_name`] -//! (all of them use the same endpoint internally and cache the results) +//! - Fetch User ID - [`Client::user_id`] +//! - Fetch Username - [`Client::username`] +//! - Fetch Display Name - [`Client::display_name`] //! - User Search - [`Client::user_search`] //! * Presence API //! - Register Presence - [`Client::register_presence`] //! * Trades API -//! - Trades List - [`Client::trades`] +//! - Fetch Trades List - [`Client::trades`] //! //! # Quick Start Examples //! @@ -239,7 +244,7 @@ pub enum RoboatError { /// a new xcsrf. #[error("Missing Xcsrf")] XcsrfNotReturned, - /// Custom Roblox errors sometimes thrown when the user calls [`Client::purchase_limited`]. + /// Custom Roblox errors sometimes thrown when the user calls [`Client::purchase_tradable_limited`]. #[error("{0}")] PurchaseLimitedError(PurchaseLimitedError), /// Used for any reqwest error that occurs. diff --git a/src/presence/mod.rs b/src/presence/mod.rs index 7366857..dbd4108 100644 --- a/src/presence/mod.rs +++ b/src/presence/mod.rs @@ -14,6 +14,11 @@ impl Client { /// # Return Value Notes /// * Will return `Ok(())` if presence was successfully registered. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// * All errors under [X-CSRF-TOKEN Required Errors](#x-csrf-token-required-errors). + /// /// # Example /// /// ```no_run diff --git a/src/trades/mod.rs b/src/trades/mod.rs index b1e3a57..26fa0ed 100644 --- a/src/trades/mod.rs +++ b/src/trades/mod.rs @@ -67,6 +67,10 @@ impl Client { /// * Requires a valid roblosecurity. /// * Trades are ordered newest to oldest. /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// /// # Example /// ```no_run /// use roboat::ClientBuilder; diff --git a/src/users/mod.rs b/src/users/mod.rs index 3576ff8..aaabfd9 100644 --- a/src/users/mod.rs +++ b/src/users/mod.rs @@ -68,6 +68,10 @@ impl Client { /// * The cursors in this response are not used as using them is currently broken. /// * Limits are not used for the same reason (the endpoint does not respect them). /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// /// # Example /// /// ```no_run