This commit is contained in:
Temirkhan Myrzamadi 2020-02-13 23:23:22 +06:00
parent c9f811a311
commit 42e2f3fb42
14 changed files with 164 additions and 130 deletions

4
.gitignore vendored
View file

@ -3,7 +3,7 @@
Cargo.lock Cargo.lock
.idea/ .idea/
.vscode/ .vscode/
examples/target
examples/ping_pong_bot/target examples/ping_pong_bot/target
examples/dialogue_bot/target examples/dialogue_bot/target
examples/multiple_handlers_bot/target examples/multiple_handlers_bot/target
examples/commands_bot/target

View file

@ -14,7 +14,6 @@ tokio-util = { version = "0.2.0", features = ["full"] }
reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] } reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] }
log = "0.4.8" log = "0.4.8"
pretty_env_logger = "0.4.0"
bytes = "0.5.3" bytes = "0.5.3"
mime = "0.3.16" mime = "0.3.16"

View file

@ -1,5 +1,5 @@
[package] [package]
name = "admin_bot" name = "commands_bot"
version = "0.1.0" version = "0.1.0"
authors = ["p0lunin <dmytro.polunin@gmail.com>"] authors = ["p0lunin <dmytro.polunin@gmail.com>"]
edition = "2018" edition = "2018"
@ -9,6 +9,7 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9" tokio = "0.2.9"
pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } teloxide = { path = "../../" }
[profile.release] [profile.release]

View file

