diff --git a/Cargo.toml b/Cargo.toml index d4c2b459..b928b697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ lazy_static = "1.3" apply = "0.2.2" derive_more = "0.15.0" tokio = "0.2.0-alpha.4" -bytes = "0.4.12" \ No newline at end of file +bytes = "0.4.12" +futures-preview = "0.3.0-alpha.18" \ No newline at end of file diff --git a/src/bot/api.rs b/src/bot/api.rs new file mode 100644 index 00000000..d10d83b1 --- /dev/null +++ b/src/bot/api.rs @@ -0,0 +1,113 @@ +use crate::{ + bot::Bot, + requests::{ + ChatId, EditMessageLiveLocation, ForwardMessage, GetFile, GetMe, + SendAudio, SendLocation, SendMediaGroup, SendMessage, SendPhoto, + StopMessageLiveLocation, + }, + types::{InputFile, InputMedia}, +}; + +/// Telegram functions +impl Bot { + pub fn get_me(&self) -> GetMe { + GetMe::new(self.ctx()) + } + + pub fn send_message(&self, chat_id: C, text: T) -> SendMessage + where + C: Into, + T: Into, + { + SendMessage::new(self.ctx(), chat_id.into(), text.into()) + } + + pub fn edit_message_live_location( + &self, + latitude: Lt, + longitude: Lg, + ) -> EditMessageLiveLocation + where + Lt: Into, + Lg: Into, + { + EditMessageLiveLocation::new( + self.ctx(), + latitude.into(), + longitude.into(), + ) + } + + pub fn forward_message( + &self, + chat_id: C, + from_chat_id: F, + message_id: M, + ) -> ForwardMessage + where + C: Into, + F: Into, + M: Into, + { + ForwardMessage::new( + self.ctx(), + chat_id.into(), + from_chat_id.into(), + message_id.into(), + ) + } + + pub fn send_audio(&self, chat_id: C, audio: A) -> SendAudio + where + C: Into, + A: Into, + { + SendAudio::new(self.ctx(), chat_id.into(), audio.into()) + } + + pub fn send_location( + &self, + chat_id: C, + latitude: Lt, + longitude: Lg, + ) -> SendLocation + where + C: Into, + Lt: Into, + Lg: Into, + { + SendLocation::new( + self.ctx(), + chat_id.into(), + latitude.into(), + longitude.into(), + ) + } + + pub fn send_media_group(&self, chat_id: C, media: M) -> SendMediaGroup + where + C: Into, + M: Into>, + { + SendMediaGroup::new(self.ctx(), chat_id.into(), media.into()) + } + + pub fn send_photo(&self, chat_id: C, photo: P) -> SendPhoto + where + C: Into, + P: Into, + { + 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(&self, file_id: F) -> GetFile + where + F: Into, + { + GetFile::new(self.ctx(), file_id.into()) + } +} diff --git a/src/bot/download.rs b/src/bot/download.rs new file mode 100644 index 00000000..31f77bfd --- /dev/null +++ b/src/bot/download.rs @@ -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> { + /// 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( + &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>, reqwest::Error> + { + download_file_stream(&self.client, &self.token, path).await + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 1a818920..c346a1f1 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,22 +1,16 @@ +mod api; +mod download; + use reqwest::r#async::Client; -use crate::core::{ - requests::{ - edit_message_live_location::EditMessageLiveLocation, - forward_message::ForwardMessage, get_me::GetMe, send_audio::SendAudio, - send_location::SendLocation, send_media_group::SendMediaGroup, - send_message::SendMessage, send_photo::SendPhoto, - stop_message_live_location::StopMessageLiveLocation, ChatId, - RequestContext, - }, - types::{InputFile, InputMedia}, -}; +use crate::requests::RequestContext; pub struct Bot { token: String, client: Client, } +/// Constructors impl Bot { pub fn new(token: &str) -> Self { Bot { @@ -31,7 +25,9 @@ impl Bot { client, } } +} +impl Bot { fn ctx(&self) -> RequestContext { RequestContext { token: &self.token, @@ -39,100 +35,3 @@ impl Bot { } } } - -/// Telegram functions -impl Bot { - pub fn get_me(&self) -> GetMe { - GetMe::new(self.ctx()) - } - - pub fn send_message(&self, chat_id: C, text: T) -> SendMessage - where - C: Into, - T: Into, - { - SendMessage::new(self.ctx(), chat_id.into(), text.into()) - } - - pub fn edit_message_live_location( - &self, - latitude: Lt, - longitude: Lg, - ) -> EditMessageLiveLocation - where - Lt: Into, - Lg: Into, - { - EditMessageLiveLocation::new( - self.ctx(), - latitude.into(), - longitude.into(), - ) - } - - pub fn forward_message( - &self, - chat_id: C, - from_chat_id: F, - message_id: M, - ) -> ForwardMessage - where - C: Into, - F: Into, - M: Into, - { - ForwardMessage::new( - self.ctx(), - chat_id.into(), - from_chat_id.into(), - message_id.into(), - ) - } - - pub fn send_audio(&self, chat_id: C, audio: A) -> SendAudio - where - C: Into, - A: Into, - { - SendAudio::new(self.ctx(), chat_id.into(), audio.into()) - } - - pub fn send_location( - &self, - chat_id: C, - latitude: Lt, - longitude: Lg, - ) -> SendLocation - where - C: Into, - Lt: Into, - Lg: Into, - { - SendLocation::new( - self.ctx(), - chat_id.into(), - latitude.into(), - longitude.into(), - ) - } - - pub fn send_media_group(&self, chat_id: C, media: M) -> SendMediaGroup - where - C: Into, - M: Into>, - { - SendMediaGroup::new(self.ctx(), chat_id.into(), media.into()) - } - - pub fn send_photo(&self, chat_id: C, photo: P) -> SendPhoto - where - C: Into, - P: Into, - { - SendPhoto::new(self.ctx(), chat_id.into(), photo.into()) - } - - pub fn stop_message_live_location(&self) -> StopMessageLiveLocation { - StopMessageLiveLocation::new(self.ctx()) - } -} diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index 8fd20ac7..00000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod network; -pub mod requests; -pub mod types; diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs deleted file mode 100644 index 244c6d63..00000000 --- a/src/core/network/mod.rs +++ /dev/null @@ -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( - client: &Client, - token: &str, - method_name: &str, - params: Option
, -) -> ResponseResult { - 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::>( - &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( - client: &Client, - token: &str, - method_name: &str, - params: &P, -) -> ResponseResult { - 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::>( - &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 { - 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, - }, -} - -#[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" - ); - } -} diff --git a/src/core/requests/restrict_chat_member.rs b/src/core/requests/restrict_chat_member.rs deleted file mode 100644 index 161f45ab..00000000 --- a/src/core/requests/restrict_chat_member.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::core::requests::RequestContext; -//TODO:: need implementation - -#[derive(Debug, Clone, Serialize)] -struct RestrictChatMember<'a> { - #[serde(skip_serializing)] - ctx: RequestContext<'a>, -} diff --git a/src/core/types/audio.rs b/src/core/types/audio.rs deleted file mode 100644 index 304d4946..00000000 --- a/src/core/types/audio.rs +++ /dev/null @@ -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, - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub file_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub thumb: Option, -} diff --git a/src/core/types/callback_query.rs b/src/core/types/callback_query.rs deleted file mode 100644 index edcbe996..00000000 --- a/src/core/types/callback_query.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::core::types::{Message, User}; - -/// This object represents an incoming callback query from a callback button in -/// an inline keyboard. -#[derive(Debug, Deserialize, PartialEq, Clone)] -pub struct CallbackQuery { - /// Unique identifier for this query - pub id: String, - /// Sender - pub from: User, - /// Message with the callback button that originated the query. - /// Note that message content and message date will not be available if the - /// message is too old - pub message: Message, - /// Global identifier, uniquely corresponding to the chat to which the - /// message with the callback button was sent. Useful for high scores - /// in games. - pub chat_instance: String, - /// Data associated with the callback button. Be aware that a bad client - /// can send arbitrary data in this field. - pub data: String, -} diff --git a/src/core/types/not_implemented_types.rs b/src/core/types/not_implemented_types.rs deleted file mode 100644 index c2700b2d..00000000 --- a/src/core/types/not_implemented_types.rs +++ /dev/null @@ -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; diff --git a/src/core/types/user.rs b/src/core/types/user.rs deleted file mode 100644 index 953e1a23..00000000 --- a/src/core/types/user.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Serialize)] -pub struct User { - pub id: i32, - pub is_bot: bool, - pub first_name: String, - pub last_name: Option, - pub username: Option, - pub language_code: Option, -} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 00000000..6482793d --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,60 @@ +use reqwest::StatusCode; + +// +#[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), + } + } +} +// + +// +#[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), + } + } +} +// diff --git a/src/lib.rs b/src/lib.rs index 7425f248..34052315 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,5 +3,11 @@ extern crate derive_more; #[macro_use] extern crate serde; +mod network; +mod errors; + pub mod bot; -pub mod core; +pub mod requests; +pub mod types; + +pub use errors::{DownloadError, RequestError}; diff --git a/src/network/download.rs b/src/network/download.rs new file mode 100644 index 00000000..96bc646e --- /dev/null +++ b/src/network/download.rs @@ -0,0 +1,41 @@ +use bytes::Buf; +use futures::StreamExt; +use reqwest::r#async::{Chunk, Client}; +use tokio::{ + io::{AsyncWrite, AsyncWriteExt}, + stream::Stream, +}; + +use crate::{ + network::{file_url, TELEGRAM_API_URL}, + DownloadError, +}; + +pub async fn download_file( + client: &Client, + token: &str, + path: &str, + destination: &mut D, +) -> Result<(), DownloadError> +where + D: AsyncWrite + Unpin, +{ + let mut stream = download_file_stream(client, token, path).await?; + + while let Some(chunk) = stream.next().await { + let chunk = chunk?; + destination.write_all(chunk.bytes()).await?; + } + + Ok(()) +} + +pub async fn download_file_stream( + client: &Client, + token: &str, + path: &str, +) -> Result>, 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()) +} diff --git a/src/network/mod.rs b/src/network/mod.rs new file mode 100644 index 00000000..99c5341b --- /dev/null +++ b/src/network/mod.rs @@ -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" + ); + } +} diff --git a/src/network/request.rs b/src/network/request.rs new file mode 100644 index 00000000..b068a5a0 --- /dev/null +++ b/src/network/request.rs @@ -0,0 +1,60 @@ +use apply::Apply; +use reqwest::r#async::{multipart::Form, Client, Response}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + network::{method_url, TelegramResponse, TELEGRAM_API_URL}, + requests::ResponseResult, + RequestError, +}; + +pub async fn request_multipart( + client: &Client, + token: &str, + method_name: &str, + params: Option, +) -> ResponseResult +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( + client: &Client, + token: &str, + method_name: &str, + params: &P, +) -> ResponseResult { + 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( + mut response: Response, +) -> ResponseResult { + let response = serde_json::from_str::>( + &response.text().await.map_err(RequestError::NetworkError)?, + ) + .map_err(RequestError::InvalidJson)?; + + response.into() +} diff --git a/src/network/telegram_response.rs b/src/network/telegram_response.rs new file mode 100644 index 00000000..05e7acb7 --- /dev/null +++ b/src/network/telegram_response.rs @@ -0,0 +1,56 @@ +use reqwest::StatusCode; + +use crate::{ + requests::ResponseResult, types::ResponseParameters, RequestError, +}; + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum TelegramResponse { + 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, + }, +} + +impl Into> for TelegramResponse { + fn into(self) -> Result { + 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(), + }) + } + } + } + } +} diff --git a/src/core/requests/answer_pre_checkout_query.rs b/src/requests/answer_pre_checkout_query.rs similarity index 99% rename from src/core/requests/answer_pre_checkout_query.rs rename to src/requests/answer_pre_checkout_query.rs index f494ead8..03a26263 100644 --- a/src/core/requests/answer_pre_checkout_query.rs +++ b/src/requests/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{Request, RequestContext, RequestFuture, ResponseResult}, }; diff --git a/src/core/requests/answer_shipping_query.rs b/src/requests/answer_shipping_query.rs similarity index 94% rename from src/core/requests/answer_shipping_query.rs rename to src/requests/answer_shipping_query.rs index c66860c9..c28fffd4 100644 --- a/src/core/requests/answer_shipping_query.rs +++ b/src/requests/answer_shipping_query.rs @@ -1,8 +1,8 @@ -use crate::core::network; -use crate::core::requests::{ - Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{Request, RequestContext, RequestFuture, ResponseResult}, + types::ShippingOption, }; -use crate::core::types::ShippingOption; #[derive(Debug, Clone, Serialize)] /// If you sent an invoice requesting a shipping address and the parameter diff --git a/src/core/requests/edit_message_live_location.rs b/src/requests/edit_message_live_location.rs similarity index 93% rename from src/core/requests/edit_message_live_location.rs rename to src/requests/edit_message_live_location.rs index 3d1171d8..9740e88c 100644 --- a/src/core/requests/edit_message_live_location.rs +++ b/src/requests/edit_message_live_location.rs @@ -1,9 +1,10 @@ -use crate::core::network; -use crate::core::requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, + types::{Message, ReplyMarkup}, }; -use crate::core::types::{Message, ReplyMarkup}; -use serde::Serialize; #[derive(Debug, Clone, Serialize)] /// Use this method to edit live location messages. A location can be edited diff --git a/src/core/requests/form_builder.rs b/src/requests/form_builder.rs similarity index 99% rename from src/core/requests/form_builder.rs rename to src/requests/form_builder.rs index d9bd8b69..d5ee4169 100644 --- a/src/core/requests/form_builder.rs +++ b/src/requests/form_builder.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; -use crate::core::{ +use reqwest::r#async::multipart::Form; + +use crate::{ requests::{utils, ChatId}, types::{InputMedia, ParseMode}, }; -use reqwest::r#async::multipart::Form; - /// This is a convenient struct that builds `reqwest::r#async::multipart::Form` /// from scratch. pub struct FormBuilder { diff --git a/src/core/requests/forward_message.rs b/src/requests/forward_message.rs similarity index 99% rename from src/core/requests/forward_message.rs rename to src/requests/forward_message.rs index ec24a7e3..c7091738 100644 --- a/src/core/requests/forward_message.rs +++ b/src/requests/forward_message.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{ ChatId, Request, RequestContext, RequestFuture, ResponseResult, diff --git a/src/core/requests/get_chat.rs b/src/requests/get_chat.rs similarity index 65% rename from src/core/requests/get_chat.rs rename to src/requests/get_chat.rs index eecf2711..3abb09cd 100644 --- a/src/core/requests/get_chat.rs +++ b/src/requests/get_chat.rs @@ -1,21 +1,24 @@ -use crate::core::requests::{ChatId, RequestContext, RequestFuture, ResponseResult, Request}; -use crate::core::types::Chat; -use crate::core::network; +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, + types::Chat, +}; -/// Use this method to get up to date information about the chat -/// (current name of the user for one-on-one conversations, -/// current username of a user, group or channel, etc.). +/// 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 + /// 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; @@ -26,16 +29,16 @@ impl<'a> Request<'a> for GetChat<'a> { &self.ctx.token, "getChat", &self, - ).await + ) + .await }) } } - -impl<'a> GetChat<'a>{ +impl<'a> GetChat<'a> { pub fn chat_id(mut self, chat_id: T) -> Self - where - T: Into, + where + T: Into, { self.chat_id = chat_id.into(); self diff --git a/src/core/requests/get_file.rs b/src/requests/get_file.rs similarity index 81% rename from src/core/requests/get_file.rs rename to src/requests/get_file.rs index 089ff743..2f3ee638 100644 --- a/src/core/requests/get_file.rs +++ b/src/requests/get_file.rs @@ -1,8 +1,8 @@ -use crate::core::network; -use crate::core::requests::{ - Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{Request, RequestContext, RequestFuture, ResponseResult}, + types::File, }; -use crate::core::types::File; /// Use this method to get basic info about a file and prepare it for /// downloading. For the moment, bots can download files of up to 20MB in size. @@ -12,11 +12,11 @@ use crate::core::types::File; /// It is guaranteed that the link will be valid for at least 1 hour. /// When the link expires, a new one can be requested by calling getFile again. #[derive(Debug, Clone, Serialize)] -struct GetFile<'a> { +pub struct GetFile<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// File identifier to get info about - file_id: String, + pub file_id: String, } impl<'a> Request<'a> for GetFile<'a> { @@ -36,6 +36,10 @@ impl<'a> Request<'a> for GetFile<'a> { } impl<'a> GetFile<'a> { + pub(crate) fn new(ctx: RequestContext<'a>, file_id: String) -> Self { + Self { ctx, file_id } + } + pub fn file_id(mut self, file_id: T) -> Self where T: Into, diff --git a/src/core/requests/get_me.rs b/src/requests/get_me.rs similarity index 97% rename from src/core/requests/get_me.rs rename to src/requests/get_me.rs index 343b36e4..c933db9e 100644 --- a/src/core/requests/get_me.rs +++ b/src/requests/get_me.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{Request, RequestContext, RequestFuture, ResponseResult}, types::User, diff --git a/src/requests/get_updates.rs b/src/requests/get_updates.rs new file mode 100644 index 00000000..2d733662 --- /dev/null +++ b/src/requests/get_updates.rs @@ -0,0 +1,88 @@ +use crate::{ + network, + requests::{Request, RequestContext, RequestFuture, ResponseResult}, + types::Update, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct GetUpdates<'a> { + #[serde(skip_serializing)] + ctx: RequestContext<'a>, + + pub offset: Option, + pub limit: Option, + pub timeout: Option, + pub allowed_updates: Option>, +} + +#[derive(Debug, Serialize, Eq, Hash, PartialEq, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum AllowedUpdate { + Message, + EditedMessage, + ChannelPost, + EditedChannelPost, + InlineQuery, + ChosenInlineResult, + CallbackQuery, +} + +impl<'a> Request<'a> for GetUpdates<'a> { + type ReturnValue = Vec; + + fn send(self) -> RequestFuture<'a, ResponseResult> { + Box::pin(async move { + network::request_json( + &self.ctx.client, + &self.ctx.token, + "getUpdates", + &self, + ) + .await + }) + } +} + +impl<'a> GetUpdates<'a> { + pub(crate) fn new(ctx: RequestContext<'a>) -> Self { + Self { + ctx, + offset: None, + limit: None, + timeout: None, + allowed_updates: None, + } + } + + pub fn offset(mut self, offset: T) -> Self + where + T: Into, + { + self.offset = Some(offset.into()); + self + } + + pub fn limit(mut self, limit: T) -> Self + where + T: Into, + { + self.limit = Some(limit.into()); + self + } + + pub fn timeout(mut self, timeout: T) -> Self + where + T: Into, + { + self.timeout = Some(timeout.into()); + self + } + + pub fn allowed_updates(mut self, allowed_updates: T) -> Self + where + T: Into>, + { + self.allowed_updates = Some(allowed_updates.into()); + self + } +} diff --git a/src/core/requests/get_user_profile_photos.rs b/src/requests/get_user_profile_photos.rs similarity index 66% rename from src/core/requests/get_user_profile_photos.rs rename to src/requests/get_user_profile_photos.rs index fb69133f..5feca84e 100644 --- a/src/core/requests/get_user_profile_photos.rs +++ b/src/requests/get_user_profile_photos.rs @@ -1,16 +1,19 @@ -use crate::core::requests::RequestContext; +use crate::requests::RequestContext; - -//TODO: complete implementation after user_profile_fotos will be added to types/mod.rs -///Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos object. +//TODO: complete implementation after user_profile_fotos will be added to +// types/mod.rs +///Use this method to get a list of profile pictures for a user. Returns a +/// UserProfilePhotos object. #[derive(Debug, Clone, Serialize)] -struct GetUserProfilePhotos<'a> { +pub struct GetUserProfilePhotos<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier of the target user user_id: i32, - /// Sequential number of the first photo to be returned. By default, all photos are returned. + /// Sequential number of the first photo to be returned. By default, all + /// photos are returned. offset: Option, - ///Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100. + ///Limits the number of photos to be retrieved. Values between 1—100 are + /// accepted. Defaults to 100. limit: Option, } diff --git a/src/requests/kick_chat_member.rs b/src/requests/kick_chat_member.rs new file mode 100644 index 00000000..baabe494 --- /dev/null +++ b/src/requests/kick_chat_member.rs @@ -0,0 +1,12 @@ +use crate::requests::RequestContext; +//TODO:: need implementation +/// Use this method to kick a user from a group, a supergroup or a channel. In +/// the case of supergroups and channels, the user will not be able to return to +/// the group on their own using invite links, etc., unless unbanned first. The +/// bot must be an administrator in the chat for this to work and must have the +/// appropriate admin rights. Returns True on success. +#[derive(Debug, Clone, Serialize)] +pub struct KickChatMember<'a> { + #[serde(skip_serializing)] + ctx: RequestContext<'a>, +} diff --git a/src/core/requests/mod.rs b/src/requests/mod.rs similarity index 53% rename from src/core/requests/mod.rs rename to src/requests/mod.rs index d04e113a..ede9b45a 100644 --- a/src/core/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,37 +1,27 @@ -use std::future::Future; -use std::pin::Pin; - -use reqwest::{r#async::Client, StatusCode}; -use serde::de::DeserializeOwned; - mod form_builder; mod utils; -#[derive(Debug, Display)] -pub enum RequestError { - #[display(fmt = "Telegram error #{}: {}", status_code, description)] - ApiError { - // TODO: add response parameters - status_code: StatusCode, - description: String, - }, +use reqwest::r#async::Client; +use serde::de::DeserializeOwned; +use std::{future::Future, pin::Pin}; - #[display(fmt = "Network error: {err}", err = _0)] - NetworkError(reqwest::Error), +use crate::RequestError; - #[display(fmt = "InvalidJson error caused by: {err}", err = _0)] - InvalidJson(serde_json::Error), -} - -impl std::error::Error for RequestError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RequestError::ApiError { .. } => None, - RequestError::NetworkError(err) => Some(err), - RequestError::InvalidJson(err) => Some(err), - } - } -} +pub use self::{ + answer_pre_checkout_query::AnswerPreCheckoutQuery, + answer_shipping_query::AnswerShippingQuery, + edit_message_live_location::EditMessageLiveLocation, + forward_message::ForwardMessage, get_chat::GetChat, get_file::GetFile, + get_me::GetMe, get_updates::GetUpdates, + get_user_profile_photos::GetUserProfilePhotos, + kick_chat_member::KickChatMember, restrict_chat_member::RestrictChatMember, + send_audio::SendAudio, send_chat_action::SendChatAction, + send_contact::SendContact, send_location::SendLocation, + send_media_group::SendMediaGroup, send_message::SendMessage, + send_photo::SendPhoto, send_poll::SendPoll, send_venue::SendVenue, + stop_message_live_location::StopMessageLiveLocation, + unban_chat_member::UnbanChatMember, +}; pub type ResponseResult = Result; @@ -89,24 +79,26 @@ mod tests { } } -pub mod answer_pre_checkout_query; -pub mod answer_shipping_query; -pub mod edit_message_live_location; -pub mod forward_message; -pub mod get_file; -pub mod get_me; -pub mod get_user_profile_photos; -pub mod kick_chat_member; -pub mod pin_chat_message; -pub mod restrict_chat_member; -pub mod send_audio; -pub mod send_chat_action; -pub mod send_contact; -pub mod send_location; -pub mod send_media_group; -pub mod send_message; -pub mod send_photo; -pub mod send_poll; -pub mod send_venue; -pub mod stop_message_live_location; -pub mod unban_chat_member; +mod answer_pre_checkout_query; +mod answer_shipping_query; +mod edit_message_live_location; +mod forward_message; +mod get_chat; +mod get_file; +mod get_me; +mod get_updates; +mod get_user_profile_photos; +mod kick_chat_member; +mod pin_chat_message; +mod restrict_chat_member; +mod send_audio; +mod send_chat_action; +mod send_contact; +mod send_location; +mod send_media_group; +mod send_message; +mod send_photo; +mod send_poll; +mod send_venue; +mod stop_message_live_location; +mod unban_chat_member; diff --git a/src/core/requests/pin_chat_message.rs b/src/requests/pin_chat_message.rs similarity index 100% rename from src/core/requests/pin_chat_message.rs rename to src/requests/pin_chat_message.rs diff --git a/src/core/requests/unban_chat_member.rs b/src/requests/restrict_chat_member.rs similarity index 63% rename from src/core/requests/unban_chat_member.rs rename to src/requests/restrict_chat_member.rs index 8f833b9b..781dead6 100644 --- a/src/core/requests/unban_chat_member.rs +++ b/src/requests/restrict_chat_member.rs @@ -1,8 +1,8 @@ -use crate::core::requests::RequestContext; +use crate::requests::RequestContext; //TODO:: need implementation #[derive(Debug, Clone, Serialize)] -struct UnbanChatMember<'a> { +pub struct RestrictChatMember<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, } diff --git a/src/core/requests/send_audio.rs b/src/requests/send_audio.rs similarity index 95% rename from src/core/requests/send_audio.rs rename to src/requests/send_audio.rs index 7580b7ce..64a2ede7 100644 --- a/src/core/requests/send_audio.rs +++ b/src/requests/send_audio.rs @@ -1,8 +1,8 @@ -use crate::core::{ +use crate::{ network, - requests::form_builder::FormBuilder, requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, + form_builder::FormBuilder, ChatId, Request, RequestContext, + RequestFuture, ResponseResult, }, types::{InputFile, Message, ParseMode, ReplyMarkup}, }; @@ -32,10 +32,10 @@ pub struct SendAudio<'a> { /// if you want Telegram apps to show [bold, italic, fixed-width text /// or inline URLs] in the media caption. /// - /// [Markdown]: crate::core::types::ParseMode::Markdown - /// [Html]: crate::core::types::ParseMode::Html + /// [Markdown]: crate::types::ParseMode::Markdown + /// [Html]: crate::types::ParseMode::Html /// [bold, italic, fixed-width text or inline URLs]: - /// crate::core::types::ParseMode + /// crate::types::ParseMode pub parse_mode: Option, /// Duration of the audio in seconds pub duration: Option, diff --git a/src/core/requests/send_chat_action.rs b/src/requests/send_chat_action.rs similarity index 92% rename from src/core/requests/send_chat_action.rs rename to src/requests/send_chat_action.rs index ae07ec05..639307cc 100644 --- a/src/core/requests/send_chat_action.rs +++ b/src/requests/send_chat_action.rs @@ -1,6 +1,8 @@ -use crate::core::network; -use crate::core::requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, }; ///Use this method when you need to tell the user that something is happening @@ -8,7 +10,7 @@ use crate::core::requests::{ /// arrives from your bot, Telegram clients clear its typing status). /// Returns True on success. #[derive(Debug, Clone, Serialize)] -struct SendChatAction<'a> { +pub struct SendChatAction<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or @@ -24,7 +26,7 @@ struct SendChatAction<'a> { #[derive(Debug, Serialize, From, Clone)] #[serde(rename_all = "snake_case")] -enum ChatAction { +pub enum ChatAction { Typing, UploadPhoto, RecordVideo, diff --git a/src/core/requests/send_contact.rs b/src/requests/send_contact.rs similarity index 94% rename from src/core/requests/send_contact.rs rename to src/requests/send_contact.rs index 16cbec0e..3a0f6e30 100644 --- a/src/core/requests/send_contact.rs +++ b/src/requests/send_contact.rs @@ -1,13 +1,15 @@ -use crate::core::network; -use crate::core::requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, + types::{Message, ReplyMarkup}, }; -use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send phone contacts. /// returned. #[derive(Debug, Clone, Serialize)] -struct SendContact<'a> { +pub struct SendContact<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or diff --git a/src/core/requests/send_location.rs b/src/requests/send_location.rs similarity index 99% rename from src/core/requests/send_location.rs rename to src/requests/send_location.rs index 9dd8af1b..0596a32f 100644 --- a/src/core/requests/send_location.rs +++ b/src/requests/send_location.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{ ChatId, Request, RequestContext, RequestFuture, ResponseResult, diff --git a/src/core/requests/send_media_group.rs b/src/requests/send_media_group.rs similarity index 99% rename from src/core/requests/send_media_group.rs rename to src/requests/send_media_group.rs index d95d4234..fdfaef2e 100644 --- a/src/core/requests/send_media_group.rs +++ b/src/requests/send_media_group.rs @@ -1,4 +1,6 @@ -use crate::core::{ +use apply::Apply; + +use crate::{ network::request_multipart, requests::{ form_builder::FormBuilder, ChatId, Request, RequestContext, @@ -6,7 +8,6 @@ use crate::core::{ }, types::{InputFile, InputMedia, Message}, }; -use apply::Apply; /// Use this method to send a group of photos or videos as an album. #[derive(Debug, Clone)] diff --git a/src/core/requests/send_message.rs b/src/requests/send_message.rs similarity index 95% rename from src/core/requests/send_message.rs rename to src/requests/send_message.rs index 2539c04d..f4f04636 100644 --- a/src/core/requests/send_message.rs +++ b/src/requests/send_message.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{ ChatId, Request, RequestContext, RequestFuture, ResponseResult, @@ -23,10 +23,10 @@ pub struct SendMessage<'a> { /// if you want Telegram apps to show [bold, italic, fixed-width text /// or inline URLs] in the media caption. /// - /// [Markdown]: crate::core::types::ParseMode::Markdown - /// [Html]: crate::core::types::ParseMode::Html + /// [Markdown]: crate::types::ParseMode::Markdown + /// [Html]: crate::types::ParseMode::Html /// [bold, italic, fixed-width text or inline URLs]: - /// crate::core::types::ParseMode + /// crate::types::ParseMode #[serde(skip_serializing_if = "Option::is_none")] pub parse_mode: Option, /// Disables link previews for links in this message diff --git a/src/core/requests/send_photo.rs b/src/requests/send_photo.rs similarity index 96% rename from src/core/requests/send_photo.rs rename to src/requests/send_photo.rs index 69f1e5d7..b4e09152 100644 --- a/src/core/requests/send_photo.rs +++ b/src/requests/send_photo.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{ form_builder::FormBuilder, ChatId, Request, RequestContext, @@ -30,10 +30,10 @@ pub struct SendPhoto<'a> { /// if you want Telegram apps to show [bold, italic, fixed-width text /// or inline URLs] in the media caption. /// - /// [Markdown]: crate::core::types::ParseMode::Markdown - /// [Html]: crate::core::types::ParseMode::Html + /// [Markdown]: crate::types::ParseMode::Markdown + /// [Html]: crate::types::ParseMode::Html /// [bold, italic, fixed-width text or inline URLs]: - /// crate::core::types::ParseMode + /// crate::types::ParseMode pub parse_mode: Option, /// Sends the message silently. Users will receive a notification with no /// sound. diff --git a/src/core/requests/send_poll.rs b/src/requests/send_poll.rs similarity index 93% rename from src/core/requests/send_poll.rs rename to src/requests/send_poll.rs index 861c98e7..7cac05fe 100644 --- a/src/core/requests/send_poll.rs +++ b/src/requests/send_poll.rs @@ -1,13 +1,15 @@ -use crate::core::network; -use crate::core::requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, + types::{Message, ReplyMarkup}, }; -use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send a native poll. A native poll can't be sent to a /// private chat. On success, the sent Message is returned. #[derive(Debug, Clone, Serialize)] -struct SendPoll<'a> { +pub struct SendPoll<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// identifier for the target chat or username of the target channel (in diff --git a/src/core/requests/send_venue.rs b/src/requests/send_venue.rs similarity index 95% rename from src/core/requests/send_venue.rs rename to src/requests/send_venue.rs index 85fee6d6..08c7b3d9 100644 --- a/src/core/requests/send_venue.rs +++ b/src/requests/send_venue.rs @@ -1,13 +1,15 @@ -use crate::core::network; -use crate::core::requests::{ - ChatId, Request, RequestContext, RequestFuture, ResponseResult, +use crate::{ + network, + requests::{ + ChatId, Request, RequestContext, RequestFuture, ResponseResult, + }, + types::{Message, ReplyMarkup}, }; -use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send information about a venue. /// Message is returned. #[derive(Debug, Clone, Serialize)] -struct SendVenue<'a> { +pub struct SendVenue<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or @@ -62,7 +64,7 @@ impl<'a> Request<'a> for SendVenue<'a> { } impl<'a> SendVenue<'a> { - pub fn new( + pub(crate) fn new( ctx: RequestContext<'a>, chat_id: ChatId, latitude: f64, diff --git a/src/core/requests/stop_message_live_location.rs b/src/requests/stop_message_live_location.rs similarity index 99% rename from src/core/requests/stop_message_live_location.rs rename to src/requests/stop_message_live_location.rs index 5fb62a54..809200d7 100644 --- a/src/core/requests/stop_message_live_location.rs +++ b/src/requests/stop_message_live_location.rs @@ -1,4 +1,4 @@ -use crate::core::{ +use crate::{ network, requests::{ ChatId, Request, RequestContext, RequestFuture, ResponseResult, diff --git a/src/core/requests/kick_chat_member.rs b/src/requests/unban_chat_member.rs similarity index 64% rename from src/core/requests/kick_chat_member.rs rename to src/requests/unban_chat_member.rs index 69c158a8..ffaace92 100644 --- a/src/core/requests/kick_chat_member.rs +++ b/src/requests/unban_chat_member.rs @@ -1,8 +1,8 @@ -use crate::core::requests::RequestContext; +use crate::requests::RequestContext; //TODO:: need implementation #[derive(Debug, Clone, Serialize)] -struct KickChatMember<'a> { +pub struct UnbanChatMember<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, } diff --git a/src/core/requests/utils.rs b/src/requests/utils.rs similarity index 100% rename from src/core/requests/utils.rs rename to src/requests/utils.rs diff --git a/src/core/types/animation.rs b/src/types/animation.rs similarity index 98% rename from src/core/types/animation.rs rename to src/types/animation.rs index 5d2ceff1..ec3b92f9 100644 --- a/src/core/types/animation.rs +++ b/src/types/animation.rs @@ -1,4 +1,4 @@ -use crate::core::types::PhotoSize; +use crate::types::PhotoSize; #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] /// This object represents an animation file (GIF or H.264/MPEG-4 AVC video diff --git a/src/types/audio.rs b/src/types/audio.rs new file mode 100644 index 00000000..fa17e27d --- /dev/null +++ b/src/types/audio.rs @@ -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, + pub title: Option, + pub mime_type: Option, + pub file_size: Option, + pub thumb: Option, +} + +#[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::