1use 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#[derive(BotCommands, Clone)]
37#[command(rename_rule = "lowercase")]
38enum Command {
39 Help,
41 Start,
43 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, 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}