mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-10 20:12:25 +01:00
rewrite requests to use serde-multipart
Rewrite multipart requests to use serde_multipart instead of FromBuilder
This commit is contained in:
parent
b303958afd
commit
5afe72b368
19 changed files with 149 additions and 475 deletions
|
@ -17,7 +17,7 @@ const TELEGRAM_API_URL: &str = "https://api.telegram.org";
|
|||
///
|
||||
/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests
|
||||
fn method_url(base: &str, token: &str, method_name: &str) -> String {
|
||||
format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name,)
|
||||
format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name)
|
||||
}
|
||||
|
||||
/// Creates URL for downloading a file. See the [Telegram documentation].
|
||||
|
|
|
@ -1,25 +1,39 @@
|
|||
use reqwest::{multipart::Form, Client, Response};
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::{Client, Response};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::{requests::ResponseResult, RequestError};
|
||||
|
||||
use super::{TelegramResponse, TELEGRAM_API_URL};
|
||||
use std::time::Duration;
|
||||
use crate::{
|
||||
net::{TelegramResponse, TELEGRAM_API_URL},
|
||||
requests::ResponseResult,
|
||||
serde_multipart::to_form,
|
||||
RequestError,
|
||||
};
|
||||
|
||||
const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10);
|
||||
|
||||
pub async fn request_multipart<T>(
|
||||
pub async fn request_multipart<P, R>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: Form,
|
||||
) -> ResponseResult<T>
|
||||
params: &P, // I'll regret this
|
||||
) -> ResponseResult<R>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
P: Serialize,
|
||||
R: DeserializeOwned,
|
||||
{
|
||||
use crate::serde_multipart::Error;
|
||||
let form = match to_form(params).await {
|
||||
Ok(x) => x,
|
||||
Err(Error::Io(ioerr)) => return Err(RequestError::Io(ioerr)),
|
||||
Err(_) => unreachable!(
|
||||
"we don't create requests those fail to serialize (if you see this, open an issue :|)"
|
||||
),
|
||||
};
|
||||
|
||||
let response = client
|
||||
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
|
||||
.multipart(params)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(RequestError::NetworkError)?;
|
||||
|
@ -27,15 +41,15 @@ where
|
|||
process_response(response).await
|
||||
}
|
||||
|
||||
pub async fn request_json<T, P>(
|
||||
pub async fn request_json<P, R>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: &P,
|
||||
) -> ResponseResult<T>
|
||||
) -> ResponseResult<R>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
P: Serialize,
|
||||
R: DeserializeOwned,
|
||||
{
|
||||
let response = client
|
||||
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
|
||||
|
|
|
@ -1,51 +1,38 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::form_builder::FormBuilder,
|
||||
types::{MaskPosition, True},
|
||||
Bot,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
requests::{RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::StickerType,
|
||||
};
|
||||
|
||||
/// Use this method to add a new sticker to a set created by the bot.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#addstickertoset).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct AddStickerToSet {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
user_id: i32,
|
||||
name: String,
|
||||
#[serde(flatten)]
|
||||
sticker_type: StickerType,
|
||||
emojis: String,
|
||||
mask_position: Option<MaskPosition>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for AddStickerToSet {
|
||||
impl Request for AddStickerToSet {
|
||||
type Output = True;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<True>> {
|
||||
let builder =
|
||||
FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name);
|
||||
|
||||
let builder = match &self.sticker_type {
|
||||
StickerType::Png(file) => builder.add_input_file("png_sticker", &file),
|
||||
StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file),
|
||||
}
|
||||
.await?
|
||||
.add_text("emojis", &self.emojis)
|
||||
.add_text("mask_position", &self.mask_position);
|
||||
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"addStickerToSet",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<True> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "addStickerToSet", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{MaskPosition, StickerType, True},
|
||||
Bot,
|
||||
};
|
||||
|
@ -9,12 +11,15 @@ use crate::{
|
|||
/// able to edit the created sticker set.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#createnewstickerset).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct CreateNewStickerSet {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
user_id: i32,
|
||||
name: String,
|
||||
title: String,
|
||||
#[serde(flatten)]
|
||||
sticker_type: StickerType,
|
||||
emojis: String,
|
||||
contains_masks: Option<bool>,
|
||||
|
@ -22,31 +27,12 @@ pub struct CreateNewStickerSet {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for CreateNewStickerSet {
|
||||
impl Request for CreateNewStickerSet {
|
||||
type Output = True;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<True>> {
|
||||
let builder = FormBuilder::new()
|
||||
.add_text("user_id", &self.user_id)
|
||||
.add_text("name", &self.name)
|
||||
.add_text("title", &self.title);
|
||||
|
||||
let builder = match &self.sticker_type {
|
||||
StickerType::Png(file) => builder.add_input_file("png_sticker", &file),
|
||||
StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file),
|
||||
}
|
||||
.await?
|
||||
.add_text("emojis", &self.emojis)
|
||||
.add_text("contains_masks", &self.contains_masks)
|
||||
.add_text("mask_position", &self.mask_position);
|
||||
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"createNewStickerSet",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<True> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "createNewStickerSet", self)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{InlineKeyboardMarkup, InputMedia, True},
|
||||
Bot,
|
||||
};
|
||||
|
@ -17,8 +19,10 @@ use crate::{
|
|||
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
|
||||
///
|
||||
/// [`True`]: crate::types::True
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct EditInlineMessageMedia {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
inline_message_id: String,
|
||||
media: InputMedia,
|
||||
|
@ -30,17 +34,7 @@ impl Request for EditInlineMessageMedia {
|
|||
type Output = True;
|
||||
|
||||
async fn send(&self) -> ResponseResult<True> {
|
||||
net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"editMessageMedia",
|
||||
FormBuilder::new()
|
||||
.add_text("media", &self.media)
|
||||
.add_text("reply_markup", &self.reply_markup)
|
||||
.add_text("inline_message_id", &self.inline_message_id)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InlineKeyboardMarkup, InputMedia, Message},
|
||||
Bot,
|
||||
};
|
||||
|
@ -15,8 +17,10 @@ use crate::{
|
|||
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
|
||||
///
|
||||
/// [`Message`]: crate::types::Message
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct EditMessageMedia {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
message_id: i32,
|
||||
|
@ -29,18 +33,7 @@ impl Request for EditMessageMedia {
|
|||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"editMessageMedia",
|
||||
FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_text("message_id", &self.message_id)
|
||||
.add_text("media", &self.media)
|
||||
.add_text("reply_markup", &self.reply_markup)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -12,8 +14,10 @@ use crate::{
|
|||
/// may be changed in the future.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#sendanimation).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendAnimation {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
pub chat_id: ChatId,
|
||||
pub animation: InputFile,
|
||||
|
@ -29,32 +33,11 @@ pub struct SendAnimation {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendAnimation {
|
||||
impl Request for SendAnimation {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
let mut builder = FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("animation", &self.animation)
|
||||
.await?
|
||||
.add_text("duration", &self.duration)
|
||||
.add_text("width", &self.width)
|
||||
.add_text("height", &self.height)
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup);
|
||||
if let Some(thumb) = self.thumb.as_ref() {
|
||||
builder = builder.add_input_file("thumb", thumb).await?;
|
||||
}
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendAnimation",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendAnimation", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -16,8 +18,10 @@ use crate::{
|
|||
/// [The official docs](https://core.telegram.org/bots/api#sendaudio).
|
||||
///
|
||||
/// [`Bot::send_voice`]: crate::Bot::send_voice
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendAudio {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
audio: InputFile,
|
||||
|
@ -33,32 +37,11 @@ pub struct SendAudio {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendAudio {
|
||||
impl Request for SendAudio {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
let mut builder = FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("audio", &self.audio)
|
||||
.await?
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("duration", &self.duration)
|
||||
.add_text("performer", &self.performer)
|
||||
.add_text("title", &self.title)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup);
|
||||
if let Some(thumb) = self.thumb.as_ref() {
|
||||
builder = builder.add_input_file("thumb", thumb).await?;
|
||||
}
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendAudio",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendAudio", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -11,8 +13,10 @@ use crate::{
|
|||
/// may be changed in the future.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#senddocument).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendDocument {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
document: InputFile,
|
||||
|
@ -25,29 +29,11 @@ pub struct SendDocument {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendDocument {
|
||||
impl Request for SendDocument {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
let mut builder = FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("document", &self.document)
|
||||
.await?
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup);
|
||||
if let Some(thumb) = self.thumb.as_ref() {
|
||||
builder = builder.add_input_file("thumb", thumb).await?;
|
||||
}
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendDocument",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendDocument", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputMedia, Message},
|
||||
Bot,
|
||||
};
|
||||
|
@ -8,8 +10,10 @@ use crate::{
|
|||
/// Use this method to send a group of photos or videos as an album.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#sendmediagroup).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendMediaGroup {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
media: Vec<InputMedia>, // TODO: InputMediaPhoto and InputMediaVideo
|
||||
|
@ -22,18 +26,7 @@ impl Request for SendMediaGroup {
|
|||
type Output = Vec<Message>;
|
||||
|
||||
async fn send(&self) -> ResponseResult<Vec<Message>> {
|
||||
net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendMediaGroup",
|
||||
FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_text("media", &self.media)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendMediaGroup", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -8,8 +10,10 @@ use crate::{
|
|||
/// Use this method to send photos.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#sendphoto).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendPhoto {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
photo: InputFile,
|
||||
|
@ -21,26 +25,11 @@ pub struct SendPhoto {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendPhoto {
|
||||
impl Request for SendPhoto {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendPhoto",
|
||||
FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("photo", &self.photo)
|
||||
.await?
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup)
|
||||
.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendPhoto", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -10,8 +12,10 @@ use crate::{
|
|||
/// [The official docs](https://core.telegram.org/bots/api#sendsticker).
|
||||
///
|
||||
/// [animated]: https://telegram.org/blog/animated-stickers
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendSticker {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
sticker: InputFile,
|
||||
|
@ -21,24 +25,11 @@ pub struct SendSticker {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendSticker {
|
||||
impl Request for SendSticker {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendSticker",
|
||||
FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("sticker", &self.sticker)
|
||||
.await?
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup)
|
||||
.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendSticker", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -12,8 +14,10 @@ use crate::{
|
|||
/// limit may be changed in the future.
|
||||
///
|
||||
/// [The official docs](https://core.telegram.org/bots/api#sendvideo).
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendVideo {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
video: InputFile,
|
||||
|
@ -30,33 +34,11 @@ pub struct SendVideo {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendVideo {
|
||||
impl Request for SendVideo {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
let mut builder = FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("video", &self.video)
|
||||
.await?
|
||||
.add_text("duration", &self.duration)
|
||||
.add_text("width", &self.width)
|
||||
.add_text("height", &self.height)
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("supports_streaming", &self.supports_streaming)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup);
|
||||
if let Some(thumb) = self.thumb.as_ref() {
|
||||
builder = builder.add_input_file("thumb", thumb).await?;
|
||||
}
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendVideo",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendVideo", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -11,8 +13,10 @@ use crate::{
|
|||
/// [The official docs](https://core.telegram.org/bots/api#sendvideonote).
|
||||
///
|
||||
/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendVideoNote {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
video_note: InputFile,
|
||||
|
@ -25,29 +29,11 @@ pub struct SendVideoNote {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendVideoNote {
|
||||
impl Request for SendVideoNote {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
let mut builder = FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("video_note", &self.video_note)
|
||||
.await?
|
||||
.add_text("duration", &self.duration)
|
||||
.add_text("length", &self.length)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup);
|
||||
if let Some(thumb) = self.thumb.as_ref() {
|
||||
builder = builder.add_input_file("thumb", thumb).await?;
|
||||
}
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendVideoNote",
|
||||
builder.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendVideoNote", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
net,
|
||||
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||
Bot,
|
||||
};
|
||||
|
@ -17,8 +19,10 @@ use crate::{
|
|||
///
|
||||
/// [`Audio`]: crate::types::Audio
|
||||
/// [`Document`]: crate::types::Document
|
||||
#[derive(Debug, Clone)]
|
||||
#[serde_with_macros::skip_serializing_none]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SendVoice {
|
||||
#[serde(skip_serializing)]
|
||||
bot: Bot,
|
||||
chat_id: ChatId,
|
||||
voice: InputFile,
|
||||
|
@ -31,27 +35,11 @@ pub struct SendVoice {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestWithFile for SendVoice {
|
||||
impl Request for SendVoice {
|
||||
type Output = Message;
|
||||
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> {
|
||||
Ok(net::request_multipart(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
"sendVoice",
|
||||
FormBuilder::new()
|
||||
.add_text("chat_id", &self.chat_id)
|
||||
.add_input_file("voice", &self.voice)
|
||||
.await?
|
||||
.add_text("caption", &self.caption)
|
||||
.add_text("parse_mode", &self.parse_mode)
|
||||
.add_text("duration", &self.duration)
|
||||
.add_text("disable_notification", &self.disable_notification)
|
||||
.add_text("reply_to_message_id", &self.reply_to_message_id)
|
||||
.add_text("reply_markup", &self.reply_markup)
|
||||
.build(),
|
||||
)
|
||||
.await)
|
||||
async fn send(&self) -> ResponseResult<Message> {
|
||||
net::request_multipart(self.bot.client(), self.bot.token(), "sendVoice", self).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use reqwest::multipart::Form;
|
||||
|
||||
use crate::{
|
||||
requests::utils::{file_from_memory_to_part, file_to_part},
|
||||
types::{
|
||||
ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition, ParseMode, ReplyMarkup,
|
||||
},
|
||||
};
|
||||
|
||||
/// This is a convenient struct that builds `reqwest::multipart::Form`
|
||||
/// from scratch.
|
||||
pub(crate) struct FormBuilder {
|
||||
form: Form,
|
||||
}
|
||||
|
||||
impl FormBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { form: Form::new() }
|
||||
}
|
||||
|
||||
pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
T: IntoFormText,
|
||||
{
|
||||
match value.into_form_text() {
|
||||
Some(val) => Self { form: self.form.text(name.into().into_owned(), val) },
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result<Self>
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
{
|
||||
Ok(match value {
|
||||
InputFile::File(path) => self.add_file(name, path.clone()).await?,
|
||||
InputFile::Memory { file_name, data } => {
|
||||
self.add_file_from_memory(name, file_name.clone(), data.clone())
|
||||
}
|
||||
InputFile::Url(url) => self.add_text(name, url),
|
||||
InputFile::FileId(file_id) => self.add_text(name, file_id),
|
||||
})
|
||||
}
|
||||
|
||||
// used in SendMediaGroup
|
||||
pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result<Self>
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
{
|
||||
Ok(Self {
|
||||
form: self.form.part(name.into().into_owned(), file_to_part(path_to_file).await?),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_file_from_memory<'a, N>(
|
||||
self,
|
||||
name: N,
|
||||
file_name: String,
|
||||
data: Cow<'static, [u8]>,
|
||||
) -> Self
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
{
|
||||
Self {
|
||||
form: self
|
||||
.form
|
||||
.part(name.into().into_owned(), file_from_memory_to_part(data, file_name)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> Form {
|
||||
self.form
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait IntoFormText {
|
||||
fn into_form_text(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
macro_rules! impl_for_struct {
|
||||
($($name:ty),*) => {
|
||||
$(
|
||||
impl IntoFormText for $name {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
let json = serde_json::to_string(self)
|
||||
.expect("serde_json::to_string failed");
|
||||
Some(json)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_for_struct!(bool, i32, i64, u32, ReplyMarkup, InlineKeyboardMarkup, MaskPosition);
|
||||
|
||||
impl<T> IntoFormText for Option<T>
|
||||
where
|
||||
T: IntoFormText,
|
||||
{
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
self.as_ref().and_then(IntoFormText::into_form_text)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't
|
||||
// encode files :|)
|
||||
impl IntoFormText for Vec<InputMedia> {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
let json = serde_json::to_string(self).expect("serde_json::to_string failed");
|
||||
Some(json)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFormText for InputMedia {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
let json = serde_json::to_string(self).expect("serde_json::to_string failed");
|
||||
Some(json)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFormText for str {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
Some(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFormText for ParseMode {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
let string = match self {
|
||||
ParseMode::MarkdownV2 => String::from("MarkdownV2"),
|
||||
ParseMode::HTML => String::from("HTML"),
|
||||
#[allow(deprecated)]
|
||||
ParseMode::Markdown => String::from("Markdown"),
|
||||
};
|
||||
Some(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFormText for ChatId {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
let string = match self {
|
||||
ChatId::Id(id) => id.to_string(),
|
||||
ChatId::ChannelUsername(username) => username.clone(),
|
||||
};
|
||||
Some(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFormText for String {
|
||||
fn into_form_text(&self) -> Option<String> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
//! API requests.
|
||||
|
||||
mod all;
|
||||
mod form_builder;
|
||||
mod utils;
|
||||
|
||||
pub use all::*;
|
||||
|
@ -18,15 +17,3 @@ pub trait Request {
|
|||
/// Asynchronously sends this request to Telegram and returns the result.
|
||||
async fn send(&self) -> ResponseResult<Self::Output>;
|
||||
}
|
||||
|
||||
/// Designates an API request with possibly sending file.
|
||||
#[async_trait::async_trait]
|
||||
pub trait RequestWithFile {
|
||||
/// A data structure returned if success.
|
||||
type Output;
|
||||
|
||||
/// Asynchronously sends this request to Telegram and returns the result.
|
||||
/// Returns `tokio::io::Result::Err` when trying to send file which does not
|
||||
/// exists.
|
||||
async fn send(&self) -> tokio::io::Result<ResponseResult<Self::Output>>;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use reqwest::{multipart::Part, Body};
|
||||
use tokio_util::codec::{Decoder, FramedRead};
|
||||
use tokio_util::codec::Decoder;
|
||||
|
||||
struct FileDecoder;
|
||||
|
||||
|
@ -17,15 +14,3 @@ impl Decoder for FileDecoder {
|
|||
Ok(Some(src.split().freeze()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result<Part> {
|
||||
let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned();
|
||||
|
||||
let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder);
|
||||
|
||||
Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name))
|
||||
}
|
||||
|
||||
pub fn file_from_memory_to_part(data: Cow<'static, [u8]>, name: String) -> Part {
|
||||
Part::bytes(data).file_name(name)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::types::InputFile;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
|
||||
#[non_exhaustive]
|
||||
#[serde(untagged)]
|
||||
pub enum StickerType {
|
||||
/// PNG image with the sticker, must be up to 512 kilobytes in size,
|
||||
/// dimensions must not exceed 512px, and either width or height must be
|
||||
|
@ -17,10 +20,10 @@ pub enum StickerType {
|
|||
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||
///
|
||||
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||
Png(InputFile),
|
||||
Png { png_sticker: InputFile },
|
||||
|
||||
/// TGS animation with the sticker, uploaded using multipart/form-data.
|
||||
///
|
||||
/// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements
|
||||
Tgs(InputFile),
|
||||
Tgs { tgs_sticker: InputFile },
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue