use serde::{Deserialize, Serialize, Serializer};
use crate::{Error, client::Client};
const URL: &str = "https://thumbnails.roblox.com/v1";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ThumbnailSize {
S30x30,
S48x48,
S50x50,
S60x60,
S75x75,
S100x100,
S110x110,
S128x128,
S140x140,
S150x150,
S180x180,
S250x250,
S256x256,
S352x352,
S420x420,
S512x512,
S720x720,
S256x144,
S384x216,
S480x270,
S576x324,
S768x432,
S1200x80,
S1440x456,
}
#[derive(Clone, Default, Debug, Serialize, PartialEq, Eq)]
pub enum ThumbnailFormat {
#[default]
Png,
Jpeg,
Webp,
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub enum ReturnPolicy {
#[default]
PlaceHolder,
ForcePlaceHolder,
AutoGenerated,
ForceAutoGenerated,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub enum ThumbnailState {
Pending,
Blocked,
Completed,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub enum ThumbnailVersion {
TN3,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ThumbnailRequestType {
Avatar = 1,
AvatarHeadShot,
GameIcon,
BadgeIcon,
GameThumbnail,
GamePass,
Asset,
BundleThumbnail,
Outfit,
GroupIcon,
DeveloperProduct,
AutoGeneratedAsset,
AvatarBust,
PlaceIcon,
AutoGeneratedGameIcon,
ForceAutoGeneratedGameIcon,
Look,
CreatorContextAsset,
Screenshot,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ThumbnailResponse {
#[serde(rename = "targetId")]
pub id: u64,
#[serde(rename = "imageUrl")]
pub image_url: String,
pub version: ThumbnailVersion,
pub state: ThumbnailState,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ThumbnailResponseFromBatch {
#[serde(rename = "targetId")]
pub id: u64,
#[serde(rename = "requestId")]
pub request_id: String,
#[serde(rename = "imageUrl")]
pub image_url: String,
pub version: ThumbnailVersion,
pub state: ThumbnailState,
#[serde(rename = "errorMessage")]
pub error: String,
#[serde(rename = "errorCode")]
pub error_code: i32,
}
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
pub struct ThumbnailBatchRequest<'a> {
#[serde(rename = "targetId")]
pub id: u64,
#[serde(rename = "requestId")]
pub request_id: &'a str,
#[serde(rename = "token")]
pub token: &'a str,
#[serde(rename = "alias")]
pub alias: &'a str,
#[serde(rename = "type")]
pub kind: ThumbnailRequestType,
#[serde(rename = "size")]
pub size: ThumbnailSize,
#[serde(rename = "format")]
pub format: ThumbnailFormat,
#[serde(rename = "isCircular")]
pub circular: bool,
}
impl Serialize for ThumbnailSize {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl std::fmt::Display for ThumbnailSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let content = format!("{:?}", self);
write!(f, "{}", content.strip_prefix('S').unwrap())
}
}
impl std::fmt::Display for ThumbnailFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let content = format!("{:?}", self).to_lowercase();
write!(f, "{}", content)
}
}
impl std::fmt::Display for ThumbnailRequestType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::fmt::Display for ReturnPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
async fn generic_thumbnail_api(
client: &mut Client,
ids: &[u64],
asset_name: &str,
domain: &str,
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
return_policy: Option<ReturnPolicy>,
count_per_universe: Option<u32>,
defaults: Option<bool>,
) -> Result<Vec<ThumbnailResponse>, Error> {
let ids = ids
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(",");
let asset_ids_key = format!("{asset_name}Ids");
let mut query = vec![
(asset_ids_key.as_str(), ids),
("size", size.to_string()),
("format", format.to_string()),
("isCircular", circular.to_string()),
];
if let Some(return_policy) = return_policy {
query.push(("returnPolicy", return_policy.to_string()));
}
if let Some(count_per_universe) = count_per_universe {
query.push(("countPerUniverse", count_per_universe.to_string()));
}
if let Some(defaults) = defaults {
query.push(("defaults", defaults.to_string()));
}
let result = client
.requestor
.client
.get(format!("{URL}/{domain}"))
.query(&query)
.headers(client.requestor.default_headers.clone())
.send()
.await;
#[derive(Clone, Debug, Deserialize)]
struct Response {
#[serde(rename = "data")]
thumbnails: Vec<ThumbnailResponse>,
}
let response = client.validate_response(result).await?;
Ok(client
.requestor
.parse_json::<Response>(response)
.await?
.thumbnails)
}
pub async fn assets(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
return_policy: ReturnPolicy,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"asset",
"assets",
size,
format,
circular,
Some(return_policy),
None,
None,
)
.await
}
pub async fn asset_3d(
client: &mut Client,
id: u64,
encode_gltf: bool,
) -> Result<ThumbnailResponse, Error> {
let result = client
.requestor
.client
.get(format!("{URL}/assets-thumbnail-3d"))
.query(&[
("assetId", id.to_string()),
("useGltf", encode_gltf.to_string()),
])
.headers(client.requestor.default_headers.clone())
.send()
.await;
let response = client.validate_response(result).await?;
client
.requestor
.parse_json::<ThumbnailResponse>(response)
.await
}
pub async fn badge_icons(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"badge",
"badges/icons",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn bundles(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"bundle",
"bundles/thumbnails",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn developer_prodcuts(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"developerProduct",
"developer-products/icons",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn gamepasses(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"gamePass",
"game-passes",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn universe_thumbnails(
client: &mut Client,
universe_id: u64,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
return_policy: ReturnPolicy,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"thumbnail",
&format!("games/{universe_id}/thumbnails"),
size,
format,
circular,
Some(return_policy),
None,
None,
)
.await
}
pub async fn games(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
return_policy: ReturnPolicy,
circular: bool,
defaults: bool, count_per_universe: u32, ) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"universe",
"games/multiget/thumbnails",
size,
format,
circular,
Some(return_policy),
Some(count_per_universe),
Some(defaults),
)
.await
}
pub async fn game_icons(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
return_policy: ReturnPolicy,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"universe",
"games/icons",
size,
format,
circular,
Some(return_policy),
None,
None,
)
.await
}
pub async fn group_icons(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"group",
"groups/icons",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn place_icons(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
return_policy: ReturnPolicy,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"place",
"places/gameicons",
size,
format,
circular,
Some(return_policy),
None,
None,
)
.await
}
pub async fn avatars(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"user",
"users/avatar",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn avatar_3d(client: &mut Client, id: u64) -> Result<ThumbnailResponse, Error> {
let result = client
.requestor
.client
.get(format!("{URL}/avatar-3d"))
.query(&[("userId", id)])
.headers(client.requestor.default_headers.clone())
.send()
.await;
let response = client.validate_response(result).await?;
client
.requestor
.parse_json::<ThumbnailResponse>(response)
.await
}
pub async fn avatar_busts(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"user",
"users/avatar-bust",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn avatar_headshots(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"user",
"users/avatar-headshot",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn outfit_3d(client: &mut Client, id: u64) -> Result<ThumbnailResponse, Error> {
let result = client
.requestor
.client
.get(format!("{URL}/outfit-3d"))
.query(&[("outfitId", id)])
.headers(client.requestor.default_headers.clone())
.send()
.await;
let response = client.validate_response(result).await?;
client
.requestor
.parse_json::<ThumbnailResponse>(response)
.await
}
pub async fn outfits(
client: &mut Client,
ids: &[u64],
size: ThumbnailSize,
format: ThumbnailFormat,
circular: bool,
) -> Result<Vec<ThumbnailResponse>, Error> {
generic_thumbnail_api(
client,
ids,
"userOutfit",
"users/outfits",
size,
format,
circular,
None,
None,
None,
)
.await
}
pub async fn batch(
client: &mut Client,
requests: Vec<ThumbnailBatchRequest<'_>>,
) -> Result<Vec<ThumbnailResponseFromBatch>, Error> {
let result = client
.requestor
.client
.post(format!("{URL}/batch"))
.headers(client.requestor.default_headers.clone())
.json(&requests)
.send()
.await;
#[derive(Clone, Debug, Deserialize)]
struct Response {
#[serde(rename = "data")]
thumbnails: Vec<ThumbnailResponseFromBatch>,
}
let response = client.validate_response(result).await?;
Ok(client
.requestor
.parse_json::<Response>(response)
.await?
.thumbnails)
}