Merge branch 'dev' of github.com:async-telegram-bot/async-telegram-bot into dev

This commit is contained in:
Waffle 2019-09-12 21:29:04 +03:00
commit 17d8ef43a0
42 changed files with 606 additions and 491 deletions

View file

@ -4,16 +4,12 @@ use crate::core::{
}; };
use apply::Apply; use apply::Apply;
use serde_json::Value;
use serde::{
Serialize,
de::DeserializeOwned
};
use reqwest::{ use reqwest::{
r#async::{multipart::Form, Client},
StatusCode, StatusCode,
r#async::{Client, multipart::Form},
}; };
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;
const TELEGRAM_API_URL: &str = "https://api.telegram.org"; const TELEGRAM_API_URL: &str = "https://api.telegram.org";
@ -58,10 +54,7 @@ pub async fn request_multipart<T: DeserializeOwned>(
.map_err(RequestError::NetworkError)?; .map_err(RequestError::NetworkError)?;
let response = serde_json::from_str::<TelegramResponse<T>>( let response = serde_json::from_str::<TelegramResponse<T>>(
&response &response.text().await.map_err(RequestError::NetworkError)?,
.text()
.await
.map_err(RequestError::NetworkError)?,
) )
.map_err(RequestError::InvalidJson)?; .map_err(RequestError::InvalidJson)?;
@ -72,7 +65,10 @@ pub async fn request_multipart<T: DeserializeOwned>(
error_code, error_code,
response_parameters, response_parameters,
.. ..
} => Err(RequestError::ApiError { description, status_code: StatusCode::from_u16(error_code).unwrap() }) } => Err(RequestError::ApiError {
description,
status_code: StatusCode::from_u16(error_code).unwrap(),
}),
} }
} }
@ -90,10 +86,7 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
.map_err(RequestError::NetworkError)?; .map_err(RequestError::NetworkError)?;
let response = serde_json::from_str::<TelegramResponse<T>>( let response = serde_json::from_str::<TelegramResponse<T>>(
&response &response.text().await.map_err(RequestError::NetworkError)?,
.text()
.await
.map_err(RequestError::NetworkError)?,
) )
.map_err(RequestError::InvalidJson)?; .map_err(RequestError::InvalidJson)?;
@ -104,7 +97,10 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
error_code, error_code,
response_parameters, response_parameters,
.. ..
} => Err(RequestError::ApiError { description, status_code: StatusCode::from_u16(error_code).unwrap() }) } => Err(RequestError::ApiError {
description,
status_code: StatusCode::from_u16(error_code).unwrap(),
}),
} }
} }

View file

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::core::requests::{RequestContext, ChatId, Request, RequestFuture, ResponseResult};
use crate::core::types::{Message, ReplyMarkup};
use crate::core::network; use crate::core::network;
use crate::core::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::core::types::{Message, ReplyMarkup};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to edit live location messages. A location can be edited /// Use this method to edit live location messages. A location can be edited
@ -13,16 +15,16 @@ pub struct EditMessageLiveLocation<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Unique identifier for /// Required if inline_message_id is not specified. Unique identifier for
/// the target chat or username of the target channel (in the format /// the target chat or username of the target channel (in the format
/// @channelusername) /// @channelusername)
chat_id: Option<ChatId>, chat_id: Option<ChatId>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Identifier of the /// Required if inline_message_id is not specified. Identifier of the
/// message to edit /// message to edit
message_id: Option<i64>, message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if chat_id and message_id are not specified. Identifier of /// Required if chat_id and message_id are not specified. Identifier of
/// the inline message /// the inline message
inline_message_id: Option<String>, inline_message_id: Option<String>,
@ -30,9 +32,9 @@ pub struct EditMessageLiveLocation<'a> {
latitude: f64, latitude: f64,
/// Longitude of new location /// Longitude of new location
longitude: f64, longitude: f64,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// A JSON-serialized object for a new inline keyboard. /// A JSON-serialized object for a new inline keyboard.
reply_markup: Option<ReplyMarkup> reply_markup: Option<ReplyMarkup>,
} }
impl<'a> Request<'a> for EditMessageLiveLocation<'a> { impl<'a> Request<'a> for EditMessageLiveLocation<'a> {
@ -44,14 +46,15 @@ impl<'a> Request<'a> for EditMessageLiveLocation<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"editMessageLiveLocation", "editMessageLiveLocation",
&self &self,
).await )
.await
}) })
} }
} }
impl<'a> EditMessageLiveLocation<'a> { impl<'a> EditMessageLiveLocation<'a> {
pub(crate) fn new ( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
latitude: f64, latitude: f64,
longitude: f64, longitude: f64,
@ -63,7 +66,7 @@ impl<'a> EditMessageLiveLocation<'a> {
inline_message_id: None, inline_message_id: None,
latitude, latitude,
longitude, longitude,
reply_markup: None reply_markup: None,
} }
} }
@ -78,7 +81,8 @@ impl<'a> EditMessageLiveLocation<'a> {
} }
pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.inline_message_id = Some(inline_message_id.into()); self.inline_message_id = Some(inline_message_id.into());
self self

View file

