roblox_api/api/avatar/
v1.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize, de::DeserializeOwned};
3
4use crate::{Error, Paging, client::Client};
5
6pub const URL: &str = "https://avatar.roblox.com/v1";
7
8pub type ColorId = u8;
9
10#[repr(u8)]
11#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
12pub enum AvatarType {
13    R6 = 1,
14    R15 = 2,
15}
16
17#[repr(u8)]
18#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
19pub enum MorphAvatarType {
20    MorphR6 = 1,
21    MorphR15 = 2,
22}
23
24#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
25#[serde(rename_all = "camelCase")]
26pub struct AvatarScales {
27    pub height: f32,
28    pub width: f32,
29    pub head: f32,
30    pub depth: f32,
31    pub proportion: f32,
32    pub body_type: f32,
33}
34
35#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
36pub struct BodyColors {
37    #[serde(rename = "headColorId")]
38    pub head: ColorId,
39    #[serde(rename = "torsoColorId")]
40    pub torso: ColorId,
41    #[serde(rename = "rightArmColorId")]
42    pub right_arm: ColorId,
43    #[serde(rename = "leftArmColorId")]
44    pub left_arm: ColorId,
45    #[serde(rename = "rightLegColorId")]
46    pub right_leg: ColorId,
47    #[serde(rename = "leftLegColorId")]
48    pub left_leg: ColorId,
49}
50
51#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
52pub struct AssetType {
53    // AssetTypeId, but repr lol
54    pub id: u8,
55    pub name: String,
56}
57
58#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
59pub struct AssetMeta {
60    pub version: u8,
61
62    pub order: u16,
63    pub puffiness: f32,
64}
65
66#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
67#[serde(rename_all = "camelCase")]
68pub struct Asset {
69    pub id: u64,
70    pub name: String,
71    #[serde(rename = "assetType")]
72    pub kind: AssetType,
73    pub current_version_id: u64,
74    pub meta: Option<AssetMeta>,
75}
76
77#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
78#[serde(rename_all = "camelCase")]
79pub struct Emote {
80    #[serde(rename = "assetId")]
81    pub id: u64,
82    #[serde(rename = "assetName")]
83    pub name: String,
84    pub position: u8,
85}
86
87#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
88#[serde(rename_all = "camelCase")]
89pub struct Outfit {
90    pub id: u64,
91    pub name: String,
92    pub is_editable: bool,
93    pub outfit_type: Option<()>,
94}
95
96#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct AvatarResponse {
99    #[serde(rename = "playerAvatarType")]
100    pub kind: AvatarType,
101    pub assets: Vec<Asset>,
102    pub scales: AvatarScales,
103    pub body_colors: BodyColors,
104    pub default_pants_applied: bool,
105    pub default_shirt_applied: bool,
106    pub emotes: Vec<Emote>,
107}
108
109#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
110#[serde(rename_all = "camelCase")]
111pub struct OutfitsResponse {
112    #[serde(rename = "data")]
113    pub outfits: Vec<Outfit>,
114    pub total: u64,
115    pub filtered_count: u64,
116}
117
118#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
119#[serde(rename_all = "camelCase")]
120pub struct OutfitDetails {
121    pub id: u64,
122    pub name: String,
123    pub universe_id: u64,
124    pub assets: Vec<Asset>,
125    pub body_colors: BodyColors,
126    #[serde(rename = "scale")]
127    pub scales: AvatarScales,
128    #[serde(rename = "playerAvatarType")]
129    pub avatar_type: AvatarType,
130    pub outfit_type: String, // TODO: change to enum
131    pub is_editable: bool,
132    pub moderation_status: Option<String>,
133}
134
135#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
136#[serde(rename_all = "camelCase")]
137pub struct UniverseAvatarSettings {
138    #[serde(rename = "gameAvatarType")]
139    pub avatar_type: MorphAvatarType,
140    pub avatar_body_type: String,       // TDOO: change to Enum
141    pub avatar_collision_type: String,  // TDOO: change to Enum
142    pub joint_positioning_type: String, // TDOO: change to Enum
143
144    pub avatar_min_scales: AvatarScales,
145    pub avatar_max_scales: AvatarScales,
146    pub avatar_asset_overrides: Vec<Option<()>>,
147
148    pub message: String,
149    pub moderation_status: Option<String>,
150
151    pub allow_custom_animations: String, // TODO: cast to bool
152}
153
154async fn generic_request<'a, R: Serialize, T: DeserializeOwned>(
155    client: &mut Client,
156    method: Method,
157    path: &str,
158    request: Option<&'a R>,
159    query: Option<&'a [(&'a str, &'a str)]>,
160) -> Result<T, Error> {
161    let mut builder = client
162        .requestor
163        .client
164        .request(method, format!("{URL}/{path}"))
165        .headers(client.requestor.default_headers.clone());
166
167    if let Some(request) = request {
168        builder = builder.json(&request);
169    }
170
171    if let Some(query) = query {
172        builder = builder.query(&query);
173    }
174
175    let response = client.validate_response(builder.send().await).await?;
176    client.requestor.parse_json::<T>(response).await
177}
178
179/// Returns details about a specified user's avatar
180pub async fn user_avatar(client: &mut Client, id: u64) -> Result<AvatarResponse, Error> {
181    generic_request::<(), AvatarResponse>(
182        client,
183        Method::GET,
184        &format!("/users/{id}/avatar"),
185        None,
186        None,
187    )
188    .await
189}
190
191/// Gets a list of asset ids that the user is currently wearing
192pub async fn user_currently_wearing(client: &mut Client, id: u64) -> Result<Vec<u64>, Error> {
193    #[derive(Deserialize)]
194    struct Response {
195        #[serde(rename = "assetIds")]
196        ids: Vec<u64>,
197    }
198
199    Ok(generic_request::<(), Response>(
200        client,
201        Method::GET,
202        &format!("/users/{id}/currently-wearing"),
203        None,
204        None,
205    )
206    .await?
207    .ids)
208}
209
210/// Sets the avatar's current assets to the list - Flagged as obsolete, does not support layered clothing meta params.
211///
212/// Warning: Deprecated
213/// Only allows items that you own, are not expired, and are wearable asset types.
214/// Any assets being worn before this method is called are automatically removed.
215pub async fn avatar_set_wearing_assets(
216    client: &mut Client,
217    assets: Vec<u64>,
218) -> Result<bool, Error> {
219    #[derive(Serialize)]
220    #[serde(rename_all = "camelCase")]
221    struct Request<'a> {
222        asset_ids: &'a [u64],
223    }
224
225    #[derive(Deserialize)]
226    pub struct Response {
227        pub success: bool,
228    }
229
230    Ok(generic_request::<Request, Response>(
231        client,
232        Method::POST,
233        &format!("/avatar/set-wearing-assets"),
234        Some(&Request { asset_ids: &assets }),
235        None,
236    )
237    .await?
238    .success)
239}
240
241/// Sets the authenticated user's player avatar type (e.g. R6 or R15).
242pub async fn avatar_set_type(client: &mut Client, kind: AvatarType) -> Result<bool, Error> {
243    #[derive(Serialize)]
244    struct Request {
245        #[serde(rename = "playerAvatarType")]
246        avatar_type: AvatarType,
247    }
248
249    #[derive(Deserialize)]
250    struct Response {
251        success: bool,
252    }
253
254    Ok(generic_request::<Request, Response>(
255        client,
256        Method::POST,
257        &format!("/avatar/set-player-avatar-type"),
258        Some(&Request { avatar_type: kind }),
259        None,
260    )
261    .await?
262    .success)
263}
264
265/// Sets the authenticated user's body colors.
266pub async fn avatar_set_body_colors(
267    client: &mut Client,
268    colors: BodyColors,
269) -> Result<bool, Error> {
270    #[derive(Deserialize)]
271    struct Response {
272        success: bool,
273    }
274
275    Ok(generic_request::<BodyColors, Response>(
276        client,
277        Method::POST,
278        &format!("/avatar/set-body-colors"),
279        Some(&colors),
280        None,
281    )
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(generic_request::<AvatarScales, Response>(
294        client,
295        Method::POST,
296        &format!("/avatar/set-scales"),
297        Some(&scales),
298        None,
299    )
300    .await?
301    .success)
302}
303
304/// Deprecated, user v2. Gets a list of outfits for the specified user.
305pub async fn user_outfits(
306    client: &mut Client,
307    id: u64,
308    paging: Paging<'_>,
309    is_editable: bool,
310    //outfit_type: OutfitType, all seem to be null
311) -> Result<OutfitsResponse, Error> {
312    let limit = paging.limit.unwrap_or(25).to_string();
313    let cursor = paging.cursor.unwrap_or("1");
314
315    generic_request::<(), OutfitsResponse>(
316        client,
317        Method::GET,
318        &format!("/users/{id}/outfits"),
319        None,
320        Some(&[
321            ("page", cursor),
322            ("itemsPerPage", &limit),
323            ("isEditable", is_editable.to_string().as_str()),
324        ]),
325    )
326    .await
327}
328
329/// Gets details about the contents of an outfit.
330pub async fn outfit_details(client: &mut Client, id: u64) -> Result<OutfitDetails, Error> {
331    generic_request::<(), OutfitDetails>(
332        client,
333        Method::GET,
334        &format!("/outfits/{id}/details"),
335        None,
336        None,
337    )
338    .await
339}
340
341/// Deletes the outfit.
342pub async fn remove_outfit(client: &mut Client, id: u64) -> Result<bool, Error> {
343    #[derive(Deserialize)]
344    struct Response {
345        success: bool,
346    }
347
348    Ok(generic_request::<(), Response>(
349        client,
350        Method::POST,
351        &format!("/outfits/{id}/delete"),
352        None,
353        None,
354    )
355    .await?
356    .success)
357}
358
359/// The server will call this on game server start to request general information about the universe.
360/// This is version 1.1, which returns an entry from the UniverseAvatarType enum.
361/// During mixed mode this may return unreliable results
362pub async fn universe_avatar_settings(
363    client: &mut Client,
364    id: u64,
365) -> Result<UniverseAvatarSettings, Error> {
366    generic_request::<(), UniverseAvatarSettings>(
367        client,
368        Method::GET,
369        &format!("/users/{id}/avatar"),
370        None,
371        None,
372    )
373    .await
374}