diff --git a/Cargo.lock b/Cargo.lock index 6c2967b..4ff7b9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,7 +110,7 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "asset-tool" -version = "0.4.3" +version = "0.4.4" dependencies = [ "anyhow", "clap", @@ -1166,7 +1166,7 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.2.1" +version = "0.2.2" dependencies = [ "chrono", "flate2", diff --git a/Cargo.toml b/Cargo.toml index a8d9edb..de0b750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ workspace = { members = ["rbx_asset", "rox_compiler"] } [package] name = "asset-tool" -version = "0.4.3" +version = "0.4.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index 36b4112..a440029 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_asset" -version = "0.2.1" +version = "0.2.2" edition = "2021" publish = ["strafesnet"] diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index 90ce74e..4206497 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -37,22 +37,27 @@ pub struct UpdateAssetRequest{ //woo nested roblox stuff #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] -pub struct Creator{ - pub userId:u64, - pub groupId:u64, +pub enum Creator{ + userId(String),//u64 string + groupId(String),//u64 string } #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] pub struct CreationContext{ pub creator:Creator, - pub expectedPrice:u64, + pub expectedPrice:Option, } #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] -pub enum ModerationResult{ - MODERATION_STATE_REVIEWING, - MODERATION_STATE_REJECTED, - MODERATION_STATE_APPROVED, +pub enum ModerationState{ + Reviewing, + Rejected, + Approved, +} +#[derive(Debug,serde::Deserialize,serde::Serialize)] +#[allow(nonstandard_style,dead_code)] +pub struct ModerationResult{ + pub moderationState:ModerationState, } #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -83,6 +88,51 @@ impl std::fmt::Display for UpdateError{ } impl std::error::Error for UpdateError{} +#[allow(nonstandard_style,dead_code)] +pub struct GetAssetInfoRequest{ + pub asset_id:u64, +} +/* +{ + "assetId": "5692158972", + "assetType": "Model", + "creationContext":{ + "creator": + { + "groupId": "6980477" + } + }, + "description": "DisplayName: Ares\nCreator: titanicguy54", + "displayName": "bhop_ares.rbxmx", + "path": "assets/5692158972", + "revisionCreateTime": "2020-09-14T16:08:05.063Z", + "revisionId": "1", + "moderationResult":{ + "moderationState": "Approved" + }, + "state": "Active" +} +*/ +#[derive(Debug,serde::Deserialize,serde::Serialize)] +#[allow(nonstandard_style,dead_code)] +pub struct AssetResponse{ + pub assetId:String,//u64 wrapped in quotes wohoo!! + pub assetType:AssetType, + pub creationContext:CreationContext, + pub description:String, + pub displayName:String, + pub path:String, + pub revisionCreateTime:chrono::DateTime, + pub revisionId:String,//u64 + pub moderationResult:ModerationResult, + pub icon:Option, + pub previews:Option>, +} +#[allow(nonstandard_style,dead_code)] +pub struct GetAssetVersionRequest{ + pub asset_id:u64, + pub version:u64, +} #[allow(nonstandard_style,dead_code)] pub struct GetAssetRequest{ pub asset_id:u64, @@ -278,19 +328,21 @@ impl CloudContext{ Ok(resp.json::().await.map_err(UpdateError::Reqwest)?) } - pub async fn get_asset(&self,config:GetAssetRequest)->Result,GetError>{ - let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/").map_err(GetError::ParseError)?; - //url borrow scope - { - let mut query=url.query_pairs_mut();//borrow here - query.append_pair("ID",config.asset_id.to_string().as_str()); - if let Some(version)=config.version{ - query.append_pair("version",version.to_string().as_str()); - } - } - let resp=self.get(url).await.map_err(GetError::Reqwest)?; + pub async fn get_asset_info(&self,config:GetAssetInfoRequest)->Result{ + let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}",config.asset_id); + let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - let body=resp.bytes().await.map_err(GetError::Reqwest)?; + Ok(self.get(url).await.map_err(GetError::Reqwest)? + .error_for_status().map_err(GetError::Reqwest)? + .json::().await.map_err(GetError::Reqwest)?) + } + pub async fn get_asset_version(&self,config:GetAssetVersionRequest)->Result,GetError>{ + let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions/{}",config.asset_id,config.version); + let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; + + let body=self.get(url).await.map_err(GetError::Reqwest)? + .error_for_status().map_err(GetError::Reqwest)? + .bytes().await.map_err(GetError::Reqwest)?; match maybe_gzip_decode(&mut std::io::Cursor::new(body)){ Ok(ReaderType::GZip(readable))=>read_readable(readable), @@ -298,11 +350,22 @@ impl CloudContext{ Err(e)=>Err(e), }.map_err(GetError::IO) } + pub async fn get_asset(&self,config:GetAssetRequest)->Result,GetError>{ + let version=match config.version{ + Some(version)=>version, + None=>self.get_asset_info(GetAssetInfoRequest{asset_id:config.asset_id}).await?.revisionId.parse().unwrap(), + }; + self.get_asset_version(GetAssetVersionRequest{ + asset_id:config.asset_id, + version, + }).await + } pub async fn get_asset_versions(&self,config:AssetVersionsRequest)->Result{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions",config.asset_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(AssetVersionsError::ParseError)?; Ok(self.get(url).await.map_err(AssetVersionsError::Reqwest)? + .error_for_status().map_err(AssetVersionsError::Reqwest)? .json::().await.map_err(AssetVersionsError::Reqwest)?) } pub async fn inventory_page(&self,config:InventoryPageRequest)->Result{ @@ -316,6 +379,7 @@ impl CloudContext{ } Ok(self.get(url).await.map_err(InventoryPageError::Reqwest)? + .error_for_status().map_err(InventoryPageError::Reqwest)? .json::().await.map_err(InventoryPageError::Reqwest)?) } pub async fn update_place(&self,config:UpdatePlaceRequest,body:impl Into+Clone)->Result{ @@ -327,9 +391,8 @@ impl CloudContext{ query.append_pair("versionType","Published"); } - let resp=self.post(url,body).await.map_err(UpdateError::Reqwest)? - .error_for_status().map_err(UpdateError::Reqwest)?; - - Ok(resp.json::().await.map_err(UpdateError::Reqwest)?) + Ok(self.post(url,body).await.map_err(UpdateError::Reqwest)? + .error_for_status().map_err(UpdateError::Reqwest)? + .json::().await.map_err(UpdateError::Reqwest)?) } } diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index 005d36f..b0c8e72 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -234,9 +234,9 @@ impl CookieContext{ } } - let resp=self.post(url,body).await.map_err(CreateError::PostError)?; - - Ok(resp.json::().await.map_err(CreateError::Reqwest)?) + Ok(self.post(url,body).await.map_err(CreateError::PostError)? + .error_for_status().map_err(CreateError::Reqwest)? + .json::().await.map_err(CreateError::Reqwest)?) } pub async fn upload(&self,config:UploadRequest,body:impl Into+Clone)->Result{ let mut url=reqwest::Url::parse("https://data.roblox.com/Data/Upload.ashx?json=1&type=Model&genreTypeId=1").map_err(UploadError::ParseError)?; @@ -265,9 +265,9 @@ impl CookieContext{ } } - let resp=self.post(url,body).await.map_err(UploadError::PostError)?; - - Ok(resp.json::().await.map_err(UploadError::Reqwest)?) + Ok(self.post(url,body).await.map_err(UploadError::PostError)? + .error_for_status().map_err(UploadError::Reqwest)? + .json::().await.map_err(UploadError::Reqwest)?) } pub async fn get_asset(&self,config:GetAssetRequest)->Result,GetError>{ let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/").map_err(GetError::ParseError)?; @@ -279,9 +279,9 @@ impl CookieContext{ query.append_pair("version",version.to_string().as_str()); } } - let resp=self.get(url).await.map_err(GetError::Reqwest)?; - - let body=resp.bytes().await.map_err(GetError::Reqwest)?; + let body=self.get(url).await.map_err(GetError::Reqwest)? + .error_for_status().map_err(GetError::Reqwest)? + .bytes().await.map_err(GetError::Reqwest)?; match maybe_gzip_decode(&mut std::io::Cursor::new(body)){ Ok(ReaderType::GZip(readable))=>read_readable(readable), @@ -303,6 +303,7 @@ impl CookieContext{ } Ok(self.get(url).await.map_err(AssetVersionsPageError::Reqwest)? + .error_for_status().map_err(AssetVersionsPageError::Reqwest)? .json::().await.map_err(AssetVersionsPageError::Reqwest)?) } pub async fn get_inventory_page(&self,config:InventoryPageRequest)->Result{ @@ -316,6 +317,7 @@ impl CookieContext{ } Ok(self.get(url).await.map_err(InventoryPageError::Reqwest)? + .error_for_status().map_err(InventoryPageError::Reqwest)? .json::().await.map_err(InventoryPageError::Reqwest)?) } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index b014d63..6a76fc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{io::Read,path::PathBuf}; use clap::{Args,Parser,Subcommand}; -use anyhow::Result as AResult; +use anyhow::{anyhow,Result as AResult}; use futures::StreamExt; use rbx_asset::cloud::{ApiKey,CloudContext}; use rbx_asset::cookie::{Cookie,CookieContext,AssetVersion,InventoryItem}; @@ -58,12 +58,12 @@ struct DownloadHistorySubcommand{ } #[derive(Args)] struct DownloadSubcommand{ - #[arg(long,group="api_key",required=true)] - api_key_literal:Option, - #[arg(long,group="api_key",required=true)] - api_key_envvar:Option, - #[arg(long,group="api_key",required=true)] - api_key_file:Option, + #[arg(long,group="cookie",required=true)] + cookie_literal:Option, + #[arg(long,group="cookie",required=true)] + cookie_envvar:Option, + #[arg(long,group="cookie",required=true)] + cookie_file:Option, #[arg(long)] output_folder:Option, #[arg(required=true)] @@ -119,9 +119,9 @@ struct CreateAssetMediaSubcommand{ input_file:PathBuf, #[arg(long)] asset_type:AssetType, - #[arg(long)] - creator_user_id:u64, - #[arg(long)] + #[arg(long,group="creator",required=true)] + creator_user_id:Option, + #[arg(long,group="creator",required=true)] creator_group_id:Option, /// Expected price limits how much robux can be spent to create the asset (defaults to 0) #[arg(long)] @@ -358,10 +358,10 @@ async fn main()->AResult<()>{ Commands::Download(subcommand)=>{ let output_folder=subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()); download_list( - api_key_from_args( - subcommand.api_key_literal, - subcommand.api_key_envvar, - subcommand.api_key_file, + cookie_from_args( + subcommand.cookie_literal, + subcommand.cookie_envvar, + subcommand.cookie_file, ).await?, subcommand.asset_ids.into_iter().map(|asset_id|{ let mut path=output_folder.clone(); @@ -413,8 +413,11 @@ async fn main()->AResult<()>{ subcommand.api_key_envvar, subcommand.api_key_file, ).await?, - creator_user_id:subcommand.creator_user_id, - creator_group_id:subcommand.creator_group_id, + creator:match (subcommand.creator_user_id,subcommand.creator_group_id){ + (Some(user_id),None)=>rbx_asset::cloud::Creator::userId(user_id.to_string()), + (None,Some(group_id))=>rbx_asset::cloud::Creator::groupId(group_id.to_string()), + other=>Err(anyhow!("Invalid creator {other:?}"))?, + }, input_file:subcommand.input_file, asset_type:subcommand.asset_type.cloud(), model_name:subcommand.model_name, @@ -568,8 +571,7 @@ struct CreateAssetMediaConfig{ model_name:String, description:String, input_file:PathBuf, - creator_user_id:u64, - creator_group_id:Option, + creator:rbx_asset::cloud::Creator, expected_price:Option, } @@ -580,11 +582,8 @@ async fn create_asset_media(config:CreateAssetMediaConfig)->AResult<()>{ displayName:config.model_name, description:config.description, creationContext:rbx_asset::cloud::CreationContext{ - creator:rbx_asset::cloud::Creator{ - userId:config.creator_user_id, - groupId:config.creator_group_id.unwrap_or(0), - }, - expectedPrice:config.expected_price.unwrap_or(0), + creator:config.creator, + expectedPrice:Some(config.expected_price.unwrap_or(0)), } },tokio::fs::read(config.input_file).await?).await?; println!("CreateResponse={:?}",resp); @@ -646,13 +645,13 @@ async fn upload_place(config:UploadPlaceConfig)->AResult<()>{ Ok(()) } -async fn download_list(api_key:ApiKey,asset_id_file_map:AssetIDFileMap)->AResult<()>{ - let context=CloudContext::new(api_key); +async fn download_list(cookie:Cookie,asset_id_file_map:AssetIDFileMap)->AResult<()>{ + let context=CookieContext::new(cookie); futures::stream::iter(asset_id_file_map.into_iter() .map(|(asset_id,file)|{ let context=&context; async move{ - Ok((file,context.get_asset(rbx_asset::cloud::GetAssetRequest{asset_id,version:None}).await?)) + Ok((file,context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id,version:None}).await?)) } })) .buffer_unordered(CONCURRENT_REQUESTS) @@ -1200,9 +1199,10 @@ async fn compile_upload_place(config:CompileUploadPlaceConfig)->AResult<()>{ //upload it let context=CloudContext::new(config.api_key); - context.update_place(rbx_asset::cloud::UpdatePlaceRequest{ + let resp=context.update_place(rbx_asset::cloud::UpdatePlaceRequest{ universeId:config.universe_id, placeId:config.place_id, },data).await?; + println!("UploadResponse={:?}",resp); Ok(()) }