Search code examples
pythontelegramtelegram-botpython-telegram-bot

Python Telegram Bot how to wait for user answer to a question And Return It


Context:

I am using PyTelegramBotAPi or Python Telegram Bot

I have a code I am running when a user starts the conversation.

When the user starts the conversation I need to send him the first picture and a question if He saw something in the picture, the function needs to wait for the user input and return whether he saw it or not.

After that, I will need to keep sending the picture in a loop and wait for the answer and run a bisection algorithm on it.

What I have tried so far:

I tried to use reply markup that waits for a response or an inline keyboard with handlers but I am stuck because my code is running without waiting for the user input.

The code:

@bot.message_handler(func=lambda msg: msg in ['Yes', 'No'])
@bot.message_handler(commands=['start', 'help'])
def main(message):
    """
    This is my main function
    """
    chat_id = message.chat.id
    try:
        reply_answer = message.reply_to_message.text
    except AttributeError:
        reply_answer = '0'
    # TODO : should wait for the answer asynchnonossly
    def tester(n, reply_answer):
        """
        Displays the current candidate to the user and asks them to
        check if they see wildfire damages.
        """
        print('call......')
        bisector.index = n
        bot.send_photo(
            chat_id=chat_id,
            photo=bisector.image.save_image(),
            caption=f"Did you see it Yes or No {bisector.date}",
            reply_markup=types.ForceReply(selective=True))
        # I SHOUL WAIT FOR THE INPUT HERE AND RETURN THE USER INPUT
        return eval(reply_answer)
    culprit = bisect(bisector.count, lambda x: x, partial(tester, reply_answer=reply_answer) )
    bisector.index = culprit
    bot.send_message(chat_id, f"Found! First apparition = {bisector.date}")


bot.polling(none_stop=True)

The algorithm I am running on the user input is something like this :

def bisect(n, mapper, tester):
    """
    Runs a bisection.

    - `n` is the number of elements to be bisected
    - `mapper` is a callable that will transform an integer from "0" to "n"
      into a value that can be tested
    - `tester` returns true if the value is within the "right" range
    """

    if n < 1:
        raise ValueError('Cannot bissect an empty array')

    left = 0
    right = n - 1

    while left + 1 < right:
        mid = int((left + right) / 2)

        val = mapper(mid)
        tester_values = tester(val) # Here is where I am using the ouput from Telegram bot
        if tester_values:
            right = mid
        else:
            left = mid

    return mapper(right)

I hope I was clear explaining the problem, feel free to ask any clarification. If you know something that can point me in the right direction in order to solve this problem, let me know.

I have tried a similar question but I am not getting answers.


Solution

  • You should save your user info in a database. Basic fields would be:

    (id, first_name, last_name, username, menu)

    What is menu?

    Menu keeps user's current state. When a user sends a message to your bot, you check the database to find out about user's current sate.

    So if the user doesn't exist, you add them to your users table with menu set to MainMenu or WelcomeMenu or in your case PictureMenu.

    Now you're going to have a listener for update function, let's assume each of these a menu.

    @bot.message_handler(commands=['start', 'help'])

    so when the user sends start you're going to check user's menu field inside the function.

    @bot.message_handler(commands=['start', 'help'])
    def main(message):
        user = fetch_user_from_db(chat_id)
        if user.menu == "PictureMenu":
            if message.photo is Not None:
                photo = message.photo[0].file_id
                photo_file = download_photo_from_telegram(photo)
                do_other_things()
                user.menu = "Picture2Menu";
                user.save();
            else:
                send_message("Please send a photo")
        if user.menu == "Picture2Menu":
            if message.photo is Not None:
                photo = message.photo[0].file_id
                photo_file = download_photo_from_telegram(photo)
                do_other_things()
                user.menu = "Picture3Menu";
                user.save();
            else:
                send_message("Please send a photo")   
        ...
    

    I hope you got it.