13g10n
На главную

Процессинг форм в телеграм ботах

3 минуты

Немного отвлечемся от расширения возможностей фласка и поговорим про небольшую библиотечку для aiogram (асинхронная python реализация Telegram API), которая позволяет обрабатывать последовательный пользовательский ввод по заданным правилам. Идея появилась у меня более года назад и практически сразу вылилась в написание aiogram-forms.

Предположим, вы пишите своего телеграм-бота, который должен принимать от пользователей заявки на какую-нибудь конференцию. Будь у нас сайт, очевидным и самым распространенным решением было бы создать HTML форму, добавить туда нужные поля и обработать отправленные данные на сервере. Но почему мы не можем сделать то же, но в формате диалога с пользователем?

Если мы заглянем в официальную документацию aiogram или любые более-менее жизнеспособные примеры, то обнаружим, что предложенным решением является использование конечного автомата для управления состоянием, причём нам даже дают реализацию и необходимые классы-помощники:

from aiogram.dispatcher.filters.state import State, StatesGroup

class ConventionForm(StatesGroup):
    name = State()
    company = State()
    topic = State()

Далее, для каждого состояния нам нужно написать ивент-хэндлер с фильтром, в котором мы сохраняем данные и двигаем состояние. Ах да, не забудьте ещё и сообщение относящееся к следующему вопросу отправить!

@dp.message_handler(state=Form.name)
async def process_name(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['name'] = message.text

    await Form.next()
    await message.reply('Из какой вы компании?')

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

Давайте сравним этот подход с тем, что мы имеем используя aiogram-forms. Все необходимые нам действия сводятся к описанию класса ConventionForm и вызову ConventionForm.start() для начала процессинга формы:

from aiogram_forms import forms, fields, validators

class ConventionForm(forms.Form):
    name = fields.StringField('Ваше имя?')
    company = fields.StringField('Из какой вы компании?')
    topic = fields.StringField('Доклад на какую тему вы бы хотели представить?')

@dp.message_handler(commands="register")
async def command_register(message: types.Message):
    await ConventionForm.start(callback=say_thanks)

Вы можете использовать один из заранее описанных типов полей из aiogram_forms.fields, если вам нужна валидация из коробки.

Если же вам нужно что-то кастомное, то вы можете написать свой тип или расширить существующие при помощи дополнительных параметров. Для валидации просто добавьте параметр validators и передайте список валидаторов из aiogram_forms.validators (а можете даже написать свои). Для управления клавиатурой всегда есть reply_keyboard параметр, а для кастомизации сообщений об ошибках валидации — validation_error_message.

В то время как aiogram уверенно идёт к новой крутой 3.0.0 версии, я решил не забрасывать разработку aiogram-forms и уже подготовил под неё работающий прототип 1.0.0 версии написанный с нуля, который работает быстрее и предоставляет ещё больше возможностей как прямиком из коробки, так и при помощи кастомизаций.

OpenSourcePythonTelegramaiogramaiogram-forms