@ -1,15 +1,19 @@
use teloxide::prelude::*; use teloxide::{
use teloxide::utils::command::BotCommand; prelude::*, types::ChatPermissions, utils::command::BotCommand,
use teloxide::types::ChatPermissions; };
// Declare type of handler context // Declare type of handler context
type Ctx = DispatcherHandlerCtx<Message>; type Ctx = DispatcherHandlerCtx<Message>;
// Derive trait which allow to parse text with command into enum // 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 // (rename = "lowercase") means that names of variants of enum will be lowercase
// `description` will be add before description of command when you call Command::descriptions() // before parsing `description` will be add before description of command when
// you call Command::descriptions()
#[derive(BotCommand)] #[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 { enum Command {
#[command(description = "kick user from chat.")] #[command(description = "kick user from chat.")]
Kick, Kick,
@ -24,10 +28,10 @@ enum Command {
// Calculate time of restrict user. // Calculate time of restrict user.
fn calc_restrict_time(num: i32, unit: &str) -> Result<i32, &str> { fn calc_restrict_time(num: i32, unit: &str) -> Result<i32, &str> {
match unit { match unit {
"h"|"hours" => Ok(num * 3600), "h" | "hours" => Ok(num * 3600),
"m"|"minutes" => Ok(num * 60), "m" | "minutes" => Ok(num * 60),
"s"|"seconds" => Ok(num), "s" | "seconds" => Ok(num),
_ => Err("Allowed units: h, m, s") _ => 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) { let unit = match args.get(1) {
Some(s) => s, 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::<i32>() { match num.parse::<i32>() {
@ -60,12 +64,14 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
Some(mes) => match parse_time_restrict(args) { Some(mes) => match parse_time_restrict(args) {
// Mute user temporarily... // Mute user temporarily...
Ok(time) => { Ok(time) => {
ctx.bot.restrict_chat_member( ctx.bot
.restrict_chat_member(
ctx.update.chat_id(), ctx.update.chat_id(),
// Sender of message cannot be only in messages from channels // Sender of message cannot be only in messages from
// so we can use unwrap() // channels so we can use
// unwrap()
mes.from().unwrap().id, mes.from().unwrap().id,
ChatPermissions::default() ChatPermissions::default(),
) )
.until_date(ctx.update.date + time) .until_date(ctx.update.date + time)
.send() .send()
@ -73,18 +79,21 @@ async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
} }
// ...or permanently // ...or permanently
Err(msg) => { Err(msg) => {
ctx.bot.restrict_chat_member( ctx.bot
.restrict_chat_member(
ctx.update.chat_id(), ctx.update.chat_id(),
mes.from().unwrap().id, mes.from().unwrap().id,
ChatPermissions::default() ChatPermissions::default(),
) )
.send() .send()
.await?; .await?;
} }
}, },
None => { 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(()) Ok(())
} }
@ -94,13 +103,15 @@ async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> {
match ctx.update.reply_to_message() { match ctx.update.reply_to_message() {
Some(mes) => { Some(mes) => {
// `unban_chat_member` will also kick user from group chat // `unban_chat_member` will also kick user from group chat
ctx.bot.unban_chat_member( ctx.bot
ctx.update.chat_id(), .unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id)
mes.from().unwrap().id .send()
).send().await?; .await?;
}, }
None => { 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(()) Ok(())
@ -112,27 +123,31 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
Some(mes) => match parse_time_restrict(args) { Some(mes) => match parse_time_restrict(args) {
// Mute user temporarily... // Mute user temporarily...
Ok(time) => { Ok(time) => {
ctx.bot.kick_chat_member( ctx.bot
ctx.update.chat_id(), .kick_chat_member(
mes.from().unwrap().id ctx.update.chat_id(),
) mes.from().unwrap().id,
)
.until_date(ctx.update.date + time) .until_date(ctx.update.date + time)
.send() .send()
.await?; .await?;
} }
// ...or permanently // ...or permanently
Err(msg) => { Err(msg) => {
ctx.bot.kick_chat_member( ctx.bot
ctx.update.chat_id(), .kick_chat_member(
mes.from().unwrap().id ctx.update.chat_id(),
) mes.from().unwrap().id,
)
.send() .send()
.await?; .await?;
}, }
}, },
None => { 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(()) Ok(())
} }
@ -140,8 +155,9 @@ async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
// Handle all messages // Handle all messages
async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { async fn handle_command(ctx: Ctx) -> Result<(), RequestError> {
// If message not from group stop handled. // If message not from group stop handled.
// NOTE: in this case we have only one `message_handler`. If you have more, return // NOTE: in this case we have only one `message_handler`. If you have more,
// DispatcherHandlerResult::next() so that the following handlers can receive this message! // return DispatcherHandlerResult::next() so that the following handlers
// can receive this message!
if ctx.update.chat.is_group() { if ctx.update.chat.is_group() {
return Ok(()); return Ok(());
} }
@ -150,7 +166,7 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> {
// Parse text into command with args // Parse text into command with args
let (command, args): (Command, Vec<&str>) = match Command::parse(text) { let (command, args): (Command, Vec<&str>) = match Command::parse(text) {
Some(tuple) => tuple, Some(tuple) => tuple,
None => return Ok(()) None => return Ok(()),
}; };
match command { match command {
@ -178,7 +194,11 @@ async fn handle_command(ctx: Ctx) -> Result<(), RequestError> {
#[tokio::main] #[tokio::main]
async fn 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) Dispatcher::new(bot)
.message_handler(&handle_command) .message_handler(&handle_command)
.dispatch() .dispatch()

View file

@ -9,6 +9,7 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9" tokio = "0.2.9"
pretty_env_logger = "0.4.0"
smart-default = "0.6.0" smart-default = "0.6.0"
parse-display = "0.1.1" parse-display = "0.1.1"
teloxide = { path = "../../" } teloxide = { path = "../../" }

View file

@ -177,9 +177,11 @@ async fn main() {
} }
async fn run() { async fn run() {
let bot = Bot::from_env().enable_logging(crate_name!()).build(); teloxide::enable_logging!();
log::info!("Starting dialogue_bot!"); log::info!("Starting dialogue_bot!");
let bot = Bot::from_env();
Dispatcher::new(bot) Dispatcher::new(bot)
.message_handler(&DialogueDispatcher::new(|ctx| async move { .message_handler(&DialogueDispatcher::new(|ctx| async move {
handle_message(ctx) handle_message(ctx)

View file

@ -9,4 +9,5 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9" tokio = "0.2.9"
pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } teloxide = { path = "../../" }

View file

@ -6,9 +6,11 @@ async fn main() {
} }
async fn run() { async fn run() {
let bot = Bot::from_env().enable_logging(crate_name!()).build(); teloxide::enable_logging!();
log::info!("Starting multiple_handlers_bot!"); log::info!("Starting multiple_handlers_bot!");
let bot = Bot::from_env();
// Create a dispatcher with multiple handlers of different types. This will // Create a dispatcher with multiple handlers of different types. This will
// print One! and Two! on every incoming UpdateKind::Message. // print One! and Two! on every incoming UpdateKind::Message.
Dispatcher::<RequestError>::new(bot) Dispatcher::<RequestError>::new(bot)

View file

@ -9,6 +9,7 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9" tokio = "0.2.9"
pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } teloxide = { path = "../../" }
[profile.release] [profile.release]

View file

@ -6,9 +6,11 @@ async fn main() {
} }
async fn run() { async fn run() {
let bot = Bot::from_env().enable_logging(crate_name!()).build(); teloxide::enable_logging!();
log::info!("Starting ping_pong_bot!"); log::info!("Starting ping_pong_bot!");
let bot = Bot::from_env();
// Create a dispatcher with a single message handler that answers "pong" to // Create a dispatcher with a single message handler that answers "pong" to
// each incoming message. // each incoming message.
Dispatcher::<RequestError>::new(bot) Dispatcher::<RequestError>::new(bot)

View file

@ -1,5 +1,3 @@
use log::LevelFilter;
use pretty_env_logger::env_logger::WriteStyle;
use reqwest::Client; use reqwest::Client;
use std::sync::Arc; use std::sync::Arc;
@ -14,92 +12,57 @@ pub struct Bot {
} }
impl Bot { impl Bot {
/// Returns [`BotBuilder`] from the `TELOXIDE_TOKEN` environmental variable /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a
/// (a bot's token). /// bot's token) and the default [`reqwest::Client`].
/// ///
/// # Panics /// # Panics
/// If cannot get the `TELOXIDE_TOKEN` environmental variable. /// If cannot get the `TELOXIDE_TOKEN` environmental variable.
/// ///
/// [`BotBuilder`]: crate::BotBuilder /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
pub fn from_env() -> BotBuilder { pub fn from_env() -> Arc<Self> {
BotBuilder { Self::new(
token: std::env::var("TELOXIDE_TOKEN") std::env::var("TELOXIDE_TOKEN")
.expect("Cannot get the TELOXIDE_TOKEN env variable"), .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 /// # Panics
pub fn new<S>(token: S) -> BotBuilder /// 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> {
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<S>(token: S) -> Arc<Self>
where where
S: Into<String>, S: Into<String>,
{ {
BotBuilder { Self::with_client(token, Client::new())
token: token.into(),
client: None,
}
} }
}
/// Used to build [`Bot`]. /// Creates a new `Bot` with the specified token and your
/// /// [`reqwest::Client`].
/// [`Bot`]: crate::Bot
pub struct BotBuilder {
token: String,
client: Option<Client>,
}
impl BotBuilder {
/// Sets your custom [`reqwest::Client`] (teloxide will make all requests
/// using it).
/// ///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
pub fn client(mut self, client: Client) -> Self { pub fn with_client<S>(token: S, client: Client) -> Arc<Self>
self.client = Some(client); where
self S: Into<String>,
} {
Arc::new(Self {
/// Enables logging through [pretty-env-logger]. token: token.into(),
/// client,
/// 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<Bot> {
Arc::new(Bot {
token: self.token,
client: self.client.unwrap_or(Client::new()),
}) })
} }
} }

View file

@ -4,7 +4,7 @@
)] )]
#![allow(clippy::match_bool)] #![allow(clippy::match_bool)]
pub use bot::{Bot, BotBuilder}; pub use bot::Bot;
pub use errors::{ApiErrorKind, DownloadError, RequestError}; pub use errors::{ApiErrorKind, DownloadError, RequestError};
mod errors; mod errors;
@ -12,17 +12,10 @@ mod net;
mod bot; mod bot;
pub mod dispatching; pub mod dispatching;
mod logging;
pub mod prelude; pub mod prelude;
pub mod requests; pub mod requests;
pub mod types; pub mod types;
pub mod utils; pub mod utils;
extern crate teloxide_macros; extern crate teloxide_macros;
/// Expands to a name of your crate.
#[macro_export]
macro_rules! crate_name {
() => {
env!("CARGO_PKG_NAME")
};
}

50
src/logging.rs Normal file
View file

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

View file

@ -1,7 +1,6 @@
//! Commonly used items. //! Commonly used items.
pub use crate::{ pub use crate::{
crate_name,
dispatching::{ dispatching::{
dialogue::{ dialogue::{
exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage,