From 99ea374fc5da596ab55145654e4fe97c1da6a7c8 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Mon, 7 Jan 2019 14:01:53 -0800 Subject: [PATCH] Add 'upload' command to publish places to Roblox for you --- server/src/bin.rs | 41 ++++++++++++++++ server/src/commands/mod.rs | 4 +- server/src/commands/upload.rs | 89 +++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 server/src/commands/upload.rs diff --git a/server/src/bin.rs b/server/src/bin.rs index 8145dce2..db0900b5 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -45,6 +45,13 @@ fn main() { (@arg PROJECT: "Path to the project to serve. Defaults to the current directory.") (@arg output: --output +takes_value +required "Where to output the result.") ) + + (@subcommand upload => + (about: "Generates a place file out of the project and uploads it to Roblox.") + (@arg PROJECT: "Path to the project to upload. Defaults to the current directory.") + (@arg cookie: --cookie +takes_value +required "Security cookie to authenticate with.") + (@arg place_id: --place_id +takes_value +required "Place ID to upload to.") + ) ); // `get_matches` consumes self for some reason. @@ -109,6 +116,40 @@ fn main() { }, } }, + ("upload", Some(sub_matches)) => { + let fuzzy_project_path = match sub_matches.value_of("PROJECT") { + Some(v) => make_path_absolute(Path::new(v)), + None => std::env::current_dir().unwrap(), + }; + + let security_cookie = sub_matches.value_of("cookie").unwrap(); + + let place_id: u64 = { + let arg = sub_matches.value_of("place_id").unwrap(); + + match arg.parse() { + Ok(v) => v, + Err(_) => { + error!("Invalid place ID {}", arg); + process::exit(1); + }, + } + }; + + let options = commands::UploadOptions { + fuzzy_project_path, + security_cookie: security_cookie.to_string(), + place_id, + }; + + match commands::upload(&options) { + Ok(_) => {}, + Err(e) => { + error!("{}", e); + process::exit(1); + }, + } + }, _ => { app.print_help().expect("Could not print help text to stdout!"); }, diff --git a/server/src/commands/mod.rs b/server/src/commands/mod.rs index 6f2bb133..966d67db 100644 --- a/server/src/commands/mod.rs +++ b/server/src/commands/mod.rs @@ -1,7 +1,9 @@ mod serve; mod init; mod build; +mod upload; pub use self::serve::*; pub use self::init::*; -pub use self::build::*; \ No newline at end of file +pub use self::build::*; +pub use self::upload::*; \ No newline at end of file diff --git a/server/src/commands/upload.rs b/server/src/commands/upload.rs new file mode 100644 index 00000000..78cf4bd9 --- /dev/null +++ b/server/src/commands/upload.rs @@ -0,0 +1,89 @@ +use std::{ + path::PathBuf, + io, +}; + +use failure::Fail; + +use reqwest::header::{USER_AGENT, CONTENT_TYPE, COOKIE}; + +use crate::{ + rbx_session::construct_oneoff_tree, + project::{Project, ProjectLoadFuzzyError}, + imfs::Imfs, +}; + +#[derive(Debug, Fail)] +pub enum UploadError { + #[fail(display = "Roblox API Error: {}", _0)] + RobloxApiError(String), + + #[fail(display = "Project load error: {}", _0)] + ProjectLoadError(#[fail(cause)] ProjectLoadFuzzyError), + + #[fail(display = "IO error: {}", _0)] + IoError(#[fail(cause)] io::Error), + + #[fail(display = "HTTP error: {}", _0)] + HttpError(#[fail(cause)] reqwest::Error), +} + +impl From for UploadError { + fn from(error: ProjectLoadFuzzyError) -> UploadError { + UploadError::ProjectLoadError(error) + } +} + +impl From for UploadError { + fn from(error: io::Error) -> UploadError { + UploadError::IoError(error) + } +} + +impl From for UploadError { + fn from(error: reqwest::Error) -> UploadError { + UploadError::HttpError(error) + } +} + +#[derive(Debug)] +pub struct UploadOptions { + pub fuzzy_project_path: PathBuf, + pub security_cookie: String, + pub place_id: u64, +} + +pub fn upload(options: &UploadOptions) -> Result<(), UploadError> { + info!("Looking for project at {}", options.fuzzy_project_path.display()); + + let project = Project::load_fuzzy(&options.fuzzy_project_path)?; + + info!("Found project at {}", project.file_location.display()); + info!("Using project {:#?}", project); + + let mut imfs = Imfs::new(); + imfs.add_roots_from_project(&project)?; + let tree = construct_oneoff_tree(&project, &imfs); + + let root_id = tree.get_root_id(); + let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids(); + let mut contents = Vec::new(); + rbx_xml::encode(&tree, top_level_ids, &mut contents); + + let url = format!("https://data.roblox.com/Data/Upload.ashx?json=1&type=Place&assetid={}", options.place_id); + + let client = reqwest::Client::new(); + let mut response = client.post(&url) + .header(COOKIE, format!(".ROBLOSECURITY={}", &options.security_cookie)) + .header(USER_AGENT, "Roblox/WinInet") + .header("Requester", "Client") + .header(CONTENT_TYPE, "application/xml") + .body(contents) + .send()?; + + if !response.status().is_success() { + return Err(UploadError::RobloxApiError(response.text()?)); + } + + Ok(()) +} \ No newline at end of file