roblox_api/
lib.rs

1pub mod api;
2pub mod challenge;
3pub mod client;
4pub mod ratelimit;
5pub mod validation;
6
7use challenge::Challenge;
8use chrono::{Datelike, TimeZone, Utc};
9use serde::{Deserialize, Serialize};
10use strum::{EnumIter, IntoEnumIterator};
11
12// TODO: using this wrapper as I couldn't figure out how to use chronos datetime alone
13#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
14pub struct DateTime(String);
15impl DateTime {
16    pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
17        Self(
18            Utc.with_ymd_and_hms(year, month as u32, day as u32, 0, 0, 0)
19                .unwrap()
20                .to_rfc3339(),
21        )
22    }
23
24    pub fn day(&self) -> u8 {
25        chrono::DateTime::parse_from_rfc3339(&self.0).unwrap().day() as u8
26    }
27
28    pub fn month(&self) -> u8 {
29        chrono::DateTime::parse_from_rfc3339(&self.0)
30            .unwrap()
31            .month() as u8
32    }
33
34    pub fn year(&self) -> i32 {
35        chrono::DateTime::parse_from_rfc3339(&self.0)
36            .unwrap()
37            .year()
38    }
39}
40
41#[derive(Debug)]
42pub enum Error {
43    ApiError(ApiError),
44    BadJson,
45    IoError(std::io::Error),
46    ReqwestError(reqwest::Error),
47    #[cfg(feature = "web-socket")]
48    ReqwestWebSocketError(reqwest_websocket::Error),
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub enum ApiError {
53    Internal,
54    BadRequest,
55    RequestMissingArgument(String),
56    Ratelimited,
57    Unknown(u16, Option<String>),
58    Unauthorized,
59    InvalidBirthdate,
60    InvalidDisplayName,
61    InvalidGender,
62    InvalidUser,
63    InvalidUserId,
64    PinIsLocked,
65    TokenValidation,
66    CaptchaFailed,
67    ChallengeRequired(Challenge),
68    ChallengeFailed,
69    InvalidChallengeId,
70    InvalidTwoStepVerificationCode,
71    TwoStepVerificationMaintenance,
72    Multiple(Vec<ApiError>),
73    PermissionError,
74    AccontLocked,
75    AccountIssue,
76    InvalidCredentials,
77    UnverifiedCredentials,
78    ExistingLoginSession,
79    DefaultLoginRequired,
80    VNGAppLoginRequired,
81    LuoBuAppLoginRequired,
82    SocialNetworkLoginRequired,
83    InvalidAssetId,
84    InvalidBrowserTrackerId,
85    AlreadyInGroup,
86    AlreadyInGroupRequests,
87    UnsupportedSortOrder,
88    InvalidBadge,
89    ConversationCreationFailed,
90    InvalidConversation,
91    ConversationUserAddFailed,
92}
93
94#[repr(u8)]
95#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, EnumIter)]
96pub enum AssetTypeId {
97    Image = 1,
98    TShirt,
99    Audio,
100    Mesh,
101    Lua,
102    Hat = 8,
103    Place,
104    Model,
105    Shirt,
106    Pants,
107    Decal,
108    Head = 17,
109    Face,
110    Gear,
111    Badge = 21,
112    Animation = 24,
113    Torso = 27,
114    RightArm,
115    LeftArm,
116    LeftLeg,
117    RightLeg,
118    Package,
119    YouTubeVideo,
120    Gamepass,
121    Plugin = 38,
122    MeshPart = 40,
123    HairAccessory,
124    FaceAccessory,
125    NeckAccessory,
126    ShoulderAccessory,
127    FrontAccessory,
128    BackAccessory,
129    WaistAccessory,
130    ClimbAnimation,
131    DeathAnimation,
132    FallAnimation,
133    IdleAnimation,
134    JumpAnimation,
135    RunAnimation,
136    SwimAnimation,
137    WalkAnimation,
138    PoseAnimation,
139    EarAccessory,
140    EyeAccessory,
141    EmoteAnimation = 61,
142    Video,
143    TShirtAccessory = 64,
144    ShirtAccessory,
145    PantsAccessory,
146    JacketAccessory,
147    SweaterAccessory,
148    ShortsAccessory,
149    LeftShoeAccessory,
150    RightShoeAccessory,
151    DressSkirtAccessory,
152    FontFamily,
153    EyebrowAccessory = 76,
154    EyelashAccessory,
155    MoodAnimation,
156    DynamicHead,
157}
158
159#[repr(u8)]
160#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
161pub enum Currency {
162    #[default]
163    Robux = 1,
164    Tickets,
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub struct Paging<'a> {
169    pub cursor: Option<&'a str>,
170    pub limit: Option<u16>,
171    pub order: Option<SortOrder>,
172}
173
174#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq, Eq)]
175pub enum SortOrder {
176    #[default]
177    #[serde(rename = "Asc")]
178    Ascending,
179    #[serde(rename = "Desc")]
180    Descending,
181}
182
183impl From<serde_json::Error> for Error {
184    fn from(_error: serde_json::Error) -> Self {
185        Error::BadJson
186    }
187}
188
189impl From<reqwest::Error> for Error {
190    fn from(error: reqwest::Error) -> Self {
191        Error::ReqwestError(error)
192    }
193}
194
195#[cfg(feature = "web-socket")]
196impl From<reqwest_websocket::Error> for Error {
197    fn from(error: reqwest_websocket::Error) -> Self {
198        Error::ReqwestWebSocketError(error)
199    }
200}
201
202impl std::fmt::Display for DateTime {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "{}", self.0)
205    }
206}
207
208impl std::fmt::Display for AssetTypeId {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(f, "{:?}", self)
211    }
212}
213
214impl TryFrom<&str> for AssetTypeId {
215    type Error = &'static str;
216
217    fn try_from(value: &str) -> Result<Self, Self::Error> {
218        for kind in AssetTypeId::iter() {
219            if kind.to_string().as_str() == value {
220                return Ok(kind);
221            }
222        }
223
224        Err("Failed to convert string to AssetTypeId")
225    }
226}
227
228impl TryFrom<u8> for AssetTypeId {
229    type Error = &'static str;
230
231    fn try_from(value: u8) -> Result<Self, Self::Error> {
232        for kind in AssetTypeId::iter() {
233            if kind as u8 == value {
234                return Ok(kind);
235            }
236        }
237
238        Err("Failed to convert u8 to AssetTypeId")
239    }
240}
241
242impl std::fmt::Display for SortOrder {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        write!(
245            f,
246            "{}",
247            match self {
248                SortOrder::Ascending => "Asc",
249                SortOrder::Descending => "Desc",
250            }
251        )
252    }
253}
254
255impl Default for Paging<'_> {
256    fn default() -> Self {
257        Self {
258            cursor: None,
259            limit: Some(10),
260            order: Some(SortOrder::Ascending),
261        }
262    }
263}
264
265impl<'a> Paging<'a> {
266    pub fn new(
267        cursor: Option<&'a str>,
268        limit: Option<u16>,
269        order: Option<SortOrder>,
270    ) -> Paging<'a> {
271        Self {
272            cursor,
273            limit,
274            order,
275        }
276    }
277}