From 6e11427ed1561cc3add6e8655bbdba520bcc8d86 Mon Sep 17 00:00:00 2001
From: Temirkhan Myrzamadi
Date: Tue, 26 May 2020 17:03:24 +0600
Subject: [PATCH] Update README.md
---
README.md | 232 ++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 165 insertions(+), 67 deletions(-)
diff --git a/README.md b/README.md
index c8d5f37e..4c4e5917 100644
--- a/README.md
+++ b/README.md
@@ -23,12 +23,11 @@
## Table of contents
- [Features](https://github.com/teloxide/teloxide#features)
- - [Getting started](https://github.com/teloxide/teloxide#getting-started)
- - [Examples](https://github.com/teloxide/teloxide#examples)
+ - [Setting up your environment](https://github.com/teloxide/teloxide#setting-up-your-environment)
+ - [API overview](https://github.com/teloxide/teloxide#api-overview)
- [The ping-pong bot](https://github.com/teloxide/teloxide#the-ping-pong-bot)
- [Commands](https://github.com/teloxide/teloxide#commands)
- - [Guess a number](https://github.com/teloxide/teloxide#guess-a-number)
- - [More examples!](https://github.com/teloxide/teloxide#more-examples)
+ - [Dialogues](https://github.com/teloxide/teloxide#dialogues)
- [Recommendations](https://github.com/teloxide/teloxide#recommendations)
- [FAQ](https://github.com/teloxide/teloxide#faq)
- [Where I can ask questions?](https://github.com/teloxide/teloxide#where-i-can-ask-questions)
@@ -64,9 +63,10 @@ Dialogues management is independent of how/where they are stored: just replace o
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](https://t.me/botfather) to get a token in the format `123456789:blablabla`.
- 2. Initialise the `TELOXIDE_TOKEN` environmental variable to your token:
+## Setting up your environment
+ 1. [Download Rust](http://rustup.rs/).
+ 2. Create a new bot using [@Botfather](https://t.me/botfather) to get a token in the format `123456789:blablabla`.
+ 3. Initialise the `TELOXIDE_TOKEN` environmental variable to your token:
```bash
# Unix
$ export TELOXIDE_TOKEN=
@@ -74,7 +74,7 @@ $ export TELOXIDE_TOKEN=
# Windows
$ set TELOXIDE_TOKEN=
```
- 3. Be sure that you are up to date:
+ 4. Be sure that you are up to date:
```bash
# If you're using stable
$ rustup update stable
@@ -85,7 +85,7 @@ $ rustup update nightly
$ rustup override set nightly
```
- 4. Execute `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
+ 5. Execute `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
```toml
[dependencies]
teloxide = "0.2.0"
@@ -94,7 +94,9 @@ tokio = "0.2.11"
pretty_env_logger = "0.4.0"
```
-## The ping-pong bot
+## API overview
+
+### 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/master/examples/ping_pong_bot/src/main.rs))
@@ -126,7 +128,7 @@ async fn main() {
-## Commands
+### Commands
Commands are defined similar to how we define CLI using [structopt](https://docs.rs/structopt/0.3.9/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/master/examples/simple_commands_bot/src/main.rs))
@@ -192,84 +194,180 @@ See? The dispatcher gives us a stream of messages, so we can handle it as we wan
- ... And lots of [others](https://docs.rs/futures/0.3.4/futures/stream/trait.StreamExt.html) and [others](https://docs.rs/teloxide/latest/teloxide/dispatching/trait.DispatcherHandlerRxExt.html) and [others](https://docs.rs/tokio/0.2.13/tokio/sync/index.html)!
-## 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):
+### Dialogues
+Wanna see more? This is how dialogues management is made in teloxide.
-([Full](https://github.com/teloxide/teloxide/blob/master/examples/guess_a_number_bot/src/main.rs))
+([dialogue_bot/src/states.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/states.rs))
```rust
// Imports are omitted...
-#[derive(SmartDefault)]
-enum Dialogue {
- #[default]
- Start,
- ReceiveAttempt(u8),
+pub struct StartState;
+
+pub struct ReceiveFullNameState {
+ rest: StartState,
}
-type Cx = DialogueDispatcherHandlerCx;
-type Res = ResponseResult>;
-
-async fn start(cx: Cx<()>) -> Res {
- 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)))
+pub struct ReceiveAgeState {
+ rest: ReceiveFullNameState,
+ full_name: String,
}
-async fn receive_attempt(cx: Cx) -> Res {
- let secret = cx.dialogue;
+pub struct ReceiveFavouriteMusicState {
+ rest: ReceiveAgeState,
+ age: u8,
+}
- match cx.update.text() {
- None => {
- cx.answer("Oh, please, send me a text message!").send().await?;
- next(Dialogue::ReceiveAttempt(secret))
+#[derive(Display)]
+#[display(
+ "Your full name: {rest.rest.full_name}, your age: {rest.age}, your \
+ favourite music: {favourite_music}"
+)]
+pub struct ExitState {
+ rest: ReceiveFavouriteMusicState,
+ favourite_music: FavouriteMusic,
+}
+
+up!(
+ StartState -> ReceiveFullNameState,
+ ReceiveFullNameState + [full_name: String] -> ReceiveAgeState,
+ ReceiveAgeState + [age: u8] -> ReceiveFavouriteMusicState,
+ ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState,
+);
+
+pub type Dialogue = Coprod!(
+ StartState,
+ ReceiveFullNameState,
+ ReceiveAgeState,
+ ReceiveFavouriteMusicState,
+);
+
+wrap_dialogue!(
+ Wrapper(Dialogue),
+ default Self(Dialogue::inject(StartState)),
+);
+```
+
+([dialogue_bot/src/transitions.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/transitions.rs))
+```rust
+// Imports are omitted...
+
+pub type In = TransitionIn;
+pub type Out = TransitionOut;
+
+pub async fn start(cx: In) -> Out {
+ let (cx, dialogue) = cx.unpack();
+
+ cx.answer_str("Let's start! First, what's your full name?").await?;
+ next(dialogue.up())
+}
+
+pub async fn receive_full_name(cx: In) -> Out {
+ let (cx, dialogue) = cx.unpack();
+
+ match cx.update.text_owned() {
+ Some(full_name) => {
+ cx.answer_str("What a wonderful name! Your age?").await?;
+ next(dialogue.up(full_name))
+ }
+ _ => {
+ cx.answer_str("Please, enter a text message!").await?;
+ next(dialogue)
}
- Some(text) => match text.parse::() {
- Ok(attempt) => {
- if attempt == secret {
- cx.answer("Congratulations! You won!").send().await?;
- exit()
- } else {
- cx.answer("No.").send().await?;
- next(Dialogue::ReceiveAttempt(secret))
- }
- }
- Err(_) => {
- cx.answer("Oh, please, send me a number in the range [1; 10]!")
- .send()
- .await?;
- next(Dialogue::ReceiveAttempt(secret))
- }
- },
}
}
-async fn handle_message(
- cx: DialogueDispatcherHandlerCx,
-) -> Res {
- // Match is omitted...
+pub async fn receive_age(cx: In) -> Out {
+ let (cx, dialogue) = cx.unpack();
+
+ match cx.update.text().map(str::parse) {
+ Some(Ok(age)) => {
+ cx.answer("Good. Now choose your favourite music:")
+ .reply_markup(FavouriteMusic::markup())
+ .send()
+ .await?;
+ next(dialogue.up(age))
+ }
+ _ => {
+ cx.answer_str("Please, enter a number!").await?;
+ next(dialogue)
+ }
+ }
}
-#[tokio::main]
-async fn main() {
- // Setup is omitted...
+pub async fn receive_favourite_music(
+ cx: In,
+) -> Out {
+ let (cx, dialogue) = cx.unpack();
+
+ match cx.update.text().map(str::parse) {
+ Some(Ok(favourite_music)) => {
+ cx.answer_str(format!("Fine. {}", dialogue.up(favourite_music)))
+ .await?;
+ exit()
+ }
+ _ => {
+ cx.answer_str("Please, enter from the keyboard!").await?;
+ next(dialogue)
+ }
+ }
}
```
-
-
-
-
-
-
+([dialogue_bot/src/favourite_music.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/favourite_music.rs))
+```rust
+// Imports are omitted...
-Our [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine), 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 would lead us to lots of unpleasant `.unwrap()`s.
+#[derive(Copy, Clone, Display, FromStr)]
+pub enum FavouriteMusic {
+ Rock,
+ Metal,
+ Pop,
+ Other,
+}
-Remember that a classical [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine) 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`).
+impl FavouriteMusic {
+ pub fn markup() -> ReplyKeyboardMarkup {
+ ReplyKeyboardMarkup::default().append_row(vec![
+ KeyboardButton::new("Rock"),
+ KeyboardButton::new("Metal"),
+ KeyboardButton::new("Pop"),
+ KeyboardButton::new("Other"),
+ ])
+ }
+}
+```
-See [examples/dialogue_bot](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs) to see a bit more complicated bot with dialogues.
-## [More examples!](https://github.com/teloxide/teloxide/tree/master/examples)
+([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs))
+```rust
+// Imports are omitted...
+
+#[tokio::main]
+async fn main() {
+ teloxide::enable_logging!();
+ log::info!("Starting dialogue_bot!");
+
+ let bot = Bot::from_env();
+
+ Dispatcher::new(bot)
+ .messages_handler(DialogueDispatcher::new(|cx| async move {
+ let DialogueWithCx { cx, dialogue } = cx;
+
+ // Unwrap without panic because of std::convert::Infallible.
+ let Wrapper(dialogue) = dialogue.unwrap();
+
+ dispatch!(
+ [cx, dialogue] ->
+ [start, receive_full_name, receive_age, receive_favourite_music]
+ )
+ .expect("Something wrong with the bot!")
+ }))
+ .dispatch()
+ .await;
+}
+```
+
+[More examples!](https://github.com/teloxide/teloxide/tree/master/examples)
## Recommendations
- Use this pattern: