From e3caf5cc6a6df706b6af451d018928c33924dd95 Mon Sep 17 00:00:00 2001 From: nezuo Date: Thu, 16 Jun 2022 14:30:34 -0700 Subject: [PATCH 1/4] Implement writeNewModelAsset and writeNewPlaceAsset --- src/remodel_api/remodel.rs | 149 ++++++++++++++++++++++++++++++++++--- 1 file changed, 138 insertions(+), 11 deletions(-) diff --git a/src/remodel_api/remodel.rs b/src/remodel_api/remodel.rs index 4c39846..d087c61 100644 --- a/src/remodel_api/remodel.rs +++ b/src/remodel_api/remodel.rs @@ -7,12 +7,13 @@ use std::{ time::Duration, }; -use mlua::{Lua, UserData, UserDataMethods}; +use mlua::{FromLua, Lua, UserData, UserDataMethods, Value}; use rbx_dom_weak::{types::VariantType, InstanceBuilder, WeakDom}; use reqwest::{ header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}, StatusCode, }; +use serde::Serialize; use crate::{ remodel_context::RemodelContext, @@ -29,6 +30,44 @@ fn xml_decode_options() -> rbx_xml::DecodeOptions { rbx_xml::DecodeOptions::new().property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown) } +#[derive(Clone)] +struct UploadQuery { + asset_type: String, + upload_options: UploadOptions, +} + +#[derive(Clone, Serialize)] +struct UploadOptions { + name: String, + description: String, + #[serde(rename(serialize = "isPublic"))] + public: bool, + #[serde(rename(serialize = "allowComments"))] + allow_comments: bool, + #[serde(rename(serialize = "groupId"))] + group_id: Option, +} + +impl<'lua> FromLua<'lua> for UploadOptions { + fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> mlua::Result { + if let Value::Table(table) = lua_value { + let description: Option<_> = table.get("description")?; + let public: Option<_> = table.get("public")?; + let allow_comments: Option<_> = table.get("allowComments")?; + + Ok(UploadOptions { + name: table.get("name")?, + description: description.unwrap_or_default(), + public: public.unwrap_or_default(), + allow_comments: allow_comments.unwrap_or_default(), + group_id: table.get("groupId")?, + }) + } else { + Err(mlua::Error::external("expected table")) + } + } +} + pub struct Remodel; impl Remodel { @@ -283,11 +322,12 @@ impl Remodel { Remodel::import_tree_root(context, source_tree) } - fn write_existing_model_asset( + fn write_model_asset( context: &Lua, lua_instance: LuaInstance, asset_id: u64, - ) -> mlua::Result<()> { + upload_query: Option, + ) -> mlua::Result { let tree = lua_instance.tree.lock().unwrap(); let instance = tree .get_by_ref(lua_instance.id) @@ -303,14 +343,15 @@ impl Remodel { rbx_binary::to_writer(&mut buffer, &tree, &[lua_instance.id]) .map_err(mlua::Error::external)?; - Remodel::upload_asset(context, buffer, asset_id) + Remodel::upload_asset(context, buffer, asset_id, upload_query) } - fn write_existing_place_asset( + fn write_place_asset( context: &Lua, lua_instance: LuaInstance, asset_id: u64, - ) -> mlua::Result<()> { + upload_query: Option, + ) -> mlua::Result { let tree = lua_instance.tree.lock().unwrap(); let instance = tree .get_by_ref(lua_instance.id) @@ -326,10 +367,67 @@ impl Remodel { rbx_binary::to_writer(&mut buffer, &tree, instance.children()) .map_err(mlua::Error::external)?; - Remodel::upload_asset(context, buffer, asset_id) + Remodel::upload_asset(context, buffer, asset_id, upload_query) + } + + fn write_new_model_asset( + context: &Lua, + lua_instance: LuaInstance, + upload_options: UploadOptions, + ) -> mlua::Result { + Remodel::write_model_asset( + context, + lua_instance, + 0, + Some(UploadQuery { + asset_type: "Model".to_string(), + upload_options, + }), + ) + } + + fn write_new_place_asset( + context: &Lua, + lua_instance: LuaInstance, + upload_options: UploadOptions, + ) -> mlua::Result { + Remodel::write_place_asset( + context, + lua_instance, + 0, + Some(UploadQuery { + asset_type: "Place".to_string(), + upload_options, + }), + ) + } + + fn write_existing_model_asset( + context: &Lua, + lua_instance: LuaInstance, + asset_id: u64, + ) -> mlua::Result<()> { + Remodel::write_model_asset(context, lua_instance, asset_id, None)?; + + Ok(()) + } + + fn write_existing_place_asset( + context: &Lua, + lua_instance: LuaInstance, + asset_id: u64, + ) -> mlua::Result<()> { + Remodel::write_place_asset(context, lua_instance, asset_id, None)?; + + Ok(()) } - fn upload_asset(context: &Lua, buffer: Vec, asset_id: u64) -> mlua::Result<()> { + fn upload_asset( + context: &Lua, + buffer: Vec, + asset_id: u64, + upload_query: Option, + ) -> mlua::Result { let re_context = RemodelContext::get(context)?; let auth_cookie = re_context.auth_cookie().ok_or_else(|| { mlua::Error::external( @@ -348,13 +446,21 @@ impl Remodel { .map_err(mlua::Error::external)?; let build_request = move || { - client + let mut request = client .post(&url) .header(COOKIE, format!(".ROBLOSECURITY={}", auth_cookie)) .header(USER_AGENT, "Roblox/WinInet") .header(CONTENT_TYPE, "application/xml") .header(ACCEPT, "application/json") - .body(buffer.clone()) + .body(buffer.clone()); + + if let Some(upload_query) = upload_query.clone() { + request = request + .query(&[("type", upload_query.asset_type)]) + .query(&upload_query.upload_options); + } + + request }; log::debug!("Uploading to Roblox..."); @@ -374,7 +480,14 @@ impl Remodel { } if response.status().is_success() { - Ok(()) + match response.headers().get("roblox-assetid") { + Some(asset_id) => Ok(asset_id + .to_str() + .map_err(mlua::Error::external)? + .parse() + .map_err(mlua::Error::external)?), + None => Err(mlua::Error::external("Roblox API didn't return asset id")), + } } else { Err(mlua::Error::external(format!( "Roblox API returned an error, status {}.", @@ -476,6 +589,20 @@ impl UserData for Remodel { Remodel::read_place_asset(context, asset_id) }); + methods.add_function( + "writeNewModelAsset", + |context, (lua_instance, upload_options): (LuaInstance, UploadOptions)| { + Remodel::write_new_model_asset(context, lua_instance, upload_options) + }, + ); + + methods.add_function( + "writeNewPlaceAsset", + |context, (lua_instance, upload_options): (LuaInstance, UploadOptions)| { + Remodel::write_new_place_asset(context, lua_instance, upload_options) + }, + ); + methods.add_function( "writeExistingModelAsset", |context, (instance, asset_id): (LuaInstance, String)| { From 9b450873ff0dd556c7430da15036a9225cdfb1ba Mon Sep 17 00:00:00 2001 From: nezuo Date: Thu, 16 Jun 2022 14:42:13 -0700 Subject: [PATCH 2/4] Convert return type to string --- README.md | 48 ++++++++++++++++++++++++++++++++++++++ src/remodel_api/remodel.rs | 10 ++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b85ac51..36ea52f 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,54 @@ If the instance is a `DataModel`, this method will throw. Places should be saved Throws on error. +### `remodel.writeNewPlaceAsset` +``` +remodel.writeNewPlaceAsset(instance: DataModel, options: Options): IDK + +where Options: { + name: string, + description: string?, + public: boolean?, + allowComments: boolean?, + groupId: string?, +} +``` + +Uploads the given `DataModel` instance to Roblox.com as a new place and returns its id. + +`description` and `groupId` default to an empty string. `public` and `allowComments` default to `false`. + +`allowComments` does not have any function for places. + +If the instance is not a `DataModel`, this method will throw. Models should be uploaded with `writeNewModelAsset` instead. + +**This method always requires web authentication! See [Authentication](#authentication) for more information.** + +Throws on error. + +### `remodel.writeNewModelAsset` +``` +remodel.writeNewModelAsset(instance: Instance, options: Options): IDK + +where Options: { + name: string, + description: string?, + public: boolean?, + allowComments: boolean?, + groupId: string?, +} +``` + +Uploads the given instance to Roblox.com as a new model and returns its id. + +`description` and `groupId` default to an empty string. `public` and `allowComments` default to `false`. + +If the instance is a `DataModel`, this method will throw. Places should be uploaded with `writeNewPlaceAsset` instead. + +**This method always requires web authentication! See [Authentication](#authentication) for more information.** + +Throws on error. + ### `remodel.writeExistingPlaceAsset` (0.5.0+) ``` remodel.writeExistingPlaceAsset(instance: Instance, assetId: string) diff --git a/src/remodel_api/remodel.rs b/src/remodel_api/remodel.rs index d087c61..f66bd41 100644 --- a/src/remodel_api/remodel.rs +++ b/src/remodel_api/remodel.rs @@ -592,14 +592,20 @@ impl UserData for Remodel { methods.add_function( "writeNewModelAsset", |context, (lua_instance, upload_options): (LuaInstance, UploadOptions)| { - Remodel::write_new_model_asset(context, lua_instance, upload_options) + Ok( + Remodel::write_new_model_asset(context, lua_instance, upload_options)? + .to_string(), + ) }, ); methods.add_function( "writeNewPlaceAsset", |context, (lua_instance, upload_options): (LuaInstance, UploadOptions)| { - Remodel::write_new_place_asset(context, lua_instance, upload_options) + Ok( + Remodel::write_new_place_asset(context, lua_instance, upload_options)? + .to_string(), + ) }, ); From c66dd39ef220a7337153552ea01295fcb0170678 Mon Sep 17 00:00:00 2001 From: nezuo Date: Thu, 16 Jun 2022 14:44:42 -0700 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d10c6..5afc6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Remodel Changelog ## Unreleased Changes +* Added APIs for uploading new models and places: + * `remodel.writeNewPlaceAsset` + * `remodel.writeNewModelAsset` ## 0.10.0 (2022-06-13) * Switched from `rlua` to `mlua`, which should improve Lua performance slightly. ([#73]) From 26ab9bfe5aef2ab3c23b6cee1f140cd58cadbb2b Mon Sep 17 00:00:00 2001 From: nezuo Date: Thu, 16 Jun 2022 14:48:41 -0700 Subject: [PATCH 4/4] Fix documented return types --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 36ea52f..1e889e3 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Throws on error. ### `remodel.writeNewPlaceAsset` ``` -remodel.writeNewPlaceAsset(instance: DataModel, options: Options): IDK +remodel.writeNewPlaceAsset(instance: DataModel, options: Options): string where Options: { name: string, @@ -170,7 +170,7 @@ Throws on error. ### `remodel.writeNewModelAsset` ``` -remodel.writeNewModelAsset(instance: Instance, options: Options): IDK +remodel.writeNewModelAsset(instance: Instance, options: Options): string where Options: { name: string,