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