Merge branch 'dev' into dispatcher

This commit is contained in:
Waffle Lapkin 2019-09-17 18:51:14 +03:00 committed by GitHub
commit 43992ec378
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1085 additions and 221 deletions

View file

@ -5,3 +5,8 @@
<img src="https://travis-ci.com/async-telegram-bot/async-telegram-bot.svg?branch=dev" /> <img src="https://travis-ci.com/async-telegram-bot/async-telegram-bot.svg?branch=dev" />
</a> </a>
</div> </div>
## Dependency graph
<div align="center">
<img src="https://github.com/async-telegram-bot/async-telegram-bot/blob/dev/graph.png" />
</div>

BIN
graph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View file

@ -1,7 +1,24 @@
use reqwest::r#async::Client; use reqwest::r#async::Client;
use crate::core::requests::{ use crate::core::{
get_me::GetMe, send_message::SendMessage, ChatId, RequestContext, types::{
InputFile,
InputMedia,
},
requests::{
ChatId,
RequestContext,
get_me::GetMe,
send_message::SendMessage,
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage,
send_audio::SendAudio,
send_location::SendLocation,
send_media_group::SendMediaGroup,
send_photo::SendPhoto,
stop_message_live_location::StopMessageLiveLocation,
}
}; };
pub struct Bot { pub struct Bot {
@ -23,15 +40,19 @@ impl Bot {
client, client,
} }
} }
fn ctx(&self) -> RequestContext {
RequestContext {
token: &self.token,
client: &self.client,
}
}
} }
/// Telegram functions /// Telegram functions
impl Bot { impl Bot {
pub fn get_me(&self) -> GetMe { pub fn get_me(&self) -> GetMe {
GetMe::new(RequestContext { GetMe::new(self.ctx())
token: &self.token,
client: &self.client,
})
} }
pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage
@ -40,12 +61,105 @@ impl Bot {
T: Into<String>, T: Into<String>,
{ {
SendMessage::new( SendMessage::new(
RequestContext { self.ctx(),
token: &self.token,
client: &self.client,
},
chat_id.into(), chat_id.into(),
text.into(), text.into(),
) )
} }
pub fn edit_message_live_location<Lt, Lg>(
&self,
latitude: Lt,
longitude: Lg,
) -> EditMessageLiveLocation
where
Lt: Into<f64>,
Lg: Into<f64>,
{
EditMessageLiveLocation::new(
self.ctx(),
latitude.into(),
longitude.into(),
)
}
pub fn forward_message<C, F, M>(
&self,
chat_id: C,
from_chat_id: F,
message_id: M
) -> ForwardMessage
where
C: Into<ChatId>,
F: Into<ChatId>,
M: Into<i64>,
{
ForwardMessage::new(
self.ctx(),
chat_id.into(),
from_chat_id.into(),
message_id.into(),
)
}
pub fn send_audio<C, A>(&self, chat_id: C, audio: A) -> SendAudio
where
C: Into<ChatId>,
A: Into<InputFile>,
{
SendAudio::new(
self.ctx(),
chat_id.into(),
audio.into()
)
}
pub fn send_location<C, Lt, Lg>(
&self,
chat_id: C,
latitude: Lt,
longitude: Lg,
) -> SendLocation
where
C: Into<ChatId>,
Lt: Into<f64>,
Lg: Into<f64>,
{
SendLocation::new(
self.ctx(),
chat_id.into(),
latitude.into(),
longitude.into(),
)
}
pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup
where
C: Into<ChatId>,
M: Into<Vec<InputMedia>>
{
SendMediaGroup::new(
self.ctx(),
chat_id.into(),
media.into(),
)
}
pub fn send_photo<C, P>(&self, chat_id: C, photo: P) -> SendPhoto
where
C: Into<ChatId>,
P: Into<InputFile>
{
SendPhoto::new(
self.ctx(),
chat_id.into(),
photo.into(),
)
}
pub fn stop_message_live_location(&self) -> StopMessageLiveLocation {
StopMessageLiveLocation::new(
self.ctx()
)
}
} }

View file

@ -9,7 +9,6 @@ use reqwest::{
StatusCode, StatusCode,
}; };
use serde::{de::DeserializeOwned, Serialize}; 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";
@ -63,7 +62,7 @@ pub async fn request_multipart<T: DeserializeOwned>(
TelegramResponse::Err { TelegramResponse::Err {
description, description,
error_code, error_code,
response_parameters, response_parameters: _,
.. ..
} => Err(RequestError::ApiError { } => Err(RequestError::ApiError {
description, description,
@ -95,7 +94,7 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
TelegramResponse::Err { TelegramResponse::Err {
description, description,
error_code, error_code,
response_parameters, response_parameters: _,
.. ..
} => Err(RequestError::ApiError { } => Err(RequestError::ApiError {
description, description,
@ -108,11 +107,17 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
#[serde(untagged)] #[serde(untagged)]
enum TelegramResponse<R> { enum TelegramResponse<R> {
Ok { Ok {
ok: bool, // true /// Dummy field. Used for deserialization.
#[allow(dead_code)]
ok: bool, // TODO: True type
result: R, result: R,
}, },
Err { Err {
ok: bool, // false /// Dummy field. Used for deserialization.
#[allow(dead_code)]
ok: bool, // TODO: False type
description: String, description: String,
error_code: u16, error_code: u16,
response_parameters: Option<ResponseParameters>, response_parameters: Option<ResponseParameters>,

View file

@ -0,0 +1,83 @@
use crate::core::{
requests::{RequestContext, Request, RequestFuture, ResponseResult},
network
};
#[derive(Debug, Serialize, Clone)]
/// Once the user has confirmed their payment and shipping details, the Bot API
/// sends the final confirmation in the form of an [`Update`] with the field
/// pre_checkout_query. Use this method to respond to such pre-checkout queries.
/// On success, True is returned. Note: The Bot API must receive an answer
/// within 10 seconds after the pre-checkout query was sent.
pub struct AnswerPreCheckoutQuery<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Unique identifier for the query to be answered
pub pre_checkout_query_id: String,
/// Specify True if everything is alright (goods are available, etc.) and
/// the bot is ready to proceed with the order. Use False if there are any
/// problems.
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
/// Required if ok is False. Error message in human readable form that
/// explains the reason for failure to proceed with the checkout (e.g.
/// "Sorry, somebody just bought the last of our amazing black T-shirts
/// while you were busy filling out your payment details. Please choose a
/// different color or garment!"). Telegram will display this message to
/// the user.
pub error_message: Option<String>,
}
impl<'a> Request<'a> for AnswerPreCheckoutQuery<'a> {
type ReturnValue = bool;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"answerPreCheckoutQuery",
&self
).await
})
}
}
impl<'a> AnswerPreCheckoutQuery<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
pre_checkout_query_id: String,
ok: bool
) -> Self {
Self {
ctx,
pre_checkout_query_id,
ok,
error_message: None
}
}
pub fn pre_checkout_query_id<T>(mut self, pre_checkout_query_id: T) -> Self
where T: Into<String>
{
self.pre_checkout_query_id = pre_checkout_query_id.into();
self
}
pub fn ok<T>(mut self, ok: T) -> Self
where T: Into<bool>
{
self.ok = ok.into();
self
}
pub fn error_message<T>(mut self, error_message: T) -> Self
where T: Into<String>
{
self.error_message = Some(error_message.into());
self
}
}

View file

@ -0,0 +1,91 @@
use crate::core::types::ShippingOption;
use crate::core::requests::{RequestContext, Request, RequestFuture, ResponseResult};
use crate::core::network;
#[derive(Debug, Clone, Serialize)]
/// If you sent an invoice requesting a shipping address and the parameter
/// is_flexible was specified, the Bot API will send an [`Update`] with a
/// shipping_query field to the bot. Use this method to reply to shipping
/// queries. On success, True is returned.
pub struct AnswerShippingQuery<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Unique identifier for the query to be answered
pub shipping_query_id: String,
/// Specify True if delivery to the specified address is possible and False
/// if there are any problems (for example, if delivery to the specified
/// address is not possible)
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
/// Required if ok is True. A JSON-serialized array of available shipping
/// options.
pub shipping_options: Option<Vec<ShippingOption>>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Required if ok is False. Error message in human readable form that
/// explains why it is impossible to complete the order (e.g. "Sorry,
/// delivery to your desired address is unavailable'). Telegram will
/// display this message to the user.
pub error_message: Option<String>,
}
impl<'a> Request<'a> for AnswerShippingQuery<'a> {
type ReturnValue = bool;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"answerShippingQuery",
&self
).await
})
}
}
impl<'a> AnswerShippingQuery<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
shipping_query_id: String,
ok: bool
) -> Self {
Self {
ctx,
shipping_query_id,
ok,
shipping_options: None,
error_message: None
}
}
pub fn shipping_query_id<T>(mut self, shipping_query_id: T) -> Self
where T: Into<String>
{
self.shipping_query_id = shipping_query_id.into();
self
}
pub fn ok<T>(mut self, ok: T) -> Self
where T: Into<bool>
{
self.ok = ok.into();
self
}
pub fn shipping_options<T>(mut self, shipping_options: T) -> Self
where T: Into<Vec<ShippingOption>>
{
self.shipping_options = Some(shipping_options.into());
self
}
pub fn error_message<T>(mut self, error_message: T) -> Self
where T: Into<String>
{
self.error_message = Some(error_message.into());
self
}
}

