diff --git a/src/main.rs b/src/main.rs index 3e11666..568721c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,6 +137,12 @@ struct CreateAssetMediasSubcommand{ 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)] description:Option, #[arg(long,group="creator",required=true)] @@ -451,6 +457,11 @@ async fn main()->AResult<()>{ subcommand.api_key_envvar, subcommand.api_key_file, ).await?, + cookie:cookie_from_args( + subcommand.cookie_literal, + subcommand.cookie_envvar, + subcommand.cookie_file, + ).await?, 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()), @@ -650,8 +661,10 @@ async fn create_asset_media(config:CreateAssetMediaConfig)->AResult<()>{ Ok(()) } +// complex operation requires both api key and cookie! how horrible! roblox please fix! struct CreateAssetMediasConfig{ api_key:ApiKey, + cookie:Cookie, description:String, input_files:Vec, creator:rbx_asset::cloud::Creator, @@ -670,19 +683,41 @@ impl std::fmt::Display for CreateAssetMediasError{ } impl std::error::Error for CreateAssetMediasError{} +#[derive(Debug)] +enum DownloadDecalError{ + ParseInt(std::num::ParseIntError), + Get(rbx_asset::cookie::GetError), + LoadDom(LoadDomError), + FromUtf8(std::string::FromUtf8Error), + NoFirstInstance, + NoTextureProperty, + TexturePropertyInvalid, +} +impl std::fmt::Display for DownloadDecalError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for DownloadDecalError{} + async fn create_asset_medias(config:CreateAssetMediasConfig)->AResult<()>{ let context=CloudContext::new(config.api_key); + let cookie_context=CookieContext::new(config.cookie); + let expected_price=Some(config.expected_price.unwrap_or(0)); futures::stream::iter(config.input_files.into_iter() //step 1: read file, make create request .map(|path|{ - let config=&config; + let description=&config.description; + let creator=&config.creator; let context=&context; async move{ let model_name=path.file_stem() .and_then(std::ffi::OsStr::to_str) - .ok_or(CreateAssetMediasError::NoFileStem(path.clone()))?; + .ok_or(CreateAssetMediasError::NoFileStem(path.clone()))? + .to_owned(); let file=tokio::fs::read(path).await?; let asset_type=match file.get(0..4){ + //png Some(b"\x89PNG")=>rbx_asset::cloud::AssetType::Decal, //jpeg Some(b"\xFF\xD8\xFF\xE0")=>rbx_asset::cloud::AssetType::Decal, @@ -692,11 +727,11 @@ async fn create_asset_medias(config:CreateAssetMediasConfig)->AResult<()>{ }; Ok(context.create_asset(rbx_asset::cloud::CreateAssetRequest{ assetType:asset_type, - displayName:model_name.to_owned(), - description:config.description.clone(), + displayName:model_name, + description:description.clone(), creationContext:rbx_asset::cloud::CreationContext{ - creator:config.creator, - expectedPrice:Some(config.expected_price.unwrap_or(0)), + creator:creator.clone(), + expectedPrice:expected_price, } },file).await?) } @@ -723,8 +758,31 @@ async fn create_asset_medias(config:CreateAssetMediasConfig)->AResult<()>{ } }) //step 3: read decal id from operation and download it - .filter_map(|operation|{ - + .filter_map(|asset_response|{ + let parse_result=asset_response.assetId.parse(); + async{ + match async{ + let file=cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ + asset_id:parse_result.map_err(DownloadDecalError::ParseInt)?, + version:None, + }).await.map_err(DownloadDecalError::Get)?; + println!("{}",String::from_utf8(file.clone()).map_err(DownloadDecalError::FromUtf8)?); + let dom=load_dom(std::io::Cursor::new(file)).map_err(DownloadDecalError::LoadDom)?; + let instance=dom.get_by_ref( + *dom.root().children().first().ok_or(DownloadDecalError::NoFirstInstance)? + ).ok_or(DownloadDecalError::NoFirstInstance)?; + match instance.properties.get("Texture").ok_or(DownloadDecalError::NoTextureProperty)?{ + rbx_dom_weak::types::Variant::String(s)=>Ok(s.clone()), + _=>Err(DownloadDecalError::TexturePropertyInvalid), + } + }.await{ + Ok(yeah)=>Some(yeah), + Err(e)=>{ + eprintln!("get_asset error: {}",e); + None + }, + } + } }); Ok(()) } @@ -974,18 +1032,33 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{ Ok(()) } -fn load_dom(input:R)->AResult{ +#[derive(Debug)] +enum LoadDomError{ + IO(std::io::Error), + RbxBinary(rbx_binary::DecodeError), + RbxXml(rbx_xml::DecodeError), + UnknownRobloxFile([u8;4]), + UnsupportedFile, +} +impl std::fmt::Display for LoadDomError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for LoadDomError{} + +fn load_dom(input:R)->Result{ let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf)?; + let peek=std::io::BufRead::fill_buf(&mut buf).map_err(LoadDomError::IO)?; match &peek[0..4]{ b"{ match &peek[4..8]{ - b"lox!"=>rbx_binary::from_reader(buf).map_err(anyhow::Error::msg), - b"lox "=>rbx_xml::from_reader_default(buf).map_err(anyhow::Error::msg), - other=>Err(anyhow::Error::msg(format!("Unknown Roblox file type {:?}",other))), + b"lox!"=>rbx_binary::from_reader(buf).map_err(LoadDomError::RbxBinary), + b"lox "=>rbx_xml::from_reader_default(buf).map_err(LoadDomError::RbxXml), + other=>Err(LoadDomError::UnknownRobloxFile(other.try_into().unwrap())), } }, - _=>Err(anyhow::Error::msg("unsupported file type")), + _=>Err(LoadDomError::UnsupportedFile), } }