roboat/
lib.rs

1//! # About
2//! A high performance interface for the Roblox API.
3//!
4//! This library is designed to be high-performance capable, meaning
5//! that a [`Client`] is designed to work with proxies, as well as make
6//! multiple requests in parallel. All API calls are made through a [`Client`].
7//!
8//! Extensive documentation is used throughout this crate.
9//! All public methods in this crate are documented and have at least one corresponding example.
10//!
11//! # Coverage
12//! * Auth API
13//!   - Force Refresh Xcsrf - [`Client::force_refresh_xcsrf`]
14//! * BEDEV2 API
15//!   - Fetch Non-Tradable Limited Details - [`Client::non_tradable_limited_details`]
16//!   - Fetch Collectible Product ID - [`Client::collectible_product_id`]
17//!   - Fetch Collectible Product ID Bulk - [`Client::collectible_product_id_bulk`]
18//!   - Fetch Collectible Creator ID - [`Client::collectible_creator_id`]
19//!   - Purchase Non-Tradable Limited - [`Client::purchase_non_tradable_limited`]
20//! * Catalog API
21//!   - Fetch Item Details - [`Client::item_details`]
22//!   - Fetch Product ID - [`Client::product_id`]  
23//!   - Fetch Product ID Bulk - [`Client::product_id_bulk`]
24//!   - Fetch Collectible Item ID - [`Client::collectible_item_id`]
25//!   - Fetch Collectible Item ID Bulk - [`Client::collectible_item_id_bulk`]
26//!   - Avatar Catalog Search - [`Client::avatar_catalog_search`]
27//! * Chat API
28//!   - Fetch Unread Conversation Count - [`Client::unread_conversation_count`]
29//! * Economy API
30//!   - Fetch Robux Balance - [`Client::robux`]
31//!   - Fetch Resellers - [`Client::resellers`]
32//!   - Fetch User Sales - [`Client::user_sales`]
33//!   - Put Limited On Sale - [`Client::put_limited_on_sale`]
34//!   - Take Limited Off Sale - [`Client::take_limited_off_sale`]
35//!   - Purchase Tradable Limited - [`Client::purchase_tradable_limited`]
36//! * Group API
37//!   - Fetch Group Roles - [`Client::group_roles`]
38//!   - Fetch Group Role Members - [`Client::group_role_members`]
39//!   - Set Group Member Role - [`Client::set_group_member_role`]
40//! * Presence API
41//!   - Register Presence - [`Client::register_presence`]
42//!   - Fetch Users Presence - [`Client::fetch_users_presence`]
43//! * Private Messages API
44//!   - Fetch Messages - [`Client::messages`]
45//! * Thumbnails API
46//!   - Fetch Thumbnail Url Bulk - [`Client::thumbnail_url_bulk`]
47//!   - Fetch Thumbnail Url - [`Client::thumbnail_url`]
48//! * Trades API
49//!   - Accept Trade - [`Client::accept_trade`]
50//!   - Decline Trade - [`Client::decline_trade`]
51//!   - Send Trade - [`Client::send_trade`]
52//!   - Fetch Trade Details - [`Client::trade_details`]
53//!   - Fetch Trades List - [`Client::trades`]
54//!   - Fetch Trade Count - [`Client::trade_count`]
55//! * Users API
56//!   - Fetch User ID - [`Client::user_id`]
57//!   - Fetch Username - [`Client::username`]
58//!   - Fetch Display Name - [`Client::display_name`]
59//!   - User Search - [`Client::user_search`]
60//!   - Username User Details - [`Client::username_user_details`]
61//!   - Fetch User Details - [`Client::user_details`]
62//! * Friends API
63//!   - Fetch Count of Pending Friend Requests - [`Client::pending_friend_requests`]
64//!   - Fetch Friend Requests - [`Client::friend_requests`]
65//!   - Fetch Friends List - [`Client::friends_list`]
66//!   - Accept Friend Request - [`Client::accept_friend_request`]
67//!   - Decline Friend Request - [`Client::decline_friend_request`]
68//!   - Send Friend Request - [`Client::send_friend_request`]
69//!   - Unfriend - [`Client::unfriend`]
70//! * Assetdelivery API
71//!   - Fetch Asset Data - [`Client::fetch_asset_data`]
72//! * IDE API (Animations)
73//!   - Upload New Animation - [`Client::upload_new_animation`]
74//!   - Get User Games - [`Client::user_games`]
75//!   - Get Group Games - [`Client::group_games`]
76//! * UNDER CONSTRUCTION
77//!   - Upload Classic Clothing To Group - [`Client::upload_classic_clothing_to_group`]
78//!
79//! # Quick Start Examples
80//!
81//! ## Example 1 - Purchase Free UGC Limited
82//! This code snippet allows you to purchase a free ugc limited.
83//!
84//! It can be modified to purchase a non-free ugc limited by changing the price.
85//!
86//! ```no_run
87//! // Replace this value with your own roblosecurity token.
88//! const ROBLOSECURITY: &str = "your-roblosecurity-token";
89//! // Replace this value with the item id of the item you want to purchase.
90//! const ITEM_ID: u64 = 13119979433;
91//! // Replace this value if you want to purchase a non-free item.
92//! const PRICE: u64 = 0;
93//!
94//! #[tokio::main]
95//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
96//!     let client = roboat::ClientBuilder::new()
97//!         .roblosecurity(ROBLOSECURITY.to_string())
98//!         .build();
99//!
100//!     let collectible_item_id = client.collectible_item_id(ITEM_ID).await?;
101//!
102//!     let collectible_product_id = client
103//!         .collectible_product_id(collectible_item_id.clone())
104//!         .await?;
105//!
106//!     let collectible_creator_id = client
107//!         .collectible_creator_id(collectible_item_id.clone())
108//!         .await?;
109//!
110//!     client
111//!         .purchase_non_tradable_limited(
112//!             collectible_item_id,
113//!             collectible_product_id,
114//!             collectible_creator_id,
115//!             PRICE,
116//!         )
117//!         .await?;
118//!
119//!     println!("Purchased item {} for {} robux!", ITEM_ID, PRICE);
120//!
121//!     Ok(())   
122//! }
123//! ```
124//!
125//! ## Example 2 - Fetch User Info
126//!
127//! This code snippet allows you to get your current robux, id, username, and display name.
128//!
129//! ```no_run
130//! // Replace this value with your own roblosecurity token.
131//! const ROBLOSECURITY: &str = "your-roblosecurity-token";
132//!
133//! #[tokio::main]
134//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
135//!     let client = roboat::ClientBuilder::new()
136//!         .roblosecurity(ROBLOSECURITY.to_string())
137//!         .build();
138//!
139//!     let robux = client.robux().await?;
140//!     let user_id = client.user_id().await?;
141//!     let username = client.username().await?;
142//!     let display_name = client.display_name().await?;    
143//!
144//!     println!("Robux: {}", robux);
145//!     println!("User ID: {}", user_id);
146//!     println!("Username: {}", username);
147//!     println!("Display Name: {}", display_name);
148//!
149//!     Ok(())   
150//! }
151//! ```
152//!
153//! ## Example 3 - Fetch Price of Tradable Limited
154//!
155//! This code snippet allows you to view the lowest price of a tradable limited item by
156//! fetching a list of reseller listings.
157//!
158//! ```no_run
159//! // Replace this value with your own roblosecurity token.
160//! const ROBLOSECURITY: &str = "your-roblosecurity-token";
161//!
162//! #[tokio::main]
163//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
164//!     let client = roboat::ClientBuilder::new()
165//!         .roblosecurity(ROBLOSECURITY.to_string())
166//!         .build();
167//!
168//!     let item_id = 1365767;
169//!     let limit = roboat::Limit::Ten;
170//!     let cursor = None;
171//!
172//!     let (resellers, _) = client.resellers(item_id, limit, cursor).await?;
173//!
174//!     println!("Lowest Price for Valkyrie Helm: {}", resellers[0].price);  
175//!
176//!     Ok(())   
177//! }
178//! ```
179//!
180//! ## Example 4 - Fetch Item Details
181//!
182//! This code snippet allows you to get the details of an item.
183//!
184//! ```no_run
185//! use roboat::catalog::{Item, ItemType};
186//!
187//! #[tokio::main]
188//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
189//!     let client = roboat::ClientBuilder::new().build();
190//!
191//!     let item = Item {
192//!         item_type: ItemType::Asset,
193//!         id: 1365767,
194//!     };
195//!
196//!     let details = &client.item_details(vec![item]).await?[0];
197//!
198//!     let name = &details.name;
199//!     let description = &details.description;
200//!     let creator_name = &details.creator_name;
201//!     let price = details.price.unwrap_or(0);
202//!
203//!     println!("Name: {}", name);
204//!     println!("Description: {}", description);
205//!     println!("Creator Name: {}", creator_name);
206//!     println!("Price: {}", price);
207//!
208//!     Ok(())   
209//! }
210//! ```
211
212#![warn(missing_docs)]
213
214// Re-export reqwest so people can use the correct version.
215pub use reqwest;
216use serde::{Deserialize, Serialize};
217
218pub use bedev2::PurchaseNonTradableLimitedError;
219pub use client::{Client, ClientBuilder};
220pub use economy::PurchaseTradableLimitedError;
221
222///
223/// A module for endpoints prefixed with <https://assetdelivery.roblox.com/*>
224pub mod assetdelivery;
225/// A module for endpoints prefixed with <https://auth.roblox.com/*>.
226mod auth;
227/// A module for endpoints prefixed with <https://apis.roblox.com/*>.
228pub mod bedev2;
229/// A module for endpoints prefixed with <https://catalog.roblox.com/*>.
230pub mod catalog;
231/// A module for endpoints prefixed with <https://chat.roblox.com/*>.
232mod chat;
233/// A module related to the [`Client`] struct.
234mod client;
235/// A module for endpoints prefixed with <https://economy.roblox.com/*>.
236pub mod economy;
237/// A module for endpoints prefixed with <https://friends.roblox.com/*>.
238pub mod friends;
239/// A module for endpoints prefixed with <https://groups.roblox.com/*>.
240pub mod groups;
241
242/// A module for endpoints prefixed with <https://games.roblox.com/*>
243pub mod games;
244
245/// A module for endpoints prefixed with <https://www.roblox.com/ide/*>
246// This is used for private APIs like ide/uploadnewanimation and ide/places/createV2
247pub mod ide;
248/// A module for endpoints prefixed with <https://presence.roblox.com/*>.
249pub mod presence;
250/// A module for endpoints prefixed with <https://privatemessages.roblox.com/*>.
251pub mod private_messages;
252/// A module for endpoints prefixed with <https://thumbnails.roblox.com/*>.
253pub mod thumbnails;
254/// A module for endpoints prefixed with <https://trades.roblox.com/*>.
255pub mod trades;
256/// A module for endpoints prefixed with <https://users.roblox.com/*>.
257pub mod users;
258/// A module related to validating requests.
259mod validation;
260// todo: figure out authtickets
261// todo: maybe respect cookies returned
262// todo: maybe add stronger types for stuff like cursors? stuff that can be returned basically and is unlikely to cbe created by the user.
263// todo: add doc example and example count somewhere
264// todo: the roblox api docs show the roblox error codes, maybe a custom sub error can be made
265// todo: add a "2 step not implemented for this endpoint" error
266
267// Used in request header keys.
268const XCSRF_HEADER: &str = "x-csrf-token";
269// The user agent used for fussy endpoints.
270const USER_AGENT: &str =
271    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0";
272// The content type used for fussy endpoints.
273const CONTENT_TYPE: &str = "application/json;charset=utf-8";
274
275/// The maximum amount of instances to return from an endpoint. Used as a parameter in various methods that call
276/// endpoints.
277///
278/// This is an enum instead of an integer as these are usually the only values that are accepted by Roblox
279/// for the limit parameter.
280///
281/// This is the most common limit used on Roblox endpoints. However, not all endpoints use this limit.
282/// Some alternative limits are as follows:
283/// * [`catalog::CatalogQueryLimit`]
284#[allow(missing_docs)]
285#[derive(
286    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, Copy,
287)]
288pub enum Limit {
289    #[default]
290    Ten,
291    TwentyFive,
292    Fifty,
293    Hundred,
294}
295
296impl Limit {
297    fn to_u64(self) -> u64 {
298        match self {
299            Limit::Ten => 10,
300            Limit::TwentyFive => 25,
301            Limit::Fifty => 50,
302            Limit::Hundred => 100,
303        }
304    }
305}
306
307/// The universal error used in this crate. Encapsulates any sub-errors used in this crate.
308#[non_exhaustive]
309#[derive(thiserror::Error, Debug, Default)]
310pub enum RoboatError {
311    /// Used when an endpoint returns status code 429.
312    #[default]
313    #[error("Too Many Requests")]
314    TooManyRequests,
315    /// Used when an endpoint returns status code 500.
316    #[error("Internal Server Error")]
317    InternalServerError,
318    /// Used when an endpoint returns status code 400 and does not embed an error.
319    /// This is used when the server cannot process the data sent, whether
320    /// it be because it is in the wrong format or it contains too much data.
321    #[error("Bad Request")]
322    BadRequest,
323    /// Returned when the user does not have a valid roblosecurity, or
324    /// does not have authorization to access the endpoint.
325    ///
326    /// This is also used as the backup error when an endpoint returns a 401 status code
327    /// but the error cannot be parsed from the response.
328    ///
329    /// Roblox error code 0.
330    #[error("Invalid Roblosecurity")]
331    InvalidRoblosecurity,
332    /// Returned when the endpoint returns a 401 status code, but the error response
333    /// contains an unknown Roblox error code.
334    #[error("Unknown Roblox Error Code {code}: {message}")]
335    UnknownRobloxErrorCode {
336        /// The error code (not status code) returned by roblox.
337        code: u16,
338        /// The error message returned by roblox.
339        message: String,
340    },
341    /// Used when no roblosecurity is set, on an endpoint that requires it.
342    #[error("Roblosecurity Not Set")]
343    RoblosecurityNotSet,
344    /// Used for any status codes that do not fit any enum variants of this error.
345    /// If you encounter this enum variant, please submit an issue so a variant can be
346    /// made or the crate can be fixed.
347    #[error("Unidentified Status Code {0}")]
348    UnidentifiedStatusCode(u16),
349    /// Used when the response from an API endpoint is malformed.
350    #[error("Malformed Response. If this occurs often it may be a bug. Please report it to the issues page."
351    )]
352    MalformedResponse,
353    /// Used when an endpoint rejects a request due to an invalid xcsrf.
354    /// Mostly used internally invalid xcsrf is returned due to the fact that rust does not
355    /// allow async recursion without making a type signature extremely messy.
356    #[error("Invalid Xcsrf. New Xcsrf Contained In Error.")]
357    InvalidXcsrf(String),
358    /// Used when an endpoint returns a 403 status code, doesn't need a challenge, but the response does not contain
359    /// a new xcsrf.
360    #[error("Missing Xcsrf")]
361    XcsrfNotReturned,
362    /// Used when an endpoint returns a 403 status code, but not because of an invalid xcsrf.
363    /// The string inside this error variant is a challenge id, which can be used to complete the challenge
364    /// (which can be either a captcha or a two step verification code).
365    #[error("Challenge Required. A captcha or two step authentication must be completed using challenge id {0}."
366    )]
367    ChallengeRequired(String),
368    /// Used when an endpoint returns a 403 status code, can be parsed into a roblox error,
369    /// but the error message is incorrect or the challenge id is not returned. This also means that no xcsrf was returned.
370    #[error("Unknown Status Code 403 Format. If this occurs often it may be a bug. Please report it to the issues page."
371    )]
372    UnknownStatus403Format,
373    /// Custom Roblox errors sometimes thrown when the user calls [`Client::purchase_tradable_limited`].
374    #[error("{0}")]
375    PurchaseTradableLimitedError(PurchaseTradableLimitedError),
376    /// Custom Roblox errors sometimes thrown when the user calls [`Client::purchase_non_tradable_limited`].
377    #[error("{0}")]
378    PurchaseNonTradableLimitedError(PurchaseNonTradableLimitedError),
379    /// Used for any reqwest error that occurs.
380    #[error("RequestError {0}")]
381    ReqwestError(reqwest::Error),
382    /// Used when an io error occurs.
383    #[error("IoError {0}")]
384    IoError(#[from] std::io::Error),
385    /// Used when a file system path passed to a method is invalid.
386    #[error("Invalid Path {0}")]
387    InvalidPath(String),
388}
389
390/// The type of the challenge required to complete a request.
391/// This can be either a captcha or a two step verification code (can be an authenticator or an email).
392#[non_exhaustive]
393#[allow(missing_docs)]
394#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
395pub enum ChallengeType {
396    #[default]
397    TwoStep,
398}
399
400impl TryFrom<String> for ChallengeType {
401    type Error = RoboatError;
402
403    fn try_from(raw: String) -> Result<Self, Self::Error> {
404        match raw.as_str() {
405            "twostepverification" => Ok(ChallengeType::TwoStep),
406            _ => Err(RoboatError::MalformedResponse),
407        }
408    }
409}
410
411/// The challenge info returned by Roblox when a challenge is required to complete a request.
412/// This challenge can be either a two step verification code or a captcha. This is specified by the `challenge_type` field.
413#[non_exhaustive]
414#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
415pub struct ChallengeInfo {
416    /// The string in the returned `rblx-challenge-id` header.
417    pub challenge_id: String,
418    /// The string in the returned `rblx-challenge-metadata` header.
419    ///
420    /// This is encoded in base64 and can be decoded using the [`base64`] crate.
421    pub challenge_metadata: String,
422    /// The type of challenge parsed from the `rblx-challenge-type` header.
423    pub challenge_type: ChallengeType,
424}
425
426/// The universal struct for a Roblox user in this crate.
427#[allow(missing_docs)]
428#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
429pub struct User {
430    pub user_id: u64,
431    pub username: String,
432    pub display_name: String,
433}