roblox_api/api/thumbnails/
v1.rs

1use serde::{Deserialize, Serialize, Serializer};
2
3use crate::{Error, client::Client};
4
5const URL: &str = "https://thumbnails.roblox.com/v1";
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum ThumbnailSize {
9    S30x30,
10    S48x48,
11    S50x50,
12    S60x60,
13    S75x75,
14
15    S100x100,
16    S110x110,
17    S128x128,
18    S140x140,
19    S150x150,
20    S180x180,
21    S250x250,
22    S256x256,
23    S352x352,
24    S420x420,
25    S512x512,
26    S720x720,
27
28    S256x144,
29    S384x216,
30    S480x270,
31    S576x324,
32    S768x432,
33
34    S1200x80,
35    S1440x456,
36}
37
38#[derive(Clone, Default, Debug, Serialize, PartialEq, Eq)]
39pub enum ThumbnailFormat {
40    #[default]
41    Png,
42    Jpeg,
43    Webp,
44}
45
46#[derive(Clone, Default, Debug, PartialEq, Eq)]
47pub enum ReturnPolicy {
48    #[default]
49    PlaceHolder,
50    ForcePlaceHolder,
51    AutoGenerated,
52    ForceAutoGenerated,
53}
54
55#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
56pub enum ThumbnailState {
57    Pending,
58    Blocked,
59    Completed,
60}
61
62#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
63pub enum ThumbnailVersion {
64    TN3,
65}
66
67#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
68pub enum ThumbnailRequestType {
69    Avatar = 1,
70    AvatarHeadShot,
71    GameIcon,
72    BadgeIcon,
73    GameThumbnail,
74    GamePass,
75    Asset,
76    BundleThumbnail,
77    Outfit,
78    GroupIcon,
79    DeveloperProduct,
80    AutoGeneratedAsset,
81    AvatarBust,
82    PlaceIcon,
83    AutoGeneratedGameIcon,
84    ForceAutoGeneratedGameIcon,
85    Look,
86    CreatorContextAsset,
87    Screenshot,
88}
89
90#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
91pub struct ThumbnailResponse {
92    #[serde(rename = "targetId")]
93    pub id: u64,
94    #[serde(rename = "imageUrl")]
95    pub image_url: String,
96    pub version: ThumbnailVersion,
97    pub state: ThumbnailState,
98}
99
100#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
101pub struct ThumbnailResponseFromBatch {
102    #[serde(rename = "targetId")]
103    pub id: u64,
104    #[serde(rename = "requestId")]
105    pub request_id: String,
106    #[serde(rename = "imageUrl")]
107    pub image_url: String,
108    pub version: ThumbnailVersion,
109    pub state: ThumbnailState,
110    #[serde(rename = "errorMessage")]
111    pub error: String,
112    #[serde(rename = "errorCode")]
113    pub error_code: i32,
114}
115
116#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
117pub struct ThumbnailBatchRequest<'a> {
118    #[serde(rename = "targetId")]
119    pub id: u64,
120    #[serde(rename = "requestId")]
121    pub request_id: &'a str,
122    #[serde(rename = "token")]
123    pub token: &'a str,
124    #[serde(rename = "alias")]
125    pub alias: &'a str,
126    #[serde(rename = "type")]
127    pub kind: ThumbnailRequestType,
128    #[serde(rename = "size")]
129    pub size: ThumbnailSize,
130    #[serde(rename = "format")]
131    pub format: ThumbnailFormat,
132    #[serde(rename = "isCircular")]
133    pub circular: bool,
134}
135
136impl Serialize for ThumbnailSize {
137    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138    where
139        S: Serializer,
140    {
141        serializer.serialize_str(&self.to_string())
142    }
143}
144
145impl std::fmt::Display for ThumbnailSize {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        let content = format!("{:?}", self);
148        write!(f, "{}", content.strip_prefix('S').unwrap())
149    }
150}
151
152impl std::fmt::Display for ThumbnailFormat {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        let content = format!("{:?}", self).to_lowercase();
155        write!(f, "{}", content)
156    }
157}
158
159impl std::fmt::Display for ThumbnailRequestType {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "{:?}", self)
162    }
163}
164
165impl std::fmt::Display for ReturnPolicy {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        write!(f, "{:?}", self)
168    }
169}
170
171async fn generic_thumbnail_api(
172    client: &mut Client,
173    ids: &[u64],
174    asset_name: &str,
175    domain: &str,
176    size: ThumbnailSize,
177    format: ThumbnailFormat,
178    circular: bool,
179    return_policy: Option<ReturnPolicy>,
180    count_per_universe: Option<u32>,
181    defaults: Option<bool>,
182) -> Result<Vec<ThumbnailResponse>, Error> {
183    let ids = ids
184        .iter()
185        .map(|x| x.to_string())
186        .collect::<Vec<String>>()
187        .join(",");
188
189    let asset_ids_key = format!("{asset_name}Ids");
190    let mut query = vec![
191        (asset_ids_key.as_str(), ids),
192        ("size", size.to_string()),
193        ("format", format.to_string()),
194        ("isCircular", circular.to_string()),
195    ];
196
197    if let Some(return_policy) = return_policy {
198        query.push(("returnPolicy", return_policy.to_string()));
199    }
200
201    if let Some(count_per_universe) = count_per_universe {
202        query.push(("countPerUniverse", count_per_universe.to_string()));
203    }
204
205    if let Some(defaults) = defaults {
206        query.push(("defaults", defaults.to_string()));
207    }
208
209    let result = client
210        .requestor
211        .client
212        .get(format!("{URL}/{domain}"))
213        .query(&query)
214        .headers(client.requestor.default_headers.clone())
215        .send()
216        .await;
217
218    #[derive(Clone, Debug, Deserialize)]
219    struct Response {
220        #[serde(rename = "data")]
221        thumbnails: Vec<ThumbnailResponse>,
222    }
223
224    let response = client.validate_response(result).await?;
225    Ok(client
226        .requestor
227        .parse_json::<Response>(response)
228        .await?
229        .thumbnails)
230}
231
232pub async fn assets(
233    client: &mut Client,
234    ids: &[u64],
235    size: ThumbnailSize,
236    format: ThumbnailFormat,
237    return_policy: ReturnPolicy,
238    circular: bool,
239) -> Result<Vec<ThumbnailResponse>, Error> {
240    generic_thumbnail_api(
241        client,
242        ids,
243        "asset",
244        "assets",
245        size,
246        format,
247        circular,
248        Some(return_policy),
249        None,
250        None,
251    )
252    .await
253}
254
255pub async fn asset_3d(
256    client: &mut Client,
257    id: u64,
258    encode_gltf: bool,
259) -> Result<ThumbnailResponse, Error> {
260    let result = client
261        .requestor
262        .client
263        .get(format!("{URL}/assets-thumbnail-3d"))
264        .query(&[
265            ("assetId", id.to_string()),
266            ("useGltf", encode_gltf.to_string()),
267        ])
268        .headers(client.requestor.default_headers.clone())
269        .send()
270        .await;
271
272    let response = client.validate_response(result).await?;
273    client
274        .requestor
275        .parse_json::<ThumbnailResponse>(response)
276        .await
277}
278
279pub async fn badge_icons(
280    client: &mut Client,
281    ids: &[u64],
282    size: ThumbnailSize,
283    format: ThumbnailFormat,
284    circular: bool,
285) -> Result<Vec<ThumbnailResponse>, Error> {
286    generic_thumbnail_api(
287        client,
288        ids,
289        "badge",
290        "badges/icons",
291        size,
292        format,
293        circular,
294        None,
295        None,
296        None,
297    )
298    .await
299}
300
301pub async fn bundles(
302    client: &mut Client,
303    ids: &[u64],
304    size: ThumbnailSize,
305    format: ThumbnailFormat,
306    circular: bool,
307) -> Result<Vec<ThumbnailResponse>, Error> {
308    generic_thumbnail_api(
309        client,
310        ids,
311        "bundle",
312        "bundles/thumbnails",
313        size,
314        format,
315        circular,
316        None,
317        None,
318        None,
319    )
320    .await
321}
322
323pub async fn developer_prodcuts(
324    client: &mut Client,
325    ids: &[u64],
326    size: ThumbnailSize,
327    format: ThumbnailFormat,
328    circular: bool,
329) -> Result<Vec<ThumbnailResponse>, Error> {
330    generic_thumbnail_api(
331        client,
332        ids,
333        "developerProduct",
334        "developer-products/icons",
335        size,
336        format,
337        circular,
338        None,
339        None,
340        None,
341    )
342    .await
343}
344
345pub async fn gamepasses(
346    client: &mut Client,
347    ids: &[u64],
348    size: ThumbnailSize,
349    format: ThumbnailFormat,
350    circular: bool,
351) -> Result<Vec<ThumbnailResponse>, Error> {
352    generic_thumbnail_api(
353        client,
354        ids,
355        "gamePass",
356        "game-passes",
357        size,
358        format,
359        circular,
360        None,
361        None,
362        None,
363    )
364    .await
365}
366
367// what the fuck does this even mean?
368/// Fetches game thumbnail URLs for a list of universes' thumbnail ids. Ids that do not correspond to a valid thumbnail will be filtered out.
369pub async fn universe_thumbnails(
370    client: &mut Client,
371    universe_id: u64,
372    ids: &[u64],
373    size: ThumbnailSize,
374    format: ThumbnailFormat,
375    return_policy: ReturnPolicy,
376    circular: bool,
377) -> Result<Vec<ThumbnailResponse>, Error> {
378    generic_thumbnail_api(
379        client,
380        ids,
381        "thumbnail",
382        &format!("games/{universe_id}/thumbnails"),
383        size,
384        format,
385        circular,
386        Some(return_policy),
387        None,
388        None,
389    )
390    .await
391}
392
393pub async fn games(
394    client: &mut Client,
395    ids: &[u64],
396    size: ThumbnailSize,
397    format: ThumbnailFormat,
398    return_policy: ReturnPolicy,
399    circular: bool,
400    defaults: bool,          // defaults (if any) should be returned if no media exists
401    count_per_universe: u32, // max number of thumbnails to return per universe
402) -> Result<Vec<ThumbnailResponse>, Error> {
403    generic_thumbnail_api(
404        client,
405        ids,
406        "universe",
407        "games/multiget/thumbnails",
408        size,
409        format,
410        circular,
411        Some(return_policy),
412        Some(count_per_universe),
413        Some(defaults),
414    )
415    .await
416}
417
418pub async fn game_icons(
419    client: &mut Client,
420    ids: &[u64],
421    size: ThumbnailSize,
422    format: ThumbnailFormat,
423    return_policy: ReturnPolicy,
424    circular: bool,
425) -> Result<Vec<ThumbnailResponse>, Error> {
426    generic_thumbnail_api(
427        client,
428        ids,
429        "universe",
430        "games/icons",
431        size,
432        format,
433        circular,
434        Some(return_policy),
435        None,
436        None,
437    )
438    .await
439}
440
441pub async fn group_icons(
442    client: &mut Client,
443    ids: &[u64],
444    size: ThumbnailSize,
445    format: ThumbnailFormat,
446    circular: bool,
447) -> Result<Vec<ThumbnailResponse>, Error> {
448    generic_thumbnail_api(
449        client,
450        ids,
451        "group",
452        "groups/icons",
453        size,
454        format,
455        circular,
456        None,
457        None,
458        None,
459    )
460    .await
461}
462
463pub async fn place_icons(
464    client: &mut Client,
465    ids: &[u64],
466    size: ThumbnailSize,
467    format: ThumbnailFormat,
468    return_policy: ReturnPolicy,
469    circular: bool,
470) -> Result<Vec<ThumbnailResponse>, Error> {
471    generic_thumbnail_api(
472        client,
473        ids,
474        "place",
475        "places/gameicons",
476        size,
477        format,
478        circular,
479        Some(return_policy),
480        None,
481        None,
482    )
483    .await
484}
485
486pub async fn avatars(
487    client: &mut Client,
488    ids: &[u64],
489    size: ThumbnailSize,
490    format: ThumbnailFormat,
491    circular: bool,
492) -> Result<Vec<ThumbnailResponse>, Error> {
493    generic_thumbnail_api(
494        client,
495        ids,
496        "user",
497        "users/avatar",
498        size,
499        format,
500        circular,
501        None,
502        None,
503        None,
504    )
505    .await
506}
507
508pub async fn avatar_3d(client: &mut Client, id: u64) -> Result<ThumbnailResponse, Error> {
509    let result = client
510        .requestor
511        .client
512        .get(format!("{URL}/avatar-3d"))
513        .query(&[("userId", id)])
514        .headers(client.requestor.default_headers.clone())
515        .send()
516        .await;
517
518    let response = client.validate_response(result).await?;
519    client
520        .requestor
521        .parse_json::<ThumbnailResponse>(response)
522        .await
523}
524
525pub async fn avatar_busts(
526    client: &mut Client,
527    ids: &[u64],
528    size: ThumbnailSize,
529    format: ThumbnailFormat,
530    circular: bool,
531) -> Result<Vec<ThumbnailResponse>, Error> {
532    generic_thumbnail_api(
533        client,
534        ids,
535        "user",
536        "users/avatar-bust",
537        size,
538        format,
539        circular,
540        None,
541        None,
542        None,
543    )
544    .await
545}
546
547pub async fn avatar_headshots(
548    client: &mut Client,
549    ids: &[u64],
550    size: ThumbnailSize,
551    format: ThumbnailFormat,
552    circular: bool,
553) -> Result<Vec<ThumbnailResponse>, Error> {
554    generic_thumbnail_api(
555        client,
556        ids,
557        "user",
558        "users/avatar-headshot",
559        size,
560        format,
561        circular,
562        None,
563        None,
564        None,
565    )
566    .await
567}
568
569pub async fn outfit_3d(client: &mut Client, id: u64) -> Result<ThumbnailResponse, Error> {
570    let result = client
571        .requestor
572        .client
573        .get(format!("{URL}/outfit-3d"))
574        .query(&[("outfitId", id)])
575        .headers(client.requestor.default_headers.clone())
576        .send()
577        .await;
578
579    let response = client.validate_response(result).await?;
580    client
581        .requestor
582        .parse_json::<ThumbnailResponse>(response)
583        .await
584}
585
586pub async fn outfits(
587    client: &mut Client,
588    ids: &[u64],
589    size: ThumbnailSize,
590    format: ThumbnailFormat,
591    circular: bool,
592) -> Result<Vec<ThumbnailResponse>, Error> {
593    generic_thumbnail_api(
594        client,
595        ids,
596        "userOutfit",
597        "users/outfits",
598        size,
599        format,
600        circular,
601        None,
602        None,
603        None,
604    )
605    .await
606}
607
608pub async fn batch(
609    client: &mut Client,
610    requests: Vec<ThumbnailBatchRequest<'_>>,
611) -> Result<Vec<ThumbnailResponseFromBatch>, Error> {
612    let result = client
613        .requestor
614        .client
615        .post(format!("{URL}/batch"))
616        .headers(client.requestor.default_headers.clone())
617        .json(&requests)
618        .send()
619        .await;
620
621    #[derive(Clone, Debug, Deserialize)]
622    struct Response {
623        #[serde(rename = "data")]
624        thumbnails: Vec<ThumbnailResponseFromBatch>,
625    }
626
627    let response = client.validate_response(result).await?;
628    Ok(client
629        .requestor
630        .parse_json::<Response>(response)
631        .await?
632        .thumbnails)
633}
634
635// TODO: measurements api