diff --git a/src/main.rs b/src/main.rs index 9ba9453..4380be8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ struct Cli{ #[derive(Subcommand)] enum Commands{ + DownloadHistory, Download, Upload, Compile, @@ -48,6 +49,24 @@ struct PathBufList{ paths:Vec } +#[derive(serde::Deserialize)] +struct VersionPage{ + previousPageCursor:Option, + nextPageCursor:Option, + data:Vec, +} +#[derive(serde::Deserialize,serde::Serialize)] +struct AssetVersion{ + Id:u64, + assetId:AssetID, + assetVersionNumber:u64, + creatorType:String, + creatorTargetId:u64, + creatingUniverseId:Option, + created:chrono::DateTime, + isPublished:bool, +} + #[tokio::main] async fn main()->AResult<()>{ let cli=Cli::parse(); @@ -71,6 +90,7 @@ async fn main()->AResult<()>{ }; match cli.command{ + Commands::DownloadHistory=>download_history(cookie.unwrap(),cli.asset_id.unwrap()).await, Commands::Download=>download_list(cookie.unwrap(),vec![(cli.asset_id.unwrap(),cli.output.unwrap())]).await, Commands::Upload=>upload_list(cookie.unwrap(),cli.group,vec![(cli.asset_id.unwrap(),cli.output.unwrap())]).await, Commands::Compile=>compile(cli.input.unwrap(),cli.output.unwrap()), @@ -196,6 +216,103 @@ async fn download_list(cookie:String,asset_id_file_map:AssetIDFileMap)->AResult< Ok(()) } +async fn download_history(cookie:String,asset_id:AssetID)->AResult<()>{ + let client=reqwest::Client::new(); + let asset_id_string=asset_id.to_string(); + + //poll paged list of all asset versions + let mut cursor:Option=None; + let mut asset_list=Vec::new(); + loop{ + let mut url=reqwest::Url::parse(format!("https://develop.roblox.com/v1/assets/{}/saved-versions",asset_id).as_str())?; + //url borrow scope + { + let mut query=url.query_pairs_mut();//borrow here + //query.append_pair("sortOrder","Asc"); + //query.append_pair("limit","100"); + //query.append_pair("count","100"); + match &cursor{ + Some(next_page)=>{query.append_pair("cursor",next_page);} + None=>(), + } + } + println!("page url={}",url); + let resp=client.get(url) + .header("Cookie",cookie.clone()) + .send().await?; + match resp.json::().await{ + Ok(mut page)=>{ + asset_list.append(&mut page.data); + if page.nextPageCursor.is_none(){ + break; + } + cursor=page.nextPageCursor; + }, + Err(e)=>panic!("error: {}",e), + } + } + asset_list.sort_by(|a,b|a.assetVersionNumber.cmp(&b.assetVersionNumber)); + let mut path=std::path::PathBuf::new(); + path.set_file_name("versions.json"); + tokio::fs::write(path,serde_json::to_string(&asset_list)?).await?; + + //download all versions + futures::stream::iter(asset_list) + .map(|asset_version|{ + let client=&client; + let cookie=cookie.as_str(); + let asset_id_str=asset_id_string.as_str(); + async move{ + let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/")?; + //url borrow scope + { + let mut query=url.query_pairs_mut();//borrow here + query.append_pair("ID",asset_id_str); + query.append_pair("version",asset_version.assetVersionNumber.to_string().as_str()); + } + println!("download url={}",url); + let mut result=Err(anyhow::Error::msg("all requests failed")); + for i in 1..=8{ + let resp=client.get(url.clone()) + .header("Cookie",cookie) + .send().await?; + + if !resp.status().is_success(){ + println!("request {} failed",i); + continue; + } + + let mut path=std::path::PathBuf::new(); + path.set_file_name(format!("{}_v{}.rbxl",asset_id,asset_version.assetVersionNumber)); + result=Ok((path,resp.bytes().await?)); + break; + } + result + } + }) + .buffer_unordered(CONCURRENT_REQUESTS) + .for_each(|b:AResult<_>|async{ + match b{ + Ok((dest,body))=>{ + let contents=match maybe_gzip_decode(&mut std::io::Cursor::new(body)){ + Ok(ReaderType::GZip(readable))=>read_readable(readable), + Ok(ReaderType::Raw(readable))=>read_readable(readable), + Err(e)=>Err(e), + }; + match contents{ + Ok(data)=>match tokio::fs::write(dest,data).await{ + Err(e)=>eprintln!("fs error: {}",e), + _=>(), + }, + Err(e)=>eprintln!("gzip error: {}",e), + }; + }, + Err(e)=>eprintln!("dl error: {}",e), + } + }).await; + Ok(()) +} + fn load_dom(input:&mut R)->AResult{ let mut first_8=[0u8;8]; if let (Ok(()),Ok(()))=(std::io::Read::read_exact(input, &mut first_8),std::io::Seek::rewind(input)){