From 52e83d2b8bac4f9ed26aefc215bf56643e6e707f Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Wed, 27 Mar 2024 19:13:49 -0400 Subject: [PATCH 1/6] Start v2 group implementation --- src/cli/group_cli.rs | 97 +++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 7 ++- src/rbx/mod.rs | 1 + src/rbx/v1/mod.rs | 2 +- src/rbx/v2/group.rs | 100 +++++++++++++++++++++++++++++++++++++++++ src/rbx/v2/http_err.rs | 46 +++++++++++++++++++ src/rbx/v2/mod.rs | 62 +++++++++++++++++++++++++ 7 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 src/cli/group_cli.rs create mode 100644 src/rbx/v2/group.rs create mode 100644 src/rbx/v2/http_err.rs create mode 100644 src/rbx/v2/mod.rs diff --git a/src/cli/group_cli.rs b/src/cli/group_cli.rs new file mode 100644 index 0000000..ec239b0 --- /dev/null +++ b/src/cli/group_cli.rs @@ -0,0 +1,97 @@ +use clap::{Args, Subcommand}; +use rbxcloud::rbx::v2::{group::GroupId, RbxCloud}; + +#[derive(Debug, Subcommand)] +pub enum GroupCommands { + /// Get info about the group + Get { + /// Group ID + #[clap(short, long, value_parser)] + group_id: u64, + + /// Pretty-print the JSON response + #[clap(short, long, value_parser, default_value_t = false)] + pretty: bool, + + /// Roblox Open Cloud API Key + #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] + api_key: String, + }, + + /// Get the current shout and other metadata + Shout { + /// Group ID + #[clap(short, long, value_parser)] + group_id: u64, + + /// Pretty-print the JSON response + #[clap(short, long, value_parser, default_value_t = false)] + pretty: bool, + + /// Only return the shout message string + #[clap(short, long, value_parser, default_value_t = false)] + only_message: bool, + + /// Roblox Open Cloud API Key + #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] + api_key: String, + }, +} + +#[derive(Debug, Args)] +pub struct Group { + #[clap(subcommand)] + command: GroupCommands, +} + +impl Group { + pub async fn run(self) -> anyhow::Result> { + match self.command { + GroupCommands::Get { + group_id, + api_key, + pretty, + } => { + let rbx_cloud = RbxCloud::new(&api_key); + let group = rbx_cloud.group(GroupId(group_id)); + let res = group.get_info().await; + match res { + Ok(group_info) => { + let r = if pretty { + serde_json::to_string_pretty(&group_info)? + } else { + serde_json::to_string(&group_info)? + }; + Ok(Some(r)) + } + Err(err) => Err(anyhow::anyhow!(err)), + } + } + + GroupCommands::Shout { + group_id, + pretty, + only_message, + api_key, + } => { + let rbx_cloud = RbxCloud::new(&api_key); + let group = rbx_cloud.group(GroupId(group_id)); + let res = group.get_shout().await; + match res { + Ok(group_info) => { + if only_message { + return Ok(Some(group_info.content)); + } + let r = if pretty { + serde_json::to_string_pretty(&group_info)? + } else { + serde_json::to_string(&group_info)? + }; + Ok(Some(r)) + } + Err(err) => Err(anyhow::anyhow!(err)), + } + } + } + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index be6cb22..1938b34 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,13 +1,14 @@ mod assets_cli; mod datastore_cli; mod experience_cli; +mod group_cli; mod messaging_cli; mod ordered_datastore_cli; use clap::{Parser, Subcommand}; use self::{ - assets_cli::Assets, datastore_cli::DataStore, experience_cli::Experience, + assets_cli::Assets, datastore_cli::DataStore, experience_cli::Experience, group_cli::Group, messaging_cli::Messaging, ordered_datastore_cli::OrderedDataStore, }; @@ -34,6 +35,9 @@ pub enum Command { /// Access the Roblox OrderedDataStore API OrderedDatastore(OrderedDataStore), + + /// Access the Roblox Group API + Group(Group), } impl Cli { @@ -44,6 +48,7 @@ impl Cli { Command::Messaging(command) => command.run().await, Command::Datastore(command) => command.run().await, Command::OrderedDatastore(command) => command.run().await, + Command::Group(command) => command.run().await, } } } diff --git a/src/rbx/mod.rs b/src/rbx/mod.rs index f79d4fd..767d4de 100644 --- a/src/rbx/mod.rs +++ b/src/rbx/mod.rs @@ -4,3 +4,4 @@ pub mod error; pub(crate) mod util; pub mod v1; +pub mod v2; diff --git a/src/rbx/v1/mod.rs b/src/rbx/v1/mod.rs index c54fd06..c2dea12 100644 --- a/src/rbx/v1/mod.rs +++ b/src/rbx/v1/mod.rs @@ -1,4 +1,4 @@ -//! Access into Roblox APIs. +//! Access into Roblox v1 APIs. //! //! Most usage should go through the `RbxCloud` struct. pub mod assets; diff --git a/src/rbx/v2/group.rs b/src/rbx/v2/group.rs new file mode 100644 index 0000000..ccbf653 --- /dev/null +++ b/src/rbx/v2/group.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Serialize}; + +use crate::rbx::error::Error; + +use super::http_err::handle_http_err; + +#[derive(Debug, Clone, Copy)] +pub struct GroupId(pub u64); + +impl std::fmt::Display for GroupId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +pub struct GetGroupParams { + pub api_key: String, + pub group_id: GroupId, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetGroupResponse { + pub path: String, + pub create_time: String, + pub update_time: String, + pub id: String, + pub display_name: String, + pub description: String, + pub owner: String, + pub member_count: u64, + pub public_entry_allowed: bool, + pub locked: bool, + pub verified: bool, +} + +pub struct GetGroupShoutParams { + pub api_key: String, + pub group_id: GroupId, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetGroupShoutResponse { + pub path: String, + pub create_time: String, + pub update_time: String, + pub content: String, + pub poster: String, +} + +pub async fn get_group(params: &GetGroupParams) -> Result { + let client = reqwest::Client::new(); + + let url = format!( + "https://apis.roblox.com/cloud/v2/groups/{groupId}", + groupId = ¶ms.group_id, + ); + + let res = client + .get(url) + .header("x-api-key", ¶ms.api_key) + .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) +} + +pub async fn get_group_shout(params: &GetGroupShoutParams) -> Result { + let client = reqwest::Client::new(); + + let url = format!( + "https://apis.roblox.com/cloud/v2/groups/{groupId}/shout", + groupId = ¶ms.group_id, + ); + + let res = client + .get(url) + .header("x-api-key", ¶ms.api_key) + .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/http_err.rs b/src/rbx/v2/http_err.rs new file mode 100644 index 0000000..5a09d0d --- /dev/null +++ b/src/rbx/v2/http_err.rs @@ -0,0 +1,46 @@ +use crate::rbx::error::Error; + +pub fn handle_http_err(code: u16) -> Result { + match code { + 400 => Err(Error::HttpStatusError { + code, + msg: "invalid argument".to_string(), + }), + 403 => Err(Error::HttpStatusError { + code, + msg: "permission denied".to_string(), + }), + 404 => Err(Error::HttpStatusError { + code, + msg: "not found".to_string(), + }), + 409 => Err(Error::HttpStatusError { + code, + msg: "aborted".to_string(), + }), + 429 => Err(Error::HttpStatusError { + code, + msg: "resource exhausted".to_string(), + }), + 499 => Err(Error::HttpStatusError { + code, + msg: "cancelled".to_string(), + }), + 500 => Err(Error::HttpStatusError { + code, + msg: "internal server error".to_string(), + }), + 501 => Err(Error::HttpStatusError { + code, + msg: "not implemented".to_string(), + }), + 503 => Err(Error::HttpStatusError { + code, + msg: "unavailable".to_string(), + }), + _ => Err(Error::HttpStatusError { + code, + msg: "unknown error".to_string(), + }), + } +} diff --git a/src/rbx/v2/mod.rs b/src/rbx/v2/mod.rs new file mode 100644 index 0000000..24435c3 --- /dev/null +++ b/src/rbx/v2/mod.rs @@ -0,0 +1,62 @@ +//! Access into Roblox v2 APIs. +//! +//! Most usage should go through the `RbxCloud` struct. + +use self::group::{ + GetGroupParams, GetGroupResponse, GetGroupShoutParams, GetGroupShoutResponse, GroupId, +}; +pub mod group; +pub(crate) mod http_err; + +use crate::rbx::error::Error; + +/// Access into the Roblox Open Cloud APIs. +/// +/// ```rust,no_run +/// use rbxcloud::rbx::v2::RbxCloud; +/// +/// let cloud = RbxCloud::new("API_KEY"); +/// ``` +#[derive(Debug)] +pub struct RbxCloud { + /// Roblox API key. + pub api_key: String, +} + +pub struct RbxGroup { + pub api_key: String, + pub group_id: GroupId, +} + +impl RbxGroup { + pub async fn get_info(&self) -> Result { + group::get_group(&GetGroupParams { + api_key: self.api_key.clone(), + group_id: self.group_id, + }) + .await + } + + pub async fn get_shout(&self) -> Result { + group::get_group_shout(&GetGroupShoutParams { + api_key: self.api_key.clone(), + group_id: self.group_id, + }) + .await + } +} + +impl RbxCloud { + pub fn new(api_key: &str) -> RbxCloud { + RbxCloud { + api_key: api_key.to_string(), + } + } + + pub fn group(&self, group_id: GroupId) -> RbxGroup { + RbxGroup { + api_key: self.api_key.clone(), + group_id, + } + } +} From 58fdcc2d2a4ae9ce94ee37633e931bfaefaaff0d Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Wed, 27 Mar 2024 19:27:16 -0400 Subject: [PATCH 2/6] Group example and docs --- docs/cli/cli-group.md | 42 +++++++++++++++++++++++++++++++++++++ examples/group-get-shout.rs | 25 ++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 docs/cli/cli-group.md create mode 100644 examples/group-get-shout.rs diff --git a/docs/cli/cli-group.md b/docs/cli/cli-group.md new file mode 100644 index 0000000..0bd2d35 --- /dev/null +++ b/docs/cli/cli-group.md @@ -0,0 +1,42 @@ +# Group API + +## Get Group Info +Get information about a group. +``` +Usage: rbxcloud.exe group get [OPTIONS] --group-id --api-key + +Options: + -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] + -g, --group-id Group ID + -p, --pretty Pretty-print the JSON response + -h, --help Print help +``` + +### Example +``` +$ rbxcloud group get -p -g 12345 -a MY_KEY +``` + +## Get Group Shout +Get a group's current shout and its metadata. +``` +Usage: rbxcloud.exe group shout [OPTIONS] --group-id --api-key + +Options: + -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] + -g, --group-id Group ID + -p, --pretty Pretty-print the JSON response + -o, --only-message Only return the shout message string + -h, --help Print help +``` + +### Example +Get a group's shout and its metadata: +``` +$ rbxcloud group shout -p -g 12345 -a MY_KEY +``` + +Get a group's shout message only: +``` +$ rbxcloud group shout -p -g 12345 -a MY_KEY --only-message +``` \ No newline at end of file diff --git a/examples/group-get-shout.rs b/examples/group-get-shout.rs new file mode 100644 index 0000000..a9afc94 --- /dev/null +++ b/examples/group-get-shout.rs @@ -0,0 +1,25 @@ +use rbxcloud::rbx::{ + error::Error, + v2::{group::GroupId, RbxCloud}, +}; + +async fn get_group_shout() -> Result { + // Inputs: + let api_key = "MY_API_KEY"; + let group_id = 9876543210; + + let cloud = RbxCloud::new(api_key); + let group = cloud.group(GroupId(group_id)); + + // Get the shout's content: + group.get_shout().await.map(|r| r.content) +} + +#[tokio::main] +async fn main() { + let shout_res = get_group_shout().await; + match shout_res { + Ok(shout) => println!("{shout}"), + Err(e) => eprintln!("{e:?}"), + } +} From b50c6ce5f9e438c4df36f877f2c3ba37e03c11de Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Wed, 27 Mar 2024 19:50:25 -0400 Subject: [PATCH 3/6] List group roles --- docs/cli/cli-group.md | 25 ++++++++++-- src/cli/group_cli.rs | 45 +++++++++++++++++++++ src/rbx/v2/group.rs | 93 +++++++++++++++++++++++++++++++++++++++++-- src/rbx/v2/mod.rs | 15 +++++++ 4 files changed, 172 insertions(+), 6 deletions(-) diff --git a/docs/cli/cli-group.md b/docs/cli/cli-group.md index 0bd2d35..21634a1 100644 --- a/docs/cli/cli-group.md +++ b/docs/cli/cli-group.md @@ -3,7 +3,7 @@ ## Get Group Info Get information about a group. ``` -Usage: rbxcloud.exe group get [OPTIONS] --group-id --api-key +Usage: rbxcloud group get [OPTIONS] --group-id --api-key Options: -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] @@ -20,7 +20,7 @@ $ rbxcloud group get -p -g 12345 -a MY_KEY ## Get Group Shout Get a group's current shout and its metadata. ``` -Usage: rbxcloud.exe group shout [OPTIONS] --group-id --api-key +Usage: rbxcloud group shout [OPTIONS] --group-id --api-key Options: -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] @@ -39,4 +39,23 @@ $ rbxcloud group shout -p -g 12345 -a MY_KEY Get a group's shout message only: ``` $ rbxcloud group shout -p -g 12345 -a MY_KEY --only-message -``` \ No newline at end of file +``` + +## List Group Roles +List the roles of a given group. +``` +Usage: rbxcloud group roles [OPTIONS] --group-id --api-key + +Options: + -g, --group-id Group ID + -p, --pretty Pretty-print the JSON response + -m, --max-page-size Max items returned per page + -n, --next-page-token Next page token + -a, --api-key Roblox Open Cloud API Key [env: RBXCLOUD_API_KEY=] + -h, --help Print help +``` + +### Example +``` +$ rbxcloud group roles -p -g 12345 -a MY_KEY +``` diff --git a/src/cli/group_cli.rs b/src/cli/group_cli.rs index ec239b0..d16ba6b 100644 --- a/src/cli/group_cli.rs +++ b/src/cli/group_cli.rs @@ -36,6 +36,29 @@ pub enum GroupCommands { #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] api_key: String, }, + + /// List the roles of a group + Roles { + /// Group ID + #[clap(short, long, value_parser)] + group_id: u64, + + /// Pretty-print the JSON response + #[clap(short, long, value_parser, default_value_t = false)] + pretty: bool, + + /// Max items returned per page + #[clap(short, long, value_parser)] + max_page_size: Option, + + /// Next page token + #[clap(short, long, value_parser)] + next_page_token: Option, + + /// Roblox Open Cloud API Key + #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] + api_key: String, + }, } #[derive(Debug, Args)] @@ -92,6 +115,28 @@ impl Group { Err(err) => Err(anyhow::anyhow!(err)), } } + GroupCommands::Roles { + group_id, + api_key, + pretty, + max_page_size, + next_page_token, + } => { + let rbx_cloud = RbxCloud::new(&api_key); + let group = rbx_cloud.group(GroupId(group_id)); + let res = group.list_roles(max_page_size, next_page_token).await; + match res { + Ok(group_info) => { + let r = if pretty { + serde_json::to_string_pretty(&group_info)? + } else { + serde_json::to_string(&group_info)? + }; + Ok(Some(r)) + } + Err(err) => Err(anyhow::anyhow!(err)), + } + } } } } diff --git a/src/rbx/v2/group.rs b/src/rbx/v2/group.rs index ccbf653..15b8fa0 100644 --- a/src/rbx/v2/group.rs +++ b/src/rbx/v2/group.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::rbx::error::Error; +use crate::rbx::{error::Error, util::QueryString}; use super::http_err::handle_http_err; @@ -27,7 +27,7 @@ pub struct GetGroupResponse { pub id: String, pub display_name: String, pub description: String, - pub owner: String, + pub owner: Option, pub member_count: u64, pub public_entry_allowed: bool, pub locked: bool, @@ -46,7 +46,58 @@ pub struct GetGroupShoutResponse { pub create_time: String, pub update_time: String, pub content: String, - pub poster: String, + pub poster: Option, +} + +pub struct ListGroupRolesParams { + pub api_key: String, + pub group_id: GroupId, + pub max_page_size: Option, + pub page_token: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GroupRolePermission { + pub view_wall_posts: bool, + pub create_wall_posts: bool, + pub delete_wall_posts: bool, + pub view_group_shout: bool, + pub create_group_shout: bool, + pub change_rank: bool, + pub accept_requests: bool, + pub exile_members: bool, + pub manage_relationships: bool, + pub view_audit_log: bool, + pub spend_group_funds: bool, + pub advertise_group: bool, + pub create_avatar_items: bool, + pub manage_avatar_items: bool, + pub manage_group_universes: bool, + pub view_universe_analytics: bool, + pub create_api_keys: bool, + pub manage_api_keys: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GroupRole { + pub path: String, + pub create_time: Option, + pub update_time: Option, + pub id: String, + pub display_name: String, + pub description: Option, + pub rank: u32, + pub member_count: Option, + pub permissions: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ListGroupRolesResponse { + pub group_roles: Vec, + pub next_page_token: Option, } pub async fn get_group(params: &GetGroupParams) -> Result { @@ -98,3 +149,39 @@ pub async fn get_group_shout(params: &GetGroupShoutParams) -> Result().await?; Ok(body) } + +pub async fn list_group_roles( + params: &ListGroupRolesParams, +) -> Result { + let client = reqwest::Client::new(); + + let url = format!( + "https://apis.roblox.com/cloud/v2/groups/{groupId}/roles", + groupId = ¶ms.group_id, + ); + + let mut query: QueryString = vec![]; + if let Some(max_page_size) = ¶ms.max_page_size { + query.push(("maxPageSize", max_page_size.to_string())) + } + if let Some(page_token) = ¶ms.page_token { + query.push(("pageToken", page_token.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/mod.rs b/src/rbx/v2/mod.rs index 24435c3..7eb0360 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -4,6 +4,7 @@ use self::group::{ GetGroupParams, GetGroupResponse, GetGroupShoutParams, GetGroupShoutResponse, GroupId, + ListGroupRolesParams, ListGroupRolesResponse, }; pub mod group; pub(crate) mod http_err; @@ -44,6 +45,20 @@ impl RbxGroup { }) .await } + + pub async fn list_roles( + &self, + max_page_size: Option, + page_token: Option, + ) -> Result { + group::list_group_roles(&ListGroupRolesParams { + api_key: self.api_key.clone(), + group_id: self.group_id, + max_page_size: max_page_size, + page_token: page_token, + }) + .await + } } impl RbxCloud { From 5df6e87e17089189861601eed0e7cb9ade4a9aa9 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Thu, 28 Mar 2024 11:42:01 -0400 Subject: [PATCH 4/6] Group memberships --- examples/group-get-shout.rs | 6 ++-- src/cli/group_cli.rs | 68 +++++++++++++++++++++++++++++++++---- src/rbx/v2/group.rs | 64 ++++++++++++++++++++++++++++++++++ src/rbx/v2/mod.rs | 45 ++++++++++++++++-------- 4 files changed, 159 insertions(+), 24 deletions(-) diff --git a/examples/group-get-shout.rs b/examples/group-get-shout.rs index a9afc94..3f65d52 100644 --- a/examples/group-get-shout.rs +++ b/examples/group-get-shout.rs @@ -1,6 +1,6 @@ use rbxcloud::rbx::{ error::Error, - v2::{group::GroupId, RbxCloud}, + v2::{group::GroupId, Client}, }; async fn get_group_shout() -> Result { @@ -8,8 +8,8 @@ async fn get_group_shout() -> Result { let api_key = "MY_API_KEY"; let group_id = 9876543210; - let cloud = RbxCloud::new(api_key); - let group = cloud.group(GroupId(group_id)); + let client = Client::new(api_key); + let group = client.group(GroupId(group_id)); // Get the shout's content: group.get_shout().await.map(|r| r.content) diff --git a/src/cli/group_cli.rs b/src/cli/group_cli.rs index d16ba6b..93b21a3 100644 --- a/src/cli/group_cli.rs +++ b/src/cli/group_cli.rs @@ -1,5 +1,5 @@ use clap::{Args, Subcommand}; -use rbxcloud::rbx::v2::{group::GroupId, RbxCloud}; +use rbxcloud::rbx::v2::{group::GroupId, Client}; #[derive(Debug, Subcommand)] pub enum GroupCommands { @@ -59,6 +59,33 @@ pub enum GroupCommands { #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] api_key: String, }, + + /// List the memberships of a group + Memberships { + /// Group ID + #[clap(short, long, value_parser)] + group_id: u64, + + /// Pretty-print the JSON response + #[clap(short, long, value_parser, default_value_t = false)] + pretty: bool, + + /// Max items returned per page + #[clap(short, long, value_parser)] + max_page_size: Option, + + /// Filter + #[clap(short, long, value_parser)] + filter: Option, + + /// Next page token + #[clap(short, long, value_parser)] + next_page_token: Option, + + /// Roblox Open Cloud API Key + #[clap(short, long, value_parser, env = "RBXCLOUD_API_KEY")] + api_key: String, + }, } #[derive(Debug, Args)] @@ -75,8 +102,8 @@ impl Group { api_key, pretty, } => { - let rbx_cloud = RbxCloud::new(&api_key); - let group = rbx_cloud.group(GroupId(group_id)); + let client = Client::new(&api_key); + let group = client.group(GroupId(group_id)); let res = group.get_info().await; match res { Ok(group_info) => { @@ -97,8 +124,8 @@ impl Group { only_message, api_key, } => { - let rbx_cloud = RbxCloud::new(&api_key); - let group = rbx_cloud.group(GroupId(group_id)); + let client = Client::new(&api_key); + let group = client.group(GroupId(group_id)); let res = group.get_shout().await; match res { Ok(group_info) => { @@ -115,6 +142,7 @@ impl Group { Err(err) => Err(anyhow::anyhow!(err)), } } + GroupCommands::Roles { group_id, api_key, @@ -122,8 +150,8 @@ impl Group { max_page_size, next_page_token, } => { - let rbx_cloud = RbxCloud::new(&api_key); - let group = rbx_cloud.group(GroupId(group_id)); + let client = Client::new(&api_key); + let group = client.group(GroupId(group_id)); let res = group.list_roles(max_page_size, next_page_token).await; match res { Ok(group_info) => { @@ -137,6 +165,32 @@ impl Group { Err(err) => Err(anyhow::anyhow!(err)), } } + + GroupCommands::Memberships { + group_id, + api_key, + pretty, + max_page_size, + next_page_token, + filter, + } => { + let client = Client::new(&api_key); + let group = client.group(GroupId(group_id)); + let res = group + .list_memberships(max_page_size, filter, next_page_token) + .await; + match res { + Ok(group_info) => { + let r = if pretty { + serde_json::to_string_pretty(&group_info)? + } else { + serde_json::to_string(&group_info)? + }; + Ok(Some(r)) + } + Err(err) => Err(anyhow::anyhow!(err)), + } + } } } } diff --git a/src/rbx/v2/group.rs b/src/rbx/v2/group.rs index 15b8fa0..4b223ff 100644 --- a/src/rbx/v2/group.rs +++ b/src/rbx/v2/group.rs @@ -100,6 +100,31 @@ pub struct ListGroupRolesResponse { pub next_page_token: Option, } +pub struct ListGroupMembershipsParams { + pub api_key: String, + pub group_id: GroupId, + pub max_page_size: Option, + pub page_token: Option, + pub filter: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GroupMembership { + pub path: String, + pub create_time: String, + pub update_time: String, + pub user: String, + pub role: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ListGroupMembershipsResponse { + pub group_memberships: Vec, + pub next_page_token: Option, +} + pub async fn get_group(params: &GetGroupParams) -> Result { let client = reqwest::Client::new(); @@ -185,3 +210,42 @@ pub async fn list_group_roles( let body = res.json::().await?; Ok(body) } + +pub async fn list_group_memberships( + params: &ListGroupMembershipsParams, +) -> Result { + let client = reqwest::Client::new(); + + let url = format!( + "https://apis.roblox.com/cloud/v2/groups/{groupId}/memberships", + groupId = ¶ms.group_id, + ); + + let mut query: QueryString = vec![]; + if let Some(max_page_size) = ¶ms.max_page_size { + query.push(("maxPageSize", max_page_size.to_string())) + } + 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/mod.rs b/src/rbx/v2/mod.rs index 7eb0360..d673a81 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -1,10 +1,11 @@ //! Access into Roblox v2 APIs. //! -//! Most usage should go through the `RbxCloud` struct. +//! Most usage should go through the `Client` struct. use self::group::{ GetGroupParams, GetGroupResponse, GetGroupShoutParams, GetGroupShoutResponse, GroupId, - ListGroupRolesParams, ListGroupRolesResponse, + ListGroupMembershipsParams, ListGroupMembershipsResponse, ListGroupRolesParams, + ListGroupRolesResponse, }; pub mod group; pub(crate) mod http_err; @@ -14,22 +15,22 @@ use crate::rbx::error::Error; /// Access into the Roblox Open Cloud APIs. /// /// ```rust,no_run -/// use rbxcloud::rbx::v2::RbxCloud; +/// use rbxcloud::rbx::v2::Client; /// -/// let cloud = RbxCloud::new("API_KEY"); +/// let client = RbxCloud::new("API_KEY"); /// ``` #[derive(Debug)] -pub struct RbxCloud { +pub struct Client { /// Roblox API key. pub api_key: String, } -pub struct RbxGroup { +pub struct GroupClient { pub api_key: String, pub group_id: GroupId, } -impl RbxGroup { +impl GroupClient { pub async fn get_info(&self) -> Result { group::get_group(&GetGroupParams { api_key: self.api_key.clone(), @@ -54,22 +55,38 @@ impl RbxGroup { group::list_group_roles(&ListGroupRolesParams { api_key: self.api_key.clone(), group_id: self.group_id, - max_page_size: max_page_size, - page_token: page_token, + max_page_size, + page_token, + }) + .await + } + + pub async fn list_memberships( + &self, + max_page_size: Option, + filter: Option, + page_token: Option, + ) -> Result { + group::list_group_memberships(&ListGroupMembershipsParams { + api_key: self.api_key.clone(), + group_id: self.group_id, + max_page_size, + page_token, + filter, }) .await } } -impl RbxCloud { - pub fn new(api_key: &str) -> RbxCloud { - RbxCloud { +impl Client { + pub fn new(api_key: &str) -> Client { + Client { api_key: api_key.to_string(), } } - pub fn group(&self, group_id: GroupId) -> RbxGroup { - RbxGroup { + pub fn group(&self, group_id: GroupId) -> GroupClient { + GroupClient { api_key: self.api_key.clone(), group_id, } From 395efaee5209bd6acb2fbc2ec7450c31fe126940 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Thu, 28 Mar 2024 11:43:24 -0400 Subject: [PATCH 5/6] Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 +++--- docs/cli/cli-install.md | 4 ++-- docs/lib/lib-install.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e387416..02b58ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,7 +786,7 @@ dependencies = [ [[package]] name = "rbxcloud" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "base64 0.22.0", diff --git a/Cargo.toml b/Cargo.toml index 9330485..a4c3c07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbxcloud" -version = "0.7.0" +version = "0.8.0" description = "CLI and SDK for the Roblox Open Cloud APIs" authors = ["Stephen Leitnick"] license = "MIT" diff --git a/README.md b/README.md index 05c68f2..b7ee37a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Possible use-cases: | | API v2 (Beta) | | -- | -- | -| :x: | Groups | +| :white_check_mark: | Groups | | :x: | Universes | | :x: | Places | | :x: | Instances | @@ -42,7 +42,7 @@ The goal of this project is to support all API endpoints that Roblox provides. ### Aftman Run the `aftman add` command within your project directory. This will add `rbxcloud` to the project's `aftman.toml` file (or create one if it doesn't yet exist). ```sh -$ aftman add Sleitnick/rbxcloud@0.7.0 +$ aftman add Sleitnick/rbxcloud@0.8.0 ``` ### From Release @@ -59,7 +59,7 @@ The library built for the CLI tool is available to use directly in Rust projects To use `rbxcloud` in a Rust project, simply add `rbxcloud` to the `Cargo.toml` dependency list. ```toml [dependencies] -rbxcloud = "0.7.0" +rbxcloud = "0.8.0" ``` Alternatively, use `cargo add`. diff --git a/docs/cli/cli-install.md b/docs/cli/cli-install.md index ab78c58..d6574ef 100644 --- a/docs/cli/cli-install.md +++ b/docs/cli/cli-install.md @@ -7,7 +7,7 @@ There are a few different ways to install the `rbxcloud` CLI. ### [Aftman](https://github.com/LPGhatguy/aftman) (Preferred) Run the `aftman add` command within your project directory. This will add `rbxcloud` to the project's `aftman.toml` file (or create one if it doesn't yet exist). ```sh -$ aftman add Sleitnick/rbxcloud@0.7.0 +$ aftman add Sleitnick/rbxcloud@0.8.0 ``` Next, run `aftman install` to install `rbxcloud`. @@ -17,7 +17,7 @@ Add `rbxcloud` under the `[tools]` section of your `foreman.toml` file. ```toml # foreman.toml [tools] -rbxcloud = { github = "Sleitnick/rbxcloud", version = "0.7.0" } +rbxcloud = { github = "Sleitnick/rbxcloud", version = "0.8.0" } ``` Next, run `foreman install` to install `rbxcloud`. diff --git a/docs/lib/lib-install.md b/docs/lib/lib-install.md index 89c002e..cfe7236 100644 --- a/docs/lib/lib-install.md +++ b/docs/lib/lib-install.md @@ -5,7 +5,7 @@ To use `rbxcloud` in a Rust project, simply add `rbxcloud` to the `Cargo.toml` dependency list. ```toml [dependencies] -rbxcloud = "0.7.0" +rbxcloud = "0.8.0" ``` Alternatively, use `cargo add`. From b9d49d93c67e8d131089aeb9cbb8ca6d5488ce20 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Thu, 28 Mar 2024 11:45:40 -0400 Subject: [PATCH 6/6] Fix name --- src/rbx/v2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rbx/v2/mod.rs b/src/rbx/v2/mod.rs index d673a81..8c4ba16 100644 --- a/src/rbx/v2/mod.rs +++ b/src/rbx/v2/mod.rs @@ -17,7 +17,7 @@ use crate::rbx::error::Error; /// ```rust,no_run /// use rbxcloud::rbx::v2::Client; /// -/// let client = RbxCloud::new("API_KEY"); +/// let client = Client::new("API_KEY"); /// ``` #[derive(Debug)] pub struct Client {