mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-03 09:49:07 +01:00
Merge branch 'dev' of github.com:async-telegram-bot/async-telegram-bot into dev
This commit is contained in:
commit
4d13609d33
123 changed files with 1239 additions and 602 deletions
|
@ -14,3 +14,4 @@ apply = "0.2.2"
|
|||
derive_more = "0.15.0"
|
||||
tokio = "0.2.0-alpha.4"
|
||||
bytes = "0.4.12"
|
||||
futures-preview = "0.3.0-alpha.18"
|
113
src/bot/api.rs
Normal file
113
src/bot/api.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use crate::{
|
||||
bot::Bot,
|
||||
requests::{
|
||||
ChatId, EditMessageLiveLocation, ForwardMessage, GetFile, GetMe,
|
||||
SendAudio, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
|
||||
StopMessageLiveLocation,
|
||||
},
|
||||
types::{InputFile, InputMedia},
|
||||
};
|
||||
|
||||
/// Telegram functions
|
||||
impl Bot {
|
||||
pub fn get_me(&self) -> GetMe {
|
||||
GetMe::new(self.ctx())
|
||||
}
|
||||
|
||||
pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage
|
||||
where
|
||||
C: Into<ChatId>,
|
||||
T: Into<String>,
|
||||
{
|
||||
SendMessage::new(self.ctx(), chat_id.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<i32>,
|
||||
{
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn get_file<F>(&self, file_id: F) -> GetFile
|
||||
where
|
||||
F: Into<String>,
|
||||
{
|
||||
GetFile::new(self.ctx(), file_id.into())
|
||||
}
|
||||
}
|
65
src/bot/download.rs
Normal file
65
src/bot/download.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use reqwest::r#async::Chunk;
|
||||
use tokio::{io::AsyncWrite, stream::Stream};
|
||||
|
||||
use crate::{
|
||||
bot::Bot,
|
||||
network::{download_file, download_file_stream},
|
||||
DownloadError,
|
||||
};
|
||||
|
||||
impl Bot {
|
||||
/// Download file from telegram into `destination`.
|
||||
/// `path` can be obtained from [`get_file`] method.
|
||||
///
|
||||
/// For downloading as Stream of Chunks see [`download_file_stream`].
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use async_telegram_bot::{
|
||||
/// bot::Bot, requests::Request, types::File as TgFile,
|
||||
/// };
|
||||
/// use tokio::fs::File;
|
||||
/// # use async_telegram_bot::RequestError;
|
||||
///
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let bot = Bot::new("TOKEN");
|
||||
/// let mut file = File::create("/home/waffle/Pictures/test.png").await?;
|
||||
///
|
||||
/// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?;
|
||||
/// bot.download_file(&file_path, &mut file).await?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
///
|
||||
/// [`get_file`]: crate::bot::Bot::get_file
|
||||
/// [`download_file_stream`]: crate::bot::Bot::download_file_stream
|
||||
pub async fn download_file<D>(
|
||||
&self,
|
||||
path: &str,
|
||||
destination: &mut D,
|
||||
) -> Result<(), DownloadError>
|
||||
where
|
||||
D: AsyncWrite + Unpin,
|
||||
{
|
||||
download_file(&self.client, &self.token, path, destination).await
|
||||
}
|
||||
|
||||
/// Download file from telegram.
|
||||
///
|
||||
/// `path` can be obtained from [`get_file`] method.
|
||||
///
|
||||
/// For downloading into [`AsyncWrite`] (e.g. [`tokio::fs::File`])
|
||||
/// see [`download_file`].
|
||||
///
|
||||
/// [`get_file`]: crate::bot::Bot::get_file
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`tokio::fs::File`]: tokio::fs::File
|
||||
/// [`download_file`]: crate::bot::Bot::download_file
|
||||
pub async fn download_file_stream(
|
||||
&self,
|
||||
path: &str,
|
||||
) -> Result<impl Stream<Item = Result<Chunk, reqwest::Error>>, reqwest::Error>
|
||||
{
|
||||
download_file_stream(&self.client, &self.token, path).await
|
||||
}
|
||||
}
|
115
src/bot/mod.rs
115
src/bot/mod.rs
|
@ -1,22 +1,16 @@
|
|||
mod api;
|
||||
mod download;
|
||||
|
||||
use reqwest::r#async::Client;
|
||||
|
||||
use crate::core::{
|
||||
requests::{
|
||||
edit_message_live_location::EditMessageLiveLocation,
|
||||
forward_message::ForwardMessage, 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,
|
||||
RequestContext,
|
||||
},
|
||||
types::{InputFile, InputMedia},
|
||||
};
|
||||
use crate::requests::RequestContext;
|
||||
|
||||
pub struct Bot {
|
||||
token: String,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
/// Constructors
|
||||
impl Bot {
|
||||
pub fn new(token: &str) -> Self {
|
||||
Bot {
|
||||
|
@ -31,7 +25,9 @@ impl Bot {
|
|||
client,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
fn ctx(&self) -> RequestContext {
|
||||
RequestContext {
|
||||
token: &self.token,
|
||||
|
@ -39,100 +35,3 @@ impl Bot {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Telegram functions
|
||||
impl Bot {
|
||||
pub fn get_me(&self) -> GetMe {
|
||||
GetMe::new(self.ctx())
|
||||
}
|
||||
|
||||
pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage
|
||||
where
|
||||
C: Into<ChatId>,
|
||||
T: Into<String>,
|
||||
{
|
||||
SendMessage::new(self.ctx(), chat_id.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<i32>,
|
||||
{
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
mod network;
|
||||
pub mod requests;
|
||||
pub mod types;
|
|
@ -1,158 +0,0 @@
|
|||
use crate::core::{
|
||||
requests::{RequestError, ResponseResult},
|
||||
types::ResponseParameters,
|
||||
};
|
||||
|
||||
use apply::Apply;
|
||||
use reqwest::{
|
||||
r#async::{multipart::Form, Client},
|
||||
StatusCode,
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
const TELEGRAM_API_URL: &str = "https://api.telegram.org";
|
||||
|
||||
/// Creates URL for making HTTPS requests. See the [Telegram documentation].
|
||||
///
|
||||
/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests
|
||||
fn method_url(base: &str, token: &str, method_name: &str) -> String {
|
||||
format!(
|
||||
"{url}/bot{token}/{method}",
|
||||
url = base,
|
||||
token = token,
|
||||
method = method_name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates URL for downloading a file. See the [Telegram documentation].
|
||||
///
|
||||
/// [Telegram documentation] (https://core.telegram.org/bots/api#file)
|
||||
fn file_url(base: &str, token: &str, file_path: &str) -> String {
|
||||
format!(
|
||||
"{url}/file/bot{token}/{file}",
|
||||
url = base,
|
||||
token = token,
|
||||
file = file_path,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn request_multipart<T: DeserializeOwned>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: Option<Form>,
|
||||
) -> ResponseResult<T> {
|
||||
let mut response = client
|
||||
.post(&method_url(TELEGRAM_API_URL, token, method_name))
|
||||
.apply(|request_builder| match params {
|
||||
Some(params) => request_builder.multipart(params),
|
||||
None => request_builder,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.map_err(RequestError::NetworkError)?;
|
||||
|
||||
let response = serde_json::from_str::<TelegramResponse<T>>(
|
||||
&response.text().await.map_err(RequestError::NetworkError)?,
|
||||
)
|
||||
.map_err(RequestError::InvalidJson)?;
|
||||
|
||||
match response {
|
||||
TelegramResponse::Ok { result, .. } => Ok(result),
|
||||
TelegramResponse::Err {
|
||||
description,
|
||||
error_code,
|
||||
response_parameters: _,
|
||||
..
|
||||
} => Err(RequestError::ApiError {
|
||||
description,
|
||||
status_code: StatusCode::from_u16(error_code).unwrap(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_json<T: DeserializeOwned, P: Serialize>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: &P,
|
||||
) -> ResponseResult<T> {
|
||||
let mut response = client
|
||||
.post(&method_url(TELEGRAM_API_URL, token, method_name))
|
||||
.json(params)
|
||||
.send()
|
||||
.await
|
||||
.map_err(RequestError::NetworkError)?;
|
||||
|
||||
let response = serde_json::from_str::<TelegramResponse<T>>(
|
||||
&response.text().await.map_err(RequestError::NetworkError)?,
|
||||
)
|
||||
.map_err(RequestError::InvalidJson)?;
|
||||
|
||||
match response {
|
||||
TelegramResponse::Ok { result, .. } => Ok(result),
|
||||
TelegramResponse::Err {
|
||||
description,
|
||||
error_code,
|
||||
response_parameters: _,
|
||||
..
|
||||
} => Err(RequestError::ApiError {
|
||||
description,
|
||||
status_code: StatusCode::from_u16(error_code).unwrap(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum TelegramResponse<R> {
|
||||
Ok {
|
||||
/// Dummy field. Used for deserialization.
|
||||
#[allow(dead_code)]
|
||||
ok: bool, // TODO: True type
|
||||
|
||||
result: R,
|
||||
},
|
||||
Err {
|
||||
/// Dummy field. Used for deserialization.
|
||||
#[allow(dead_code)]
|
||||
ok: bool, // TODO: False type
|
||||
|
||||
description: String,
|
||||
error_code: u16,
|
||||
response_parameters: Option<ResponseParameters>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn method_url_test() {
|
||||
let url = method_url(
|
||||
TELEGRAM_API_URL,
|
||||
"535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
|
||||
"methodName",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_url_test() {
|
||||
let url = file_url(
|
||||
TELEGRAM_API_URL,
|
||||
"535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
|
||||
"AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
use crate::core::requests::RequestContext;
|
||||
//TODO:: need implementation
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct RestrictChatMember<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
use crate::core::types::PhotoSize;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
|
||||
pub struct Audio {
|
||||
pub file_id: String,
|
||||
pub duration: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub performer: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file_size: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumb: Option<PhotoSize>,
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use crate::core::types::{Message, User};
|
||||
|
||||
/// This object represents an incoming callback query from a callback button in
|
||||
/// an inline keyboard.
|
||||
#[derive(Debug, Deserialize, PartialEq, Clone)]
|
||||
pub struct CallbackQuery {
|
||||
/// Unique identifier for this query
|
||||
pub id: String,
|
||||
/// Sender
|
||||
pub from: User,
|
||||
/// Message with the callback button that originated the query.
|
||||
/// Note that message content and message date will not be available if the
|
||||
/// message is too old
|
||||
pub message: Message,
|
||||
/// Global identifier, uniquely corresponding to the chat to which the
|
||||
/// message with the callback button was sent. Useful for high scores
|
||||
/// in games.
|
||||
pub chat_instance: String,
|
||||
/// Data associated with the callback button. Be aware that a bad client
|
||||
/// can send arbitrary data in this field.
|
||||
pub data: String,
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
|
||||
pub struct PassportData;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
|
||||
pub struct ChatMemberStatus;
|
|
@ -1,9 +0,0 @@
|
|||
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub is_bot: bool,
|
||||
pub first_name: String,
|
||||
pub last_name: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub language_code: Option<String>,
|
||||
}
|
60
src/errors.rs
Normal file
60
src/errors.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use reqwest::StatusCode;
|
||||
|
||||
//<editor-fold desc="download">
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum DownloadError {
|
||||
#[display(fmt = "Network error: {err}", err = _0)]
|
||||
NetworkError(reqwest::Error),
|
||||
|
||||
#[display(fmt = "IO Error: {err}", err = _0)]
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for DownloadError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
DownloadError::NetworkError(err) => Some(err),
|
||||
DownloadError::Io(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="request">
|
||||
#[derive(Debug, Display)]
|
||||
pub enum RequestError {
|
||||
#[display(fmt = "Telegram error #{}: {}", status_code, description)]
|
||||
ApiError {
|
||||
status_code: StatusCode,
|
||||
description: String,
|
||||
},
|
||||
|
||||
/// The group has been migrated to a supergroup with the specified
|
||||
/// identifier.
|
||||
#[display(fmt = "The group has been migrated to a supergroup with id {id}", id = _0)]
|
||||
MigrateToChatId(i64),
|
||||
|
||||
/// In case of exceeding flood control, the number of seconds left to wait
|
||||
/// before the request can be repeated
|
||||
#[display(fmt = "Retry after {secs} seconds", secs = _0)]
|
||||
RetryAfter(i32),
|
||||
|
||||
#[display(fmt = "Network error: {err}", err = _0)]
|
||||
NetworkError(reqwest::Error),
|
||||
|
||||
#[display(fmt = "InvalidJson error caused by: {err}", err = _0)]
|
||||
InvalidJson(serde_json::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for RequestError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
RequestError::ApiError { .. } => None,
|
||||
RequestError::MigrateToChatId(_) => None,
|
||||
RequestError::RetryAfter(_) => None,
|
||||
RequestError::NetworkError(err) => Some(err),
|
||||
RequestError::InvalidJson(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
|
@ -3,5 +3,11 @@ extern crate derive_more;
|
|||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
mod network;
|
||||
mod errors;
|
||||
|
||||
pub mod bot;
|
||||
pub mod core;
|
||||
pub mod requests;
|
||||
pub mod types;
|
||||
|
||||
pub use errors::{DownloadError, RequestError};
|
||||
|
|
41
src/network/download.rs
Normal file
41
src/network/download.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use bytes::Buf;
|
||||
use futures::StreamExt;
|
||||
use reqwest::r#async::{Chunk, Client};
|
||||
use tokio::{
|
||||
io::{AsyncWrite, AsyncWriteExt},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
network::{file_url, TELEGRAM_API_URL},
|
||||
DownloadError,
|
||||
};
|
||||
|
||||
pub async fn download_file<D>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
path: &str,
|
||||
destination: &mut D,
|
||||
) -> Result<(), DownloadError>
|
||||
where
|
||||
D: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut stream = download_file_stream(client, token, path).await?;
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
destination.write_all(chunk.bytes()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_file_stream(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
path: &str,
|
||||
) -> Result<impl Stream<Item = Result<Chunk, reqwest::Error>>, reqwest::Error> {
|
||||
let url = file_url(TELEGRAM_API_URL, token, path);
|
||||
let resp = client.get(&url).send().await?.error_for_status()?;
|
||||
Ok(resp.into_body())
|
||||
}
|
66
src/network/mod.rs
Normal file
66
src/network/mod.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
mod download;
|
||||
mod request;
|
||||
mod telegram_response;
|
||||
|
||||
pub use download::{download_file, download_file_stream};
|
||||
pub use request::{request_json, request_multipart};
|
||||
pub use telegram_response::TelegramResponse;
|
||||
|
||||
pub const TELEGRAM_API_URL: &str = "https://api.telegram.org";
|
||||
|
||||
/// Creates URL for making HTTPS requests. See the [Telegram documentation].
|
||||
///
|
||||
/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests
|
||||
fn method_url(base: &str, token: &str, method_name: &str) -> String {
|
||||
format!(
|
||||
"{url}/bot{token}/{method}",
|
||||
url = base,
|
||||
token = token,
|
||||
method = method_name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates URL for downloading a file. See the [Telegram documentation].
|
||||
///
|
||||
/// [Telegram documentation] (https://core.telegram.org/bots/api#file)
|
||||
fn file_url(base: &str, token: &str, file_path: &str) -> String {
|
||||
format!(
|
||||
"{url}/file/bot{token}/{file}",
|
||||
url = base,
|
||||
token = token,
|
||||
file = file_path,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn method_url_test() {
|
||||
let url = method_url(
|
||||
TELEGRAM_API_URL,
|
||||
"535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
|
||||
"methodName",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_url_test() {
|
||||
let url = file_url(
|
||||
TELEGRAM_API_URL,
|
||||
"535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
|
||||
"AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ"
|
||||
);
|
||||
}
|
||||
}
|
60
src/network/request.rs
Normal file
60
src/network/request.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use apply::Apply;
|
||||
use reqwest::r#async::{multipart::Form, Client, Response};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::{
|
||||
network::{method_url, TelegramResponse, TELEGRAM_API_URL},
|
||||
requests::ResponseResult,
|
||||
RequestError,
|
||||
};
|
||||
|
||||
pub async fn request_multipart<T>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: Option<Form>,
|
||||
) -> ResponseResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
process_response(
|
||||
client
|
||||
.post(&method_url(TELEGRAM_API_URL, token, method_name))
|
||||
.apply(|request_builder| match params {
|
||||
Some(params) => request_builder.multipart(params),
|
||||
None => request_builder,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.map_err(RequestError::NetworkError)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn request_json<T: DeserializeOwned, P: Serialize>(
|
||||
client: &Client,
|
||||
token: &str,
|
||||
method_name: &str,
|
||||
params: &P,
|
||||
) -> ResponseResult<T> {
|
||||
process_response(
|
||||
client
|
||||
.post(&method_url(TELEGRAM_API_URL, token, method_name))
|
||||
.json(params)
|
||||
.send()
|
||||
.await
|
||||
.map_err(RequestError::NetworkError)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn process_response<T: DeserializeOwned>(
|
||||
mut response: Response,
|
||||
) -> ResponseResult<T> {
|
||||
let response = serde_json::from_str::<TelegramResponse<T>>(
|
||||
&response.text().await.map_err(RequestError::NetworkError)?,
|
||||
)
|
||||
.map_err(RequestError::InvalidJson)?;
|
||||
|
||||
response.into()
|
||||
}
|
56
src/network/telegram_response.rs
Normal file
56
src/network/telegram_response.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use reqwest::StatusCode;
|
||||
|
||||
use crate::{
|
||||
requests::ResponseResult, types::ResponseParameters, RequestError,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum TelegramResponse<R> {
|
||||
Ok {
|
||||
/// A dummy field. Used only for deserialization.
|
||||
#[allow(dead_code)]
|
||||
ok: bool, // TODO: True type
|
||||
|
||||
result: R,
|
||||
},
|
||||
Err {
|
||||
/// A dummy field. Used only for deserialization.
|
||||
#[allow(dead_code)]
|
||||
ok: bool, // TODO: False type
|
||||
|
||||
description: String,
|
||||
error_code: u16,
|
||||
response_parameters: Option<ResponseParameters>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
|
||||
fn into(self) -> Result<R, RequestError> {
|
||||
match self {
|
||||
TelegramResponse::Ok { result, .. } => Ok(result),
|
||||
TelegramResponse::Err {
|
||||
description,
|
||||
error_code,
|
||||
response_parameters,
|
||||
..
|
||||
} => {
|
||||
if let Some(params) = response_parameters {
|
||||
match params {
|
||||
ResponseParameters::RetryAfter(i) => {
|
||||
Err(RequestError::RetryAfter(i))
|
||||
}
|
||||
ResponseParameters::MigrateToChatId(to) => {
|
||||
Err(RequestError::MigrateToChatId(to))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(RequestError::ApiError {
|
||||
description,
|
||||
status_code: StatusCode::from_u16(error_code).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{Request, RequestContext, RequestFuture, ResponseResult},
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
Request, RequestContext, RequestFuture, ResponseResult,
|
||||
use crate::{
|
||||
network,
|
||||
requests::{Request, RequestContext, RequestFuture, ResponseResult},
|
||||
types::ShippingOption,
|
||||
};
|
||||
use crate::core::types::ShippingOption;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
/// If you sent an invoice requesting a shipping address and the parameter
|
|
@ -1,9 +1,10 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
types::{Message, ReplyMarkup},
|
||||
};
|
||||
use crate::core::types::{Message, ReplyMarkup};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
/// Use this method to edit live location messages. A location can be edited
|
|
@ -1,12 +1,12 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::core::{
|
||||
use reqwest::r#async::multipart::Form;
|
||||
|
||||
use crate::{
|
||||
requests::{utils, ChatId},
|
||||
types::{InputMedia, ParseMode},
|
||||
};
|
||||
|
||||
use reqwest::r#async::multipart::Form;
|
||||
|
||||
/// This is a convenient struct that builds `reqwest::r#async::multipart::Form`
|
||||
/// from scratch.
|
||||
pub struct FormBuilder {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
|
@ -1,6 +1,10 @@
|
|||
use crate::core::requests::{ChatId, RequestContext, RequestFuture, ResponseResult, Request};
|
||||
use crate::core::types::Chat;
|
||||
use crate::core::network;
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
types::Chat,
|
||||
};
|
||||
|
||||
/// Use this method to get up to date information about the chat
|
||||
/// (current name of the user for one-on-one conversations,
|
||||
|
@ -15,7 +19,6 @@ pub struct GetChat<'a> {
|
|||
chat_id: ChatId,
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Request<'a> for GetChat<'a> {
|
||||
type ReturnValue = Chat;
|
||||
|
||||
|
@ -26,12 +29,12 @@ impl<'a> Request<'a> for GetChat<'a> {
|
|||
&self.ctx.token,
|
||||
"getChat",
|
||||
&self,
|
||||
).await
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> GetChat<'a> {
|
||||
pub fn chat_id<T>(mut self, chat_id: T) -> Self
|
||||
where
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
Request, RequestContext, RequestFuture, ResponseResult,
|
||||
use crate::{
|
||||
network,
|
||||
requests::{Request, RequestContext, RequestFuture, ResponseResult},
|
||||
types::File,
|
||||
};
|
||||
use crate::core::types::File;
|
||||
|
||||
/// Use this method to get basic info about a file and prepare it for
|
||||
/// downloading. For the moment, bots can download files of up to 20MB in size.
|
||||
|
@ -12,11 +12,11 @@ use crate::core::types::File;
|
|||
/// It is guaranteed that the link will be valid for at least 1 hour.
|
||||
/// When the link expires, a new one can be requested by calling getFile again.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct GetFile<'a> {
|
||||
pub struct GetFile<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
/// File identifier to get info about
|
||||
file_id: String,
|
||||
pub file_id: String,
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> for GetFile<'a> {
|
||||
|
@ -36,6 +36,10 @@ impl<'a> Request<'a> for GetFile<'a> {
|
|||
}
|
||||
|
||||
impl<'a> GetFile<'a> {
|
||||
pub(crate) fn new(ctx: RequestContext<'a>, file_id: String) -> Self {
|
||||
Self { ctx, file_id }
|
||||
}
|
||||
|
||||
pub fn file_id<T>(mut self, file_id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{Request, RequestContext, RequestFuture, ResponseResult},
|
||||
types::User,
|
88
src/requests/get_updates.rs
Normal file
88
src/requests/get_updates.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use crate::{
|
||||
network,
|
||||
requests::{Request, RequestContext, RequestFuture, ResponseResult},
|
||||
types::Update,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct GetUpdates<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
|
||||
pub offset: Option<i32>,
|
||||
pub limit: Option<u8>,
|
||||
pub timeout: Option<u32>,
|
||||
pub allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, Hash, PartialEq, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AllowedUpdate {
|
||||
Message,
|
||||
EditedMessage,
|
||||
ChannelPost,
|
||||
EditedChannelPost,
|
||||
InlineQuery,
|
||||
ChosenInlineResult,
|
||||
CallbackQuery,
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> for GetUpdates<'a> {
|
||||
type ReturnValue = Vec<Update>;
|
||||
|
||||
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
|
||||
Box::pin(async move {
|
||||
network::request_json(
|
||||
&self.ctx.client,
|
||||
&self.ctx.token,
|
||||
"getUpdates",
|
||||
&self,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GetUpdates<'a> {
|
||||
pub(crate) fn new(ctx: RequestContext<'a>) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
offset: None,
|
||||
limit: None,
|
||||
timeout: None,
|
||||
allowed_updates: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset<T>(mut self, offset: T) -> Self
|
||||
where
|
||||
T: Into<i32>,
|
||||
{
|
||||
self.offset = Some(offset.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn limit<T>(mut self, limit: T) -> Self
|
||||
where
|
||||
T: Into<u8>,
|
||||
{
|
||||
self.limit = Some(limit.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn timeout<T>(mut self, timeout: T) -> Self
|
||||
where
|
||||
T: Into<u32>,
|
||||
{
|
||||
self.timeout = Some(timeout.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn allowed_updates<T>(mut self, allowed_updates: T) -> Self
|
||||
where
|
||||
T: Into<Vec<AllowedUpdate>>,
|
||||
{
|
||||
self.allowed_updates = Some(allowed_updates.into());
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,16 +1,19 @@
|
|||
use crate::core::requests::RequestContext;
|
||||
use crate::requests::RequestContext;
|
||||
|
||||
|
||||
//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.
|
||||
//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,
|
||||
/// Sequential number of the first photo to be returned. By default, all photos are returned.
|
||||
/// Sequential number of the first photo to be returned. By default, all
|
||||
/// photos are returned.
|
||||
offset: Option<i64>,
|
||||
///Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100.
|
||||
///Limits the number of photos to be retrieved. Values between 1—100 are
|
||||
/// accepted. Defaults to 100.
|
||||
limit: Option<i64>,
|
||||
}
|
12
src/requests/kick_chat_member.rs
Normal file
12
src/requests/kick_chat_member.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::requests::RequestContext;
|
||||
//TODO:: need implementation
|
||||
/// 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)]
|
||||
pub struct KickChatMember<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
}
|
|
@ -1,37 +1,27 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use reqwest::{r#async::Client, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
mod form_builder;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum RequestError {
|
||||
#[display(fmt = "Telegram error #{}: {}", status_code, description)]
|
||||
ApiError {
|
||||
// TODO: add response parameters
|
||||
status_code: StatusCode,
|
||||
description: String,
|
||||
},
|
||||
use reqwest::r#async::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
#[display(fmt = "Network error: {err}", err = _0)]
|
||||
NetworkError(reqwest::Error),
|
||||
use crate::RequestError;
|
||||
|
||||
#[display(fmt = "InvalidJson error caused by: {err}", err = _0)]
|
||||
InvalidJson(serde_json::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for RequestError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
RequestError::ApiError { .. } => None,
|
||||
RequestError::NetworkError(err) => Some(err),
|
||||
RequestError::InvalidJson(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub 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, 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>;
|
||||
|
||||
|
@ -89,24 +79,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_file;
|
||||
pub mod get_me;
|
||||
pub mod get_user_profile_photos;
|
||||
pub mod kick_chat_member;
|
||||
pub mod pin_chat_message;
|
||||
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;
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::requests::RequestContext;
|
||||
use crate::requests::RequestContext;
|
||||
//TODO:: need implementation
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct UnbanChatMember<'a> {
|
||||
pub struct RestrictChatMember<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::form_builder::FormBuilder,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
form_builder::FormBuilder, ChatId, Request, RequestContext,
|
||||
RequestFuture, ResponseResult,
|
||||
},
|
||||
types::{InputFile, Message, ParseMode, ReplyMarkup},
|
||||
};
|
||||
|
@ -32,10 +32,10 @@ pub struct SendAudio<'a> {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
pub parse_mode: Option<ParseMode>,
|
||||
/// Duration of the audio in seconds
|
||||
pub duration: Option<i32>,
|
|
@ -1,6 +1,8 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
};
|
||||
|
||||
///Use this method when you need to tell the user that something is happening
|
||||
|
@ -8,7 +10,7 @@ use crate::core::requests::{
|
|||
/// arrives from your bot, Telegram clients clear its typing status).
|
||||
/// Returns True on success.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct SendChatAction<'a> {
|
||||
pub struct SendChatAction<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
/// Unique identifier for the target chat or
|
||||
|
@ -24,7 +26,7 @@ struct SendChatAction<'a> {
|
|||
|
||||
#[derive(Debug, Serialize, From, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum ChatAction {
|
||||
pub enum ChatAction {
|
||||
Typing,
|
||||
UploadPhoto,
|
||||
RecordVideo,
|
|
@ -1,13 +1,15 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
types::{Message, ReplyMarkup},
|
||||
};
|
||||
use crate::core::types::{Message, ReplyMarkup};
|
||||
|
||||
/// Use this method to send phone contacts.
|
||||
/// returned.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct SendContact<'a> {
|
||||
pub struct SendContact<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
/// Unique identifier for the target chat or
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
|
@ -1,4 +1,6 @@
|
|||
use crate::core::{
|
||||
use apply::Apply;
|
||||
|
||||
use crate::{
|
||||
network::request_multipart,
|
||||
requests::{
|
||||
form_builder::FormBuilder, ChatId, Request, RequestContext,
|
||||
|
@ -6,7 +8,6 @@ use crate::core::{
|
|||
},
|
||||
types::{InputFile, InputMedia, Message},
|
||||
};
|
||||
use apply::Apply;
|
||||
|
||||
/// Use this method to send a group of photos or videos as an album.
|
||||
#[derive(Debug, Clone)]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
|
@ -23,10 +23,10 @@ pub struct SendMessage<'a> {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parse_mode: Option<ParseMode>,
|
||||
/// Disables link previews for links in this message
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
form_builder::FormBuilder, ChatId, Request, RequestContext,
|
||||
|
@ -30,10 +30,10 @@ pub struct SendPhoto<'a> {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
pub parse_mode: Option<ParseMode>,
|
||||
/// Sends the message silently. Users will receive a notification with no
|
||||
/// sound.
|
|
@ -1,13 +1,15 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
types::{Message, ReplyMarkup},
|
||||
};
|
||||
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)]
|
||||
struct SendPoll<'a> {
|
||||
pub struct SendPoll<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
/// identifier for the target chat or username of the target channel (in
|
|
@ -1,13 +1,15 @@
|
|||
use crate::core::network;
|
||||
use crate::core::requests::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
||||
},
|
||||
types::{Message, ReplyMarkup},
|
||||
};
|
||||
use crate::core::types::{Message, ReplyMarkup};
|
||||
|
||||
/// Use this method to send information about a venue.
|
||||
/// Message is returned.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct SendVenue<'a> {
|
||||
pub struct SendVenue<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
/// Unique identifier for the target chat or
|
||||
|
@ -62,7 +64,7 @@ impl<'a> Request<'a> for SendVenue<'a> {
|
|||
}
|
||||
|
||||
impl<'a> SendVenue<'a> {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
ctx: RequestContext<'a>,
|
||||
chat_id: ChatId,
|
||||
latitude: f64,
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{
|
||||
use crate::{
|
||||
network,
|
||||
requests::{
|
||||
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::requests::RequestContext;
|
||||
use crate::requests::RequestContext;
|
||||
//TODO:: need implementation
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct KickChatMember<'a> {
|
||||
pub struct UnbanChatMember<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
ctx: RequestContext<'a>,
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::PhotoSize;
|
||||
use crate::types::PhotoSize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video
|
51
src/types/audio.rs
Normal file
51
src/types/audio.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::types::PhotoSize;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone)]
|
||||
pub struct Audio {
|
||||
pub file_id: String,
|
||||
pub duration: u32,
|
||||
pub performer: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub mime_type: Option<String>,
|
||||
pub file_size: Option<u32>,
|
||||
pub thumb: Option<PhotoSize>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
let json = r#"{
|
||||
"file_id":"id",
|
||||
"duration":60,
|
||||
"performer":"Performer",
|
||||
"title":"Title",
|
||||
"mime_type":"MimeType",
|
||||
"file_size":123456,
|
||||
"thumb":{
|
||||
"file_id":"id",
|
||||
"width":320,
|
||||
"height":320,
|
||||
"file_size":3452
|
||||
}
|
||||
}"#;
|
||||
let expected = Audio {
|
||||
file_id: "id".to_string(),
|
||||
duration: 60,
|
||||
performer: Some("Performer".to_string()),
|
||||
title: Some("Title".to_string()),
|
||||
mime_type: Some("MimeType".to_string()),
|
||||
file_size: Some(123456),
|
||||
thumb: Some(PhotoSize {
|
||||
file_id: "id".to_string(),
|
||||
width: 320,
|
||||
height: 320,
|
||||
file_size: Some(3452),
|
||||
}),
|
||||
};
|
||||
let actual = serde_json::from_str::<Audio>(&json).unwrap();
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
}
|
51
src/types/callback_query.rs
Normal file
51
src/types/callback_query.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::types::{Message, User};
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Clone)]
|
||||
pub struct CallbackQuery {
|
||||
pub id: String,
|
||||
pub from: User,
|
||||
pub chat_instance: String,
|
||||
pub message: Option<Message>,
|
||||
pub inline_message_id: Option<String>,
|
||||
pub data: Option<String>,
|
||||
pub game_short_name: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
let json = r#"{
|
||||
"id":"id",
|
||||
"from":{
|
||||
"id":12345,
|
||||
"is_bot":false,
|
||||
"first_name":"firstName"
|
||||
},
|
||||
"inline_message_id":"i_m_id",
|
||||
"chat_instance":"123456",
|
||||
"data":"some_data",
|
||||
"game_short_name":"game_name"
|
||||
}"#;
|
||||
let expected = CallbackQuery {
|
||||
id: "id".to_string(),
|
||||
from: User {
|
||||
id: 12345,
|
||||
is_bot: false,
|
||||
first_name: "firstName".to_string(),
|
||||
last_name: None,
|
||||
username: None,
|
||||
language_code: None,
|
||||
},
|
||||
chat_instance: "123456".to_string(),
|
||||
message: None,
|
||||
inline_message_id: Some("i_m_id".to_string()),
|
||||
data: Some("some_data".to_string()),
|
||||
game_short_name: Some("game_name".to_string()),
|
||||
};
|
||||
let actual = serde_json::from_str::<CallbackQuery>(json).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{ChatPermissions, ChatPhoto, Message};
|
||||
use crate::types::{ChatPermissions, ChatPhoto, Message};
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Clone)]
|
||||
pub struct Chat {
|
||||
|
@ -80,7 +80,7 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::types::*;
|
||||
use crate::types::*;
|
||||
use serde_json::from_str;
|
||||
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{ChatMemberStatus, User};
|
||||
use crate::types::User;
|
||||
|
||||
/// This object contains information about one member of the chat.
|
||||
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
|
||||
|
@ -54,3 +54,74 @@ pub struct ChatMember {
|
|||
/// his messages, implies can_send_media_messages
|
||||
pub can_add_web_page_previews: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChatMemberStatus {
|
||||
Creator,
|
||||
Administrator,
|
||||
Member,
|
||||
Restricted,
|
||||
Left,
|
||||
Kicked,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
let json = r#"{
|
||||
"user":{
|
||||
"id":12345,
|
||||
"is_bot":false,
|
||||
"first_name":"firstName"
|
||||
},
|
||||
"status":"creator",
|
||||
"until_date":123456,
|
||||
"can_be_edited":true,
|
||||
"can_post_messages":true,
|
||||
"can_edit_messages":true,
|
||||
"can_delete_messages":true,
|
||||
"can_restrict_members":true,
|
||||
"can_promote_members":true,
|
||||
"can_change_info":true,
|
||||
"can_invite_users":true,
|
||||
"can_pin_messages":true,
|
||||
"is_member":true,
|
||||
"can_send_messages":true,
|
||||
"can_send_media_messages":true,
|
||||
"can_send_polls":true,
|
||||
"can_send_other_messages":true,
|
||||
"can_add_web_page_previews":true
|
||||
}"#;
|
||||
let expected = ChatMember {
|
||||
user: User {
|
||||
id: 12345,
|
||||
is_bot: false,
|
||||
first_name: "firstName".to_string(),
|
||||
last_name: None,
|
||||
username: None,
|
||||
language_code: None,
|
||||
},
|
||||
status: ChatMemberStatus::Creator,
|
||||
until_date: Some(123456),
|
||||
can_be_edited: Some(true),
|
||||
can_change_info: Some(true),
|
||||
can_post_messages: Some(true),
|
||||
can_edit_messages: Some(true),
|
||||
can_delete_messages: Some(true),
|
||||
can_invite_users: Some(true),
|
||||
can_restrict_members: Some(true),
|
||||
can_pin_messages: Some(true),
|
||||
can_promote_members: Some(true),
|
||||
can_send_messages: Some(true),
|
||||
can_send_media_messages: Some(true),
|
||||
can_send_other_messages: Some(true),
|
||||
can_add_web_page_previews: Some(true),
|
||||
};
|
||||
let actual = serde_json::from_str::<ChatMember>(&json).unwrap();
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{Location, User};
|
||||
use crate::types::{Location, User};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq)]
|
||||
/// Represents a result of an inline query that was chosen by the user and
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::PhotoSize;
|
||||
use crate::types::PhotoSize;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
|
||||
pub struct Document {
|
35
src/types/encrypted_credintials.rs
Normal file
35
src/types/encrypted_credintials.rs
Normal 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)
|
||||
}
|
||||
}
|
75
src/types/encrypted_passport_element.rs
Normal file
75
src/types/encrypted_passport_element.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use super::passport_file::PassportFile;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
|
||||
pub struct EncryptedPassportElement {
|
||||
#[serde(rename = "type")]
|
||||
pub element_type: ElementType,
|
||||
pub data: String,
|
||||
pub phone_number: String,
|
||||
pub email: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub files: Option<Vec<PassportFile>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub front_size: Option<PassportFile>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reverse_side: Option<PassportFile>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selfie: Option<PassportFile>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub translation: Option<Vec<PassportFile>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ElementType {
|
||||
PersonalData,
|
||||
Passport,
|
||||
DriverLicense,
|
||||
IdentityCard,
|
||||
Address,
|
||||
UtilityBill,
|
||||
BankStatement,
|
||||
RentalAgreement,
|
||||
PassportRegistration,
|
||||
TemporaryRegistration,
|
||||
PhoneNumber,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{ElementType, EncryptedPassportElement, PassportFile};
|
||||
#[test]
|
||||
fn must_serialize_encrypted_passport_element_to_json() {
|
||||
// given
|
||||
let expected_json = r#"
|
||||
{
|
||||
"type":"passport_registration",
|
||||
"data":"somedata",
|
||||
"phone_number":"1313",
|
||||
"email":"someemail",
|
||||
"front_size":{"file_id":"someId","file_size":13,"file_date":13}
|
||||
}"#
|
||||
.replace("\n", "")
|
||||
.replace(" ", "");
|
||||
|
||||
let passport_element = EncryptedPassportElement {
|
||||
element_type: ElementType::PassportRegistration,
|
||||
data: "somedata".to_string(),
|
||||
phone_number: "1313".to_string(),
|
||||
email: "someemail".to_string(),
|
||||
files: None,
|
||||
front_size: Some(PassportFile {
|
||||
file_id: "someId".to_string(),
|
||||
file_size: 13,
|
||||
file_date: 13,
|
||||
}),
|
||||
reverse_side: None,
|
||||
selfie: None,
|
||||
translation: None,
|
||||
};
|
||||
|
||||
let actual_json = serde_json::to_string(&passport_element).unwrap();
|
||||
assert_eq!(actual_json, expected_json)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::core::types::{Animation, MessageEntity, PhotoSize};
|
||||
use crate::types::{Animation, MessageEntity, PhotoSize};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
/// This object represents a game. Use BotFather to create and edit games, their
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::core::types::user::User;
|
||||
use crate::types::user::User;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
/// This object represents one row of the high scores table for a game.
|
|
@ -45,7 +45,7 @@ pub enum InlineKeyboardButtonKind {
|
|||
///
|
||||
/// Example:
|
||||
/// ```edition2018
|
||||
/// use async_telegram_bot::core::types::InlineKeyboardButton;
|
||||
/// use async_telegram_bot::types::InlineKeyboardButton;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let url_button = InlineKeyboardButton::url(
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::InlineKeyboardButton;
|
||||
use crate::types::InlineKeyboardButton;
|
||||
|
||||
/// This object represents an inline keyboard that appears right next to the
|
||||
/// message it belongs to.
|
||||
|
@ -16,7 +16,7 @@ pub struct InlineKeyboardMarkup {
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use async_telegram_bot::core::types::{
|
||||
/// use async_telegram_bot::types::{
|
||||
/// InlineKeyboardButton, InlineKeyboardMarkup,
|
||||
/// };
|
||||
///
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{Location, User};
|
||||
use crate::types::{Location, User};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQuery {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{
|
||||
use crate::types::{
|
||||
InlineQueryResultArticle, InlineQueryResultAudio,
|
||||
InlineQueryResultCachedAudio, InlineQueryResultCachedDocument,
|
||||
InlineQueryResultCachedGif, InlineQueryResultCachedMpeg4Gif,
|
||||
|
@ -49,9 +49,9 @@ pub enum InlineQueryResult {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::types::inline_keyboard_markup::InlineKeyboardMarkup;
|
||||
use crate::core::types::parse_mode::ParseMode;
|
||||
use crate::core::types::{
|
||||
use crate::types::inline_keyboard_markup::InlineKeyboardMarkup;
|
||||
use crate::types::parse_mode::ParseMode;
|
||||
use crate::types::{
|
||||
InlineQueryResult, InlineQueryResultCachedAudio, InputMessageContent,
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultArticle {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultAudio {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedAudio {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedDocument {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedGif {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedMpeg4Gif {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedPhoto {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedSticker {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedVideo {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultCachedVoice {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultContact {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultDocument {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::InlineKeyboardMarkup;
|
||||
|
||||
#[derive(Debug, Serialize, Hash, PartialEq, Eq, Clone)]
|
||||
pub struct InlineQueryResultGame {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultGif {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultLocation {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultMpeg4Gif {
|
|
@ -1,7 +1,8 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
/// Represents a link to a photo. By default, this photo will be sent by the
|
||||
/// user with optional caption. Alternatively, you can use input_message_content
|
||||
/// to send a message with the specified content instead of the photo.
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultPhoto {
|
||||
pub id: String,
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultVenue {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultVideo {
|
|
@ -1,6 +1,4 @@
|
|||
use crate::core::types::{
|
||||
InlineKeyboardMarkup, InputMessageContent, ParseMode,
|
||||
};
|
||||
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
pub struct InlineQueryResultVoice {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{InputFile, ParseMode};
|
||||
use crate::types::{InputFile, ParseMode};
|
||||
|
||||
// TODO: should variants use new-type?
|
||||
#[derive(Debug, Serialize, PartialEq, Eq, Clone)]
|
||||
|
@ -18,10 +18,10 @@ pub enum InputMedia {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<ParseMode>,
|
||||
},
|
||||
|
@ -35,7 +35,7 @@ pub enum InputMedia {
|
|||
/// size. A thumbnail‘s width and height should not exceed 320.
|
||||
/// Ignored if the file is not uploaded using [InputFile::File].
|
||||
///
|
||||
/// [InputFile::File]: crate::core::types::InputFile::File
|
||||
/// [InputFile::File]: crate::types::InputFile::File
|
||||
thumb: Option<InputFile>,
|
||||
/// Caption of the video to be sent, 0-1024 characters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -44,10 +44,10 @@ pub enum InputMedia {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<ParseMode>,
|
||||
/// Video width
|
||||
|
@ -74,7 +74,7 @@ pub enum InputMedia {
|
|||
/// size. A thumbnail‘s width and height should not exceed 320.
|
||||
/// Ignored if the file is not uploaded using [InputFile::File].
|
||||
///
|
||||
/// [InputFile::File]: crate::core::types::InputFile::File
|
||||
/// [InputFile::File]: crate::types::InputFile::File
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
thumb: Option<InputFile>,
|
||||
/// Caption of the animation to be sent, 0-1024 characters
|
||||
|
@ -84,10 +84,10 @@ pub enum InputMedia {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<ParseMode>,
|
||||
/// Animation width
|
||||
|
@ -110,7 +110,7 @@ pub enum InputMedia {
|
|||
/// size. A thumbnail‘s width and height should not exceed 320.
|
||||
/// Ignored if the file is not uploaded using [InputFile::File].
|
||||
///
|
||||
/// [InputFile::File]: crate::core::types::InputFile::File
|
||||
/// [InputFile::File]: crate::types::InputFile::File
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
thumb: Option<InputFile>,
|
||||
/// Caption of the audio to be sent, 0-1024 characters
|
||||
|
@ -120,10 +120,10 @@ pub enum InputMedia {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<String>,
|
||||
/// Duration of the audio in seconds
|
||||
|
@ -146,7 +146,7 @@ pub enum InputMedia {
|
|||
/// size. A thumbnail‘s width and height should not exceed 320.
|
||||
/// Ignored if the file is not uploaded using [InputFile::File].
|
||||
///
|
||||
/// [InputFile::File]: crate::core::types::InputFile::File
|
||||
/// [InputFile::File]: crate::types::InputFile::File
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
thumb: Option<InputFile>,
|
||||
/// Caption of the document to be sent, 0-1024 characters
|
||||
|
@ -156,10 +156,10 @@ pub enum InputMedia {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<ParseMode>,
|
||||
},
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::core::types::ParseMode;
|
||||
use crate::types::ParseMode;
|
||||
|
||||
#[derive(Debug, Serialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
|
@ -18,10 +18,10 @@ pub enum InputMessageContent {
|
|||
/// if you want Telegram apps to show [bold, italic, fixed-width text
|
||||
/// or inline URLs] in the media caption.
|
||||
///
|
||||
/// [Markdown]: crate::core::types::ParseMode::Markdown
|
||||
/// [Html]: crate::core::types::ParseMode::Html
|
||||
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||
/// [Html]: crate::types::ParseMode::Html
|
||||
/// [bold, italic, fixed-width text or inline URLs]:
|
||||
/// crate::core::types::ParseMode
|
||||
/// crate::types::ParseMode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
parse_mode: Option<ParseMode>,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::{
|
||||
use crate::types::{
|
||||
Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup,
|
||||
Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, Sticker,
|
||||
SuccessfulPayment, User, Venue, Video, VideoNote, Voice,
|
||||
|
@ -16,10 +16,11 @@ pub struct Message {
|
|||
|
||||
impl Message {
|
||||
fn text(&self) -> Option<&str> {
|
||||
if let MessageKind::CommonMessage {
|
||||
media_kind: MediaKind::Text {
|
||||
ref text, ..
|
||||
}, .. } = self.kind {
|
||||
if let MessageKind::Common {
|
||||
media_kind: MediaKind::Text { ref text, .. },
|
||||
..
|
||||
} = self.kind
|
||||
{
|
||||
Some(text)
|
||||
} else {
|
||||
None
|
||||
|
@ -30,7 +31,7 @@ impl Message {
|
|||
#[derive(Debug, Deserialize, PartialEq, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum MessageKind {
|
||||
CommonMessage {
|
||||
Common {
|
||||
#[serde(flatten)]
|
||||
from: Sender,
|
||||
#[serde(flatten)]
|
||||
|
@ -68,8 +69,8 @@ pub enum MessageKind {
|
|||
migrate_to_chat_id: i64,
|
||||
migrate_from_chat_id: i64,
|
||||
},
|
||||
PinnedMessage {
|
||||
pinned_message: Box<Message>,
|
||||
Pinned {
|
||||
pinned: Box<Message>,
|
||||
},
|
||||
Invoice {
|
||||
invoice: Invoice,
|
||||
|
@ -202,7 +203,7 @@ pub enum MediaKind {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::types::*;
|
||||
use crate::types::*;
|
||||
use serde_json::from_str;
|
||||
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::User;
|
||||
use crate::types::User;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
|
||||
pub struct MessageEntity {
|
|
@ -2,17 +2,21 @@ use self::not_implemented_types::*;
|
|||
pub use self::{
|
||||
animation::Animation,
|
||||
audio::Audio,
|
||||
callback_game::CallbackGame,
|
||||
callback_query::CallbackQuery,
|
||||
chat::{Chat, ChatKind, NonPrivateChatKind},
|
||||
chat_member::ChatMember,
|
||||
chat_member::{ChatMember, ChatMemberStatus},
|
||||
chat_permissions::ChatPermissions,
|
||||
chat_photo::ChatPhoto,
|
||||
chosen_inline_result::ChosenInlineResult,
|
||||
contact::Contact,
|
||||
document::Document,
|
||||
encrypted_credintials::EncryptedCredentials,
|
||||
encrypted_passport_element::{ElementType, EncryptedPassportElement},
|
||||
file::File,
|
||||
force_reply::ForceReply,
|
||||
game::Game,
|
||||
game_high_score::GameHighScore,
|
||||
inline_keyboard_button::{InlineKeyboardButton, InlineKeyboardButtonKind},
|
||||
inline_keyboard_markup::InlineKeyboardMarkup,
|
||||
inline_query::InlineQuery,
|
||||
|
@ -44,6 +48,7 @@ pub use self::{
|
|||
keyboard_button::KeyboardButton,
|
||||
label_price::LabeledPrice,
|
||||
location::Location,
|
||||
login_url::LoginUrl,
|
||||
mask_position::MaskPosition,
|
||||
message::{
|
||||
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
|
||||
|
@ -51,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,
|
||||
|
@ -63,17 +70,21 @@ pub use self::{
|
|||
shipping_option::ShippingOption,
|
||||
shipping_query::ShippingQuery,
|
||||
sticker::Sticker,
|
||||
sticker_set::StickerSet,
|
||||
successful_payment::SuccessfulPayment,
|
||||
update::{Update, UpdateKind},
|
||||
user::User,
|
||||
user_profile_photos::UserProfilePhotos,
|
||||
venue::Venue,
|
||||
video::Video,
|
||||
video_note::VideoNote,
|
||||
voice::Voice,
|
||||
webhook_info::WebhookInfo,
|
||||
};
|
||||
|
||||
mod animation;
|
||||
mod audio;
|
||||
mod callback_game;
|
||||
mod callback_query;
|
||||
mod chat;
|
||||
mod chat_member;
|
||||
|
@ -85,6 +96,7 @@ mod document;
|
|||
mod file;
|
||||
mod force_reply;
|
||||
mod game;
|
||||
mod game_high_score;
|
||||
mod inline_keyboard_button;
|
||||
mod inline_keyboard_markup;
|
||||
mod input_file;
|
||||
|
@ -94,6 +106,7 @@ mod invoice;
|
|||
mod keyboard_button;
|
||||
mod label_price;
|
||||
mod location;
|
||||
mod login_url;
|
||||
mod mask_position;
|
||||
mod message;
|
||||
mod message_entity;
|
||||
|
@ -112,13 +125,16 @@ mod shipping_address;
|
|||
mod shipping_option;
|
||||
mod shipping_query;
|
||||
mod sticker;
|
||||
mod sticker_set;
|
||||
mod successful_payment;
|
||||
mod update;
|
||||
mod user;
|
||||
mod user_profile_photos;
|
||||
mod venue;
|
||||
mod video;
|
||||
mod video_note;
|
||||
mod voice;
|
||||
mod webhook_info;
|
||||
|
||||
mod inline_query;
|
||||
mod inline_query_result;
|
||||
|
@ -142,3 +158,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;
|
2
src/types/not_implemented_types.rs
Normal file
2
src/types/not_implemented_types.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
|
||||
pub struct PassportData;
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::types::ShippingAddress;
|
||||
use crate::types::ShippingAddress;
|
||||
|
||||
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)]
|
||||
pub struct OrderInfo {
|
68
src/types/passport_data.rs
Normal file
68
src/types/passport_data.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
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,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{ElementType, PassportFile};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn must_serialize_passport_data_to_json() {
|
||||
let expected_json = r#"{
|
||||
"data":
|
||||
[
|
||||
{
|
||||
"type":"passport_registration",
|
||||
"data":"somedata",
|
||||
"phone_number":"1313",
|
||||
"email":"someemail",
|
||||
"front_size":
|
||||
{
|
||||
"file_id":"someId",
|
||||
"file_size":13,
|
||||
"file_date":13
|
||||
}
|
||||
}
|
||||
],
|
||||
"credentials":
|
||||
{
|
||||
"data":"someData",
|
||||
"hash":"1122",
|
||||
"secret":"secret"
|
||||
}
|
||||
}"#
|
||||
.replace("\n", "")
|
||||
.replace(" ", "");
|
||||
let passport_data = PassportData {
|
||||
data: vec![EncryptedPassportElement {
|
||||
element_type: ElementType::PassportRegistration,
|
||||
data: "somedata".to_string(),
|
||||
phone_number: "1313".to_string(),
|
||||
email: "someemail".to_string(),
|
||||
files: None,
|
||||
front_size: Some(PassportFile {
|
||||
file_id: "someId".to_string(),
|
||||
file_size: 13,
|
||||
file_date: 13,
|
||||
}),
|
||||
reverse_side: None,
|
||||
selfie: None,
|
||||
translation: None,
|
||||
}],
|
||||
credentials: EncryptedCredentials {
|
||||
data: "someData".to_string(),
|
||||
hash: "1122".to_string(),
|
||||
secret: "secret".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let actual_json = serde_json::to_string(&passport_data).unwrap();
|
||||
assert_eq!(actual_json, expected_json)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue