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#[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}