upload.rs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. use std::{collections::HashMap, path::PathBuf};
  2. use failure::Fail;
  3. use rbx_dom_weak::RbxInstanceProperties;
  4. use reqwest::header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT};
  5. use crate::{
  6. auth_cookie::get_auth_cookie,
  7. imfs::new::{Imfs, RealFetcher, WatchMode},
  8. snapshot::{apply_patch_set, compute_patch_set, InstancePropertiesWithMeta, RojoTree},
  9. snapshot_middleware::snapshot_from_imfs,
  10. };
  11. #[derive(Debug, Fail)]
  12. pub enum UploadError {
  13. #[fail(display = "Rojo could not find your Roblox auth cookie. Please pass one via --cookie.")]
  14. NeedAuthCookie,
  15. #[fail(display = "XML model file encode error: {}", _0)]
  16. XmlModelEncode(#[fail(cause)] rbx_xml::EncodeError),
  17. #[fail(display = "HTTP error: {}", _0)]
  18. Http(#[fail(cause)] reqwest::Error),
  19. #[fail(display = "Roblox API error: {}", _0)]
  20. RobloxApi(String),
  21. }
  22. impl_from!(UploadError {
  23. rbx_xml::EncodeError => XmlModelEncode,
  24. reqwest::Error => Http,
  25. });
  26. #[derive(Debug)]
  27. pub struct UploadOptions<'a> {
  28. pub fuzzy_project_path: PathBuf,
  29. pub auth_cookie: Option<String>,
  30. pub asset_id: u64,
  31. pub kind: Option<&'a str>,
  32. }
  33. pub fn upload(options: UploadOptions) -> Result<(), UploadError> {
  34. let cookie = options
  35. .auth_cookie
  36. .or_else(get_auth_cookie)
  37. .ok_or(UploadError::NeedAuthCookie)?;
  38. let mut tree = RojoTree::new(InstancePropertiesWithMeta {
  39. properties: RbxInstanceProperties {
  40. name: "ROOT".to_owned(),
  41. class_name: "Folder".to_owned(),
  42. properties: HashMap::new(),
  43. },
  44. metadata: Default::default(),
  45. });
  46. let root_id = tree.get_root_id();
  47. log::trace!("Constructing in-memory filesystem");
  48. let mut imfs = Imfs::new(RealFetcher::new(WatchMode::Disabled));
  49. log::trace!("Reading project root");
  50. let entry = imfs
  51. .get(&options.fuzzy_project_path)
  52. .expect("could not get project path");
  53. log::trace!("Generating snapshot of instances from IMFS");
  54. let snapshot = snapshot_from_imfs(&mut imfs, &entry)
  55. .expect("snapshot failed")
  56. .expect("snapshot did not return an instance");
  57. log::trace!("Computing patch set");
  58. let patch_set = compute_patch_set(&snapshot, &tree, root_id);
  59. log::trace!("Applying patch set");
  60. apply_patch_set(&mut tree, &patch_set);
  61. let root_id = tree.get_root_id();
  62. let mut buffer = Vec::new();
  63. let config = rbx_xml::EncodeOptions::new()
  64. .property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown);
  65. rbx_xml::to_writer(&mut buffer, tree.inner(), &[root_id], config)?;
  66. let url = format!(
  67. "https://data.roblox.com/Data/Upload.ashx?assetid={}",
  68. options.asset_id
  69. );
  70. let client = reqwest::Client::new();
  71. let mut response = client
  72. .post(&url)
  73. .header(COOKIE, format!(".ROBLOSECURITY={}", &cookie))
  74. .header(USER_AGENT, "Roblox/WinInet")
  75. .header("Requester", "Client")
  76. .header(CONTENT_TYPE, "application/xml")
  77. .header(ACCEPT, "application/json")
  78. .body(buffer)
  79. .send()?;
  80. if !response.status().is_success() {
  81. return Err(UploadError::RobloxApi(response.text()?));
  82. }
  83. Ok(())
  84. }