Merge branch 'dev' into dispatcher

This commit is contained in:
Waffle Lapkin 2019-09-21 01:17:25 +03:00 committed by GitHub
commit 78988205e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
120 changed files with 1818 additions and 783 deletions

View file

@ -15,4 +15,4 @@ derive_more = "0.15.0"
tokio = "0.2.0-alpha.4" tokio = "0.2.0-alpha.4"
bytes = "0.4.12" bytes = "0.4.12"
log = "0.4.8" log = "0.4.8"
futures-util-preview = "0.3.0-alpha.18" futures-preview = "0.3.0-alpha.18"

116
src/bot/api.rs Normal file
View file

@ -0,0 +1,116 @@
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,
},
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
View 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
}
}

View file

@ -1,31 +1,16 @@
mod api;
mod download;
use reqwest::r#async::Client; use reqwest::r#async::Client;
use crate::core::{ use crate::requests::RequestContext;
types::{
InputFile,
InputMedia,
},
requests::{
ChatId,
RequestContext,
get_me::GetMe,
send_message::SendMessage,
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage,
send_audio::SendAudio,
send_location::SendLocation,
send_media_group::SendMediaGroup,
send_photo::SendPhoto,
stop_message_live_location::StopMessageLiveLocation,
}
};
pub struct Bot { pub struct Bot {
token: String, token: String,
client: Client, client: Client,
} }
/// Constructors
impl Bot { impl Bot {
pub fn new(token: &str) -> Self { pub fn new(token: &str) -> Self {
Bot { Bot {
@ -40,7 +25,9 @@ impl Bot {
client, client,
} }
} }
}
impl Bot {
fn ctx(&self) -> RequestContext { fn ctx(&self) -> RequestContext {
RequestContext { RequestContext {
token: &self.token, token: &self.token,
@ -48,118 +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<i64>,
{
ForwardMessage::new(
self.ctx(),
chat_id.into(),
from_chat_id.into(),
message_id.into(),
)
}
pub fn send_audio<C, A>(&self, chat_id: C, audio: A) -> SendAudio
where
C: Into<ChatId>,
A: Into<InputFile>,
{
SendAudio::new(
self.ctx(),
chat_id.into(),
audio.into()
)
}
pub fn send_location<C, Lt, Lg>(
&self,
chat_id: C,
latitude: Lt,
longitude: Lg,
) -> SendLocation
where
C: Into<ChatId>,
Lt: Into<f64>,
Lg: Into<f64>,
{
SendLocation::new(
self.ctx(),
chat_id.into(),
latitude.into(),
longitude.into(),
)
}
pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup
where
C: Into<ChatId>,
M: Into<Vec<InputMedia>>
{
SendMediaGroup::new(
self.ctx(),
chat_id.into(),
media.into(),
)
}
pub fn send_photo<C, P>(&self, chat_id: C, photo: P) -> SendPhoto
where
C: Into<ChatId>,
P: Into<InputFile>
{
SendPhoto::new(
self.ctx(),
chat_id.into(),
photo.into(),
)
}
pub fn stop_message_live_location(&self) -> StopMessageLiveLocation {
StopMessageLiveLocation::new(
self.ctx()
)
}
}

View file

@ -1,3 +0,0 @@
mod network;
pub mod requests;
pub mod types;

View file

@ -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"
);
}
}

View file

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

View file

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

View file

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

View file

@ -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>,
}

View file

@ -1,19 +0,0 @@
use crate::core::types::{User, Message};
/// 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,
}

View file

@ -1,95 +0,0 @@
use self::not_implemented_types::*;
pub use self::{
animation::Animation,
audio::Audio,
callback_query::CallbackQuery,
chat::{Chat, ChatKind, NonPrivateChatKind},
chat_member::ChatMember,
chat_permissions::ChatPermissions,
chat_photo::ChatPhoto,
chosen_inline_result::ChosenInlineResult,
contact::Contact,
document::Document,
force_reply::ForceReply,
game::Game,
inline_keyboard_button::{InlineKeyboardButton, InlineKeyboardButtonKind},
inline_keyboard_markup::InlineKeyboardMarkup,
input_file::InputFile,
input_media::InputMedia,
invoice::Invoice,
keyboard_button::KeyboardButton,
label_price::LabeledPrice,
location::Location,
mask_position::MaskPosition,
message::{
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
},
message_entity::MessageEntity,
order_info::OrderInfo,
parse_mode::ParseMode,
photo_size::PhotoSize,
poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery,
reply_keyboard_markup::ReplyKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_markup::ReplyMarkup,
response_parameters::ResponseParameters,
send_invoice::SendInvoice,
shipping_address::ShippingAddress,
shipping_option::ShippingOption,
shipping_query::ShippingQuery,
sticker::Sticker,
successful_payment::SuccessfulPayment,
update::{Update, UpdateKind},
user::User,
venue::Venue,
video::Video,
video_note::VideoNote,
voice::Voice,
};
mod animation;
mod audio;
mod callback_query;
mod chat;
mod chat_member;
mod chat_permissions;
mod chat_photo;
mod chosen_inline_result;
mod contact;
mod document;
mod force_reply;
mod game;
mod inline_keyboard_button;
mod inline_keyboard_markup;
mod input_file;
mod input_media;
mod invoice;
mod keyboard_button;
mod label_price;
mod location;
mod mask_position;
mod message;
mod message_entity;
mod not_implemented_types;
mod order_info;
mod parse_mode;
mod photo_size;
mod poll;
mod pre_checkout_query;
mod reply_keyboard_markup;
mod reply_keyboard_remove;
mod reply_markup;
mod response_parameters;
mod send_invoice;
mod shipping_address;
mod shipping_option;
mod shipping_query;
mod sticker;
mod successful_payment;
mod update;
mod user;
mod venue;
mod video;
mod video_note;
mod voice;

View file

@ -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;

View file

@ -1,9 +0,0 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)]
pub struct User {
pub id: i64,
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
View 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>

View file

@ -3,6 +3,12 @@ extern crate derive_more;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
mod network;
pub mod bot; pub mod bot;
pub mod core;
pub mod dispatcher; pub mod dispatcher;
pub mod errors;
pub mod requests;
pub mod types;
pub use errors::{DownloadError, RequestError};

40
src/network/download.rs Normal file
View file

@ -0,0 +1,40 @@
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},
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 {
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
View 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
View 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, TELEGRAM_API_URL, TelegramResponse},
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()
}

View 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(),
})
}
}
}
}
}

View file

