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

强颜欢笑 提交于 2021-01-03 06:24:28

问题


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.


回答1:


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.




回答2:


I have found the answer:

  • the trick was to use next_step_handler, and message_handler_function to handle command starting with start and help

  • Then as suggested by @ALi in his answer, I will be saving the user input answer as well as the question id he replied to in a dictionary where keys are questions and id are the answer.

  • Once the user has answered all questions, I can run the algorithms on his answer

Here is how it looks like in the code :

user_dict = {}


# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
    # initialise the the bisector and 
    bisector = LandsatBisector(LON, LAT)
    indice = 0
    message = send_current_candidate(bot, message, bisector, indice)
    bot.register_next_step_handler(
        message, partial(
            process_step, indice, bisector))


def process_step(indice, bisector, message):
    # this run a while loop and will that send picture and will stop when the count is reached
    response = message.text
    user = User.create_get_user(message, bisector=bisector)
    if indice < bisector.count - 1:
        indice += 1
        try:
            # get or create
            user.responses[bisector.date] = response # save the response
            message = send_current_candidate(bot, message, bisector, indice)
            bot.register_next_step_handler(
                message, partial(
                    process_step, indice, bisector))
        except Exception as e:
            print(e)
            bot.reply_to(message, 'oooops')
    else:
        culprit = bisect(bisector.count,
                         lambda x: x,
                         partial(
                             tester_function,
                             responses=list(user.responses.values())))
        bisector.index = culprit
        bot.reply_to(message, f"Found! First apparition = {bisector.date}")


来源:https://stackoverflow.com/questions/60704532/python-telegram-bot-how-to-wait-for-user-answer-to-a-question-and-return-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!