View file

@ -6,7 +6,6 @@ use crate::core::{
}; };
use reqwest::r#async::multipart::Form; use reqwest::r#async::multipart::Form;
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.

View file

@ -1,8 +1,11 @@
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{
form_builder::FormBuilder, ChatId, Request, RequestContext, ChatId,
RequestFuture, ResponseResult, Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::Message, types::Message,
}; };

View file

@ -89,6 +89,8 @@ mod tests {
} }
} }
pub mod answer_pre_checkout_query;
pub mod answer_shipping_query;
pub mod edit_message_live_location; pub mod edit_message_live_location;
pub mod forward_message; pub mod forward_message;
pub mod get_file; pub mod get_file;

View file

@ -1,8 +1,83 @@
use crate::core::requests::RequestContext; use crate::core::network;
//TODO:: need implementation use crate::core::requests::{ChatId, Request, RequestContext, RequestFuture, ResponseResult};
use crate::core::types::Message;
///Use this method when you need to tell the user that something is happening on the bot's side.
///The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status).
///Returns True on success.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendChatAction<'a> { struct SendChatAction<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
pub chat_id: ChatId,
/// Type of action to broadcast. Choose one, depending on what the user is
/// about to receive: typing for text messages, upload_photo for photos,
/// record_video or upload_video for videos, record_audio or upload_audio
/// for audio files, upload_document for general files, find_location for
/// location data, record_video_note or upload_video_note for video notes.
pub action: ChatAction,
} }
#[derive(Debug, Serialize, From, Clone)]
#[serde(rename_all = "snake_case")]
enum ChatAction {
Typing,
UploadPhoto,
RecordVideo,
UploadVideo,
RecordAudio,
UploadAudio,
UploadDocument,
FindLocation,
RecordVideoNote,
UploadVideoNote,
}
impl<'a> Request<'a> for SendChatAction<'a> {
type ReturnValue = bool;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"sendChatAction",
&self,
)
.await
})
}
}
impl<'a> SendChatAction<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
action: ChatAction,
) -> Self {
Self {
ctx,
chat_id,
action,
}
}
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn action<T>(mut self, action: T) -> Self
where
T: Into<ChatAction>,
{
self.action = action.into();
self
}
}

