From 621b3725d4dbd64c27f7daeabcc6f11b15e7e9b4 Mon Sep 17 00:00:00 2001 From: vasilytray Date: Wed, 12 Mar 2025 18:37:26 +0700 Subject: [PATCH] callback in inline Keyboard --- README.md | 32 ++++++++++++++++++++++++ bot2.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/README.md b/README.md index 7583986..34a54e2 100644 --- a/README.md +++ b/README.md @@ -558,3 +558,35 @@ async def on_user_shared(message: types.Message): У колбэк-кнопок есть специальное значение **(data)**, по которому ваше приложение опознаёт, что нажато и что надо сделать. И выбор правильного data очень важен! Стоит также отметить, что, в отличие от обычных кнопок, нажатие на колбэк-кнопку позволяет сделать практически что угодно, от заказа пиццы до запуска вычислений на кластере суперкомпьютеров. +Сервер Telegram отправив нам кнопку callback ждет от нас подтверждения о доставке колбэка. Нам необходимо вызвать метод **answer()** в общем случае в него можем ничего не передавать, но можно вызвать специальное окошко (всплывающее сверху или поверх экрана): + +```py +@dp.callback_query(F.data == "random_value") +async def send_random_value(callback: types.CallbackQuery): + await callback.message.answer(str(randint(1, 10))) + await callback.answer( + text="Спасибо, что воспользовались ботом!", + show_alert=True + ) + # или просто await callback.answer() +``` +> В функции send_random_value мы вызывали метод answer() не у message, а у callback.message. Это связано с тем, что колбэк-хэндлеры работают не с сообщениями (тип Message), а с колбэками (тип CallbackQuery), у которого другие поля, и само сообщение — всего лишь его часть. Учтите также, что message — это сообщение, к которому была прицеплена кнопка (т.е. отправитель такого сообщения — сам бот). Если хотите узнать, кто нажал на кнопку, смотрите поле from (в вашем коде это будет callback.from_user, т.к. слово from зарезервировано в Python) + +Иногда пользователь, вызвав несколько раз сообщение с колбэками, может нажимать кнопки новых и старых сообщений и тогда может возникнуть ошибка, которую мы получим от Bot API, что старый и новый тексты совпадают, а бот словит исключение: ``Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message. + +Ошибка MessageNotModified относится к категории Bad Request, поэтому у нас есть выбор: проигнорировать весь подобный класс ошибок, либо отловить весь класс BadRequest и попытаться по тексту ошибки опознать конкретную причину. + +Чтобы игнорировать весь подобный класс обновим функцию ``update_num_text()`` + +```py +# Новые импорты! +from contextlib import suppress +from aiogram.exceptions import TelegramBadRequest + +async def update_num_text(message: types.Message, new_value: int): + with suppress(TelegramBadRequest): + await message.edit_text( + f"Укажите число: {new_value}", + reply_markup=get_keyboard() + ) +``` diff --git a/bot2.py b/bot2.py index f63d27b..11029b3 100644 --- a/bot2.py +++ b/bot2.py @@ -1,3 +1,4 @@ +from random import randint import re import asyncio import logging @@ -10,6 +11,9 @@ from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode # новый импорт! from aiogram.utils.keyboard import ReplyKeyboardBuilder +# Новые импорты! +from contextlib import suppress +from aiogram.exceptions import TelegramBadRequest from config_reader import config @@ -128,6 +132,75 @@ async def handle_contact(message: types.Message): # новый импорт from aiogram.utils.keyboard import InlineKeyboardBuilder +# Запускаем Callback - кнопка +@dp.message(Command("random")) +async def cmd_random(message: types.Message): + builder = InlineKeyboardBuilder() + builder.add(types.InlineKeyboardButton( + text="Нажми меня", + callback_data="random_value") + ) + await message.answer( + "Нажмите на кнопку, чтобы бот отправил число от 1 до 10", + reply_markup=builder.as_markup() + ) +# хэндлер обработки callback кнопки +@dp.callback_query(F.data == "random_value") +async def send_random_value(callback: types.CallbackQuery): + await callback.message.answer(str(randint(1, 10))) + # отправим всплывающее окно после результата + await callback.answer( + text="Спасибо, что воспользовались ботом!", + show_alert=True + ) + # или просто await callback.answer() + +# Продолжим с колбэками +# Здесь хранятся пользовательские данные. +# Т.к. это словарь в памяти, то при перезапуске он очистится +user_data = {} +#сформируем инлайн-клавиатуру +def get_keyboard(): + buttons = [ + [ + types.InlineKeyboardButton(text="-1", callback_data="num_decr"), + types.InlineKeyboardButton(text="+1", callback_data="num_incr") + ], + [types.InlineKeyboardButton(text="Подтвердить", callback_data="num_finish")] + ] + keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) + return keyboard + +# формируем сообщение с переменным аргументом +async def update_num_text(message: types.Message, new_value: int): + with suppress(TelegramBadRequest): + await message.edit_text( + f"Укажите число: {new_value}", + reply_markup=get_keyboard() + ) + +# запуск клавиатуры по команде /numbers +@dp.message(Command("numbers")) +async def cmd_numbers(message: types.Message): + user_data[message.from_user.id] = 0 + await message.answer("Укажите число: 0", reply_markup=get_keyboard()) + + +@dp.callback_query(F.data.startswith("num_")) +async def callbacks_num(callback: types.CallbackQuery): + user_value = user_data.get(callback.from_user.id, 0) + action = callback.data.split("_")[1] + + if action == "incr": + user_data[callback.from_user.id] = user_value+1 + await update_num_text(callback.message, user_value+1) + elif action == "decr": + user_data[callback.from_user.id] = user_value-1 + await update_num_text(callback.message, user_value-1) + elif action == "finish": + await callback.message.edit_text(f"Итого: {user_value}") + + await callback.answer() @dp.message(Command("inline_url")) async def cmd_inline_url(message: types.Message, bot: Bot):