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

# `conversationbot2.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
     18from typing import Dict
     19
     20from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
     21from telegram.ext import (
     22    Application,
     23    CommandHandler,
     24    ContextTypes,
     25    ConversationHandler,
     26    MessageHandler,
     27    filters,
     28)
     29
     30# Enable logging
     31logging.basicConfig(
     32    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
     33)
     34# set higher logging level for httpx to avoid all GET and POST requests being logged
     35logging.getLogger("httpx").setLevel(logging.WARNING)
     36
     37logger = logging.getLogger(__name__)
     38
     39CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
     40
     41reply_keyboard = [
     42    ["Age", "Favourite colour"],
     43    ["Number of siblings", "Something else..."],
     44    ["Done"],
     45]
     46markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
     47
     48
     49def facts_to_str(user_data: Dict[str, str]) -> str:
     50    """Helper function for formatting the gathered user info."""
     51    facts = [f"{key} - {value}" for key, value in user_data.items()]
     52    return "\n".join(facts).join(["\n", "\n"])
     53
     54
     55async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
     56    """Start the conversation and ask user for input."""
     57    await update.message.reply_text(
     58        "Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
     59        "Why don't you tell me something about yourself?",
     60        reply_markup=markup,
     61    )
     62
     63    return CHOOSING
     64
     65
     66async def regular_choice(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
     67    """Ask the user for info about the selected predefined choice."""
     68    text = update.message.text
     69    context.user_data["choice"] = text
     70    await update.message.reply_text(f"Your {text.lower()}? Yes, I would love to hear about that!")
     71
     72    return TYPING_REPLY
     73
     74
     75async def custom_choice(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
     76    """Ask the user for a description of a custom category."""
     77    await update.message.reply_text(
     78        'Alright, please send me the category first, for example "Most impressive skill"'
     79    )
     80
     81    return TYPING_CHOICE
     82
     83
     84async def received_information(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
     85    """Store info provided by user and ask for the next category."""
     86    user_data = context.user_data
     87    text = update.message.text
     88    category = user_data["choice"]
     89    user_data[category] = text
     90    del user_data["choice"]
     91
     92    await update.message.reply_text(
     93        "Neat! Just so you know, this is what you already told me:"
     94        f"{facts_to_str(user_data)}You can tell me more, or change your opinion"
     95        " on something.",
     96        reply_markup=markup,
     97    )
     98
     99    return CHOOSING
    100
    101
    102async def done(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    103    """Display the gathered info and end the conversation."""
    104    user_data = context.user_data
    105    if "choice" in user_data:
    106        del user_data["choice"]
    107
    108    await update.message.reply_text(
    109        f"I learned these facts about you: {facts_to_str(user_data)}Until next time!",
    110        reply_markup=ReplyKeyboardRemove(),
    111    )
    112
    113    user_data.clear()
    114    return ConversationHandler.END
    115
    116
    117def main() -> None:
    118    """Run the bot."""
    119    # Create the Application and pass it your bot's token.
    120    application = Application.builder().token("TOKEN").build()
    121
    122    # Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
    123    conv_handler = ConversationHandler(
    124        entry_points=[CommandHandler("start", start)],
    125        states={
    126            CHOOSING: [
    127                MessageHandler(
    128                    filters.Regex("^(Age|Favourite colour|Number of siblings)$"), regular_choice
    129                ),
    130                MessageHandler(filters.Regex("^Something else...$"), custom_choice),
    131            ],
    132            TYPING_CHOICE: [
    133                MessageHandler(
    134                    filters.TEXT & ~(filters.COMMAND | filters.Regex("^Done$")), regular_choice
    135                )
    136            ],
    137            TYPING_REPLY: [
    138                MessageHandler(
    139                    filters.TEXT & ~(filters.COMMAND | filters.Regex("^Done$")),
    140                    received_information,
    141                )
    142            ],
    143        },
    144        fallbacks=[MessageHandler(filters.Regex("^Done$"), done)],
    145    )
    146
    147    application.add_handler(conv_handler)
    148
    149    # Run the bot until the user presses Ctrl-C
    150    application.run_polling(allowed_updates=Update.ALL_TYPES)
    151
    152
    153if __name__ == "__main__":
    154    main()

## State Diagram

flowchart TB %% Documentation: https://mermaid-js.github.io/mermaid/#/flowchart A(("/start")):::entryPoint -->|Hi! My name is Doctor Botter...| B((CHOOSING)):::state B --> C("Something else..."):::userInput C --> |What category?| D((TYPING_CHOICE)):::state D --> E("(text)"):::userInput E --> |"[save choice] <br /> I'd love to hear about that!"| F((TYPING_REPLY)):::state F --> G("(text)"):::userInput G --> |"[save choice: text] <br /> Neat! <br /> (List of facts) <br /> More?"| B B --> H("- Age <br /> - Favourite colour <br /> - Number of siblings"):::userInput H --> |"[save choice] <br /> I'd love to hear about that!"| F B --> I("Done"):::userInput I --> |"I learned these facts about you: <br /> ..."| 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