diff --git a/src/main.rs b/src/main.rs index a6316cc..f924f0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ struct Cli{ enum Commands{ DownloadHistory(DownloadHistorySubcommand), Download(DownloadSubcommand), + Create(CreateSubcommand), Upload(UploadSubcommand), Compile(CompileSubcommand), Decompile(DecompileSubcommand), @@ -58,6 +59,19 @@ struct DownloadSubcommand{ asset_ids:Vec, } #[derive(Args)] +struct CreateSubcommand{ + #[arg(long)] + cookie_type:CookieType, + #[arg(long)] + cookie:String, + #[arg(long)] + model_name:String, + #[arg(long)] + input_file:PathBuf, + #[arg(long)] + group:Option, +} +#[derive(Args)] struct UploadSubcommand{ #[arg(long)] asset_id:AssetID, @@ -196,6 +210,12 @@ async fn main()->AResult<()>{ }).collect() ).await }, + Commands::Create(subcommand)=>create( + Cookie::from_type(subcommand.cookie_type,subcommand.cookie).await?.0, + subcommand.group, + subcommand.input_file, + subcommand.model_name, + ).await, Commands::Upload(subcommand)=>upload_list( Cookie::from_type(subcommand.cookie_type,subcommand.cookie).await?.0, subcommand.group, @@ -264,6 +284,51 @@ fn maybe_gzip_decode(input:R)->AResult>{ } } +async fn create(cookie:String,group:Option,file:PathBuf,model_name:String)->AResult<()>{ + let client=reqwest::Client::new(); + let client=&client; + let cookie=cookie.as_str(); + let group=&group; + let mut url=reqwest::Url::parse("https://data.roblox.com/Data/Upload.ashx?json=1&type=Model&genreTypeId=1&description&ispublic=False&allowComments=False")?; + //url borrow scope + { + let mut query=url.query_pairs_mut();//borrow here + //archaic roblox api uses 0 for new asset + query.append_pair("assetid","0"); + query.append_pair("name",model_name.as_str()); + match group{ + Some(group_id)=>{query.append_pair("groupId",group_id.to_string().as_str());}, + None=>(), + } + } + + let body=tokio::fs::read(file).await?; + let mut resp=client.post(url.clone()) + .header("Cookie",cookie) + .body(body.clone()) + .send().await?; + + //This is called a CSRF challenge apparently + if resp.status()==reqwest::StatusCode::FORBIDDEN{ + if let Some(csrf_token)=resp.headers().get("X-CSRF-Token"){ + resp=client.post(url) + .header("X-CSRF-Token",csrf_token) + .header("Cookie",cookie) + .body(body) + .send().await?; + }else{ + Err(anyhow::Error::msg("Roblox returned 403 with no CSRF"))?; + } + } + let body=match resp.status(){ + reqwest::StatusCode::OK=>Ok(resp.bytes().await?), + other=>Err(anyhow::Error::msg(other)), + }; + + println!("response.body={:?}",body?); + Ok(()) +} + async fn upload_list(cookie:String,group:Option,asset_id_file_map:AssetIDFileMap)->AResult<()>{ let client=reqwest::Client::new(); //this is calling map on the vec because the closure produces an iterator of futures