View file

@ -1,7 +1,142 @@
use crate::core::requests::RequestContext; use crate::core::network;
//TODO:: need implementation use crate::core::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::core::types::{Message, ReplyMarkup};
/// Use this method to send phone contacts.
/// returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendContact<'a> { struct SendContact<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
pub chat_id: ChatId,
/// Contact's phone number
pub phone_number: String,
/// Contact's first name
pub first_name: String,
/// Contact's last name
#[serde(skip_serializing_if = "Option::is_none")]
pub last_name: Option<String>,
/// Additional data about the contact in the form of a
/// vCard, 0-2048 bytes
#[serde(skip_serializing_if = "Option::is_none")]
pub vcard: Option<String>,
/// Sends the message silently. Users will receive a
/// notification with no sound.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original
/// message
#[serde(skip_serializing_if = "Option::is_none")]
pub 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 keyboard or to force a reply from the user.
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<ReplyMarkup>,
}
impl<'a> Request<'a> for SendContact<'a> {
type ReturnValue = Message;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"sendContact",
&self,
)
.await
})
}
}
impl<'a> SendContact<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
phone_number: String,
first_name: String,
) -> Self {
Self {
ctx,
chat_id,
phone_number,
first_name,
last_name: None,
vcard: None,
disable_notification: None,
reply_to_message_id: None,
reply_markup: None,
}
}
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn phone_number<T>(mut self, phone_number: T) -> Self
where
T: Into<String>,
{
self.phone_number = phone_number.into();
self
}
pub fn first_name<T>(mut self, first_name: T) -> Self
where
T: Into<String>,
{
self.first_name = first_name.into();
self
}
pub fn last_name<T>(mut self, last_name: T) -> Self
where
T: Into<String>,
{
self.last_name = Some(last_name.into());
self
}
pub fn vcard<T>(mut self, vcard: T) -> Self
where
T: Into<String>,
{
self.vcard = Some(vcard.into());
self
}
pub fn disable_notification<T>(mut self, disable_notification: T) -> Self
where
T: Into<bool>,
{
self.disable_notification = Some(disable_notification.into());
self
}
pub fn reply_to_message_id<T>(mut self, reply_to_message_id: T) -> Self
where
T: Into<i32>,
{
self.reply_to_message_id = Some(reply_to_message_id.into());
self
}
pub fn reply_markup<T>(mut self, reply_markup: T) -> Self
where
T: Into<ReplyMarkup>,
{
self.reply_markup = Some(reply_markup.into());
self
}
} }

View file

@ -1,8 +1,11 @@
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{
form_builder::FormBuilder, ChatId, Request, RequestContext, ChatId,
RequestFuture, ResponseResult, Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::{Message, ParseMode, ReplyMarkup}, types::{Message, ParseMode, ReplyMarkup},
}; };

View file

@ -1,5 +1,3 @@
use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{

View file

@ -1,8 +1,114 @@
use crate::core::requests::RequestContext; use crate::core::network;
//TODO:: need implementation use crate::core::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::core::types::{Message, ReplyMarkup};
/// Use this method to send a native poll. A native poll can't be sent to a
/// private chat. On success, the sent Message is returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendPoll<'a> { struct SendPoll<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// identifier for the target chat or username of the target channel (in
/// the format @channelusername). A native poll can't be sent to a private
/// chat.
chat_id: ChatId,
/// Poll question, 1-255 characters
question: String,
/// List of answer options, 2-10 strings 1-100 characters each
options: Vec<String>,
/// Sends the message silently. Users will receive a notification with no
/// sound.
disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message
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.
reply_markup: Option<ReplyMarkup>,
}
impl<'a> Request<'a> for SendPoll<'a> {
type ReturnValue = Message;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"sendPoll",
&self,
)
.await
})
}
}
impl<'a> SendPoll<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
question: String,
options: Vec<String>,
) -> Self {
Self {
ctx,
chat_id,
question,
options,
disable_notification: None,
reply_to_message_id: None,
reply_markup: None,
}
}
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn question<T>(mut self, question: T) -> Self
where
T: Into<String>,
{
self.question = question.into();
self
}
pub fn options<T>(mut self, options: T) -> Self
where
T: Into<Vec<String>>,
{
self.options = options.into();
self
}
pub fn disable_notification<T>(mut self, disable_notification: T) -> Self
where
T: Into<bool>,
{
self.disable_notification = Some(disable_notification.into());
self
}
pub fn reply_to_message_id<T>(mut self, reply_to_message_id: T) -> Self
where
T: Into<i32>,
{
self.reply_to_message_id = Some(reply_to_message_id.into());
self
}
pub fn reply_markup<T>(mut self, reply_markup: T) -> Self
where
T: Into<ReplyMarkup>,
{
self.reply_markup = Some(reply_markup.into());
self
}
} }

View file

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

View file

@ -1,19 +1,20 @@
use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{
form_builder::FormBuilder, ChatId, Request, RequestContext, ChatId,
RequestFuture, ResponseResult, Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::{InlineKeyboardMarkup, Message, ParseMode}, types::{InlineKeyboardMarkup, Message},
}; };
/// Use this method to stop updating a live location message before live_period /// Use this method to stop updating a live location message before live_period
/// expires. On success, if the message was sent by the bot, the sent Message is /// expires. On success, if the message was sent by the bot, the sent Message is
/// returned, otherwise True is returned. /// returned, otherwise True is returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct StopMessageLiveLocation<'a> { pub struct StopMessageLiveLocation<'a> {
#[serde(skip_serializing)] #[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
@ -52,7 +53,7 @@ impl<'a> Request<'a> for StopMessageLiveLocation<'a> {
} }
impl<'a> StopMessageLiveLocation<'a> { impl<'a> StopMessageLiveLocation<'a> {
fn new(ctx: RequestContext<'a>) -> Self { pub(crate) fn new(ctx: RequestContext<'a>) -> Self {
Self { Self {
ctx, ctx,
chat_id: None, chat_id: None,

View file

@ -1,9 +1,12 @@
use std::path::PathBuf;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use reqwest::r#async::multipart::Part; use reqwest::r#async::multipart::Part;
use std::fs::File; use tokio::{
use std::path::PathBuf; prelude::*,
use tokio::codec::FramedRead; codec::FramedRead,
use tokio::prelude::*; };
struct FileDecoder; struct FileDecoder;

View file

@ -1,13 +1,63 @@
use crate::core::types::PhotoSize; use crate::core::types::PhotoSize;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video
/// without sound).
pub struct Animation { pub struct Animation {
/// Identifier for this file
pub file_id: String, pub file_id: String,
/// Video width as defined by sender
pub width: u32, pub width: u32,
/// Video height as defined by sender
pub height: u32, pub height: u32,
/// Duration of the video in seconds as defined by sender
pub duration: u32, pub duration: u32,
pub thumb: PhotoSize, /// Optional. Animation thumbnail as defined by sender
pub thumb: Option<PhotoSize>,
/// Optional. Original animation filename as defined by sender
pub file_name: Option<String>, pub file_name: Option<String>,
/// Optional. MIME type of the file as defined by sender
pub mime_type: Option<String>, pub mime_type: Option<String>,
/// Optional. File size
pub file_size: Option<u32> pub file_size: Option<u32>
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize() {
let json = r#"{
"file_id":"id",
"width":320,
"height":320,
"duration":59,
"thumb":{
"file_id":"id",
"width":320,
"height":320,
"file_size":3452
},
"file_name":"some",
"mime_type":"gif",
"file_size":6500}"#;
let expected = Animation {
file_id: "id".to_string(),
width: 320,
height: 320,
duration: 59,
thumb: Some(PhotoSize {
file_id: "id".to_string(),
width: 320,
height: 320,
file_size: Some(3452)
}),
file_name: Some("some".to_string()),
mime_type: Some("gif".to_string()),
file_size: Some(6500)
};
let actual = serde_json::from_str::<Animation>(json).unwrap();
assert_eq!(actual, expected)
}
}

View file

@ -1,7 +0,0 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct AnswerPreCheckoutQuery {
pub pre_checkout_query_id: String,
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
}

View file

@ -1,11 +0,0 @@
use crate::core::types::ShippingOption;
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct AnswerShippingQuery {
pub shipping_query_id: String,
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_options: Option<Vec<ShippingOption>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_message: Option<String>,
}

View file

@ -40,3 +40,54 @@ pub enum InlineKeyboardButtonKind {
/* CallbackGame(CallbackGame), TODO: разобраться, что с этим делать /* CallbackGame(CallbackGame), TODO: разобраться, что с этим делать
* TODO: add LoginUrl, pay */ * TODO: add LoginUrl, pay */
} }
/// Build buttons
///
/// Example:
/// ```edition2018
/// use async_telegram_bot::core::types::InlineKeyboardButton;
///
/// fn main() {
/// let url_button = InlineKeyboardButton::url(
/// "Text".to_string(),
/// "http://url.com".to_string(),
/// );
/// }
/// ```
impl InlineKeyboardButton {
pub fn url(text: String, url: String) -> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::Url(url),
}
}
pub fn callback(text: String, callback_data: String)
-> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::CallbackData(callback_data),
}
}
pub fn switch_inline_query(text: String, switch_inline_query: String)
-> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query)
}
}
pub fn switch_inline_query_current_chat(
text: String,
switch_inline_query_current_chat: String
) -> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat(
switch_inline_query_current_chat
)
}
}
}

View file

@ -11,3 +11,111 @@ pub struct InlineKeyboardMarkup {
/// [`InlineKeyboardButton`] objects /// [`InlineKeyboardButton`] objects
pub inline_keyboard: Vec<Vec<InlineKeyboardButton>>, pub inline_keyboard: Vec<Vec<InlineKeyboardButton>>,
} }
/// Build Markup
///
/// Example:
/// ```edition2018
/// use async_telegram_bot::core::types::{
/// InlineKeyboardMarkup,
/// InlineKeyboardButton
/// };
///
/// fn main() {
/// let url_button = InlineKeyboardButton::url(
/// "text".to_string(),
/// "http://url.com".to_string()
/// );
/// let keyboard = InlineKeyboardMarkup::new()
/// .row(vec![url_button]);
/// }
/// ```
impl InlineKeyboardMarkup {
pub fn new() -> Self {
Self {
inline_keyboard: vec![]
}
}
pub fn append_row(mut self, buttons: Vec<InlineKeyboardButton>) -> Self {
self.inline_keyboard.push(buttons);
self
}
pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize)
-> Self {
match self.inline_keyboard.get_mut(index) {
Some(buttons) => buttons.push(button),
None => self.inline_keyboard.push(vec![button])
};
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn append_row() {
let button1 = InlineKeyboardButton::url(
"text 1".to_string(),
"url 1".to_string(),
);
let button2 = InlineKeyboardButton::url(
"text 2".to_string(),
"url 2".to_string(),
);
let markup = InlineKeyboardMarkup::new()
.append_row(vec![button1.clone(), button2.clone()]);
let expected = InlineKeyboardMarkup {
inline_keyboard: vec![
vec![button1.clone(), button2.clone()]
]
};
assert_eq!(markup, expected);
}
#[test]
fn append_to_row__existent_row() {
let button1 = InlineKeyboardButton::url(
"text 1".to_string(),
"url 1".to_string(),
);
let button2 = InlineKeyboardButton::url(
"text 2".to_string(),
"url 2".to_string(),
);
let markup = InlineKeyboardMarkup::new()
.append_row(vec![button1.clone()])
.append_to_row(button2.clone(), 0);
let expected = InlineKeyboardMarkup {
inline_keyboard: vec![
vec![button1.clone(), button2.clone()]
]
};
assert_eq!(markup, expected);
}
#[test]
fn append_to_row__nonexistent_row() {
let button1 = InlineKeyboardButton::url(
"text 1".to_string(),
"url 1".to_string(),
);
let button2 = InlineKeyboardButton::url(
"text 2".to_string(),
"url 2".to_string(),
);
let markup = InlineKeyboardMarkup::new()
.append_row(vec![button1.clone()])
.append_to_row(button2.clone(), 1);
let expected = InlineKeyboardMarkup {
inline_keyboard: vec![
vec![button1.clone()],
vec![button2.clone()]
]
};
assert_eq!(markup, expected);
}
}

