conversationbot.py
¶
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"""
6First, a few callback functions are defined. Then, those functions are passed to
7the Application and registered at their respective places.
8Then, the bot is started and runs until we press Ctrl-C on the command line.
9
10Usage:
11Example of a bot-user conversation using ConversationHandler.
12Send /start to initiate the conversation.
13Press Ctrl-C on the command line or send a signal to the process to stop the
14bot.
15"""
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", 5):
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 ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
33from telegram.ext import (
34 Application,
35 CommandHandler,
36 ContextTypes,
37 ConversationHandler,
38 MessageHandler,
39 filters,
40)
41
42# Enable logging
43logging.basicConfig(
44 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
45)
46# set higher logging level for httpx to avoid all GET and POST requests being logged
47logging.getLogger("httpx").setLevel(logging.WARNING)
48
49logger = logging.getLogger(__name__)
50
51GENDER, PHOTO, LOCATION, BIO = range(4)
52
53
54async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
55 """Starts the conversation and asks the user about their gender."""
56 reply_keyboard = [["Boy", "Girl", "Other"]]
57
58 await update.message.reply_text(
59 "Hi! My name is Professor Bot. I will hold a conversation with you. "
60 "Send /cancel to stop talking to me.\n\n"
61 "Are you a boy or a girl?",
62 reply_markup=ReplyKeyboardMarkup(
63 reply_keyboard, one_time_keyboard=True, input_field_placeholder="Boy or Girl?"
64 ),
65 )
66
67 return GENDER
68
69
70async def gender(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
71 """Stores the selected gender and asks for a photo."""
72 user = update.message.from_user
73 logger.info("Gender of %s: %s", user.first_name, update.message.text)
74 await update.message.reply_text(
75 "I see! Please send me a photo of yourself, "
76 "so I know what you look like, or send /skip if you don't want to.",
77 reply_markup=ReplyKeyboardRemove(),
78 )
79
80 return PHOTO
81
82
83async def photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
84 """Stores the photo and asks for a location."""
85 user = update.message.from_user
86 photo_file = await update.message.photo[-1].get_file()
87 await photo_file.download_to_drive("user_photo.jpg")
88 logger.info("Photo of %s: %s", user.first_name, "user_photo.jpg")
89 await update.message.reply_text(
90 "Gorgeous! Now, send me your location please, or send /skip if you don't want to."
91 )
92
93 return LOCATION
94
95
96async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
97 """Skips the photo and asks for a location."""
98 user = update.message.from_user
99 logger.info("User %s did not send a photo.", user.first_name)
100 await update.message.reply_text(
101 "I bet you look great! Now, send me your location please, or send /skip."
102 )
103
104 return LOCATION
105
106
107async def location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
108 """Stores the location and asks for some info about the user."""
109 user = update.message.from_user
110 user_location = update.message.location
111 logger.info(
112 "Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
113 )
114 await update.message.reply_text(
115 "Maybe I can visit you sometime! At last, tell me something about yourself."
116 )
117
118 return BIO
119
120
121async def skip_location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
122 """Skips the location and asks for info about the user."""
123 user = update.message.from_user
124 logger.info("User %s did not send a location.", user.first_name)
125 await update.message.reply_text(
126 "You seem a bit paranoid! At last, tell me something about yourself."
127 )
128
129 return BIO
130
131
132async def bio(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
133 """Stores the info about the user and ends the conversation."""
134 user = update.message.from_user
135 logger.info("Bio of %s: %s", user.first_name, update.message.text)
136 await update.message.reply_text("Thank you! I hope we can talk again some day.")
137
138 return ConversationHandler.END
139
140
141async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
142 """Cancels and ends the conversation."""
143 user = update.message.from_user
144 logger.info("User %s canceled the conversation.", user.first_name)
145 await update.message.reply_text(
146 "Bye! I hope we can talk again some day.", reply_markup=ReplyKeyboardRemove()
147 )
148
149 return ConversationHandler.END
150
151
152def main() -> None:
153 """Run the bot."""
154 # Create the Application and pass it your bot's token.
155 application = Application.builder().token("TOKEN").build()
156
157 # Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
158 conv_handler = ConversationHandler(
159 entry_points=[CommandHandler("start", start)],
160 states={
161 GENDER: [MessageHandler(filters.Regex("^(Boy|Girl|Other)$"), gender)],
162 PHOTO: [MessageHandler(filters.PHOTO, photo), CommandHandler("skip", skip_photo)],
163 LOCATION: [
164 MessageHandler(filters.LOCATION, location),
165 CommandHandler("skip", skip_location),
166 ],
167 BIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, bio)],
168 },
169 fallbacks=[CommandHandler("cancel", cancel)],
170 )
171
172 application.add_handler(conv_handler)
173
174 # Run the bot until the user presses Ctrl-C
175 application.run_polling(allowed_updates=Update.ALL_TYPES)
176
177
178if __name__ == "__main__":
179 main()
State Diagram¶
flowchart TB
%% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart
A(("/start")):::entryPoint -->|Hi! My name is Professor Bot...| B((GENDER)):::state
B --> |"- Boy <br /> - Girl <br /> - Other"|C("(choice)"):::userInput
C --> |I see! Please send me a photo...| D((PHOTO)):::state
D --> E("/skip"):::userInput
D --> F("(photo)"):::userInput
E --> |I bet you look great!| G[\ /]:::userInput
F --> |Gorgeous!| G[\ /]
G --> |"Now, send me your location .."| H((LOCATION)):::state
H --> I("/skip"):::userInput
H --> J("(location)"):::userInput
I --> |You seem a bit paranoid!| K[\" "/]:::userInput
J --> |Maybe I can visit...| K
K --> |"Tell me about yourself..."| L(("BIO")):::state
L --> M("(text)"):::userInput
M --> |"Thanks and bye!"| End(("END")):::termination
classDef userInput fill:#2a5279, color:#ffffff, stroke:#ffffff
classDef state fill:#222222, color:#ffffff, stroke:#ffffff
classDef entryPoint fill:#009c11, stroke:#42FF57, color:#ffffff
classDef termination fill:#bb0007, stroke:#E60109, color:#ffffff