1use layer_tl_types as tl;
16use layer_tl_types::{Cursor, Deserializable};
17use tokio::io::AsyncRead;
18use tokio::io::AsyncReadExt;
19
20use crate::{Client, InvocationError};
21
22pub const UPLOAD_CHUNK_SIZE: i32 = 512 * 1024;
26pub const DOWNLOAD_CHUNK_SIZE: i32 = 512 * 1024;
28const BIG_FILE_THRESHOLD: i64 = 10 * 1024 * 1024; #[derive(Debug, Clone)]
35pub struct UploadedFile {
36 pub(crate) inner: tl::enums::InputFile,
37 pub(crate) mime_type: String,
38 pub(crate) name: String,
39}
40
41impl UploadedFile {
42 pub fn mime_type(&self) -> &str { &self.mime_type }
44 pub fn name(&self) -> &str { &self.name }
46
47 pub fn as_document_media(&self) -> tl::enums::InputMedia {
49 tl::enums::InputMedia::UploadedDocument(tl::types::InputMediaUploadedDocument {
50 nosound_video: false,
51 force_file: false,
52 spoiler: false,
53 file: self.inner.clone(),
54 thumb: None,
55 mime_type: self.mime_type.clone(),
56 attributes: vec![tl::enums::DocumentAttribute::Filename(
57 tl::types::DocumentAttributeFilename { file_name: self.name.clone() }
58 )],
59 stickers: None,
60 ttl_seconds: None,
61 video_cover: None,
62 video_timestamp: None,
63 })
64 }
65
66 pub fn as_photo_media(&self) -> tl::enums::InputMedia {
68 tl::enums::InputMedia::UploadedPhoto(tl::types::InputMediaUploadedPhoto {
69 spoiler: false,
70 file: self.inner.clone(),
71 stickers: None,
72 ttl_seconds: None,
73 })
74 }
75}
76
77pub struct DownloadIter {
83 client: Client,
84 request: Option<tl::functions::upload::GetFile>,
85 done: bool,
86}
87
88impl DownloadIter {
89 pub fn chunk_size(mut self, size: i32) -> Self {
91 if let Some(r) = &mut self.request { r.limit = size; }
92 self
93 }
94
95 pub async fn next(&mut self) -> Result<Option<Vec<u8>>, InvocationError> {
97 if self.done { return Ok(None); }
98 let req = match &self.request {
99 Some(r) => r.clone(),
100 None => return Ok(None),
101 };
102 let body = self.client.rpc_call_raw_pub(&req).await?;
103 let mut cur = Cursor::from_slice(&body);
104 match tl::enums::upload::File::deserialize(&mut cur)? {
105 tl::enums::upload::File::File(f) => {
106 if (f.bytes.len() as i32) < req.limit {
107 self.done = true;
108 if f.bytes.is_empty() { return Ok(None); }
109 }
110 if let Some(r) = &mut self.request {
111 r.offset += req.limit as i64;
112 }
113 Ok(Some(f.bytes))
114 }
115 tl::enums::upload::File::CdnRedirect(_) => {
116 self.done = true;
117 Err(InvocationError::Deserialize("CDN redirect not supported".into()))
118 }
119 }
120 }
121}
122
123impl Client {
126 pub async fn upload_file(
133 &self,
134 data: &[u8],
135 name: &str,
136 mime_type: &str,
137 ) -> Result<UploadedFile, InvocationError> {
138 let file_id = crate::random_i64_pub();
139 let total = data.len() as i64;
140 let big = total >= BIG_FILE_THRESHOLD;
141 let part_size = UPLOAD_CHUNK_SIZE as usize;
142 let total_parts = ((total as usize + part_size - 1) / part_size) as i32;
143
144 for (part_num, chunk) in data.chunks(part_size).enumerate() {
145 if big {
146 let req = tl::functions::upload::SaveBigFilePart {
147 file_id,
148 file_part: part_num as i32,
149 file_total_parts: total_parts,
150 bytes: chunk.to_vec(),
151 };
152 self.rpc_call_raw_pub(&req).await?;
153 } else {
154 let req = tl::functions::upload::SaveFilePart {
155 file_id,
156 file_part: part_num as i32,
157 bytes: chunk.to_vec(),
158 };
159 self.rpc_call_raw_pub(&req).await?;
160 }
161 log::debug!("[layer] Uploaded part {} / {}", part_num + 1, total_parts);
162 }
163
164 let inner: tl::enums::InputFile = if big {
165 tl::enums::InputFile::Big(tl::types::InputFileBig {
166 id: file_id,
167 parts: total_parts,
168 name: name.to_string(),
169 })
170 } else {
171 let md5 = format!("{:x}", md5_bytes(data));
172 tl::enums::InputFile::InputFile(tl::types::InputFile {
173 id: file_id,
174 parts: total_parts,
175 name: name.to_string(),
176 md5_checksum: md5,
177 })
178 };
179
180 log::info!("[layer] File '{}' uploaded ({} bytes, {} parts)", name, total, total_parts);
181 Ok(UploadedFile {
182 inner,
183 mime_type: mime_type.to_string(),
184 name: name.to_string(),
185 })
186 }
187
188 pub async fn upload_stream<R: AsyncRead + Unpin>(
190 &self,
191 reader: &mut R,
192 name: &str,
193 mime_type: &str,
194 ) -> Result<UploadedFile, InvocationError> {
195 let mut data = Vec::new();
196 reader.read_to_end(&mut data).await?;
197 self.upload_file(&data, name, mime_type).await
198 }
199
200 pub async fn send_file(
205 &self,
206 peer: tl::enums::Peer,
207 media: tl::enums::InputMedia,
208 caption: &str,
209 ) -> Result<(), InvocationError> {
210 let input_peer = {
211 let cache = self.inner.peer_cache.lock().await;
212 cache.peer_to_input(&peer)
213 };
214 let req = tl::functions::messages::SendMedia {
215 silent: false,
216 background: false,
217 clear_draft: false,
218 noforwards: false,
219 update_stickersets_order: false,
220 invert_media: false,
221 allow_paid_floodskip: false,
222 peer: input_peer,
223 reply_to: None,
224 media,
225 message: caption.to_string(),
226 random_id: crate::random_i64_pub(),
227 reply_markup: None,
228 entities: None,
229 schedule_date: None,
230 schedule_repeat_period: None,
231 send_as: None,
232 quick_reply_shortcut: None,
233 effect: None,
234 allow_paid_stars: None,
235 suggested_post: None,
236 };
237 self.rpc_call_raw_pub(&req).await?;
238 Ok(())
239 }
240
241 pub async fn send_album(
245 &self,
246 peer: tl::enums::Peer,
247 items: Vec<(tl::enums::InputMedia, String)>, ) -> Result<(), InvocationError> {
249 let input_peer = {
250 let cache = self.inner.peer_cache.lock().await;
251 cache.peer_to_input(&peer)
252 };
253
254 let grouped_id = crate::random_i64_pub().unsigned_abs() as i64;
255
256 let multi: Vec<tl::enums::InputSingleMedia> = items.into_iter().map(|(media, caption)| {
257 tl::enums::InputSingleMedia::InputSingleMedia(tl::types::InputSingleMedia {
258 media,
259 random_id: crate::random_i64_pub(),
260 message: caption,
261 entities: None,
262 })
263 }).collect();
264
265 let req = tl::functions::messages::SendMultiMedia {
266 silent: false,
267 background: false,
268 clear_draft: false,
269 noforwards: false,
270 update_stickersets_order: false,
271 invert_media: false,
272 allow_paid_floodskip: false,
273 peer: input_peer,
274 reply_to: None,
275 multi_media: multi,
276 schedule_date: None,
277 send_as: None,
278 quick_reply_shortcut: None,
279 effect: None,
280 allow_paid_stars: None,
281 };
282 let _ = grouped_id; self.rpc_call_raw_pub(&req).await?;
284 Ok(())
285 }
286
287 pub fn iter_download(&self, location: tl::enums::InputFileLocation) -> DownloadIter {
301 DownloadIter {
302 client: self.clone(),
303 done: false,
304 request: Some(tl::functions::upload::GetFile {
305 precise: false,
306 cdn_supported: false,
307 location,
308 offset: 0,
309 limit: DOWNLOAD_CHUNK_SIZE,
310 }),
311 }
312 }
313
314 pub async fn download_media(
316 &self,
317 location: tl::enums::InputFileLocation,
318 ) -> Result<Vec<u8>, InvocationError> {
319 let mut bytes = Vec::new();
320 let mut iter = self.iter_download(location);
321 while let Some(chunk) = iter.next().await? {
322 bytes.extend_from_slice(&chunk);
323 }
324 Ok(bytes)
325 }
326}
327
328impl crate::update::IncomingMessage {
331 pub fn download_location(&self) -> Option<tl::enums::InputFileLocation> {
335 let media = match &self.raw {
336 tl::enums::Message::Message(m) => m.media.as_ref()?,
337 _ => return None,
338 };
339 match media {
340 tl::enums::MessageMedia::Photo(mp) => {
341 if let Some(tl::enums::Photo::Photo(p)) = &mp.photo {
342 let thumb = p.sizes.iter().filter_map(|s| match s {
344 tl::enums::PhotoSize::PhotoSize(ps) => Some(ps.r#type.clone()),
345 _ => None,
346 }).last().unwrap_or_else(|| "s".to_string());
347
348 Some(tl::enums::InputFileLocation::InputPhotoFileLocation(
349 tl::types::InputPhotoFileLocation {
350 id: p.id,
351 access_hash: p.access_hash,
352 file_reference: p.file_reference.clone(),
353 thumb_size: thumb,
354 }
355 ))
356 } else { None }
357 }
358 tl::enums::MessageMedia::Document(md) => {
359 if let Some(tl::enums::Document::Document(d)) = &md.document {
360 Some(tl::enums::InputFileLocation::InputDocumentFileLocation(
361 tl::types::InputDocumentFileLocation {
362 id: d.id,
363 access_hash: d.access_hash,
364 file_reference: d.file_reference.clone(),
365 thumb_size: String::new(),
366 }
367 ))
368 } else { None }
369 }
370 _ => None,
371 }
372 }
373}
374
375fn md5_bytes(data: &[u8]) -> u128 {
378 let _ = data;
383 0u128
384}