1use crate::{DateTime, Error, Paging, client::Client};
2use serde::{Deserialize, Serialize};
3
4pub const URL: &str = "https://users.roblox.com/v1";
5
6#[repr(u8)]
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Gender {
9 None = 1,
10 Male = 2,
11 Female = 3,
12}
13
14impl Gender {
15 fn from_u8(value: u8) -> Self {
16 match value {
17 2 => Self::Male,
18 3 => Self::Female,
19 _ => Self::None,
20 }
21 }
22}
23
24#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
25pub struct ClientDetails {
26 pub id: u64,
27 pub name: String,
28 #[serde(rename = "displayName")]
29 pub display_name: String,
30}
31
32#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
33pub struct UserDetails {
34 pub id: u64,
35 pub name: String,
36 #[serde(rename = "displayName")]
37 pub display_name: String,
38 pub description: String,
39 #[serde(rename = "created")]
40 pub created_date: DateTime,
41 #[serde(rename = "isBanned")]
42 pub is_terminated: bool,
43 #[serde(rename = "hasVerifiedBadge")]
44 pub is_verified: bool,
45}
46
47#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
48pub struct UserById {
49 pub id: u64,
50 pub name: String,
51 #[serde(rename = "displayName")]
52 pub display_name: String,
53 #[serde(rename = "hasVerifiedBadge")]
54 pub is_verified: bool,
55}
56
57#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
58pub struct UserByName {
59 pub id: u64,
60 pub name: String,
61 #[serde(rename = "requestedUsername")]
62 pub requested_name: String,
63 #[serde(rename = "displayName")]
64 pub display_name: String,
65 #[serde(rename = "hasVerifiedBadge")]
66 pub is_verified: bool,
67}
68
69#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
70pub struct UserByKeyword {
71 pub id: u64,
72 pub name: String,
73 #[serde(rename = "displayName")]
74 pub display_name: String,
75 #[serde(rename = "previousUsernames")]
76 pub previous_names: Vec<String>,
77 #[serde(rename = "hasVerifiedBadge")]
78 pub is_verified: bool,
79}
80
81#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
82pub struct NameHistory {
83 pub names: Vec<String>,
84 pub next_cursor: Option<String>,
85 pub previous_cursor: Option<String>,
86}
87
88#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
89pub struct UserSearchByKeyword {
90 #[serde(rename = "data")]
91 pub users: Vec<UserByKeyword>,
92 #[serde(rename = "nextPageCursor")]
93 pub next_cursor: Option<String>,
94 #[serde(rename = "previousPageCursor")]
95 pub previous_cursor: Option<String>,
96}
97
98pub async fn user_details(client: &mut Client, id: u64) -> Result<UserDetails, Error> {
99 let result = client
100 .requestor
101 .client
102 .get(format!("{URL}/users/{id}"))
103 .headers(client.requestor.default_headers.clone())
104 .send()
105 .await;
106
107 let response = client.validate_response(result).await?;
108 client.requestor.parse_json::<UserDetails>(response).await
109}
110
111pub async fn user_username_history(
112 client: &mut Client,
113 id: u64,
114 paging: Paging<'_>,
115) -> Result<NameHistory, Error> {
116 let limit = paging.limit.unwrap_or(10);
117 let sort_order = paging.order.unwrap_or_default().to_string();
118 let cursor = match paging.cursor {
119 Some(cursor) => format!("&cursor={cursor}"),
120 None => String::new(),
121 };
122
123 let result = client
124 .requestor
125 .client
126 .get(format!(
127 "{URL}/users/{id}/username-history?limit={limit}&sortOrder={sort_order}{cursor}"
128 ))
129 .headers(client.requestor.default_headers.clone())
130 .send()
131 .await;
132
133 #[derive(Debug, Deserialize)]
135 struct Username {
136 name: String,
137 }
138
139 #[derive(Debug, Deserialize)]
140 struct Response {
141 #[serde(rename = "data")]
142 names: Vec<Username>,
143 #[serde(rename = "nextPageCursor")]
144 next_cursor: Option<String>,
145 #[serde(rename = "previousPageCursor")]
146 previous_cursor: Option<String>,
147 }
148
149 let response = client.validate_response(result).await?;
150 let result = client.requestor.parse_json::<Response>(response).await?;
151
152 let names = result.names.iter().map(|x| x.name.clone()).collect();
153 Ok(NameHistory {
154 names,
155 next_cursor: result.next_cursor,
156 previous_cursor: result.previous_cursor,
157 })
158}
159
160pub async fn users_by_id(
161 client: &mut Client,
162 ids: &[u64],
163 exclude_terminated: bool,
164) -> Result<Vec<UserById>, Error> {
165 #[derive(Debug, Serialize)]
166 struct Request<'a> {
167 #[serde(rename = "userIds")]
168 ids: &'a [u64],
169 #[serde(rename = "excludeBannedUsers")]
170 exclude_terminated: bool,
171 }
172
173 let result = client
174 .requestor
175 .client
176 .post(format!("{URL}/users"))
177 .headers(client.requestor.default_headers.clone())
178 .json(&Request {
179 ids,
180 exclude_terminated,
181 })
182 .send()
183 .await;
184
185 #[derive(Debug, Deserialize)]
186 struct Response {
187 #[serde(rename = "data")]
188 users: Vec<UserById>,
189 }
190
191 let response = client.validate_response(result).await?;
192 Ok(client
193 .requestor
194 .parse_json::<Response>(response)
195 .await?
196 .users)
197}
198
199pub async fn users_by_name(
200 client: &mut Client,
201 names: &[&str],
202 exclude_terminated: bool,
203) -> Result<Vec<UserByName>, Error> {
204 #[derive(Debug, Serialize)]
205 struct Request<'a> {
206 #[serde(rename = "usernames")]
207 names: &'a [&'a str],
208 #[serde(rename = "excludeBannedUsers")]
209 exclude_terminated: bool,
210 }
211
212 let result = client
213 .requestor
214 .client
215 .post(format!("{URL}/usernames/users"))
216 .headers(client.requestor.default_headers.clone())
217 .json(&Request {
218 names,
219 exclude_terminated,
220 })
221 .send()
222 .await;
223
224 #[derive(Debug, Deserialize)]
225 struct Response {
226 #[serde(rename = "data")]
227 names: Vec<UserByName>,
228 }
229
230 let response = client.validate_response(result).await?;
231 Ok(client
232 .requestor
233 .parse_json::<Response>(response)
234 .await?
235 .names)
236}
237
238pub async fn search_by_keyword(
240 client: &mut Client,
241 keyword: &str,
242 session_id: Option<&str>,
243 paging: Paging<'_>,
244) -> Result<UserSearchByKeyword, Error> {
245 let limit = paging.limit.unwrap_or(10);
246 let cursor = match paging.cursor {
247 Some(cursor) => format!("&cursor={cursor}"),
248 None => String::new(),
249 };
250
251 let session_id = match session_id {
252 Some(session_id) => format!("&session_id={session_id}"),
253 None => String::new(),
254 };
255
256 let result = client
257 .requestor
258 .client
259 .get(format!(
260 "{URL}/users/search?keyword={keyword}&limit={limit}{cursor}{session_id}"
261 ))
262 .headers(client.requestor.default_headers.clone())
263 .send()
264 .await;
265
266 let response = client.validate_response(result).await?;
267 client
268 .requestor
269 .parse_json::<UserSearchByKeyword>(response)
270 .await
271}
272
273pub async fn authenticated_details(client: &mut Client) -> Result<ClientDetails, Error> {
274 let result = client
275 .requestor
276 .client
277 .get(format!("{URL}/users/authenticated"))
278 .headers(client.requestor.default_headers.clone())
279 .send()
280 .await;
281
282 let response = client.validate_response(result).await?;
283 client.requestor.parse_json::<ClientDetails>(response).await
284}
285
286pub async fn authenticated_age_bracket(client: &mut Client) -> Result<u64, Error> {
287 let result = client
288 .requestor
289 .client
290 .get(format!("{URL}/users/authenticated/age-bracket"))
291 .headers(client.requestor.default_headers.clone())
292 .send()
293 .await;
294
295 #[derive(Debug, Deserialize)]
296 struct Response {
297 #[serde(rename = "ageBracket")]
298 age_bracket: u64,
299 }
300
301 let response = client.validate_response(result).await?;
302 Ok(client
303 .requestor
304 .parse_json::<Response>(response)
305 .await?
306 .age_bracket)
307}
308
309pub async fn authenticated_country_code(client: &mut Client) -> Result<String, Error> {
310 let result = client
311 .requestor
312 .client
313 .get(format!("{URL}/users/authenticated/country-code"))
314 .headers(client.requestor.default_headers.clone())
315 .send()
316 .await;
317
318 #[derive(Debug, Deserialize)]
319 struct Response {
320 #[serde(rename = "countryCode")]
321 country_code: String,
322 }
323
324 let response = client.validate_response(result).await?;
325 Ok(client
326 .requestor
327 .parse_json::<Response>(response)
328 .await?
329 .country_code)
330}
331
332pub async fn authenticated_roles(client: &mut Client) -> Result<Vec<String>, Error> {
333 let result = client
334 .requestor
335 .client
336 .get(format!("{URL}/users/authenticated/roles"))
337 .headers(client.requestor.default_headers.clone())
338 .send()
339 .await;
340
341 #[derive(Debug, Deserialize)]
342 struct Response {
343 roles: Vec<String>,
344 }
345
346 let response = client.validate_response(result).await?;
347 Ok(client
348 .requestor
349 .parse_json::<Response>(response)
350 .await?
351 .roles)
352}
353
354pub async fn birthdate(client: &mut Client) -> Result<DateTime, Error> {
355 let result = client
356 .requestor
357 .client
358 .get(format!("{URL}/birthdate"))
359 .headers(client.requestor.default_headers.clone())
360 .send()
361 .await;
362
363 #[derive(Debug, Deserialize)]
364 struct Response {
365 #[serde(rename = "birthDay")]
366 day: u8,
367 #[serde(rename = "birthMonth")]
368 month: u8,
369 #[serde(rename = "birthYear")]
370 year: i32,
371 }
372
373 let response = client.validate_response(result).await?;
374 let birthdate = client.requestor.parse_json::<Response>(response).await?;
375
376 Ok(DateTime::from_ymd(
377 birthdate.year,
378 birthdate.month,
379 birthdate.day,
380 ))
381}
382
383pub async fn set_birthdate(
386 client: &mut Client,
387 birthdate: DateTime,
388 ) -> Result<(), Error> {
390 #[derive(Debug, Serialize)]
391 struct Request {
392 #[serde(rename = "birthDay")]
393 day: u8,
394 #[serde(rename = "birthMonth")]
395 month: u8,
396 #[serde(rename = "birthYear")]
397 year: i32,
398 }
400
401 let result = client
402 .requestor
403 .client
404 .post(format!("{URL}/birthdate"))
405 .headers(client.requestor.default_headers.clone())
406 .json(&Request {
407 day: birthdate.day(),
408 month: birthdate.month(),
409 year: birthdate.year(),
410 })
412 .send()
413 .await;
414
415 client.validate_response(result).await?;
416 Ok(())
417}
418
419pub async fn description(client: &mut Client) -> Result<String, Error> {
420 let result = client
421 .requestor
422 .client
423 .get(format!("{URL}/description"))
424 .headers(client.requestor.default_headers.clone())
425 .send()
426 .await;
427
428 #[derive(Debug, Deserialize)]
429 struct Response {
430 #[serde(rename = "description")]
431 value: String,
432 }
433
434 let response = client.validate_response(result).await?;
435 let description = client.requestor.parse_json::<Response>(response).await?;
436
437 Ok(description.value)
438}
439
440pub async fn set_description(client: &mut Client, description: &str) -> Result<(), Error> {
441 #[derive(Debug, Serialize)]
442 struct Request<'a> {
443 #[serde(rename = "description")]
444 value: &'a str,
445 }
446
447 let result = client
448 .requestor
449 .client
450 .post(format!("{URL}/description"))
451 .headers(client.requestor.default_headers.clone())
452 .json(&Request { value: description })
453 .send()
454 .await;
455
456 client.validate_response(result).await?;
457 Ok(())
458}
459
460pub async fn gender(client: &mut Client) -> Result<Gender, Error> {
461 let result = client
462 .requestor
463 .client
464 .get(format!("{URL}/gender"))
465 .headers(client.requestor.default_headers.clone())
466 .send()
467 .await;
468
469 #[derive(Debug, Deserialize)]
470 struct Response {
471 #[serde(rename = "gender")]
472 value: u8,
473 }
474
475 let response = client.validate_response(result).await?;
476 let gender: Response = client.requestor.parse_json(response).await?;
477
478 Ok(Gender::from_u8(gender.value))
479}
480
481pub async fn set_gender(client: &mut Client, gender: Gender) -> Result<(), Error> {
482 #[derive(Debug, Serialize)]
483 struct Request {
484 #[serde(rename = "gender")]
485 value: u8,
486 }
487
488 let result = client
489 .requestor
490 .client
491 .post(format!("{URL}/gender"))
492 .headers(client.requestor.default_headers.clone())
493 .json(&Request {
494 value: gender as u8,
495 })
496 .send()
497 .await;
498
499 client.validate_response(result).await?;
500 Ok(())
501}
502
503pub async fn validate_display_name(
504 client: &mut Client,
505 display_name: &str,
506 birthdate: DateTime,
507) -> Result<(), Error> {
508 let result = client
509 .requestor
510 .client
511 .get(format!(
512 "{URL}/display-names/validate?displayName={display_name}&birthdate={}",
513 birthdate.to_string()
514 ))
515 .headers(client.requestor.default_headers.clone())
516 .send()
517 .await;
518
519 client.validate_response(result).await?;
520 Ok(())
521}
522
523pub async fn validate_display_name_by_id(
527 client: &mut Client,
528 id: u64,
529 display_name: &str,
530) -> Result<(), Error> {
531 let result = client
532 .requestor
533 .client
534 .get(format!(
535 "{URL}/users/{id}/display-names/validate?displayName={display_name}"
536 ))
537 .headers(client.requestor.default_headers.clone())
538 .send()
539 .await;
540
541 client.validate_response(result).await?;
542 Ok(())
543}
544
545pub async fn set_display_name(
549 client: &mut Client,
550 id: u64,
551 display_name: &str,
552) -> Result<(), Error> {
553 #[derive(Debug, Serialize)]
554 struct Request<'a> {
555 #[serde(rename = "newDisplayName")]
556 display_name: &'a str,
557 }
558
559 let result = client
560 .requestor
561 .client
562 .patch(format!(
563 "{URL}/users/{id}/display-names?displayName={display_name}"
564 ))
565 .headers(client.requestor.default_headers.clone())
566 .json(&Request { display_name })
567 .send()
568 .await;
569
570 client.validate_response(result).await?;
571 Ok(())
572}