diff --git a/.gitignore b/.gitignore index 218bb4a8..0071482e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ Cargo.lock .idea/ .vscode/ -examples/target examples/ping_pong_bot/target examples/dialogue_bot/target -examples/multiple_handlers_bot/target \ No newline at end of file +examples/multiple_handlers_bot/target +examples/commands_bot/target \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7b1da981..7df89eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ tokio-util = { version = "0.2.0", features = ["full"] } reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] } log = "0.4.8" -pretty_env_logger = "0.4.0" bytes = "0.5.3" mime = "0.3.16" diff --git a/examples/admin_bot/Cargo.toml b/examples/commands_bot/Cargo.toml similarity index 85% rename from examples/admin_bot/Cargo.toml rename to examples/commands_bot/Cargo.toml index 90a0913c..fc126db6 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/commands_bot/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "admin_bot" +name = "commands_bot" version = "0.1.0" authors = ["p0lunin "] edition = "2018" @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } [profile.release] diff --git a/examples/admin_bot/src/main.rs b/examples/commands_bot/src/main.rs similarity index 70% rename from examples/admin_bot/src/main.rs rename to examples/commands_bot/src/main.rs index cc32058c..9cbf335c 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/commands_bot/src/main.rs @@ -1,15 +1,19 @@ -use teloxide::prelude::*; -use teloxide::utils::command::BotCommand; -use teloxide::types::ChatPermissions; +use teloxide::{ + prelude::*, types::ChatPermissions, utils::command::BotCommand, +}; // Declare type of handler context type Ctx = DispatcherHandlerCtx; // Derive trait which allow to parse text with command into enum -// (rename = "lowercase") means that names of variants of enum will be lowercase before parsing -// `description` will be add before description of command when you call Command::descriptions() +// (rename = "lowercase") means that names of variants of enum will be lowercase +// before parsing `description` will be add before description of command when +// you call Command::descriptions() #[derive(BotCommand)] -#[command(rename = "lowercase", description = "Use commands in format /%command% %num% %unit%")] +#[command( + rename = "lowercase", + description = "Use commands in format /%command% %num% %unit%" +)] enum Command { #[command(description = "kick user from chat.")] Kick, @@ -24,10 +28,10 @@ enum Command { // Calculate time of restrict user. fn calc_restrict_time(num: i32, unit: &str) -> Result { match unit { - "h"|"hours" => Ok(num * 3600), - "m"|"minutes" => Ok(num * 60), - "s"|"seconds" => Ok(num), - _ => Err("Allowed units: h, m, s") + "h" | "hours" => Ok(num * 3600), + "m" | "minutes" => Ok(num * 60), + "s" | "seconds" => Ok(num), + _ => Err("Allowed units: h, m, s"), } } @@ -39,7 +43,7 @@ fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { }; let unit = match args.get(1) { Some(s) => s, - None => return Err("Use command in format /%command% %num% %unit%") + None => return Err("Use command in format /%command% %num% %unit%"), }; match num.parse::() { @@ -60,12 +64,14 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Some(mes) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { - ctx.bot.restrict_chat_member( + ctx.bot + .restrict_chat_member( ctx.update.chat_id(), - // Sender of message cannot be only in messages from channels - // so we can use unwrap() + // Sender of message cannot be only in messages from + // channels so we can use + // unwrap() mes.from().unwrap().id, - ChatPermissions::default() + ChatPermissions::default(), ) .until_date(ctx.update.date + time) .send() @@ -73,18 +79,21 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { } // ...or permanently Err(msg) => { - ctx.bot.restrict_chat_member( + ctx.bot + .restrict_chat_member( ctx.update.chat_id(), mes.from().unwrap().id, - ChatPermissions::default() + ChatPermissions::default(), ) .send() .await?; } }, None => { - ctx.reply_to("Use this command in reply to another message").send().await?; - }, + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } } Ok(()) } @@ -94,13 +103,15 @@ async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { match ctx.update.reply_to_message() { Some(mes) => { // `unban_chat_member` will also kick user from group chat - ctx.bot.unban_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ).send().await?; - }, + ctx.bot + .unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id) + .send() + .await?; + } None => { - ctx.reply_to("Use this command in reply to another message").send().await?; + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; } } Ok(()) @@ -112,27 +123,31 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { Some(mes) => match parse_time_restrict(args) { // Mute user temporarily... Ok(time) => { - ctx.bot.kick_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ) + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id, + ) .until_date(ctx.update.date + time) .send() .await?; } // ...or permanently Err(msg) => { - ctx.bot.kick_chat_member( - ctx.update.chat_id(), - mes.from().unwrap().id - ) + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + mes.from().unwrap().id, + ) .send() .await?; - }, + } }, None => { - ctx.reply_to("Use this command in reply to another message").send().await?; - }, + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } } Ok(()) } @@ -140,8 +155,9 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { // Handle all messages async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { // If message not from group stop handled. - // NOTE: in this case we have only one `message_handler`. If you have more, return - // DispatcherHandlerResult::next() so that the following handlers can receive this message! + // NOTE: in this case we have only one `message_handler`. If you have more, + // return DispatcherHandlerResult::next() so that the following handlers + // can receive this message! if ctx.update.chat.is_group() { return Ok(()); } @@ -150,7 +166,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { // Parse text into command with args let (command, args): (Command, Vec<&str>) = match Command::parse(text) { Some(tuple) => tuple, - None => return Ok(()) + None => return Ok(()), }; match command { @@ -178,7 +194,11 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { #[tokio::main] async fn main() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); + log::info!("Starting commands_bot!"); + + let bot = Bot::from_env(); + Dispatcher::new(bot) .message_handler(&handle_command) .dispatch() diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 76482737..947470f0 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" smart-default = "0.6.0" parse-display = "0.1.1" teloxide = { path = "../../" } diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 0a7003cb..4591523a 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -177,9 +177,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting dialogue_bot!"); + let bot = Bot::from_env(); + Dispatcher::new(bot) .message_handler(&DialogueDispatcher::new(|ctx| async move { handle_message(ctx) diff --git a/examples/multiple_handlers_bot/Cargo.toml b/examples/multiple_handlers_bot/Cargo.toml index 37a9436a..89a1b61a 100644 --- a/examples/multiple_handlers_bot/Cargo.toml +++ b/examples/multiple_handlers_bot/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs index ff814ca4..924fceac 100644 --- a/examples/multiple_handlers_bot/src/main.rs +++ b/examples/multiple_handlers_bot/src/main.rs @@ -6,9 +6,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting multiple_handlers_bot!"); + let bot = Bot::from_env(); + // Create a dispatcher with multiple handlers of different types. This will // print One! and Two! on every incoming UpdateKind::Message. Dispatcher::::new(bot) diff --git a/examples/ping_pong_bot/Cargo.toml b/examples/ping_pong_bot/Cargo.toml index 5fc453e8..73a9d9ed 100644 --- a/examples/ping_pong_bot/Cargo.toml +++ b/examples/ping_pong_bot/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" tokio = "0.2.9" +pretty_env_logger = "0.4.0" teloxide = { path = "../../" } [profile.release] diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs index 617fb1c0..e731200c 100644 --- a/examples/ping_pong_bot/src/main.rs +++ b/examples/ping_pong_bot/src/main.rs @@ -6,9 +6,11 @@ async fn main() { } async fn run() { - let bot = Bot::from_env().enable_logging(crate_name!()).build(); + teloxide::enable_logging!(); log::info!("Starting ping_pong_bot!"); + let bot = Bot::from_env(); + // Create a dispatcher with a single message handler that answers "pong" to // each incoming message. Dispatcher::::new(bot) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index ad251844..942caae1 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,5 +1,3 @@ -use log::LevelFilter; -use pretty_env_logger::env_logger::WriteStyle; use reqwest::Client; use std::sync::Arc; @@ -14,92 +12,57 @@ pub struct Bot { } impl Bot { - /// Returns [`BotBuilder`] from the `TELOXIDE_TOKEN` environmental variable - /// (a bot's token). + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and the default [`reqwest::Client`]. /// /// # Panics /// If cannot get the `TELOXIDE_TOKEN` environmental variable. /// - /// [`BotBuilder`]: crate::BotBuilder - pub fn from_env() -> BotBuilder { - BotBuilder { - token: std::env::var("TELOXIDE_TOKEN") + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env() -> Arc { + Self::new( + std::env::var("TELOXIDE_TOKEN") .expect("Cannot get the TELOXIDE_TOKEN env variable"), - client: None, - } + ) } - /// Returns [`BotBuilder`] with the specified token. + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and your [`reqwest::Client`]. /// - /// [`BotBuilder`]: crate::BotBuilder - pub fn new(token: S) -> BotBuilder + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env_with_client(client: Client) -> Arc { + Self::with_client( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + client, + ) + } + + /// Creates a new `Bot` with the specified token and the default + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn new(token: S) -> Arc where S: Into, { - BotBuilder { - token: token.into(), - client: None, - } + Self::with_client(token, Client::new()) } -} -/// Used to build [`Bot`]. -/// -/// [`Bot`]: crate::Bot -pub struct BotBuilder { - token: String, - client: Option, -} - -impl BotBuilder { - /// Sets your custom [`reqwest::Client`] (teloxide will make all requests - /// using it). + /// Creates a new `Bot` with the specified token and your + /// [`reqwest::Client`]. /// /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - pub fn client(mut self, client: Client) -> Self { - self.client = Some(client); - self - } - - /// Enables logging through [pretty-env-logger]. - /// - /// A logger will **only** print errors from teloxide and **all** logs from - /// your program. - /// - /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger - pub fn enable_logging(self, crate_name: &'static str) -> Self { - Self::enable_logging_with_filter(self, crate_name, LevelFilter::Trace) - } - - /// Enables logging through [pretty-env-logger]. - /// - /// A logger will **only** print errors from teloxide and restrict logs from - /// your program by the specified filter. - /// - /// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger - pub fn enable_logging_with_filter( - self, - crate_name: &'static str, - filter: LevelFilter, - ) -> Self { - pretty_env_logger::formatted_builder() - .write_style(WriteStyle::Auto) - .filter(Some(crate_name), filter) - .filter(Some("teloxide"), LevelFilter::Error) - .init(); - self - } - - /// Builds [`Bot`]. - /// - /// Sets the default [`request::Client`] if you haven't specified yours. - /// - /// [`request::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [`Bot`]: crate::Bot - pub fn build(self) -> Arc { - Arc::new(Bot { - token: self.token, - client: self.client.unwrap_or(Client::new()), + pub fn with_client(token: S, client: Client) -> Arc + where + S: Into, + { + Arc::new(Self { + token: token.into(), + client, }) } } diff --git a/src/lib.rs b/src/lib.rs index 2b2a3254..64f273ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ )] #![allow(clippy::match_bool)] -pub use bot::{Bot, BotBuilder}; +pub use bot::Bot; pub use errors::{ApiErrorKind, DownloadError, RequestError}; mod errors; @@ -12,17 +12,10 @@ mod net; mod bot; pub mod dispatching; +mod logging; pub mod prelude; pub mod requests; pub mod types; pub mod utils; extern crate teloxide_macros; - -/// Expands to a name of your crate. -#[macro_export] -macro_rules! crate_name { - () => { - env!("CARGO_PKG_NAME") - }; -} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 00000000..5080fb8d --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,50 @@ +/// Enables logging through [pretty-env-logger]. +/// +/// A logger will **only** print errors from teloxide and **all** logs from +/// your program. +/// +/// # Example +/// ``` +/// teloxide::enable_logging!(); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +#[macro_export] +macro_rules! enable_logging { + () => { + teloxide::enable_logging_with_filter!(log::LevelFilter::Trace); + }; +} + +/// Enables logging through [pretty-env-logger]. +/// +/// A logger will **only** print errors from teloxide and restrict logs from +/// your program by the specified filter. +/// +/// # Example +/// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e. +/// do not print traces): +/// +/// ``` +/// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +#[macro_export] +macro_rules! enable_logging_with_filter { + ($filter:expr) => { + pretty_env_logger::formatted_builder() + .write_style(pretty_env_logger::env_logger::WriteStyle::Auto) + .filter(Some(env!("CARGO_PKG_NAME")), $filter) + .filter(Some("teloxide"), log::LevelFilter::Error) + .init(); + }; +} diff --git a/src/prelude.rs b/src/prelude.rs index 7162d9b0..97b01f16 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,6 @@ //! Commonly used items. pub use crate::{ - crate_name, dispatching::{ dialogue::{ exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage,