Search code examples
python-3.xexceptiontelegram-botaiogram

Handling text input exception instead of button click in callback_query aiogram bot


Friends, I am learning to create simple bots. And I'm myself looking for a solution to problems on the Internet. But now I can not do without your experience and I hope for help! :)

Expected Result:

  1. By the start command, we launch the bot.
  2. A message appears: "Hello from the bot!" and under it there is a button: "Select a city"
  3. When you press the button: "Select a city", the inscription appears: "Your city:" and a number of buttons with the names of cities.
  4. The user clicks on one of the buttons with the name of the city.

5.After that, the buttons with the cities disappear and the text sent to the chat appears with the name of the city button pressed.

6.TOTAL: In the code variable we get the name of the city.

Problem: After step 3, in addition to clicking on any of the buttons with the city, the user can enter something in the text field. Then the buttons are collapsed, and if something other than the name of the cities indicated on the buttons is entered, the process will stop, since the name of the city is the parameter needed later to send the request.

Solution: Need to catch any text input after the 3rd step, other than the name of the specified buttons, and if the text != the name of one of the buttons => you need:

  1. display a message that you need to select a city!
  2. display the buttons with the name of the cities again.
  3. catch and write to the variable the correct name, obtained either by clicking on the button, or by entering the exact match of the city name in the text field by the user.

Regarding the expected result, everything worked out for me, but I can’t handle the problem - incorrect input or accidental clicking by the user in the text field... I tried wrapping it in a While Loop, but I'm having trouble getting the correct response. Please tell me how can solve this problem?

from aiogram import Bot, Dispatcher, executor, types
from aiogram.dispatcher.filters.state import StatesGroup, State
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
import asyncio
import os as OSis
 
bot = Bot(token=OSis.getenv("TOKEN"), parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot, storage=MemoryStorage())
 
 
# State class:
class UserAnswer(StatesGroup):
    City = State()
 
 
@dp.message_handler(commands="start")
async def start(message: types.Message):
    # add button:
    markup = InlineKeyboardMarkup()
    start_button = InlineKeyboardButton(text='Select a city', callback_data="town_id")
    markup.add(start_button)
    await bot.send_message(message.chat.id, "Hello from the bot!", reply_markup=markup)
 
 
# Handler for displaying the result of a button click: start_button
@dp.callback_query_handler(lambda c: c.data == "town_id")
async def button_press(call: types.callback_query):
    """Displaying a list of cities in the form of buttons"""
    list_cities = ['Washington', 'Montgomery', 'Phoenix', 'Atlanta', 'Boise', 'Frankfort', 'Boston', 'Springfield', 'Helena', 'Nashville']
    while True:
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
        for name in list_cities:
            keyboard.row(types.KeyboardButton(name))
        await bot.answer_callback_query(call.id)
        await bot.send_message(call.message.chat.id, "Your city:", reply_markup=keyboard)
        # Set the status for the 1st response:
        await UserAnswer.City.set()
 
        # I tried to write the code below to handle the exception...
        enter_city = await get_answer_correct(call.message)
        if any(enter_city in item for item in list_cities):
            break
        else:
            await bot.send_message(call.message.chat.id, "CHOOSE YOUR CITY:", reply_markup=keyboard)
            await asyncio.sleep(5)  # I tried to slow down the loop but it didn't work
 
@dp.message_handler(state=UserAnswer.City)
async def get_answer_correct(message: types.Message):
    # Recording the user's response after clicking the city button or entering text in the field
    answer_correct_city = message.text
    print('answer_correct_city =', answer_correct_city)
    return answer_correct_city

def main():
    executor.start_polling(dp)

if __name__ == '__main__':
    main()

Solution

  • Try using InlineKeyboardButtons instead of simple Keyboard buttons like you did in "select a city". It will look like this:

    keyboard = InlineKeyboardMarkup(one_time_keyboard=True)
    
    for city in cities:
        new_city = InlineKeyboardButton(f"{city}", callback_data=f"{city}")
        keyboard.row(new_city)
    
    @dp.message_handler(commands=["start"])
    async def bot_message(message: types.Message):
        await bot.send_message(message.from_user.id, "Text", reply_markup=keyboard)
    
    @dp.callback_query_handler(text="insert the name of city 1 here")
    async def send_the_city(call: types.CallbackQuery):
        await bot.send_message(call.from_user.id, "Your city: Insert the name of city 1 here")
        
    @dp.callback_query_handler(text="insert the name of city N here")
    async def send_the_city(call: types.CallbackQuery):
        await bot.send_message(call.from_user.id, "Your city: Insert the name of city N here")
    

    Inline buttons do not depend on input text and thus the keyboard will not collapse.