From d48659b93e3540e4787790b2584076e8003f2f16 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 4 Feb 2022 09:40:15 +0600 Subject: [PATCH] Update examples in the README --- README.md | 277 ++++++++++++++++++++++++------------------------------ 1 file changed, 123 insertions(+), 154 deletions(-) diff --git a/README.md b/README.md index d30e0163..8f53e0e8 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,12 @@ tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } ## API overview ### The dices bot + This bot replies with a dice throw to each received message: -([Full](./examples/dices_bot/src/main.rs)) +([Full](./examples/dices.rs)) ```rust,no_run -use teloxide::prelude::*; +use teloxide::prelude2::*; #[tokio::main] async fn main() { @@ -94,8 +95,8 @@ async fn main() { let bot = Bot::from_env().auto_send(); - teloxide::repl(bot, |message| async move { - message.answer_dice().await?; + teloxide::repls2::repl(bot, |message: Message, bot: AutoSend| async move { + bot.send_dice(message.chat.id).await?; respond(()) }) .await; @@ -109,6 +110,7 @@ async fn main() { ### Commands + Commands are strongly typed and defined declaratively, similar to how we define CLI using [structopt] and JSON structures in [serde-json]. The following bot accepts these commands: - `/username ` @@ -118,13 +120,14 @@ Commands are strongly typed and defined declaratively, similar to how we define [structopt]: https://docs.rs/structopt/0.3.9/structopt/ [serde-json]: https://github.com/serde-rs/json -([Full](./examples/simple_commands_bot/src/main.rs)) +([Full](./examples/simple_commands.rs)) + ```rust,no_run -use teloxide::{prelude::*, utils::command::BotCommand}; +use teloxide::{prelude2::*, utils::command::BotCommand}; use std::error::Error; -#[derive(BotCommand)] +#[derive(BotCommand, Clone)] #[command(rename = "lowercase", description = "These commands are supported:")] enum Command { #[command(description = "display this text.")] @@ -136,16 +139,21 @@ enum Command { } async fn answer( - cx: UpdateWithCx, Message>, + bot: AutoSend, + message: Message, command: Command, ) -> Result<(), Box> { match command { - Command::Help => cx.answer(Command::descriptions()).await?, + Command::Help => bot.send_message(message.chat.id, Command::descriptions()).await?, Command::Username(username) => { - cx.answer(format!("Your username is @{}.", username)).await? + bot.send_message(message.chat.id, format!("Your username is @{}.", username)).await? } Command::UsernameAndAge { username, age } => { - cx.answer(format!("Your username is @{} and age is {}.", username, age)).await? + bot.send_message( + message.chat.id, + format!("Your username is @{} and age is {}.", username, age), + ) + .await? } }; @@ -159,8 +167,7 @@ async fn main() { let bot = Bot::from_env().auto_send(); - let bot_name: String = panic!("Your bot's name here"); - teloxide::commands_repl(bot, bot_name, answer).await; + teloxide::repls2::commands_repl(bot, answer, Command::ty()).await; } ``` @@ -171,145 +178,41 @@ async fn main() { ### Dialogues management -A dialogue is described by an enumeration where each variant is one of possible dialogue's states. There are also _subtransition functions_, which turn a dialogue from one state to another, thereby forming an [FSM]. + +A dialogue is typically described by an enumeration where each variant is one of possible dialogue's states. There are also _state handler functions_, which may turn a dialogue from one state to another, thereby forming an [FSM]. [FSM]: https://en.wikipedia.org/wiki/Finite-state_machine -Below is a bot that asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states): +Below is a bot that asks you three questions and then sends the answers back to you: + +([Full](examples/dialogue.rs)) -([dialogue_bot/src/dialogue/mod.rs](examples/dialogue_bot/src/state/mod.rs)) ```rust,ignore -// Imports are omitted... +use teloxide::{dispatching2::dialogue::InMemStorage, macros::DialogueState, prelude2::*}; -#[derive(Transition, From)] -pub enum Dialogue { - Start(StartState), - ReceiveFullName(ReceiveFullNameState), - ReceiveAge(ReceiveAgeState), - ReceiveLocation(ReceiveLocationState), +type MyDialogue = Dialogue>; + +#[derive(DialogueState, Clone)] +#[handler_out(anyhow::Result<()>)] +pub enum State { + #[handler(handle_start)] + Start, + + #[handler(handle_receive_full_name)] + ReceiveFullName, + + #[handler(handle_receive_age)] + ReceiveAge { full_name: String }, + + #[handler(handle_receive_location)] + ReceiveLocation { full_name: String, age: u8 }, } -impl Default for Dialogue { +impl Default for State { fn default() -> Self { - Self::Start(StartState) + Self::Start } } -``` - -When a user sends a message to our bot and such a dialogue does not exist yet, a `Dialogue::default()` is invoked, which is a `Dialogue::Start` in this case. Every time a message is received, an associated dialogue is extracted and then passed to a corresponding subtransition function: - -
- Dialogue::Start - -([dialogue_bot/src/dialogue/states/start.rs](examples/dialogue_bot/src/state/states/start.rs)) -```rust,ignore -// Imports are omitted... - -pub struct StartState; - -#[teloxide(subtransition)] -async fn start( - _state: StartState, - cx: TransitionIn>, - _ans: String, -) -> TransitionOut { - cx.answer("Let's start! What's your full name?").await?; - next(ReceiveFullNameState) -} -``` - -
- -
- Dialogue::ReceiveFullName - -([dialogue_bot/src/dialogue/states/receive_full_name.rs](examples/dialogue_bot/src/state/states/receive_full_name.rs)) -```rust,ignore -// Imports are omitted... - -#[derive(Generic)] -pub struct ReceiveFullNameState; - -#[teloxide(subtransition)] -async fn receive_full_name( - state: ReceiveFullNameState, - cx: TransitionIn>, - ans: String, -) -> TransitionOut { - cx.answer("How old are you?").await?; - next(ReceiveAgeState::up(state, ans)) -} -``` - -
- -
- Dialogue::ReceiveAge - -([dialogue_bot/src/dialogue/states/receive_age.rs](examples/dialogue_bot/src/state/states/receive_age.rs)) -```rust,ignore -// Imports are omitted... - -#[derive(Generic)] -pub struct ReceiveAgeState { - pub full_name: String, -} - -#[teloxide(subtransition)] -async fn receive_age_state( - state: ReceiveAgeState, - cx: TransitionIn>, - ans: String, -) -> TransitionOut { - match ans.parse::() { - Ok(ans) => { - cx.answer("What's your location?").await?; - next(ReceiveLocationState::up(state, ans)) - } - _ => { - cx.answer("Send me a number.").await?; - next(state) - } - } -} -``` - -
- -
- Dialogue::ReceiveLocation - -([dialogue_bot/src/dialogue/states/receive_location.rs](examples/dialogue_bot/src/state/states/receive_location.rs)) -```rust,ignore -// Imports are omitted... - -#[derive(Generic)] -pub struct ReceiveLocationState { - pub full_name: String, - pub age: u8, -} - -#[teloxide(subtransition)] -async fn receive_location( - state: ReceiveLocationState, - cx: TransitionIn>, - ans: String, -) -> TransitionOut { - cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) - .await?; - exit() -} -``` - -
- -All these subtransition functions accept a corresponding state (one of the many variants of `Dialogue`), a context, and a textual message. They return `TransitionOut`, e.g. a mapping from `` to `Dialogue`. - -Finally, the `main` function looks like this: - -([dialogue_bot/src/main.rs](./examples/dialogue_bot/src/main.rs)) -```rust,ignore -// Imports are omitted... #[tokio::main] async fn main() { @@ -318,23 +221,89 @@ async fn main() { let bot = Bot::from_env().auto_send(); - teloxide::dialogues_repl(bot, |message, dialogue| async move { - handle_message(message, dialogue).await.expect("Something wrong with the bot!") - }) + DispatcherBuilder::new( + bot, + Update::filter_message() + .add_dialogue::, State>() + .dispatch_by::(), + ) + .dependencies(dptree::deps![InMemStorage::::new()]) + .build() + .setup_ctrlc_handler() + .dispatch() .await; } -async fn handle_message( - cx: UpdateWithCx, Message>, - dialogue: Dialogue, -) -> TransitionOut { - match cx.update.text().map(ToOwned::to_owned) { - None => { - cx.answer("Send me a text message.").await?; - next(dialogue) +async fn handle_start( + bot: AutoSend, + msg: Message, + dialogue: MyDialogue, +) -> anyhow::Result<()> { + bot.send_message(msg.chat_id(), "Let's start! What's your full name?").await?; + dialogue.update(State::ReceiveFullName).await?; + Ok(()) +} + +async fn handle_receive_full_name( + bot: AutoSend, + msg: Message, + dialogue: MyDialogue, +) -> anyhow::Result<()> { + match msg.text() { + Some(text) => { + bot.send_message(msg.chat_id(), "How old are you?").await?; + dialogue.update(State::ReceiveAge { full_name: text.into() }).await?; + } + None => { + bot.send_message(msg.chat_id(), "Send me a text message.").await?; } - Some(ans) => dialogue.react(cx, ans).await, } + + Ok(()) +} + +async fn handle_receive_age( + bot: AutoSend, + msg: Message, + dialogue: MyDialogue, + (full_name,): (String,), +) -> anyhow::Result<()> { + match msg.text() { + Some(number) => match number.parse::() { + Ok(age) => { + bot.send_message(msg.chat_id(), "What's your location?").await?; + dialogue.update(State::ReceiveLocation { full_name, age }).await?; + } + _ => { + bot.send_message(msg.chat_id(), "Send me a number.").await?; + } + }, + None => { + bot.send_message(msg.chat_id(), "Send me a text message.").await?; + } + } + + Ok(()) +} + +async fn handle_receive_location( + bot: AutoSend, + msg: Message, + dialogue: MyDialogue, + (full_name, age): (String, u8), +) -> anyhow::Result<()> { + match msg.text() { + Some(location) => { + let message = format!("Full name: {}\nAge: {}\nLocation: {}", full_name, age, location); + bot.send_message(msg.chat_id(), message).await?; + dialogue.exit().await?; + } + None => { + bot.send_message(msg.chat_id(), "Send me a text message.").await?; + } + } + + Ok(()) } ``` @@ -344,7 +313,7 @@ async fn handle_message( -[More examples!](./examples) +[More examples >>](./examples) ## FAQ **Q: Where I can ask questions?**