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