Как предотвратить обработку inline кнопок после изменения сообщения в Telebot?

У меня есть 2 функции, обе из которых редактируют сообщение бота (telebot), 1 из них при последующем вызове другой должна не дать ей отредактировать сообщение

Пользователь может написав "Поиск" - получить сообщение вместе с inline клавиатурой, но при этом вызвав повторный раз "Поиск" предыдущее сообщение с клавиатурой должно измениться на сообщение "Клавиатура более недоступна" и лишиться клавиатуры. Используя inline клавиатуру пользователь тоже меняет сообщение и генерирует новую клавиатуру этому же сообщению (т.к. он должен ввести с помощью нее несколько параметров). Но может быть ситуация, когда пользователь после отправления слова "Поиск" до изменения сообщения нажмет на inline кнопку - в этом случае я хочу, чтобы сообщение тем не менее не выполняло эту часть кода в @bot.callback_query_handler():

global users_keyboard_data
users_keyboard_data = change_to_other_inline_keyboard(bot, call, users_keyboard_data)

То есть должна сработать только эта часть(т.к. она первой была вызвана) в @bot.message_handler():

if message.text == "Поиск":
    global users_keyboard_data
    users_keyboard_data = search(bot, message.chat.id, users_keyboard_data)

Я скопировал не весь код, а главные работоспособные фрагменты. Также хочу уточнить, что глобальная переменная users_keyboard_data отвечает за хранение полученных от пользователя с inline клавиатуры параметров, если есть способ для хранения легче - подскажите пожалуйста.

from telebot import TeleBot
from telebot.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from settings import config


users_keyboard_data = {}


bot = TeleBot(config.TOKEN)


def build_inline_keyboard(buttons: list[str]) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup().add(*[InlineKeyboardButton(text=button, callback_data=button) for button in buttons])


def get_keys_if_chat_id_in_dict(chat_id: int, dictionary: dict) -> list[str]:
    keys = []
    for key in dictionary:
        if key.split("_")[0] == str(chat_id):
            keys.append(key)
    return keys


def delete_previous_searches_besides(bot: TeleBot, id: int, users_keyboard_data: dict[dict[str: str]], besides: Message) -> dict[dict[str: str]]:
    to_delete_lst = get_keys_if_chat_id_in_dict(id, users_keyboard_data)
    to_delete_lst.remove(f"{id}_{besides.message_id}")
    for key in to_delete_lst:
        users_keyboard_data.pop(key, None)
        bot.edit_message_text(chat_id=id, message_id=int(key.split("_")[1]), text="Клавиатура более недоступна")
    return users_keyboard_data


def search(bot: TeleBot, id: int, users_keyboard_data: dict[dict[str: str]]) -> dict[dict[str: str]]:
    message = bot.send_message(chat_id=id, text="Выберите округ", reply_markup=build_inline_keyboard(["Кнопка 1", "Кнопка 2"]))
    users_keyboard_data[f"{id}_{message.message_id}"] = {}
    users_keyboard_data = delete_previous_searches_besides(bot, id, users_keyboard_data, message)
    return users_keyboard_data


def change_to_other_inline_keyboard(bot: TeleBot, call: CallbackQuery,  users_keyboard_data: dict[dict[str: str]]) -> dict[dict[str: str]]:
    users_keyboard_data[f"{call.from_user.id}_{call.message.message_id}"] = {"FIRST_DATA": f"{call.data}"}
    bot.edit_message_text(chat_id=call.from_user.id, message_id=call.message.id, text="Новая клавиатура", 
                        reply_markup=build_inline_keyboard(["Новая кнопка 1", "Новая кнопка 2"]))
    return users_keyboard_data


@bot.message_handler()
def handle_message(message: Message) -> None:
    if message.text == "Поиск":
        global users_keyboard_data
        users_keyboard_data = search(bot, message.chat.id, users_keyboard_data)


@bot.callback_query_handler(func=lambda call: True)
def handle_query(call: CallbackQuery) -> None:
    global users_keyboard_data
    users_keyboard_data = change_to_other_inline_keyboard(bot, call, users_keyboard_data)


if __name__ == "__main__":
    bot.polling(none_stop=True)

Я пробовал сделать вот так:

@bot.callback_query_handler(func=lambda call: True)
def handle_query(call: CallbackQuery) -> None:
    if call.message.text != "Клавиатура более недоступна":
        global users_keyboard_data
        users_keyboard_data = change_to_other_inline_keyboard(bot, call, users_keyboard_data)

Но даже не смотря на отсутствие асинхронности в библиотеке telebot - он все равно принимает условие за правду.


Ответы (1 шт):

Автор решения: Вадим-VOLT VIA

Итак, у тебя Telebot что это значит? (Ну в принципе ничего кроме того, что в твоём случае у тебя синхронный код).

Итак синхронность, казалось бы, и на что это влияет.

Как ты сам видишь, тут всё работает по очереди, но что если что-то очень долго генерируется? Допустим, кнопки сгенерировались позже, чем пользователь нажал на кнопку (имеется в виду реализация кнопок по типу недоступных). Тогда Telegram отправит Апдейт, что так-то так, а кнопка содержит текст, отличный от "Клавиатура более недоступна".

Дальше пройдёмся по фильтрам: вроде всё просто и легко, но всплывает айсберг, кажется проблема мала, но что же на самом деле?

@bot.callback_query_handler(func=lambda call: True) - Вроде простой понятный фильтр, и что тут париться, но он приводит к логике, которая не должна происходить, а именно к обработке всех колбеков: Используя func=lambda call: True, вы обрабатываете все колбек-запросы, независимо от их содержания.

Так что лучше сделать практику вот такую:

@bot.callback_query_handler(func=lambda call: call.message.text != "Клавиатура более недоступна")
def handle_query(call: CallbackQuery) -> None:
    # Ваша логика обработки колбеков

Она будет убирать возможность активировать кнопки, которые недоступны.

А можно сделать ещё практичнее - написать Хэндлер, который будет говорить: Эй, пользователь, кнопка уже недоступна зачем жмёшь?

Если идти совсем на уровень корректной логики, то можно назначать кнопкам, которые не нужны, специфичный data, и обрабатывать этот data по своему усмотрению.

Далее по поводу вашего сообщения о том, что я хочу прервать логику callback, а как?

Всё просто: после "Поиск" сделайте быстрый кэш, его суть будет в том, чтобы быстрее всех сохранить информацию о пользователе и о кнопке, а точнее лишь:

[f"{call.from_user.id}_{call.message.message_id}_can_change"] = False

Далее вы уже применяете её для того, чтобы посмотреть, а надо ли менять, и если нет, ну значит нет, а если значения нет или оно True, то надо.

Хотя для большей простоты и понятности рекомендую вам обзавестись базой данных, к примеру SQLite для Синхронного бота (это на самом деле не очень хорошая БД, но для старта пойдёт), а потом вы уже можете перейти к PostgreSQL, MySQL, MongoDB и другим (ну или для хранения кэша можно использовать Redis, но это, конечно, всё поводы для других вопросов и ответов).

Я надеюсь, что смог помочь вам решить вашу проблему или хотя бы натолкнуть на верный ход мыслей. Если это не так - уточняйте, что непонятно в комментариях.

→ Ссылка