Встроенные фильтры в aiogram

Встроенные фильтры в aiogram

F0rzend

Используя aiogram вы получаете множество дополнительных штук, которых нет в других библиотеках, как например встроенные фильтры. Вместо того, чтобы лепить страшную func=lambda message: message.text=="Текст", как в pyTelegramBotApi, в aiogram вы пропишете просто text="Текст". Но это далеко не все. При этом встроенные фильтры постоянно добавляются, но это нигде нормально не освещено (кроме источников), это подтолкнуло меня к написанию данной статьи. Сегодня мы полазим в исходниках aiogram и посмотрим, какие же фильтры у нас есть "из коробки".

Начнём, как не удивительно, с открытия репозиторий aiogram на github. Не сложно догадаться, что все фильтры находятся в пакете filters. Пара минут поиска приводят нас к aiogram/dispatcher/filters/. Далеко ходить не будем, сразу заглянем в файл __init__.py

aiogram/dispatcher/filters/__init__.py

В этом файле находятся просто импорты. Но это даёт нам очень важную информацию — всё самое интересное находится в файле .builtin. Таким образом составим небольшой список всех фильтров в текущей версии aiogram 2.10.2:

Command — проверка сообщения на команду

CommandHelp — проверка на команду /help

CommandPrivacy — проверка на команду /privacy

CommandSettings — проверка на команду /settings

CommandStart — проверка на команду /start

ContentTypeFilter — проверка типа контента

ExceptionsFilter — исключение для errors_handler

HashTag — обработка сообщений с #hashtag и $cashtags

Regexp — регулярное выражение для сообщений callback query

RegexpCommandsFilter — проверка команды регулярным выражением

StateFilter — проверка состояния пользователя

Text — фильтр текста. Работает на большинстве обработчиков

IDFilter — фильтр для проверки id чата или пользователя

AdminFilter — проверка на то, является ли пользователь администратором чата

IsReplyFilter — проверяет, что отправленное сообщение является ответом

IsSenderContact — проверяет, что пользователь отправил именно свой контакт

ForwardedMessageFilter — проверка на то, что сообщение переслано

ChatTypeFilter — проверка типа чата

Все стандартные фильтры подключаются на Dispatcher при его инициализации, при помощи приватного метода _setup_filters. Здесь мы можем сразу увидеть к каким из обработчиков применяются фильтры. Например первый подключаемый фильтр — StateFilter, который вы используете для работы с машиной состояний, подключается всем обработчикам кроме errors_handlers, poll_handlers и poll_answer_handlers, а ContentTypeFilter исключительно к сообщениям, которые содержат в update тип Message (т.к. только в нем есть content_type). Это следует из следующих строк:

Дальше мы рассмотрим подробнее каждый из фильтров.

Command фильтры

Все command фильтры применяются исключительно на обработчики message_handler и edited_message_handler.
Может использоваться как аргумент обработчика commands. Т.е. вы можете использовать данный фильтр двумя способами:

@dp.message_handler(commands='myCommand', commands_ignore_caption=False)

Либо

from aiogram.dispatcher.filters import Command


@dp.message_handler(Command('myCommand', ignore_caption=False))
. . .

Наверное самый часто используемый фильтр — Command. Наверняка все о нём знают, но я уверен, что не все знают о том, на сколько это "крутой" фильтр. Кроме всем известного аргумента commands он принимает prefixes, ignore_case, ignore_mention, ignore_caption.

  • prefixes — префиксы команды, т.е. то, с чего команда начинается. Самыми частыми префиксами являются стандартный "/", а также "!".
  • ignore_case — игнорировать регистр команды. Проверяется с помощью str.lower().
  • ignore_mention — игнорировать упоминание бота. По умолчанию False. Таким образом, когда бот получает команду с mention другого бота, он её не обрабатывает. Если передать True, в независимости от mention в /command@mention команда попадёт в обработчик, даже если это команда с упоминанием другого бота. Помните, что бот с включенным privacy mode не получит команду с упоминанием другого бота.
  • ignore_caption — игнорировать команды, которые написаны под изображением. По умолчанию True.

CommandStart

Этот фильтр, для проверки команды /start. Это фильтр Command, в который передаётся команда 'start', а остальные аргументы остаются по умолчанию. Фильтр CommandStart принимает лишь два аргумента:

  • deep_link — строка или регулярное выражение, для обработки deep_link.
  • encoded — обрабатывать закодированную ссылку deep_link (по умолчанию False).

Пример использования можно взять следующий:

from aiogram import types
from aiogram.dispatcher.filters import CommandStart


