Merge branch 'dev' into dispatcher

This commit is contained in:
Waffle Lapkin 2019-09-27 15:53:55 +03:00 committed by GitHub
commit e05a008053
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 528 additions and 51 deletions

View file

@ -17,3 +17,4 @@ bytes = "0.4.12"
log = "0.4.8"
futures-preview = "0.3.0-alpha.18"
pin-project = "0.4.0-alpha.7"
libc = "0.2.62"

View file

@ -2,12 +2,9 @@ use crate::requests::get_updates::GetUpdates;
use crate::{
bot::Bot,
requests::{
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage, get_file::GetFile, get_me::GetMe,
send_audio::SendAudio, send_location::SendLocation,
send_media_group::SendMediaGroup, send_message::SendMessage,
send_photo::SendPhoto,
stop_message_live_location::StopMessageLiveLocation, ChatId,
ChatId, EditMessageLiveLocation, ForwardMessage, GetFile, GetMe,
SendAudio, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
StopMessageLiveLocation,
},
types::{InputFile, InputMedia},
};

View file

@ -1,13 +1,15 @@
#![feature(termination_trait_lib)]
#[macro_use]
extern crate derive_more;
#[macro_use]
extern crate serde;
mod network;
mod errors;
pub mod bot;
pub mod dispatcher;
pub mod errors;
pub mod requests;
pub mod types;

View file

@ -1,10 +1,10 @@
use bytes::Buf;
use futures::StreamExt;
use reqwest::r#async::{Chunk, Client};
use tokio::{
io::{AsyncWrite, AsyncWriteExt},
stream::Stream,
};
use bytes::Buf;
use crate::{
network::{file_url, TELEGRAM_API_URL},
@ -23,7 +23,8 @@ where
let mut stream = download_file_stream(client, token, path).await?;
while let Some(chunk) = stream.next().await {
destination.write_all(chunk?.bytes()).await?;
let chunk = chunk?;
destination.write_all(chunk.bytes()).await?;
}
Ok(())

View file

@ -3,7 +3,7 @@ use reqwest::r#async::{multipart::Form, Client, Response};
use serde::{de::DeserializeOwned, Serialize};
use crate::{
network::{method_url, TELEGRAM_API_URL, TelegramResponse},
network::{method_url, TelegramResponse, TELEGRAM_API_URL},
requests::ResponseResult,
RequestError,
};

View file

@ -1,6 +1,7 @@
use crate::{
network,
requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::True
};
#[derive(Debug, Serialize, Clone)]
@ -32,7 +33,7 @@ pub struct AnswerPreCheckoutQuery<'a> {
}
impl<'a> Request<'a> for AnswerPreCheckoutQuery<'a> {
type ReturnValue = bool;
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {

View file

@ -1,7 +1,7 @@
use crate::{
network,
requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::ShippingOption,
types::{ShippingOption, True},
};
#[derive(Debug, Clone, Serialize)]
@ -34,7 +34,7 @@ pub struct AnswerShippingQuery<'a> {
}
impl<'a> Request<'a> for AnswerShippingQuery<'a> {
type ReturnValue = bool;
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {

View file

@ -1,19 +1,73 @@
use crate::requests::RequestContext;
use crate::network;
use crate::requests::{Request, RequestContext, RequestFuture, ResponseResult};
use crate::types::UserProfilePhotos;
//TODO: complete implementation after user_profile_fotos will be added to
// types/mod.rs
///Use this method to get a list of profile pictures for a user. Returns a
/// UserProfilePhotos object.
#[derive(Debug, Clone, Serialize)]
struct GetUserProfilePhotos<'a> {
pub struct GetUserProfilePhotos<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Unique identifier of the target user
user_id: i32,
pub user_id: i32,
/// Sequential number of the first photo to be returned. By default, all
/// photos are returned.
offset: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<i64>,
///Limits the number of photos to be retrieved. Values between 1—100 are
/// accepted. Defaults to 100.
limit: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i64>,
}
impl<'a> Request<'a> for GetUserProfilePhotos<'a> {
type ReturnValue = UserProfilePhotos;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"getUserProfilePhotos",
&self,
)
.await
})
}
}
impl<'a> GetUserProfilePhotos<'a> {
pub fn new(ctx: RequestContext<'a>, user_id: i32) -> Self {
Self {
ctx,
user_id,
offset: None,
limit: None,
}
}
pub fn user_id<T>(mut self, user_id: T) -> Self
where
T: Into<i32>,
{
self.user_id = user_id.into();
self
}
pub fn offset<T>(mut self, offset: T) -> Self
where
T: Into<i64>,
{
self.offset = Some(offset.into());
self
}
pub fn limit<T>(mut self, limit: T) -> Self
where
T: Into<i64>,
{
self.limit = Some(limit.into());
self
}
}

View file

@ -1,12 +1,72 @@
use crate::requests::RequestContext;
//TODO:: need implementation
use crate::network;
use crate::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::types::True;
/// Use this method to kick a user from a group, a supergroup or a channel. In
/// the case of supergroups and channels, the user will not be able to return to
/// the group on their own using invite links, etc., unless unbanned first. The
/// bot must be an administrator in the chat for this to work and must have the
/// appropriate admin rights. Returns True on success.
#[derive(Debug, Clone, Serialize)]
struct KickChatMember<'a> {
pub struct KickChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
///Unique identifier for the target group or username of the target
/// supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
/// Unique identifier of the target user
pub user_id: i32,
///Date when the user will be unbanned, unix time. If user is banned for
/// more than 366 days or less than 30 seconds from the current time they
/// are considered to be banned forever
#[serde(skip_serializing_if = "Option::is_none")]
pub until_date: Option<u64>,
}
impl<'a> Request<'a> for KickChatMember<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
self.ctx.client,
self.ctx.token,
"kickChatMember",
&self,
)
.await
})
}
}
impl<'a> KickChatMember<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
user_id: i32,
) -> Self {
Self {
ctx,
chat_id,
user_id,
until_date: None,
}
}
pub fn chat_id<T: Into<ChatId>>(mut self, chat_id: T) -> Self {
self.chat_id = chat_id.into();
self
}
pub fn user_id<T: Into<i32>>(mut self, user_id: T) -> Self {
self.user_id = user_id.into();
self
}
pub fn until_date<T: Into<u64>>(mut self, until_date: T) -> Self {
self.until_date = Some(until_date.into());
self
}
}

View file

@ -7,6 +7,23 @@ use std::{future::Future, pin::Pin};
use crate::RequestError;
pub use self::{
answer_pre_checkout_query::AnswerPreCheckoutQuery,
answer_shipping_query::AnswerShippingQuery,
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage, get_chat::GetChat, get_file::GetFile,
get_me::GetMe, get_updates::GetUpdates,
get_user_profile_photos::GetUserProfilePhotos,
kick_chat_member::KickChatMember, pin_chat_message::PinChatMessage,
restrict_chat_member::RestrictChatMember,
send_audio::SendAudio, send_chat_action::SendChatAction,
send_contact::SendContact, send_location::SendLocation,
send_media_group::SendMediaGroup, send_message::SendMessage,
send_photo::SendPhoto, send_poll::SendPoll, send_venue::SendVenue,
stop_message_live_location::StopMessageLiveLocation,
unban_chat_member::UnbanChatMember,
};
pub type ResponseResult<T> = Result<T, RequestError>;
/// Request that can be sent to telegram.
@ -63,25 +80,26 @@ mod tests {
}
}
pub mod answer_pre_checkout_query;
pub mod answer_shipping_query;
pub mod edit_message_live_location;
pub mod forward_message;
pub mod get_chat;
pub mod get_file;
pub mod get_me;
pub mod get_updates;
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 unban_chat_member;
mod answer_pre_checkout_query;
mod answer_shipping_query;
mod edit_message_live_location;
mod forward_message;
mod get_chat;
mod get_file;
mod get_me;
mod get_updates;
mod get_user_profile_photos;
mod kick_chat_member;
mod pin_chat_message;
mod restrict_chat_member;
mod send_audio;
mod send_chat_action;
mod send_contact;
mod send_location;
mod send_media_group;
mod send_message;
mod send_photo;
mod send_poll;
mod send_venue;
mod stop_message_live_location;
mod unban_chat_member;

View file

@ -0,0 +1,50 @@
use crate::{
requests::{ChatId, RequestContext, RequestFuture, ResponseResult, Request},
types::True
};
use crate::network;
/// Use this method to get up to date information about the chat
/// (current name of the user for one-on-one conversations,
/// current username of a user, group or channel, etc.).
/// Returns a Chat object on success.
#[derive(Debug, Clone, Serialize)]
pub struct PinChatMessage<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
pub message_id: i32,
pub disable_notification: Option<bool>
}
impl<'a> PinChatMessage<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>, chat_id: ChatId, message_id: i32
) -> Self {
Self { ctx, chat_id, message_id, disable_notification: None }
}
pub fn disable_notification<T>(mut self, val: T) -> Self
where T: Into<bool>
{
self.disable_notification = Some(val.into());
self
}
}
impl<'a> Request<'a> for PinChatMessage<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"pinChatMessage",
&self,
).await
})
}
}

View file

@ -1,8 +1,92 @@
use crate::requests::RequestContext;
//TODO:: need implementation
use crate::network;
use crate::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::types::{ChatPermissions, True};
/// Use this method to restrict a user in a supergroup. The bot must be an
/// administrator in the supergroup for this to work and must have the
/// appropriate admin rights. Pass True for all permissions to lift restrictions
/// from a user. Returns True on success.
#[derive(Debug, Clone, Serialize)]
struct RestrictChatMember<'a> {
pub struct RestrictChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
///Unique identifier for the target chat or username of the target
/// supergroup (in the format @supergroupusername)
pub chat_id: ChatId,
///Unique identifier of the target user
pub user_id: i32,
///New user permissions
pub permissions: ChatPermissions,
///Date when restrictions will be lifted for the user, unix time. If user
/// is restricted for more than 366 days or less than 30 seconds from the
/// current time, they are considered to be restricted forever
#[serde(skip_serializing_if = "Option::is_none")]
pub until_date: Option<u64>,
}
impl<'a> Request<'a> for RestrictChatMember<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"restrictChatMember",
&self,
)
.await
})
}
}
impl<'a> RestrictChatMember<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
user_id: i32,
permissions: ChatPermissions,
) -> Self {
Self {
ctx,
chat_id,
user_id,
permissions,
until_date: 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 user_id<T>(mut self, user_id: T) -> Self
where
T: Into<i32>,
{
self.user_id = user_id.into();
self
}
pub fn permissions<T>(mut self, permissions: T) -> Self
where
T: Into<ChatPermissions>,
{
self.permissions = permissions.into();
self
}
pub fn until_date<T>(mut self, until_date: T) -> Self
where
T: Into<u64>,
{
self.until_date = Some(until_date.into());
self
}
}

View file

@ -3,6 +3,7 @@ use crate::{
requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::True
};
///Use this method when you need to tell the user that something is happening
@ -40,7 +41,7 @@ pub enum ChatAction {
}
impl<'a> Request<'a> for SendChatAction<'a> {
type ReturnValue = bool;
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {

View file

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

View file

@ -0,0 +1,35 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct EncryptedCredentials {
// TODO: check base64 type
pub data: String,
pub hash: String,
pub secret: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn must_serialize_encrypted_credentials_to_json() {
// given
let expected_json = r#"
{
"data":"someData",
"hash":"1122",
"secret":"secret"
}"#
.replace("\n", "")
.replace(" ", "");
let encrypted_credentials = EncryptedCredentials {
data: "someData".to_string(),
hash: "1122".to_string(),
secret: "secret".to_string(),
};
// when
let actual_json =
serde_json::to_string(&encrypted_credentials).unwrap();
//then
assert_eq!(actual_json, expected_json)
}
}

View file

@ -0,0 +1,67 @@
use super::passport_file::PassportFile;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct EncryptedPassportElement {
pub hash: String,
#[serde(flatten)]
pub kind: EncryptedPassportElementKind
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EncryptedPassportElementKind {
PersonalDetails {
data: String
},
Passport {
data: String,
front_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
DriverLicense {
data: String,
front_side: PassportFile,
reverse_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
IdentityCard {
data: String,
front_side: PassportFile,
reverse_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
InternalPassport {
data: String,
front_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
Address {
data: String
},
UtilityBill {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
BankStatement {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
RentalAgreement {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
PassportRegistration {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
TemporaryRegistration {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
PhoneNumber { phone_number: String },
Email { email: String }
}

View file

@ -15,7 +15,7 @@ pub struct Message {
}
impl Message {
fn text(&self) -> Option<&str> {
pub fn text(&self) -> Option<&str> {
if let MessageKind::Common {
media_kind: MediaKind::Text { ref text, .. },
..

View file

@ -11,6 +11,8 @@ pub use self::{
chosen_inline_result::ChosenInlineResult,
contact::Contact,
document::Document,
encrypted_credintials::EncryptedCredentials,
encrypted_passport_element::{EncryptedPassportElement, EncryptedPassportElementKind},
file::File,
force_reply::ForceReply,
game::Game,
@ -54,6 +56,8 @@ pub use self::{
message_entity::MessageEntity,
order_info::OrderInfo,
parse_mode::ParseMode,
passport_data::PassportData,
passport_file::PassportFile,
photo_size::PhotoSize,
poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery,
@ -68,6 +72,7 @@ pub use self::{
sticker::Sticker,
sticker_set::StickerSet,
successful_payment::SuccessfulPayment,
unit_true::True,
update::{Update, UpdateKind},
user::User,
user_profile_photos::UserProfilePhotos,
@ -123,6 +128,7 @@ mod shipping_query;
mod sticker;
mod sticker_set;
mod successful_payment;
mod unit_true;
mod update;
mod user;
mod user_profile_photos;
@ -154,3 +160,8 @@ mod inline_query_result_photo;
mod inline_query_result_venue;
mod inline_query_result_video;
mod inline_query_result_voice;
mod encrypted_credintials;
mod encrypted_passport_element;
mod passport_data;
mod passport_file;

View file

@ -0,0 +1,8 @@
use super::encrypted_credintials::EncryptedCredentials;
use super::encrypted_passport_element::EncryptedPassportElement;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct PassportData {
pub data: Vec<EncryptedPassportElement>,
pub credentials: EncryptedCredentials,
}

View file

@ -0,0 +1,6 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct PassportFile {
pub file_id: String,
pub file_size: u64,
pub file_date: u64,
}

81
src/types/unit_true.rs Normal file
View file

@ -0,0 +1,81 @@
use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde::ser::{Serialize, Serializer};
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct True;
impl std::process::Termination for True {
fn report(self) -> i32 {
libc::EXIT_SUCCESS
}
}
impl std::convert::TryFrom<bool> for True {
type Error = ();
fn try_from(value: bool) -> Result<Self, Self::Error> {
match value {
true => Ok(True),
false => Err(())
}
}
}
impl<'de> Deserialize<'de> for True {
fn deserialize<D>(des: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
des.deserialize_bool(TrueVisitor)
}
}
struct TrueVisitor;
impl<'de> Visitor<'de> for TrueVisitor {
type Value = True;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "bool, equal to `true`")
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: de::Error
{
match value {
true => Ok(True),
false => Err(E::custom("expected `true`, found `false`"))
}
}
}
impl Serialize for True {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(true)
}
}
#[cfg(test)]
mod tests {
use super::True;
use serde_json::{from_str, to_string};
#[test]
fn unit_true_de() {
let json = "true";
let expected = True;
let actual = from_str(json).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn unit_true_se() {
let actual = to_string(&True).unwrap();
let expected = "true";
assert_eq!(expected, actual);
}
}