@ -1,14 +1,13 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::core::{ use crate::core::{
types::{ParseMode, InputMedia}, requests::{utils, ChatId},
requests::{ChatId, utils}, types::{InputMedia, ParseMode},
}; };
use reqwest::r#async::multipart::Form; use reqwest::r#async::multipart::Form;
use serde::Serialize; use serde::Serialize;
/// This is a convenient struct that builds `reqwest::r#async::multipart::Form` /// This is a convenient struct that builds `reqwest::r#async::multipart::Form`
/// from scratch. /// from scratch.
pub struct FormBuilder { pub struct FormBuilder {
@ -22,20 +21,19 @@ impl FormBuilder {
/// Add the supplied key-value pair to this `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 where
T: ToFormValue + ?Sized,
{ {
Self { Self {
form: self.form.text( form: self.form.text(name.to_owned(), value.to_form_value()),
name.to_owned(),
value.to_form_value()
)
} }
} }
/// Adds a key-value pair to the supplied `FormBuilder` if `value` is some. /// Adds a key-value pair to the supplied `FormBuilder` if `value` is some.
/// Don't forget to implement `serde::Serialize` for `T`! /// Don't forget to implement `serde::Serialize` for `T`!
pub fn add_if_some<T>(self, name: &str, value: Option<&T>) -> Self pub fn add_if_some<T>(self, name: &str, value: Option<&T>) -> Self
where T: ToFormValue + ?Sized where
T: ToFormValue + ?Sized,
{ {
match value { match value {
None => Self { form: self.form }, None => Self { form: self.form },
@ -45,7 +43,9 @@ impl FormBuilder {
pub fn add_file(self, name: &str, path_to_file: &PathBuf) -> Self { pub fn add_file(self, name: &str, path_to_file: &PathBuf) -> Self {
Self { Self {
form: self.form.part(name.to_owned(), utils::file_to_part(path_to_file)) form: self
.form
.part(name.to_owned(), utils::file_to_part(path_to_file)),
} }
} }
@ -70,9 +70,7 @@ macro_rules! impl_for_struct {
}; };
} }
impl_for_struct!( impl_for_struct!(bool, i32, i64, Vec<InputMedia>);
bool, i32, i64, Vec<InputMedia>
);
impl ToFormValue for str { impl ToFormValue for str {
fn to_form_value(&self) -> String { fn to_form_value(&self) -> String {

View file

@ -1,17 +1,12 @@
use crate::core::{ use crate::core::{
network, network,
types::Message,
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture,
RequestContext,
ResponseResult,
form_builder::FormBuilder,
}, },
types::Message,
}; };
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to forward messages of any kind. On success, the sent /// Use this method to forward messages of any kind. On success, the sent
/// [`Message`] is returned. /// [`Message`] is returned.
@ -28,8 +23,9 @@ pub struct ForwardMessage<'a> {
/// Message identifier in the chat specified in from_chat_id /// Message identifier in the chat specified in from_chat_id
pub message_id: i64, pub message_id: i64,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
#[serde(skip_serializing_if="Option::is_none")] /// sound.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
} }
@ -43,22 +39,25 @@ impl<'a> Request<'a> for ForwardMessage<'a> {
self.ctx.token, self.ctx.token,
"forwardMessage", "forwardMessage",
&self, &self,
).await )
.await
}) })
} }
} }
impl<'a> ForwardMessage<'a> { impl<'a> ForwardMessage<'a> {
pub(crate) fn new(ctx: RequestContext<'a>, pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId, chat_id: ChatId,
from_chat_id: ChatId, from_chat_id: ChatId,
message_id: i64) -> Self { message_id: i64,
) -> Self {
Self { Self {
ctx, ctx,
chat_id, chat_id,
from_chat_id, from_chat_id,
message_id, message_id,
disable_notification: None disable_notification: None,
} }
} }

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct GetFile<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,15 +1,9 @@
use crate::core::{ use crate::core::{
network, network,
requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::User, types::User,
requests::{
Request,
RequestFuture,
RequestContext,
ResponseResult
},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// A simple method for testing your bot's auth token. Requires no parameters. /// A simple method for testing your bot's auth token. Requires no parameters.
/// Returns basic information about the bot in form of a [`User`] object. /// Returns basic information about the bot in form of a [`User`] object.
@ -21,14 +15,12 @@ impl<'a> Request<'a> for GetMe<'a> {
type ReturnValue = User; type ReturnValue = User;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> { fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin( Box::pin(network::request_multipart(
network::request_multipart(
self.ctx.client, self.ctx.client,
self.ctx.token, self.ctx.token,
"getMe", "getMe",
None None,
) ))
)
} }
} }

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct GetUserProfilePhotos<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct KickChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,19 +1,17 @@
use std::pin::Pin;
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use reqwest::{ use reqwest::{r#async::Client, StatusCode};
r#async::Client, StatusCode
};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
mod form_builder; mod form_builder;
mod utils; mod utils;
#[derive(Debug, Display)] #[derive(Debug, Display)]
pub enum RequestError { pub enum RequestError {
#[display(fmt = "Telegram error #{}: {}", status_code, description)] #[display(fmt = "Telegram error #{}: {}", status_code, description)]
ApiError { // TODO: add response parameters ApiError {
// TODO: add response parameters
status_code: StatusCode, status_code: StatusCode,
description: String, description: String,
}, },
@ -82,18 +80,30 @@ mod tests {
#[test] #[test]
fn chat_id_channel_username_serialization() { fn chat_id_channel_username_serialization() {
let expected_json = String::from(r#""@username""#); let expected_json = String::from(r#""@username""#);
let actual_json = serde_json::to_string(&ChatId::ChannelUsername(String::from("@username"))).unwrap(); let actual_json = serde_json::to_string(&ChatId::ChannelUsername(
String::from("@username"),
))
.unwrap();
assert_eq!(expected_json, actual_json) assert_eq!(expected_json, actual_json)
} }
} }
pub mod get_me;
pub mod send_message;
pub mod forward_message;
pub mod send_photo;
pub mod send_media_group;
pub mod send_audio;
pub mod send_location;
pub mod edit_message_live_location; pub mod edit_message_live_location;
pub mod forward_message;
pub mod get_file;
pub mod get_me;
pub mod get_user_profile_photos;
pub mod kick_chat_member;
pub mod restrict_chat_member;
pub mod send_audio;
pub mod send_chat_action;
pub mod send_contact;
pub mod send_location;
pub mod send_media_group;
pub mod send_message;
pub mod send_photo;
pub mod send_poll;
pub mod send_venue;
pub mod stop_message_live_location; pub mod stop_message_live_location;
pub mod unban_chat_member;

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct RestrictChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,8 +1,10 @@
use crate::core::{ use crate::core::{
network, network,
requests::{ChatId, Request, RequestFuture, ResponseResult, RequestContext},
requests::form_builder::FormBuilder, requests::form_builder::FormBuilder,
types::{InputFile, ParseMode, Message, ReplyMarkup}, requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{InputFile, Message, ParseMode, ReplyMarkup},
}; };
/// Use this method to send audio files, if you want Telegram clients to display /// Use this method to send audio files, if you want Telegram clients to display
@ -71,11 +73,11 @@ impl<'a> Request<'a> for SendAudio<'a> {
.add_if_some("title", self.title.as_ref()) .add_if_some("title", self.title.as_ref())
.add_if_some( .add_if_some(
"disable_notification", "disable_notification",
self.disable_notification.as_ref() self.disable_notification.as_ref(),
) )
.add_if_some( .add_if_some(
"reply_to_message_id", "reply_to_message_id",
self.reply_to_message_id.as_ref() self.reply_to_message_id.as_ref(),
); );
params = match self.audio { params = match self.audio {
InputFile::File(file) => params.add_file("audio", &file), InputFile::File(file) => params.add_file("audio", &file),
@ -95,8 +97,9 @@ impl<'a> Request<'a> for SendAudio<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendAudio", "sendAudio",
Some(params) Some(params),
).await )
.await
}) })
} }
} }
@ -119,7 +122,7 @@ impl<'a> SendAudio<'a> {
thumb: None, thumb: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }
@ -163,12 +166,18 @@ impl<'a> SendAudio<'a> {
self self
} }
pub fn disable_notification<T: Into<bool>>(mut self, disable_notification: T) -> Self { pub fn disable_notification<T: Into<bool>>(
mut self,
disable_notification: T,
) -> Self {
self.disable_notification = Some(disable_notification.into()); self.disable_notification = Some(disable_notification.into());
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, reply_to_message_id: T) -> Self { pub fn reply_to_message_id<T: Into<i64>>(
mut self,
reply_to_message_id: T,
) -> Self {
self.reply_to_message_id = Some(reply_to_message_id.into()); self.reply_to_message_id = Some(reply_to_message_id.into());
self self
} }

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct SendChatAction<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -0,0 +1,7 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct SendContact<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,7 +1,9 @@
use crate::core::{ use crate::core::{
requests::{RequestContext, ChatId, Request, RequestFuture, ResponseResult},
types::{Message, ReplyMarkup},
network, network,
requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{Message, ReplyMarkup},
}; };
use serde::Serialize; use serde::Serialize;
@ -20,19 +22,19 @@ pub struct SendLocation<'a> {
latitude: f64, latitude: f64,
/// Longitude of the location /// Longitude of the location
longitude: f64, longitude: f64,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Period in seconds for which the location will be updated /// Period in seconds for which the location will be updated
/// (see [Live Locations](https://telegram.org/blog/live-locations)), /// (see [Live Locations](https://telegram.org/blog/live-locations)),
/// should be between 60 and 86400. /// should be between 60 and 86400.
live_period: Option<i32>, live_period: Option<i32>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Sends the message silently. Users will receive a notification with /// Sends the message silently. Users will receive a notification with
/// no sound. /// no sound.
disable_notification: Option<bool>, disable_notification: Option<bool>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
reply_to_message_id: Option<i64>, reply_to_message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
reply_markup: Option<ReplyMarkup>, reply_markup: Option<ReplyMarkup>,
} }
@ -45,8 +47,9 @@ impl<'a> Request<'a> for SendLocation<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendLocation", "sendLocation",
&self &self,
).await )
.await
}) })
} }
} }
@ -66,7 +69,7 @@ impl<'a> SendLocation<'a> {
live_period: None, live_period: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }

