Процессинг форм в телеграм ботах
Немного отвлечемся от расширения возможностей фласка и поговорим про небольшую библиотечку для 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 версии написанный с нуля, который работает быстрее и предоставляет ещё больше возможностей как прямиком из коробки, так и при помощи кастомизаций.