v0.4.7 user inventory + git fix #8

Merged
Quaternions merged 9 commits from staging into master 2024-10-01 20:10:30 +00:00
2 changed files with 56 additions and 27 deletions
Showing only changes of commit ac674778f6 - Show all commits

View File

@ -114,19 +114,31 @@ impl std::fmt::Display for AssetVersionsPageError{
} }
impl std::error::Error for AssetVersionsPageError{} impl std::error::Error for AssetVersionsPageError{}
pub struct InventoryPageRequest{ pub enum Owner{
pub group:u64, User(u64),
Group(u64),
}
impl Owner{
fn get_url_info(&self)->(&str,u64){
match self{
&Owner::User(id)=>("user",id),
&Owner::Group(id)=>("group",id),
}
}
}
pub struct CreationsPageRequest{
pub owner:Owner,
pub cursor:Option<String>, pub cursor:Option<String>,
} }
#[derive(serde::Deserialize,serde::Serialize)] #[derive(serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)] #[allow(nonstandard_style,dead_code)]
pub struct InventoryItem{ pub struct CreationsItem{
pub id:u64, pub id:u64,
pub name:String, pub name:String,
} }
#[derive(serde::Deserialize,serde::Serialize)] #[derive(serde::Deserialize,serde::Serialize)]
#[allow(nonstandard_style,dead_code)] #[allow(nonstandard_style,dead_code)]
pub struct InventoryPageResponse{ pub struct CreationsPageResponse{
pub totalResults:u64,//up to 50 pub totalResults:u64,//up to 50
pub filteredKeyword:Option<String>,//"" pub filteredKeyword:Option<String>,//""
pub searchDebugInfo:Option<String>,//null pub searchDebugInfo:Option<String>,//null
@ -135,19 +147,19 @@ pub struct InventoryPageResponse{
pub imageSearchStatus:Option<String>,//null pub imageSearchStatus:Option<String>,//null
pub previousPageCursor:Option<String>, pub previousPageCursor:Option<String>,
pub nextPageCursor:Option<String>, pub nextPageCursor:Option<String>,
pub data:Vec<InventoryItem>, pub data:Vec<CreationsItem>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum InventoryPageError{ pub enum CreationsPageError{
ParseError(url::ParseError), ParseError(url::ParseError),
Reqwest(reqwest::Error), Reqwest(reqwest::Error),
} }
impl std::fmt::Display for InventoryPageError{ impl std::fmt::Display for CreationsPageError{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"{self:?}") write!(f,"{self:?}")
} }
} }
impl std::error::Error for InventoryPageError{} impl std::error::Error for CreationsPageError{}
//idk how to do this better //idk how to do this better
enum ReaderType<R:std::io::Read>{ enum ReaderType<R:std::io::Read>{
@ -305,8 +317,9 @@ impl CookieContext{
.error_for_status().map_err(AssetVersionsPageError::Reqwest)? .error_for_status().map_err(AssetVersionsPageError::Reqwest)?
.json::<AssetVersionsPageResponse>().await.map_err(AssetVersionsPageError::Reqwest) .json::<AssetVersionsPageResponse>().await.map_err(AssetVersionsPageError::Reqwest)
} }
pub async fn get_inventory_page(&self,config:InventoryPageRequest)->Result<InventoryPageResponse,InventoryPageError>{ pub async fn get_creations_page(&self,config:&CreationsPageRequest)->Result<CreationsPageResponse,CreationsPageError>{
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)?; let (owner,id)=config.owner.get_url_info();
let mut url=reqwest::Url::parse(format!("https://apis.roblox.com/toolbox-service/v1/creations/{}/{}/10?limit=50",owner,id).as_str()).map_err(CreationsPageError::ParseError)?;
//url borrow scope //url borrow scope
{ {
let mut query=url.query_pairs_mut();//borrow here let mut query=url.query_pairs_mut();//borrow here
@ -315,8 +328,8 @@ impl CookieContext{
} }
} }
self.get(url).await.map_err(InventoryPageError::Reqwest)? self.get(url).await.map_err(CreationsPageError::Reqwest)?
.error_for_status().map_err(InventoryPageError::Reqwest)? .error_for_status().map_err(CreationsPageError::Reqwest)?
.json::<InventoryPageResponse>().await.map_err(InventoryPageError::Reqwest) .json::<CreationsPageResponse>().await.map_err(CreationsPageError::Reqwest)
} }
} }

View File