@ -1,6 +1,6 @@
use crate::core::{ use crate::{
requests::{RequestContext, Request, RequestFuture, ResponseResult}, network,
network requests::{Request, RequestContext, RequestFuture, ResponseResult},
}; };
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
@ -40,8 +40,9 @@ impl<'a> Request<'a> for AnswerPreCheckoutQuery<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"answerPreCheckoutQuery", "answerPreCheckoutQuery",
&self &self,
).await )
.await
}) })
} }
} }
@ -50,32 +51,35 @@ impl<'a> AnswerPreCheckoutQuery<'a> {
pub(crate) fn new( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
pre_checkout_query_id: String, pre_checkout_query_id: String,
ok: bool ok: bool,
) -> Self { ) -> Self {
Self { Self {
ctx, ctx,
pre_checkout_query_id, pre_checkout_query_id,
ok, ok,
error_message: None error_message: None,
} }
} }
pub fn pre_checkout_query_id<T>(mut self, pre_checkout_query_id: T) -> Self pub fn pre_checkout_query_id<T>(mut self, pre_checkout_query_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.pre_checkout_query_id = pre_checkout_query_id.into(); self.pre_checkout_query_id = pre_checkout_query_id.into();
self self
} }
pub fn ok<T>(mut self, ok: T) -> Self pub fn ok<T>(mut self, ok: T) -> Self
where T: Into<bool> where
T: Into<bool>,
{ {
self.ok = ok.into(); self.ok = ok.into();
self self
} }
pub fn error_message<T>(mut self, error_message: T) -> Self pub fn error_message<T>(mut self, error_message: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.error_message = Some(error_message.into()); self.error_message = Some(error_message.into());
self self

View file

@ -1,6 +1,8 @@
use crate::core::types::ShippingOption; use crate::{
use crate::core::requests::{RequestContext, Request, RequestFuture, ResponseResult}; network,
use crate::core::network; requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::ShippingOption,
};
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// If you sent an invoice requesting a shipping address and the parameter /// If you sent an invoice requesting a shipping address and the parameter
@ -40,8 +42,9 @@ impl<'a> Request<'a> for AnswerShippingQuery<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"answerShippingQuery", "answerShippingQuery",
&self &self,
).await )
.await
}) })
} }
} }
@ -50,40 +53,44 @@ impl<'a> AnswerShippingQuery<'a> {
pub(crate) fn new( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
shipping_query_id: String, shipping_query_id: String,
ok: bool ok: bool,
) -> Self { ) -> Self {
Self { Self {
ctx, ctx,
shipping_query_id, shipping_query_id,
ok, ok,
shipping_options: None, shipping_options: None,
error_message: None error_message: None,
} }
} }
pub fn shipping_query_id<T>(mut self, shipping_query_id: T) -> Self pub fn shipping_query_id<T>(mut self, shipping_query_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.shipping_query_id = shipping_query_id.into(); self.shipping_query_id = shipping_query_id.into();
self self
} }
pub fn ok<T>(mut self, ok: T) -> Self pub fn ok<T>(mut self, ok: T) -> Self
where T: Into<bool> where
T: Into<bool>,
{ {
self.ok = ok.into(); self.ok = ok.into();
self self
} }
pub fn shipping_options<T>(mut self, shipping_options: T) -> Self pub fn shipping_options<T>(mut self, shipping_options: T) -> Self
where T: Into<Vec<ShippingOption>> where
T: Into<Vec<ShippingOption>>,
{ {
self.shipping_options = Some(shipping_options.into()); self.shipping_options = Some(shipping_options.into());
self self
} }
pub fn error_message<T>(mut self, error_message: T) -> Self pub fn error_message<T>(mut self, error_message: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.error_message = Some(error_message.into()); self.error_message = Some(error_message.into());
self self

View file

@ -1,9 +1,10 @@
use crate::core::network; use crate::{
use crate::core::requests::{ network,
ChatId, Request, RequestContext, RequestFuture, ResponseResult, requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{Message, ReplyMarkup},
}; };
use crate::core::types::{Message, ReplyMarkup};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to edit live location messages. A location can be edited /// Use this method to edit live location messages. A location can be edited
@ -23,7 +24,7 @@ pub struct EditMessageLiveLocation<'a> {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Identifier of the /// Required if inline_message_id is not specified. Identifier of the
/// message to edit /// message to edit
message_id: Option<i64>, message_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if chat_id and message_id are not specified. Identifier of /// Required if chat_id and message_id are not specified. Identifier of
/// the inline message /// the inline message
@ -75,7 +76,7 @@ impl<'a> EditMessageLiveLocation<'a> {
self self
} }
pub fn message_id<T: Into<i64>>(mut self, message_id: T) -> Self { pub fn message_id<T: Into<i32>>(mut self, message_id: T) -> Self {
self.message_id = Some(message_id.into()); self.message_id = Some(message_id.into());
self self
} }

View file

@ -1,12 +1,12 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::core::{ use reqwest::r#async::multipart::Form;
use crate::{
requests::{utils, ChatId}, requests::{utils, ChatId},
types::{InputMedia, ParseMode}, types::{InputMedia, ParseMode},
}; };
use reqwest::r#async::multipart::Form;
/// This is a convenient struct that builds `reqwest::r#async::multipart::Form` /// This is a convenient struct that builds `reqwest::r#async::multipart::Form`
/// from scratch. /// from scratch.
pub struct FormBuilder { pub struct FormBuilder {

View file

@ -1,11 +1,7 @@
use crate::core::{ use crate::{
network, network,
requests::{ requests::{
ChatId, ChatId, Request, RequestContext, RequestFuture, ResponseResult,
Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::Message, types::Message,
}; };
@ -24,7 +20,7 @@ pub struct ForwardMessage<'a> {
/// (in the format @channelusername) /// (in the format @channelusername)
pub from_chat_id: ChatId, pub from_chat_id: ChatId,
/// Message identifier in the chat specified in from_chat_id /// Message identifier in the chat specified in from_chat_id
pub message_id: i64, pub message_id: i32,
/// Sends the message silently. Users will receive a notification with no /// Sends the message silently. Users will receive a notification with no
/// sound. /// sound.
@ -53,7 +49,7 @@ impl<'a> ForwardMessage<'a> {
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
chat_id: ChatId, chat_id: ChatId,
from_chat_id: ChatId, from_chat_id: ChatId,
message_id: i64, message_id: i32,
) -> Self { ) -> Self {
Self { Self {
ctx, ctx,
@ -74,7 +70,7 @@ impl<'a> ForwardMessage<'a> {
self self
} }
pub fn message_id<T: Into<i64>>(mut self, val: T) -> Self { pub fn message_id<T: Into<i32>>(mut self, val: T) -> Self {
self.message_id = val.into(); self.message_id = val.into();
self self
} }

46
src/requests/get_chat.rs Normal file
View file

@ -0,0 +1,46 @@
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,
/// current username of a user, group or channel, etc.).
/// Returns a Chat object on success.
#[derive(Debug, Clone, Serialize)]
pub struct GetChat<'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)
chat_id: ChatId,
}
impl<'a> Request<'a> for GetChat<'a> {
type ReturnValue = Chat;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"getChat",
&self,
)
.await
})
}
}
impl<'a> GetChat<'a> {
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
}

50
src/requests/get_file.rs Normal file
View file

@ -0,0 +1,50 @@
use crate::{
network,
requests::{Request, RequestContext, RequestFuture, ResponseResult},
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.
/// On success, a File object is returned.
/// The file can then be downloaded via the link https://api.telegram.org/file/bot<token>/<file_path>,
/// where <file_path> is taken from the response.
/// 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)]
pub struct GetFile<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// File identifier to get info about
pub file_id: String,
}
impl<'a> Request<'a> for GetFile<'a> {
type ReturnValue = File;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"getFile",
&self,
)
.await
})
}
}
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>,
{
self.file_id = file_id.into();
self
}
}

View file

@ -1,4 +1,4 @@
use crate::core::{ use crate::{
network, network,
requests::{Request, RequestContext, RequestFuture, ResponseResult}, requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::User, types::User,

View file

@ -0,0 +1,19 @@
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.
#[derive(Debug, Clone, Serialize)]
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.
offset: Option<i64>,
///Limits the number of photos to be retrieved. Values between 1—100 are
/// accepted. Defaults to 100.
limit: Option<i64>,
}

View 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)]
struct KickChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
}

View file

