mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-03 09:49:07 +01:00
Update README.md
This commit is contained in:
parent
75baa88c7e
commit
6e11427ed1
1 changed files with 165 additions and 67 deletions
232
README.md
232
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 <a href="https://en.wikipedia.org/wiki/Finite-state_machine">finite automaton</a> and transition functions to drive a user dialogue with ease (see <a href="#guess-a-number">the guess-a-number example</a> below).
|
||||
</p>
|
||||
|
||||
## 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=<Your token here>
|
||||
|
@ -74,7 +74,7 @@ $ export TELOXIDE_TOKEN=<Your token here>
|
|||
# Windows
|
||||
$ set TELOXIDE_TOKEN=<Your token here>
|
||||
```
|
||||
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() {
|
|||
</kbd>
|
||||
</div>
|
||||
|
||||
## 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<State> = DialogueDispatcherHandlerCx<Message, State>;
|
||||
type Res = ResponseResult<DialogueStage<Dialogue>>;
|
||||
|
||||
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<u8>) -> 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<State> = TransitionIn<State>;
|
||||
pub type Out = TransitionOut<Wrapper>;
|
||||
|
||||
pub async fn start(cx: In<StartState>) -> 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<ReceiveFullNameState>) -> 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::<u8>() {
|
||||
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<Message, Dialogue>,
|
||||
) -> Res {
|
||||
// Match is omitted...
|
||||
pub async fn receive_age(cx: In<ReceiveAgeState>) -> 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<ReceiveFavouriteMusicState>,
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div align="center">
|
||||
<kbd>
|
||||
<img src=https://github.com/teloxide/teloxide/raw/master/media/GUESS_A_NUMBER_BOT.png width="600" />
|
||||
</kbd>
|
||||
<br/><br/>
|
||||
</div>
|
||||
([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:
|
||||
|
|
Loading…
Reference in a new issue