1#!/usr/bin/env python
2# pylint: disable=unused-argument
3# This program is dedicated to the public domain under the CC0 license.
4
5"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
6
7This Bot uses the Application class to handle the bot.
8First, a few callback functions are defined as callback query handler. Then, those functions are
9passed to the Application and registered at their respective places.
10Then, the bot is started and runs until we press Ctrl-C on the command line.
11Usage:
12Example of a bot that uses inline keyboard that has multiple CallbackQueryHandlers arranged in a
13ConversationHandler.
14Send /start to initiate the conversation.
15Press Ctrl-C on the command line to stop the bot.
16"""
17import logging
18
19from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
20from telegram.ext import (
21 Application,
22 CallbackQueryHandler,
23 CommandHandler,
24 ContextTypes,
25 ConversationHandler,
26)
27
28# Enable logging
29logging.basicConfig(
30 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
31)
32# set higher logging level for httpx to avoid all GET and POST requests being logged
33logging.getLogger("httpx").setLevel(logging.WARNING)
34
35logger = logging.getLogger(__name__)
36
37# Stages
38START_ROUTES, END_ROUTES = range(2)
39# Callback data
40ONE, TWO, THREE, FOUR = range(4)
41
42
43async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
44 """Send message on `/start`."""
45 # Get user that sent /start and log his name
46 user = update.message.from_user
47 logger.info("User %s started the conversation.", user.first_name)
48 # Build InlineKeyboard where each button has a displayed text
49 # and a string as callback_data
50 # The keyboard is a list of button rows, where each row is in turn
51 # a list (hence `[[...]]`).
52 keyboard = [
53 [
54 InlineKeyboardButton("1", callback_data=str(ONE)),
55 InlineKeyboardButton("2", callback_data=str(TWO)),
56 ]
57 ]
58 reply_markup = InlineKeyboardMarkup(keyboard)
59 # Send message with text and appended InlineKeyboard
60 await update.message.reply_text("Start handler, Choose a route", reply_markup=reply_markup)
61 # Tell ConversationHandler that we're in state `FIRST` now
62 return START_ROUTES
63
64
65async def start_over(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
66 """Prompt same text & keyboard as `start` does but not as new message"""
67 # Get CallbackQuery from Update
68 query = update.callback_query
69 # CallbackQueries need to be answered, even if no notification to the user is needed
70 # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
71 await query.answer()
72 keyboard = [
73 [
74 InlineKeyboardButton("1", callback_data=str(ONE)),
75 InlineKeyboardButton("2", callback_data=str(TWO)),
76 ]
77 ]
78 reply_markup = InlineKeyboardMarkup(keyboard)
79 # Instead of sending a new message, edit the message that
80 # originated the CallbackQuery. This gives the feeling of an
81 # interactive menu.
82 await query.edit_message_text(text="Start handler, Choose a route", reply_markup=reply_markup)
83 return START_ROUTES
84
85
86async def one(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
87 """Show new choice of buttons"""
88 query = update.callback_query
89 await query.answer()
90 keyboard = [
91 [
92 InlineKeyboardButton("3", callback_data=str(THREE)),
93 InlineKeyboardButton("4", callback_data=str(FOUR)),
94 ]
95 ]
96 reply_markup = InlineKeyboardMarkup(keyboard)
97 await query.edit_message_text(
98 text="First CallbackQueryHandler, Choose a route", reply_markup=reply_markup
99 )
100 return START_ROUTES
101
102
103async def two(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
104 """Show new choice of buttons"""
105 query = update.callback_query
106 await query.answer()
107 keyboard = [
108 [
109 InlineKeyboardButton("1", callback_data=str(ONE)),
110 InlineKeyboardButton("3", callback_data=str(THREE)),
111 ]
112 ]
113 reply_markup = InlineKeyboardMarkup(keyboard)
114 await query.edit_message_text(
115 text="Second CallbackQueryHandler, Choose a route", reply_markup=reply_markup
116 )
117 return START_ROUTES
118
119
120async def three(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
121 """Show new choice of buttons. This is the end point of the conversation."""
122 query = update.callback_query
123 await query.answer()
124 keyboard = [
125 [
126 InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
127 InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO)),
128 ]
129 ]
130 reply_markup = InlineKeyboardMarkup(keyboard)
131 await query.edit_message_text(
132 text="Third CallbackQueryHandler. Do want to start over?", reply_markup=reply_markup
133 )
134 # Transfer to conversation state `SECOND`
135 return END_ROUTES
136
137
138async def four(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
139 """Show new choice of buttons"""
140 query = update.callback_query
141 await query.answer()
142 keyboard = [
143 [
144 InlineKeyboardButton("2", callback_data=str(TWO)),
145 InlineKeyboardButton("3", callback_data=str(THREE)),
146 ]
147 ]
148 reply_markup = InlineKeyboardMarkup(keyboard)
149 await query.edit_message_text(
150 text="Fourth CallbackQueryHandler, Choose a route", reply_markup=reply_markup
151 )
152 return START_ROUTES
153
154
155async def end(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
156 """Returns `ConversationHandler.END`, which tells the
157 ConversationHandler that the conversation is over.
158 """
159 query = update.callback_query
160 await query.answer()
161 await query.edit_message_text(text="See you next time!")
162 return ConversationHandler.END
163
164
165def main() -> None:
166 """Run the bot."""
167 # Create the Application and pass it your bot's token.
168 application = Application.builder().token("TOKEN").build()
169
170 # Setup conversation handler with the states FIRST and SECOND
171 # Use the pattern parameter to pass CallbackQueries with specific
172 # data pattern to the corresponding handlers.
173 # ^ means "start of line/string"
174 # $ means "end of line/string"
175 # So ^ABC$ will only allow 'ABC'
176 conv_handler = ConversationHandler(
177 entry_points=[CommandHandler("start", start)],
178 states={
179 START_ROUTES: [
180 CallbackQueryHandler(one, pattern="^" + str(ONE) + "$"),
181 CallbackQueryHandler(two, pattern="^" + str(TWO) + "$"),
182 CallbackQueryHandler(three, pattern="^" + str(THREE) + "$"),
183 CallbackQueryHandler(four, pattern="^" + str(FOUR) + "$"),
184 ],
185 END_ROUTES: [
186 CallbackQueryHandler(start_over, pattern="^" + str(ONE) + "$"),
187 CallbackQueryHandler(end, pattern="^" + str(TWO) + "$"),
188 ],
189 },
190 fallbacks=[CommandHandler("start", start)],
191 )
192
193 # Add ConversationHandler to application that will be used for handling updates
194 application.add_handler(conv_handler)
195
196 # Run the bot until the user presses Ctrl-C
197 application.run_polling(allowed_updates=Update.ALL_TYPES)
198
199
200if __name__ == "__main__":
201 main()