diff --git a/Cargo.lock b/Cargo.lock index 978e0ae..3f60a23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,7 +1293,7 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.3.4" +version = "0.4.2" dependencies = [ "chrono", "flate2", diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index 42ee64b..e652476 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_asset" -version = "0.3.4" +version = "0.4.2" edition = "2021" publish = ["strafesnet"] repository = "https://git.itzana.me/StrafesNET/asset-tool" diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index e94c1aa..f65c7aa 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -1,4 +1,4 @@ -use crate::ResponseError; +use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable}; #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -31,7 +31,7 @@ pub struct AssetOperation{ operation:RobloxOperation, } impl AssetOperation{ - pub async fn try_get_asset(&self,context:&CloudContext)->Result<AssetResponse,AssetOperationError>{ + pub async fn try_get_asset(&self,context:&Context)->Result<AssetResponse,AssetOperationError>{ serde_json::from_value( self.operation .try_get_reponse(context).await @@ -119,7 +119,7 @@ impl std::error::Error for UpdateError{} struct GetAssetOperationRequest{ operation_id:String, } -pub struct GetAssetInfoRequest{ +pub struct GetAssetLatestRequest{ pub asset_id:u64, } /* @@ -149,25 +149,21 @@ pub struct AssetResponse{ pub assetId:String,//u64 wrapped in quotes wohoo!! pub assetType:AssetType, pub creationContext:CreationContext, - pub description:String, + pub description:Option<String>, pub displayName:String, pub path:String, pub revisionCreateTime:chrono::DateTime<chrono::Utc>, pub revisionId:String,//u64 pub moderationResult:ModerationResult, pub icon:Option<String>, - pub previews:Option<Vec<Preview>>, + #[serde(default)] + pub previews:Vec<Preview>, } #[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, - pub version:Option<u64>, -} #[derive(Debug)] pub enum GetError{ ParseError(url::ParseError), @@ -182,6 +178,23 @@ impl std::fmt::Display for GetError{ } impl std::error::Error for GetError{} +#[derive(Debug,serde::Deserialize)] +#[allow(nonstandard_style,dead_code)] +pub struct AssetLocation{ + // this field is private so users cannot mutate it + location:String, + pub requestId:String, + pub IsHashDynamic:bool, + pub IsCopyrightProtected:bool, + pub isArchived:bool, + pub assetTypeId:u32, +} +impl AssetLocation{ + pub fn location(&self)->&str{ + &self.location + } +} + pub struct AssetVersionsRequest{ pub asset_id:u64, pub cursor:Option<String>, @@ -286,7 +299,7 @@ impl RobloxOperation{ None=>self.path.as_deref()?.get(11..), } } - pub async fn try_get_reponse(&self,context:&CloudContext)->Result<serde_json::Value,OperationError>{ + pub async fn try_get_reponse(&self,context:&Context)->Result<serde_json::Value,OperationError>{ context.get_asset_operation(GetAssetOperationRequest{ operation_id:self.operation_id() .ok_or(OperationError::NoOperationId)? @@ -296,25 +309,6 @@ impl RobloxOperation{ } } -//idk how to do this better -enum ReaderType<R:std::io::Read>{ - GZip(flate2::read::GzDecoder<std::io::BufReader<R>>), - Raw(std::io::BufReader<R>), -} -fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{ - let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf)?; - match &peek[0..2]{ - b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))), - _=>Ok(ReaderType::Raw(buf)), - } -} -fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{ - let mut contents=Vec::new(); - readable.read_to_end(&mut contents)?; - Ok(contents) -} - #[derive(Clone)] pub struct ApiKey(String); impl ApiKey{ @@ -327,12 +321,12 @@ impl ApiKey{ } #[derive(Clone)] -pub struct CloudContext{ - pub api_key:String, - pub client:reqwest::Client, +pub struct Context{ + api_key:String, + client:reqwest::Client, } -impl CloudContext{ +impl Context{ pub fn new(api_key:ApiKey)->Self{ Self{ api_key:api_key.get(), @@ -412,7 +406,7 @@ impl CloudContext{ ).await.map_err(GetError::Response)? .json::<RobloxOperation>().await.map_err(GetError::Reqwest) } - pub async fn get_asset_info(&self,config:GetAssetInfoRequest)->Result<AssetResponse,GetError>{ + pub async fn get_asset_info(&self,config:GetAssetLatestRequest)->Result<AssetResponse,GetError>{ 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)?; @@ -421,31 +415,47 @@ impl CloudContext{ ).await.map_err(GetError::Response)? .json::<AssetResponse>().await.map_err(GetError::Reqwest) } - pub async fn get_asset_version(&self,config:GetAssetVersionRequest)->Result<Vec<u8>,GetError>{ + pub async fn get_asset_version_info(&self,config:GetAssetVersionRequest)->Result<AssetResponse,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)?; + crate::response_ok( + self.get(url).await.map_err(GetError::Reqwest)? + ).await.map_err(GetError::Response)? + .json::<AssetResponse>().await.map_err(GetError::Reqwest) + } + pub async fn get_asset_location(&self,config:GetAssetLatestRequest)->Result<AssetLocation,GetError>{ + let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}",config.asset_id); + let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; + + crate::response_ok( + self.get(url).await.map_err(GetError::Reqwest)? + ).await.map_err(GetError::Response)? + .json().await.map_err(GetError::Reqwest) + } + pub async fn get_asset_version_location(&self,config:GetAssetVersionRequest)->Result<AssetLocation,GetError>{ + let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}/version/{}",config.asset_id,config.version); + let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; + + crate::response_ok( + self.get(url).await.map_err(GetError::Reqwest)? + ).await.map_err(GetError::Response)? + .json().await.map_err(GetError::Reqwest) + } + pub async fn get_asset(&self,config:&AssetLocation)->Result<Vec<u8>,GetError>{ + let url=reqwest::Url::parse(config.location.as_str()).map_err(GetError::ParseError)?; + let body=crate::response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - match maybe_gzip_decode(&mut std::io::Cursor::new(body)){ + match maybe_gzip_decode(std::io::Cursor::new(body)){ Ok(ReaderType::GZip(readable))=>read_readable(readable), Ok(ReaderType::Raw(readable))=>read_readable(readable), Err(e)=>Err(e), }.map_err(GetError::IO) } - pub async fn get_asset(&self,config:GetAssetRequest)->Result<Vec<u8>,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<AssetVersionsResponse,AssetVersionsError>{ 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)?; diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index f09223d..7ad53f2 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -1,4 +1,4 @@ -use crate::ResponseError; +use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable}; #[derive(Debug)] pub enum PostError{ @@ -306,25 +306,6 @@ pub struct UserInventoryPageResponse{ pub data:Vec<UserInventoryItem>, } -//idk how to do this better -enum ReaderType<R:std::io::Read>{ - GZip(flate2::read::GzDecoder<std::io::BufReader<R>>), - Raw(std::io::BufReader<R>), -} -fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{ - let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf)?; - match &peek[0..2]{ - b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))), - _=>Ok(ReaderType::Raw(buf)), - } -} -fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{ - let mut contents=Vec::new(); - readable.read_to_end(&mut contents)?; - Ok(contents) -} - #[derive(Clone)] pub struct Cookie(String); impl Cookie{ @@ -337,12 +318,12 @@ impl Cookie{ } } #[derive(Clone)] -pub struct CookieContext{ - pub cookie:String, - pub client:reqwest::Client, +pub struct Context{ + cookie:String, + client:reqwest::Client, } -impl CookieContext{ +impl Context{ pub fn new(cookie:Cookie)->Self{ Self{ cookie:cookie.get(), diff --git a/rbx_asset/src/lib.rs b/rbx_asset/src/lib.rs index 7728978..c41ff74 100644 --- a/rbx_asset/src/lib.rs +++ b/rbx_asset/src/lib.rs @@ -20,7 +20,7 @@ impl std::fmt::Display for ResponseError{ } impl std::error::Error for ResponseError{} // lazy function to draw out meaningful info from http response on failure -pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{ +pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{ let status_code=response.status(); if status_code.is_success(){ Ok(response) @@ -35,3 +35,22 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R })) } } + +//idk how to do this better +pub(crate) enum ReaderType<R:std::io::Read>{ + GZip(flate2::read::GzDecoder<std::io::BufReader<R>>), + Raw(std::io::BufReader<R>), +} +pub(crate) fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{ + let mut buf=std::io::BufReader::new(input); + let peek=std::io::BufRead::fill_buf(&mut buf)?; + match &peek[0..2]{ + b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))), + _=>Ok(ReaderType::Raw(buf)), + } +} +pub(crate) fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{ + let mut contents=Vec::new(); + readable.read_to_end(&mut contents)?; + Ok(contents) +} diff --git a/src/main.rs b/src/main.rs index 9d4c79b..ba89009 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,8 @@ use std::{io::Read,path::PathBuf}; use clap::{Args,Parser,Subcommand}; use anyhow::{anyhow,Result as AResult}; use futures::StreamExt; -use rbx_asset::cloud::{ApiKey,CloudContext}; -use rbx_asset::cookie::{Cookie,CookieContext,AssetVersion,CreationsItem}; +use rbx_asset::cloud::{ApiKey,Context as CloudContext}; +use rbx_asset::cookie::{Cookie,Context as CookieContext,AssetVersion,CreationsItem}; type AssetID=u64; type AssetIDFileMap=Vec<(AssetID,PathBuf)>;