@dp.message_handler(filters.CommandStart(deep_link='deep_link'))
async def deep_link(msg: types.Message):
    await msg.answer('Да, знаем мы такое')

@dp.message_handler(filters.CommandStart())
async def command_start_handler(msg: types.Message):
    await msg.answer(f'Ну привет, хотел чего?')

Кроме обычного ответа на команду /start, этот фильтр также обрабатывает deep_link, для обработки deep_link. Мы ловим с его помощью ссылки с говорящим аргументом 'deep_link' (т.е ссылок вида https://t.me/{bot.username}?start=deep_link).

CommandHelp, CommandPrivacy и CommandSettings

Фильтры CommandHelp, CommandPrivacy и CommandSettings не представляют из себя ничего особо интересного. Это просто фильтр Command с переданной в него командой 'help', 'privacy' или 'settings' соответственно. Особенность этих команд заключается в том, что это глобальные команды, наличие которые добавляет дополнительные кнопки в профиле бота.

ContentTypeFilter

Этот фильтр проверяет тип контента, будь то фото, текст или что-нибудь другое. Должен использоваться исключительно как аргумент content_types. По умолчанию проверяет на текст. Принимает либо строку, либо aiogram.types.ContentTypes (что строкой и является).

Можно придумать много примеров использования, но возьмём самый простой: пусть бот хвалит картиночки.

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(content_types='photo')
@dp.message_handler(content_types=types.ContentTypes.PHOTO)
async def content_type_example(msg: types.Message):
    await msg.answer('Красивенько 😍')

ExceptionsFilter

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

from aiogram import types
from aiogram.utils import exceptions
from loguru import logger

from app.loader import dp


@dp.errors_handler(exception=exceptions.BotBlocked)
async def bot_blocked_error(update: types.Update, exception: exceptions.BotBlocked):
    logger.exception(f'Bot blocked by user {update.message.from_user.id}')
    return True

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

HashTag

Данный фильтр проверяет то, что в сообщении содержится определённый хеш (#) или кеш ($) тег. Может использоваться либо как callable, либо как аргументы hashtags и cashtags.

Допустим пусть бот понтуется, если видит в сообщении хештег или один из кештегов: #money или $EUR или $USD.

from aiogram import types
from aiogram.dispatcher import filters, FSMContext

from app.loader import dp


@dp.message_handler(hashtags='money')
@dp.message_handler(cashtags=['eur', 'usd'])
async def hashtag_example(msg: types.Message):
    await msg.answer('Ееее, деньги 😎')

Regexp

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

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp

IMAGE_REGEXP = r'https://.+?\.(jpg|png|jpeg)'


@dp.message_handler(filters.Regexp(IMAGE_REGEXP))
async def regexp_example(msg: types.Message):
    await msg.answer('Похоже на картинку, не так ли?')

Этот фильтр может использоваться также и как аргумент. Используйте для этого ключевое слово regexp:

@dp.message_handler(regexp=IMAGE_REGEXP)
async def regexp_example(msg: types.Message):
. . .

RegexpCommandsFilter

Фильтр, проверяющий команду через регулярное выражение. Может использоваться как аргумент regexp_commands (Именно поэтому у меня функция с двумя декораторами, вам стоит использовать один из приведенных вариантов).

Возьмём идентичный пример. Реализуем команду image с несколькими префиксами, которые проверяют ссылку на изображение:

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp

IMAGE_REGEXP = r'https://.+?\.(jpg|png|jpeg)'
COMMAND_IMAGE_REGEXP = r"/image:" + IMAGE_REGEXP


@dp.message_handler(filters.RegexpCommandsFilter([COMMAND_IMAGE_REGEXP]))
@dp.message_handler(regexp_commands=[COMMAND_IMAGE_REGEXP])
async def command_regexp_example(msg: types.Message):
    await msg.answer('По вашей команде докладываю, что данная ссылка является изображением!')

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

StateFilter

Все, кто использовал FSM знакомы с этим фильтром. Этот фильтр проверяет состояние, в котором находится пользователь. Он может использоваться только как аргумент функции state. Для примера опишем 2 обработчика: для присвоения состояния и для его сброса.

from aiogram import types

from aiogram.dispatcher import FSMContext

from app.loader import dp


@dp.message_handler(commands='set_state')
async def set_state(msg: types.Message, state: FSMContext):
    """Присваиваем пользователю состояние для теста"""
    await state.set_state('example_state')
    await msg.answer('Состояние установлено')


@dp.message_handler(state='example_state')
async def state_example(msg: types.Message, state: FSMContext):
    await msg.answer('Ой всё, иди отсюда')
    await state.finish()

Text

Ещё один очень популярный фильтр. Он проверяет текст, будь это текст сообщения, или callback_data кнопки. Кроме полной идентичности (equals), данный фильтр также может проверить то, что текст начинается, содержит или заканчивается какой-либо строкой. Вы можете использовать импортированный класс, передавая в него аргументы либо использовать фильтр как аргумент функции. Пройдёмся по аргументам конструктора класса:

  • equals — строка, которой текст должен быть идентичен
  • contains — строка, которую должен содержаться текст
  • startswith — строка, которой текст должен начинаться
  • endswith — строка, которой текст должен заканчиваться
  • ignore_case — игнорировать регистр текст. Проверяется с помощью str.lower().

Как я и сказал ранее, данный фильтр может использоваться как аргумент. Для этого используйте text, text_contains, text_startswith или text_endswith.

Рассмотрим следующий пример: допустим нам нужно ругать в чате участника за какую-нибудь фразу:

import asyncio

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


FORBIDDEN_PHRASE = [
    'Курс',
    'Фигня'
]


@dp.message_handler(filters.Text(contains=FORBIDDEN_PHRASE, ignore_case=True))
async def text_example(msg: types.Message):
    await msg.reply('Сам фигня!')

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

Результат работы фильтра

IDFilter

Ещё один фильтр, который заслуживает внимания — фильтр для проверки идентификатора. Он может использоваться как аргумент user_id, chat_id, так и как callable объект IDFilter(user_id=12345789)

Сам фильтр имеет два аргумента:

  • user_id — проверяет ID пользователя
  • chat_id — проверяет ID чата

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

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp
from app.config import SUPERUSER_IDS


@dp.message_handler(filters.IDFilter(chat_id=SUPERUSER_IDS))
@dp.message_handler(chat_id=SUPERUSER_IDS)
async def id_filter_example(msg: types.Message):
    await msg.answer('Да, помню тебя, наш человек')

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

AdminFilter

Один из любимых фильтров разработчиков чат модераторов. Как понятно из названия проверяет, что запрос прилетел от администратора чата. Может использоваться как callable объект, или аргумент функции.

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

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(commands='change_photo', is_chat_admin=True)
@dp.message_handler(filters.Command('change_photo'), filters.AdminFilter())
async def chat_admin_example(msg: types.Message):
    await msg.answer('Не, мне и эта нравится')

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

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

Этот фильтр не может быть False.

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

IsReplyFilter

Один из простейших фильтров. Он проверит, является ли сообщение или пост в канале ответом. Может использоваться как аргумент is_reply.

Допустим по команде user_id пусть бот возвращает ID пользователя, ответом на сообщение которого была использована команда.

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(is_reply=True, commands='user_id')
@dp.message_handler(filters.IsReplyFilter(True), commands='user_id')
async def reply_filter_example(msg: types.Message):
    await msg.answer(msg.reply_to_message.from_user.id)

IsSenderContact

Этот фильтр проверяет, что пользователь отправил именно свой контакт. Может использоваться как callable или как аргумент is_sender_contact. Думаю никому не нужно объяснять, где это необходимо.

Пример использования:

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(content_types='contact', is_sender_contact=True)
@dp.message_handler(filters.IsSenderContact(True), content_types='contact')
async def sender_contact_example(msg: types.Message):
    await msg.answer('Да, вроде на тебя похож, ладно')

ForwardedMessageFilter

Этот фильтр проверяет, что отправленное сообщение является пересланным. Может использоваться как аргумент is_forwarded или как объект класса.

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

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(is_forwarded=True)
@dp.message_handler(filters.ForwardedMessageFilter(True))
async def forwarded_example(msg: types.Message):
    await msg.answer('Не пытайся меня обмануть, я же вижу, что это не твоё сообщение')

ChatTypeFilter

Из названия этого фильтра понятно, что он проверяет тип чата. Он может использоваться либо как объект, либо как аргумент chat_type. Принимает либо строку, либо types.ChatType (что тоже является строкой).

Допустим нам нужно, чтобы бот по команде is_pm подтверждал, что команда выполнена в личных сообщениях.

from aiogram import types
from aiogram.dispatcher import filters

from app.loader import dp


@dp.message_handler(chat_type=types.ChatType.PRIVATE, commands='is_pm')
@dp.message_handler(chat_type='private', commands='is_pm')
@dp.message_handler(filters.ChatTypeFilter(types.ChatType.PRIVATE), commands='is_pm')
async def chat_type_example(msg: types.Message):
    await msg.answer('Да, это личные сообщения')

Итоги

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