View file

@ -1,14 +1,10 @@
use crate::core::{ use crate::core::{
network::request_multipart, network::request_multipart,
types::{Message, InputMedia, InputFile},
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture, },
RequestContext, types::{InputFile, InputMedia, Message},
ResponseResult,
form_builder::FormBuilder,
}
}; };
use apply::Apply; use apply::Apply;
@ -32,28 +28,40 @@ impl<'a> Request<'a> for SendMediaGroup<'a> {
let params = FormBuilder::new() let params = FormBuilder::new()
.add("chat_id", &self.chat_id) .add("chat_id", &self.chat_id)
.apply(|form| { .apply(|form| {
self.media self.media.iter().map(|e| e.media()).fold(
.iter() form,
.map(|e| e.media()) |acc, file| {
.fold(form, |acc, file| {
if let InputFile::File(path) = file { if let InputFile::File(path) = file {
acc.add_file( acc.add_file(
&path &path
.file_name() .file_name()
.unwrap() .unwrap()
.to_string_lossy(), .to_string_lossy(),
path path,
) )
} else { } else {
acc acc
} }
}) },
)
}) })
.add("media", &self.media) .add("media", &self.media)
.add_if_some("disable_notification", self.disable_notification.as_ref()) .add_if_some(
.add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) "disable_notification",
self.disable_notification.as_ref(),
)
.add_if_some(
"reply_to_message_id",
self.reply_to_message_id.as_ref(),
)
.build(); .build();
request_multipart(&self.ctx.client, &self.ctx.token, "sendMediaGroup", Some(params)).await request_multipart(
&self.ctx.client,
&self.ctx.token,
"sendMediaGroup",
Some(params),
)
.await
}) })
} }
} }

View file

