Skip to content
Back to top
View this page

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