Skip to main content

roblox_api/api/avatar/
v1.rs

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    // AssetTypeId, but repr lol
59    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, // TODO: change to enum
136    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,       // TDOO: change to Enum
146    pub avatar_collision_type: String,  // TDOO: change to Enum
147    pub joint_positioning_type: String, // TDOO: change to Enum
148
149    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, // TODO: cast to bool
157}
158
159/// Returns details about a specified user's avatar
160pub 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
175/// Gets a list of asset ids that the user is currently wearing
176pub 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
198/// Sets the avatar's current assets to the list - Flagged as obsolete, does not support layered clothing meta params.
199///
200/// Warning: Deprecated
201/// Only allows items that you own, are not expired, and are wearable asset types.
202/// Any assets being worn before this method is called are automatically removed.
203pub 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
233/// Sets the authenticated user's player avatar type (e.g. R6 or R15).
234pub 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
261/// Sets the authenticated user's body colors.
262pub 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
286/// Sets the authenticated user's body colors.
287pub 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
308/// Deprecated, user v2. Gets a list of outfits for the specified user.
309pub async fn user_outfits(
310    client: &mut Client,
311    id: u64,
312    paging: Paging<'_>,
313    is_editable: Option<bool>,
314    //outfit_type: OutfitType, all seem to be null
315) -> 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
341/// Gets details about the contents of an outfit.
342pub 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
357/// Deletes the outfit.
358pub 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
379/// The server will call this on game server start to request general information about the universe.
380/// This is version 1.1, which returns an entry from the UniverseAvatarType enum.
381/// During mixed mode this may return unreliable results
382pub 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}