From 0a6dc12f92351d63ef292da596e2f2943b4f27f3 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 3 Sep 2019 20:17:19 +0600 Subject: [PATCH 1/7] Make the 'core' module public --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4f1e44c6..c9bdacec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,4 @@ extern crate serde; #[macro_use] extern crate typed_builder; -mod core; +pub mod core; From e9ebd62993e26c62c4df9564b72e6cbbd0f0009e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 3 Sep 2019 20:24:52 +0600 Subject: [PATCH 2/7] Make the 'core.network' module private --- src/core/mod.rs | 2 +- src/core/network/mod.rs | 28 +--------------------------- src/core/requests/get_me.rs | 3 ++- src/core/requests/mod.rs | 29 +++++++++++++++++++++++++++-- src/core/requests/send_message.rs | 3 ++- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/core/mod.rs b/src/core/mod.rs index cd8bb2d5..8fd20ac7 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,3 @@ -pub mod network; +mod network; pub mod requests; pub mod types; diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index c1d5eba0..543be058 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -3,9 +3,9 @@ use serde::de::DeserializeOwned; use serde_json::Value; use reqwest::{ r#async::{Client, multipart::Form}, - StatusCode, }; use apply::Apply; +use crate::core::requests::{RequestError, ResponseResult}; const TELEGRAM_API_URL: &str = "https://api.telegram.org"; @@ -31,32 +31,6 @@ fn file_url(base: &str, token: &str, file_path: &str) -> String { ) } -#[derive(Debug, Display)] -pub enum RequestError { - #[display(fmt = "Telegram error #{}: {}", status_code, description)] - ApiError { - status_code: StatusCode, - description: String, - }, - - #[display(fmt = "Network error: {err}", err = _0)] - NetworkError(reqwest::Error), - - #[display(fmt = "InvalidJson error caused by: {err}", err = _0)] - InvalidJson(serde_json::Error), -} - -impl std::error::Error for RequestError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RequestError::ApiError { .. } => None, - RequestError::NetworkError(err) => Some(err), - RequestError::InvalidJson(err) => Some(err), - } - } -} - -pub type ResponseResult = Result; pub async fn request( client: &Client, diff --git a/src/core/requests/get_me.rs b/src/core/requests/get_me.rs index 464c85bf..a6f6ae5f 100644 --- a/src/core/requests/get_me.rs +++ b/src/core/requests/get_me.rs @@ -1,12 +1,13 @@ use crate::core::{ types::User, network::{ - request, ResponseResult, + request, }, requests::{ Request, RequestInfo, RequestFuture, } }; +use crate::core::requests::ResponseResult; #[derive(Debug, Constructor)] diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index 1c88f1c6..e16ffe67 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -1,13 +1,38 @@ use std::future::Future; -use crate::core::network::ResponseResult; - use serde::de::DeserializeOwned; use reqwest::r#async::Client; +use reqwest::StatusCode; mod form_builder; +#[derive(Debug, Display)] +pub enum RequestError { + #[display(fmt = "Telegram error #{}: {}", status_code, description)] + ApiError { + status_code: StatusCode, + description: String, + }, + + #[display(fmt = "Network error: {err}", err = _0)] + NetworkError(reqwest::Error), + + #[display(fmt = "InvalidJson error caused by: {err}", err = _0)] + InvalidJson(serde_json::Error), +} + +impl std::error::Error for RequestError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + RequestError::ApiError { .. } => None, + RequestError::NetworkError(err) => Some(err), + RequestError::InvalidJson(err) => Some(err), + } + } +} + +pub type ResponseResult = Result; /// Request that can be sent to telegram. /// `ReturnValue` - a type that will be returned from Telegram. diff --git a/src/core/requests/send_message.rs b/src/core/requests/send_message.rs index 95cdb319..5c725fb6 100644 --- a/src/core/requests/send_message.rs +++ b/src/core/requests/send_message.rs @@ -1,7 +1,7 @@ use crate::core::{ types::Message, network::{ - request, ResponseResult, + request, }, requests::{ form_builder::FormBuilder, @@ -11,6 +11,7 @@ use crate::core::{ RequestFuture, } }; +use crate::core::requests::ResponseResult; #[derive(Debug, TypedBuilder)] From 3d6479be6278d4404235ac3e24df20b6232acacf Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 3 Sep 2019 20:25:38 +0600 Subject: [PATCH 3/7] Format the sources --- src/core/network/mod.rs | 10 +++------ src/core/requests/form_builder.rs | 4 ++-- src/core/requests/get_me.rs | 17 +++------------ src/core/requests/mod.rs | 3 +-- src/core/requests/send_message.rs | 24 +++++++-------------- src/core/types/chosen_inline_result.rs | 1 + src/core/types/input_media.rs | 1 + src/core/types/input_message_content.rs | 1 + src/core/types/message.rs | 8 +++---- src/core/types/message_entity.rs | 25 +++++++++++++++------- src/core/types/mod.rs | 28 +++++++------------------ 11 files changed, 47 insertions(+), 75 deletions(-) diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index 543be058..d9f9727b 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -1,16 +1,13 @@ +use apply::Apply; use futures::compat::Future01CompatExt; +use reqwest::r#async::{multipart::Form, Client}; use serde::de::DeserializeOwned; use serde_json::Value; -use reqwest::{ - r#async::{Client, multipart::Form}, -}; -use apply::Apply; + use crate::core::requests::{RequestError, ResponseResult}; - const TELEGRAM_API_URL: &str = "https://api.telegram.org"; - /// Create url for macking requests, see [telegram docs](https://core.telegram.org/bots/api#making-requests) fn method_url(base: &str, token: &str, method_name: &str) -> String { format!( @@ -31,7 +28,6 @@ fn file_url(base: &str, token: &str, file_path: &str) -> String { ) } - pub async fn request( client: &Client, token: &str, diff --git a/src/core/requests/form_builder.rs b/src/core/requests/form_builder.rs index ce4a3d39..ba38bc19 100644 --- a/src/core/requests/form_builder.rs +++ b/src/core/requests/form_builder.rs @@ -38,8 +38,8 @@ impl FormBuilder { form: self.form.text( name.to_owned(), serde_json::to_string(value).expect("serde_json::to_string failed"), - ) - } + ), + }, } } diff --git a/src/core/requests/get_me.rs b/src/core/requests/get_me.rs index a6f6ae5f..87a20e85 100644 --- a/src/core/requests/get_me.rs +++ b/src/core/requests/get_me.rs @@ -1,26 +1,15 @@ -use crate::core::{ - types::User, - network::{ - request, - }, - requests::{ - Request, RequestInfo, RequestFuture, - } -}; use crate::core::requests::ResponseResult; - +use crate::core::{network::request, network::request, types::User}; #[derive(Debug, Constructor)] pub struct GetMe { - info: RequestInfo + info: RequestInfo, } impl Request for GetMe { type ReturnValue = User; fn send(self) -> RequestFuture> { - Box::new(async move { - request(&self.info.client, &self.info.token, "getMe", None).await - }) + Box::new(async move { request(&self.info.client, &self.info.token, "getMe", None).await }) } } diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index e16ffe67..797ea3b8 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -1,9 +1,8 @@ use std::future::Future; -use serde::de::DeserializeOwned; use reqwest::r#async::Client; use reqwest::StatusCode; - +use serde::de::DeserializeOwned; mod form_builder; diff --git a/src/core/requests/send_message.rs b/src/core/requests/send_message.rs index 5c725fb6..5d4b4414 100644 --- a/src/core/requests/send_message.rs +++ b/src/core/requests/send_message.rs @@ -1,18 +1,5 @@ -use crate::core::{ - types::Message, - network::{ - request, - }, - requests::{ - form_builder::FormBuilder, - ChatId, - Request, - RequestInfo, - RequestFuture, - } -}; use crate::core::requests::ResponseResult; - +use crate::core::{network::request, network::request, types::Message}; #[derive(Debug, TypedBuilder)] pub struct SendMessage { @@ -36,7 +23,6 @@ pub struct SendMessage { impl Request for SendMessage { type ReturnValue = Message; - fn send(self) -> RequestFuture> { Box::new(async move { let params = FormBuilder::new() @@ -51,7 +37,13 @@ impl Request for SendMessage { .add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) .build(); - request(&self.info.client, &self.info.token, "sendMessage", Some(params)).await + request( + &self.info.client, + &self.info.token, + "sendMessage", + Some(params), + ) + .await }) } } diff --git a/src/core/types/chosen_inline_result.rs b/src/core/types/chosen_inline_result.rs index bd479fa5..489acfaa 100644 --- a/src/core/types/chosen_inline_result.rs +++ b/src/core/types/chosen_inline_result.rs @@ -1,4 +1,5 @@ use serde::Deserialize; + use crate::core::types::user::User; #[derive(Debug, Deserealize)] diff --git a/src/core/types/input_media.rs b/src/core/types/input_media.rs index 435fb48f..367267ac 100644 --- a/src/core/types/input_media.rs +++ b/src/core/types/input_media.rs @@ -1,4 +1,5 @@ use serde::Deserialize; + use crate::core::types::InputFile; pub enum InputMedia { diff --git a/src/core/types/input_message_content.rs b/src/core/types/input_message_content.rs index 14e4b142..cf4c6873 100644 --- a/src/core/types/input_message_content.rs +++ b/src/core/types/input_message_content.rs @@ -1,4 +1,5 @@ use serde::Serialize; + use crate::core::parse_mode::ParseMode; pub enum InputMessageContent { diff --git a/src/core/types/message.rs b/src/core/types/message.rs index 2a3e3dc3..f53bb5a6 100644 --- a/src/core/types/message.rs +++ b/src/core/types/message.rs @@ -1,11 +1,9 @@ // use serde::Deserialize; use crate::core::types::{ - Animation, Audio, Chat, Contact, - Document, Game, Invoice, InlineKeyboardMarkup, - PhotoSize, MessageEntity, Location, PassportData, Poll, - Sticker, SuccessfulPayment, - User, Video, VideoNote, Venue, Voice, + Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup, Invoice, Location, + MessageEntity, PassportData, PhotoSize, Poll, Sticker, SuccessfulPayment, User, Venue, Video, + VideoNote, Voice, }; #[derive(Debug, Deserialize, Hash, PartialEq, Eq)] diff --git a/src/core/types/message_entity.rs b/src/core/types/message_entity.rs index 0763727f..9235bd2f 100644 --- a/src/core/types/message_entity.rs +++ b/src/core/types/message_entity.rs @@ -1,6 +1,5 @@ use crate::core::types::User; - #[derive(Deserialize, Debug, PartialEq, Hash, Eq)] pub struct MessageEntity { #[serde(flatten)] @@ -13,10 +12,19 @@ pub struct MessageEntity { #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub enum MessageEntityKind { - Mention, Hashtag, Cashtag, BotCommand, Url, Email, PhoneNumber, - Bold, Italic, Code, Pre, + Mention, + Hashtag, + Cashtag, + BotCommand, + Url, + Email, + PhoneNumber, + Bold, + Italic, + Code, + Pre, TextLink { url: String }, - TextMention { user: User } + TextMention { user: User }, } #[test] @@ -25,12 +33,13 @@ fn recursive_kind() { assert_eq!( MessageEntity { - kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, + kind: MessageEntityKind::TextLink { + url: "ya.ru".into() + }, offset: 1, length: 2 }, - from_str::( - r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# - ).unwrap() + from_str::(r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"#) + .unwrap() ); } diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index 3d815491..ffd1a60c 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -1,26 +1,11 @@ -mod not_implemented_types; use self::not_implemented_types::*; - - pub use self::{ - answer_pre_checkout_query::AnswerPreCheckoutQuery, - answer_shipping_query::AnswerShippingQuery, - chat::Chat, - chat_permissions::ChatPermissions, - chat_photo::ChatPhoto, - document::Document, - invoice::Invoice, - label_price::LabeledPrice, - message::Message, - message_entity::MessageEntity, - order_info::OrderInfo, - pre_checkout_query::PreCheckoutQuery, - send_invoice::SendInvoice, - shipping_address::ShippingAddress, - shipping_option::ShippingOption, - shipping_query::ShippingQuery, - sticker::Sticker, - successful_payment::SuccessfulPayment, + answer_pre_checkout_query::AnswerPreCheckoutQuery, answer_shipping_query::AnswerShippingQuery, + chat::Chat, chat_permissions::ChatPermissions, chat_photo::ChatPhoto, document::Document, + invoice::Invoice, label_price::LabeledPrice, message::Message, message_entity::MessageEntity, + order_info::OrderInfo, pre_checkout_query::PreCheckoutQuery, send_invoice::SendInvoice, + shipping_address::ShippingAddress, shipping_option::ShippingOption, + shipping_query::ShippingQuery, sticker::Sticker, successful_payment::SuccessfulPayment, user::User, }; @@ -34,6 +19,7 @@ mod invoice; mod label_price; mod message; mod message_entity; +mod not_implemented_types; mod order_info; mod pre_checkout_query; mod send_invoice; From 383ea99e81c1a47d9d6d128076b8a8a09fe34459 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 3 Sep 2019 20:34:29 +0600 Subject: [PATCH 4/7] Refine core/network/mod.rs --- src/core/network/mod.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index d9f9727b..35561c40 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -8,7 +8,9 @@ use crate::core::requests::{RequestError, ResponseResult}; const TELEGRAM_API_URL: &str = "https://api.telegram.org"; -/// Create url for macking requests, see [telegram docs](https://core.telegram.org/bots/api#making-requests) +/// Creates URL for making HTTPS requests. See the [Telegram documentation]. +/// +/// [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}", @@ -18,7 +20,9 @@ fn method_url(base: &str, token: &str, method_name: &str) -> String { ) } -/// Create url for downloading file, see [telegram docs](https://core.telegram.org/bots/api#file) +/// Creates URL for downloading a file. See the [Telegram documentation]. +/// +/// [Telegram documentation] (https://core.telegram.org/bots/api#file) fn file_url(base: &str, token: &str, file_path: &str) -> String { format!( "{url}/file/bot{token}/{file}", @@ -37,11 +41,10 @@ pub async fn request( let mut response = client .post(&method_url(TELEGRAM_API_URL, token, method_name)) .apply(|request_builder| { - if let Some(params) = params { - request_builder.multipart(params) - } else { - request_builder - } + params.map_or_else( + || request_builder, + |params| request_builder.multipart(params), + ) }) .send() .compat() From d71c4d39a00c3645cc6203b0258098a71e1b1949 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 3 Sep 2019 20:39:49 +0600 Subject: [PATCH 5/7] Fix the compilation errors --- src/core/network/mod.rs | 8 +++----- src/core/requests/get_me.rs | 9 ++++++--- src/core/requests/send_message.rs | 7 ++++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index 35561c40..da42bd36 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -40,11 +40,9 @@ pub async fn request( ) -> ResponseResult { let mut response = client .post(&method_url(TELEGRAM_API_URL, token, method_name)) - .apply(|request_builder| { - params.map_or_else( - || request_builder, - |params| request_builder.multipart(params), - ) + .apply(|request_builder| match params { + Some(params) => request_builder.multipart(params), + None => request_builder, }) .send() .compat() diff --git a/src/core/requests/get_me.rs b/src/core/requests/get_me.rs index 87a20e85..5f5a14fe 100644 --- a/src/core/requests/get_me.rs +++ b/src/core/requests/get_me.rs @@ -1,5 +1,6 @@ -use crate::core::requests::ResponseResult; -use crate::core::{network::request, network::request, types::User}; +use crate::core::network; +use crate::core::requests::{Request, RequestFuture, RequestInfo, ResponseResult}; +use crate::core::types::User; #[derive(Debug, Constructor)] pub struct GetMe { @@ -10,6 +11,8 @@ impl Request for GetMe { type ReturnValue = User; fn send(self) -> RequestFuture> { - Box::new(async move { request(&self.info.client, &self.info.token, "getMe", None).await }) + Box::new(async move { + network::request(&self.info.client, &self.info.token, "getMe", None).await + }) } } diff --git a/src/core/requests/send_message.rs b/src/core/requests/send_message.rs index 5d4b4414..e767d32a 100644 --- a/src/core/requests/send_message.rs +++ b/src/core/requests/send_message.rs @@ -1,5 +1,6 @@ -use crate::core::requests::ResponseResult; -use crate::core::{network::request, network::request, types::Message}; +use crate::core::requests::form_builder::FormBuilder; +use crate::core::requests::{ChatId, Request, RequestFuture, RequestInfo, ResponseResult}; +use crate::core::{network, types::Message}; #[derive(Debug, TypedBuilder)] pub struct SendMessage { @@ -37,7 +38,7 @@ impl Request for SendMessage { .add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) .build(); - request( + network::request( &self.info.client, &self.info.token, "sendMessage", From 38b5945ec8b26001824085e352828a9ad9c3c6ac Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 3 Sep 2019 19:25:13 +0300 Subject: [PATCH 6/7] Fix `ParseMode`, `InputFile` and `InputMedia` (add to module tree, add docs, etc) --- src/core/parse_mode.rs | 4 - src/core/types/input_file.rs | 21 ++- src/core/types/input_media.rs | 294 +++++++++++++++++++++++++++------- src/core/types/mod.rs | 6 + src/core/types/parse_mode.rs | 63 ++++++++ 5 files changed, 323 insertions(+), 65 deletions(-) delete mode 100644 src/core/parse_mode.rs create mode 100644 src/core/types/parse_mode.rs diff --git a/src/core/parse_mode.rs b/src/core/parse_mode.rs deleted file mode 100644 index 3a4f7054..00000000 --- a/src/core/parse_mode.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub enum ParseMode { - HTML, - Markdown, -} \ No newline at end of file diff --git a/src/core/types/input_file.rs b/src/core/types/input_file.rs index 5475c572..0c3110f1 100644 --- a/src/core/types/input_file.rs +++ b/src/core/types/input_file.rs @@ -1,8 +1,25 @@ use serde::Deserialize; -#[derive(Debug, Deserialize, Hash, PartialEq, Eq)] +#[derive(Debug, Hash, PartialEq, Eq)] pub enum InputFile { - File(std::fs::File), + File(std::path::PathBuf), Url(String), FileId(String), } + +impl serde::Serialize for InputFile { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + match self { + InputFile::File(path) => { + // NOTE: file should be actually attached with multipart/form-data + serializer.serialize_str( + // TODO: remove unwrap (?) + &format!("attach://{}", path.file_name().unwrap().to_string_lossy()) + ) + }, + InputFile::Url(url) => serializer.serialize_str(url), + InputFile::FileId(id) => serializer.serialize_str(id), + } + } + +} diff --git a/src/core/types/input_media.rs b/src/core/types/input_media.rs index 435fb48f..e9cdc5fd 100644 --- a/src/core/types/input_media.rs +++ b/src/core/types/input_media.rs @@ -1,69 +1,245 @@ use serde::Deserialize; -use crate::core::types::InputFile; +use crate::core::types::{InputFile, ParseMode}; +// TODO: should variants use new-type? +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +/// This object represents the content of a media message to be sent. +/// [More](https://core.telegram.org/bots/api#inputmedia) pub enum InputMedia { - InputMediaPhoto(InputMediaPhoto), - InputMediaVideo(InputMediaVideo), - InputMediaAnimation(InputMediaAnimation), - InputMediaAudio(InputMediaAudiotype), - InputMediaDocument(InputMediaDocument), + /// Represents a photo to be sent. + Photo { + /// File to send. + media: InputFile, + /// Caption of the photo to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + }, + Video { + /// File to send.File to send. + media: InputFile, + #[serde(skip_serializing_if = "Option::is_none")] + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + thumb: Option, + /// Caption of the video to be sent, 0-1024 characters. + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Video width + #[serde(skip_serializing_if = "Option::is_none")] + width: Option, + /// Video height + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + /// Video duration + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + /// Pass `true`, if the uploaded video is suitable for streaming + #[serde(skip_serializing_if = "Option::is_none")] + supports_streaming: Option, + }, + /// Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. + Animation { + /// File to send. + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the animation to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Animation width + #[serde(skip_serializing_if = "Option::is_none")] + width: Option, + /// Animation height + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + /// Animation duration + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + }, + /// Represents an audio file to be treated as music to be sent. + Audio { + /// File to send, + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the audio to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Duration of the audio in seconds + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + /// Performer of the audio + #[serde(skip_serializing_if = "Option::is_none")] + performer: Option, + /// Title of the audio + #[serde(skip_serializing_if = "Option::is_none")] + title: Option + }, + /// Represents a general file to be sent. + Document { + /// File to send. + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the document to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + }, } -pub enum ThumbKind { - InputFile, - String, -} +#[cfg(test)] +mod tests { + use super::*; -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaPhoto { - type_: String, - media: String, - caption: Option, - parse_mode: Option, -} + #[test] + fn photo_serialize() { + let expected_json = r#"{"type":"photo","media":"123456"}"#; + let photo = InputMedia::Photo { + media: InputFile::FileId(String::from("123456")), + caption: None, + parse_mode: None, + }; -#[derive(Debug, Serialize), Deserialize] -pub struct InputMediaVideo { - type_: String, - media: String, - thumb: ThumbKind, - caption: Option, - parse_mode: Option, - width: Option, - height: Option, - duration: Option, - supports_streaming: Option, -} + let actual_json = serde_json::to_string(&photo).unwrap(); + assert_eq!(expected_json, actual_json); + } -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaAnimation { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: Option, - width: Option, - height: Option, - duration: Option, -} + #[test] + fn video_serialize() { + let expected_json = r#"{"type":"video","media":"123456"}"#; + let video = InputMedia::Video { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + supports_streaming: None, + }; -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaAudio { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: Option, - duration: Option, - performer: Option, - title: Option -} + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaDocument { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: parse_mode, -} \ No newline at end of file + #[test] + fn animation_serialize() { + let expected_json = r#"{"type":"animation","media":"123456"}"#; + let video = InputMedia::Animation { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn audio_serialize() { + let expected_json = r#"{"type":"audio","media":"123456"}"#; + let video = InputMedia::Audio { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + duration: None, + performer: None, + title: None + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn document_serialize() { + let expected_json = r#"{"type":"document","media":"123456"}"#; + let video = InputMedia::Document { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } +} diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index 3d815491..568022a3 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -22,6 +22,9 @@ pub use self::{ sticker::Sticker, successful_payment::SuccessfulPayment, user::User, + input_file::InputFile, + input_media::InputMedia, + parse_mode::ParseMode, }; mod answer_pre_checkout_query; @@ -43,3 +46,6 @@ mod shipping_query; mod sticker; mod successful_payment; mod user; +mod input_file; +mod input_media; +mod parse_mode; diff --git a/src/core/types/parse_mode.rs b/src/core/types/parse_mode.rs new file mode 100644 index 00000000..5c885cfe --- /dev/null +++ b/src/core/types/parse_mode.rs @@ -0,0 +1,63 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +/// ## Formatting options +/// The Bot API supports basic formatting for messages. +/// You can use **bold** and *italic* text, as well as [inline links](https://example.com) and `pre-formatted code` in +/// your bots' messages. Telegram clients will render them accordingly. You can use either +/// markdown-style or HTML-style formatting. +/// +/// Note that Telegram clients will display an alert to the user before opening an inline link +/// (‘Open this link?’ together with the full URL). +/// +/// Links `tg://user?id=` can be used to mention a user by their id without using a username. +/// Please note: +/// +/// - These links will work only if they are used inside an inline link. +/// For example, they will not work, when used in an inline keyboard button or in a message text. +/// - The mentions are only guaranteed to work if: **A**. the user is a member in the group where he +/// was mentioned or **B**. the user has contacted the bot in the past or has sent a callback +/// query to the bot via inline button and has not restricted linking to their account in +/// `Settings > Privacy & Security > Forwarded Messages`. +/// +/// ## Markdown style +/// To use this mode, pass [Markdown] in the `parse_mode` field when using [SendMessage] (or other methods). +/// +/// Use the following syntax in your message: +/// +/// ```ignore +/// *bold text* +/// _italic text_ +/// [inline URL](http://www.example.com/) +/// [inline mention of a user](tg://user?id=123456789) +/// `inline fixed-width code` +/// ```block_language +/// pre-formatted fixed-width code block +/// ``` +/// ``` +/// +/// ## HTML style +/// To use this mode, pass [HTML] in the `parse_mode` field when using [SendMessage] (or other methods). +/// +/// The following tags are currently supported: +/// +/// ```ignore +/// bold, bold +/// italic, italic +/// inline URL +/// inline mention of a user +/// inline fixed-width code +///
pre-formatted fixed-width code block
+/// ``` +/// +/// Please note: +/// +/// - Only the tags mentioned above are currently supported. +/// - Tags must not be nested. +/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML entity must be replaced with the corresponding HTML entities (`<` with `<`, `>` with `>` and `&` with `&`). +/// - All numerical HTML entities are supported. +/// - The API currently supports only the following named HTML entities: `<`, `>`, `&` and `"`. +pub enum ParseMode { + HTML, + Markdown, +} \ No newline at end of file From f100bdc7edfa20210f645cc83cc08704e4f13ddf Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 3 Sep 2019 20:23:52 +0300 Subject: [PATCH 7/7] Pin `RequestFuture` --- src/core/requests/get_me.rs | 2 +- src/core/requests/mod.rs | 9 ++++++++- src/core/requests/send_message.rs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/requests/get_me.rs b/src/core/requests/get_me.rs index 5f5a14fe..7c9c4c3c 100644 --- a/src/core/requests/get_me.rs +++ b/src/core/requests/get_me.rs @@ -11,7 +11,7 @@ impl Request for GetMe { type ReturnValue = User; fn send(self) -> RequestFuture> { - Box::new(async move { + Box::pin(async move { network::request(&self.info.client, &self.info.token, "getMe", None).await }) } diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index 797ea3b8..dd0ba59e 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -1,4 +1,5 @@ use std::future::Future; +use std::pin::Pin; use reqwest::r#async::Client; use reqwest::StatusCode; @@ -42,7 +43,9 @@ pub trait Request { fn send(self) -> RequestFuture>; } -pub type RequestFuture = Box>; +pub type RequestFuture = Pin + Send>>; + + // todo: better name? #[derive(Debug)] @@ -65,3 +68,7 @@ pub enum ChatId { pub mod get_me; pub mod send_message; + +fn a() { + use futures; +} \ No newline at end of file diff --git a/src/core/requests/send_message.rs b/src/core/requests/send_message.rs index e767d32a..6244cce7 100644 --- a/src/core/requests/send_message.rs +++ b/src/core/requests/send_message.rs @@ -25,7 +25,7 @@ impl Request for SendMessage { type ReturnValue = Message; fn send(self) -> RequestFuture> { - Box::new(async move { + Box::pin(async move { let params = FormBuilder::new() .add("chat_id", &self.chat_id) .add("text", &self.text)