diff --git a/README.md b/README.md index d70c314..bb24b31 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@

frankenstein

-[![Crates.io][s1]][ci] [![docs page][docs-badge]][docs] ![test][ga-test] - # Frankenstein Telegram bot API client for Rust. @@ -79,50 +77,42 @@ Optional fields are described as `Option`. Every struct can be created with the associated builder. Only required fields are required to set, optional fields are set to `None` when not provided: ```rust +use frankenstein::api_params::SendMessageParams; let send_message_params = SendMessageParams::builder() - .chat_id(message.chat.id) + .chat_id(1337) .text("hello") - .reply_to_message_id(message.message_id) .build(); ``` -For API parameters, the same approach is used. The only difference for parameters is the name of the struct in Frankenstein ends with `Params` postfix. - -For example, parameters for `leaveChat` method: - -```rust -pub struct LeaveChatParams { - chat_id: ChatId, -} -``` - ### Making requests -To make a request to the Telegram bot API initialize the `Api` struct. - -```rust -use frankenstein::Api; +```rust,no_run +#![cfg(feature = "client-ureq")] use frankenstein::TelegramApi; +use frankenstein::api_params::{GetUpdatesParams, SendMessageParams}; +use frankenstein::client_ureq::Bot; +use frankenstein::objects::AllowedUpdate; -... +let token = "123:ABC"; +let bot = Bot::new(token); -let token = "My_token"; -let api = Api::new(token); -``` - -Then use this API object to make requests to the Bot API: +// Send a message +let send_message_params = SendMessageParams::builder() + .chat_id(1337) + .text("hello") + .build(); +let result = bot.send_message(&send_message_params); -```rust +// or get the updates (= interactions with the bot) let update_params = GetUpdatesParams::builder() .allowed_updates(vec![AllowedUpdate::Message]) .build(); - -let result = api.get_updates(&update_params); +let result = bot.get_updates(&update_params); ``` -Every function returns a `Result` enum with a successful response or failed response. +Every function returns a `Result` with a successful response or failed response. -See a complete example in the `examples` directory. +See more examples in the [`examples`](https://github.com/ayrat555/frankenstein/tree/0.38.0/examples) directory. ### Uploading files @@ -144,46 +134,9 @@ It has two variants: - `FileUpload::String` is used to pass the ID of the already uploaded file - `FileUpload::InputFile` is used to upload a new file using multipart upload. -### Customizing HTTP clients - -Both the async (`reqwest`) and the blocking (`ureq`) HTTP clients can be customized with their builders. - -Customizing the blocking client: - -```rust -use frankenstein::ureq; -use frankenstein::Api; -use std::time::Duration; - -let request_agent = ureq::builder().timeout(Duration::from_secs(100)).build(); -let api_url = format!("{}{}", BASE_API_URL, TOKEN); - -Api::builder() - .api_url(api_url) - .request_agent(request_agent) - .build() -``` - -Customizing the async client: - -```rust -use frankenstein::reqwest; -use frankenstein::AsyncApi; -use std::time::Duration; - -let client = reqwest::ClientBuilder::new() - .connect_timeout(Duration::from_secs(100)) - .timeout(Duration::from_secs(100)) - .build() - .unwrap(); -let api_url = format!("{}{}", BASE_API_URL, TOKEN); - -AsyncApi::builder().api_url(api_url).client(client).build() -``` - ### Documentation -Frankenstein implements all Telegram bot API methods. To see which parameters you should pass, check [docs.rs](https://docs.rs/frankenstein/0.38.0/frankenstein/api_traits/telegram_api/trait.TelegramApi.html#provided-methods) +Frankenstein implements all Telegram bot API methods. To see which parameters you should pass, check the [official Telegram Bot API documentation](https://core.telegram.org/bots/api#available-methods) or [docs.rs/frankenstein](https://docs.rs/frankenstein/0.38.0/frankenstein/trait.TelegramApi.html#provided-methods) You can check out real-world bots created using this library: @@ -192,24 +145,6 @@ You can check out real-world bots created using this library: - [wdr-maus-downloader](https://github.com/EdJoPaTo/wdr-maus-downloader) - checks for a new episode of the WDR Maus and downloads it. - [weather_bot_rust](https://github.com/pxp9/weather_bot_rust) - A Telegram bot that provides weather info around the world. -## Replacing the default HTTP client - -The library uses `ureq` HTTP client by default, but it can be easily replaced with any HTTP client of your choice. -This is described here for the `trait-sync` and can be done similarly with `trait-async` based on your needs. - -```toml -frankenstein = { version = "0.39", features = ["trait-sync"] } -``` - -Then implement the `TelegramApi` trait for your HTTP client which requires two functions: - -- `request_with_form_data` is used to upload files -- `request` is used for requests without file uploads - -You can check [the default `TelegramApi` trait implementation](https://github.com/ayrat555/frankenstein/blob/aac88c01d06aa945393db7255ef2485a7c764d47/src/api_impl.rs) for `ureq`. - -Also, you can take a look at the [implementation for `isahc` HTTP client](https://github.com/ayrat555/frankenstein/blob/master/examples/api_trait_implementation.rs) in the `examples` directory. - ## Contributing 1. [Fork it!](https://github.com/ayrat555/frankenstein/fork) @@ -221,9 +156,3 @@ Also, you can take a look at the [implementation for `isahc` HTTP client](https: ## Author Ayrat Badykov (@ayrat555) - -[s1]: https://img.shields.io/crates/v/frankenstein.svg -[docs-badge]: https://img.shields.io/badge/docs-website-blue.svg -[ci]: https://crates.io/crates/frankenstein -[docs]: https://docs.rs/frankenstein/ -[ga-test]: https://github.com/ayrat555/frankenstein/actions/workflows/rust.yml/badge.svg diff --git a/examples/api_trait_implementation.rs b/examples/api_trait_implementation.rs index aa973a9..8f7be4d 100644 --- a/examples/api_trait_implementation.rs +++ b/examples/api_trait_implementation.rs @@ -10,7 +10,7 @@ static TOKEN: &str = "TOKEN"; static BASE_API_URL: &str = "https://api.telegram.org/bot"; static CHAT_ID: i64 = 1; -pub struct Api { +pub struct MyApiClient { pub api_url: String, } @@ -20,7 +20,7 @@ pub enum Error { Api(ErrorResponse), } -impl Api { +impl MyApiClient { #[must_use] pub fn new(api_key: &str) -> Self { let api_url = format!("{BASE_API_URL}{api_key}"); @@ -47,7 +47,7 @@ impl From for Error { } } -impl TelegramApi for Api { +impl TelegramApi for MyApiClient { type Error = Error; fn request( @@ -105,14 +105,14 @@ impl TelegramApi for Api { } fn main() { - let api = Api::new(TOKEN); + let bot = MyApiClient::new(TOKEN); let params = SendMessageParams::builder() .chat_id(CHAT_ID) .text("Hello!") .build(); - let result = api.send_message(¶ms); + let result = bot.send_message(¶ms); eprintln!("{result:?}"); } diff --git a/examples/async_custom_client.rs b/examples/async_custom_client.rs index 9e9e98f..e1ff52c 100644 --- a/examples/async_custom_client.rs +++ b/examples/async_custom_client.rs @@ -1,15 +1,16 @@ use std::time::Duration; -use frankenstein::{AsyncApi, AsyncTelegramApi}; +use frankenstein::client_reqwest::Bot; +use frankenstein::AsyncTelegramApi; static TOKEN: &str = "API_TOKEN"; static BASE_API_URL: &str = "https://api.telegram.org/bot"; #[tokio::main] async fn main() { - let api = custom_client(); + let bot = custom_client(); - match api.get_me().await { + match bot.get_me().await { Ok(response) => { let user = response.result; println!( @@ -24,7 +25,7 @@ async fn main() { } } -fn custom_client() -> AsyncApi { +fn custom_client() -> Bot { let client = frankenstein::reqwest::ClientBuilder::new() .connect_timeout(Duration::from_secs(100)) .timeout(Duration::from_secs(100)) @@ -32,5 +33,5 @@ fn custom_client() -> AsyncApi { .unwrap(); let api_url = format!("{BASE_API_URL}{TOKEN}"); - AsyncApi::builder().api_url(api_url).client(client).build() + Bot::builder().api_url(api_url).client(client).build() } diff --git a/examples/async_file_upload.rs b/examples/async_file_upload.rs index 58e6a1e..4a25df6 100644 --- a/examples/async_file_upload.rs +++ b/examples/async_file_upload.rs @@ -1,12 +1,13 @@ use frankenstein::api_params::SendPhotoParams; -use frankenstein::{AsyncApi, AsyncTelegramApi}; +use frankenstein::client_reqwest::Bot; +use frankenstein::AsyncTelegramApi; static TOKEN: &str = "TOKEN"; static CHAT_ID: i64 = 1; #[tokio::main] async fn main() { - let api = AsyncApi::new(TOKEN); + let bot = Bot::new(TOKEN); let file = std::path::PathBuf::from("./frankenstein_logo.png"); @@ -15,7 +16,7 @@ async fn main() { .photo(file) .build(); - match api.send_photo(¶ms).await { + match bot.send_photo(¶ms).await { Ok(response) => { println!("Photo was uploaded {response:?}"); } diff --git a/examples/async_get_me.rs b/examples/async_get_me.rs index ef0e104..d497c47 100644 --- a/examples/async_get_me.rs +++ b/examples/async_get_me.rs @@ -1,12 +1,13 @@ -use frankenstein::{AsyncApi, AsyncTelegramApi}; +use frankenstein::client_reqwest::Bot; +use frankenstein::AsyncTelegramApi; static TOKEN: &str = "API_TOKEN"; #[tokio::main] async fn main() { - let api = AsyncApi::new(TOKEN); + let bot = Bot::new(TOKEN); - match api.get_me().await { + match bot.get_me().await { Ok(response) => { let user = response.result; println!( diff --git a/examples/async_reply_to_message_updates.rs b/examples/async_reply_to_message_updates.rs index 9c8abd1..fe0ba26 100644 --- a/examples/async_reply_to_message_updates.rs +++ b/examples/async_reply_to_message_updates.rs @@ -1,17 +1,18 @@ use frankenstein::api_params::{GetUpdatesParams, ReplyParameters, SendMessageParams}; +use frankenstein::client_reqwest::Bot; use frankenstein::objects::{Message, UpdateContent}; -use frankenstein::{AsyncApi, AsyncTelegramApi}; +use frankenstein::AsyncTelegramApi; static TOKEN: &str = "API_TOKEN"; #[tokio::main] async fn main() { - let api = AsyncApi::new(TOKEN); + let bot = Bot::new(TOKEN); let mut update_params = GetUpdatesParams::builder().build(); loop { - let result = api.get_updates(&update_params).await; + let result = bot.get_updates(&update_params).await; println!("result: {result:?}"); @@ -19,10 +20,10 @@ async fn main() { Ok(response) => { for update in response.result { if let UpdateContent::Message(message) = update.content { - let api_clone = api.clone(); + let bot_clone = bot.clone(); tokio::spawn(async move { - process_message(message, api_clone).await; + process_message(message, bot_clone).await; }); } update_params.offset = Some(i64::from(update.update_id) + 1); @@ -35,7 +36,7 @@ async fn main() { } } -async fn process_message(message: Message, api: AsyncApi) { +async fn process_message(message: Message, bot: Bot) { let reply_parameters = ReplyParameters::builder() .message_id(message.message_id) .build(); @@ -44,7 +45,7 @@ async fn process_message(message: Message, api: AsyncApi) { .text("hello") .reply_parameters(reply_parameters) .build(); - if let Err(error) = api.send_message(&send_message_params).await { + if let Err(error) = bot.send_message(&send_message_params).await { println!("Failed to send message: {error:?}"); } } diff --git a/examples/custom_client.rs b/examples/custom_client.rs index 0a97e10..52bae57 100644 --- a/examples/custom_client.rs +++ b/examples/custom_client.rs @@ -1,14 +1,15 @@ use std::time::Duration; -use frankenstein::{Api, TelegramApi}; +use frankenstein::client_ureq::Bot; +use frankenstein::TelegramApi; static TOKEN: &str = "API_TOKEN"; static BASE_API_URL: &str = "https://api.telegram.org/bot"; fn main() { - let api = custom_client(); + let bot = custom_client(); - match api.get_me() { + match bot.get_me() { Ok(response) => { let user = response.result; println!( @@ -23,7 +24,7 @@ fn main() { } } -fn custom_client() -> Api { +fn custom_client() -> Bot { let config = frankenstein::ureq::Agent::config_builder() .http_status_as_error(false) .timeout_global(Some(Duration::from_secs(100))) @@ -31,7 +32,7 @@ fn custom_client() -> Api { let request_agent = frankenstein::ureq::Agent::new_with_config(config); let api_url = format!("{BASE_API_URL}{TOKEN}"); - Api::builder() + Bot::builder() .api_url(api_url) .request_agent(request_agent) .build() diff --git a/examples/get_me.rs b/examples/get_me.rs index 7ce6198..9804af1 100644 --- a/examples/get_me.rs +++ b/examples/get_me.rs @@ -1,11 +1,12 @@ -use frankenstein::{Api, TelegramApi}; +use frankenstein::client_ureq::Bot; +use frankenstein::TelegramApi; static TOKEN: &str = "API_TOKEN"; fn main() { - let api = Api::new(TOKEN); + let bot = Bot::new(TOKEN); - match api.get_me() { + match bot.get_me() { Ok(response) => { let user = response.result; println!( diff --git a/examples/inline_keyboard.rs b/examples/inline_keyboard.rs index 32b178d..ecf8ece 100644 --- a/examples/inline_keyboard.rs +++ b/examples/inline_keyboard.rs @@ -1,6 +1,7 @@ use frankenstein::api_params::{ReplyMarkup, SendMessageParams}; +use frankenstein::client_ureq::Bot; use frankenstein::objects::{InlineKeyboardButton, InlineKeyboardMarkup}; -use frankenstein::{Api, TelegramApi}; +use frankenstein::TelegramApi; // replace with your token static TOKEN: &str = "TOKEN"; @@ -8,7 +9,7 @@ static TOKEN: &str = "TOKEN"; static CHAT_ID: i64 = 275_808_073; fn main() { - let api = Api::new(TOKEN); + let bot = Bot::new(TOKEN); let mut keyboard: Vec> = Vec::new(); @@ -38,5 +39,5 @@ fn main() { .reply_markup(ReplyMarkup::InlineKeyboardMarkup(inline_keyboard)) .build(); - api.send_message(&send_message_params).unwrap(); + bot.send_message(&send_message_params).unwrap(); } diff --git a/examples/reply_keyboard.rs b/examples/reply_keyboard.rs index 2014347..ba155b6 100644 --- a/examples/reply_keyboard.rs +++ b/examples/reply_keyboard.rs @@ -1,6 +1,7 @@ use frankenstein::api_params::{ReplyMarkup, SendMessageParams}; +use frankenstein::client_ureq::Bot; use frankenstein::objects::{KeyboardButton, ReplyKeyboardMarkup}; -use frankenstein::{Api, TelegramApi}; +use frankenstein::TelegramApi; // replace with your token static TOKEN: &str = "TOKEN"; @@ -8,7 +9,7 @@ static TOKEN: &str = "TOKEN"; static CHAT_ID: i64 = 275_808_073; fn main() { - let api = Api::new(TOKEN); + let bot = Bot::new(TOKEN); let mut keyboard: Vec> = Vec::new(); @@ -33,5 +34,5 @@ fn main() { .reply_markup(ReplyMarkup::ReplyKeyboardMarkup(keyboard_markup)) .build(); - api.send_message(&send_message_params).unwrap(); + bot.send_message(&send_message_params).unwrap(); } diff --git a/examples/reply_to_message_updates.rs b/examples/reply_to_message_updates.rs index dcbf262..4acf922 100644 --- a/examples/reply_to_message_updates.rs +++ b/examples/reply_to_message_updates.rs @@ -1,16 +1,17 @@ use frankenstein::api_params::{GetUpdatesParams, ReplyParameters, SendMessageParams}; +use frankenstein::client_ureq::Bot; use frankenstein::objects::UpdateContent; -use frankenstein::{Api, TelegramApi}; +use frankenstein::TelegramApi; static TOKEN: &str = "API_TOKEN"; fn main() { - let api = Api::new(TOKEN); + let bot = Bot::new(TOKEN); let mut update_params = GetUpdatesParams::builder().build(); loop { - let result = api.get_updates(&update_params); + let result = bot.get_updates(&update_params); println!("result: {result:?}"); @@ -26,7 +27,7 @@ fn main() { .text("hello") .reply_parameters(reply_parameters) .build(); - if let Err(error) = api.send_message(&send_message_params) { + if let Err(error) = bot.send_message(&send_message_params) { println!("Failed to send message: {error:?}"); } } diff --git a/src/client_reqwest.rs b/src/client_reqwest.rs index 03accab..0b1daa3 100644 --- a/src/client_reqwest.rs +++ b/src/client_reqwest.rs @@ -6,10 +6,10 @@ use bon::Builder; use crate::trait_async::AsyncTelegramApi; use crate::Error; -/// Asynchronous [`AsyncTelegramApi`] client implementation with [`reqwest`]. +/// Asynchronous [`AsyncTelegramApi`] implementation with [`reqwest`] #[derive(Debug, Clone, Builder)] -#[must_use = "API needs to be used in order to be useful"] -pub struct AsyncApi { +#[must_use = "Bot needs to be used in order to be useful"] +pub struct Bot { #[builder(into)] pub api_url: String, @@ -28,13 +28,13 @@ fn default_client() -> reqwest::Client { client_builder.build().unwrap() } -impl AsyncApi { - /// Create a new `AsyncApi`. You can use [`AsyncApi::new_url`] or [`AsyncApi::builder`] for more options. +impl Bot { + /// Create a new `Bot`. You can use [`Bot::new_url`] or [`Bot::builder`] for more options. pub fn new(api_key: &str) -> Self { Self::new_url(format!("{}{api_key}", crate::BASE_API_URL)) } - /// Create a new `AsyncApi`. You can use [`AsyncApi::builder`] for more options. + /// Create a new `Bot`. You can use [`Bot::builder`] for more options. pub fn new_url>(api_url: S) -> Self { Self::builder().api_url(api_url).build() } @@ -63,7 +63,7 @@ impl From for Error { // Wasm target need not be `Send` because it is single-threaded #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl AsyncTelegramApi for AsyncApi { +impl AsyncTelegramApi for Bot { type Error = Error; async fn request( @@ -169,7 +169,7 @@ mod tests { .with_body(response_string) .create_async() .await; - let api = AsyncApi::new_url(server.url()); + let api = Bot::new_url(server.url()); let response = api.send_message(¶ms).await.unwrap(); mock.assert(); @@ -193,7 +193,7 @@ mod tests { .with_body(response_string) .create_async() .await; - let api = AsyncApi::new_url(server.url()); + let api = Bot::new_url(server.url()); let error = api.send_message(¶ms).await.unwrap_err().unwrap_api(); mock.assert(); diff --git a/src/client_ureq.rs b/src/client_ureq.rs index 37ef6d6..10b5087 100644 --- a/src/client_ureq.rs +++ b/src/client_ureq.rs @@ -8,10 +8,10 @@ use serde_json::Value; use crate::trait_sync::TelegramApi; use crate::Error; -/// Synchronous [`TelegramApi`] client implementation with [`ureq`]. +/// Synchronous [`TelegramApi`] implementation with [`ureq`]. #[derive(Debug, Clone, Builder)] -#[must_use = "API needs to be used in order to be useful"] -pub struct Api { +#[must_use = "Bot needs to be used in order to be useful"] +pub struct Bot { #[builder(into)] pub api_url: String, @@ -28,13 +28,13 @@ fn default_agent() -> ureq::Agent { ) } -impl Api { - /// Create a new `Api`. You can use [`Api::new_url`] or [`Api::builder`] for more options. +impl Bot { + /// Create a new `Bot`. You can use [`Bot::new_url`] or [`Bot::builder`] for more options. pub fn new(api_key: &str) -> Self { Self::new_url(format!("{}{api_key}", crate::BASE_API_URL)) } - /// Create a new `Api`. You can use [`Api::builder`] for more options. + /// Create a new `Bot`. You can use [`Bot::builder`] for more options. pub fn new_url>(api_url: S) -> Self { Self::builder().api_url(api_url).build() } @@ -56,7 +56,7 @@ impl Api { } } -impl TelegramApi for Api { +impl TelegramApi for Bot { type Error = Error; fn request(&self, method: &str, params: Option) -> Result @@ -173,7 +173,7 @@ mod tests { .with_status($status) .with_body($body) .create(); - let api = Api::new_url(server.url()); + let api = Bot::new_url(server.url()); let response = dbg!(api.[<$method:snake>]($(& $params )?)); mock.assert(); drop(server); @@ -184,7 +184,7 @@ mod tests { #[test] fn new_sets_correct_url() { - let api = Api::new("hey"); + let api = Bot::new("hey"); assert_eq!("https://api.telegram.org/bothey", api.api_url); } diff --git a/src/lib.rs b/src/lib.rs index 76c5bff..0450868 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(test, allow(dead_code))] +#![doc = include_str!("../README.md")] #[cfg(feature = "client-reqwest")] pub use reqwest; @@ -7,10 +8,6 @@ pub use reqwest; pub use ureq; pub use self::api_params::*; -#[cfg(feature = "client-reqwest")] -pub use self::client_reqwest::*; -#[cfg(feature = "client-ureq")] -pub use self::client_ureq::*; pub use self::error::Error; pub use self::objects::*; pub use self::parse_mode::ParseMode; @@ -22,9 +19,9 @@ pub use self::trait_sync::TelegramApi; pub mod api_params; #[cfg(feature = "client-reqwest")] -mod client_reqwest; +pub mod client_reqwest; #[cfg(feature = "client-ureq")] -mod client_ureq; +pub mod client_ureq; mod error; #[cfg(any(feature = "client-reqwest", feature = "client-ureq"))] mod json; @@ -41,3 +38,16 @@ mod trait_sync; /// Default Bot API URL pub const BASE_API_URL: &str = "https://api.telegram.org/bot"; + +#[deprecated( + since = "0.39.0", + note = "enable the client-reqwest feature and use frankenstein::client_reqwest::Bot instead" +)] +#[doc(hidden)] +pub struct AsyncApi; +#[deprecated( + since = "0.39.0", + note = "enable the client-ureq feature and use frankenstein::client_ureq::Bot instead" +)] +#[doc(hidden)] +pub struct Api;