Skip to content

Commit

Permalink
add support for image game assets (#66)
Browse files Browse the repository at this point in the history
* add create and partial update support for images
* finished update and delete support for images
* added glob support for asset files
* restructured project fixtures
  • Loading branch information
blake-mealey authored Nov 16, 2021
1 parent d0ecf36 commit 641584b
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ templates:
name: My first badge
description: The best badge of all
icon: badge-1.png
assets:
- assets/*

state:
remote:
Expand Down
31 changes: 0 additions & 31 deletions project-fixtures/multi-places/rocat.yml

This file was deleted.

9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub struct TemplateConfig {
pub passes: Option<HashMap<String, PassTemplateConfig>>,

pub badges: Option<HashMap<String, BadgeConfig>>,

pub assets: Option<Vec<AssetConfig>>,
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -146,6 +148,13 @@ pub struct BadgeConfig {
pub enabled: Option<bool>,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase", untagged)]
pub enum AssetConfig {
File(String),
FileWithAlias { file: String, name: String },
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExperienceTemplateConfig {
Expand Down
107 changes: 104 additions & 3 deletions src/resource_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::{
resources::ResourceManager,
roblox_api::{
CreateBadgeResponse, CreateDeveloperProductResponse, CreateExperienceResponse,
CreateGamePassResponse, CreatePlaceResponse, ExperienceConfigurationModel,
GetDeveloperProductResponse, GetExperienceResponse, GetPlaceResponse,
PlaceConfigurationModel, RobloxApi, UploadImageResponse,
CreateGamePassResponse, CreateImageAssetResponse, CreatePlaceResponse,
ExperienceConfigurationModel, GetDeveloperProductResponse, GetExperienceResponse,
GetPlaceResponse, PlaceConfigurationModel, RobloxApi, UploadImageResponse,
},
roblox_auth::RobloxAuth,
};
Expand All @@ -32,6 +32,8 @@ pub mod resource_types {
pub const GAME_PASS_ICON: &str = "gamePassIcon";
pub const BADGE: &str = "badge";
pub const BADGE_ICON: &str = "badgeIcon";
pub const ASSET_ALIAS: &str = "assetAlias";
pub const IMAGE_ASSET: &str = "imageAsset";
}

pub const SINGLETON_RESOURCE_ID: &str = "singleton";
Expand Down Expand Up @@ -220,6 +222,32 @@ struct BadgeIconOutputs {
asset_id: AssetId,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct AssetAliasInputs {
experience_id: AssetId,
asset_id: AssetId,
name: String,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct AssetAliasOutputs {
name: String,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct ImageAssetInputs {
file_path: String,
file_hash: String,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct ImageAssetOutputs {
asset_id: AssetId,
decal_asset_id: AssetId,
}

pub struct RobloxResourceManager {
roblox_api: RobloxApi,
project_path: PathBuf,
Expand Down Expand Up @@ -504,6 +532,41 @@ impl ResourceManager for RobloxResourceManager {
.map_err(|e| format!("Failed to serialize outputs: {}", e))?,
))
}
resource_types::ASSET_ALIAS => {
let inputs = serde_yaml::from_value::<AssetAliasInputs>(resource_inputs)
.map_err(|e| format!("Failed to deserialize inputs: {}", e))?;

self.roblox_api.create_asset_alias(
inputs.experience_id,
inputs.asset_id,
inputs.name.clone(),
)?;

Ok(Some(
serde_yaml::to_value(AssetAliasOutputs { name: inputs.name })
.map_err(|e| format!("Failed to serialize outputs: {}", e))?,
))
}
resource_types::IMAGE_ASSET => {
let inputs = serde_yaml::from_value::<ImageAssetInputs>(resource_inputs)
.map_err(|e| format!("Failed to deserialize inputs: {}", e))?;

let CreateImageAssetResponse {
asset_id,
backing_asset_id,
..
} = self
.roblox_api
.create_image_asset(self.project_path.join(inputs.file_path).as_path())?;

Ok(Some(
serde_yaml::to_value(ImageAssetOutputs {
asset_id: backing_asset_id,
decal_asset_id: asset_id,
})
.map_err(|e| format!("Failed to serialize outputs: {}", e))?,
))
}
_ => panic!(
"Create not implemented for resource type: {}",
resource_type
Expand Down Expand Up @@ -616,6 +679,25 @@ impl ResourceManager for RobloxResourceManager {
.map_err(|e| format!("Failed to serialize outputs: {}", e))?,
))
}
resource_types::ASSET_ALIAS => {
let inputs = serde_yaml::from_value::<AssetAliasInputs>(resource_inputs)
.map_err(|e| format!("Failed to deserialize inputs: {}", e))?;
let outputs = serde_yaml::from_value::<AssetAliasOutputs>(resource_outputs)
.map_err(|e| format!("Failed to deserialize outputs: {}", e))?;

self.roblox_api.update_asset_alias(
inputs.experience_id,
inputs.asset_id,
outputs.name,
inputs.name.clone(),
)?;

Ok(Some(
serde_yaml::to_value(AssetAliasOutputs { name: inputs.name })
.map_err(|e| format!("Failed to serialize outputs: {}", e))?,
))
}
resource_types::IMAGE_ASSET => self.create(resource_type, resource_inputs),
_ => panic!(
"Update not implemented for resource type: {}",
resource_type
Expand Down Expand Up @@ -772,6 +854,25 @@ impl ResourceManager for RobloxResourceManager {
Ok(())
}
resource_types::BADGE_ICON => Ok(()),
resource_types::ASSET_ALIAS => {
let inputs = serde_yaml::from_value::<AssetAliasInputs>(resource_inputs)
.map_err(|e| format!("Failed to deserialize inputs: {}", e))?;
let outputs = serde_yaml::from_value::<AssetAliasOutputs>(resource_outputs)
.map_err(|e| format!("Failed to deserialize outputs: {}", e))?;

self.roblox_api
.delete_asset_alias(inputs.experience_id, outputs.name)?;

Ok(())
}
resource_types::IMAGE_ASSET => {
let outputs = serde_yaml::from_value::<ImageAssetOutputs>(resource_outputs)
.map_err(|e| format!("Failed to deserialize outputs: {}", e))?;

self.roblox_api.archive_asset(outputs.decal_asset_id)?;

Ok(())
}
_ => panic!(
"Delete not implemented for resource type: {}",
resource_type
Expand Down
113 changes: 113 additions & 0 deletions src/roblox_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ pub struct CreateBadgeResponse {
pub icon_image_id: AssetId,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct CreateImageAssetResponse {
pub success: bool,
pub asset_id: AssetId,
pub backing_asset_id: AssetId,
}

#[derive(Serialize, Deserialize, Clone)]
pub enum ExperienceGenre {
All,
Expand Down Expand Up @@ -1063,4 +1071,109 @@ impl RobloxApi {

Ok(model)
}

pub fn create_asset_alias(
&mut self,
experience_id: AssetId,
asset_id: AssetId,
name: String,
) -> Result<(), String> {
let res = ureq::post(&format!(
"https://develop.roblox.com/v1/universes/{}/aliases",
experience_id
))
.set_auth(AuthType::CookieAndCsrfToken, &mut self.roblox_auth)?
.send_json(json!({
"name": name,
"type": "1",
"targetId": asset_id,
}));

Self::handle_response(res)?;

Ok(())
}

pub fn update_asset_alias(
&mut self,
experience_id: AssetId,
asset_id: AssetId,
previous_name: String,
name: String,
) -> Result<(), String> {
let res = ureq::post("https://api.roblox.com/universes/update-alias-v2")
.query("universeId", &experience_id.to_string())
.query("oldName", &previous_name)
.set_auth(AuthType::CookieAndCsrfToken, &mut self.roblox_auth)?
.send_json(json!({
"name": name,
"type": "1",
"targetId": asset_id,
}));

Self::handle_response(res)?;

Ok(())
}

pub fn delete_asset_alias(
&mut self,
experience_id: AssetId,
name: String,
) -> Result<(), String> {
let res = ureq::post("https://api.roblox.com/universes/delete-alias")
.query("universeId", &experience_id.to_string())
.query("name", &name)
.set_auth(AuthType::CookieAndCsrfToken, &mut self.roblox_auth)?
.send_string("");

Self::handle_response(res)?;

Ok(())
}

pub fn create_image_asset(
&mut self,
file_path: &Path,
) -> Result<CreateImageAssetResponse, String> {
let data = fs::read(file_path).map_err(|e| {
format!(
"Unable to read image asset file: {}\n\t{}",
file_path.display(),
e
)
})?;

let file_name = format!(
"Images/{}",
file_path.file_stem().map(OsStr::to_str).flatten().unwrap()
);
let res = ureq::post("https://data.roblox.com/data/upload/json")
.query("assetTypeId", "13")
.query("name", &file_name)
.query("description", "madewithrocat")
.set("Content-Type", "*/*")
.set_auth(AuthType::CookieAndCsrfToken, &mut self.roblox_auth)?
.send_bytes(&data);

let response = Self::handle_response(res)?;
let model = response
.into_json::<CreateImageAssetResponse>()
.map_err(|e| format!("Failed to deserialize create image asset response: {}", e))?;

Ok(model)
}

pub fn archive_asset(&mut self, asset_id: AssetId) -> Result<(), String> {
let res = ureq::post(&format!(
"https://develop.roblox.com/v1/assets/{}/archive",
asset_id
))
.set_auth(AuthType::CookieAndCsrfToken, &mut self.roblox_auth)?
.send_string("");

Self::handle_response(res)?;

Ok(())
}
}
Loading

0 comments on commit 641584b

Please sign in to comment.