diff --git a/Cargo.lock b/Cargo.lock index 047ca0b..b49535d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1339,7 +1339,7 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.4.5" +version = "0.4.6" dependencies = [ "bytes", "chrono", diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index bc81d7d..d7c747a 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_asset" -version = "0.4.5" +version = "0.4.6" edition = "2021" publish = ["strafesnet"] repository = "https://git.itzana.me/StrafesNET/asset-tool" diff --git a/rbx_asset/src/body.rs b/rbx_asset/src/body.rs new file mode 100644 index 0000000..bc46164 --- /dev/null +++ b/rbx_asset/src/body.rs @@ -0,0 +1,44 @@ +use reqwest::Body; + +pub trait ContentType:Into{ + fn content_type(&self)->&'static str; +} + +#[derive(Clone,Copy,Debug)] +pub struct Json(pub(crate)T); +impl> From> for Body{ + fn from(Json(value):Json)->Self{ + value.into() + } +} +impl> ContentType for Json{ + fn content_type(&self)->&'static str{ + "application/json" + } +} + +#[derive(Clone,Copy,Debug)] +pub struct Text(pub(crate)T); +impl> From> for Body{ + fn from(Text(value):Text)->Self{ + value.into() + } +} +impl> ContentType for Text{ + fn content_type(&self)->&'static str{ + "text/plain" + } +} + +#[derive(Clone,Copy,Debug)] +pub struct Binary(pub(crate)T); +impl> From> for Body{ + fn from(Binary(value):Binary)->Self{ + value.into() + } +} +impl> ContentType for Binary{ + fn content_type(&self)->&'static str{ + "application/octet-stream" + } +} diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index efee3dc..2ef2ab2 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -1,3 +1,4 @@ +use crate::body::{ContentType,Json}; use crate::util::response_ok; use crate::types::{ResponseError,MaybeGzippedBytes}; @@ -306,6 +307,58 @@ pub struct UserInventoryPageResponse{ pub data:Vec, } +#[derive(Debug)] +pub enum SetAssetsPermissionsError{ + Parse(url::ParseError), + JSONEncode(serde_json::Error), + Patch(PostError), + Response(ResponseError), + Reqwest(reqwest::Error), +} +impl std::fmt::Display for SetAssetsPermissionsError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f,"{self:?}") + } +} +impl std::error::Error for SetAssetsPermissionsError{} + +#[derive(serde::Serialize)] +#[allow(nonstandard_style)] +struct AssetPermissions{ + assetId:u64, + grantToDependencies:bool,//true +} +#[derive(serde::Serialize)] +#[allow(nonstandard_style)] +struct SetAssetsPermissions<'a>{ + subjectType:&'a str,// "Universe" + subjectId:&'a str,// "4422715291" + action:&'a str,// "Use", + enableDeepAccessCheck:bool,//true, + requests:&'a [AssetPermissions], +} +pub struct SetAssetsPermissionsRequest<'a>{ + pub universe_id:u64, + pub asset_ids:&'a [u64], +} +impl SetAssetsPermissionsRequest<'_>{ + fn serialize(&self)->Result{ + let requests:&Vec<_>=&self.asset_ids.iter().map(|&asset_id|AssetPermissions{ + assetId:asset_id, + grantToDependencies:true, + }).collect(); + let subject_id=&self.universe_id.to_string(); + let permissions=SetAssetsPermissions{ + subjectType:"Universe", + subjectId:subject_id, + action:"Use", + enableDeepAccessCheck:true, + requests, + }; + serde_json::to_string(&permissions) + } +} + #[derive(Clone)] pub struct Cookie(String); impl Cookie{ @@ -356,6 +409,29 @@ impl Context{ Ok(resp) } + async fn patch(&self,url:url::Url,body:impl ContentType+Clone)->Result{ + let mut resp=self.client.patch(url.clone()) + .header("Cookie",self.cookie.as_str()) + .header("Content-Type",body.content_type()) + .body(body.clone()) + .send().await.map_err(PostError::Reqwest)?; + + //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=self.client.patch(url) + .header("X-CSRF-Token",csrf_token) + .header("Cookie",self.cookie.as_str()) + .header("Content-Type",body.content_type()) + .body(body) + .send().await.map_err(PostError::Reqwest)?; + }else{ + Err(PostError::CSRF)?; + } + } + + Ok(resp) + } pub async fn create(&self,config:CreateRequest,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(CreateError::ParseError)?; //url borrow scope @@ -560,4 +636,16 @@ impl Context{ ).await.map_err(PageError::Response)? .json::().await.map_err(PageError::Reqwest) } + /// Used to enable an asset to be loaded onto a group game. + pub async fn set_assets_permissions(&self,config:SetAssetsPermissionsRequest<'_>)->Result<(),SetAssetsPermissionsError>{ + let url=reqwest::Url::parse("https://apis.roblox.com/asset-permissions-api/v1/assets/permissions").map_err(SetAssetsPermissionsError::Parse)?; + + let body=config.serialize().map_err(SetAssetsPermissionsError::JSONEncode)?; + + response_ok( + self.patch(url,Json(body)).await.map_err(SetAssetsPermissionsError::Patch)? + ).await.map_err(SetAssetsPermissionsError::Response)?; + + Ok(()) + } } diff --git a/rbx_asset/src/lib.rs b/rbx_asset/src/lib.rs index a6a3487..76136e9 100644 --- a/rbx_asset/src/lib.rs +++ b/rbx_asset/src/lib.rs @@ -1,4 +1,5 @@ pub mod cloud; pub mod cookie; pub mod types; +mod body; mod util;