1#!/usr/bin/env python
2# pylint: disable=unused-argument, wrong-import-position
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 __version__ as TG_VER
20
21try:
22 from telegram import __version_info__
23except ImportError:
24 __version_info__ = (0, 0, 0, 0, 0) # type: ignore[assignment]
25
26if __version_info__ < (20, 0, 0, "alpha", 1):
27 raise RuntimeError(
28 f"This example is not compatible with your current PTB version {TG_VER}. To view the "
29 f"{TG_VER} version of this example, "
30 f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html"
31 )
32from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
33from telegram.ext import (
34 Application,
35 CallbackQueryHandler,
36 CommandHandler,
37 ContextTypes,
38 ConversationHandler,
39)
40
41# Enable logging
42logging.basicConfig(
43 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
44)
45logger = logging.getLogger(__name__)
46
47# Stages
48START_ROUTES, END_ROUTES = range(2)
49# Callback data
50ONE, TWO, THREE, FOUR = range(4)
51
52
53async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
54 """Send message on `/start`."""
55 # Get user that sent /start and log his name
56 user = update.message.from_user
57 logger.info("User %s started the conversation.", user.first_name)
58 # Build InlineKeyboard where each button has a displayed text
59 # and a string as callback_data
60 # The keyboard is a list of button rows, where each row is in turn
61 # a list (hence `[[...]]`).
62 keyboard = [
63 [
64 InlineKeyboardButton("1", callback_data=str(ONE)),
65 InlineKeyboardButton("2", callback_data=str(TWO)),
66 ]
67 ]
68 reply_markup = InlineKeyboardMarkup(keyboard)
69 # Send message with text and appended InlineKeyboard
70 await update.message.reply_text("Start handler, Choose a route", reply_markup=reply_markup)
71 # Tell ConversationHandler that we're in state `FIRST` now
72 return START_ROUTES
73
74
75async def start_over(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
76 """Prompt same text & keyboard as `start` does but not as new message"""
77 # Get CallbackQuery from Update
78 query = update.callback_query
79 # CallbackQueries need to be answered, even if no notification to the user is needed
80 # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
81 await query.answer()
82 keyboard = [
83 [
84 InlineKeyboardButton("1", callback_data=str(ONE)),
85 InlineKeyboardButton("2", callback_data=str(TWO)),
86 ]
87 ]
88 reply_markup = InlineKeyboardMarkup(keyboard)
89 # Instead of sending a new message, edit the message that
90 # originated the CallbackQuery. This gives the feeling of an
91 # interactive menu.
92 await query.edit_message_text(text="Start handler, Choose a route", reply_markup=reply_markup)
93 return START_ROUTES
94
95
96async def one(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
97 """Show new choice of buttons"""
98 query = update.callback_query
99 await query.answer()
100 keyboard = [
101 [
102 InlineKeyboardButton("3", callback_data=str(THREE)),
103 InlineKeyboardButton("4", callback_data=str(FOUR)),
104 ]
105 ]
106 reply_markup = InlineKeyboardMarkup(keyboard)
107 await query.edit_message_text(
108 text="First CallbackQueryHandler, Choose a route", reply_markup=reply_markup
109 )
110 return START_ROUTES
111
112
113async def two(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
114 """Show new choice of buttons"""
115 query = update.callback_query
116 await query.answer()
117 keyboard = [
118 [
119 InlineKeyboardButton("1", callback_data=str(ONE)),
120 InlineKeyboardButton("3", callback_data=str(THREE)),
121 ]
122 ]
123 reply_markup = InlineKeyboardMarkup(keyboard)
124 await query.edit_message_text(
125 text="Second CallbackQueryHandler, Choose a route", reply_markup=reply_markup
126 )
127 return START_ROUTES
128
129
130async def three(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
131 """Show new choice of buttons. This is the end point of the conversation."""
132 query = update.callback_query
133 await query.answer()
134 keyboard = [
135 [
136 InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
137 InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO)),
138 ]
139 ]
140 reply_markup = InlineKeyboardMarkup(keyboard)
141 await query.edit_message_text(
142 text="Third CallbackQueryHandler. Do want to start over?", reply_markup=reply_markup
143 )
144 # Transfer to conversation state `SECOND`
145 return END_ROUTES
146
147
148async def four(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
149 """Show new choice of buttons"""
150 query = update.callback_query
151 await query.answer()
152 keyboard = [
153 [
154 InlineKeyboardButton("2", callback_data=str(TWO)),
155 InlineKeyboardButton("3", callback_data=str(THREE)),
156 ]
157 ]
158 reply_markup = InlineKeyboardMarkup(keyboard)
159 await query.edit_message_text(
160 text="Fourth CallbackQueryHandler, Choose a route", reply_markup=reply_markup
161 )
162 return START_ROUTES
163
164
165async def end(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
166 """Returns `ConversationHandler.END`, which tells the
167 ConversationHandler that the conversation is over.
168 """
169 query = update.callback_query
170 await query.answer()
171 await query.edit_message_text(text="See you next time!")
172 return ConversationHandler.END
173
174
175def main() -> None:
176 """Run the bot."""
177 # Create the Application and pass it your bot's token.
178 application = Application.builder().token("TOKEN").build()
179
180 # Setup conversation handler with the states FIRST and SECOND
181 # Use the pattern parameter to pass CallbackQueries with specific
182 # data pattern to the corresponding handlers.
183 # ^ means "start of line/string"
184 # $ means "end of line/string"
185 # So ^ABC$ will only allow 'ABC'
186 conv_handler = ConversationHandler(
187 entry_points=[CommandHandler("start", start)],
188 states={
189 START_ROUTES: [
190 CallbackQueryHandler(one, pattern="^" + str(ONE) + "$"),
191 CallbackQueryHandler(two, pattern="^" + str(TWO) + "$"),
192 CallbackQueryHandler(three, pattern="^" + str(THREE) + "$"),
193 CallbackQueryHandler(four, pattern="^" + str(FOUR) + "$"),
194 ],
195 END_ROUTES: [
196 CallbackQueryHandler(start_over, pattern="^" + str(ONE) + "$"),
197 CallbackQueryHandler(end, pattern="^" + str(TWO) + "$"),
198 ],
199 },
200 fallbacks=[CommandHandler("start", start)],
201 )
202
203 # Add ConversationHandler to application that will be used for handling updates
204 application.add_handler(conv_handler)
205
206 # Run the bot until the user presses Ctrl-C
207 application.run_polling()
208
209
210if __name__ == "__main__":
211 main()