diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a314e6..488c7057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait - Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494)) - Add `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to update filters ([PR 1146](https://github.com/teloxide/teloxide/pull/1146)) +- Add local TBA file downloading support in `crate::net::download` ### Changed diff --git a/crates/teloxide-core/src/bot/download.rs b/crates/teloxide-core/src/bot/download.rs index 8e224a6d..3b9e03a5 100644 --- a/crates/teloxide-core/src/bot/download.rs +++ b/crates/teloxide-core/src/bot/download.rs @@ -1,6 +1,12 @@ +use std::path::Path; + use bytes::Bytes; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; -use tokio::io::AsyncWrite; + +use tokio::{ + fs::File, + io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}, +}; use crate::{ bot::Bot, @@ -17,9 +23,19 @@ impl Download for Bot { fn download_file<'dst>( &self, - path: &str, + path: &'dst str, destination: &'dst mut (dyn AsyncWrite + Unpin + Send), ) -> Self::Fut<'dst> { + let is_localhost = match &self.api_url.host_str() { + Some(host) => ["localhost", "127.0.0.1"].contains(host), + None => false, + }; + // If path is absolute and api_url contains localhost, it is pretty clear there + // is a local TBA server with --local option, we can just copy the file + if is_localhost && Path::new(&path).is_absolute() { + return copy_file(path, destination).boxed(); + } + net::download_file( &self.client, reqwest::Url::clone(&*self.api_url), @@ -45,3 +61,23 @@ impl Download for Bot { .boxed() } } + +async fn copy_file<'o, D>(path: &'o str, dst: &'o mut D) -> Result<(), DownloadError> +where + D: ?Sized + AsyncWrite + Unpin, +{ + let mut src_file = File::open(path).await?; + + let mut buffer = [0; 1024]; + loop { + let n = src_file.read(&mut buffer).await?; + if n == 0 { + break; + } + + dst.write_all(&buffer[..n]).await?; + } + + dst.flush().await?; + Ok(()) +} diff --git a/crates/teloxide-core/src/local_macros.rs b/crates/teloxide-core/src/local_macros.rs index a6d2abbf..f40450eb 100644 --- a/crates/teloxide-core/src/local_macros.rs +++ b/crates/teloxide-core/src/local_macros.rs @@ -380,7 +380,7 @@ macro_rules! download_forward { fn download_file<'dst>( &self, - path: &str, + path: &'dst str, destination: &'dst mut (dyn tokio::io::AsyncWrite + core::marker::Unpin + core::marker::Send), diff --git a/crates/teloxide-core/src/net/download.rs b/crates/teloxide-core/src/net/download.rs index 3a6ea43a..7fae3de1 100644 --- a/crates/teloxide-core/src/net/download.rs +++ b/crates/teloxide-core/src/net/download.rs @@ -28,6 +28,9 @@ pub trait Download { /// /// `path` can be obtained from [`GetFile`]. /// + /// If the bot uses a [local bot api](https://github.com/tdlib/telegram-bot-api), this function + /// just copies the file into `destination`. + /// /// To download as a stream of chunks, see [`download_file_stream`]. /// /// ## Examples @@ -54,7 +57,7 @@ pub trait Download { /// [`download_file_stream`]: Self::download_file_stream fn download_file<'dst>( &self, - path: &str, + path: &'dst str, destination: &'dst mut (dyn AsyncWrite + Unpin + Send), ) -> Self::Fut<'dst>;