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}