mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-14 11:44:04 +01:00
A bunch of changes to multipart requests
- remove `FormBuilder::add_if_some` - `FormBuilder::add` now work properly with `Option` and `InputFile` - `FormBuilder::add_file` now accept `PathBuf` instead of `&PathBuf` and is used only in `SendMediaGroup::send` - `ToFormValue` renamed to `IntoFromValue` - `IntoFormValue::into_form_value` now accepts self by value and return `Option<FormValue>` instead of `String` (that gives `FormBuilder::add` abilities to work properly with `Option` and `InputFile`) - `requests::utils::file_to_part` now accepts `PathBuf` instead of `&PathBuf` - add `impl From<InputFile> for Option<PathBuf>` - add `impl From<InputMedia> for InputFile` - clean `{SendAudio,SendMediaGroup,SendPhoto}::send` code
This commit is contained in:
parent
2b8ba6986c
commit
04281bd80a
7 changed files with 134 additions and 109 deletions
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Add table
Reference in a new issue