Skip to main content

roblox_api/api/thumbnails/
v1.rs

1use serde::{Deserialize, Serialize, Serializer};
2use strum::{EnumIter, IntoEnumIterator};
3use strum_macros::{Display, EnumString};
4
5use crate::endpoint;
6
7pub const URL: &str = "https://thumbnails.roblox.com/v1";
8
9#[derive(Clone, Debug, Deserialize, PartialEq, Eq, EnumIter)]
10pub enum ThumbnailSize {
11    S30x30,
12    S48x48,
13    S50x50,
14    S60x60,
15    S75x75,
16
17    S100x100,
18    S110x110,
19    S128x128,
20    S140x140,
21    S150x150,
22    S180x180,
23    S250x250,
24    S256x256,
25    S352x352,
26    S420x420,
27    S512x512,
28    S720x720,
29
30    S256x144,
31    S384x216,
32    S480x270,
33    S576x324,
34    S768x432,
35
36    S1200x80,
37    S1440x456,
38}
39
40#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
41pub enum ThumbnailFormat {
42    #[default]
43    Png,
44    Jpeg,
45    Webp,
46}
47
48#[derive(Clone, Default, Debug, PartialEq, Eq, Display, EnumString)]
49pub enum ReturnPolicy {
50    #[default]
51    PlaceHolder,
52    ForcePlaceHolder,
53    AutoGenerated,
54    ForceAutoGenerated,
55}
56
57#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
58pub enum ThumbnailState {
59    Pending,
60    Blocked,
61    Completed,
62}
63
64#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
65pub enum ThumbnailVersion {
66    TN3,
67    #[serde(rename = "TN3.5")]
68    TN3_5,
69}
70
71#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
72pub enum ThumbnailRequestType {
73    Avatar = 1,
74    AvatarHeadShot,
75    GameIcon,
76    BadgeIcon,
77    GameThumbnail,
78    GamePass,
79    Asset,
80    BundleThumbnail,
81    Outfit,
82    GroupIcon,
83    DeveloperProduct,
84    AutoGeneratedAsset,
85    AvatarBust,
86    PlaceIcon,
87    AutoGeneratedGameIcon,
88    ForceAutoGeneratedGameIcon,
89    Look,
90    CreatorContextAsset,
91    Screenshot,
92}
93
94#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
95#[serde(rename_all = "camelCase")]
96pub struct ThumbnailResponse {
97    #[serde(rename = "targetId")]
98    pub id: u64,
99    pub image_url: String,
100    pub version: ThumbnailVersion,
101    pub state: ThumbnailState,
102}
103
104#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
105#[serde(rename_all = "camelCase")]
106pub struct ThumbnailResponseFromBatch {
107    #[serde(rename = "targetId")]
108    pub id: u64,
109    pub request_id: String,
110    pub image_url: String,
111    pub version: ThumbnailVersion,
112    pub state: ThumbnailState,
113    #[serde(rename = "errorMessage")]
114    pub error: String,
115    pub error_code: i32,
116}
117
118#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
119#[serde(rename_all = "camelCase")]
120pub struct ThumbnailBatchRequest<'a> {
121    #[serde(rename = "targetId")]
122    pub id: u64,
123    pub request_id: &'a str,
124    pub token: &'a str,
125    pub alias: &'a str,
126    #[serde(rename = "type")]
127    pub kind: ThumbnailRequestType,
128    pub size: ThumbnailSize,
129    pub format: ThumbnailFormat,
130    #[serde(rename = "isCircular")]
131    pub circular: bool,
132}
133
134impl Serialize for ThumbnailSize {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: Serializer,
138    {
139        serializer.serialize_str(&self.to_string())
140    }
141}
142
143impl std::fmt::Display for ThumbnailSize {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        let content = format!("{:?}", self);
146        write!(f, "{}", content.strip_prefix('S').unwrap())
147    }
148}
149
150impl TryFrom<&str> for ThumbnailSize {
151    type Error = &'static str;
152
153    fn try_from(value: &str) -> Result<Self, Self::Error> {
154        for size in ThumbnailSize::iter() {
155            if size.to_string().as_str() == value {
156                return Ok(size);
157            }
158        }
159
160        Err("Failed to convert string to ThumbnailSize")
161    }
162}
163
164impl ThumbnailFormat {
165    pub fn extension(&self) -> &str {
166        match self {
167            ThumbnailFormat::Png => "png",
168            ThumbnailFormat::Jpeg => "jpeg",
169            ThumbnailFormat::Webp => "webp",
170        }
171    }
172}
173
174endpoint! {
175    /// Returns thumbnail URLs for a list of asset ids.
176    assets(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
177        GET "{URL}/assets";
178        types {
179            Response { data("data"): Vec<ThumbnailResponse> }
180        }
181        prelude {
182            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
183            let size = size.to_string();
184            let format = format.to_string();
185            let return_policy = return_policy.to_string();
186            let circular = circular.to_string();
187        }
188        query {
189            "assetIds" => &joined_ids,
190            "size" => &size,
191            "format" => &format,
192            "returnPolicy" => &return_policy,
193            "isCircular" => &circular,
194        }
195        map |r: Response| r.data
196    }
197
198    /// Returns a 3D thumbnail of an asset.
199    asset_3d(id: u64, encode_gltf: bool) -> ThumbnailResponse {
200        GET "{URL}/assets-thumbnail-3d";
201        prelude {
202            let id = id.to_string();
203            let encode_gltf = encode_gltf.to_string();
204        }
205        query {
206            "assetId" => &id,
207            "useGltf" => &encode_gltf,
208        }
209    }
210
211    /// Returns thumbnail URLs for a list of badge ids.
212    badge_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
213        GET "{URL}/badges/icons";
214        types {
215            Response { data("data"): Vec<ThumbnailResponse> }
216        }
217        prelude {
218            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
219            let size = size.to_string();
220            let format = format.to_string();
221            let circular = circular.to_string();
222        }
223        query {
224            "badgeIds" => &joined_ids,
225            "size" => &size,
226            "format" => &format,
227            "isCircular" => &circular,
228        }
229        map |r: Response| r.data
230    }
231
232    /// Returns thumbnail URLs for a list of bundle ids.
233    bundles(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
234        GET "{URL}/bundles/thumbnails";
235        types {
236            Response { data("data"): Vec<ThumbnailResponse> }
237        }
238        prelude {
239            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
240            let size = size.to_string();
241            let format = format.to_string();
242            let circular = circular.to_string();
243        }
244        query {
245            "bundleIds" => &joined_ids,
246            "size" => &size,
247            "format" => &format,
248            "isCircular" => &circular,
249        }
250        map |r: Response| r.data
251    }
252
253    /// Returns thumbnail URLs for a list of developer product ids.
254    developer_products(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
255        GET "{URL}/developer-products/icons";
256        types {
257            Response { data("data"): Vec<ThumbnailResponse> }
258        }
259        prelude {
260            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
261            let size = size.to_string();
262            let format = format.to_string();
263            let circular = circular.to_string();
264        }
265        query {
266            "developerProductIds" => &joined_ids,
267            "size" => &size,
268            "format" => &format,
269            "isCircular" => &circular,
270        }
271        map |r: Response| r.data
272    }
273
274    /// Returns thumbnail URLs for a list of game pass ids.
275    gamepasses(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
276        GET "{URL}/game-passes";
277        types {
278            Response { data("data"): Vec<ThumbnailResponse> }
279        }
280        prelude {
281            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
282            let size = size.to_string();
283            let format = format.to_string();
284            let circular = circular.to_string();
285        }
286        query {
287            "gamePassIds" => &joined_ids,
288            "size" => &size,
289            "format" => &format,
290            "isCircular" => &circular,
291        }
292        map |r: Response| r.data
293    }
294
295    /// Fetches game thumbnail URLs for a list of universes' thumbnail ids.
296    /// Ids that do not correspond to a valid thumbnail will be filtered out.
297    universe_thumbnails(universe_id: u64, ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
298        GET "{URL}/games/{universe_id}/thumbnails";
299        types {
300            Response { data("data"): Vec<ThumbnailResponse> }
301        }
302        prelude {
303            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
304            let size = size.to_string();
305            let format = format.to_string();
306            let return_policy = return_policy.to_string();
307            let circular = circular.to_string();
308        }
309        query {
310            "thumbnailIds" => &joined_ids,
311            "size" => &size,
312            "format" => &format,
313            "returnPolicy" => &return_policy,
314            "isCircular" => &circular,
315        }
316        map |r: Response| r.data
317    }
318
319    /// Returns thumbnail URLs for a list of universe ids.
320    games(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool, defaults: bool, count_per_universe: u32) -> Vec<ThumbnailResponse> {
321        GET "{URL}/games/multiget/thumbnails";
322        types {
323            Response { data("data"): Vec<ThumbnailResponse> }
324        }
325        prelude {
326            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
327            let size = size.to_string();
328            let format = format.to_string();
329            let return_policy = return_policy.to_string();
330            let circular = circular.to_string();
331            let defaults = defaults.to_string();
332            let count_per_universe = count_per_universe.to_string();
333        }
334        query {
335            "universeIds" => &joined_ids,
336            "size" => &size,
337            "format" => &format,
338            "returnPolicy" => &return_policy,
339            "isCircular" => &circular,
340            "defaults" => &defaults,
341            "countPerUniverse" => &count_per_universe,
342        }
343        map |r: Response| r.data
344    }
345
346    /// Returns game icon URLs for a list of universe ids.
347    game_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
348        GET "{URL}/games/icons";
349        types {
350            Response { data("data"): Vec<ThumbnailResponse> }
351        }
352        prelude {
353            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
354            let size = size.to_string();
355            let format = format.to_string();
356            let return_policy = return_policy.to_string();
357            let circular = circular.to_string();
358        }
359        query {
360            "universeIds" => &joined_ids,
361            "size" => &size,
362            "format" => &format,
363            "returnPolicy" => &return_policy,
364            "isCircular" => &circular,
365        }
366        map |r: Response| r.data
367    }
368
369    /// Returns thumbnail URLs for a list of group ids.
370    group_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
371        GET "{URL}/groups/icons";
372        types {
373            Response { data("data"): Vec<ThumbnailResponse> }
374        }
375        prelude {
376            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
377            let size = size.to_string();
378            let format = format.to_string();
379            let circular = circular.to_string();
380        }
381        query {
382            "groupIds" => &joined_ids,
383            "size" => &size,
384            "format" => &format,
385            "isCircular" => &circular,
386        }
387        map |r: Response| r.data
388    }
389
390    /// Returns place icon URLs for a list of place ids.
391    place_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
392        GET "{URL}/places/gameicons";
393        types {
394            Response { data("data"): Vec<ThumbnailResponse> }
395        }
396        prelude {
397            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
398            let size = size.to_string();
399            let format = format.to_string();
400            let return_policy = return_policy.to_string();
401            let circular = circular.to_string();
402        }
403        query {
404            "placeIds" => &joined_ids,
405            "size" => &size,
406            "format" => &format,
407            "returnPolicy" => &return_policy,
408            "isCircular" => &circular,
409        }
410        map |r: Response| r.data
411    }
412
413    /// Returns avatar thumbnail URLs for a list of user ids.
414    avatars(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
415        GET "{URL}/users/avatar";
416        types {
417            Response { data("data"): Vec<ThumbnailResponse> }
418        }
419        prelude {
420            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
421            let size = size.to_string();
422            let format = format.to_string();
423            let circular = circular.to_string();
424        }
425        query {
426            "userIds" => &joined_ids,
427            "size" => &size,
428            "format" => &format,
429            "isCircular" => &circular,
430        }
431        map |r: Response| r.data
432    }
433
434    /// Returns a 3D avatar thumbnail for a user id.
435    avatar_3d(id: u64) -> ThumbnailResponse {
436        GET "{URL}/avatar-3d";
437        prelude {
438            let id = id.to_string();
439        }
440        query {
441            "userId" => &id,
442        }
443    }
444
445    /// Returns avatar bust thumbnail URLs for a list of user ids.
446    avatar_busts(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
447        GET "{URL}/users/avatar-bust";
448        types {
449            Response { data("data"): Vec<ThumbnailResponse> }
450        }
451        prelude {
452            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
453            let size = size.to_string();
454            let format = format.to_string();
455            let circular = circular.to_string();
456        }
457        query {
458            "userIds" => &joined_ids,
459            "size" => &size,
460            "format" => &format,
461            "isCircular" => &circular,
462        }
463        map |r: Response| r.data
464    }
465
466    /// Returns avatar headshot thumbnail URLs for a list of user ids.
467    avatar_headshots(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
468        GET "{URL}/users/avatar-headshot";
469        types {
470            Response { data("data"): Vec<ThumbnailResponse> }
471        }
472        prelude {
473            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
474            let size = size.to_string();
475            let format = format.to_string();
476            let circular = circular.to_string();
477        }
478        query {
479            "userIds" => &joined_ids,
480            "size" => &size,
481            "format" => &format,
482            "isCircular" => &circular,
483        }
484        map |r: Response| r.data
485    }
486
487    /// Returns a 3D outfit thumbnail for an outfit id.
488    outfit_3d(id: u64) -> ThumbnailResponse {
489        GET "{URL}/outfit-3d";
490        prelude {
491            let id = id.to_string();
492        }
493        query {
494            "outfitId" => &id,
495        }
496    }
497
498    /// Returns outfit thumbnail URLs for a list of user outfit ids.
499    outfits(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
500        GET "{URL}/users/outfits";
501        types {
502            Response { data("data"): Vec<ThumbnailResponse> }
503        }
504        prelude {
505            let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
506            let size = size.to_string();
507            let format = format.to_string();
508            let circular = circular.to_string();
509        }
510        query {
511            "userOutfitIds" => &joined_ids,
512            "size" => &size,
513            "format" => &format,
514            "isCircular" => &circular,
515        }
516        map |r: Response| r.data
517    }
518
519    /// Returns batch thumbnail results for multiple requests of varying types and sizes.
520    batch(requests: Vec<ThumbnailBatchRequest<'_>>) -> Vec<ThumbnailResponseFromBatch> {
521        POST "{URL}/batch";
522        types {
523            Response { data("data"): Vec<ThumbnailResponseFromBatch> }
524        }
525        body_serialize { &requests }
526        map |r: Response| r.data
527    }
528}
529
530// TODO: measurements api