Merge pull request 'rbx_asset: cloud: implement new asset-delivery-api' (#14) from staging into master
Reviewed-on: #14
This commit is contained in:
commit
ad8e1865f3
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1293,7 +1293,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbx_asset"
|
name = "rbx_asset"
|
||||||
version = "0.3.4"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rbx_asset"
|
name = "rbx_asset"
|
||||||
version = "0.3.4"
|
version = "0.4.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = ["strafesnet"]
|
publish = ["strafesnet"]
|
||||||
repository = "https://git.itzana.me/StrafesNET/asset-tool"
|
repository = "https://git.itzana.me/StrafesNET/asset-tool"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::ResponseError;
|
use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable};
|
||||||
|
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[allow(nonstandard_style,dead_code)]
|
||||||
@ -31,7 +31,7 @@ pub struct AssetOperation{
|
|||||||
operation:RobloxOperation,
|
operation:RobloxOperation,
|
||||||
}
|
}
|
||||||
impl AssetOperation{
|
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(
|
serde_json::from_value(
|
||||||
self.operation
|
self.operation
|
||||||
.try_get_reponse(context).await
|
.try_get_reponse(context).await
|
||||||
@ -119,7 +119,7 @@ impl std::error::Error for UpdateError{}
|
|||||||
struct GetAssetOperationRequest{
|
struct GetAssetOperationRequest{
|
||||||
operation_id:String,
|
operation_id:String,
|
||||||
}
|
}
|
||||||
pub struct GetAssetInfoRequest{
|
pub struct GetAssetLatestRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -149,25 +149,21 @@ pub struct AssetResponse{
|
|||||||
pub assetId:String,//u64 wrapped in quotes wohoo!!
|
pub assetId:String,//u64 wrapped in quotes wohoo!!
|
||||||
pub assetType:AssetType,
|
pub assetType:AssetType,
|
||||||
pub creationContext:CreationContext,
|
pub creationContext:CreationContext,
|
||||||
pub description:String,
|
pub description:Option<String>,
|
||||||
pub displayName:String,
|
pub displayName:String,
|
||||||
pub path:String,
|
pub path:String,
|
||||||
pub revisionCreateTime:chrono::DateTime<chrono::Utc>,
|
pub revisionCreateTime:chrono::DateTime<chrono::Utc>,
|
||||||
pub revisionId:String,//u64
|
pub revisionId:String,//u64
|
||||||
pub moderationResult:ModerationResult,
|
pub moderationResult:ModerationResult,
|
||||||
pub icon:Option<String>,
|
pub icon:Option<String>,
|
||||||
pub previews:Option<Vec<Preview>>,
|
#[serde(default)]
|
||||||
|
pub previews:Vec<Preview>,
|
||||||
}
|
}
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[allow(nonstandard_style,dead_code)]
|
||||||
pub struct GetAssetVersionRequest{
|
pub struct GetAssetVersionRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
pub version:u64,
|
pub version:u64,
|
||||||
}
|
}
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct GetAssetRequest{
|
|
||||||
pub asset_id:u64,
|
|
||||||
pub version:Option<u64>,
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum GetError{
|
pub enum GetError{
|
||||||
ParseError(url::ParseError),
|
ParseError(url::ParseError),
|
||||||
@ -182,6 +178,23 @@ impl std::fmt::Display for GetError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error 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 struct AssetVersionsRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
pub cursor:Option<String>,
|
pub cursor:Option<String>,
|
||||||
@ -286,7 +299,7 @@ impl RobloxOperation{
|
|||||||
None=>self.path.as_deref()?.get(11..),
|
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{
|
context.get_asset_operation(GetAssetOperationRequest{
|
||||||
operation_id:self.operation_id()
|
operation_id:self.operation_id()
|
||||||
.ok_or(OperationError::NoOperationId)?
|
.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)]
|
#[derive(Clone)]
|
||||||
pub struct ApiKey(String);
|
pub struct ApiKey(String);
|
||||||
impl ApiKey{
|
impl ApiKey{
|
||||||
@ -327,12 +321,12 @@ impl ApiKey{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CloudContext{
|
pub struct Context{
|
||||||
pub api_key:String,
|
api_key:String,
|
||||||
pub client:reqwest::Client,
|
client:reqwest::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CloudContext{
|
impl Context{
|
||||||
pub fn new(api_key:ApiKey)->Self{
|
pub fn new(api_key:ApiKey)->Self{
|
||||||
Self{
|
Self{
|
||||||
api_key:api_key.get(),
|
api_key:api_key.get(),
|
||||||
@ -412,7 +406,7 @@ impl CloudContext{
|
|||||||
).await.map_err(GetError::Response)?
|
).await.map_err(GetError::Response)?
|
||||||
.json::<RobloxOperation>().await.map_err(GetError::Reqwest)
|
.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 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)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
||||||
|
|
||||||
@ -421,31 +415,47 @@ impl CloudContext{
|
|||||||
).await.map_err(GetError::Response)?
|
).await.map_err(GetError::Response)?
|
||||||
.json::<AssetResponse>().await.map_err(GetError::Reqwest)
|
.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 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)?;
|
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(
|
let body=crate::response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
).await.map_err(GetError::Response)?
|
).await.map_err(GetError::Response)?
|
||||||
.bytes().await.map_err(GetError::Reqwest)?;
|
.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::GZip(readable))=>read_readable(readable),
|
||||||
Ok(ReaderType::Raw(readable))=>read_readable(readable),
|
Ok(ReaderType::Raw(readable))=>read_readable(readable),
|
||||||
Err(e)=>Err(e),
|
Err(e)=>Err(e),
|
||||||
}.map_err(GetError::IO)
|
}.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>{
|
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 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)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(AssetVersionsError::ParseError)?;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::ResponseError;
|
use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PostError{
|
pub enum PostError{
|
||||||
@ -306,25 +306,6 @@ pub struct UserInventoryPageResponse{
|
|||||||
pub data:Vec<UserInventoryItem>,
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Cookie(String);
|
pub struct Cookie(String);
|
||||||
impl Cookie{
|
impl Cookie{
|
||||||
@ -337,12 +318,12 @@ impl Cookie{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CookieContext{
|
pub struct Context{
|
||||||
pub cookie:String,
|
cookie:String,
|
||||||
pub client:reqwest::Client,
|
client:reqwest::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CookieContext{
|
impl Context{
|
||||||
pub fn new(cookie:Cookie)->Self{
|
pub fn new(cookie:Cookie)->Self{
|
||||||
Self{
|
Self{
|
||||||
cookie:cookie.get(),
|
cookie:cookie.get(),
|
||||||
|
@ -20,7 +20,7 @@ impl std::fmt::Display for ResponseError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ResponseError{}
|
impl std::error::Error for ResponseError{}
|
||||||
// lazy function to draw out meaningful info from http response on failure
|
// 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();
|
let status_code=response.status();
|
||||||
if status_code.is_success(){
|
if status_code.is_success(){
|
||||||
Ok(response)
|
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)
|
||||||
|
}
|
||||||
|
@ -2,8 +2,8 @@ use std::{io::Read,path::PathBuf};
|
|||||||
use clap::{Args,Parser,Subcommand};
|
use clap::{Args,Parser,Subcommand};
|
||||||
use anyhow::{anyhow,Result as AResult};
|
use anyhow::{anyhow,Result as AResult};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use rbx_asset::cloud::{ApiKey,CloudContext};
|
use rbx_asset::cloud::{ApiKey,Context as CloudContext};
|
||||||
use rbx_asset::cookie::{Cookie,CookieContext,AssetVersion,CreationsItem};
|
use rbx_asset::cookie::{Cookie,Context as CookieContext,AssetVersion,CreationsItem};
|
||||||
|
|
||||||
type AssetID=u64;
|
type AssetID=u64;
|
||||||
type AssetIDFileMap=Vec<(AssetID,PathBuf)>;
|
type AssetIDFileMap=Vec<(AssetID,PathBuf)>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user