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}