View file

@ -1,5 +1,29 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize)]
/// This object represents a portion of the price for goods or services.
pub struct LabeledPrice { pub struct LabeledPrice {
/// Portion label
pub label: String, pub label: String,
/// Price of the product in the smallest units of the
/// [currency](https://core.telegram.org/bots/payments#supported-currencies)
/// (integer, not float/double). For example, for a price of US$ 1.45 pass
/// amount = 145. See the exp parameter in [`currencies.json`](https://core.telegram.org/bots/payments/currencies.json),
/// it shows the number of digits past the decimal point for each currency
/// (2 for the majority of currencies).
pub amount: i64, pub amount: i64,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let labeled_price = LabeledPrice {
label: "Label".to_string(),
amount: 60
};
let expected = r#"{"label":"Label","amount":60}"#;
let actual = serde_json::to_string(&labeled_price).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -1,7 +1,5 @@
use self::not_implemented_types::*; use self::not_implemented_types::*;
pub use self::{ pub use self::{
answer_pre_checkout_query::AnswerPreCheckoutQuery,
answer_shipping_query::AnswerShippingQuery,
animation::Animation, animation::Animation,
audio::Audio, audio::Audio,
callback_query::CallbackQuery, callback_query::CallbackQuery,
@ -51,8 +49,6 @@ pub use self::{
}; };
mod animation; mod animation;
mod answer_pre_checkout_query;
mod answer_shipping_query;
mod audio; mod audio;
mod callback_query; mod callback_query;
mod chat; mod chat;

View file

@ -1,7 +1,32 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
/// This object represents one size of a photo or a [`Document`] /
/// [`Sticker`] thumbnail.
pub struct PhotoSize { pub struct PhotoSize {
/// Identifier for this file
pub file_id: String, pub file_id: String,
/// Photo width
pub width: i32, pub width: i32,
/// Photo height
pub height: i32, pub height: i32,
/// Optional. File size
pub file_size: Option<u32>, pub file_size: Option<u32>,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize() {
let json = r#"{"file_id":"id","width":320,"height":320,
"file_size":3452}"#;
let expected = PhotoSize {
file_id: "id".to_string(),
width: 320,
height: 320,
file_size: Some(3452)
};
let actual = serde_json::from_str::<PhotoSize>(json).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -1,6 +1,6 @@
use crate::core::types::{InlineKeyboardMarkup, LabeledPrice}; use crate::core::types::{InlineKeyboardMarkup, LabeledPrice};
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct SendInvoice { pub struct SendInvoice {
pub chat_id: i64, pub chat_id: i64,
pub title: String, pub title: String,

View file

@ -1,8 +1,31 @@
use crate::core::types::LabeledPrice; use crate::core::types::LabeledPrice;
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize)]
/// This object represents one shipping option.
pub struct ShippingOption { pub struct ShippingOption {
pub id: i64, /// Shipping option identifier
pub id: String,
/// Option title
pub title: String, pub title: String,
/// List of price portions
pub prices: Vec<LabeledPrice>, pub prices: Vec<LabeledPrice>,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let shipping_option = ShippingOption {
id: "0".to_string(),
title: "Option".to_string(),
prices: vec![
LabeledPrice { label: "Label".to_string(), amount: 60 }
]
};
let expected = r#"{"id":"0","title":"Option","prices":[{"label":"Label","amount":60}]}"#;
let actual = serde_json::to_string(&shipping_option).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -1,55 +0,0 @@
use crate::core::types::{InlineKeyboardButton, InlineKeyboardButtonKind};
pub struct InlineKeyboardButtonBuilder;
/// Build buttons
///
/// Example:
/// ```edition2018
/// use async_telegram_bot::keyboards::InlineKeyboardButtonBuilder;
///
/// fn main() {
/// let url_button = InlineKeyboardButtonBuilder::url(
/// "Text".to_string(),
/// "http://url.com".to_string(),
/// );
/// }
/// ```
impl InlineKeyboardButtonBuilder {
pub fn url(text: String, url: String) -> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::Url(url),
}
}
pub fn callback(text: String, callback_data: String)
-> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::CallbackData(callback_data),
}
}
pub fn switch_inline_query(text: String, switch_inline_query: String)
-> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query)
}
}
pub fn switch_inline_query_current_chat(
text: String,
switch_inline_query_current_chat: String
) -> InlineKeyboardButton {
InlineKeyboardButton {
text,
kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat(
switch_inline_query_current_chat
)
}
}
}