@ -1,19 +1,15 @@
use crate::core::{ use crate::core::{
network, network,
types::{Message, ParseMode, ReplyMarkup},
requests::{ requests::{
form_builder::FormBuilder, form_builder::FormBuilder, ChatId, Request, RequestContext,
ChatId, RequestFuture, ResponseResult,
Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::{Message, ParseMode, ReplyMarkup},
}; };
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to send text messages. On success, the sent [`Message`] is returned. /// Use this method to send text messages. On success, the sent [`Message`] is
/// returned.
pub struct SendMessage<'a> { pub struct SendMessage<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
@ -32,18 +28,19 @@ pub struct SendMessage<'a> {
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::core::types::ParseMode
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Disables link previews for links in this message /// Disables link previews for links in this message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub disable_web_page_preview: Option<bool>, pub disable_web_page_preview: Option<bool>,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
#[serde(skip_serializing_if="Option::is_none")] /// sound.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<ReplyMarkup>, pub reply_markup: Option<ReplyMarkup>,
} }
@ -57,7 +54,8 @@ impl<'a> Request<'a> for SendMessage<'a> {
self.ctx.token, self.ctx.token,
"sendMessage", "sendMessage",
&self, &self,
).await )
.await
}) })
} }
} }

View file

@ -2,20 +2,16 @@ use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
types::{ParseMode, Message, InputFile, ReplyMarkup},
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture,
RequestContext,
ResponseResult,
form_builder::FormBuilder,
}, },
types::{InputFile, Message, ParseMode, ReplyMarkup},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Use this method to send photos. On success, the sent [`Message`] is returned. /// Use this method to send photos. On success, the sent [`Message`] is
/// returned.
pub struct SendPhoto<'a> { pub struct SendPhoto<'a> {
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
@ -23,13 +19,14 @@ pub struct SendPhoto<'a> {
/// (in the format @channelusername) /// (in the format @channelusername)
pub chat_id: ChatId, pub chat_id: ChatId,
/// Photo to send. /// Photo to send.
/// [`InputFile::FileId`] - Pass a file_id as String to send a photo that exists on the /// [`InputFile::FileId`] - Pass a file_id as String to send a photo that
/// Telegram servers (recommended) /// exists on the Telegram servers (recommended)
/// [`InputFile::Url`] - Pass an HTTP URL as a String for Telegram /// [`InputFile::Url`] - Pass an HTTP URL as a String for Telegram
/// to get a photo from the Internet /// to get a photo from the Internet
/// [`InputFile::File`] - Upload a new photo. /// [`InputFile::File`] - Upload a new photo.
pub photo: InputFile, pub photo: InputFile,
/// Photo caption (may also be used when resending photos by file_id), 0-1024 characters /// Photo caption (may also be used when resending photos by file_id),
/// 0-1024 characters
pub caption: Option<String>, pub caption: Option<String>,
/// Send [Markdown] or [HTML], /// Send [Markdown] or [HTML],
/// if you want Telegram apps to show [bold, italic, fixed-width text /// if you want Telegram apps to show [bold, italic, fixed-width text
@ -40,7 +37,8 @@ pub struct SendPhoto<'a> {
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::core::types::ParseMode
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
/// sound.
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i64>,
@ -58,11 +56,11 @@ impl<'a> Request<'a> for SendPhoto<'a> {
.add_if_some("parse_mode", self.parse_mode.as_ref()) .add_if_some("parse_mode", self.parse_mode.as_ref())
.add_if_some( .add_if_some(
"disable_notification", "disable_notification",
self.disable_notification.as_ref() self.disable_notification.as_ref(),
) )
.add_if_some( .add_if_some(
"reply_to_message_id", "reply_to_message_id",
self.reply_to_message_id.as_ref() self.reply_to_message_id.as_ref(),
); );
params = match self.photo { params = match self.photo {
@ -76,8 +74,9 @@ impl<'a> Request<'a> for SendPhoto<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendPhoto", "sendPhoto",
Some(params) Some(params),
).await )
.await
}) })
} }
} }
@ -86,7 +85,7 @@ impl<'a> SendPhoto<'a> {
pub(crate) fn new( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
chat_id: ChatId, chat_id: ChatId,
photo: InputFile photo: InputFile,
) -> Self { ) -> Self {
Self { Self {
ctx, ctx,
@ -96,7 +95,7 @@ impl<'a> SendPhoto<'a> {
parse_mode: None, parse_mode: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }
@ -120,12 +119,18 @@ impl<'a> SendPhoto<'a> {
self self
} }
pub fn disable_notification<T: Into<bool>>(mut self, disable_notification: T) -> Self { pub fn disable_notification<T: Into<bool>>(
mut self,
disable_notification: T,
) -> Self {
self.disable_notification = Some(disable_notification.into()); self.disable_notification = Some(disable_notification.into());
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, reply_to_message_id: T) -> Self { pub fn reply_to_message_id<T: Into<i64>>(
mut self,
reply_to_message_id: T,
) -> Self {
self.reply_to_message_id = Some(reply_to_message_id.into()); self.reply_to_message_id = Some(reply_to_message_id.into());
self self
} }

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct SendPoll<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -0,0 +1,43 @@
use crate::core::requests::{ChatId, RequestContext};
//TODO:: need implementation
///Use this method to send information about a venue. On success, the sent
/// Message is returned.
#[derive(Debug, Clone, Serialize)]
struct SendVenue<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Integer or String Yes Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
chat_id: ChatId,
/// Float number Yes Latitude of the venue
latitude: f64,
///Float number Yes Longitude of the venue
longitude: f64,
/// Yes Name of the venue
title: String,
///String Yes Address of the venue
address: String,
/// String Optional Foursquare identifier of the venue
#[serde(skip_serializing_if = "Option::is_none")]
foursquare_id: Option<String>,
/// String Optional Foursquare type of the venue, if known. (For
/// example, “arts_entertainment/default”, “arts_entertainment/aquarium” or
/// “food/icecream”.)
#[serde(skip_serializing_if = "Option::is_none")]
foursquare_type: Option<String>,
/// Boolean Optional Sends the message silently. Users will receive a
/// notification with no sound.
#[serde(skip_serializing_if = "Option::is_none")]
disable_notification: Option<bool>,
/// Integer Optional If the message is a reply, ID of the original
/// message
#[serde(skip_serializing_if = "Option::is_none")]
reply_to_message_id: Option<i32>,
/// InlineKeyboardMarkup or ReplyKeyboardMarkup or ReplyKeyboardRemove or
/// ForceReply Optional Additional interface options. A JSON-serialized
/// object for an inline keyboard, custom reply keyboard, instructions to
/// remove reply keyboard or to force a reply from the user.
#[serde(skip_serializing_if = "Option::is_none")]
reply_markup: Option<()>, //TODO: need concrete type
}

View file

@ -3,7 +3,7 @@ use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{
ChatId, form_builder::FormBuilder, Request, RequestContext, form_builder::FormBuilder, ChatId, Request, RequestContext,
RequestFuture, ResponseResult, RequestFuture, ResponseResult,
}, },
types::{InlineKeyboardMarkup, Message, ParseMode}, types::{InlineKeyboardMarkup, Message, ParseMode},
@ -14,23 +14,24 @@ use crate::core::{
/// returned, otherwise True is returned. /// returned, otherwise True is returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct StopMessageLiveLocation<'a> { struct StopMessageLiveLocation<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Required if inline_message_id is not specified. Unique identifier for /// Required if inline_message_id is not specified. Unique identifier for
/// the target chat or username of the target channel (in the format /// the target chat or username of the target channel (in the format
/// @channelusername) /// @channelusername)
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub chat_id: Option<ChatId>, pub chat_id: Option<ChatId>,
/// Required if inline_message_id is not specified. Identifier of the /// Required if inline_message_id is not specified. Identifier of the
/// message with live location to stop /// message with live location to stop
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<i32>, pub message_id: Option<i32>,
/// Required if chat_id and message_id are not specified. Identifier of the /// Required if chat_id and message_id are not specified. Identifier of the
/// inline message /// inline message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub inline_message_id: Option<String>, pub inline_message_id: Option<String>,
/// A JSON-serialized object InlineKeyboardMarkup for a new inline /// A JSON-serialized object InlineKeyboardMarkup for a new inline
/// keyboard. /// keyboard.
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>, pub reply_markup: Option<InlineKeyboardMarkup>,
} }
@ -51,9 +52,7 @@ impl<'a> Request<'a> for StopMessageLiveLocation<'a> {
} }
impl<'a> StopMessageLiveLocation<'a> { impl<'a> StopMessageLiveLocation<'a> {
fn new( fn new(ctx: RequestContext<'a>) -> Self {
ctx: RequestContext<'a>,
) -> Self {
Self { Self {
ctx, ctx,
chat_id: None, chat_id: None,
@ -64,30 +63,34 @@ impl<'a> StopMessageLiveLocation<'a> {
} }
pub fn chat_id<T>(mut self, chat_id: T) -> Self pub fn chat_id<T>(mut self, chat_id: T) -> Self
where T: Into<ChatId> where
T: Into<ChatId>,
{ {
self.chat_id = chat_id.into(); self.chat_id = Some(chat_id.into());
self self
} }
pub fn message_id<T>(mut self, message_id: T) -> Self pub fn message_id<T>(mut self, message_id: T) -> Self
where T: Into<i32> where
T: Into<i32>,
{ {
self.message_id = Some(message_id.into()); self.message_id = Some(message_id.into());
self self
} }
pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.inline_message_id = Some(inline_message_id.into()); self.inline_message_id = Some(inline_message_id.into());
self self
} }
pub fn reply_markup<T>(mut self, reply_markup: T) -> Self pub fn reply_markup<T>(mut self, reply_markup: T) -> Self
where T: Into<InlineKeyboardMarkup> where
T: Into<InlineKeyboardMarkup>,
{ {
self.inline_message_id = Some(reply_markup.into()); self.reply_markup = Some(reply_markup.into());
self self
} }
} }

View file

@ -0,0 +1,8 @@
use crate::core::requests::RequestContext;
//TODO:: need implementation
#[derive(Debug, Clone, Serialize)]
struct UnbanChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,9 +1,9 @@
use tokio::codec::FramedRead;
use std::fs::File;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use tokio::prelude::*;
use reqwest::r#async::multipart::Part; use reqwest::r#async::multipart::Part;
use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::codec::FramedRead;
use tokio::prelude::*;
struct FileDecoder; struct FileDecoder;
@ -11,9 +11,12 @@ impl tokio::codec::Decoder for FileDecoder {
type Item = Bytes; type Item = Bytes;
type Error = std::io::Error; type Error = std::io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(
&mut self,
src: &mut BytesMut,
) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() { if src.is_empty() {
return Ok(None) return Ok(None);
} }
Ok(Some(src.take().freeze())) Ok(Some(src.take().freeze()))
} }
@ -21,9 +24,19 @@ impl tokio::codec::Decoder for FileDecoder {
pub fn file_to_part(path_to_file: &PathBuf) -> Part { pub fn file_to_part(path_to_file: &PathBuf) -> Part {
let file = tokio::fs::File::open(path_to_file.clone()) let file = tokio::fs::File::open(path_to_file.clone())
.map(|file| FramedRead::new(file.unwrap() /* TODO: this can cause panics */, FileDecoder)) .map(|file| {
FramedRead::new(
file.unwrap(), /* TODO: this can cause panics */
FileDecoder,
)
})
.flatten_stream(); .flatten_stream();
let part = Part::stream(file) let part = Part::stream(file).file_name(
.file_name(path_to_file.file_name().unwrap().to_string_lossy().into_owned()); path_to_file
.file_name()
.unwrap()
.to_string_lossy()
.into_owned(),
);
part part
} }

View file

@ -0,0 +1,13 @@
use crate::core::types::PhotoSize;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Animation {
pub file_id: String,
pub width: u32,
pub height: u32,
pub duration: u32,
pub thumb: PhotoSize,
pub file_name: Option<String>,
pub mime_type: Option<String>,
pub file_size: Option<u32>
}

View file

@ -1,4 +1,4 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone )] #[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct AnswerPreCheckoutQuery { pub struct AnswerPreCheckoutQuery {
pub pre_checkout_query_id: String, pub pre_checkout_query_id: String,
pub ok: bool, pub ok: bool,

