1use anyhow::{Result, anyhow};
2use base64::Engine;
3use serde::Deserialize;
4use wacore::download::MediaType;
5
6use crate::client::Client;
7
8#[derive(Debug, Clone)]
9pub struct UploadResponse {
10 pub url: String,
11 pub direct_path: String,
12 pub media_key: Vec<u8>,
13 pub file_enc_sha256: Vec<u8>,
14 pub file_sha256: Vec<u8>,
15 pub file_length: u64,
16}
17
18#[derive(Deserialize)]
19struct RawUploadResponse {
20 url: String,
21 direct_path: String,
22}
23
24impl Client {
25 pub async fn upload(&self, data: Vec<u8>, media_type: MediaType) -> Result<UploadResponse> {
26 let enc = tokio::task::spawn_blocking({
27 let data = data.clone();
28 move || wacore::upload::encrypt_media(&data, media_type)
29 })
30 .await??;
31
32 let media_conn = self.refresh_media_conn(false).await?;
33 let host = media_conn
34 .hosts
35 .first()
36 .ok_or_else(|| anyhow!("No media hosts"))?;
37
38 let token = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(enc.file_enc_sha256);
39 let mms_type = media_type.mms_type();
40 let url = format!(
41 "https://{}/mms/{}/{}?auth={}&token={}",
42 host.hostname, mms_type, token, media_conn.auth, token
43 );
44
45 let raw: RawUploadResponse =
47 tokio::task::spawn_blocking(move || -> Result<RawUploadResponse> {
48 let mut resp = ureq::post(&url)
49 .header("Content-Type", "application/octet-stream")
51 .header("Origin", "https://web.whatsapp.com")
52 .send(&enc.data_to_upload)?;
53
54 if resp.status().as_u16() >= 400 {
55 return Err(anyhow!(
56 "Upload failed {} body={}",
57 resp.status(),
58 resp.body_mut().read_to_string().unwrap_or_default()
59 ));
60 }
61
62 Ok(resp.body_mut().read_json()?)
63 })
64 .await??;
65
66 let result = UploadResponse {
67 url: raw.url,
68 direct_path: raw.direct_path,
69 media_key: enc.media_key.to_vec(),
70 file_enc_sha256: enc.file_enc_sha256.to_vec(),
71 file_sha256: enc.file_sha256.to_vec(),
72 file_length: data.len() as u64,
73 };
74 Ok(result)
75 }
76}