From 87683e02775974f753d5da03d6d6a3be7ea9db06 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Wed, 2 Apr 2025 16:40:44 -0700 Subject: [PATCH] holy --- validation/src/create_submission.rs | 119 +++++++++++++++++++++++++++- validation/src/nats_types.rs | 2 +- validation/src/validator.rs | 4 +- 3 files changed, 120 insertions(+), 5 deletions(-) diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs index 563798b..4e5ea95 100644 --- a/validation/src/create_submission.rs +++ b/validation/src/create_submission.rs @@ -3,7 +3,13 @@ use crate::nats_types::CreateSubmissionRequest; #[allow(dead_code)] #[derive(Debug)] pub enum Error{ - Get(rbx_asset::cookie::GetError), + ModelVersionsPage(rbx_asset::cookie::PageError), + EmptyVersionsPage, + WrongCreatorType, + ModelFileDownload(rbx_asset::cookie::GetError), + ModelFileDecode(crate::validator::ReadDomError), + GetMapInfo(GetMapInfoError), + ParseGameID(ParseGameIDError), ApiActionSubmissionCreate(submissions_api::Error), } impl std::fmt::Display for Error{ @@ -15,16 +21,125 @@ impl std::error::Error for Error{} impl crate::message_handler::MessageHandler{ pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{ + // discover the latest asset version + let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{ + asset_id:create_info.ModelID, + cursor:None + }).await.map_err(Error::ModelVersionsPage)?; + + // grab version info + let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?; + + if first_version.creatorType!="User"{ + return Err(Error::WrongCreatorType); + } + + let asset_creator_id=first_version.creatorTargetId; + let asset_version=first_version.assetVersionNumber; + // download the map model version let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:create_info.ModelID, version:None, - }).await.map_err(Error::Get)?; + }).await.map_err(Error::ModelFileDownload)?; + + // decode dom (slow!) + let dom=crate::validator::read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; // parse create fields out of asset + let MapInfo{ + display_name, + creator, + game_id, + }=get_mapinfo(&dom).map_err(Error::GetMapInfo)?; + + let game_id=game_id.map_err(Error::ParseGameID)?; // call create on api + self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ + OperationID:create_info.OperationID, + AssetOwner:asset_creator_id as i64, + DisplayName:display_name.unwrap_or_default(), + Creator:creator.unwrap_or_default(), + GameID:game_id as i32, + AssetID:create_info.ModelID, + AssetVersion:asset_version, + }).await.map_err(Error::ApiActionSubmissionCreate)?; Ok(()) } } + +enum GameID{ + Bhop=1, + Surf=2, + FlyTrials=5, +} +#[derive(Debug)] +pub struct ParseGameIDError; +impl std::str::FromStr for GameID{ + type Err=ParseGameIDError; + fn from_str(s:&str)->Result<Self,Self::Err>{ + if s.starts_with("bhop_"){ + return Ok(GameID::Bhop); + } + if s.starts_with("surf_"){ + return Ok(GameID::Surf); + } + if s.starts_with("flytrials_"){ + return Ok(GameID::FlyTrials); + } + return Err(ParseGameIDError); + } +} + +struct MapInfo<'a>{ + display_name:Result<&'a str,StringValueError>, + creator:Result<&'a str,StringValueError>, + game_id:Result<GameID,ParseGameIDError>, +} + +enum StringValueError{ + ObjectNotFound, + ValueNotSet, + NonStringValue, +} + +fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{ + let instance=instance.ok_or(StringValueError::ObjectNotFound)?; + let value=instance.properties.get("Value").ok_or(StringValueError::ValueNotSet)?; + match value{ + rbx_dom_weak::types::Variant::String(value)=>Ok(value), + _=>Err(StringValueError::NonStringValue), + } +} + +#[derive(Debug)] +pub enum GetMapInfoError{ + ModelFileRootMustHaveOneChild, + ModelFileChildRefIsNil, +} + +fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result<MapInfo,GetMapInfoError>{ + let &[map_ref]=dom.root().children()else{ + return Err(GetMapInfoError::ModelFileRootMustHaveOneChild); + }; + let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?; + + Ok(MapInfo{ + display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")), + creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")), + game_id:model_instance.name.parse(), + }) +} + +fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&'a rbx_dom_weak::Instance,name:&'a str,class:&'a str)->Option<&'a rbx_dom_weak::Instance>{ + for &referent in instance.children(){ + if let Some(c)=dom.get_by_ref(referent){ + if c.name==name&&crate::validator::class_is_a(c.class.as_str(),class) { + return Some(c); + } + } + } + None +} diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs index f84e4b6..4221532 100644 --- a/validation/src/nats_types.rs +++ b/validation/src/nats_types.rs @@ -8,7 +8,7 @@ #[derive(serde::Deserialize)] pub struct CreateSubmissionRequest{ // operation_id is passed back in the response message - pub OperationID:i64, + pub OperationID:i32, pub ModelID:u64, } diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 0302c4e..5a0dc83 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -291,7 +291,7 @@ impl std::fmt::Display for ReadDomError{ } impl std::error::Error for ReadDomError{} -fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{ +pub fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{ let mut first_8=[0u8;8]; std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?; std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?; @@ -307,7 +307,7 @@ fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::W } } -fn class_is_a(class:&str,superclass:&str)->bool{ +pub fn class_is_a(class:&str,superclass:&str)->bool{ if class==superclass{ return true }