use serde::{Deserialize, Serialize, Serializer};
use strum::{EnumIter, IntoEnumIterator};
use strum_macros::{Display, EnumString};
use crate::endpoint;
pub const URL: &str = "https://thumbnails.roblox.com/v1";
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, EnumIter)]
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, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
pub enum ThumbnailFormat {
#[default]
Png,
Jpeg,
Webp,
}
#[derive(Clone, Default, Debug, PartialEq, Eq, Display, EnumString)]
pub enum ReturnPolicy {
#[default]
PlaceHolder,
ForcePlaceHolder,
AutoGenerated,
ForceAutoGenerated,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
pub enum ThumbnailState {
Pending,
Blocked,
Completed,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
pub enum ThumbnailVersion {
TN3,
#[serde(rename = "TN3.5")]
TN3_5,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
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, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ThumbnailResponse {
#[serde(rename = "targetId")]
pub id: u64,
pub image_url: String,
pub version: ThumbnailVersion,
pub state: ThumbnailState,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ThumbnailResponseFromBatch {
#[serde(rename = "targetId")]
pub id: u64,
pub request_id: String,
pub image_url: String,
pub version: ThumbnailVersion,
pub state: ThumbnailState,
#[serde(rename = "errorMessage")]
pub error: String,
pub error_code: i32,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ThumbnailBatchRequest<'a> {
#[serde(rename = "targetId")]
pub id: u64,
pub request_id: &'a str,
pub token: &'a str,
pub alias: &'a str,
#[serde(rename = "type")]
pub kind: ThumbnailRequestType,
pub size: ThumbnailSize,
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 TryFrom<&str> for ThumbnailSize {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
for size in ThumbnailSize::iter() {
if size.to_string().as_str() == value {
return Ok(size);
}
}
Err("Failed to convert string to ThumbnailSize")
}
}
impl ThumbnailFormat {
pub fn extension(&self) -> &str {
match self {
ThumbnailFormat::Png => "png",
ThumbnailFormat::Jpeg => "jpeg",
ThumbnailFormat::Webp => "webp",
}
}
}
endpoint! {
assets(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/assets";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let return_policy = return_policy.to_string();
let circular = circular.to_string();
}
query {
"assetIds" => &joined_ids,
"size" => &size,
"format" => &format,
"returnPolicy" => &return_policy,
"isCircular" => &circular,
}
map |r: Response| r.data
}
asset_3d(id: u64, encode_gltf: bool) -> ThumbnailResponse {
GET "{URL}/assets-thumbnail-3d";
prelude {
let id = id.to_string();
let encode_gltf = encode_gltf.to_string();
}
query {
"assetId" => &id,
"useGltf" => &encode_gltf,
}
}
badge_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/badges/icons";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"badgeIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
bundles(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/bundles/thumbnails";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"bundleIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
developer_products(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/developer-products/icons";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"developerProductIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
gamepasses(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/game-passes";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"gamePassIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
universe_thumbnails(universe_id: u64, ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/games/{universe_id}/thumbnails";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let return_policy = return_policy.to_string();
let circular = circular.to_string();
}
query {
"thumbnailIds" => &joined_ids,
"size" => &size,
"format" => &format,
"returnPolicy" => &return_policy,
"isCircular" => &circular,
}
map |r: Response| r.data
}
games(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool, defaults: bool, count_per_universe: u32) -> Vec<ThumbnailResponse> {
GET "{URL}/games/multiget/thumbnails";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let return_policy = return_policy.to_string();
let circular = circular.to_string();
let defaults = defaults.to_string();
let count_per_universe = count_per_universe.to_string();
}
query {
"universeIds" => &joined_ids,
"size" => &size,
"format" => &format,
"returnPolicy" => &return_policy,
"isCircular" => &circular,
"defaults" => &defaults,
"countPerUniverse" => &count_per_universe,
}
map |r: Response| r.data
}
game_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/games/icons";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let return_policy = return_policy.to_string();
let circular = circular.to_string();
}
query {
"universeIds" => &joined_ids,
"size" => &size,
"format" => &format,
"returnPolicy" => &return_policy,
"isCircular" => &circular,
}
map |r: Response| r.data
}
group_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/groups/icons";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"groupIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
place_icons(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, return_policy: ReturnPolicy, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/places/gameicons";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let return_policy = return_policy.to_string();
let circular = circular.to_string();
}
query {
"placeIds" => &joined_ids,
"size" => &size,
"format" => &format,
"returnPolicy" => &return_policy,
"isCircular" => &circular,
}
map |r: Response| r.data
}
avatars(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/users/avatar";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"userIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
avatar_3d(id: u64) -> ThumbnailResponse {
GET "{URL}/avatar-3d";
prelude {
let id = id.to_string();
}
query {
"userId" => &id,
}
}
avatar_busts(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/users/avatar-bust";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"userIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
avatar_headshots(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/users/avatar-headshot";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"userIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
outfit_3d(id: u64) -> ThumbnailResponse {
GET "{URL}/outfit-3d";
prelude {
let id = id.to_string();
}
query {
"outfitId" => &id,
}
}
outfits(ids: &[u64], size: ThumbnailSize, format: ThumbnailFormat, circular: bool) -> Vec<ThumbnailResponse> {
GET "{URL}/users/outfits";
types {
Response { data("data"): Vec<ThumbnailResponse> }
}
prelude {
let joined_ids = ids.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");
let size = size.to_string();
let format = format.to_string();
let circular = circular.to_string();
}
query {
"userOutfitIds" => &joined_ids,
"size" => &size,
"format" => &format,
"isCircular" => &circular,
}
map |r: Response| r.data
}
batch(requests: Vec<ThumbnailBatchRequest<'_>>) -> Vec<ThumbnailResponseFromBatch> {
POST "{URL}/batch";
types {
Response { data("data"): Vec<ThumbnailResponseFromBatch> }
}
body_serialize { &requests }
map |r: Response| r.data
}
}