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