All checks were successful
continuous-integration/drone/push Build is passing
Tested to some extent Reviewed-on: #18 Co-authored-by: Rhys Lloyd <krakow20@gmail.com> Co-committed-by: Rhys Lloyd <krakow20@gmail.com>
629 lines
19 KiB
Rust
629 lines
19 KiB
Rust
use crate::body::{Binary,ContentType,Json};
|
|
use crate::util::{serialize_u64,deserialize_u64,response_ok};
|
|
use crate::types::{ResponseError,MaybeGzippedBytes};
|
|
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub enum AssetType{
|
|
Audio,
|
|
Decal,
|
|
Model,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct CreateAssetRequest{
|
|
pub assetType:AssetType,
|
|
pub creationContext:CreationContext,
|
|
pub description:String,
|
|
pub displayName:String,
|
|
}
|
|
#[derive(Debug)]
|
|
pub enum AssetOperationError{
|
|
Operation(OperationError),
|
|
Serialize(serde_json::Error),
|
|
}
|
|
impl std::fmt::Display for AssetOperationError{
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for AssetOperationError{}
|
|
#[derive(Debug)]
|
|
pub struct AssetOperation{
|
|
operation:RobloxOperation,
|
|
}
|
|
impl AssetOperation{
|
|
pub async fn try_get_asset(&self,context:&Context)->Result<AssetResponse,AssetOperationError>{
|
|
serde_json::from_value(
|
|
self.operation
|
|
.try_get_reponse(context).await
|
|
.map_err(AssetOperationError::Operation)?
|
|
).map_err(AssetOperationError::Serialize)
|
|
}
|
|
}
|
|
#[derive(Debug)]
|
|
pub enum CreateError{
|
|
Parse(url::ParseError),
|
|
Response(ResponseError),
|
|
Serialize(serde_json::Error),
|
|
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{}
|
|
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct UpdateAssetRequest{
|
|
pub assetId:u64,
|
|
pub displayName:Option<String>,
|
|
pub description:Option<String>,
|
|
}
|
|
|
|
//woo nested roblox stuff
|
|
#[derive(Clone,Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub enum Creator{
|
|
userId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
|
groupId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct CreationContext{
|
|
pub creator:Creator,
|
|
pub expectedPrice:Option<u64>,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub enum ModerationState{
|
|
Reviewing,
|
|
Rejected,
|
|
Approved,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct ModerationResult{
|
|
pub moderationState:ModerationState,
|
|
}
|
|
#[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,
|
|
}
|
|
#[derive(Debug)]
|
|
pub enum UpdateError{
|
|
ParseError(url::ParseError),
|
|
Response(ResponseError),
|
|
SerializeError(serde_json::Error),
|
|
Reqwest(reqwest::Error),
|
|
}
|
|
impl std::fmt::Display for UpdateError{
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for UpdateError{}
|
|
|
|
struct GetAssetOperationRequest<'a>{
|
|
operation_id:&'a str,
|
|
}
|
|
pub struct GetAssetLatestRequest{
|
|
pub asset_id:u64,
|
|
}
|
|
/*
|
|
{
|
|
"assetId": "5692158972",
|
|
"assetType": "Model",
|
|
"creationContext":{
|
|
"creator":
|
|
{
|
|
"groupId": "6980477"
|
|
}
|
|
},
|
|
"description": "DisplayName: Ares\nCreator: titanicguy54",
|
|
"displayName": "bhop_ares.rbxmx",
|
|
"path": "assets/5692158972",
|
|
"revisionCreateTime": "2020-09-14T16:08:05.063Z",
|
|
"revisionId": "1",
|
|
"moderationResult":{
|
|
"moderationState": "Approved"
|
|
},
|
|
"state": "Active"
|
|
}
|
|
*/
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct AssetResponse{
|
|
//u64 wrapped in quotes wohoo!!
|
|
#[serde(deserialize_with="deserialize_u64")]
|
|
#[serde(serialize_with="serialize_u64")]
|
|
pub assetId:u64,
|
|
pub assetType:AssetType,
|
|
pub creationContext:CreationContext,
|
|
pub description:Option<String>,
|
|
pub displayName:String,
|
|
pub path:String,
|
|
pub revisionCreateTime:chrono::DateTime<chrono::Utc>,
|
|
#[serde(deserialize_with="deserialize_u64")]
|
|
#[serde(serialize_with="serialize_u64")]
|
|
pub revisionId:u64,
|
|
pub moderationResult:ModerationResult,
|
|
pub icon:Option<String>,
|
|
#[serde(default)]
|
|
pub previews:Vec<Preview>,
|
|
}
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct GetAssetVersionRequest{
|
|
pub asset_id:u64,
|
|
pub version:u64,
|
|
}
|
|
#[derive(Debug)]
|
|
pub enum GetError{
|
|
Parse(url::ParseError),
|
|
Response(ResponseError),
|
|
Reqwest(reqwest::Error),
|
|
}
|
|
impl std::fmt::Display for GetError{
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for GetError{}
|
|
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
pub struct AssetLocation(
|
|
// the location is private so users cannot mutate it
|
|
String
|
|
);
|
|
impl AssetLocation{
|
|
pub fn location(&self)->&str{
|
|
let Self(location)=self;
|
|
location
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,serde::Deserialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct AssetMetadata{
|
|
pub metadataType:u32,
|
|
pub value:String,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct AssetLocationInfo{
|
|
pub location:Option<AssetLocation>,
|
|
pub requestId:String,
|
|
pub isArchived:bool,
|
|
pub assetTypeId:u32,
|
|
#[serde(default)]
|
|
pub assetMetadatas:Vec<AssetMetadata>,
|
|
pub isRecordable:bool,
|
|
}
|
|
|
|
pub struct AssetVersionsRequest{
|
|
pub asset_id:u64,
|
|
pub cursor:Option<String>,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[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,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct AssetVersionsResponse{
|
|
pub previousPageCursor:Option<String>,
|
|
pub nextPageCursor:Option<String>,
|
|
pub data:Vec<AssetVersion>,
|
|
}
|
|
#[derive(Debug)]
|
|
pub enum AssetVersionsError{
|
|
ParseError(url::ParseError),
|
|
Response(ResponseError),
|
|
Reqwest(reqwest::Error),
|
|
}
|
|
impl std::fmt::Display for AssetVersionsError{
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for AssetVersionsError{}
|
|
|
|
pub struct InventoryPageRequest{
|
|
pub group:u64,
|
|
pub cursor:Option<String>,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
pub struct InventoryItem{
|
|
pub id:u64,
|
|
pub name:String,
|
|
}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[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),
|
|
Response(ResponseError),
|
|
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{}
|
|
|
|
#[derive(Debug)]
|
|
pub enum OperationError{
|
|
Get(GetError),
|
|
NoOperationId,
|
|
NotDone,
|
|
}
|
|
impl std::fmt::Display for OperationError{
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for OperationError{}
|
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
|
#[allow(nonstandard_style,dead_code)]
|
|
struct RobloxOperation{
|
|
pub path:Option<String>,
|
|
pub metadata:Option<String>,
|
|
pub done:Option<bool>,
|
|
pub error:Option<String>,
|
|
pub response:Option<serde_json::Value>,
|
|
pub operationId:Option<String>,
|
|
}
|
|
impl RobloxOperation{
|
|
pub fn operation_id(&self)->Option<&str>{
|
|
match self.operationId.as_deref(){
|
|
//try getting it from undocumented operationId first
|
|
Some(operation_id)=>Some(operation_id),
|
|
//skip the first 11 characters
|
|
//operations/[uuid]
|
|
None=>self.path.as_deref()?.get(11..),
|
|
}
|
|
}
|
|
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)?,
|
|
}).await.map_err(OperationError::Get)?
|
|
.response.ok_or(OperationError::NotDone)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum LuauSessionError{
|
|
Get(GetError),
|
|
Unspecified,
|
|
NotDone,
|
|
NoOutput,
|
|
NoError,
|
|
}
|
|
impl std::fmt::Display for LuauSessionError{
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
impl std::error::Error for LuauSessionError{}
|
|
#[derive(Debug,serde::Serialize)]
|
|
#[expect(nonstandard_style)]
|
|
pub struct LuauSessionCreate<'a>{
|
|
pub script:&'a str,
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
pub user:Option<&'a str>,
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
pub timeout:Option<&'a str>,
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
pub binaryInput:Option<&'a str>,
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
pub enableBinaryOutput:Option<bool>,
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
pub binaryOutputUri:Option<&'a str>,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
#[expect(nonstandard_style)]
|
|
pub enum LuauSessionState{
|
|
STATE_UNSPECIFIED,
|
|
PROCESSING,
|
|
COMPLETE,
|
|
FAILED,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
pub struct LuauError{
|
|
pub code:String,
|
|
pub message:String,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
pub struct LuauResults{
|
|
pub results:Vec<serde_json::Value>,
|
|
}
|
|
#[derive(Debug,serde::Deserialize)]
|
|
#[expect(nonstandard_style)]
|
|
pub struct LuauSessionResponse{
|
|
path:String,
|
|
#[serde(deserialize_with="deserialize_u64")]
|
|
pub user:u64,
|
|
pub state:LuauSessionState,
|
|
pub script:String,
|
|
pub error:Option<LuauError>,
|
|
pub output:Option<LuauResults>,
|
|
pub binaryInput:String,
|
|
pub enableBinaryOutput:bool,
|
|
pub binaryOutputUri:String,
|
|
}
|
|
impl LuauSessionResponse{
|
|
pub fn path(&self)->&str{
|
|
&self.path
|
|
}
|
|
pub async fn try_get_result(&self,context:&Context)->Result<Result<LuauResults,LuauError>,LuauSessionError>{
|
|
let response=context.get_luau_session(self).await.map_err(LuauSessionError::Get)?;
|
|
match response.state{
|
|
LuauSessionState::STATE_UNSPECIFIED=>Err(LuauSessionError::Unspecified),
|
|
LuauSessionState::PROCESSING=>Err(LuauSessionError::NotDone),
|
|
LuauSessionState::COMPLETE=>Ok(Ok(response.output.ok_or(LuauSessionError::NoOutput)?)),
|
|
LuauSessionState::FAILED=>Ok(Err(response.error.ok_or(LuauSessionError::NoError)?)),
|
|
}
|
|
}
|
|
}
|
|
pub trait AsSessionPath{
|
|
fn into_session_path(&self)->impl AsRef<str>;
|
|
}
|
|
impl AsSessionPath for LuauSessionResponse{
|
|
fn into_session_path(&self)->impl AsRef<str>{
|
|
&self.path
|
|
}
|
|
}
|
|
pub struct LuauSessionRequest{
|
|
pub universe_id:u64,
|
|
pub place_id:u64,
|
|
pub version_id:u64,
|
|
}
|
|
impl AsSessionPath for LuauSessionRequest{
|
|
fn into_session_path(&self)->impl AsRef<str>{
|
|
let universe_id=self.universe_id;
|
|
let place_id=self.place_id;
|
|
let version_id=self.version_id;
|
|
format!("universes/{universe_id}/places/{place_id}/versions/{version_id}/luau-execution-session-tasks")
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
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 Context{
|
|
api_key:String,
|
|
client:reqwest::Client,
|
|
}
|
|
|
|
impl Context{
|
|
pub fn new(api_key:ApiKey)->Self{
|
|
Self{
|
|
api_key:api_key.get(),
|
|
client:reqwest::Client::new(),
|
|
}
|
|
}
|
|
async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
|
|
self.client.get(url)
|
|
.header("x-api-key",self.api_key.as_str())
|
|
.send().await
|
|
}
|
|
async fn post(&self,url:url::Url,body:impl ContentType)->Result<reqwest::Response,reqwest::Error>{
|
|
self.client.post(url)
|
|
.header("x-api-key",self.api_key.as_str())
|
|
.header("Content-Type",body.content_type())
|
|
.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<AssetOperation,CreateError>{
|
|
let url=reqwest::Url::parse("https://apis.roblox.com/assets/v1/assets").map_err(CreateError::Parse)?;
|
|
|
|
let request_config=serde_json::to_string(&config).map_err(CreateError::Serialize)?;
|
|
|
|
let part=reqwest::multipart::Part::bytes(body)
|
|
//you must have a file name or roblox will 400!!!!!!!!!
|
|
.file_name("image");
|
|
|
|
let form=reqwest::multipart::Form::new()
|
|
.text("request",request_config)
|
|
.part("fileContent",part);
|
|
|
|
let operation=response_ok(
|
|
self.post_form(url,form).await.map_err(CreateError::Reqwest)?
|
|
).await.map_err(CreateError::Response)?
|
|
.json::<RobloxOperation>().await.map_err(CreateError::Reqwest)?;
|
|
|
|
Ok(AssetOperation{
|
|
operation,
|
|
})
|
|
}
|
|
pub async fn update_asset(&self,config:UpdateAssetRequest,body:impl Into<std::borrow::Cow<'static,[u8]>>)->Result<AssetOperation,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));
|
|
|
|
let operation=response_ok(
|
|
self.patch_form(url,form).await.map_err(UpdateError::Reqwest)?
|
|
).await.map_err(UpdateError::Response)?
|
|
.json::<RobloxOperation>().await.map_err(UpdateError::Reqwest)?;
|
|
|
|
Ok(AssetOperation{
|
|
operation,
|
|
})
|
|
}
|
|
async fn get_asset_operation(&self,config:GetAssetOperationRequest<'_>)->Result<RobloxOperation,GetError>{
|
|
let raw_url=format!("https://apis.roblox.com/assets/v1/operations/{}",config.operation_id);
|
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
|
|
|
response_ok(
|
|
self.get(url).await.map_err(GetError::Reqwest)?
|
|
).await.map_err(GetError::Response)?
|
|
.json::<RobloxOperation>().await.map_err(GetError::Reqwest)
|
|
}
|
|
pub async fn create_luau_session(&self,config:&impl AsSessionPath,session:LuauSessionCreate<'_>)->Result<LuauSessionResponse,CreateError>{
|
|
let raw_url=format!("https://apis.roblox.com/cloud/v2/{}",config.into_session_path().as_ref());
|
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(CreateError::Parse)?;
|
|
|
|
let body=serde_json::to_string(&session).map_err(CreateError::Serialize)?;
|
|
|
|
response_ok(
|
|
self.post(url,Json(body)).await.map_err(CreateError::Reqwest)?
|
|
).await.map_err(CreateError::Response)?
|
|
.json::<LuauSessionResponse>().await.map_err(CreateError::Reqwest)
|
|
}
|
|
pub async fn get_luau_session(&self,config:&impl AsSessionPath)->Result<LuauSessionResponse,GetError>{
|
|
let raw_url=format!("https://apis.roblox.com/cloud/v2/{}",config.into_session_path().as_ref());
|
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
|
|
|
response_ok(
|
|
self.get(url).await.map_err(GetError::Reqwest)?
|
|
).await.map_err(GetError::Response)?
|
|
.json::<LuauSessionResponse>().await.map_err(GetError::Reqwest)
|
|
}
|
|
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::Parse)?;
|
|
|
|
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_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::Parse)?;
|
|
|
|
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<AssetLocationInfo,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::Parse)?;
|
|
|
|
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<AssetLocationInfo,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::Parse)?;
|
|
|
|
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<MaybeGzippedBytes,GetError>{
|
|
let url=reqwest::Url::parse(config.location()).map_err(GetError::Parse)?;
|
|
|
|
let bytes=response_ok(
|
|
self.get(url).await.map_err(GetError::Reqwest)?
|
|
).await.map_err(GetError::Response)?
|
|
.bytes().await.map_err(GetError::Reqwest)?;
|
|
|
|
Ok(MaybeGzippedBytes::new(bytes))
|
|
}
|
|
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)?;
|
|
|
|
response_ok(
|
|
self.get(url).await.map_err(AssetVersionsError::Reqwest)?
|
|
).await.map_err(AssetVersionsError::Response)?
|
|
.json::<AssetVersionsResponse>().await.map_err(AssetVersionsError::Reqwest)
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
response_ok(
|
|
self.get(url).await.map_err(InventoryPageError::Reqwest)?
|
|
).await.map_err(InventoryPageError::Response)?
|
|
.json::<InventoryPageResponse>().await.map_err(InventoryPageError::Reqwest)
|
|
}
|
|
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");
|
|
}
|
|
|
|
response_ok(
|
|
self.post(url,Binary(body)).await.map_err(UpdateError::Reqwest)?
|
|
).await.map_err(UpdateError::Response)?
|
|
.json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest)
|
|
}
|
|
}
|