View file

@ -32,7 +32,6 @@ pub enum ChatKind {
}, },
} }
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -101,9 +100,8 @@ mod tests {
}, },
photo: None, photo: None,
}; };
let actual = from_str( let actual =
r#"{"id":-1,"type":"channel","username":"channelname"}"#, from_str(r#"{"id":-1,"type":"channel","username":"channelname"}"#)
)
.unwrap(); .unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }

View file

@ -1,8 +1,6 @@
use serde::Deserialization; #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[derive(Debug, Deserialization, Clone)]
/// This object represents a phone contact. /// This object represents a phone contact.
struct Contact { pub struct Contact {
/// Contact's phone number /// Contact's phone number
pub phone_number: String, pub phone_number: String,
/// Contact's first name /// Contact's first name

6
src/core/types/file.rs Normal file
View file

@ -0,0 +1,6 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone)]
pub struct File {
pub file_id: String,
pub file_size: u32,
pub file_path: String
}

View file

@ -8,7 +8,9 @@ pub struct InlineKeyboardButton {
pub kind: InlineKeyboardButtonKind, pub kind: InlineKeyboardButtonKind,
} }
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Eq, Hash, Deserialize)] #[derive(
Debug, Clone, PartialEq, PartialOrd, Serialize, Eq, Hash, Deserialize,
)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum InlineKeyboardButtonKind { pub enum InlineKeyboardButtonKind {
/// HTTP or tg:// url to be opened when button is pressed /// HTTP or tg:// url to be opened when button is pressed
@ -27,13 +29,14 @@ pub enum InlineKeyboardButtonKind {
/// the user will be automatically returned to the chat they switched from, /// the user will be automatically returned to the chat they switched from,
/// skipping the chat selection screen. /// skipping the chat selection screen.
SwitchInlineQuery(String), SwitchInlineQuery(String),
/// Optional. If set, pressing the button will insert the bots username and /// Optional. If set, pressing the button will insert the bots username
/// the specified inline query in the current chat's input field. Can be /// and the specified inline query in the current chat's input field.
/// empty, in which case only the bots username will be inserted. /// Can be empty, in which case only the bots username will be
/// inserted.
/// ///
///This offers a quick way for the user to open your bot in inline mode in ///This offers a quick way for the user to open your bot in inline mode in
/// the same chat good for selecting something from multiple options. /// the same chat good for selecting something from multiple options.
SwitchInlineQueryCurrentChat(String), SwitchInlineQueryCurrentChat(String),
// CallbackGame(CallbackGame), TODO: разобраться, что с этим делать /* CallbackGame(CallbackGame), TODO: разобраться, что с этим делать
// TODO: add LoginUrl, pay * TODO: add LoginUrl, pay */
} }

View file

@ -168,11 +168,11 @@ pub enum InputMedia {
impl InputMedia { impl InputMedia {
pub fn media(&self) -> &InputFile { pub fn media(&self) -> &InputFile {
match self { match self {
InputMedia::Photo { media, .. } | InputMedia::Photo { media, .. }
InputMedia::Document { media, .. } | | InputMedia::Document { media, .. }
InputMedia::Audio { media, .. } | | InputMedia::Audio { media, .. }
InputMedia::Animation { media, .. } | | InputMedia::Animation { media, .. }
InputMedia::Video { media, .. } => media, | InputMedia::Video { media, .. } => media,
} }
} }
} }

View file

@ -72,7 +72,7 @@ pub enum MessageKind {
}, },
} }
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum Sender { pub enum Sender {
/// If message is sent from Chat /// If message is sent from Chat
#[serde(rename = "from")] #[serde(rename = "from")]
@ -106,7 +106,7 @@ pub enum ForwardKind {
}, },
} }
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ForwardedFrom { pub enum ForwardedFrom {
#[serde(rename = "forward_from")] #[serde(rename = "forward_from")]
User(User), User(User),
@ -125,13 +125,13 @@ pub enum MediaKind {
document: (), document: (),
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Audio { Audio {
audio: Audio, audio: Audio,
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Contact { Contact {
contact: Contact, contact: Contact,
@ -140,7 +140,7 @@ pub enum MediaKind {
document: Document, document: Document,
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Game { Game {
game: Game, game: Game,
@ -193,64 +193,7 @@ mod tests {
use serde_json::from_str; use serde_json::from_str;
#[test] #[test]
fn sent_message_de() { fn de_media_forwarded() {
let expected = Message {
id: 6534,
date: 1567898953,
chat: Chat {
id: 218485655,
photo: None,
kind: ChatKind::Private {
type_: (),
first_name: Some("W".to_string()),
last_name: None,
username: Some("WaffleLapkin".to_string()),
},
},
message_kind: MessageKind::IncomingMessage {
from: Sender::User(User {
id: 457569668,
is_bot: true,
first_name: "BT".to_string(),
last_name: None,
username: Some("BloodyTestBot".to_string()),
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text: "text".to_string(),
entities: vec![],
},
reply_markup: None,
},
};
// actual message from telegram
let json = r#"{
"message_id": 6534,
"from": {
"id": 457569668,
"is_bot": true,
"first_name": "BT",
"username": "BloodyTestBot"
},
"chat": {
"id": 218485655,
"first_name": "W",
"username": "WaffleLapkin",
"type": "private"
},
"date": 1567898953,
"text": "text"
}"#;
let actual = from_str::<Message>(json).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn media_message_de() {
let json = r#"{ let json = r#"{
"message_id": 198283, "message_id": 198283,
"from": { "from": {
@ -283,60 +226,13 @@ mod tests {
"file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
"file_size": 1381334 "file_size": 1381334
} }
}"#; }"#;
let actual = from_str::<Message>(json).unwrap(); let message = from_str::<Message>(json);
let expected = Message { assert!(message.is_ok());
id: 198283,
date: 1567927221,
chat: Chat {
id: 250918540,
photo: None,
kind: ChatKind::Private {
first_name: Some("Андрей".to_string()),
last_name: Some("Власов".to_string()),
username: Some("aka_dude".to_string()),
type_: ()
}
},
message_kind: MessageKind::IncomingMessage {
from: Sender::User(User {
id: 250918540,
is_bot: false,
first_name: "Андрей".to_string(),
last_name: Some("Власов".to_string()),
username: Some("aka_dude".to_string()),
language_code: Some("en".to_string())
}),
forward_kind: ForwardKind::Origin { reply_to_message: None },
edit_date: None,
media_kind: MediaKind::Video {
video: Video {
duration: 13,
width: 512,
height: 640,
mime_type: Some("video/mp4".to_string()),
thumb: Some(PhotoSize {
file_id: "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE".to_string(),
file_size: Some(10339),
width: 256,
height: 320
}),
file_id: "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE".to_string(),
file_size: Some(1381334)
},
caption: None,
caption_entities: vec![],
media_group_id: None
},
reply_markup: None
},
};
assert_eq!(actual, expected);
} }
#[test] #[test]
fn media_group_message_de() { fn de_media_group_forwarded() {
let json = r#"{ let json = r#"{
"message_id": 198283, "message_id": 198283,
"from": { "from": {
@ -370,55 +266,119 @@ mod tests {
"file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE",
"file_size": 1381334 "file_size": 1381334
} }
}"#; }"#;
let actual = from_str::<Message>(json).unwrap(); let message = from_str::<Message>(json);
let expected = Message { assert!(message.is_ok());
id: 198283,
date: 1567927221,
chat: Chat {
id: 250918540,
photo: None,
kind: ChatKind::Private {
first_name: Some("Андрей".to_string()),
last_name: Some("Власов".to_string()),
username: Some("aka_dude".to_string()),
type_: ()
} }
},
message_kind: MessageKind::IncomingMessage {
from: Sender::User(User {
id: 250918540,
is_bot: false,
first_name: "Андрей".to_string(),
last_name: Some("Власов".to_string()),
username: Some("aka_dude".to_string()),
language_code: Some("en".to_string())
}),
forward_kind: ForwardKind::Origin { reply_to_message: None },
edit_date: None,
media_kind: MediaKind::Video {
video: Video {
duration: 13,
width: 512,
height: 640,
mime_type: Some("video/mp4".to_string()),
thumb: Some(PhotoSize {
file_id: "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE".to_string(),
file_size: Some(10339),
width: 256,
height: 320
}),
file_id: "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE".to_string(),
file_size: Some(1381334)
},
caption: None,
caption_entities: vec![],
media_group_id: Some("12543417770506682".to_string())
},
reply_markup: None
},
}; #[test]
assert_eq!(actual, expected); fn de_text() {
let json = r#"{
"message_id": 199785,
"from": {
"id": 250918540,
"is_bot": false,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"language_code": "en"
},
"chat": {
"id": 250918540,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"type": "private"
},
"date": 1568289890,
"text": "Лол кек 😂"
}"#;
let message = from_str::<Message>(json);
assert!(message.is_ok());
}
#[test]
fn de_sticker() {
let json = r#"{
"message_id": 199787,
"from": {
"id": 250918540,
"is_bot": false,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"language_code": "en"
},
"chat": {
"id": 250918540,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"type": "private"
},
"date": 1568290188,
"sticker": {
"width": 512,
"height": 512,
"emoji": "😡",
"set_name": "AdvenTimeAnim",
"is_animated": true,
"thumb": {
"file_id": "AAQCAAMjAAOw0PgMaabKAcaXKCBLubkPAAQBAAdtAAPGKwACFgQ",
"file_size": 4118,
"width": 128,
"height": 128
},
"file_id": "CAADAgADIwADsND4DGmmygHGlyggFgQ",
"file_size": 16639
}
}"#;
let message = from_str::<Message>(json);
assert!(message.is_ok());
}
#[test]
fn de_image() {
let json = r#"{
"message_id": 199791,
"from": {
"id": 250918540,
"is_bot": false,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"language_code": "en"
},
"chat": {
"id": 250918540,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"type": "private"
},
"date": 1568290622,
"photo": [
{
"file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ",
"file_size": 18188,
"width": 320,
"height": 239
},
{
"file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ",
"file_size": 62123,
"width": 800,
"height": 598
},
{
"file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ",
"file_size": 75245,
"width": 962,
"height": 719
}
]
}"#;
let message = from_str::<Message>(json);
assert!(message.is_ok());
} }
} }

View file

@ -8,9 +8,13 @@ pub use self::{
chat_permissions::ChatPermissions, chat_permissions::ChatPermissions,
chat_photo::ChatPhoto, chat_photo::ChatPhoto,
document::Document, document::Document,
force_reply::ForceReply,
inline_keyboard_button::InlineKeyboardButton,
inline_keyboard_markup::InlineKeyboardMarkup,
input_file::InputFile, input_file::InputFile,
input_media::InputMedia, input_media::InputMedia,
invoice::Invoice, invoice::Invoice,
keyboard_button::KeyboardButton,
label_price::LabeledPrice, label_price::LabeledPrice,
message::{ message::{
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender, ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
@ -19,7 +23,11 @@ pub use self::{
order_info::OrderInfo, order_info::OrderInfo,
parse_mode::ParseMode, parse_mode::ParseMode,
photo_size::PhotoSize, photo_size::PhotoSize,
poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery, pre_checkout_query::PreCheckoutQuery,
reply_keyboard_markup::ReplyKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_markup::ReplyMarkup,
response_parameters::ResponseParameters, response_parameters::ResponseParameters,
send_invoice::SendInvoice, send_invoice::SendInvoice,
shipping_address::ShippingAddress, shipping_address::ShippingAddress,
@ -51,8 +59,8 @@ mod chat_permissions;
mod chat_photo; mod chat_photo;
mod document; mod document;
mod force_reply; mod force_reply;
mod inline_keyboard_markup;
mod inline_keyboard_button; mod inline_keyboard_button;
mod inline_keyboard_markup;
mod input_file; mod input_file;
mod input_media; mod input_media;
mod invoice; mod invoice;
@ -64,11 +72,12 @@ mod not_implemented_types;
mod order_info; mod order_info;
mod parse_mode; mod parse_mode;
mod photo_size; mod photo_size;
mod poll;
mod pre_checkout_query; mod pre_checkout_query;
mod response_parameters;
mod reply_markup;
mod reply_keyboard_markup; mod reply_keyboard_markup;
mod reply_keyboard_remove; mod reply_keyboard_remove;
mod reply_markup;
mod response_parameters;
mod send_invoice; mod send_invoice;
mod shipping_address; mod shipping_address;
mod shipping_option; mod shipping_option;

View file

@ -1,35 +1,5 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Location;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct InlineKeyboardMarkup;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct PassportData; pub struct PassportData;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Poll;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Animation;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Game;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Contact;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct VideoNote;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Venue;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Voice;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct MaskPosition;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct ChatMemberStatus; pub struct ChatMemberStatus;

View file

@ -69,7 +69,6 @@ pub enum ParseMode {
Markdown, Markdown,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

13
src/core/types/poll.rs Normal file
View file

@ -0,0 +1,13 @@
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Poll {
pub id: String,
pub question: String,
pub options: Vec<PollOption>,
pub is_closed: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct PollOption {
pub text: String,
pub voter_count: i32,
}

View file

@ -3,23 +3,24 @@ use crate::core::types::KeyboardButton;
/// This object represents a custom keyboard with reply options. /// This object represents a custom keyboard with reply options.
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct ReplyKeyboardMarkup { pub struct ReplyKeyboardMarkup {
/// Array of button rows, each represented by an Array of [`KeyboardButton`] /// Array of button rows, each represented by an Array of
/// objects /// [`KeyboardButton`] objects
pub keyboard: Vec<Vec<KeyboardButton>>, pub keyboard: Vec<Vec<KeyboardButton>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Optional. Requests clients to resize the keyboard vertically for optimal /// Optional. Requests clients to resize the keyboard vertically for
/// fit (e.g., make the keyboard smaller if there are just two rows of /// optimal fit (e.g., make the keyboard smaller if there are just two
/// buttons). Defaults to false, in which case the custom keyboard is always /// rows of buttons). Defaults to false, in which case the custom
/// of the same height as the app's standard keyboard. /// keyboard is always of the same height as the app's standard
/// keyboard.
pub resize_keyboard: Option<bool>, pub resize_keyboard: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Optional. Requests clients to hide the keyboard as soon as it's been /// Optional. Requests clients to hide the keyboard as soon as it's been
/// used. The keyboard will still be available, but clients will /// used. The keyboard will still be available, but clients will
/// automatically display the usual letter-keyboard in the chat the user /// automatically display the usual letter-keyboard in the chat the user
/// can press a special button in the input field to see the custom keyboard /// can press a special button in the input field to see the custom
/// again. Defaults to false. /// keyboard again. Defaults to false.
pub one_time_keyboard: Option<bool>, pub one_time_keyboard: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View file

@ -1,8 +1,5 @@
use crate::core::types::{ use crate::core::types::{
InlineKeyboardMarkup, ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
ForceReply,
}; };
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -12,9 +12,8 @@ mod tests {
#[test] #[test]
fn migrate_to_chat_id_deserialization() { fn migrate_to_chat_id_deserialization() {
let expected = ResponseParameters::MigrateToChatId(123456); let expected = ResponseParameters::MigrateToChatId(123456);
let actual: ResponseParameters = serde_json::from_str( let actual: ResponseParameters =
r#"{"migrate_to_chat_id":123456}"# serde_json::from_str(r#"{"migrate_to_chat_id":123456}"#).unwrap();
).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
@ -22,9 +21,8 @@ mod tests {
#[test] #[test]
fn retry_after_deserialization() { fn retry_after_deserialization() {
let expected = ResponseParameters::RetryAfter(123456); let expected = ResponseParameters::RetryAfter(123456);
let actual: ResponseParameters = serde_json::from_str( let actual: ResponseParameters =
r#"{"retry_after":123456}"# serde_json::from_str(r#"{"retry_after":123456}"#).unwrap();
).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }

View file

@ -0,0 +1,7 @@
use crate::core::types::PhotoSize;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct UserProfilePhotos {
pub total_count: u32,
pub photos: Vec<Vec<PhotoSize>>
}

View file

@ -1,6 +1,5 @@
use crate::core::types::PhotoSize; use crate::core::types::PhotoSize;
/// This object represents a video file. /// This object represents a video file.
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Video { pub struct Video {