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