View file

@ -1,70 +0,0 @@
use crate::core::types::{InlineKeyboardMarkup, InlineKeyboardButton};
pub struct InlineKeyboardMarkupBuilder {
keyboard: InlineKeyboardMarkup,
}
/// Builder for [`InlineKeyboardMarkup`]
///
/// Example:
/// ```edition2018
/// use async_telegram_bot::keyboards;
///
/// fn main() {
/// let url_button = keyboards::InlineKeyboardButtonBuilder::url(
/// "text".to_string(),
/// "http://url.com".to_string()
/// );
/// let keyboard = keyboards::InlineKeyboardMarkupBuilder::new()
/// .row(vec![url_button])
/// .build();
/// }
/// ```
impl InlineKeyboardMarkupBuilder {
pub fn new() -> Self {
Self {
keyboard: InlineKeyboardMarkup {
inline_keyboard: vec![]
}
}
}
pub fn row(mut self, buttons: Vec<InlineKeyboardButton>) -> Self {
self.keyboard.inline_keyboard.push(buttons);
self
}
pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize)
-> Self {
match self.keyboard.inline_keyboard.get_mut(index) {
Some(buttons) => buttons.push(button),
None => self.keyboard.inline_keyboard.push(vec![button])
};
self
}
pub fn build(self) -> InlineKeyboardMarkup {
self.keyboard
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keyboards::InlineKeyboardButtonBuilder;
#[test]
fn test_row() {
let btn = InlineKeyboardButtonBuilder::url(
"text".to_string(),
"http://url".to_string(),
);
let kb = InlineKeyboardMarkupBuilder::new()
.row(vec![btn.clone()])
.build();
let expected = InlineKeyboardMarkup {
inline_keyboard: vec![vec![btn.clone()]],
};
assert_eq!(kb, expected);
}
}

View file

@ -1,7 +0,0 @@
mod inline_keyboard_button;
mod inline_keyboard_markup;
pub use self::{
inline_keyboard_button::InlineKeyboardButtonBuilder,
inline_keyboard_markup::InlineKeyboardMarkupBuilder,
};

View file

@ -5,5 +5,4 @@ extern crate serde;
pub mod bot; pub mod bot;
pub mod core; pub mod core;
pub mod keyboards;
pub mod dispatcher; pub mod dispatcher;