Skip to main content

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