purchase/
purchase.rs

1// This example demonstrates how to deal with messages and callback queries
2// within a single dialogue.
3//
4// # Example
5// ```
6// - /start
7// - Let's start! What's your full name?
8// - John Doe
9// - Select a product:
10//   [Apple, Banana, Orange, Potato]
11// - <A user selects "Banana">
12// - John Doe, product 'Banana' has been purchased successfully!
13// ```
14
15use teloxide::{
16    dispatching::{dialogue, dialogue::InMemStorage, UpdateHandler},
17    prelude::*,
18    types::{InlineKeyboardButton, InlineKeyboardMarkup},
19    utils::command::BotCommands,
20};
21
22type MyDialogue = Dialogue<State, InMemStorage<State>>;
23type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
24
25#[derive(Clone, Default)]
26pub enum State {
27    #[default]
28    Start,
29    ReceiveFullName,
30    ReceiveProductChoice {
31        full_name: String,
32    },
33}
34
35/// These commands are supported:
36#[derive(BotCommands, Clone)]
37#[command(rename_rule = "lowercase")]
38enum Command {
39    /// Display this text.
40    Help,
41    /// Start the purchase procedure.
42    Start,
43    /// Cancel the purchase procedure.
44    Cancel,
45}
46
47#[tokio::main]
48async fn main() {
49    pretty_env_logger::init();
50    log::info!("Starting purchase bot...");
51
52    let bot = Bot::from_env();
53
54    Dispatcher::builder(bot, schema())
55        .dependencies(dptree::deps![InMemStorage::<State>::new()])
56        .enable_ctrlc_handler()
57        .build()
58        .dispatch()
59        .await;
60}
61
62fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
63    use dptree::case;
64
65    let command_handler = teloxide::filter_command::<Command, _>()
66        .branch(
67            case![State::Start]
68                .branch(case![Command::Help].endpoint(help))
69                .branch(case![Command::Start].endpoint(start)),
70        )
71        .branch(case![Command::Cancel].endpoint(cancel));
72
73    let message_handler = Update::filter_message()
74        .branch(command_handler)
75        .branch(case![State::ReceiveFullName].endpoint(receive_full_name))
76        .branch(dptree::endpoint(invalid_state));
77
78    let callback_query_handler = Update::filter_callback_query().branch(
79        case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection),
80    );
81
82    dialogue::enter::<Update, InMemStorage<State>, State, _>()
83        .branch(message_handler)
84        .branch(callback_query_handler)
85}
86
87async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
88    bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
89    dialogue.update(State::ReceiveFullName).await?;
90    Ok(())
91}
92
93async fn help(bot: Bot, msg: Message) -> HandlerResult {
94    bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?;
95    Ok(())
96}
97
98async fn cancel(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
99    bot.send_message(msg.chat.id, "Cancelling the dialogue.").await?;
100    dialogue.exit().await?;
101    Ok(())
102}
103
104async fn invalid_state(bot: Bot, msg: Message) -> HandlerResult {
105    bot.send_message(msg.chat.id, "Unable to handle the message. Type /help to see the usage.")
106        .await?;
107    Ok(())
108}
109
110async fn receive_full_name(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
111    match msg.text().map(ToOwned::to_owned) {
112        Some(full_name) => {
113            let products = ["Apple", "Banana", "Orange", "Potato"]
114                .map(|product| InlineKeyboardButton::callback(product, product));
115
116            bot.send_message(msg.chat.id, "Select a product:")
117                .reply_markup(InlineKeyboardMarkup::new([products]))
118                .await?;
119            dialogue.update(State::ReceiveProductChoice { full_name }).await?;
120        }
121        None => {
122            bot.send_message(msg.chat.id, "Please, send me your full name.").await?;
123        }
124    }
125
126    Ok(())
127}
128
129async fn receive_product_selection(
130    bot: Bot,
131    dialogue: MyDialogue,
132    full_name: String, // Available from `State::ReceiveProductChoice`.
133    q: CallbackQuery,
134) -> HandlerResult {
135    if let Some(product) = &q.data {
136        bot.send_message(
137            dialogue.chat_id(),
138            format!("{full_name}, product '{product}' has been purchased successfully!"),
139        )
140        .await?;
141        dialogue.exit().await?;
142    }
143
144    Ok(())
145}