asset-tool/rbx_asset/src/cloud.rs

336 lines
10 KiB
Rust
Raw Normal View History

2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub enum AssetType{
Audio,
Decal,
Model,
2024-04-28 04:07:06 +00:00
}
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
2024-07-02 21:26:14 +00:00
pub struct CreateAssetRequest{
pub assetType:AssetType,
pub creationContext:CreationContext,
2024-04-28 04:07:06 +00:00
pub description:String,
2024-07-02 21:26:14 +00:00
pub displayName:String,
2024-04-28 04:07:06 +00:00
}
#[derive(Debug)]
pub enum CreateError{
ParseError(url::ParseError),
2024-07-02 21:26:14 +00:00
SerializeError(serde_json::Error),
2024-04-28 04:07:06 +00:00
Reqwest(reqwest::Error),
}
impl std::fmt::Display for CreateError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
impl std::error::Error for CreateError{}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
2024-07-02 21:26:14 +00:00
pub struct UpdateAssetRequest{
pub assetId:u64,
pub displayName:Option<String>,
2024-04-28 04:07:06 +00:00
pub description:Option<String>,
2024-07-02 21:26:14 +00:00
}
//woo nested roblox stuff
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub struct Creator{
pub userId:u64,
pub groupId:u64,
}
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub struct CreationContext{
pub creator:Creator,
pub expectedPrice:u64,
}
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub enum ModerationResult{
MODERATION_STATE_REVIEWING,
MODERATION_STATE_REJECTED,
MODERATION_STATE_APPROVED,
}
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub struct Preview{
pub asset:String,
pub altText:String,
}
#[allow(nonstandard_style,dead_code)]
pub struct UpdatePlaceRequest{
pub universeId:u64,
pub placeId:u64,
}
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub struct UpdatePlaceResponse{
pub versionNumber:u64,
2024-04-28 04:07:06 +00:00
}
#[derive(Debug)]
2024-07-02 21:26:14 +00:00
pub enum UpdateError{
2024-04-28 04:07:06 +00:00
ParseError(url::ParseError),
2024-07-02 21:26:14 +00:00
SerializeError(serde_json::Error),
2024-04-28 04:07:06 +00:00
Reqwest(reqwest::Error),
}
2024-07-02 21:26:14 +00:00
impl std::fmt::Display for UpdateError{
2024-04-28 04:07:06 +00:00
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
2024-07-02 21:26:14 +00:00
impl std::error::Error for UpdateError{}
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
2024-07-02 21:26:14 +00:00
pub struct GetAssetRequest{
2024-04-28 04:07:06 +00:00
pub asset_id:u64,
pub version:Option<u64>,
}
#[derive(Debug)]
2024-07-02 21:26:14 +00:00
pub enum GetError{
2024-04-28 04:07:06 +00:00
ParseError(url::ParseError),
Reqwest(reqwest::Error),
IO(std::io::Error)
}
2024-07-02 21:26:14 +00:00
impl std::fmt::Display for GetError{
2024-04-28 04:07:06 +00:00
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
2024-07-02 21:26:14 +00:00
impl std::error::Error for GetError{}
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
pub struct AssetVersionsRequest{
2024-04-28 04:07:06 +00:00
pub asset_id:u64,
pub cursor:Option<String>,
}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
pub struct AssetVersion{
pub Id:u64,
pub assetId:u64,
pub assetVersionNumber:u64,
pub creatorType:String,
pub creatorTargetId:u64,
pub creatingUniverseId:Option<u64>,
pub created:chrono::DateTime<chrono::Utc>,
pub isPublished:bool,
}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize)]
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
2024-07-02 21:26:14 +00:00
pub struct AssetVersionsResponse{
2024-04-28 04:07:06 +00:00
pub previousPageCursor:Option<String>,
pub nextPageCursor:Option<String>,
pub data:Vec<AssetVersion>,
}
#[derive(Debug)]
2024-07-02 21:26:14 +00:00
pub enum AssetVersionsError{
2024-04-28 04:07:06 +00:00
ParseError(url::ParseError),
Reqwest(reqwest::Error),
}
2024-07-02 21:26:14 +00:00
impl std::fmt::Display for AssetVersionsError{
2024-04-28 04:07:06 +00:00
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
2024-07-02 21:26:14 +00:00
impl std::error::Error for AssetVersionsError{}
2024-04-28 04:07:06 +00:00
pub struct InventoryPageRequest{
pub group:u64,
pub cursor:Option<String>,
}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
pub struct InventoryItem{
pub id:u64,
pub name:String,
}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
2024-04-28 04:07:06 +00:00
#[allow(nonstandard_style,dead_code)]
pub struct InventoryPageResponse{
pub totalResults:u64,//up to 50
pub filteredKeyword:Option<String>,//""
pub searchDebugInfo:Option<String>,//null
pub spellCheckerResult:Option<String>,//null
pub queryFacets:Option<String>,//null
pub imageSearchStatus:Option<String>,//null
pub previousPageCursor:Option<String>,
pub nextPageCursor:Option<String>,
pub data:Vec<InventoryItem>,
}
#[derive(Debug)]
pub enum InventoryPageError{
ParseError(url::ParseError),
Reqwest(reqwest::Error),
}
impl std::fmt::Display for InventoryPageError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}")
}
}
impl std::error::Error for InventoryPageError{}
2024-07-02 21:26:14 +00:00
#[derive(Debug,serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)]
pub struct RobloxOperation{
pub path:Option<std::path::PathBuf>,
pub metadata:Option<String>,
pub done:Option<bool>,
pub error:Option<String>,
pub response:Option<String>,
}
2024-04-28 04:07:06 +00:00
//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)]
2024-07-03 19:20:11 +00:00
pub struct ApiKey(String);
impl ApiKey{
pub fn new(api_key:String)->Self{
Self(api_key)
}
pub fn get(self)->String{
self.0
}
}
#[derive(Clone)]
pub struct CloudContext{
2024-07-02 21:26:14 +00:00
pub api_key:String,
2024-04-28 04:07:06 +00:00
pub client:reqwest::Client,
}
2024-07-03 19:20:11 +00:00
impl CloudContext{
pub fn new(api_key:ApiKey)->Self{
2024-04-28 04:07:06 +00:00
Self{
2024-07-03 19:20:11 +00:00
api_key:api_key.get(),
2024-04-28 04:07:06 +00:00
client:reqwest::Client::new(),
}
}
async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.get(url)
2024-07-02 21:26:14 +00:00
.header("x-api-key",self.api_key.as_str())
2024-04-28 04:07:06 +00:00
.send().await
}
2024-07-02 21:26:14 +00:00
async fn post(&self,url:url::Url,body:impl Into<reqwest::Body>+Clone)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.header("x-api-key",self.api_key.as_str())
.body(body)
.send().await
}
async fn patch_form(&self,url:url::Url,form:reqwest::multipart::Form)->Result<reqwest::Response,reqwest::Error>{
self.client.patch(url)
.header("x-api-key",self.api_key.as_str())
.multipart(form)
.send().await
}
async fn post_form(&self,url:url::Url,form:reqwest::multipart::Form)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.header("x-api-key",self.api_key.as_str())
.multipart(form)
.send().await
}
pub async fn create_asset(&self,config:CreateAssetRequest,body:impl Into<std::borrow::Cow<'static,[u8]>>)->Result<RobloxOperation,CreateError>{
let url=reqwest::Url::parse("https://apis.roblox.com/assets/v1/assets").map_err(CreateError::ParseError)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
let request_config=serde_json::to_string(&config).map_err(CreateError::SerializeError)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
let form=reqwest::multipart::Form::new()
.text("request",request_config)
.part("fileContent",reqwest::multipart::Part::bytes(body));
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
let resp=self.post_form(url,form).await.map_err(CreateError::Reqwest)?
.error_for_status().map_err(CreateError::Reqwest)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
Ok(resp.json::<RobloxOperation>().await.map_err(CreateError::Reqwest)?)
2024-04-28 04:07:06 +00:00
}
2024-07-02 21:26:14 +00:00
pub async fn update_asset(&self,config:UpdateAssetRequest,body:impl Into<std::borrow::Cow<'static,[u8]>>)->Result<RobloxOperation,UpdateError>{
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}",config.assetId);
let url=reqwest::Url::parse(raw_url.as_str()).map_err(UpdateError::ParseError)?;
let request_config=serde_json::to_string(&config).map_err(UpdateError::SerializeError)?;
let form=reqwest::multipart::Form::new()
.text("request",request_config)
.part("fileContent",reqwest::multipart::Part::bytes(body));
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
let resp=self.patch_form(url,form).await
.map_err(UpdateError::Reqwest)?
//roblox api documentation is very poor, just give the status code and drop the json
.error_for_status().map_err(UpdateError::Reqwest)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
Ok(resp.json::<RobloxOperation>().await.map_err(UpdateError::Reqwest)?)
2024-04-28 04:07:06 +00:00
}
2024-07-02 21:26:14 +00:00
pub async fn get_asset(&self,config:GetAssetRequest)->Result<Vec<u8>,GetError>{
let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/").map_err(GetError::ParseError)?;
2024-04-28 04:07:06 +00:00
//url borrow scope
{
let mut query=url.query_pairs_mut();//borrow here
query.append_pair("ID",config.asset_id.to_string().as_str());
if let Some(version)=config.version{
query.append_pair("version",version.to_string().as_str());
}
}
2024-07-02 21:26:14 +00:00
let resp=self.get(url).await.map_err(GetError::Reqwest)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
let body=resp.bytes().await.map_err(GetError::Reqwest)?;
2024-04-28 04:07:06 +00:00
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),
2024-07-02 21:26:14 +00:00
}.map_err(GetError::IO)
2024-04-28 04:07:06 +00:00
}
2024-07-02 21:26:14 +00:00
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)?;
2024-04-28 04:07:06 +00:00
2024-07-02 21:26:14 +00:00
Ok(self.get(url).await.map_err(AssetVersionsError::Reqwest)?
.json::<AssetVersionsResponse>().await.map_err(AssetVersionsError::Reqwest)?)
2024-04-28 04:07:06 +00:00
}
pub async fn inventory_page(&self,config:InventoryPageRequest)->Result<InventoryPageResponse,InventoryPageError>{
let mut url=reqwest::Url::parse(format!("https://apis.roblox.com/toolbox-service/v1/creations/group/{}/10?limit=50",config.group).as_str()).map_err(InventoryPageError::ParseError)?;
//url borrow scope
{
let mut query=url.query_pairs_mut();//borrow here
if let Some(cursor)=config.cursor.as_deref(){
query.append_pair("cursor",cursor);
}
}
Ok(self.get(url).await.map_err(InventoryPageError::Reqwest)?
.json::<InventoryPageResponse>().await.map_err(InventoryPageError::Reqwest)?)
}
2024-07-02 21:26:14 +00:00
pub async fn update_place(&self,config:UpdatePlaceRequest,body:impl Into<reqwest::Body>+Clone)->Result<UpdatePlaceResponse,UpdateError>{
let raw_url=format!("https://apis.roblox.com/universes/v1/{}/places/{}/versions",config.universeId,config.placeId);
let mut url=reqwest::Url::parse(raw_url.as_str()).map_err(UpdateError::ParseError)?;
//url borrow scope
{
let mut query=url.query_pairs_mut();//borrow here
query.append_pair("versionType","Published");
}
let resp=self.post(url,body).await.map_err(UpdateError::Reqwest)?
.error_for_status().map_err(UpdateError::Reqwest)?;
Ok(resp.json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest)?)
}
}