🤖 An elegant Telegram bots framework for Rust https://docs.rs/teloxide
Find a file
2020-02-19 07:38:24 +06:00
.github/workflows Fix linter errors, enable ci for pull requests 2020-01-04 11:56:34 +03:00
examples Update README.md 2020-02-19 05:17:40 +06:00
media Create new screenshots from MacOS 2020-02-19 04:27:24 +03:00
src Replace MyAwesomeTOken with <Your token here> 2020-02-19 05:50:02 +06:00
teloxide-macros Fix rustfmt check 2020-02-19 05:02:51 +06:00
.gitignore Add examples/simple_commands_bot 2020-02-14 03:23:41 +06:00
Cargo.toml Remove native-tls-vendored (Cargo.toml) 2020-02-19 05:46:54 +06:00
CODE_STYLE.md Update CODE_STYLE.md 2020-01-25 04:59:37 +06:00
CONTRIBUTING.md Update CONTRIBUTING.md 2020-02-19 07:10:06 +06:00
ICON.png Improve the ICON.png quality 2019-10-15 16:16:56 +06:00
LICENSE Rename the lib to "teloxide" 2019-12-07 22:30:15 +03:00
logo.svg Make logo svg 2019-12-30 12:14:05 +03:00
README.md Create new screenshots from MacOS 2020-02-19 04:27:24 +03:00
rustfmt.toml Fixes 2020-02-19 04:54:41 +06:00

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

  1. Create a new bot using @Botfather to get a token in the format 123456789:blablabla.
  2. Initialise the TELOXIDE_TOKEN environmental variable to your token:
# Unix
$ export TELOXIDE_TOKEN=<Your token here>

# Windows
$ set TELOXITE_TOKEN=<Your token here>
  1. Be sure that you are up to date:
$ rustup update stable
  1. Execute cargo new my_bot, enter the directory and put these lines into your Cargo.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=<Your token here> 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=<Your token here> 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:

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=<Your token here> 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 + Options 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.

More examples!

Bot Description
ping_pong_bot Answers "pong" to each incoming message.
simple_commands_bot Shows how to deal with bot's commands.
guess_a_number_bot The "guess a number" game.
dialogue_bot Drive a dialogue with a user using a type-safe finite automaton.
admin_bot A bot, which can ban, kick, and mute on a command.

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.