Skip to content

Aiogram 3: пользовательские фильтры

https://habr.com/ru/articles/821085/

Фильтры в aiogram нужны для того, чтобы бот понимал, как реагировать на то или иное событие (действие, сообщение или тип сообщения).

Пользовательские фильтры через классы

Если пишите бота по предложенной мною структуре из предыдущих статей, то у вас так-же есть пакет filters. Давайте там создадим файл с именем is_admin.py и там пропишем фильтр, который будет проверять является ли пользователь администратором.

Полный код фильтра:

python
from typing import List

from aiogram.filters import BaseFilter
from aiogram.types import Message


class IsAdmin(BaseFilter):
    def __init__(self, user_ids: int | List[int]) -> None:
        self.user_ids = user_ids

    async def __call__(self, message: Message) -> bool:
        if isinstance(self.user_ids, int):
            return message.from_user.id == self.user_ids
        return message.from_user.id in self.user_ids

Из библиотеки typing мы импортировали List, чтобы корректно указать, что мы можем передавать либо список, либо одиночный идентификатор администратора.

Из библиотеки aiogram мы импортировали BaseFilter и Message, необходимые для создания пользовательского фильтра и работы с сообщениями.

Теперь перейдем к самому фильтру:

Фильтр, который мы создали, называется IsAdmin. Этот фильтр предназначен для проверки, является ли пользователь, отправивший сообщение, администратором.

Конструктор __init__ инициализирует объект класса IsAdmin и принимает один параметр user_ids. Этот параметр может быть либо целым числом (если у нас только один администратор), либо списком целых чисел (если у нас несколько администраторов). Сохраняем этот параметр в атрибуте self.user_ids.

python
class IsAdmin(BaseFilter):
    def __init__(self, user_ids: int | List[int]) -> None:
        self.user_ids = user_ids

Асинхронный метод call:

python
async def __call__(self, message: Message) -> bool:
    if isinstance(self.user_ids, int):
        return message.from_user.id == self.user_ids
    return message.from_user.id in self.user_ids

Метод call является обязательным для классов пользовательских фильтров. Этот метод вызывается каждый раз, когда необходимо применить фильтр к сообщению.

Проверка типа self.user_ids:

Если self.user_ids является целым числом int, значит у нас один администратор. В этом случае просто проверяем, совпадает ли идентификатор пользователя, отправившего сообщение message.from_user.id, с self.user_ids.

Если self.user_ids является списком List[int], значит у нас несколько администраторов. В этом случае проверяем, содержится ли идентификатор пользователя в этом списке.

Стоит отметить, что, будь то пользовательские фильтры или магические фильтры, их конечная цель всегда заключается в получении логического значения True или False. Иными словами, написание класса фильтров или конструкции магического фильтра сводится к проверке условия: если условие истинно, действие выполняется, если нет — действие не выполняется.

Использование фильтра

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

python
from filters.is_admin import IsAdmin

@start_router.message(F.text.lower().contains('подписывайся'), IsAdmin(admins))
async def process_find_word(message: Message):
    await message.answer('О, админ, здарова! А тебе можно писать подписывайся.')


@start_router.message(F.text.lower().contains('подписывайся'))
async def process_find_word(message: Message):
    await message.answer('В твоем сообщении было найдено слово "подписывайся", а у нас такое писать запрещено!')

from filters.is_admin import IsAdmin импортируем наш пользовательский фильтр IsAdmin из пакета с фильтрами.

Обработчик для администраторов:

python
@start_router.message(F.text.lower().contains('подписывайся'), IsAdmin(admins))
async def process_find_word(message: Message):
    await message.answer('О, админ, здарова! А тебе можно писать "подписывайся".')

Здесь мы создаем обработчик, который реагирует на сообщения, содержащие слово "подписывайся". Этот обработчик будет срабатывать только в том случае, если сообщение отправлено администратором, чьи идентификаторы указаны в admins. Если условие выполняется, бот отвечает: "О, админ, здарова! А тебе можно писать "подписывайся".".

Обработчик для всех остальных пользователей:

python
@start_router.message(F.text.lower().contains('подписывайся'))
async def process_find_word(message: Message):
    await message.answer('В твоем сообщении было найдено слово "подписывайся", а у нас такое писать запрещено!')

Этот обработчик также реагирует на сообщения, содержащие слово "подписывайся". Но в отличие от предыдущего обработчика, он срабатывает для всех пользователей, кроме администраторов. Если обычный пользователь отправляет такое сообщение, бот отвечает: "В твоем сообщении было найдено слово 'подписывайся', а у нас такое писать запрещено!".

Порядок обработки

Здесь важно обратить внимание не только на сам код, но и на порядок его выполнения. Напоминаю, что интерпретатор Python читает код сверху вниз. В связи с этим фильтры с более точной настройкой должны располагаться выше.

Почему это важно:

Если более общий фильтр будет стоять выше, он перехватит сообщение до того, как его сможет обработать более специфический фильтр. В нашем случае, если бы обработчик для всех пользователей стоял выше, то сообщение с текстом "подписывайся" было бы обработано этим обработчиком, и администраторы никогда не получили бы свой специальный ответ.

Таким образом, располагая более специфичные фильтры выше, мы обеспечиваем корректную обработку сообщений и выполнение нужных действий в зависимости от условий. Это же касается и включения роутеров в главном файле бота. То что имеет более высокий приоритет (более точную настройку) всегда должно включаться раньше чем более общее.

Напоминаю, что список администраторов у меня записан таким образом:

python
admins = [int(admin_id) for admin_id in config('ADMINS').split(',')]

Данные тянутся с файла .env и при помощи python-decouple вытягивает эту информацию с файла .env (сами данные в хендлер старт тянутся с файла create_bot).

python
from create_bot import admins

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

Contacts: teffal@mail.ru