.github/workflows | ||
examples | ||
media | ||
src | ||
teloxide-macros | ||
.gitignore | ||
Cargo.toml | ||
CODE_STYLE.md | ||
CONTRIBUTING.md | ||
ICON.png | ||
LICENSE | ||
logo.svg | ||
README.md | ||
rustfmt.toml |
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.
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 much as possible.
-
Flexible API. teloxide gives you the power of streams: you can combine all 30+ patterns when working with updates from Telegram.
-
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 guess-a-number example below).
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=MyAwesomeToken
# Windows
$ set TELOXITE_TOKEN=MyAwesomeToken
- Be sure that you are up to date:
$ rustup update stable
- Execute
cargo new my_bot
, enter the directory and put these lines into yourCargo.toml
:
[dependencies]
teloxide = "0.1.0"
log = "0.4.8"
futures = "0.3.4"
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;
}
Click here to run it!
git clone https://github.com/teloxide/teloxide.git
cd teloxide/examples/ping_pong_bot
TELOXIDE_TOKEN=MyAwesomeToken cargo run
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_command(rx: DispatcherHandlerRx<Message>) {
rx.filter_map(|cx| {
future::ready(cx.update.text_owned().map(|text| (cx, text)))
})
.filter_map(|(cx, text)| {
future::ready(Command::parse(&text).map(|(command, _)| (cx, command)))
})
.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...
}
Click here to run it!
git clone https://github.com/teloxide/teloxide.git
cd teloxide/examples/simple_commands_bot
TELOXIDE_TOKEN=MyAwesomeToken cargo run
See? The dispatcher gives us a stream of messages, so we can handle it as we want! Here we use .filter_map()
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...
}
Click here to run it!
git clone https://github.com/teloxide/teloxide.git
cd teloxide/examples/guess_a_number_bot
TELOXIDE_TOKEN=MyAwesomeToken cargo run
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
).
If you're familiar with category theory, Dialogue
is almost a coproduct, such that:
X1
is()
X2
isu8
i1
isDialogue::Start
i2
isDialogue::ReceiveAttempt
But without the f
, f1
, f2
morphisms and the Y
object (which we can freely define if we wanted).
See examples/dialogue_bot to see a bit more complicated bot with dialogues. See more examples to get into teloxide!
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 in this README use the second variant for brevity.
Contributing
See CONRIBUTING.md.