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