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