mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-22 06:45:37 +01:00
Merge pull request #234 from teloxide/rework-dialogue-bot
Rework examples/dialogue_bot
This commit is contained in:
commit
36824b4bbb
13 changed files with 200 additions and 225 deletions
|
@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [0.3.0] - ???
|
## [0.3.0] - ???
|
||||||
### Added
|
### Added
|
||||||
- `BotBuilder`, which allows setting a default `ParseMode`.
|
- `BotBuilder`, which allows setting a default `ParseMode`.
|
||||||
|
- The `BotDialogue` trait.
|
||||||
|
- Automatic `dispatch` function generation via `#[derive(BotDialogue)]` + `#[transition(transition_fn)]`.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
- `Bot::{from_env_with_client, new, with_client}`.
|
- `Bot::{from_env_with_client, new, with_client}`.
|
||||||
|
|
|
@ -53,7 +53,7 @@ serde_cbor = { version = "0.11.1", optional = true }
|
||||||
bincode = { version = "1.3.1", optional = true }
|
bincode = { version = "1.3.1", optional = true }
|
||||||
|
|
||||||
#teloxide-macros = "0.3.1"
|
#teloxide-macros = "0.3.1"
|
||||||
teloxide-macros = { git = "http://github.com/teloxide/teloxide-macros", branch = "dev" }
|
teloxide-macros = { git = "http://github.com/teloxide/teloxide-macros", branch = "master" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
|
|
183
README.md
183
README.md
|
@ -185,48 +185,50 @@ States and transition functions are placed into separated modules. For example:
|
||||||
```rust
|
```rust
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
#[derive(BotDialogue, SmartDefault, From)]
|
||||||
|
pub enum Dialogue {
|
||||||
|
#[default]
|
||||||
|
#[transition(start)]
|
||||||
|
Start(StartState),
|
||||||
|
|
||||||
|
#[transition(receive_days_of_week)]
|
||||||
|
ReceiveDaysOfWeek(ReceiveDaysOfWeekState),
|
||||||
|
|
||||||
|
#[transition(receive_10x5_answer)]
|
||||||
|
Receive10x5Answer(Receive10x5AnswerState),
|
||||||
|
|
||||||
|
#[transition(receive_gandalf_alternative_name)]
|
||||||
|
ReceiveGandalfAlternativeName(ReceiveGandalfAlternativeNameState),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StartState;
|
pub struct StartState;
|
||||||
|
|
||||||
pub struct ReceiveFullNameState {
|
pub struct ReceiveDaysOfWeekState {
|
||||||
rest: StartState,
|
rest: StartState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ReceiveAgeState {
|
pub struct Receive10x5AnswerState {
|
||||||
rest: ReceiveFullNameState,
|
rest: ReceiveDaysOfWeekState,
|
||||||
full_name: String,
|
days_of_week: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ReceiveFavouriteMusicState {
|
pub struct ReceiveGandalfAlternativeNameState {
|
||||||
rest: ReceiveAgeState,
|
rest: Receive10x5AnswerState,
|
||||||
age: u8,
|
_10x5_answer: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Display)]
|
|
||||||
#[display(
|
|
||||||
"Your full name: {rest.rest.full_name}, your age: {rest.age}, your \
|
|
||||||
favourite music: {favourite_music}"
|
|
||||||
)]
|
|
||||||
pub struct ExitState {
|
pub struct ExitState {
|
||||||
rest: ReceiveFavouriteMusicState,
|
rest: ReceiveGandalfAlternativeNameState,
|
||||||
favourite_music: FavouriteMusic,
|
gandalf_alternative_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
up!(
|
up!(
|
||||||
StartState -> ReceiveFullNameState,
|
StartState -> ReceiveDaysOfWeekState,
|
||||||
ReceiveFullNameState + [full_name: String] -> ReceiveAgeState,
|
ReceiveDaysOfWeekState + [days_of_week: u8] -> Receive10x5AnswerState,
|
||||||
ReceiveAgeState + [age: u8] -> ReceiveFavouriteMusicState,
|
Receive10x5AnswerState + [_10x5_answer: u8] -> ReceiveGandalfAlternativeNameState,
|
||||||
ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState,
|
ReceiveGandalfAlternativeNameState + [gandalf_alternative_name: String] -> ExitState,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(SmartDefault, From)]
|
|
||||||
pub enum Dialogue {
|
|
||||||
#[default]
|
|
||||||
Start(StartState),
|
|
||||||
ReceiveFullName(ReceiveFullNameState),
|
|
||||||
ReceiveAge(ReceiveAgeState),
|
|
||||||
ReceiveFavouriteMusic(ReceiveFavouriteMusicState),
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The handy `up!` macro automatically generates functions that complete one state to another by appending a field. Here are the transition functions:
|
The handy `up!` macro automatically generates functions that complete one state to another by appending a field. Here are the transition functions:
|
||||||
|
@ -235,98 +237,66 @@ The handy `up!` macro automatically generates functions that complete one state
|
||||||
```rust
|
```rust
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
pub type Cx = UpdateWithCx<Message>;
|
|
||||||
pub type Out = TransitionOut<Dialogue>;
|
pub type Out = TransitionOut<Dialogue>;
|
||||||
|
|
||||||
async fn start(cx: Cx, state: StartState) -> Out {
|
pub async fn start(cx: TransitionIn, state: StartState) -> Out {
|
||||||
cx.answer_str("Let's start! First, what's your full name?").await?;
|
cx.answer_str("Let's start our test! How many days per week are there?")
|
||||||
|
.await?;
|
||||||
next(state.up())
|
next(state.up())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive_full_name(cx: Cx, state: ReceiveFullNameState) -> Out {
|
pub async fn receive_days_of_week(
|
||||||
match cx.update.text_owned() {
|
cx: TransitionIn,
|
||||||
Some(full_name) => {
|
state: ReceiveDaysOfWeekState,
|
||||||
cx.answer_str("What a wonderful name! Your age?").await?;
|
|
||||||
next(state.up(full_name))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
cx.answer_str("Please, enter a text message!").await?;
|
|
||||||
next(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_age(cx: Cx, state: ReceiveAgeState) -> Out {
|
|
||||||
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(state.up(age))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
cx.answer_str("Please, enter a number!").await?;
|
|
||||||
next(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_favourite_music(
|
|
||||||
cx: Cx,
|
|
||||||
state: ReceiveFavouriteMusicState,
|
|
||||||
) -> Out {
|
) -> Out {
|
||||||
match cx.update.text().map(str::parse) {
|
match cx.update.text().map(str::parse) {
|
||||||
Some(Ok(favourite_music)) => {
|
Some(Ok(ans)) if ans == 7 => {
|
||||||
cx.answer_str(format!("Fine. {}", state.up(favourite_music)))
|
cx.answer_str("10*5 = ?").await?;
|
||||||
.await?;
|
next(state.up(ans))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cx.answer_str("Try again.").await?;
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_10x5_answer(
|
||||||
|
cx: TransitionIn,
|
||||||
|
state: Receive10x5AnswerState,
|
||||||
|
) -> Out {
|
||||||
|
match cx.update.text().map(str::parse) {
|
||||||
|
Some(Ok(ans)) if ans == 50 => {
|
||||||
|
cx.answer_str("What's an alternative name of Gandalf?").await?;
|
||||||
|
next(state.up(ans))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cx.answer_str("Try again.").await?;
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_gandalf_alternative_name(
|
||||||
|
cx: TransitionIn,
|
||||||
|
state: ReceiveGandalfAlternativeNameState,
|
||||||
|
) -> Out {
|
||||||
|
match cx.update.text() {
|
||||||
|
Some(ans) if ans == "Mithrandir" => {
|
||||||
|
cx.answer_str(
|
||||||
|
"Congratulations! You've successfully passed the test!",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
exit()
|
exit()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
cx.answer_str("Please, enter from the keyboard!").await?;
|
cx.answer_str("Try again.").await?;
|
||||||
next(state)
|
next(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch(cx: Cx, dialogue: Dialogue) -> Out {
|
|
||||||
match dialogue {
|
|
||||||
Dialogue::Start(state) => start(cx, state).await,
|
|
||||||
Dialogue::ReceiveFullName(state) => receive_full_name(cx, state).await,
|
|
||||||
Dialogue::ReceiveAge(state) => receive_age(cx, state).await,
|
|
||||||
Dialogue::ReceiveFavouriteMusic(state) => {
|
|
||||||
receive_favourite_music(cx, state).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
([dialogue_bot/src/favourite_music.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/favourite_music.rs))
|
|
||||||
```rust
|
|
||||||
// Imports are omitted...
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Display, FromStr)]
|
|
||||||
pub enum FavouriteMusic {
|
|
||||||
Rock,
|
|
||||||
Metal,
|
|
||||||
Pop,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FavouriteMusic {
|
|
||||||
pub fn markup() -> ReplyKeyboardMarkup {
|
|
||||||
ReplyKeyboardMarkup::default().append_row(vec![
|
|
||||||
KeyboardButton::new("Rock"),
|
|
||||||
KeyboardButton::new("Metal"),
|
|
||||||
KeyboardButton::new("Pop"),
|
|
||||||
KeyboardButton::new("Other"),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs))
|
([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs))
|
||||||
```rust
|
```rust
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
@ -340,9 +310,12 @@ async fn main() {
|
||||||
|
|
||||||
Dispatcher::new(bot)
|
Dispatcher::new(bot)
|
||||||
.messages_handler(DialogueDispatcher::new(
|
.messages_handler(DialogueDispatcher::new(
|
||||||
|input: TransitionIn<Dialogue, Infallible>| async move {
|
|input: DialogueWithCx<Message, Dialogue, Infallible>| async move {
|
||||||
// Unwrap without panic because of std::convert::Infallible.
|
// Unwrap without panic because of std::convert::Infallible.
|
||||||
dispatch(input.cx, input.dialogue.unwrap())
|
input
|
||||||
|
.dialogue
|
||||||
|
.unwrap()
|
||||||
|
.dispatch(input.cx)
|
||||||
.await
|
.await
|
||||||
.expect("Something wrong with the bot!")
|
.expect("Something wrong with the bot!")
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,12 +10,11 @@ edition = "2018"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
tokio = "0.2.9"
|
tokio = "0.2.9"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
futures = "0.3.5"
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
derive_more = "0.99.9"
|
derive_more = "0.99.9"
|
||||||
parse-display = "0.1.1"
|
|
||||||
|
|
||||||
teloxide = { path = "../../" }
|
teloxide = { path = "../../" }
|
||||||
|
teloxide-macros = { git = "http://github.com/teloxide/teloxide-macros", branch = "master" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
|
@ -1,22 +0,0 @@
|
||||||
use parse_display::{Display, FromStr};
|
|
||||||
|
|
||||||
use teloxide::types::{KeyboardButton, ReplyKeyboardMarkup};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Display, FromStr)]
|
|
||||||
pub enum FavouriteMusic {
|
|
||||||
Rock,
|
|
||||||
Metal,
|
|
||||||
Pop,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FavouriteMusic {
|
|
||||||
pub fn markup() -> ReplyKeyboardMarkup {
|
|
||||||
ReplyKeyboardMarkup::default().append_row(vec![
|
|
||||||
KeyboardButton::new("Rock"),
|
|
||||||
KeyboardButton::new("Metal"),
|
|
||||||
KeyboardButton::new("Pop"),
|
|
||||||
KeyboardButton::new("Other"),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,13 +21,13 @@
|
||||||
extern crate smart_default;
|
extern crate smart_default;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate derive_more;
|
extern crate derive_more;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate teloxide_macros;
|
||||||
|
|
||||||
mod favourite_music;
|
|
||||||
mod states;
|
mod states;
|
||||||
mod transitions;
|
mod transitions;
|
||||||
|
|
||||||
use states::*;
|
use states::*;
|
||||||
use transitions::*;
|
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use teloxide::prelude::*;
|
use teloxide::prelude::*;
|
||||||
|
@ -45,9 +45,12 @@ async fn run() {
|
||||||
|
|
||||||
Dispatcher::new(bot)
|
Dispatcher::new(bot)
|
||||||
.messages_handler(DialogueDispatcher::new(
|
.messages_handler(DialogueDispatcher::new(
|
||||||
|input: TransitionIn<Dialogue, Infallible>| async move {
|
|input: DialogueWithCx<Message, Dialogue, Infallible>| async move {
|
||||||
// Unwrap without panic because of std::convert::Infallible.
|
// Unwrap without panic because of std::convert::Infallible.
|
||||||
dispatch(input.cx, input.dialogue.unwrap())
|
input
|
||||||
|
.dialogue
|
||||||
|
.unwrap()
|
||||||
|
.dispatch(input.cx)
|
||||||
.await
|
.await
|
||||||
.expect("Something wrong with the bot!")
|
.expect("Something wrong with the bot!")
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,47 +1,51 @@
|
||||||
use teloxide::prelude::*;
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
use super::favourite_music::FavouriteMusic;
|
use super::transitions::{
|
||||||
use parse_display::Display;
|
receive_10x5_answer, receive_days_of_week,
|
||||||
|
receive_gandalf_alternative_name, start,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(BotDialogue, SmartDefault, From)]
|
||||||
|
pub enum Dialogue {
|
||||||
|
#[default]
|
||||||
|
#[transition(start)]
|
||||||
|
Start(StartState),
|
||||||
|
|
||||||
|
#[transition(receive_days_of_week)]
|
||||||
|
ReceiveDaysOfWeek(ReceiveDaysOfWeekState),
|
||||||
|
|
||||||
|
#[transition(receive_10x5_answer)]
|
||||||
|
Receive10x5Answer(Receive10x5AnswerState),
|
||||||
|
|
||||||
|
#[transition(receive_gandalf_alternative_name)]
|
||||||
|
ReceiveGandalfAlternativeName(ReceiveGandalfAlternativeNameState),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StartState;
|
pub struct StartState;
|
||||||
|
|
||||||
pub struct ReceiveFullNameState {
|
pub struct ReceiveDaysOfWeekState {
|
||||||
rest: StartState,
|
rest: StartState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ReceiveAgeState {
|
pub struct Receive10x5AnswerState {
|
||||||
rest: ReceiveFullNameState,
|
rest: ReceiveDaysOfWeekState,
|
||||||
full_name: String,
|
days_of_week: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ReceiveFavouriteMusicState {
|
pub struct ReceiveGandalfAlternativeNameState {
|
||||||
rest: ReceiveAgeState,
|
rest: Receive10x5AnswerState,
|
||||||
age: u8,
|
_10x5_answer: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Display)]
|
|
||||||
#[display(
|
|
||||||
"Your full name: {rest.rest.full_name}, your age: {rest.age}, your \
|
|
||||||
favourite music: {favourite_music}"
|
|
||||||
)]
|
|
||||||
pub struct ExitState {
|
pub struct ExitState {
|
||||||
rest: ReceiveFavouriteMusicState,
|
rest: ReceiveGandalfAlternativeNameState,
|
||||||
favourite_music: FavouriteMusic,
|
gandalf_alternative_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
up!(
|
up!(
|
||||||
StartState -> ReceiveFullNameState,
|
StartState -> ReceiveDaysOfWeekState,
|
||||||
ReceiveFullNameState + [full_name: String] -> ReceiveAgeState,
|
ReceiveDaysOfWeekState + [days_of_week: u8] -> Receive10x5AnswerState,
|
||||||
ReceiveAgeState + [age: u8] -> ReceiveFavouriteMusicState,
|
Receive10x5AnswerState + [_10x5_answer: u8] -> ReceiveGandalfAlternativeNameState,
|
||||||
ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState,
|
ReceiveGandalfAlternativeNameState + [gandalf_alternative_name: String] -> ExitState,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(SmartDefault, From)]
|
|
||||||
pub enum Dialogue {
|
|
||||||
#[default]
|
|
||||||
Start(StartState),
|
|
||||||
ReceiveFullName(ReceiveFullNameState),
|
|
||||||
ReceiveAge(ReceiveAgeState),
|
|
||||||
ReceiveFavouriteMusic(ReceiveFavouriteMusicState),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,68 +1,64 @@
|
||||||
|
use crate::states::{
|
||||||
|
Dialogue, Receive10x5AnswerState, ReceiveDaysOfWeekState,
|
||||||
|
ReceiveGandalfAlternativeNameState, StartState,
|
||||||
|
};
|
||||||
use teloxide::prelude::*;
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
use super::{favourite_music::FavouriteMusic, states::*};
|
|
||||||
|
|
||||||
pub type Cx = UpdateWithCx<Message>;
|
|
||||||
pub type Out = TransitionOut<Dialogue>;
|
pub type Out = TransitionOut<Dialogue>;
|
||||||
|
|
||||||
async fn start(cx: Cx, state: StartState) -> Out {
|
pub async fn start(cx: TransitionIn, state: StartState) -> Out {
|
||||||
cx.answer_str("Let's start! First, what's your full name?").await?;
|
cx.answer_str("Let's start our test! How many days per week are there?")
|
||||||
|
.await?;
|
||||||
next(state.up())
|
next(state.up())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive_full_name(cx: Cx, state: ReceiveFullNameState) -> Out {
|
pub async fn receive_days_of_week(
|
||||||
match cx.update.text_owned() {
|
cx: TransitionIn,
|
||||||
Some(full_name) => {
|
state: ReceiveDaysOfWeekState,
|
||||||
cx.answer_str("What a wonderful name! Your age?").await?;
|
|
||||||
next(state.up(full_name))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
cx.answer_str("Please, enter a text message!").await?;
|
|
||||||
next(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_age(cx: Cx, state: ReceiveAgeState) -> Out {
|
|
||||||
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(state.up(age))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
cx.answer_str("Please, enter a number!").await?;
|
|
||||||
next(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive_favourite_music(
|
|
||||||
cx: Cx,
|
|
||||||
state: ReceiveFavouriteMusicState,
|
|
||||||
) -> Out {
|
) -> Out {
|
||||||
match cx.update.text().map(str::parse) {
|
match cx.update.text().map(str::parse) {
|
||||||
Some(Ok(favourite_music)) => {
|
Some(Ok(ans)) if ans == 7 => {
|
||||||
cx.answer_str(format!("Fine. {}", state.up(favourite_music)))
|
cx.answer_str("10*5 = ?").await?;
|
||||||
.await?;
|
next(state.up(ans))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cx.answer_str("Try again.").await?;
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_10x5_answer(
|
||||||
|
cx: TransitionIn,
|
||||||
|
state: Receive10x5AnswerState,
|
||||||
|
) -> Out {
|
||||||
|
match cx.update.text().map(str::parse) {
|
||||||
|
Some(Ok(ans)) if ans == 50 => {
|
||||||
|
cx.answer_str("What's an alternative name of Gandalf?").await?;
|
||||||
|
next(state.up(ans))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cx.answer_str("Try again.").await?;
|
||||||
|
next(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_gandalf_alternative_name(
|
||||||
|
cx: TransitionIn,
|
||||||
|
state: ReceiveGandalfAlternativeNameState,
|
||||||
|
) -> Out {
|
||||||
|
match cx.update.text() {
|
||||||
|
Some(ans) if ans == "Mithrandir" => {
|
||||||
|
cx.answer_str(
|
||||||
|
"Congratulations! You've successfully passed the test!",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
exit()
|
exit()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
cx.answer_str("Please, enter from the keyboard!").await?;
|
cx.answer_str("Try again.").await?;
|
||||||
next(state)
|
next(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch(cx: Cx, dialogue: Dialogue) -> Out {
|
|
||||||
match dialogue {
|
|
||||||
Dialogue::Start(state) => start(cx, state).await,
|
|
||||||
Dialogue::ReceiveFullName(state) => receive_full_name(cx, state).await,
|
|
||||||
Dialogue::ReceiveAge(state) => receive_age(cx, state).await,
|
|
||||||
Dialogue::ReceiveFavouriteMusic(state) => {
|
|
||||||
receive_favourite_music(cx, state).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ enum Error {
|
||||||
StorageError(#[from] StorageError),
|
StorageError(#[from] StorageError),
|
||||||
}
|
}
|
||||||
|
|
||||||
type In = TransitionIn<Dialogue, StorageError>;
|
type In = DialogueWithCx<Message, Dialogue, StorageError>;
|
||||||
|
|
||||||
async fn handle_message(input: In) -> Out {
|
async fn handle_message(input: In) -> Out {
|
||||||
let (cx, dialogue) = input.unpack();
|
let (cx, dialogue) = input.unpack();
|
||||||
|
|
|
@ -2,10 +2,9 @@ use teloxide::prelude::*;
|
||||||
|
|
||||||
use super::states::*;
|
use super::states::*;
|
||||||
|
|
||||||
pub type Cx = UpdateWithCx<Message>;
|
|
||||||
pub type Out = TransitionOut<Dialogue>;
|
pub type Out = TransitionOut<Dialogue>;
|
||||||
|
|
||||||
async fn start(cx: Cx, state: StartState, text: &str) -> Out {
|
async fn start(cx: TransitionIn, state: StartState, text: &str) -> Out {
|
||||||
if let Ok(number) = text.parse() {
|
if let Ok(number) = text.parse() {
|
||||||
cx.answer_str(format!(
|
cx.answer_str(format!(
|
||||||
"Remembered number {}. Now use /get or /reset",
|
"Remembered number {}. Now use /get or /reset",
|
||||||
|
@ -19,7 +18,11 @@ async fn start(cx: Cx, state: StartState, text: &str) -> Out {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn have_number(cx: Cx, state: HaveNumberState, text: &str) -> Out {
|
async fn have_number(
|
||||||
|
cx: TransitionIn,
|
||||||
|
state: HaveNumberState,
|
||||||
|
text: &str,
|
||||||
|
) -> Out {
|
||||||
let num = state.number;
|
let num = state.number;
|
||||||
|
|
||||||
if text.starts_with("/get") {
|
if text.starts_with("/get") {
|
||||||
|
@ -34,7 +37,7 @@ async fn have_number(cx: Cx, state: HaveNumberState, text: &str) -> Out {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch(cx: Cx, dialogue: Dialogue, text: &str) -> Out {
|
pub async fn dispatch(cx: TransitionIn, dialogue: Dialogue, text: &str) -> Out {
|
||||||
match dialogue {
|
match dialogue {
|
||||||
Dialogue::Start(state) => start(cx, state, text).await,
|
Dialogue::Start(state) => start(cx, state, text).await,
|
||||||
Dialogue::HaveNumber(state) => have_number(cx, state, text).await,
|
Dialogue::HaveNumber(state) => have_number(cx, state, text).await,
|
||||||
|
|
14
src/dispatching/dialogue/bot_dialogue.rs
Normal file
14
src/dispatching/dialogue/bot_dialogue.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::{
|
||||||
|
dispatching::{dialogue::TransitionOut, UpdateWithCx},
|
||||||
|
types::Message,
|
||||||
|
};
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
|
||||||
|
/// Represents a dialogue FSM.
|
||||||
|
pub trait BotDialogue: Default {
|
||||||
|
/// Turns itself into another state, depending on the input message.
|
||||||
|
fn dispatch(
|
||||||
|
self,
|
||||||
|
cx: UpdateWithCx<Message>,
|
||||||
|
) -> BoxFuture<'static, TransitionOut<Self>>;
|
||||||
|
}
|
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
|
mod bot_dialogue;
|
||||||
mod dialogue_dispatcher;
|
mod dialogue_dispatcher;
|
||||||
mod dialogue_dispatcher_handler;
|
mod dialogue_dispatcher_handler;
|
||||||
mod dialogue_stage;
|
mod dialogue_stage;
|
||||||
|
@ -50,6 +51,7 @@ mod get_chat_id;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
use crate::{requests::ResponseResult, types::Message};
|
use crate::{requests::ResponseResult, types::Message};
|
||||||
|
pub use bot_dialogue::BotDialogue;
|
||||||
pub use dialogue_dispatcher::DialogueDispatcher;
|
pub use dialogue_dispatcher::DialogueDispatcher;
|
||||||
pub use dialogue_dispatcher_handler::DialogueDispatcherHandler;
|
pub use dialogue_dispatcher_handler::DialogueDispatcherHandler;
|
||||||
pub use dialogue_stage::{exit, next, DialogueStage};
|
pub use dialogue_stage::{exit, next, DialogueStage};
|
||||||
|
@ -59,6 +61,7 @@ pub use get_chat_id::GetChatId;
|
||||||
#[cfg(feature = "redis-storage")]
|
#[cfg(feature = "redis-storage")]
|
||||||
pub use storage::{RedisStorage, RedisStorageError};
|
pub use storage::{RedisStorage, RedisStorageError};
|
||||||
|
|
||||||
|
use crate::dispatching::UpdateWithCx;
|
||||||
pub use storage::{serializer, InMemStorage, Serializer, Storage};
|
pub use storage::{serializer, InMemStorage, Serializer, Storage};
|
||||||
|
|
||||||
/// Generates `.up(field)` methods for dialogue states.
|
/// Generates `.up(field)` methods for dialogue states.
|
||||||
|
@ -110,8 +113,8 @@ macro_rules! up {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type passed into a FSM transition function.
|
/// An input passed into a FSM transition function.
|
||||||
pub type TransitionIn<State, E> = DialogueWithCx<Message, State, E>;
|
pub type TransitionIn = UpdateWithCx<Message>;
|
||||||
|
|
||||||
// A type returned from a FSM transition function.
|
// A type returned from a FSM transition function.
|
||||||
pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>;
|
pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
dispatching::{
|
dispatching::{
|
||||||
dialogue::{
|
dialogue::{
|
||||||
exit, next, DialogueDispatcher, DialogueStage, DialogueWithCx,
|
exit, next, BotDialogue, DialogueDispatcher, DialogueStage,
|
||||||
GetChatId, TransitionIn, TransitionOut,
|
DialogueWithCx, GetChatId, TransitionIn, TransitionOut,
|
||||||
},
|
},
|
||||||
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
|
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue