mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-14 11:44:04 +01:00
fixed documentation and examples
This commit is contained in:
commit
996af02fde
20 changed files with 689 additions and 162 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -3,7 +3,9 @@
|
|||
Cargo.lock
|
||||
.idea/
|
||||
.vscode/
|
||||
examples/target
|
||||
examples/ping_pong_bot/target
|
||||
examples/dialogue_bot/target
|
||||
examples/multiple_handlers_bot/target
|
||||
examples/multiple_handlers_bot/target
|
||||
examples/admin_bot/target
|
||||
examples/guess_a_number_bot/target
|
||||
examples/simple_commands_bot/target
|
|
@ -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"
|
||||
|
||||
|
@ -26,4 +25,9 @@ pin-project = "0.4.6"
|
|||
serde_with_macros = "1.0.1"
|
||||
either = "1.5.3"
|
||||
|
||||
teloxide-macros = { path = "teloxide-macros" }
|
||||
teloxide-macros = { path = "teloxide-macros" }
|
||||
|
||||
[dev-dependencies]
|
||||
smart-default = "0.6.0"
|
||||
rand = "0.7.3"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
|
|
@ -1,2 +1,9 @@
|
|||
# Examples
|
||||
Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable.
|
||||
Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable.
|
||||
|
||||
- [ping_pong_bot](ping_pong_bot) - Answers "pong" to each incoming message.
|
||||
- [simple_commands_bot](simple_commands_bot) - Shows how to deal with bot's commands.
|
||||
- [guess_a_number_bot](guess_a_number_bot) - The "guess a number" game.
|
||||
- [dialogue_bot](dialogue_bot) - Drive a dialogue with a user using a type-safe finite automaton.
|
||||
- [admin_bot](admin_bot) - A bot, which can ban, kick, and mute on a command.
|
||||
- [multiple_handlers_bot](multiple_handlers_bot) - Shows how multiple dispatcher's handlers relate to each other.
|
|
@ -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]
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
use teloxide::prelude::*;
|
||||
use teloxide::utils::command::BotCommand;
|
||||
use teloxide::types::ChatPermissions;
|
||||
// TODO: simplify this and use typed command variants (see https://github.com/teloxide/teloxide/issues/152).
|
||||
|
||||
// Declare type of handler context
|
||||
type Ctx = DispatcherHandlerCtx<Message>;
|
||||
use teloxide::{
|
||||
prelude::*, types::ChatPermissions, utils::command::BotCommand,
|
||||
};
|
||||
|
||||
// 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()
|
||||
// Derive BotCommand to parse text with a command into this enumeration.
|
||||
//
|
||||
// 1. rename = "lowercase" turns all the commands into lowercase letters.
|
||||
// 2. `description = "..."` specifies a text before all the commands.
|
||||
//
|
||||
// That is, you can just call Command::descriptions() to get a description of
|
||||
// your commands in this format:
|
||||
// %GENERAL-DESCRIPTION%
|
||||
// %PREFIX%%COMMAND% - %DESCRIPTION%
|
||||
#[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,
|
||||
|
@ -21,17 +29,17 @@ enum Command {
|
|||
Help,
|
||||
}
|
||||
|
||||
// Calculate time of restrict user.
|
||||
// Calculates time of user restriction.
|
||||
fn calc_restrict_time(num: i32, unit: &str) -> Result<i32, &str> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse args which user printed after command.
|
||||
// Parse arguments after a command.
|
||||
fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> {
|
||||
let num = match args.get(0) {
|
||||
Some(s) => s,
|
||||
|
@ -39,7 +47,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::<i32>() {
|
||||
|
@ -48,117 +56,122 @@ fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> {
|
|||
}
|
||||
}
|
||||
|
||||
// Parse input args into time to restrict
|
||||
// Parse arguments into a user restriction duration.
|
||||
fn parse_time_restrict(args: Vec<&str>) -> Result<i32, &str> {
|
||||
let (num, unit) = parse_args(args)?;
|
||||
calc_restrict_time(num, unit)
|
||||
}
|
||||
|
||||
// Mute user by replied message
|
||||
type Ctx = DispatcherHandlerCtx<Message>;
|
||||
|
||||
// Mute a user with a replied message.
|
||||
async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
|
||||
match ctx.update.reply_to_message() {
|
||||
Some(mes) => match parse_time_restrict(args) {
|
||||
Some(msg1) => 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()
|
||||
mes.from().unwrap().id,
|
||||
ChatPermissions::default()
|
||||
msg1.from().expect("Must be MessageKind::Common").id,
|
||||
ChatPermissions::default(),
|
||||
)
|
||||
.until_date(ctx.update.date + time)
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
// ...or permanently
|
||||
Err(msg) => {
|
||||
ctx.bot.restrict_chat_member(
|
||||
Err(_) => {
|
||||
ctx.bot
|
||||
.restrict_chat_member(
|
||||
ctx.update.chat_id(),
|
||||
mes.from().unwrap().id,
|
||||
ChatPermissions::default()
|
||||
msg1.from().unwrap().id,
|
||||
ChatPermissions::default(),
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
ctx.reply_to("Use this command in reply to another message").send().await?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Kick user by replied message
|
||||
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?;
|
||||
},
|
||||
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(())
|
||||
}
|
||||
|
||||
// Ban user by replied message
|
||||
// Kick a user with a replied message.
|
||||
async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> {
|
||||
match ctx.update.reply_to_message() {
|
||||
Some(mes) => {
|
||||
// bot.unban_chat_member can also kicks a user from a group chat.
|
||||
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?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Ban a user with replied message.
|
||||
async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
|
||||
match ctx.update.reply_to_message() {
|
||||
Some(mes) => match parse_time_restrict(args) {
|
||||
Some(message) => 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(),
|
||||
message.from().expect("Must be MessageKind::Common").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
|
||||
)
|
||||
Err(_) => {
|
||||
ctx.bot
|
||||
.kick_chat_member(
|
||||
ctx.update.chat_id(),
|
||||
message.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 a reply to another message!")
|
||||
.send()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Handle all messages
|
||||
// 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!
|
||||
if ctx.update.chat.is_group() {
|
||||
// The same as DispatcherHandlerResult::exit(Ok(())). If you have more
|
||||
// handlers, use DispatcherHandlerResult::next(...)
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(text) = ctx.update.text() {
|
||||
// Parse text into command with args
|
||||
// Parse text into a command with args.
|
||||
let (command, args): (Command, Vec<&str>) = match Command::parse(text) {
|
||||
Some(tuple) => tuple,
|
||||
None => return Ok(())
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
match command {
|
||||
Command::Help => {
|
||||
// Command::descriptions() return a message in format:
|
||||
//
|
||||
// %general_description%
|
||||
// %prefix%%command% - %description%
|
||||
ctx.answer(Command::descriptions()).send().await?;
|
||||
}
|
||||
Command::Kick => {
|
||||
|
@ -178,7 +191,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 admin_bot!");
|
||||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(bot)
|
||||
.message_handler(&handle_command)
|
||||
.dispatch()
|
||||
|
|
|
@ -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 = "../../" }
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// This is a bot that asks your full name, your age, your favourite kind of
|
||||
// music and sends all the gathered information back.
|
||||
//
|
||||
// # Example
|
||||
// ```
|
||||
// - Let's start! First, what's your full name?
|
||||
// - Luke Skywalker
|
||||
// - What a wonderful name! Your age?
|
||||
// - 26
|
||||
// - Good. Now choose your favourite music
|
||||
// *A keyboard of music kinds is displayed*
|
||||
// *You select Metal*
|
||||
// - Metal
|
||||
// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal
|
||||
// ```
|
||||
|
||||
#![allow(clippy::trivial_regex)]
|
||||
|
||||
#[macro_use]
|
||||
|
@ -177,9 +193,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)
|
||||
|
|
15
examples/guess_a_number_bot/Cargo.toml
Normal file
15
examples/guess_a_number_bot/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "guess_a_number_bot"
|
||||
version = "0.1.0"
|
||||
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
tokio = "0.2.9"
|
||||
smart-default = "0.6.0"
|
||||
rand = "0.7.3"
|
||||
pretty_env_logger = "0.4.0"
|
||||
teloxide = { path = "../../" }
|
116
examples/guess_a_number_bot/src/main.rs
Normal file
116
examples/guess_a_number_bot/src/main.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
// This is a guess-a-number game!
|
||||
//
|
||||
// # Example
|
||||
// ```
|
||||
// - Hello
|
||||
// - Let's play a game! Guess a number from 1 to 10 (inclusively).
|
||||
// - 4
|
||||
// - No.
|
||||
// - 3
|
||||
// - No.
|
||||
// - Blablabla
|
||||
// - Oh, please, send me a text message!
|
||||
// - 111
|
||||
// - Oh, please, send me a number in the range [1; 10]!
|
||||
// - 5
|
||||
// - Congratulations! You won!
|
||||
// ```
|
||||
|
||||
#[macro_use]
|
||||
extern crate smart_default;
|
||||
|
||||
use teloxide::prelude::*;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
// ============================================================================
|
||||
// [A type-safe finite automaton]
|
||||
// ============================================================================
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
enum Dialogue {
|
||||
#[default]
|
||||
Start,
|
||||
ReceiveAttempt(u8),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [Control a dialogue]
|
||||
// ============================================================================
|
||||
|
||||
async fn handle_message(
|
||||
ctx: DialogueHandlerCtx<Message, Dialogue>,
|
||||
) -> Result<DialogueStage<Dialogue>, RequestError> {
|
||||
match ctx.dialogue {
|
||||
Dialogue::Start => {
|
||||
ctx.answer(
|
||||
"Let's play a game! Guess a number from 1 to 10 (inclusively).",
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11)))
|
||||
}
|
||||
Dialogue::ReceiveAttempt(secret) => match ctx.update.text() {
|
||||
None => {
|
||||
ctx.answer("Oh, please, send me a text message!")
|
||||
.send()
|
||||
.await?;
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
Some(text) => match text.parse::<u8>() {
|
||||
Ok(attempt) => match attempt {
|
||||
x if !(1..=10).contains(&x) => {
|
||||
ctx.answer(
|
||||
"Oh, please, send me a number in the range [1; \
|
||||
10]!",
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
x if x == secret => {
|
||||
ctx.answer("Congratulations! You won!").send().await?;
|
||||
exit()
|
||||
}
|
||||
_ => {
|
||||
ctx.answer("No.").send().await?;
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
ctx.answer(
|
||||
"Oh, please, send me a number in the range [1; 10]!",
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [Run!]
|
||||
// ============================================================================
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
run().await;
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
teloxide::enable_logging!();
|
||||
log::info!("Starting guess_a_number_bot!");
|
||||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(bot)
|
||||
.message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||
handle_message(ctx)
|
||||
.await
|
||||
.expect("Something wrong with the bot!")
|
||||
}))
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
|
@ -9,4 +9,5 @@ edition = "2018"
|
|||
[dependencies]
|
||||
log = "0.4.8"
|
||||
tokio = "0.2.9"
|
||||
pretty_env_logger = "0.4.0"
|
||||
teloxide = { path = "../../" }
|
|
@ -1,3 +1,6 @@
|
|||
// This example demonstrates the ability of Dispatcher to deal with multiple
|
||||
// handlers.
|
||||
|
||||
use teloxide::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -6,9 +9,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::<RequestError>::new(bot)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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::<RequestError>::new(bot)
|
||||
|
|
14
examples/simple_commands_bot/Cargo.toml
Normal file
14
examples/simple_commands_bot/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "simple_commands_bot"
|
||||
version = "0.1.0"
|
||||
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
tokio = "0.2.9"
|
||||
rand = "0.7.3"
|
||||
pretty_env_logger = "0.4.0"
|
||||
teloxide = { path = "../../" }
|
63
examples/simple_commands_bot/src/main.rs
Normal file
63
examples/simple_commands_bot/src/main.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use teloxide::{prelude::*, utils::command::BotCommand};
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
#[derive(BotCommand)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
#[command(description = "display this text.")]
|
||||
Help,
|
||||
#[command(description = "be a cat.")]
|
||||
Meow,
|
||||
#[command(description = "generate a random number within [0; 1).")]
|
||||
Generate,
|
||||
}
|
||||
|
||||
async fn handle_command(
|
||||
ctx: DispatcherHandlerCtx<Message>,
|
||||
) -> Result<(), RequestError> {
|
||||
let text = match ctx.update.text() {
|
||||
Some(text) => text,
|
||||
None => {
|
||||
log::info!("Received a message, but not text.");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let command = match Command::parse(text) {
|
||||
Some((command, _)) => command,
|
||||
None => {
|
||||
log::info!("Received a text message, but not a command.");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match command {
|
||||
Command::Help => ctx.answer(Command::descriptions()).send().await?,
|
||||
Command::Generate => {
|
||||
ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string())
|
||||
.send()
|
||||
.await?
|
||||
}
|
||||
Command::Meow => ctx.answer("I am a cat! Meow!").send().await?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
run().await;
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
teloxide::enable_logging!();
|
||||
log::info!("Starting simple_commands_bot!");
|
||||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::<RequestError>::new(bot)
|
||||
.message_handler(&handle_command)
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
109
src/bot/mod.rs
109
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> {
|
||||
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<S>(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> {
|
||||
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
|
||||
S: Into<String>,
|
||||
{
|
||||
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<Client>,
|
||||
}
|
||||
|
||||
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<Bot> {
|
||||
Arc::new(Bot {
|
||||
token: self.token,
|
||||
client: self.client.unwrap_or(Client::new()),
|
||||
pub fn with_client<S>(token: S, client: Client) -> Arc<Self>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
Arc::new(Self {
|
||||
token: token.into(),
|
||||
client,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
261
src/lib.rs
261
src/lib.rs
|
@ -1,10 +1,260 @@
|
|||
//! A full-featured framework that empowers you to easily build [Telegram bots]
|
||||
//! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult
|
||||
//! stuff so you can focus only on your business logic.
|
||||
//!
|
||||
//! ## Features
|
||||
//! - **Type-safe.** teloxide leverages the Rust's type system with two serious
|
||||
//! implications: resistance to human mistakes and tight integration with
|
||||
//! IDEs. Write fast, avoid debugging as possible.
|
||||
//!
|
||||
//! - **Persistency.** By default, teloxide stores all user dialogues in RAM,
|
||||
//! but you can store them somewhere else (for example, in DB) just by
|
||||
//! implementing 2 functions.
|
||||
//!
|
||||
//! - **Convenient dialogues system.** Define a type-safe [finite automaton]
|
||||
//! and transition functions to drive a user dialogue with ease (see the
|
||||
//! examples below).
|
||||
//!
|
||||
//! - **Convenient API.** Automatic conversions are used to avoid boilerplate.
|
||||
//! For example, functions accept `Into<String>`, rather than `&str` or
|
||||
//! `String`, so you can call them without `.to_string()`/`.as_str()`/etc.
|
||||
//!
|
||||
//! ## The ping-pong bot
|
||||
//! This bot has a single message handler, which answers "pong" to each incoming
|
||||
//! message:
|
||||
//!
|
||||
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs))
|
||||
//! ```rust,no_run
|
||||
//! use teloxide::prelude::*;
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! teloxide::enable_logging!();
|
||||
//! log::info!("Starting the ping-pong bot!");
|
||||
//!
|
||||
//! let bot = Bot::from_env();
|
||||
//!
|
||||
//! Dispatcher::<RequestError>::new(bot)
|
||||
//! .message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||
//! ctx.answer("pong").send().await?;
|
||||
//! Ok(())
|
||||
//! })
|
||||
//! .dispatch()
|
||||
//! .await;
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Commands
|
||||
//! Commands are defined similar to how we define CLI using [structopt]. This
|
||||
//! bot says "I am a cat! Meow!" on `/meow`, generates a random number within
|
||||
//! [0; 1) on `/generate`, and shows the usage guide on `/help`:
|
||||
//!
|
||||
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs))
|
||||
//! ```rust,no_run
|
||||
//! # use teloxide::{prelude::*, utils::command::BotCommand};
|
||||
//! # use rand::{thread_rng, Rng};
|
||||
//! // Imports are omitted...
|
||||
//!
|
||||
//! #[derive(BotCommand)]
|
||||
//! #[command(
|
||||
//! rename = "lowercase",
|
||||
//! description = "These commands are supported:"
|
||||
//! )]
|
||||
//! enum Command {
|
||||
//! #[command(description = "display this text.")]
|
||||
//! Help,
|
||||
//! #[command(description = "be a cat.")]
|
||||
//! Meow,
|
||||
//! #[command(description = "generate a random number within [0; 1).")]
|
||||
//! Generate,
|
||||
//! }
|
||||
//!
|
||||
//! async fn handle_command(
|
||||
//! ctx: DispatcherHandlerCtx<Message>,
|
||||
//! ) -> Result<(), RequestError> {
|
||||
//! let text = match ctx.update.text() {
|
||||
//! Some(text) => text,
|
||||
//! None => {
|
||||
//! log::info!("Received a message, but not text.");
|
||||
//! return Ok(());
|
||||
//! }
|
||||
//! };
|
||||
//!
|
||||
//! let command = match Command::parse(text) {
|
||||
//! Some((command, _)) => command,
|
||||
//! None => {
|
||||
//! log::info!("Received a text message, but not a command.");
|
||||
//! return Ok(());
|
||||
//! }
|
||||
//! };
|
||||
//!
|
||||
//! match command {
|
||||
//! Command::Help => ctx.answer(Command::descriptions()).send().await?,
|
||||
//! Command::Generate => {
|
||||
//! ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string())
|
||||
//! .send()
|
||||
//! .await?
|
||||
//! }
|
||||
//! Command::Meow => ctx.answer("I am a cat! Meow!").send().await?,
|
||||
//! };
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // Setup is omitted...
|
||||
//! # teloxide::enable_logging!();
|
||||
//! # log::info!("Starting simple_commands_bot!");
|
||||
//! #
|
||||
//! # let bot = Bot::from_env();
|
||||
//! #
|
||||
//! # Dispatcher::<RequestError>::new(bot)
|
||||
//! # .message_handler(&handle_command)
|
||||
//! # .dispatch()
|
||||
//! # .await;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Guess a number
|
||||
//! Wanna see more? This is a bot, which starts a game on each incoming message.
|
||||
//! You must guess a number from 1 to 10 (inclusively):
|
||||
//!
|
||||
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs))
|
||||
//! ```rust,no_run
|
||||
//! # #[macro_use]
|
||||
//! # extern crate smart_default;
|
||||
//! # use teloxide::prelude::*;
|
||||
//! # use rand::{thread_rng, Rng};
|
||||
//! // Imports are omitted...
|
||||
//!
|
||||
//! #[derive(SmartDefault)]
|
||||
//! enum Dialogue {
|
||||
//! #[default]
|
||||
//! Start,
|
||||
//! ReceiveAttempt(u8),
|
||||
//! }
|
||||
//! async fn handle_message(
|
||||
//! ctx: DialogueHandlerCtx<Message, Dialogue>,
|
||||
//! ) -> Result<DialogueStage<Dialogue>, RequestError> {
|
||||
//! match ctx.dialogue {
|
||||
//! Dialogue::Start => {
|
||||
//! ctx.answer(
|
||||
//! "Let's play a game! Guess a number from 1 to 10
|
||||
//! (inclusively).",
|
||||
//! )
|
||||
//! .send()
|
||||
//! .await?;
|
||||
//! next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11)))
|
||||
//! }
|
||||
//! Dialogue::ReceiveAttempt(secret) => match ctx.update.text() {
|
||||
//! None => {
|
||||
//! ctx.answer("Oh, please, send me a text message!")
|
||||
//! .send()
|
||||
//! .await?;
|
||||
//! next(ctx.dialogue)
|
||||
//! }
|
||||
//! Some(text) => match text.parse::<u8>() {
|
||||
//! Ok(attempt) => match attempt {
|
||||
//! x if !(1..=10).contains(&x) => {
|
||||
//! ctx.answer(
|
||||
//! "Oh, please, send me a number in the range \
|
||||
//! [1; 10]!",
|
||||
//! )
|
||||
//! .send()
|
||||
//! .await?;
|
||||
//! next(ctx.dialogue)
|
||||
//! }
|
||||
//! x if x == secret => {
|
||||
//! ctx.answer("Congratulations! You won!")
|
||||
//! .send()
|
||||
//! .await?;
|
||||
//! exit()
|
||||
//! }
|
||||
//! _ => {
|
||||
//! ctx.answer("No.").send().await?;
|
||||
//! next(ctx.dialogue)
|
||||
//! }
|
||||
//! },
|
||||
//! Err(_) => {
|
||||
//! ctx.answer(
|
||||
//! "Oh, please, send me a number in the range [1; \
|
||||
//! 10]!",
|
||||
//! )
|
||||
//! .send()
|
||||
//! .await?;
|
||||
//! next(ctx.dialogue)
|
||||
//! }
|
||||
//! },
|
||||
//! },
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! # teloxide::enable_logging!();
|
||||
//! # log::info!("Starting guess_a_number_bot!");
|
||||
//! # let bot = Bot::from_env();
|
||||
//! // Setup is omitted...
|
||||
//!
|
||||
//! Dispatcher::new(bot)
|
||||
//! .message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||
//! handle_message(ctx)
|
||||
//! .await
|
||||
//! .expect("Something wrong with the bot!")
|
||||
//! }))
|
||||
//! .dispatch()
|
||||
//! .await;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Our [finite automaton], designating a user dialogue, cannot be in an invalid
|
||||
//! state. See [examples/dialogue_bot] to see a bit more complicated bot with
|
||||
//! dialogues.
|
||||
//!
|
||||
//! [See more examples](https://github.com/teloxide/teloxide/tree/dev/examples).
|
||||
//!
|
||||
//! ## Recommendations
|
||||
//!
|
||||
//! - Use this pattern:
|
||||
//!
|
||||
//! ```rust
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! run().await;
|
||||
//! }
|
||||
//!
|
||||
//! async fn run() {
|
||||
//! // Your logic here...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Instead of this:
|
||||
//!
|
||||
//! ```rust
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // Your logic here...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The second one produces very strange compiler messages because of the
|
||||
//! `#[tokio::main]` macro. The examples above use the first one for brevity.
|
||||
//!
|
||||
//! [Telegram bots]: https://telegram.org/blog/bot-revolution
|
||||
//! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html
|
||||
//! [Rust]: https://www.rust-lang.org/
|
||||
//! [finite automaton]: https://en.wikipedia.org/wiki/Finite-state_machine
|
||||
//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs
|
||||
//! [structopt]: https://docs.rs/structopt/0.3.9/structopt/
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://github.com/teloxide/teloxide/raw/dev/logo.svg",
|
||||
html_favicon_url = "https://github.com/teloxide/teloxide/raw/dev/ICON.png"
|
||||
)]
|
||||
#![allow(clippy::match_bool)]
|
||||
|
||||
pub use bot::{Bot, BotBuilder};
|
||||
pub use bot::Bot;
|
||||
pub use errors::{ApiErrorKind, DownloadError, RequestError};
|
||||
|
||||
mod errors;
|
||||
|
@ -12,17 +262,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")
|
||||
};
|
||||
}
|
||||
|
|
52
src/logging.rs
Normal file
52
src/logging.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/// Enables logging through [pretty-env-logger].
|
||||
///
|
||||
/// A logger will **only** print errors from teloxide and **all** logs from
|
||||
/// your program.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_compile
|
||||
/// 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] with a custom filter for your
|
||||
/// program.
|
||||
///
|
||||
/// 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):
|
||||
///
|
||||
/// ```no_compile
|
||||
/// 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
|
||||
/// [`LevelFilter::Debug`]: https://docs.rs/log/0.4.10/log/enum.LevelFilter.html
|
||||
#[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();
|
||||
};
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
//! Commonly used items.
|
||||
|
||||
pub use crate::{
|
||||
crate_name,
|
||||
dispatching::{
|
||||
dialogue::{
|
||||
exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage,
|
||||
|
|
|
@ -39,9 +39,12 @@
|
|||
//! assert_eq!(args, vec!["3", "hours"]);
|
||||
//! ```
|
||||
//!
|
||||
//! See [examples/admin_bot] as a more complicated examples.
|
||||
//!
|
||||
//! [`parse_command`]: crate::utils::command::parse_command
|
||||
//! [`parse_command_with_prefix`]:
|
||||
//! crate::utils::command::parse_command_with_prefix
|
||||
//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/
|
||||
|
||||
pub use teloxide_macros::BotCommand;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue