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;