From d0b6623b73d0e981b44e617c8532817da8066647 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 19:41:38 -0400 Subject: [PATCH 01/10] Start inventory impl --- src/rbx/types.rs | 2 +- src/rbx/v2/inventory.rs | 166 +++++++++++++++++++++++++++++++++++ src/rbx/v2/luau_execution.rs | 2 - src/rbx/v2/mod.rs | 1 + 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/rbx/v2/inventory.rs diff --git a/src/rbx/types.rs b/src/rbx/types.rs index 81eea6c..13d0127 100644 --- a/src/rbx/types.rs +++ b/src/rbx/types.rs @@ -13,7 +13,7 @@ pub struct PlaceId(pub u64); pub struct ReturnLimit(pub u64); /// Represents a Roblox user's ID. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct RobloxUserId(pub u64); #[derive(Debug, Clone, Copy)] diff --git a/src/rbx/v2/inventory.rs b/src/rbx/v2/inventory.rs new file mode 100644 index 0000000..4b03fa5 --- /dev/null +++ b/src/rbx/v2/inventory.rs @@ -0,0 +1,166 @@ +use serde::{Deserialize, Serialize}; + +use crate::rbx::{error::Error, types::RobloxUserId, util::QueryString}; + +use super::http_err::handle_http_err; + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ListInventoryItemsParams { + pub api_key: String, + pub user_id: RobloxUserId, + pub max_page_size: Option, + pub page_token: Option, + pub filter: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItems { + inventory_items: Vec, + next_page_token: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItem { + path: String, + #[serde(skip_serializing_if = "Option::is_none")] + asset_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] + badge_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] + game_pass_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] + private_server_details: Option, + add_time: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItemAssetDetails { + asset_id: String, + instance_id: String, + inventory_item_asset_type: InventoryItemAssetType, + collectible_details: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItemBadgeDetails { + badge_id: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItemGamePassDetails { + game_pass_id: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItemPrivateServerDetails { + private_server_id: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InventoryItemCollectibleDetails { + // TODO +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InventoryItemAssetType { + ClassicTshirt, + Audio, + Hat, + Model, + ClassicShirt, + ClassicPants, + Decal, + ClassicHead, + Face, + Gear, + Animation, + Torso, + RightArm, + LeftArm, + LeftLeg, + RightLeg, + Package, + Plugin, + MeshPart, + HairAccessory, + FaceAccessory, + NeckAccessory, + ShoulderAccessory, + FrontAccessory, + BackAccessory, + WaistAccessory, + ClimbAnimation, + DeathAnimation, + FallAnimation, + IdleAnimation, + JumpAnimation, + RunAnimation, + SwimAnimation, + WalkAnimation, + PoseAnimation, + EmoteAnimation, + Video, + TshirtAccessory, + ShirtAccessory, + PantsAccessory, + JacketAccessory, + SweaterAccessory, + ShortsAccessory, + LeftShoeAccessory, + RightShoeAccessory, + DressSkirtAccessory, + EyebrowAccessory, + EyelashAccessory, + MoodAnimation, + DynamicHead, + CreatedPlace, + PurchasedPlace, +} + +pub async fn list_inventory_items( + params: &ListInventoryItemsParams, +) -> Result { + let client = reqwest::Client::new(); + + let url = format!( + "https://apis.roblox.com/cloud/v2/users/{userId}/inventory-items", + userId = params.user_id, + ); + + let mut query: QueryString = vec![]; + if let Some(max_page_size) = params.max_page_size { + query.push(("maxPageSize", format!("{max_page_size}"))); + } + if let Some(page_token) = ¶ms.page_token { + query.push(("pageToken", page_token.clone())); + } + if let Some(filter) = ¶ms.filter { + query.push(("filter", filter.clone())); + } + + let res = client + .get(url) + .header("x-api-key", ¶ms.api_key) + .query(&query) + .send() + .await?; + + let status = res.status(); + + if !status.is_success() { + let code = status.as_u16(); + return handle_http_err(code); + } + + let body = res.json::().await?; + Ok(body) +} diff --git a/src/rbx/v2/luau_execution.rs b/src/rbx/v2/luau_execution.rs index eb29e01..62e275e 100644 --- a/src/rbx/v2/luau_execution.rs +++ b/src/rbx/v2/luau_execution.rs @@ -258,8 +258,6 @@ pub async fn get_luau_execution_task_logs( .send() .await?; - println!("URL SENT: {}", res.url()); - let status = res.status(); if !status.is_success() { diff --git a/src/rbx/v2/mod.rs b/src/rbx/v2/mod.rs index 4ace408..c6a008b 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -28,6 +28,7 @@ use self::{ }; pub mod group; pub(crate) mod http_err; +pub mod inventory; pub mod luau_execution; pub mod notification; pub mod place; From 329d477a7edbebd379cb49e529b034b068b45e6b Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:11:08 -0400 Subject: [PATCH 02/10] Collectible details --- src/rbx/v2/inventory.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/rbx/v2/inventory.rs b/src/rbx/v2/inventory.rs index 4b03fa5..fba6ecb 100644 --- a/src/rbx/v2/inventory.rs +++ b/src/rbx/v2/inventory.rs @@ -66,7 +66,18 @@ pub struct InventoryItemPrivateServerDetails { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct InventoryItemCollectibleDetails { - // TODO + item_id: String, + instance_id: String, + instance_state: InventoryItemInstanceState, + serial_number: u64, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InventoryItemInstanceState { + CollectibleItemInstanceStateUnspecified, + Available, + Hold, } #[derive(Deserialize, Serialize, Debug)] From 66846d0d5f57fe8e75896b82f96694794c7863b0 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:13:25 -0400 Subject: [PATCH 03/10] Doc stub --- README.md | 2 +- docs/cli/cli-inventory.md | 1 + mkdocs.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/cli/cli-inventory.md diff --git a/README.md b/README.md index cdb973f..074a96e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Possible use-cases: | :white_check_mark: | Places | | :x: | Instances | | :white_check_mark: | Subscriptions | -| :x: | Inventory | +| :white_check_mark: | Inventory | | :white_check_mark: | User Notifications | | :white_check_mark: | User | | :x: | Creator Store | diff --git a/docs/cli/cli-inventory.md b/docs/cli/cli-inventory.md new file mode 100644 index 0000000..4640904 --- /dev/null +++ b/docs/cli/cli-inventory.md @@ -0,0 +1 @@ +# TODO diff --git a/mkdocs.yml b/mkdocs.yml index 1b2c5b0..6dca0a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ nav: - DataStore: cli/cli-datastore.md - Experience: cli/cli-experience.md - Group: cli/cli-group.md + - Inventory: cli/cli-inventory.md - Luau Execution: cli/cli-luau-execution.md - Messaging: cli/cli-messaging.md - Notification: cli/cli-notification.md From 25df29011c616c3f570f1ae370e3fcbccf6b36f4 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:16:52 -0400 Subject: [PATCH 04/10] Inventory client --- src/rbx/v2/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/rbx/v2/mod.rs b/src/rbx/v2/mod.rs index c6a008b..90d5189 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -2,6 +2,7 @@ //! //! Most usage should go through the `Client` struct. +use inventory::{InventoryItems, ListInventoryItemsParams}; use luau_execution::{ CreateLuauExecutionTaskParams, GetLuauExecutionSessionTaskLogsParams, GetLuauExecutionSessionTaskParams, LuauExecutionSessionTask, LuauExecutionSessionTaskLogPage, @@ -58,6 +59,10 @@ pub struct GroupClient { pub group_id: GroupId, } +pub struct InventoryClient { + pub api_key: String, +} + pub struct LuauExecutionClient { pub api_key: String, pub universe_id: UniverseId, @@ -137,6 +142,25 @@ impl GroupClient { } } +impl InventoryClient { + pub async fn list_inventory_items( + &self, + user_id: RobloxUserId, + max_page_size: Option, + page_token: Option, + filter: Option, + ) -> Result { + inventory::list_inventory_items(&ListInventoryItemsParams { + api_key: self.api_key.clone(), + user_id, + max_page_size, + page_token, + filter, + }) + .await + } +} + impl LuauExecutionClient { pub async fn create_task( &self, @@ -326,6 +350,12 @@ impl Client { } } + pub fn inventory(&self) -> InventoryClient { + InventoryClient { + api_key: self.api_key.clone(), + } + } + pub fn luau( &self, universe_id: UniverseId, From 8307f0304d3053a22cee2a31e332c78978800937 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:19:48 -0400 Subject: [PATCH 05/10] Inventory CLI stub --- src/cli/inventory_cli.rs | 17 +++++++++++++++++ src/cli/mod.rs | 6 ++++++ 2 files changed, 23 insertions(+) create mode 100644 src/cli/inventory_cli.rs diff --git a/src/cli/inventory_cli.rs b/src/cli/inventory_cli.rs new file mode 100644 index 0000000..863df77 --- /dev/null +++ b/src/cli/inventory_cli.rs @@ -0,0 +1,17 @@ +use clap::{Args, Subcommand}; +use rbxcloud::rbx::v2::Client; + +#[derive(Debug, Subcommand)] +pub enum InventoryCommands {} + +#[derive(Debug, Args)] +pub struct Inventory { + #[clap(subcommand)] + command: InventoryCommands, +} + +impl Inventory { + pub async fn run(self) -> anyhow::Result> { + match self.command {} + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2ecaab5..18aba72 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -2,6 +2,7 @@ mod assets_cli; mod datastore_cli; mod experience_cli; mod group_cli; +mod inventory_cli; mod luau_execution_cli; mod messaging_cli; mod notification_cli; @@ -12,6 +13,7 @@ mod universe_cli; mod user_cli; use clap::{Parser, Subcommand}; +use inventory_cli::Inventory; use luau_execution_cli::Luau; use universe_cli::Universe; use user_cli::User; @@ -46,6 +48,9 @@ pub enum Command { /// Access the Roblox OrderedDataStore API OrderedDatastore(OrderedDataStore), + /// Access the Roblox user inventory API + Inventory(Inventory), + /// Access the Roblox Group API Group(Group), @@ -76,6 +81,7 @@ impl Cli { Command::Datastore(command) => command.run().await, Command::OrderedDatastore(command) => command.run().await, Command::Group(command) => command.run().await, + Command::Inventory(command) => command.run().await, Command::Luau(command) => command.run().await, Command::Subscription(command) => command.run().await, Command::Notification(command) => command.run().await, From 093dd89234f8233fb9d74dcaf7b7756cfbdf2e15 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:23:15 -0400 Subject: [PATCH 06/10] Inventory CLI impl --- src/cli/inventory_cli.rs | 62 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/cli/inventory_cli.rs b/src/cli/inventory_cli.rs index 863df77..cb1c53c 100644 --- a/src/cli/inventory_cli.rs +++ b/src/cli/inventory_cli.rs @@ -1,8 +1,35 @@ use clap::{Args, Subcommand}; -use rbxcloud::rbx::v2::Client; +use rbxcloud::rbx::{types::RobloxUserId, v2::Client}; #[derive(Debug, Subcommand)] -pub enum InventoryCommands {} +pub enum InventoryCommands { + /// List inventory items for a given user + List { + /// Roblox user ID + #[clap(short, long, value_parser)] + user_id: u64, + + /// Pretty-print the JSON response + #[clap(short, long, value_parser, default_value_t = false)] + pretty: bool, + + /// Max page size + #[clap(short, long, value_parser)] + max_page_size: Option, + + /// Next page token + #[clap(short = 'n', long, value_parser)] + page_token: Option, + + /// Filter string + #[clap(short, long, value_parser)] + filter: Option, + + /// Roblox Open Cloud API Key + #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] + api_key: String, + }, +} #[derive(Debug, Args)] pub struct Inventory { @@ -12,6 +39,35 @@ pub struct Inventory { impl Inventory { pub async fn run(self) -> anyhow::Result> { - match self.command {} + match self.command { + InventoryCommands::List { + user_id, + pretty, + max_page_size, + page_token, + filter, + api_key, + } => { + let client = Client::new(&api_key); + + let inventory = client.inventory(); + + let res = inventory + .list_inventory_items(RobloxUserId(user_id), max_page_size, page_token, filter) + .await; + + match res { + Ok(data) => { + let r = if pretty { + serde_json::to_string_pretty(&data)? + } else { + serde_json::to_string(&data)? + }; + Ok(Some(r)) + } + Err(err) => Err(anyhow::anyhow!(err)), + } + } + } } } From d88b20f7cf15d11f292f3aeff4f4304a3ce2697a Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:25:42 -0400 Subject: [PATCH 07/10] Inventory CLI docs --- docs/cli/cli-inventory.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/cli/cli-inventory.md b/docs/cli/cli-inventory.md index 4640904..c48f80a 100644 --- a/docs/cli/cli-inventory.md +++ b/docs/cli/cli-inventory.md @@ -1 +1,17 @@ -# TODO +# Inventory API + +## List +List inventory items for a given user. + +``` +Usage: rbxcloud inventory list [OPTIONS] --user-id --api-key + +Options: + -u, --user-id Roblox user ID + -p, --pretty Pretty-print the JSON response + -m, --max-page-size Max page size + -n, --page-token Next page token + -f, --filter Filter string + -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] + -h, --help Print help +``` From 3648e9f89de81200b0e7178b1bce4e3dbc66d012 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:36:10 -0400 Subject: [PATCH 08/10] Serial number optional --- src/rbx/v2/inventory.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rbx/v2/inventory.rs b/src/rbx/v2/inventory.rs index fba6ecb..9b97c78 100644 --- a/src/rbx/v2/inventory.rs +++ b/src/rbx/v2/inventory.rs @@ -33,6 +33,7 @@ pub struct InventoryItem { game_pass_details: Option, #[serde(skip_serializing_if = "Option::is_none")] private_server_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] add_time: Option, } @@ -42,6 +43,7 @@ pub struct InventoryItemAssetDetails { asset_id: String, instance_id: String, inventory_item_asset_type: InventoryItemAssetType, + #[serde(skip_serializing_if = "Option::is_none")] collectible_details: Option, } @@ -69,7 +71,8 @@ pub struct InventoryItemCollectibleDetails { item_id: String, instance_id: String, instance_state: InventoryItemInstanceState, - serial_number: u64, + #[serde(skip_serializing_if = "Option::is_none")] + serial_number: Option, } #[derive(Deserialize, Serialize, Debug)] @@ -165,6 +168,8 @@ pub async fn list_inventory_items( .send() .await?; + println!("URL: {}", res.url()); + let status = res.status(); if !status.is_success() { From 17b634ebcdcb2a16f4d9272a685fac614d10f5ac Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:37:05 -0400 Subject: [PATCH 09/10] Remove print --- src/rbx/v2/inventory.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rbx/v2/inventory.rs b/src/rbx/v2/inventory.rs index 9b97c78..ace218a 100644 --- a/src/rbx/v2/inventory.rs +++ b/src/rbx/v2/inventory.rs @@ -168,8 +168,6 @@ pub async fn list_inventory_items( .send() .await?; - println!("URL: {}", res.url()); - let status = res.status(); if !status.is_success() { From 6140cc2c5de4cbf989e06f87e7d9ecc382bc4a06 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Sat, 22 Mar 2025 23:41:38 -0400 Subject: [PATCH 10/10] Clean up warnings --- src/rbx/v2/mod.rs | 2 +- src/rbx/v2/place.rs | 3 +-- src/rbx/v2/universe.rs | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rbx/v2/mod.rs b/src/rbx/v2/mod.rs index 90d5189..54a4d9e 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -295,7 +295,7 @@ impl UniverseClient { api_key: self.api_key.clone(), universe_id: self.universe_id, update_mask, - info: info, + info, }) .await } diff --git a/src/rbx/v2/place.rs b/src/rbx/v2/place.rs index 61c2e6c..745df49 100644 --- a/src/rbx/v2/place.rs +++ b/src/rbx/v2/place.rs @@ -83,8 +83,7 @@ pub async fn update_place(params: &UpdatePlaceParams) -> Result Result