@ -1,37 +1,11 @@
use std::future::Future;
use std::pin::Pin;
use reqwest::{r#async::Client, StatusCode};
use serde::de::DeserializeOwned;
mod form_builder; mod form_builder;
mod utils; mod utils;
#[derive(Debug, Display)] use reqwest::r#async::Client;
pub enum RequestError { use serde::de::DeserializeOwned;
#[display(fmt = "Telegram error #{}: {}", status_code, description)] use std::{future::Future, pin::Pin};
ApiError {
// TODO: add response parameters
status_code: StatusCode,
description: String,
},
#[display(fmt = "Network error: {err}", err = _0)] use crate::RequestError;
NetworkError(reqwest::Error),
#[display(fmt = "InvalidJson error caused by: {err}", err = _0)]
InvalidJson(serde_json::Error),
}
impl std::error::Error for RequestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
RequestError::ApiError { .. } => None,
RequestError::NetworkError(err) => Some(err),
RequestError::InvalidJson(err) => Some(err),
}
}
}
pub type ResponseResult<T> = Result<T, RequestError>; pub type ResponseResult<T> = Result<T, RequestError>;
@ -93,6 +67,7 @@ pub mod answer_pre_checkout_query;
pub mod answer_shipping_query; pub mod answer_shipping_query;
pub mod edit_message_live_location; pub mod edit_message_live_location;
pub mod forward_message; pub mod forward_message;
pub mod get_chat;
pub mod get_file; pub mod get_file;
pub mod get_me; pub mod get_me;
pub mod get_user_profile_photos; pub mod get_user_profile_photos;

View file

@ -1,4 +1,4 @@
use crate::core::requests::RequestContext; use crate::requests::RequestContext;
//TODO:: need implementation //TODO:: need implementation
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]

View file

@ -1,8 +1,8 @@
use crate::core::{ use crate::{
network, network,
requests::form_builder::FormBuilder,
requests::{ requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult, form_builder::FormBuilder, ChatId, Request, RequestContext,
RequestFuture, ResponseResult,
}, },
types::{InputFile, Message, ParseMode, ReplyMarkup}, 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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Duration of the audio in seconds /// Duration of the audio in seconds
pub duration: Option<i32>, pub duration: Option<i32>,
@ -55,7 +55,7 @@ pub struct SendAudio<'a> {
/// sound. /// sound.
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i32>,
pub reply_markup: Option<ReplyMarkup>, pub reply_markup: Option<ReplyMarkup>,
} }
@ -174,7 +174,7 @@ impl<'a> SendAudio<'a> {
self self
} }
pub fn reply_to_message_id<T: Into<i64>>( pub fn reply_to_message_id<T: Into<i32>>(
mut self, mut self,
reply_to_message_id: T, reply_to_message_id: T,
) -> Self { ) -> Self {

View file

@ -1,12 +1,16 @@
use crate::core::network; use crate::{
use crate::core::requests::{ChatId, Request, RequestContext, RequestFuture, ResponseResult}; network,
use crate::core::types::Message; requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
};
///Use this method when you need to tell the user that something is happening on the bot's side. ///Use this method when you need to tell the user that something is happening
///The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). /// on the bot's side. The status is set for 5 seconds or less (when a message
///Returns True on success. /// arrives from your bot, Telegram clients clear its typing status).
/// Returns True on success.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendChatAction<'a> { pub struct SendChatAction<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier for the target chat or /// Unique identifier for the target chat or
@ -22,7 +26,7 @@ struct SendChatAction<'a> {
#[derive(Debug, Serialize, From, Clone)] #[derive(Debug, Serialize, From, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
enum ChatAction { pub enum ChatAction {
Typing, Typing,
UploadPhoto, UploadPhoto,
RecordVideo, RecordVideo,
@ -46,7 +50,7 @@ impl<'a> Request<'a> for SendChatAction<'a> {
"sendChatAction", "sendChatAction",
&self, &self,
) )
.await .await
}) })
} }
} }
@ -65,19 +69,18 @@ impl<'a> SendChatAction<'a> {
} }
pub fn chat_id<T>(mut self, chat_id: T) -> Self pub fn chat_id<T>(mut self, chat_id: T) -> Self
where where
T: Into<ChatId>, T: Into<ChatId>,
{ {
self.chat_id = chat_id.into(); self.chat_id = chat_id.into();
self self
} }
pub fn action<T>(mut self, action: T) -> Self pub fn action<T>(mut self, action: T) -> Self
where where
T: Into<ChatAction>, T: Into<ChatAction>,
{ {
self.action = action.into(); self.action = action.into();
self self
} }
} }

View file

@ -1,13 +1,15 @@
use crate::core::network; use crate::{
use crate::core::requests::{ network,
ChatId, Request, RequestContext, RequestFuture, ResponseResult, requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{Message, ReplyMarkup},
}; };
use crate::core::types::{Message, ReplyMarkup};
/// Use this method to send phone contacts. /// Use this method to send phone contacts.
/// returned. /// returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendContact<'a> { pub struct SendContact<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier for the target chat or /// Unique identifier for the target chat or

View file

