From ad4eb9f9c65889c824e1521e5a59bf87efc2df28 Mon Sep 17 00:00:00 2001 From: Chloe-Woahie <68732833+Chloe-Woahie@users.noreply.github.com> Date: Sun, 14 May 2023 16:40:48 -0400 Subject: [PATCH 1/4] feat: add messages() --- src/lib.rs | 2 + src/private_messages/mod.rs | 154 ++++++++++++++++++++++++++ src/private_messages/request_types.rs | 43 +++++++ 3 files changed, 199 insertions(+) create mode 100644 src/private_messages/mod.rs create mode 100644 src/private_messages/request_types.rs diff --git a/src/lib.rs b/src/lib.rs index a73c228..3becce0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,6 +206,8 @@ pub mod economy; pub mod groups; /// A module for endpoints prefixed with . mod presence; +/// A module for endpoints prefixed with . +pub mod private_messages; /// A module for endpoints prefixed with . pub mod trades; /// A module for endpoints prefixed with . diff --git a/src/private_messages/mod.rs b/src/private_messages/mod.rs new file mode 100644 index 0000000..30324de --- /dev/null +++ b/src/private_messages/mod.rs @@ -0,0 +1,154 @@ +use crate::{Client, RoboatError}; +use reqwest::header; +use serde::{Deserialize, Serialize}; + +mod request_types; + +/// Fun fact, pageSize doesn't actually do anything. It's always 20. +const PRIVATE_MESSAGES_API: &str = + "https://privatemessages.roblox.com/v1/messages?messageTab={message_tab_type}&pageNumber={page_number}&pageSize=20"; + +/// An enum that corresponds to the different message tabs. +#[allow(missing_docs)] +#[derive( + Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, Copy, +)] +pub enum MessageTabType { + #[default] + Inbox, + Sent, + Archive, +} + +impl std::fmt::Display for MessageTabType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Inbox => write!(f, "inbox"), + Self::Sent => write!(f, "sent"), + Self::Archive => write!(f, "archive"), + } + } +} + +/// A private message. This can be from the user's inbox, sent, or archive tab. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)] +pub struct Message { + pub message_id: u64, + pub sender_id: u64, + pub sender_username: String, + pub sender_display_name: String, + pub receiver_id: u64, + pub receiver_username: String, + pub receiver_display_name: String, + pub subject: String, + pub body: String, + /// ISO 8601 timestamp of when the message was created. + pub created: String, + /// Whether the message has been read. + pub is_read: bool, + /// Whether the message is from Roblox. + pub is_system_message: bool, +} + +/// Contains the metadata of the messages. This includes the total amount of messages +/// and the total amount of 20 message pages. +#[derive( + Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, Copy, +)] +pub struct MessagesMetadata { + /// The total amount of messages in that message tab. + pub total_message_count: u64, + /// The total amount of pages in that message tab. The page numbers go from 0..total_pages. + pub total_pages: u64, +} + +impl Client { + /// Page starts at 0 + /// + /// Fetches private messages from the specified message tab using . + /// + /// # Notes + /// * Requires a valid roblosecurity. + /// * Returns 20 messages at a time. + /// + /// # Argument Notes + /// * The page starts at 0. + /// + /// # Return Value Notes + /// * The first value in the tuple is a vector of messages. + /// * The second value in the tuple is the metadata of the messages (total count and page amount). + /// + /// # Errors + /// * All errors under [Standard Errors](#standard-errors). + /// * All errors under [Auth Required Errors](#auth-required-errors). + /// + /// # Example + /// ```no_run + /// use roboat::ClientBuilder; + /// use roboat::private_messages::MessageTabType::Inbox; + /// + /// const ROBLOSECURITY: &str = "roblosecurity"; + /// + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// let client = ClientBuilder::new().roblosecurity(ROBLOSECURITY.to_string()).build(); + /// + /// let inbox_type = Inbox; + /// + /// let (messages, messages_metadata) = client.messages(0, inbox_type).await?; + /// + /// println!("First Message Subject: {}", messages[0].subject); + /// println!("Total Messages: {}", messages_metadata.total_message_count); + /// println!("Total Pages: {}", messages_metadata.total_pages); + /// # Ok(()) + /// # } + /// ``` + pub async fn messages( + &self, + page: u64, + message_tab_type: MessageTabType, + ) -> Result<(Vec, MessagesMetadata), RoboatError> { + let cookie_string = self.cookie_string()?; + + let url = PRIVATE_MESSAGES_API + .replace("{message_tab_type}", message_tab_type.to_string().as_str()) + .replace("{page_number}", page.to_string().as_str()); + + let request_result = self + .reqwest_client + .get(&url) + .header(header::COOKIE, cookie_string) + .send() + .await; + + let response = Self::validate_request_result(request_result).await?; + let raw = Self::parse_to_raw::(response).await?; + + let messages = raw + .collection + .into_iter() + .map(|message| Message { + message_id: message.id as u64, + sender_id: message.sender.id as u64, + sender_username: message.sender.name, + sender_display_name: message.sender.display_name, + receiver_id: message.recipient.id as u64, + receiver_username: message.recipient.name, + receiver_display_name: message.recipient.display_name, + subject: message.subject, + body: message.body, + created: message.created, + is_read: message.is_read, + is_system_message: message.is_system_message, + }) + .collect(); + + let metadata = MessagesMetadata { + total_message_count: raw.total_collection_size as u64, + total_pages: raw.total_pages as u64, + }; + + Ok((messages, metadata)) + } +} diff --git a/src/private_messages/request_types.rs b/src/private_messages/request_types.rs new file mode 100644 index 0000000..4320b1e --- /dev/null +++ b/src/private_messages/request_types.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct MessagesResponse { + pub collection: Vec, + pub total_collection_size: i64, + pub total_pages: i64, + pub page_number: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct MessageRaw { + pub id: i64, + pub sender: Sender, + pub recipient: Recipient, + pub subject: String, + pub body: String, + pub created: String, + pub updated: String, + pub is_read: bool, + pub is_system_message: bool, + pub is_report_abuse_displayed: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct Sender { + pub has_verified_badge: bool, + pub id: i64, + pub name: String, + pub display_name: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct Recipient { + pub has_verified_badge: bool, + pub id: i64, + pub name: String, + pub display_name: String, +} From 6cfdacc6c77a3810abebdeb1cc80f75e685552f6 Mon Sep 17 00:00:00 2001 From: Chloe-Woahie <68732833+Chloe-Woahie@users.noreply.github.com> Date: Sun, 14 May 2023 16:40:58 -0400 Subject: [PATCH 2/4] chore: add example for messages() --- examples/fetch_inbound_messages.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 examples/fetch_inbound_messages.rs diff --git a/examples/fetch_inbound_messages.rs b/examples/fetch_inbound_messages.rs new file mode 100644 index 0000000..a5266a0 --- /dev/null +++ b/examples/fetch_inbound_messages.rs @@ -0,0 +1,28 @@ +use clap::Parser; +use roboat::private_messages::MessageTabType::Inbox; +use roboat::ClientBuilder; + +#[derive(Parser, Debug)] +struct Args { + #[arg(long, short)] + roblosecurity: String, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + + let client = ClientBuilder::new() + .roblosecurity(args.roblosecurity) + .build(); + + let inbox_type = Inbox; + + let (messages, messages_metadata) = client.messages(0, inbox_type).await?; + + println!("First Message Subject: {}", messages[0].subject); + println!("Total Messages: {}", messages_metadata.total_message_count); + println!("Total Pages: {}", messages_metadata.total_pages); + + Ok(()) +} From d4f3a4d9e1c47e1f5c445b383ddebd0a03d5e557 Mon Sep 17 00:00:00 2001 From: Chloe-Woahie <68732833+Chloe-Woahie@users.noreply.github.com> Date: Sun, 14 May 2023 16:41:24 -0400 Subject: [PATCH 3/4] chore: reflect api coverage --- README.md | 2 ++ src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 91f23d6..1d16916 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ Documentation can be found [here](https://docs.rs/roboat/). - Set Group Member Role - [`Client::set_group_member_role`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.set_group_member_role) * Presence API - [`presence.roblox.com/*`] - Register Presence - [`Client::register_presence`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.register_presence) +* Private Messages API - [`privatemessages.roblox.com/*`] + - Fetch Messages - [`Client::messages`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.messages) * Trades API - [`trades.roblox.com/*`] - Fetch Trades List - [`Client::trades`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trades) * Users API - [`users.roblox.com/*`] diff --git a/src/lib.rs b/src/lib.rs index 3becce0..fd04559 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,8 @@ //! - Set Group Member Role - [`Client::set_group_member_role`] //! * Presence API //! - Register Presence - [`Client::register_presence`] +//! * Private Messages API +//! - Fetch Messages - [`Client::messages`] //! * Trades API //! - Fetch Trades List - [`Client::trades`] //! * Users API From 98c0b131111e99399dcf2c8c76dda63f106d6abb Mon Sep 17 00:00:00 2001 From: Chloe-Woahie <68732833+Chloe-Woahie@users.noreply.github.com> Date: Sun, 14 May 2023 16:44:00 -0400 Subject: [PATCH 4/4] chore: increase crate version --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b188e0..ad83911 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.22.0" +version = "0.23.0" [dependencies] reqwest = { version = "0.11.14", default-features=false, features = ["rustls-tls", "json"] } diff --git a/README.md b/README.md index 1d16916..17a5fa8 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Alternatively, you can add a specific version of roboat to your project by addin ```toml [dependencies] -roboat = "0.22.0" +roboat = "0.23.0" ``` # Quick Start Examples