Hide navigation sidebar
Hide table of contents sidebar
PTB has undergone significant changes in v20. Please read the documentation carefully and also check out the transition guide in the wiki.
Back to top
Toggle table of contents sidebar

# `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