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