conversationbot.py
¶
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"""
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 ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
20from telegram.ext import (
21 Application,
22 CommandHandler,
23 ContextTypes,
24 ConversationHandler,
25 MessageHandler,
26 filters,
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
38GENDER, PHOTO, LOCATION, BIO = range(4)
39
40
41async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
42 """Starts the conversation and asks the user about their gender."""
43 reply_keyboard = [["Boy", "Girl", "Other"]]
44
45 await update.message.reply_text(
46 "Hi! My name is Professor Bot. I will hold a conversation with you. "
47 "Send /cancel to stop talking to me.\n\n"
48 "Are you a boy or a girl?",
49 reply_markup=ReplyKeyboardMarkup(
50 reply_keyboard, one_time_keyboard=True, input_field_placeholder="Boy or Girl?"
51 ),
52 )
53
54 return GENDER
55
56
57async def gender(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
58 """Stores the selected gender and asks for a photo."""
59 user = update.message.from_user
60 logger.info("Gender of %s: %s", user.first_name, update.message.text)
61 await update.message.reply_text(
62 "I see! Please send me a photo of yourself, "
63 "so I know what you look like, or send /skip if you don't want to.",
64 reply_markup=ReplyKeyboardRemove(),
65 )
66
67 return PHOTO
68
69
70async def photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
71 """Stores the photo and asks for a location."""
72 user = update.message.from_user
73 photo_file = await update.message.photo[-1].get_file()
74 await photo_file.download_to_drive("user_photo.jpg")
75 logger.info("Photo of %s: %s", user.first_name, "user_photo.jpg")
76 await update.message.reply_text(
77 "Gorgeous! Now, send me your location please, or send /skip if you don't want to."
78 )
79
80 return LOCATION
81
82
83async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
84 """Skips the photo and asks for a location."""
85 user = update.message.from_user
86 logger.info("User %s did not send a photo.", user.first_name)
87 await update.message.reply_text(
88 "I bet you look great! Now, send me your location please, or send /skip."
89 )
90
91 return LOCATION
92
93
94async def location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
95 """Stores the location and asks for some info about the user."""
96 user = update.message.from_user
97 user_location = update.message.location
98 logger.info(
99 "Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
100 )
101 await update.message.reply_text(
102 "Maybe I can visit you sometime! At last, tell me something about yourself."
103 )
104
105 return BIO
106
107
108async def skip_location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
109 """Skips the location and asks for info about the user."""
110 user = update.message.from_user
111 logger.info("User %s did not send a location.", user.first_name)
112 await update.message.reply_text(
113 "You seem a bit paranoid! At last, tell me something about yourself."
114 )
115
116 return BIO
117
118
119async def bio(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
120 """Stores the info about the user and ends the conversation."""
121 user = update.message.from_user
122 logger.info("Bio of %s: %s", user.first_name, update.message.text)
123 await update.message.reply_text("Thank you! I hope we can talk again some day.")
124
125 return ConversationHandler.END
126
127
128async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
129 """Cancels and ends the conversation."""
130 user = update.message.from_user
131 logger.info("User %s canceled the conversation.", user.first_name)
132 await update.message.reply_text(
133 "Bye! I hope we can talk again some day.", reply_markup=ReplyKeyboardRemove()
134 )
135
136 return ConversationHandler.END
137
138
139def main() -> None:
140 """Run the bot."""
141 # Create the Application and pass it your bot's token.
142 application = Application.builder().token("TOKEN").build()
143
144 # Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
145 conv_handler = ConversationHandler(
146 entry_points=[CommandHandler("start", start)],
147 states={
148 GENDER: [MessageHandler(filters.Regex("^(Boy|Girl|Other)$"), gender)],
149 PHOTO: [MessageHandler(filters.PHOTO, photo), CommandHandler("skip", skip_photo)],
150 LOCATION: [
151 MessageHandler(filters.LOCATION, location),
152 CommandHandler("skip", skip_location),
153 ],
154 BIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, bio)],
155 },
156 fallbacks=[CommandHandler("cancel", cancel)],
157 )
158
159 application.add_handler(conv_handler)
160
161 # Run the bot until the user presses Ctrl-C
162 application.run_polling(allowed_updates=Update.ALL_TYPES)
163
164
165if __name__ == "__main__":
166 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