[−][src]Crate teloxide
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.
See also our GitHub repository.
Getting started
- Create a new bot using @Botfather to get a token in the format
123456789:blablabla
. - Initialise the
TELOXIDE_TOKEN
environmental variable to your token:
# Unix
$ export TELOXIDE_TOKEN=<Your token here>
# Windows
$ set TELOXIDE_TOKEN=<Your token here>
- Be sure that you are up to date:
# If you're using stable
$ rustup update stable
$ rustup override set stable
# If you're using nightly
$ rustup update nightly
$ rustup override set nightly
- Execute
cargo new my_bot
, enter the directory and put these lines into yourCargo.toml
:
[dependencies]
teloxide = "0.2.0"
log = "0.4.8"
tokio = "0.2.11"
pretty_env_logger = "0.4.0"
The ping-pong bot
This bot has a single message handler, which answers "pong" to each incoming message:
(Full)
use teloxide::prelude::*; #[tokio::main] async fn main() { teloxide::enable_logging!(); log::info!("Starting ping_pong_bot!"); let bot = Bot::from_env(); Dispatcher::new(bot) .messages_handler(|rx: DispatcherHandlerRx<Message>| { rx.for_each(|message| async move { message.answer("pong").send().await.log_on_error().await; }) }) .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)
// 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, } fn generate() -> String { thread_rng().gen_range(0.0, 1.0).to_string() } async fn answer( cx: DispatcherHandlerCx<Message>, command: Command, ) -> ResponseResult<()> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Generate => cx.answer(generate()).send().await?, Command::Meow => cx.answer("I am a cat! Meow!").send().await?, }; Ok(()) } async fn handle_commands(rx: DispatcherHandlerRx<Message>) { // Only iterate through commands in a proper format: rx.commands::<Command, &str>(panic!("Insert here your bot's name")) // Execute all incoming commands concurrently: .for_each_concurrent(None, |(cx, command, _)| async move { answer(cx, command).await.log_on_error().await; }) .await; } #[tokio::main] async fn main() { // Setup is omitted... }
See? The dispatcher gives us a stream of messages, so we can handle it as we
want! Here we use our .commands::<Command>()
and
.for_each_concurrent()
, but others are also available:
.flatten()
.left_stream()
.scan()
.skip_while()
.zip()
.select_next_some()
.fold()
.inspect()
- ... And lots of others!
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)
// Setup is omitted... #[derive(SmartDefault)] enum Dialogue { #[default] Start, ReceiveAttempt(u8), } async fn handle_message( cx: DialogueDispatcherHandlerCx<Message, Dialogue>, ) -> ResponseResult<DialogueStage<Dialogue>> { match cx.dialogue { Dialogue::Start => { cx.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 cx.update.text() { None => { cx.answer("Oh, please, send me a text message!") .send() .await?; next(cx.dialogue) } Some(text) => match text.parse::<u8>() { Ok(attempt) => match attempt { x if !(1..=10).contains(&x) => { cx.answer( "Oh, please, send me a number in the range \ [1; 10]!", ) .send() .await?; next(cx.dialogue) } x if x == secret => { cx.answer("Congratulations! You won!") .send() .await?; exit() } _ => { cx.answer("No.").send().await?; next(cx.dialogue) } }, Err(_) => { cx.answer( "Oh, please, send me a number in the range [1; \ 10]!", ) .send() .await?; next(cx.dialogue) } }, }, } } #[tokio::main] async fn main() { // Setup is omitted... }
Our finite automaton, designating a user dialogue, cannot be in an invalid
state, and this is why it is called "type-safe". We could use enum
+
Option
s instead, but it will lead is to lots of unpleasure .unwrap()
s.
Remember that a classical finite automaton is defined by its initial
state, a list of its possible states and a transition function? We can think
that Dialogue
is a finite automaton with a context type at each state
(Dialogue::Start
has ()
, Dialogue::ReceiveAttempt
has u8
).
See examples/dialogue_bot to see a bit more complicated bot with dialogues. See all examples.
Recommendations
- Use this pattern:
#[tokio::main] async fn main() { run().await; } async fn run() { // Your logic here... }
Instead of this:
#[tokio::main] async fn main() { // Your logic here... }
The second one produces very strange compiler messages because of the
#[tokio::main]
macro. However, the examples above use the second one for
brevity.
Modules
dispatching | Updates dispatching. |
error_handlers | Convenient error handling. |
prelude | Commonly used items. |
requests | API requests. |
types | API types. |
utils | Some useful utilities. |
Macros
enable_logging | Enables logging through pretty-env-logger. |
enable_logging_with_filter | Enables logging through pretty-env-logger with a custom filter for your program. |
Structs
Bot | A Telegram bot used to send requests. |
Enums
ApiErrorKind | A kind of an API error returned from Telegram. |
DownloadError | An error occurred after downloading a file. |
RequestError | An error occurred after making a request to Telegram. |