@ -1,4 +1,4 @@
use crate::core::{ use crate::{
network, network,
requests::{ requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult, ChatId, Request, RequestContext, RequestFuture, ResponseResult,
@ -33,7 +33,7 @@ pub struct SendLocation<'a> {
disable_notification: Option<bool>, disable_notification: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
reply_to_message_id: Option<i64>, reply_to_message_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
reply_markup: Option<ReplyMarkup>, reply_markup: Option<ReplyMarkup>,
} }
@ -98,7 +98,7 @@ impl<'a> SendLocation<'a> {
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, val: T) -> Self { pub fn reply_to_message_id<T: Into<i32>>(mut self, val: T) -> Self {
self.reply_to_message_id = Some(val.into()); self.reply_to_message_id = Some(val.into());
self self
} }

View file

@ -1,4 +1,6 @@
use crate::core::{ use apply::Apply;
use crate::{
network::request_multipart, network::request_multipart,
requests::{ requests::{
form_builder::FormBuilder, ChatId, Request, RequestContext, form_builder::FormBuilder, ChatId, Request, RequestContext,
@ -6,7 +8,6 @@ use crate::core::{
}, },
types::{InputFile, InputMedia, Message}, types::{InputFile, InputMedia, Message},
}; };
use apply::Apply;
/// Use this method to send a group of photos or videos as an album. /// Use this method to send a group of photos or videos as an album.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -17,7 +18,7 @@ pub struct SendMediaGroup<'a> {
pub media: Vec<InputMedia>, pub media: Vec<InputMedia>,
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i32>,
} }
impl<'a> Request<'a> for SendMediaGroup<'a> { impl<'a> Request<'a> for SendMediaGroup<'a> {
@ -96,7 +97,7 @@ impl<'a> SendMediaGroup<'a> {
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, val: T) -> Self { pub fn reply_to_message_id<T: Into<i32>>(mut self, val: T) -> Self {
self.reply_to_message_id = Some(val.into()); self.reply_to_message_id = Some(val.into());
self self
} }

View file

@ -1,11 +1,7 @@
use crate::core::{ use crate::{
network, network,
requests::{ requests::{
ChatId, ChatId, Request, RequestContext, RequestFuture, ResponseResult,
Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::{Message, ParseMode, ReplyMarkup}, types::{Message, ParseMode, ReplyMarkup},
}; };
@ -27,10 +23,10 @@ pub struct SendMessage<'a> {
/// if you want Telegram apps to show [bold, italic, fixed-width text /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Disables link previews for links in this message /// Disables link previews for links in this message
@ -42,7 +38,7 @@ pub struct SendMessage<'a> {
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<ReplyMarkup>, pub reply_markup: Option<ReplyMarkup>,
} }
@ -106,7 +102,7 @@ impl<'a> SendMessage<'a> {
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, val: T) -> Self { pub fn reply_to_message_id<T: Into<i32>>(mut self, val: T) -> Self {
self.reply_to_message_id = Some(val.into()); self.reply_to_message_id = Some(val.into());
self self
} }

View file

@ -1,4 +1,4 @@
use crate::core::{ use crate::{
network, network,
requests::{ requests::{
form_builder::FormBuilder, ChatId, Request, RequestContext, form_builder::FormBuilder, ChatId, Request, RequestContext,
@ -30,16 +30,16 @@ pub struct SendPhoto<'a> {
/// if you want Telegram apps to show [bold, italic, fixed-width text /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Sends the message silently. Users will receive a notification with no /// Sends the message silently. Users will receive a notification with no
/// sound. /// sound.
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i32>,
pub reply_markup: Option<ReplyMarkup>, pub reply_markup: Option<ReplyMarkup>,
} }
@ -125,7 +125,7 @@ impl<'a> SendPhoto<'a> {
self self
} }
pub fn reply_to_message_id<T: Into<i64>>( pub fn reply_to_message_id<T: Into<i32>>(
mut self, mut self,
reply_to_message_id: T, reply_to_message_id: T,
) -> Self { ) -> Self {

View file

@ -1,13 +1,15 @@
use crate::core::network; use crate::{
use crate::core::requests::{ network,
ChatId, Request, RequestContext, RequestFuture, ResponseResult, 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 /// 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. /// private chat. On success, the sent Message is returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendPoll<'a> { pub struct SendPoll<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// identifier for the target chat or username of the target channel (in /// identifier for the target chat or username of the target channel (in

View file

@ -1,13 +1,15 @@
use crate::core::network; use crate::{
use crate::core::requests::{ network,
ChatId, Request, RequestContext, RequestFuture, ResponseResult, requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{Message, ReplyMarkup},
}; };
use crate::core::types::{Message, ReplyMarkup};
/// Use this method to send information about a venue. /// Use this method to send information about a venue.
/// Message is returned. /// Message is returned.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct SendVenue<'a> { pub struct SendVenue<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier for the target chat or /// Unique identifier for the target chat or
@ -62,7 +64,7 @@ impl<'a> Request<'a> for SendVenue<'a> {
} }
impl<'a> SendVenue<'a> { impl<'a> SendVenue<'a> {
pub fn new( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
chat_id: ChatId, chat_id: ChatId,
latitude: f64, latitude: f64,

View file

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

View file

@ -1,4 +1,4 @@
use crate::core::requests::RequestContext; use crate::requests::RequestContext;
//TODO:: need implementation //TODO:: need implementation
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]

View file

@ -2,11 +2,7 @@ use std::path::PathBuf;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use reqwest::r#async::multipart::Part; use reqwest::r#async::multipart::Part;
use tokio::{ use tokio::{codec::FramedRead, prelude::*};
prelude::*,
codec::FramedRead,
};
struct FileDecoder; struct FileDecoder;

View file

@ -1,4 +1,4 @@
use crate::core::types::PhotoSize; use crate::types::PhotoSize;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video /// This object represents an animation file (GIF or H.264/MPEG-4 AVC video
@ -19,7 +19,7 @@ pub struct Animation {
/// Optional. MIME type of the file as defined by sender /// Optional. MIME type of the file as defined by sender
pub mime_type: Option<String>, pub mime_type: Option<String>,
/// Optional. File size /// Optional. File size
pub file_size: Option<u32> pub file_size: Option<u32>,
} }
#[cfg(test)] #[cfg(test)]
@ -51,11 +51,11 @@ mod tests {
file_id: "id".to_string(), file_id: "id".to_string(),
width: 320, width: 320,
height: 320, height: 320,
file_size: Some(3452) file_size: Some(3452),
}), }),
file_name: Some("some".to_string()), file_name: Some("some".to_string()),
mime_type: Some("gif".to_string()), mime_type: Some("gif".to_string()),
file_size: Some(6500) file_size: Some(6500),
}; };
let actual = serde_json::from_str::<Animation>(json).unwrap(); let actual = serde_json::from_str::<Animation>(json).unwrap();
assert_eq!(actual, expected) assert_eq!(actual, expected)

51
src/types/audio.rs Normal file
View 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)
}
}

View file

@ -1,2 +1,2 @@
/// A placeholder, currently holds no information. Use [BotFather](https://t.me/botfather) to set up your game. /// A placeholder, currently holds no information. Use [BotFather](https://t.me/botfather) to set up your game.
pub struct CallbackGame; pub struct CallbackGame;

View 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);
}
}

View file

@ -1,5 +1,4 @@
use crate::core::types::{ChatPermissions, ChatPhoto, Message}; use crate::types::{ChatPermissions, ChatPhoto, Message};
#[derive(Debug, Deserialize, PartialEq, Clone)] #[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Chat { pub struct Chat {
@ -9,7 +8,6 @@ pub struct Chat {
pub photo: Option<ChatPhoto>, pub photo: Option<ChatPhoto>,
} }
#[derive(Debug, Deserialize, PartialEq, Clone)] #[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(untagged)] #[serde(untagged)]
pub enum ChatKind { pub enum ChatKind {
@ -82,7 +80,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::core::types::*; use crate::types::*;
use serde_json::from_str; use serde_json::from_str;
#[test] #[test]

View file

@ -1,4 +1,4 @@
use crate::core::types::{ChatMemberStatus, User}; use crate::types::User;
/// This object contains information about one member of the chat. /// This object contains information about one member of the chat.
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
@ -54,3 +54,74 @@ pub struct ChatMember {
/// his messages, implies can_send_media_messages /// his messages, implies can_send_media_messages
pub can_add_web_page_previews: Option<bool>, 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)
}
}

View file

@ -1,4 +1,4 @@
use crate::core::types::{User, Location}; use crate::types::{Location, User};
#[derive(Debug, Deserialize, Clone, PartialEq)] #[derive(Debug, Deserialize, Clone, PartialEq)]
/// Represents a result of an inline query that was chosen by the user and /// Represents a result of an inline query that was chosen by the user and
@ -11,9 +11,9 @@ pub struct ChosenInlineResult {
pub from: User, pub from: User,
/// Optional. Sender location, only for bots that require user location /// Optional. Sender location, only for bots that require user location
pub location: Option<Location>, pub location: Option<Location>,
/// Optional. Identifier of the sent inline message. Available only if there is an inline /// Optional. Identifier of the sent inline message. Available only if
/// keyboard attached to the message. Will be also received in callback queries and can /// there is an inline keyboard attached to the message. Will be also
/// be used to edit the message. /// received in callback queries and can be used to edit the message.
pub inline_message_id: Option<String>, pub inline_message_id: Option<String>,
/// The query that was used to obtain the result /// The query that was used to obtain the result
pub query: String, pub query: String,

View file

@ -8,8 +8,8 @@ pub struct Contact {
/// Optional. Contact's last name /// Optional. Contact's last name
pub last_name: Option<String>, pub last_name: Option<String>,
/// Optional. Contact's user identifier in Telegram /// Optional. Contact's user identifier in Telegram
pub user_id: Option<i64>, pub user_id: Option<i32>,
/// Optional. Additional data about the contact in the form of a /// Optional. Additional data about the contact in the form of a
/// [vCard](https://en.wikipedia.org/wiki/VCard) /// [vCard](https://en.wikipedia.org/wiki/VCard)
pub vcard: Option<String>, pub vcard: Option<String>,
} }

View file

@ -1,4 +1,4 @@
use crate::core::types::PhotoSize; use crate::types::PhotoSize;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct Document { pub struct Document {

View file

@ -2,5 +2,5 @@
pub struct File { pub struct File {
pub file_id: String, pub file_id: String,
pub file_size: u32, pub file_size: u32,
pub file_path: String pub file_path: String,
} }

View file

@ -1,10 +1,10 @@
use serde::Deserialize; use serde::Deserialize;
use crate::core::types::{MessageEntity, PhotoSize, Animation}; use crate::types::{Animation, MessageEntity, PhotoSize};
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
/// This object represents a game. Use BotFather to create and edit games, their short names /// This object represents a game. Use BotFather to create and edit games, their
/// will act as unique identifiers. /// short names will act as unique identifiers.
pub struct Game { pub struct Game {
/// Title of the game /// Title of the game
pub title: String, pub title: String,
@ -12,13 +12,15 @@ pub struct Game {
pub description: String, pub description: String,
/// Photo that will be displayed in the game message in chats. /// Photo that will be displayed in the game message in chats.
pub photo: Vec<PhotoSize>, pub photo: Vec<PhotoSize>,
/// Optional. Brief description of the game or high scores included in the game message. /// Optional. Brief description of the game or high scores included in the
/// Can be automatically edited to include current high scores for the game when /// game message. Can be automatically edited to include current high
/// the bot calls setGameScore, or manually edited using editMessageText. 0-4096 characters. /// scores for the game when the bot calls setGameScore, or manually
/// edited using editMessageText. 0-4096 characters.
pub text: Option<String>, pub text: Option<String>,
/// Optional. Special entities that appear in text, such as usernames, URLs, bot commands, etc. /// Optional. Special entities that appear in text, such as usernames,
/// URLs, bot commands, etc.
pub text_entities: Option<Vec<MessageEntity>>, pub text_entities: Option<Vec<MessageEntity>>,
/// Optional. Animation that will be displayed in the game message in chats. /// Optional. Animation that will be displayed in the game message in
/// Upload via BotFather /// chats. Upload via BotFather
pub animation: Option<Animation>, pub animation: Option<Animation>,
} }

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
use crate::core::types::user::User; use crate::types::user::User;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
/// This object represents one row of the high scores table for a game. /// This object represents one row of the high scores table for a game.
@ -11,4 +11,4 @@ pub struct GameHighScore {
pub user: User, pub user: User,
/// Score /// Score
pub score: u32, pub score: u32,
} }

View file

@ -45,7 +45,7 @@ pub enum InlineKeyboardButtonKind {
/// ///
/// Example: /// Example:
/// ```edition2018 /// ```edition2018
/// use async_telegram_bot::core::types::InlineKeyboardButton; /// use async_telegram_bot::types::InlineKeyboardButton;
/// ///
/// fn main() { /// fn main() {
/// let url_button = InlineKeyboardButton::url( /// let url_button = InlineKeyboardButton::url(
@ -62,32 +62,37 @@ impl InlineKeyboardButton {
} }
} }
pub fn callback(text: String, callback_data: String) pub fn callback(
-> InlineKeyboardButton { text: String,
callback_data: String,
) -> InlineKeyboardButton {
InlineKeyboardButton { InlineKeyboardButton {
text, text,
kind: InlineKeyboardButtonKind::CallbackData(callback_data), kind: InlineKeyboardButtonKind::CallbackData(callback_data),
} }
} }
pub fn switch_inline_query(text: String, switch_inline_query: String) pub fn switch_inline_query(
-> InlineKeyboardButton { text: String,
switch_inline_query: String,
) -> InlineKeyboardButton {
InlineKeyboardButton { InlineKeyboardButton {
text, text,
kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query) kind: InlineKeyboardButtonKind::SwitchInlineQuery(
switch_inline_query,
),
} }
} }
pub fn switch_inline_query_current_chat( pub fn switch_inline_query_current_chat(
text: String, text: String,
switch_inline_query_current_chat: String switch_inline_query_current_chat: String,
) -> InlineKeyboardButton { ) -> InlineKeyboardButton {
InlineKeyboardButton { InlineKeyboardButton {
text, text,
kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat( kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat(
switch_inline_query_current_chat switch_inline_query_current_chat,
) ),
} }
} }
} }

View file

@ -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 /// This object represents an inline keyboard that appears right next to the
/// message it belongs to. /// message it belongs to.
@ -15,25 +15,21 @@ pub struct InlineKeyboardMarkup {
/// Build Markup /// Build Markup
/// ///
/// Example: /// Example:
/// ```edition2018 /// ```
/// use async_telegram_bot::core::types::{ /// use async_telegram_bot::types::{
/// InlineKeyboardMarkup, /// InlineKeyboardButton, InlineKeyboardMarkup,
/// InlineKeyboardButton
/// }; /// };
/// ///
/// fn main() { /// let url_button = InlineKeyboardButton::url(
/// let url_button = InlineKeyboardButton::url( /// "text".to_string(),
/// "text".to_string(), /// "http://url.com".to_string(),
/// "http://url.com".to_string() /// );
/// ); /// let keyboard = InlineKeyboardMarkup::new().append_row(vec![url_button]);
/// let keyboard = InlineKeyboardMarkup::new()
/// .row(vec![url_button]);
/// }
/// ``` /// ```
impl InlineKeyboardMarkup { impl InlineKeyboardMarkup {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
inline_keyboard: vec![] inline_keyboard: vec![],
} }
} }
@ -42,11 +38,14 @@ impl InlineKeyboardMarkup {
self self
} }
pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize) pub fn append_to_row(
-> Self { mut self,
button: InlineKeyboardButton,
index: usize,
) -> Self {
match self.inline_keyboard.get_mut(index) { match self.inline_keyboard.get_mut(index) {
Some(buttons) => buttons.push(button), Some(buttons) => buttons.push(button),
None => self.inline_keyboard.push(vec![button]) None => self.inline_keyboard.push(vec![button]),
}; };
self self
} }
@ -69,15 +68,13 @@ mod tests {
let markup = InlineKeyboardMarkup::new() let markup = InlineKeyboardMarkup::new()
.append_row(vec![button1.clone(), button2.clone()]); .append_row(vec![button1.clone(), button2.clone()]);
let expected = InlineKeyboardMarkup { let expected = InlineKeyboardMarkup {
inline_keyboard: vec![ inline_keyboard: vec![vec![button1.clone(), button2.clone()]],
vec![button1.clone(), button2.clone()]
]
}; };
assert_eq!(markup, expected); assert_eq!(markup, expected);
} }
#[test] #[test]
fn append_to_row__existent_row() { fn append_to_row_existent_row() {
let button1 = InlineKeyboardButton::url( let button1 = InlineKeyboardButton::url(
"text 1".to_string(), "text 1".to_string(),
"url 1".to_string(), "url 1".to_string(),
@ -90,15 +87,13 @@ mod tests {
.append_row(vec![button1.clone()]) .append_row(vec![button1.clone()])
.append_to_row(button2.clone(), 0); .append_to_row(button2.clone(), 0);
let expected = InlineKeyboardMarkup { let expected = InlineKeyboardMarkup {
inline_keyboard: vec![ inline_keyboard: vec![vec![button1.clone(), button2.clone()]],
vec![button1.clone(), button2.clone()]
]
}; };
assert_eq!(markup, expected); assert_eq!(markup, expected);
} }
#[test] #[test]
fn append_to_row__nonexistent_row() { fn append_to_row_nonexistent_row() {
let button1 = InlineKeyboardButton::url( let button1 = InlineKeyboardButton::url(
"text 1".to_string(), "text 1".to_string(),
"url 1".to_string(), "url 1".to_string(),
@ -111,10 +106,7 @@ mod tests {
.append_row(vec![button1.clone()]) .append_row(vec![button1.clone()])
.append_to_row(button2.clone(), 1); .append_to_row(button2.clone(), 1);
let expected = InlineKeyboardMarkup { let expected = InlineKeyboardMarkup {
inline_keyboard: vec![ inline_keyboard: vec![vec![button1.clone()], vec![button2.clone()]],
vec![button1.clone()],
vec![button2.clone()]
]
}; };
assert_eq!(markup, expected); assert_eq!(markup, expected);
} }

15
src/types/inline_query.rs Normal file
View file

@ -0,0 +1,15 @@
use crate::types::{Location, User};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQuery {
/// Unique identifier for this query
pub id: String,
/// Sender
pub from: User,
/// Optional. Sender location, only for bots that request user location
pub location: Option<Location>,
/// Text of the query (up to 512 characters)
pub query: String,
/// Offset of the results to be returned, can be controlled by the bot
pub offset: String,
}

View file

@ -0,0 +1,115 @@
use crate::types::{
InlineQueryResultArticle, InlineQueryResultAudio,
InlineQueryResultCachedAudio, InlineQueryResultCachedDocument,
InlineQueryResultCachedGif, InlineQueryResultCachedMpeg4Gif,
InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker,
InlineQueryResultCachedVideo, InlineQueryResultCachedVoice,
InlineQueryResultContact, InlineQueryResultDocument, InlineQueryResultGame,
InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif,
InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo,
InlineQueryResultVoice,
};
/// This object represents one result of an inline query.
#[derive(Debug, Serialize, PartialEq, Clone, From)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum InlineQueryResult {
#[serde(rename = "audio")]
CachedAudio(InlineQueryResultCachedAudio),
#[serde(rename = "document")]
CachedDocument(InlineQueryResultCachedDocument),
#[serde(rename = "gif")]
CachedGif(InlineQueryResultCachedGif),
#[serde(rename = "mpeg4_gif")]
CachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif),
#[serde(rename = "photo")]
CachedPhoto(InlineQueryResultCachedPhoto),
#[serde(rename = "sticker")]
CachedSticker(InlineQueryResultCachedSticker),
#[serde(rename = "video")]
CachedVideo(InlineQueryResultCachedVideo),
#[serde(rename = "voice")]
CachedVoice(InlineQueryResultCachedVoice),
Article(InlineQueryResultArticle),
Audio(InlineQueryResultAudio),
Contact(InlineQueryResultContact),
Game(InlineQueryResultGame),
Document(InlineQueryResultDocument),
Gif(InlineQueryResultGif),
Location(InlineQueryResultLocation),
#[serde(rename = "mpeg4_gif")]
Mpeg4Gif(InlineQueryResultMpeg4Gif),
Photo(InlineQueryResultPhoto),
Venue(InlineQueryResultVenue),
Video(InlineQueryResultVideo),
Voice(InlineQueryResultVoice),
}
#[cfg(test)]
mod tests {
use crate::types::inline_keyboard_markup::InlineKeyboardMarkup;
use crate::types::parse_mode::ParseMode;
use crate::types::{
InlineQueryResult, InlineQueryResultCachedAudio, InputMessageContent,
};
#[test]
fn into() {
let structure =
InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio {
id: String::from("id"),
audio_file_id: String::from("audio_file_id"),
caption: None,
parse_mode: None,
reply_markup: None,
input_message_content: None,
});
let _: InlineQueryResult = structure.into();
}
#[test]
fn cached_audio_min_serialize() {
let structure =
InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio {
id: String::from("id"),
audio_file_id: String::from("audio_file_id"),
caption: None,
parse_mode: None,
reply_markup: None,
input_message_content: None,
});
let expected_json =
r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id"}"#;
let actual_json = serde_json::to_string(&structure).unwrap();
assert_eq!(expected_json, actual_json);
}
#[test]
fn cached_audio_full_serialize() {
let structure =
InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio {
id: String::from("id"),
audio_file_id: String::from("audio_file_id"),
caption: Some(String::from("caption")),
parse_mode: Some(ParseMode::HTML),
reply_markup: Some(InlineKeyboardMarkup::new()),
input_message_content: Some(InputMessageContent::Text {
message_text: String::from("message_text"),
parse_mode: Some(ParseMode::Markdown),
disable_web_page_preview: Some(true),
}),
});
let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"Markdown","disable_web_page_preview":true}}"#;
let actual_json = serde_json::to_string(&structure).unwrap();
assert_eq!(expected_json, actual_json);
}
// TODO: Add more tests
}

View file

@ -0,0 +1,33 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultArticle {
/// Unique identifier for this result, 1-64 Bytes
pub id: String,
/// Title of the result
pub title: String,
/// Content of the message to be sent
pub input_message_content: InputMessageContent,
/// Optional. Inline keyboard attached to the message
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
/// Optional. URL of the result
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// Optional. Pass True, if you don't want the URL to be shown in the
/// message
#[serde(skip_serializing_if = "Option::is_none")]
pub hide_url: Option<bool>,
/// Optional. Short description of the result
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// Optional. Url of the thumbnail for the result
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_url: Option<String>,
/// Optional. Thumbnail width
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
/// Optional. Thumbnail height
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
}

View file

@ -0,0 +1,20 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultAudio {
pub id: String,
pub audio_url: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub performer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_duration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,15 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedAudio {
pub id: String,
pub audio_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,18 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedDocument {
pub id: String,
pub title: String,
pub document_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,17 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedGif {
pub id: String,
pub gif_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,17 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedMpeg4Gif {
pub id: String,
pub mpeg4_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,19 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedPhoto {
pub id: String,
pub photo_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,11 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedSticker {
pub id: String,
pub sticker_file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,18 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedVideo {
pub id: String,
pub video_file_id: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,16 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultCachedVoice {
pub id: String,
pub voice_file_id: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,22 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultContact {
pub id: String,
pub phone_number: String,
pub first_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vcard: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
}

View file

@ -0,0 +1,25 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultDocument {
pub id: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
pub document_url: String,
pub mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
}

View file

@ -0,0 +1,9 @@
use crate::types::InlineKeyboardMarkup;
#[derive(Debug, Serialize, Hash, PartialEq, Eq, Clone)]
pub struct InlineQueryResultGame {
pub id: String,
pub game_short_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
}

View file

@ -0,0 +1,24 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultGif {
pub id: String,
pub gif_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub gif_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gif_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gif_duration: Option<i32>,
pub thumb_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,21 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultLocation {
pub id: String,
pub latitude: f64,
pub longitude: f64,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub live_period: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
}

View file

@ -0,0 +1,24 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultMpeg4Gif {
pub id: String,
pub mpeg4_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mpeg4_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mpeg4_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mpeg4_duration: Option<i32>,
pub thumb_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,27 @@
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,
pub photo_url: String,
pub thumb_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub photo_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub photo_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,24 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultVenue {
pub id: String,
pub latitude: f64,
pub longitude: f64,
pub title: String,
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub foursquare_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub foursquare_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb_height: Option<i32>,
}

View file

@ -0,0 +1,26 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultVideo {
pub id: String,
pub video_url: String,
pub mime_type: String,
pub thumb_url: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_width: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_height: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_duration: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -0,0 +1,18 @@
use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode};
#[derive(Debug, Serialize, PartialEq, Clone)]
pub struct InlineQueryResultVoice {
pub id: String,
pub voice_url: String,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_duration: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_message_content: Option<InputMessageContent>,
}

View file

@ -1,4 +1,4 @@
use crate::core::types::{InputFile, ParseMode}; use crate::types::{InputFile, ParseMode};
// TODO: should variants use new-type? // TODO: should variants use new-type?
#[derive(Debug, Serialize, PartialEq, Eq, Clone)] #[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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
}, },
@ -35,7 +35,7 @@ pub enum InputMedia {
/// size. A thumbnails width and height should not exceed 320. /// size. A thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File]. /// 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>, thumb: Option<InputFile>,
/// Caption of the video to be sent, 0-1024 characters. /// Caption of the video to be sent, 0-1024 characters.
#[serde(skip_serializing_if = "Option::is_none")] #[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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
/// Video width /// Video width
@ -74,7 +74,7 @@ pub enum InputMedia {
/// size. A thumbnails width and height should not exceed 320. /// size. A thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File]. /// 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")] #[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>, thumb: Option<InputFile>,
/// Caption of the animation to be sent, 0-1024 characters /// 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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
/// Animation width /// Animation width
@ -110,7 +110,7 @@ pub enum InputMedia {
/// size. A thumbnails width and height should not exceed 320. /// size. A thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File]. /// 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")] #[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>, thumb: Option<InputFile>,
/// Caption of the audio to be sent, 0-1024 characters /// 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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<String>, parse_mode: Option<String>,
/// Duration of the audio in seconds /// Duration of the audio in seconds
@ -146,7 +146,7 @@ pub enum InputMedia {
/// size. A thumbnails width and height should not exceed 320. /// size. A thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File]. /// 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")] #[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>, thumb: Option<InputFile>,
/// Caption of the document to be sent, 0-1024 characters /// 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 /// if you want Telegram apps to show [bold, italic, fixed-width text
/// or inline URLs] in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
}, },

View file

@ -1,25 +1,27 @@
use serde::Serialize; use serde::Serialize;
use crate::core::types::ParseMode; use crate::types::ParseMode;
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
/// This object represents the content of a message to be sent as /// This object represents the content of a message to be sent as
/// a result of an inline query. /// a result of an inline query.
/// [More](https://core.telegram.org/bots/api#inputmessagecontent) /// [More](https://core.telegram.org/bots/api#inputmessagecontent)
pub enum InputMessageContent { pub enum InputMessageContent {
/// Represents the content of a text message to be sent as the result of an inline query. /// Represents the content of a text message to be sent as the result of an
/// inline query.
Text { Text {
/// Text of the message to be sent, 1-4096 characters /// Text of the message to be sent, 1-4096 characters
message_text: String, message_text: String,
/// Send [Markdown] or [HTML], /// Send [Markdown] or [HTML],
/// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] /// if you want Telegram apps to show [bold, italic, fixed-width text
/// in the media caption. /// or inline URLs] in the media caption.
/// ///
/// [Markdown]: crate::core::types::ParseMode::Markdown /// [Markdown]: crate::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode /// [bold, italic, fixed-width text or inline URLs]:
/// crate::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
@ -27,18 +29,21 @@ pub enum InputMessageContent {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
disable_web_page_preview: Option<bool>, disable_web_page_preview: Option<bool>,
}, },
/// Represents the content of a location message to be sent as the result of an inline query. /// Represents the content of a location message to be sent as the result
/// of an inline query.
Location { Location {
/// Latitude of the location in degrees /// Latitude of the location in degrees
latitude: f64, latitude: f64,
/// Longitude of the location in degrees /// Longitude of the location in degrees
longitude: f64, longitude: f64,
/// Period in seconds for which the location can be updated, should be between 60 and 86400. /// Period in seconds for which the location can be updated, should be
/// between 60 and 86400.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
live_period: Option<u32>, live_period: Option<u32>,
}, },
/// Represents the content of a venue message to be sent as the result of an inline query. /// Represents the content of a venue message to be sent as the result of
/// an inline query.
Venue { Venue {
/// Latitude of the venue in degrees /// Latitude of the venue in degrees
latitude: f64, latitude: f64,
@ -53,12 +58,14 @@ pub enum InputMessageContent {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
foursquare_id: Option<String>, foursquare_id: Option<String>,
/// Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, /// Foursquare type of the venue, if known. (For example,
/// “arts_entertainment/aquarium” or “food/icecream”.) /// “arts_entertainment/default”, “arts_entertainment/aquarium”
/// or “food/icecream”.)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
foursquare_type: Option<String>, foursquare_type: Option<String>,
}, },
/// Represents the content of a contact message to be sent as the result of an inline query. /// Represents the content of a contact message to be sent as the result of
/// an inline query.
Contact { Contact {
/// Contact's phone number /// Contact's phone number
phone_number: String, phone_number: String,
@ -108,8 +115,7 @@ mod tests {
#[test] #[test]
fn venue_serialize() { fn venue_serialize() {
let expected_json = r#"{"latitude":59.08,"longitude":38.4326,"title":"some title", let expected_json = r#"{"latitude":59.08,"longitude":38.4326,"title":"some title","address":"some address"}"#;
"address":"some address"}"#;
let venue_content = InputMessageContent::Venue { let venue_content = InputMessageContent::Venue {
latitude: 59.08, latitude: 59.08,
longitude: 38.4326, longitude: 38.4326,
@ -125,7 +131,8 @@ mod tests {
#[test] #[test]
fn contact_serialize() { fn contact_serialize() {
let expected_json = r#"{"phone_number":"+3800000000","first_name":"jhon"}"#; let expected_json =
r#"{"phone_number":"+3800000000","first_name":"jhon"}"#;
let contact_content = InputMessageContent::Contact { let contact_content = InputMessageContent::Contact {
phone_number: String::from("+3800000000"), phone_number: String::from("+3800000000"),
first_name: String::from("jhon"), first_name: String::from("jhon"),
@ -136,4 +143,4 @@ mod tests {
let actual_json = serde_json::to_string(&contact_content).unwrap(); let actual_json = serde_json::to_string(&contact_content).unwrap();
assert_eq!(expected_json, actual_json); assert_eq!(expected_json, actual_json);
} }
} }

View file

@ -4,5 +4,5 @@ pub struct Invoice {
pub description: String, pub description: String,
pub start_parameter: String, pub start_parameter: String,
pub currency: String, pub currency: String,
pub total_amount: i64, pub total_amount: i32,
} }

View file

@ -9,7 +9,7 @@ pub struct LabeledPrice {
/// amount = 145. See the exp parameter in [`currencies.json`](https://core.telegram.org/bots/payments/currencies.json), /// amount = 145. See the exp parameter in [`currencies.json`](https://core.telegram.org/bots/payments/currencies.json),
/// it shows the number of digits past the decimal point for each currency /// it shows the number of digits past the decimal point for each currency
/// (2 for the majority of currencies). /// (2 for the majority of currencies).
pub amount: i64, pub amount: i32,
} }
#[cfg(test)] #[cfg(test)]
@ -20,7 +20,7 @@ mod tests {
fn serialize() { fn serialize() {
let labeled_price = LabeledPrice { let labeled_price = LabeledPrice {
label: "Label".to_string(), label: "Label".to_string(),
amount: 60 amount: 60,
}; };
let expected = r#"{"label":"Label","amount":60}"#; let expected = r#"{"label":"Label","amount":60}"#;
let actual = serde_json::to_string(&labeled_price).unwrap(); let actual = serde_json::to_string(&labeled_price).unwrap();

View file

@ -5,4 +5,4 @@ pub struct Location {
pub longitude: f64, pub longitude: f64,
/// Latitude as defined by sender /// Latitude as defined by sender
pub latitude: f64, pub latitude: f64,
} }

View file

@ -7,4 +7,4 @@ pub struct LoginUrl {
pub bot_username: Option<String>, pub bot_username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub request_write_access: Option<bool>, pub request_write_access: Option<bool>,
} }

View file

@ -5,4 +5,3 @@ pub struct MaskPosition {
pub y_shift: f64, pub y_shift: f64,
pub scale: f64, pub scale: f64,
} }

View file

@ -1,4 +1,4 @@
use crate::core::types::{ use crate::types::{
Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup, Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup,
Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, Sticker, Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, Sticker,
SuccessfulPayment, User, Venue, Video, VideoNote, Voice, SuccessfulPayment, User, Venue, Video, VideoNote, Voice,
@ -7,17 +7,31 @@ use crate::core::types::{
#[derive(Debug, Deserialize, PartialEq, Clone)] #[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Message { pub struct Message {
#[serde(rename = "message_id")] #[serde(rename = "message_id")]
pub id: i64, pub id: i32,
pub date: i32, pub date: i32,
pub chat: Chat, pub chat: Chat,
#[serde(flatten)] #[serde(flatten)]
pub message_kind: MessageKind, pub kind: MessageKind,
}
impl Message {
fn text(&self) -> Option<&str> {
if let MessageKind::Common {
media_kind: MediaKind::Text { ref text, .. },
..
} = self.kind
{
Some(text)
} else {
None
}
}
} }
#[derive(Debug, Deserialize, PartialEq, Clone)] #[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(untagged)] #[serde(untagged)]
pub enum MessageKind { pub enum MessageKind {
IncomingMessage { Common {
#[serde(flatten)] #[serde(flatten)]
from: Sender, from: Sender,
#[serde(flatten)] #[serde(flatten)]
@ -55,8 +69,8 @@ pub enum MessageKind {
migrate_to_chat_id: i64, migrate_to_chat_id: i64,
migrate_from_chat_id: i64, migrate_from_chat_id: i64,
}, },
PinnedMessage { Pinned {
pinned_message: Box<Message>, pinned: Box<Message>,
}, },
Invoice { Invoice {
invoice: Invoice, invoice: Invoice,
@ -91,7 +105,7 @@ pub enum ForwardKind {
#[serde(rename = "forward_from_chat")] #[serde(rename = "forward_from_chat")]
chat: Chat, chat: Chat,
#[serde(rename = "forward_from_message_id")] #[serde(rename = "forward_from_message_id")]
message_id: i64, message_id: i32,
#[serde(rename = "forward_signature")] #[serde(rename = "forward_signature")]
signature: Option<String>, signature: Option<String>,
}, },
@ -189,7 +203,7 @@ pub enum MediaKind {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::core::types::*; use crate::types::*;
use serde_json::from_str; use serde_json::from_str;
#[test] #[test]

View file

@ -1,4 +1,4 @@
use crate::core::types::User; use crate::types::User;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct MessageEntity { pub struct MessageEntity {

156
src/types/mod.rs Normal file
View file

@ -0,0 +1,156 @@
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, ChatMemberStatus},
chat_permissions::ChatPermissions,
chat_photo::ChatPhoto,
chosen_inline_result::ChosenInlineResult,
contact::Contact,
document::Document,
file::File,
force_reply::ForceReply,
game::Game,
game_high_score::GameHighScore,
inline_keyboard_button::{InlineKeyboardButton, InlineKeyboardButtonKind},
inline_keyboard_markup::InlineKeyboardMarkup,
inline_query::InlineQuery,
inline_query_result::InlineQueryResult,
inline_query_result_article::InlineQueryResultArticle,
inline_query_result_audio::InlineQueryResultAudio,
inline_query_result_cached_audio::InlineQueryResultCachedAudio,
inline_query_result_cached_document::InlineQueryResultCachedDocument,
inline_query_result_cached_gif::InlineQueryResultCachedGif,
inline_query_result_cached_mpeg4_gif::InlineQueryResultCachedMpeg4Gif,
inline_query_result_cached_photo::InlineQueryResultCachedPhoto,
inline_query_result_cached_sticker::InlineQueryResultCachedSticker,
inline_query_result_cached_video::InlineQueryResultCachedVideo,
inline_query_result_cached_voice::InlineQueryResultCachedVoice,
inline_query_result_contact::InlineQueryResultContact,
inline_query_result_document::InlineQueryResultDocument,
inline_query_result_game::InlineQueryResultGame,
inline_query_result_gif::InlineQueryResultGif,
inline_query_result_location::InlineQueryResultLocation,
inline_query_result_mpeg4_gif::InlineQueryResultMpeg4Gif,
inline_query_result_photo::InlineQueryResultPhoto,
inline_query_result_venue::InlineQueryResultVenue,
inline_query_result_video::InlineQueryResultVideo,
inline_query_result_voice::InlineQueryResultVoice,
input_file::InputFile,
input_media::InputMedia,
input_message_content::InputMessageContent,
invoice::Invoice,
keyboard_button::KeyboardButton,
label_price::LabeledPrice,
location::Location,
login_url::LoginUrl,
mask_position::MaskPosition,
message::{
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
},
message_entity::MessageEntity,
order_info::OrderInfo,
parse_mode::ParseMode,
photo_size::PhotoSize,
poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery,
reply_keyboard_markup::ReplyKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_markup::ReplyMarkup,
response_parameters::ResponseParameters,
send_invoice::SendInvoice,
shipping_address::ShippingAddress,
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;
mod chat_permissions;
mod chat_photo;
mod chosen_inline_result;
mod contact;
mod document;
mod file;
mod force_reply;
mod game;
mod game_high_score;
mod inline_keyboard_button;
mod inline_keyboard_markup;
mod input_file;
mod input_media;
mod input_message_content;
mod invoice;
mod keyboard_button;
mod label_price;
mod location;
mod login_url;
mod mask_position;
mod message;
mod message_entity;
mod not_implemented_types;
mod order_info;
mod parse_mode;
mod photo_size;
mod poll;
mod pre_checkout_query;
mod reply_keyboard_markup;
mod reply_keyboard_remove;
mod reply_markup;
mod response_parameters;
mod send_invoice;
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;
mod inline_query_result_article;
mod inline_query_result_audio;
mod inline_query_result_cached_audio;
mod inline_query_result_cached_document;
mod inline_query_result_cached_gif;
mod inline_query_result_cached_mpeg4_gif;
mod inline_query_result_cached_photo;
mod inline_query_result_cached_sticker;
mod inline_query_result_cached_video;
mod inline_query_result_cached_voice;
mod inline_query_result_contact;
mod inline_query_result_document;
mod inline_query_result_game;
mod inline_query_result_gif;
mod inline_query_result_location;
mod inline_query_result_mpeg4_gif;
mod inline_query_result_photo;
mod inline_query_result_venue;
mod inline_query_result_video;
mod inline_query_result_voice;

View file

@ -0,0 +1,2 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct PassportData;

View file

@ -1,4 +1,4 @@
use crate::core::types::ShippingAddress; use crate::types::ShippingAddress;
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)] #[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)]
pub struct OrderInfo { pub struct OrderInfo {

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
/// ## Formatting options /// ## Formatting options
/// The Bot API supports basic formatting for messages. /// The Bot API supports basic formatting for messages.
/// You can use **bold** and *italic* text, as well as [inline links](https://example.com) and `pre-formatted code` in /// You can use **bold** and *italic* text, as well as [inline links](https://example.com) and `pre-formatted code` in

View file

@ -24,7 +24,7 @@ mod tests {
file_id: "id".to_string(), file_id: "id".to_string(),
width: 320, width: 320,
height: 320, height: 320,
file_size: Some(3452) file_size: Some(3452),
}; };
let actual = serde_json::from_str::<PhotoSize>(json).unwrap(); let actual = serde_json::from_str::<PhotoSize>(json).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);

Some files were not shown because too many files have changed in this diff Show more