Как можно одновременно обработать команду и callback в одной функции?

Как можно решить проблему с ошибкой, которая возникает из-за попытки вызвать метод `delete()` на объекте `CallbackQuery`, если в коде происходит присвоение переменной `message` объекта `callback.message`? Кроме того, как улучшить реализацию декоратора `check_is_admin` для правильного контроля доступа?
  • 17 октября 2024 г. 10:22
Ответы на вопрос 2
Чтобы одновременно обработать команду и callback в одной функции, вы можете использовать условные операторные конструкции или тип проверки для различения входящих данных. Например, если вы работаете с библиотекой для создания ботов в Telegram, такой как `aiogram`, вы можете использовать декоратор `@dp.message_handler` и `@dp.callback_query_handler` для обработки сообщений и callback-запросов соответственно. Однако для более удобной организации кода, возможно, стоит объединить их в одну функцию, которая будет различать тип входящего сообщения.

Вот пример, как это можно реализовать:

```python
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.utils import executor

bot = Bot(token='YOUR_BOT_TOKEN')
dp = Dispatcher(bot, storage=MemoryStorage())
dp.middleware.setup(LoggingMiddleware())

@dp.message_handler(commands=['start'])
@dp.callback_query_handler(lambda c: True)  # Это условие замените на нужное
async def handle_command_and_callback(message_or_callback: types.Message = None, callback: types.CallbackQuery = None):
    if message_or_callback:
        # Обработка команды
        await message_or_callback.answer("Это команда!")
    if callback:
        # Обработка callback
        await callback.message.answer("Это ответ на callback!")
        # Не забудьте удалить callback
        await callback.answer()

if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=True)
```

### Обработка ошибки с `delete()`

Ошибка, когда вы пытаетесь вызвать метод `delete()` на объекте `CallbackQuery`, может возникнуть из-за того, что объект `callback` не содержит свойства `message`, если это `callback`-запрос. Вам нужно использовать `callback.message`, чтобы правильно сохранить ссылку на сообщение.

Следует использовать проверку на наличие `callback`:

```python
if callback:
    message = callback.message  # Это будет работать
    await callback.answer()  # Попробуйте всегда вызывать это
```

### Улучшение декоратора `check_is_admin`

При контроле доступа через декораторы, вам нужно убедиться, что проверка прав администратора является надежной. Соответствующий декоратор может выглядеть так:

```python
from aiogram import types
from aiogram.dispatcher import Dispatcher

def check_is_admin(func):
    async def wrapper(message: types.Message, *args, **kwargs):
        if message.from_user.id in ADMIN_IDS:  # ADMIN_IDS - список ID администраторов
            return await func(message, *args, **kwargs)
        else:
            await message.answer("У вас нет прав для выполнения этой команды.")
    return wrapper

@dp.message_handler(commands=['admin_command'])
@check_is_admin
async def admin_command_handler(message: types.Message):
    await message.answer("Вы администратор!")
```

Важно, чтобы `ADMIN_IDS` был заранее задан, и все, кто имеет доступ, был в этом списке.  

Такой подход с использованием декоратора позволяет не дублировать логику проверки прав администратора для каждой команды и легко управлять доступом ко всем функциям.
В итоге забил и создал вторую функцию для обработки callback: 

@admin_router.message(Command('proportions'))
@check_is_admin
async def proportions(
        message: Message,
        state: FSMContext,
        keep_last_msg: bool = False
):

    """
    Handles the 'proportions' command. Receive a str value from admin and
    triggers the calculate_proportion function.

    :param state:
    :param keep_last_msg:
    :param message:
    :return: None
    """

    if not keep_last_msg:
        await message.delete()
        await sleep(DEL_TIME)

    await state.set_state(ProportionStates.waiting_for_proportion_input)
    sent_message = await message.answer(
        text='Введите через пробел: '
             '\nПропорции компонентов '
             '<b>A</b> и <b>B</b>, '
             'и общую массу - <b>C</b>'
    )

    await record_message_id_to_db(sent_message)


@admin_router.callback_query(F.data == 'another_proportion')
async def callback_proportion(callback: CallbackQuery, state: FSMContext):
    await callback.answer()
    await proportions(callback.message, state=state, keep_last_msg=True)
Похожие вопросы