@ -3,7 +3,7 @@ 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,CloudContext};
use rbx_asset::cookie::{Cookie,CookieContext,AssetVersion,InventoryItem}; use rbx_asset::cookie::{Cookie,CookieContext,AssetVersion,CreationsItem};
type AssetID=u64; type AssetID=u64;
type AssetIDFileMap=Vec<(AssetID,PathBuf)>; type AssetIDFileMap=Vec<(AssetID,PathBuf)>;
@ -23,7 +23,7 @@ enum Commands{
DownloadHistory(DownloadHistorySubcommand), DownloadHistory(DownloadHistorySubcommand),
Download(DownloadSubcommand), Download(DownloadSubcommand),
DownloadDecompile(DownloadDecompileSubcommand), DownloadDecompile(DownloadDecompileSubcommand),
DownloadGroupInventoryJson(DownloadGroupInventoryJsonSubcommand), DownloadCreationsJson(DownloadCreationsJsonSubcommand),
CreateAsset(CreateAssetSubcommand), CreateAsset(CreateAssetSubcommand),
CreateAssetMedia(CreateAssetMediaSubcommand), CreateAssetMedia(CreateAssetMediaSubcommand),
CreateAssetMedias(CreateAssetMediasSubcommand), CreateAssetMedias(CreateAssetMediasSubcommand),
@ -72,9 +72,9 @@ struct DownloadSubcommand{
#[arg(required=true)] #[arg(required=true)]
asset_ids:Vec<AssetID>, asset_ids:Vec<AssetID>,
} }
/// Download the list of asset ids (not the assets themselves) in a group inventory. The output is written to `output_folder/versions.json` /// Download the list of asset ids (not the assets themselves) created by a group or user. The output is written to `output_folder/versions.json`
#[derive(Args)] #[derive(Args)]
struct DownloadGroupInventoryJsonSubcommand{ struct DownloadCreationsJsonSubcommand{
#[arg(long,group="cookie",required=true)] #[arg(long,group="cookie",required=true)]
cookie_literal:Option<String>, cookie_literal:Option<String>,
#[arg(long,group="cookie",required=true)] #[arg(long,group="cookie",required=true)]
@ -83,8 +83,10 @@ struct DownloadGroupInventoryJsonSubcommand{
cookie_file:Option<PathBuf>, cookie_file:Option<PathBuf>,
#[arg(long)] #[arg(long)]
output_folder:Option<PathBuf>, output_folder:Option<PathBuf>,
#[arg(long)] #[arg(long,group="owner",required=true)]
group:u64, group_id:Option<u64>,
#[arg(long,group="owner",required=true)]
user_id:Option<u64>,
} }
/// Upload a (.rbxm, .rbxmx) model file, creating a new asset. Can be any type of model, including modulescripts. /// Upload a (.rbxm, .rbxmx) model file, creating a new asset. Can be any type of model, including modulescripts.
#[derive(Args)] #[derive(Args)]
@ -427,13 +429,16 @@ async fn main()->AResult<()>{
write_scripts:subcommand.write_scripts.unwrap_or(true), write_scripts:subcommand.write_scripts.unwrap_or(true),
}).await }).await
}, },
Commands::DownloadGroupInventoryJson(subcommand)=>download_group_inventory_json( Commands::DownloadCreationsJson(subcommand)=>download_creations_json(
cookie_from_args( cookie_from_args(
subcommand.cookie_literal, subcommand.cookie_literal,
subcommand.cookie_envvar, subcommand.cookie_envvar,
subcommand.cookie_file, subcommand.cookie_file,
).await?, ).await?,
subcommand.group, owner_from_args(
subcommand.user_id,
subcommand.group_id,
)?,
subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()), subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()),
).await, ).await,
Commands::CreateAsset(subcommand)=>create_asset(CreateAssetConfig{ Commands::CreateAsset(subcommand)=>create_asset(CreateAssetConfig{
@ -603,6 +608,14 @@ async fn api_key_from_args(literal:Option<String>,environment:Option<String>,fil
}; };
Ok(ApiKey::new(api_key)) Ok(ApiKey::new(api_key))
} }
fn owner_from_args(user_id:Option<u64>,group_id:Option<u64>)->AResult<rbx_asset::cookie::Owner>{
let owner=match (user_id,group_id){
(Some(id),None)=>rbx_asset::cookie::Owner::User(id),
(None,Some(id))=>rbx_asset::cookie::Owner::Group(id),
_=>Err(anyhow::Error::msg("Illegal owner argument pair"))?,
};
Ok(owner)
}
struct CreateAssetConfig{ struct CreateAssetConfig{
cookie:Cookie, cookie:Cookie,
@ -890,23 +903,26 @@ async fn download_list(cookie:Cookie,asset_id_file_map:AssetIDFileMap)->AResult<
Ok(()) Ok(())
} }
async fn get_inventory_pages(context:&CookieContext,group:u64)->AResult<Vec<InventoryItem>>{ async fn get_creations_pages(context:&CookieContext,owner:rbx_asset::cookie::Owner)->AResult<Vec<CreationsItem>>{
let mut cursor:Option<String>=None; let mut config=rbx_asset::cookie::CreationsPageRequest{
owner,
cursor:None,
};
let mut asset_list=Vec::new(); let mut asset_list=Vec::new();
loop{ loop{
let mut page=context.get_inventory_page(rbx_asset::cookie::InventoryPageRequest{group,cursor}).await?; let mut page=context.get_creations_page(&config).await?;
asset_list.append(&mut page.data); asset_list.append(&mut page.data);
if page.nextPageCursor.is_none(){ if page.nextPageCursor.is_none(){
break; break;
} }
cursor=page.nextPageCursor; config.cursor=page.nextPageCursor;
} }
Ok(asset_list) Ok(asset_list)
} }
async fn download_group_inventory_json(cookie:Cookie,group:u64,output_folder:PathBuf)->AResult<()>{ async fn download_creations_json(cookie:Cookie,owner:rbx_asset::cookie::Owner,output_folder:PathBuf)->AResult<()>{
let context=CookieContext::new(cookie); let context=CookieContext::new(cookie);
let item_list=get_inventory_pages(&context,group).await?; let item_list=get_creations_pages(&context,owner).await?;
let mut path=output_folder.clone(); let mut path=output_folder.clone();
path.set_file_name("versions.json"); path.set_file_name("versions.json");