1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3use strum_macros::{Display, EnumString, FromRepr};
4
5use crate::{Error, Paging, client::Client};
6
7pub const URL: &str = "https://avatar.roblox.com/v1";
8
9pub type ColorId = u8;
10
11#[repr(u8)]
12#[derive(
13 Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString, FromRepr,
14)]
15pub enum AvatarType {
16 R6 = 1,
17 R15 = 2,
18}
19
20#[repr(u8)]
21#[derive(
22 Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString, FromRepr,
23)]
24pub enum MorphAvatarType {
25 MorphR6 = 1,
26 MorphR15 = 2,
27}
28
29#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
30#[serde(rename_all = "camelCase")]
31pub struct AvatarScales {
32 pub height: f32,
33 pub width: f32,
34 pub head: f32,
35 pub depth: f32,
36 pub proportion: f32,
37 pub body_type: f32,
38}
39
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
41pub struct BodyColors {
42 #[serde(rename = "headColorId")]
43 pub head: ColorId,
44 #[serde(rename = "torsoColorId")]
45 pub torso: ColorId,
46 #[serde(rename = "rightArmColorId")]
47 pub right_arm: ColorId,
48 #[serde(rename = "leftArmColorId")]
49 pub left_arm: ColorId,
50 #[serde(rename = "rightLegColorId")]
51 pub right_leg: ColorId,
52 #[serde(rename = "leftLegColorId")]
53 pub left_leg: ColorId,
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
57pub struct AssetType {
58 pub id: u8,
60 pub name: String,
61}
62
63#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
64pub struct AssetMeta {
65 pub version: u8,
66
67 pub order: u16,
68 pub puffiness: Option<f32>,
69}
70
71#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
72#[serde(rename_all = "camelCase")]
73pub struct Asset {
74 pub id: u64,
75 pub name: String,
76 #[serde(rename = "assetType")]
77 pub kind: AssetType,
78 pub current_version_id: u64,
79 pub meta: Option<AssetMeta>,
80}
81
82#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
83#[serde(rename_all = "camelCase")]
84pub struct Emote {
85 #[serde(rename = "assetId")]
86 pub id: u64,
87 #[serde(rename = "assetName")]
88 pub name: String,
89 pub position: u8,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
93#[serde(rename_all = "camelCase")]
94pub struct Outfit {
95 pub id: u64,
96 pub name: String,
97 pub is_editable: bool,
98 pub outfit_type: Option<()>,
99}
100
101#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
102#[serde(rename_all = "camelCase")]
103pub struct AvatarResponse {
104 #[serde(rename = "playerAvatarType")]
105 pub kind: AvatarType,
106 pub assets: Vec<Asset>,
107 pub scales: AvatarScales,
108 pub body_colors: BodyColors,
109 pub default_pants_applied: bool,
110 pub default_shirt_applied: bool,
111 pub emotes: Vec<Emote>,
112}
113
114#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
115#[serde(rename_all = "camelCase")]
116pub struct OutfitsResponse {
117 #[serde(rename = "data")]
118 pub outfits: Vec<Outfit>,
119 pub total: u64,
120 pub filtered_count: u64,
121}
122
123#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
124#[serde(rename_all = "camelCase")]
125pub struct OutfitDetails {
126 pub id: u64,
127 pub name: String,
128 pub universe_id: u64,
129 pub assets: Vec<Asset>,
130 pub body_colors: BodyColors,
131 #[serde(rename = "scale")]
132 pub scales: AvatarScales,
133 #[serde(rename = "playerAvatarType")]
134 pub avatar_type: AvatarType,
135 pub outfit_type: String, pub is_editable: bool,
137 pub moderation_status: Option<String>,
138}
139
140#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
141#[serde(rename_all = "camelCase")]
142pub struct UniverseAvatarSettings {
143 #[serde(rename = "gameAvatarType")]
144 pub avatar_type: MorphAvatarType,
145 pub avatar_body_type: String, pub avatar_collision_type: String, pub joint_positioning_type: String, pub avatar_min_scales: AvatarScales,
150 pub avatar_max_scales: AvatarScales,
151 pub avatar_asset_overrides: Vec<Option<()>>,
152
153 pub message: String,
154 pub moderation_status: Option<String>,
155
156 pub allow_custom_animations: String, }
158
159pub async fn user_avatar(client: &mut Client, id: u64) -> Result<AvatarResponse, Error> {
161 client
162 .requestor
163 .request::<()>(
164 Method::GET,
165 &format!("{URL}/users/{id}/avatar"),
166 None,
167 None,
168 None,
169 )
170 .await?
171 .json::<AvatarResponse>()
172 .await
173}
174
175pub async fn user_currently_wearing(client: &mut Client, id: u64) -> Result<Vec<u64>, Error> {
177 #[derive(Deserialize)]
178 struct Response {
179 #[serde(rename = "assetIds")]
180 ids: Vec<u64>,
181 }
182
183 Ok(client
184 .requestor
185 .request::<()>(
186 Method::GET,
187 &format!("{URL}/users/{id}/currently-wearing"),
188 None,
189 None,
190 None,
191 )
192 .await?
193 .json::<Response>()
194 .await?
195 .ids)
196}
197
198pub async fn avatar_set_wearing_assets(
204 client: &mut Client,
205 assets: Vec<u64>,
206) -> Result<bool, Error> {
207 #[derive(Serialize)]
208 #[serde(rename_all = "camelCase")]
209 struct Request<'a> {
210 asset_ids: &'a [u64],
211 }
212
213 #[derive(Deserialize)]
214 pub struct Response {
215 pub success: bool,
216 }
217
218 Ok(client
219 .requestor
220 .request::<Request>(
221 Method::POST,
222 &format!("{URL}/avatar/set-wearing-assets"),
223 Some(&Request { asset_ids: &assets }),
224 None,
225 None,
226 )
227 .await?
228 .json::<Response>()
229 .await?
230 .success)
231}
232
233pub async fn avatar_set_type(client: &mut Client, kind: AvatarType) -> Result<bool, Error> {
235 #[derive(Serialize)]
236 struct Request {
237 #[serde(rename = "playerAvatarType")]
238 avatar_type: AvatarType,
239 }
240
241 #[derive(Deserialize)]
242 struct Response {
243 success: bool,
244 }
245
246 Ok(client
247 .requestor
248 .request::<Request>(
249 Method::POST,
250 &format!("{URL}/avatar/set-player-avatar-type"),
251 Some(&Request { avatar_type: kind }),
252 None,
253 None,
254 )
255 .await?
256 .json::<Response>()
257 .await?
258 .success)
259}
260
261pub async fn avatar_set_body_colors(
263 client: &mut Client,
264 colors: BodyColors,
265) -> Result<bool, Error> {
266 #[derive(Deserialize)]
267 struct Response {
268 success: bool,
269 }
270
271 Ok(client
272 .requestor
273 .request::<BodyColors>(
274 Method::POST,
275 &format!("{URL}/avatar/set-body-colors"),
276 Some(&colors),
277 None,
278 None,
279 )
280 .await?
281 .json::<Response>()
282 .await?
283 .success)
284}
285
286pub async fn avatar_set_scales(client: &mut Client, scales: AvatarScales) -> Result<bool, Error> {
288 #[derive(Deserialize)]
289 struct Response {
290 success: bool,
291 }
292
293 Ok(client
294 .requestor
295 .request::<AvatarScales>(
296 Method::POST,
297 &format!("{URL}/avatar/set-scales"),
298 Some(&scales),
299 None,
300 None,
301 )
302 .await?
303 .json::<Response>()
304 .await?
305 .success)
306}
307
308pub async fn user_outfits(
310 client: &mut Client,
311 id: u64,
312 paging: Paging<'_>,
313 is_editable: Option<bool>,
314 ) -> Result<OutfitsResponse, Error> {
316 let limit = paging.limit.unwrap_or(25).to_string();
317 let cursor = paging.cursor.unwrap_or("1");
318 let is_editable = match is_editable {
319 Some(editable) => editable.to_string(),
320 None => "".to_string(),
321 };
322
323 client
324 .requestor
325 .request::<()>(
326 Method::GET,
327 &format!("{URL}/users/{id}/outfits"),
328 None,
329 Some(&[
330 ("page", cursor),
331 ("itemsPerPage", &limit),
332 ("isEditable", &is_editable),
333 ]),
334 None,
335 )
336 .await?
337 .json::<OutfitsResponse>()
338 .await
339}
340
341pub async fn outfit_details(client: &mut Client, id: u64) -> Result<OutfitDetails, Error> {
343 client
344 .requestor
345 .request::<()>(
346 Method::GET,
347 &format!("{URL}/outfits/{id}/details"),
348 None,
349 None,
350 None,
351 )
352 .await?
353 .json::<OutfitDetails>()
354 .await
355}
356
357pub async fn remove_outfit(client: &mut Client, id: u64) -> Result<bool, Error> {
359 #[derive(Deserialize)]
360 struct Response {
361 success: bool,
362 }
363
364 Ok(client
365 .requestor
366 .request::<()>(
367 Method::POST,
368 &format!("{URL}/outfits/{id}/delete"),
369 None,
370 None,
371 None,
372 )
373 .await?
374 .json::<Response>()
375 .await?
376 .success)
377}
378
379pub async fn universe_avatar_settings(
383 client: &mut Client,
384 id: u64,
385) -> Result<UniverseAvatarSettings, Error> {
386 client
387 .requestor
388 .request::<()>(
389 Method::GET,
390 &format!("{URL}/users/{id}/avatar"),
391 None,
392 None,
393 None,
394 )
395 .await?
396 .json::<UniverseAvatarSettings>()
397 .await
398}