diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs index 72a013e7..6198e403 100644 --- a/src/requests/form_builder.rs +++ b/src/requests/form_builder.rs @@ -6,6 +6,7 @@ use crate::{ requests::utils, types::{ChatId, InputMedia, ParseMode}, }; +use crate::types::InputFile; /// This is a convenient struct that builds `reqwest::multipart::Form` /// from scratch. @@ -19,28 +20,22 @@ impl FormBuilder { } /// Add the supplied key-value pair to this `FormBuilder`. - pub fn add<T>(self, name: &str, value: &T) -> Self + pub fn add<T>(self, name: &str, value: T) -> Self where - T: ToFormValue + ?Sized, + T: IntoFormValue, { - Self { - form: self.form.text(name.to_owned(), value.to_form_value()), + let name = name.to_owned(); + match value.into_form_value() { + Some(FormValue::Str(string)) => Self { + form: self.form.text(name, string), + }, + Some(FormValue::File(path)) => self.add_file(&name, path), + None => self, } } - /// Adds a key-value pair to the supplied `FormBuilder` if `value` is some. - /// Don't forget to implement `serde::Serialize` for `T`! - pub fn add_if_some<T>(self, name: &str, value: Option<&T>) -> Self - where - T: ToFormValue + ?Sized, - { - match value { - None => Self { form: self.form }, - Some(value) => self.add(name, value), - } - } - - pub fn add_file(self, name: &str, path_to_file: &PathBuf) -> Self { + // used in SendMediaGroup + pub fn add_file(self, name: &str, path_to_file: PathBuf) -> Self { Self { form: self .form @@ -53,50 +48,83 @@ impl FormBuilder { } } -pub trait ToFormValue { - fn to_form_value(&self) -> String; +pub enum FormValue { + File(PathBuf), + Str(String), +} + +pub trait IntoFormValue { + fn into_form_value(self) -> Option<FormValue>; } macro_rules! impl_for_struct { ($($name:ty),*) => { $( - impl ToFormValue for $name { - fn to_form_value(&self) -> String { - serde_json::to_string(self).expect("serde_json::to_string failed") + impl IntoFormValue for $name { + fn into_form_value(self) -> Option<FormValue> { + let json = serde_json::to_string(&self) + .expect("serde_json::to_string failed"); + Some(FormValue::Str(json)) } } )* }; } -impl_for_struct!(bool, i32, i64, Vec<InputMedia>); +impl_for_struct!(bool, i32, i64); -impl ToFormValue for str { - fn to_form_value(&self) -> String { - self.to_owned() +impl<T> IntoFormValue for Option<T> where T: IntoFormValue { + fn into_form_value(self) -> Option<FormValue> { + self.and_then(IntoFormValue::into_form_value) } } -impl ToFormValue for ParseMode { - fn to_form_value(&self) -> String { - match self { +impl IntoFormValue for &[InputMedia] { + fn into_form_value(self) -> Option<FormValue> { + let json = serde_json::to_string(self) + .expect("serde_json::to_string failed"); + Some(FormValue::Str(json)) + } +} + +impl IntoFormValue for &str { + fn into_form_value(self) -> Option<FormValue> { + Some(FormValue::Str(self.to_owned())) + } +} + +impl IntoFormValue for ParseMode { + fn into_form_value(self) -> Option<FormValue> { + let string = match self { ParseMode::HTML => String::from("HTML"), ParseMode::Markdown => String::from("Markdown"), - } + }; + Some(FormValue::Str(string)) } } -impl ToFormValue for ChatId { - fn to_form_value(&self) -> String { - match self { +impl IntoFormValue for ChatId { + fn into_form_value(self) -> Option<FormValue> { + let string = match self { ChatId::Id(id) => id.to_string(), ChatId::ChannelUsername(username) => username.clone(), - } + }; + Some(FormValue::Str(string)) } } -impl ToFormValue for String { - fn to_form_value(&self) -> String { - self.to_owned() +impl IntoFormValue for String { + fn into_form_value(self) -> Option<FormValue> { + Some(FormValue::Str(self.to_owned())) + } +} + +impl IntoFormValue for InputFile { + fn into_form_value(self) -> Option<FormValue> { + match self { + InputFile::File(path) => Some(FormValue::File(path)), + InputFile::Url(url) => Some(FormValue::Str(url)), + InputFile::FileId(file_id) => Some(FormValue::Str(file_id)), + } } } diff --git a/src/requests/send_audio.rs b/src/requests/send_audio.rs index b41ff0b0..e0e9aca7 100644 --- a/src/requests/send_audio.rs +++ b/src/requests/send_audio.rs @@ -74,40 +74,23 @@ impl Request for SendAudio<'_> { impl SendAudio<'_> { pub async fn send(self) -> ResponseResult<Message> { - let mut params = FormBuilder::new() - .add("chat_id", &self.chat_id) - .add_if_some("caption", self.caption.as_ref()) - .add_if_some("parse_mode", self.parse_mode.as_ref()) - .add_if_some("duration", self.duration.as_ref()) - .add_if_some("performer", self.performer.as_ref()) - .add_if_some("title", self.title.as_ref()) - .add_if_some( - "disable_notification", - self.disable_notification.as_ref(), - ) - .add_if_some( - "reply_to_message_id", - self.reply_to_message_id.as_ref(), - ); - params = match self.audio { - InputFile::File(file) => params.add_file("audio", &file), - InputFile::Url(url) => params.add("audio", &url), - InputFile::FileId(file_id) => params.add("audio", &file_id), - }; - if let Some(thumb) = self.thumb { - params = match thumb { - InputFile::File(file) => params.add_file("thumb", &file), - InputFile::Url(url) => params.add("thumb", &url), - InputFile::FileId(file_id) => params.add("thumb", &file_id), - } - } - let params = params.build(); + let params = FormBuilder::new() + .add("chat_id", self.chat_id) + .add("caption", self.caption) + .add("parse_mode", self.parse_mode) + .add("duration", self.duration) + .add("performer", self.performer) + .add("title", self.title) + .add("disable_notification", self.disable_notification) + .add("reply_to_message_id", self.reply_to_message_id) + .add("audio", self.audio) + .add("thumb", self.thumb); network::request_multipart( &self.ctx.client, &self.ctx.token, "sendAudio", - params, + params.build(), ) .await } diff --git a/src/requests/send_media_group.rs b/src/requests/send_media_group.rs index 4e81ab61..3044ca52 100644 --- a/src/requests/send_media_group.rs +++ b/src/requests/send_media_group.rs @@ -5,7 +5,7 @@ use crate::{ requests::{ form_builder::FormBuilder, Request, RequestContext, ResponseResult, }, - types::{ChatId, InputMedia, Message}, + types::{ChatId, InputMedia, Message, InputFile}, }; /// Use this method to send a group of photos or videos as an album. @@ -32,21 +32,15 @@ impl Request for SendMediaGroup<'_> { impl SendMediaGroup<'_> { pub async fn send(self) -> ResponseResult<Vec<Message>> { let form = FormBuilder::new() - .add("chat_id", &self.chat_id) - .add("media", &self.media) - .add_if_some( - "disable_notification", - self.disable_notification.as_ref(), - ) - .add_if_some( - "reply_to_message_id", - self.reply_to_message_id.as_ref(), - ); + .add("chat_id", self.chat_id) + .add("media", &self.media[..]) + .add("disable_notification", self.disable_notification) + .add("reply_to_message_id", self.reply_to_message_id); - let form = self.media.iter().filter_map(|e| e.media().as_file()) - .fold(form, |acc, path| + let form = self.media.into_iter().filter_map(|e| InputFile::from(e).into()) + .fold(form, |acc, path: std::path::PathBuf| acc.add_file( - &path.file_name().unwrap().to_string_lossy(), + &path.file_name().unwrap().to_string_lossy().into_owned(), path, ) ); @@ -96,3 +90,14 @@ impl<'a> SendMediaGroup<'a> { self } } + +#[tokio::test] +async fn main() { + use crate::types::InputMedia; + + let bot = crate::bot::Bot::new("457569668:AAF4mhmoPmH1Ud943bZqX-EYRCxKXmTt0f8"); + bot.send_media_group(218485655, vec![ + InputMedia::Photo { media: InputFile::File(std::path::PathBuf::from("/home/waffle/Pictures/28b.png")), caption: None, parse_mode: None }, + InputMedia::Photo { media: InputFile::File(std::path::PathBuf::from("/home/waffle/Pictures/334-3341035_free-png-download-tide-pod-chan-transparent-png.png")), caption: None, parse_mode: None }]).send().await.unwrap(); + bot.send_photo(218485655, InputFile::File(std::path::PathBuf::from("/home/waffle/Pictures/28b.png"))).send().await.unwrap(); +} \ No newline at end of file diff --git a/src/requests/send_photo.rs b/src/requests/send_photo.rs index 939510b2..33a1daa5 100644 --- a/src/requests/send_photo.rs +++ b/src/requests/send_photo.rs @@ -55,31 +55,19 @@ impl Request for SendPhoto<'_> { impl SendPhoto<'_> { pub async fn send(self) -> ResponseResult<Message> { - let mut params = FormBuilder::new() - .add("chat_id", &self.chat_id) - .add_if_some("caption", self.caption.as_ref()) - .add_if_some("parse_mode", self.parse_mode.as_ref()) - .add_if_some( - "disable_notification", - self.disable_notification.as_ref(), - ) - .add_if_some( - "reply_to_message_id", - self.reply_to_message_id.as_ref(), - ); - - params = match self.photo { - InputFile::File(path) => params.add_file("photo", &path), - InputFile::Url(url) => params.add("photo", &url), - InputFile::FileId(file_id) => params.add("photo", &file_id), - }; - let params = params.build(); + let params = FormBuilder::new() + .add("chat_id", self.chat_id) + .add("caption", self.caption) + .add("parse_mode", self.parse_mode) + .add("disable_notification", self.disable_notification) + .add("reply_to_message_id", self.reply_to_message_id) + .add("photo", self.photo); network::request_multipart( &self.ctx.client, &self.ctx.token, "sendPhoto", - params, + params.build(), ) .await } diff --git a/src/requests/utils.rs b/src/requests/utils.rs index 9add97f0..9eecadcf 100644 --- a/src/requests/utils.rs +++ b/src/requests/utils.rs @@ -21,8 +21,14 @@ impl tokio::codec::Decoder for FileDecoder { } } -pub fn file_to_part(path_to_file: &PathBuf) -> Part { - let file = tokio::fs::File::open(path_to_file.clone()) +pub fn file_to_part(path_to_file: PathBuf) -> Part { + let file_name = path_to_file + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(); + + let file = tokio::fs::File::open(path_to_file) .map(|file| { FramedRead::new( file.unwrap(), /* TODO: this can cause panics */ @@ -30,12 +36,6 @@ pub fn file_to_part(path_to_file: &PathBuf) -> Part { ) }) .flatten_stream(); - let part = Part::stream(Body::wrap_stream(file)).file_name( - path_to_file - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(), - ); + let part = Part::stream(Body::wrap_stream(file)).file_name(file_name); part } diff --git a/src/types/input_file.rs b/src/types/input_file.rs index 632e744f..90ac9b08 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -30,6 +30,15 @@ impl InputFile { } } +impl From<InputFile> for Option<PathBuf> { + fn from(file: InputFile) -> Self { + match file { + InputFile::File(path) => Some(path), + _ => None, + } + } +} + impl serde::Serialize for InputFile { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where diff --git a/src/types/input_media.rs b/src/types/input_media.rs index 2fb8fff7..43374167 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -177,6 +177,18 @@ impl InputMedia { } } +impl From<InputMedia> for InputFile { + fn from(media: InputMedia) -> InputFile { + match media { + InputMedia::Photo { media, .. } + | InputMedia::Document { media, .. } + | InputMedia::Audio { media, .. } + | InputMedia::Animation { media, .. } + | InputMedia::Video { media, .. } => media, + } + } +} + #[cfg(test)] mod tests { use super::*;