dingtalk_stream/client/stream_/
download_resources.rs1use crate::frames::down_message::callback_message::{PayloadFile, PayloadPicture};
2use crate::DingTalkStream;
3use anyhow::anyhow;
4use async_trait::async_trait;
5use serde_json::json;
6use std::ops::Deref;
7use std::path::PathBuf;
8use log::info;
9use url::Url;
10
11#[async_trait]
12pub trait DingtalkResource {
13 type T;
14 async fn fetch(
15 &self,
16 dingtalk: &DingTalkStream,
17 save_to: PathBuf,
18 ) -> crate::Result<(PathBuf, Self::T)>;
19}
20
21#[cfg(feature = "image")]
22type Picture = image::DynamicImage;
23
24#[cfg(not(feature = "image"))]
25type Picture = Vec<u8>;
26
27#[async_trait]
28impl DingtalkResource for PayloadPicture {
29 type T = Picture;
30
31 async fn fetch(
32 &self,
33 dingtalk: &DingTalkStream,
34 save_to_dir: PathBuf,
35 ) -> crate::Result<(PathBuf, Self::T)> {
36 if !save_to_dir.exists() {
37 tokio::fs::create_dir_all(&save_to_dir).await?;
38 }
39 if save_to_dir.is_file() {
40 return Err(anyhow!("save_to_dir is a file"));
41 }
42 let filepath = save_to_dir.join(format!(
43 "{}.png",
44 format!("{:x}", md5::compute(&self.download_code))
45 ));
46 if filepath.exists() {
47 let bytes = tokio::fs::read(&filepath).await?;
48 #[cfg(feature = "image")]
49 return Ok((filepath, image::load_from_memory(&bytes)?));
50 #[cfg(not(feature = "image"))]
51 return Ok((filepath, bytes));
52 }
53 let download_url = fetch_download_url(dingtalk, &self.download_code).await?;
54 let bytes = dingtalk
55 .http_client
56 .get(download_url)
57 .send()
58 .await?
59 .bytes()
60 .await?;
61
62 #[cfg(feature = "image")]
63 let image = {
64 use std::io::Cursor;
65 let image = image::load_from_memory(&bytes)?;
66 let mut cursor = Cursor::new(vec![]);
67 image.write_to(&mut cursor, image::ImageFormat::Png)?;
68 tokio::fs::write(&filepath, cursor.into_inner()).await?;
69 image
70 };
71 #[cfg(not(feature = "image"))]
72 let image = {
73 tokio::fs::write(&filepath, bytes.as_ref()).await?;
74 bytes.to_vec()
75 };
76 info!("Downloaded image to {}", filepath.display());
77 Ok((filepath, image))
78 }
79}
80
81#[async_trait]
82impl DingtalkResource for PayloadFile {
83 type T = Vec<u8>;
84
85 async fn fetch(
86 &self,
87 dingtalk: &DingTalkStream,
88 save_to_dir: PathBuf,
89 ) -> crate::Result<(PathBuf, Self::T)> {
90 if !save_to_dir.exists() {
91 tokio::fs::create_dir_all(&save_to_dir).await?;
92 }
93 if save_to_dir.is_file() {
94 return Err(anyhow!("save_to_dir is a file"));
95 }
96 let filepath = save_to_dir.join(format!(
97 "{}_{}",
98 format!("{:x}", md5::compute(&self.download_code)),
99 self.file_name
100 ));
101 if filepath.exists() {
102 let bytes = tokio::fs::read(&filepath).await?;
103 return Ok((filepath, bytes));
104 }
105 let download_url = fetch_download_url(dingtalk, &self.download_code).await?;
106 let bytes = dingtalk
107 .http_client
108 .get(download_url)
109 .send()
110 .await?
111 .bytes()
112 .await?;
113 tokio::fs::write(&filepath, bytes.as_ref()).await?;
114 info!(
115 "Downloaded {} file to {}",
116 self.download_code,
117 filepath.display()
118 );
119 Ok((filepath, bytes.to_vec()))
120 }
121}
122
123async fn fetch_download_url(dingtalk: &DingTalkStream, download_code: &str) -> crate::Result<Url> {
124 let access_token = dingtalk.get_access_token().await?;
125 let response = dingtalk
126 .http_client
127 .post(crate::MESSAGE_FILES_DOWNLOAD_URL)
128 .header("x-acs-dingtalk-access-token", access_token.deref())
129 .header("Content-Type", "application/json")
130 .json(&json!({
131 "robotCode": &dingtalk.credential.client_id,
132 "downloadCode": download_code,
133 }))
134 .send()
135 .await?;
136 let code = response.status();
137 if response.status().is_success() {
138 let json = response.json::<serde_json::Value>().await?;
139 let download_url = json.get("downloadUrl").and_then(|it| it.as_str());
140 info!(
141 "Get download url by download_code: {download_code}, download_url: {}",
142 download_url.unwrap_or("None")
143 );
144 Ok(download_url
145 .ok_or(anyhow!("download_url is not found"))?
146 .try_into()?)
147 } else {
148 Err(anyhow!(
149 "Failed to download file with unexpected http-code: {}, download_code: {}",
150 code,
151 download_code
152 ))
153 }
154}