Конфигурация вместо кода при написании Telegram-бота

от автора

Привет! Меня зовут Никита и я пишу от имени небольшой команды студентов, разработчиков проекта Cloffer — система онлайн-заказа для кофеен. Мы решили начать наш путь с написания связки бэкенд + набор телеграм-ботов. Эта статья будет посвящена подходу, который мы использовали для реализации именно телеграм-части.

Часть 1. Мотивация

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

Первое — мы пишем на Python, и он исполняемый. Это значит, что для отладки нового сценария нужно повторять этот сценарий целиком после каждого изменения, поскольку новый код может просто не запуститься. С компилируемыми языками проще, поскольку все опечатки исправляются на этапе билда. Хочется какую-то часть кода исполнять при запуске приложения.

Второе — в предыдущем проекте ко мне пришёл менеджер и попросил поменять текст в одном сообщении. А потом ещё раз, но в другом. А потом ещё раз, но в третьем. Что понял: я хочу дать менеджерам возможность кастомизировать ботов; я ОЧЕНЬ не хочу видеть эмоджи в коде приложения.

def get_random_text():     return f"?\n\n\n<i>{get_random_quote()}</i>"

Решение, которое пришло — давайте вынесем настройку IO бота в отдельный конфигурационный файл с читабельным синтаксисом. Часть кода будет жить в статическом файле и будет провалидирована на старте, изменять её сможет не-технический специалист.

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

Часть 2. Имплементация

Посмотрим на конфигурацию, которую мы используем в одном из телеграм-ботов.

dialogues:   start:     description: Стартовое сообщение после команды /start. Стартовая картинка с логотипом кофенйи, приветственный текст и 5 inline-кнопок под сообщением.     message: |       Привет! ??       Это телеграм-бот кофейни {cafe}, с помощью которого ты можешь заказать любые наши напитки или еду онлайн!              — Используй /menu, чтобы найти нужные блюда              — Добавь их в корзину, её можно открыть с помощью /cart, и оплати через СБП              — Дождись готовности заказа в разделе /orders, а затем забери его в кофейне!?              По всем вопросам пиши: {support}      image: start.png     buttons:       - text: ? Меню         callback:           prefix: start_menu       - text: ? Корзина         callback:           prefix: cart           params:             d: 0        - text: ? Заказы         callback:           prefix: history_page           params:             page: 0       - text: ??‍? Поддержка         callback:           prefix: support       - text: ? Оставить фидбэк         callback:           prefix: feedback    menu:     description: Клик на кнопку "Меню" внизу. Открытие меню.     message: |       <b>{title}</b>        {description}        <b>Выбери категорию ⤵️</b>     image: menu.png

Как теперь выглядят хэндлеры телеграм-бота? Пишем с использованием библиотеки aiogram версии 3.

router = Router()  @router.message(CommandStart(), MessageConfigSection("start")) async def start(_, answer: Callable):     await answer()

Мы достигли этого с помощью filters/middlewares фичи библиотеки: она позволяет добавлять объекты в вызов функции, в нашем случае это answer — функция, берущая нужную конфигурацию сообщения (текст, кнопки, картинки) и отправляющая ответ на него. В случае необходимости мы совершаем нужный procedure call и передаём дополнительные аргументы в answer, чтобы он вставил переменные в статический текст:

@menu_router.callback_query(StartMenuPageCallback.filter(), MessageConfigSection("menu")) @menu_router.message(Command("menu"), MessageConfigSection("menu")) async def start_menu(_, answer: Callable, api: ApiClient) -> None:     menu = await api.get("menu")     await answer(reply_markup=build_markup_from_api(menu, page=0), context=menu)

В конце концов мы получаем такой экран:

— ???
— Profit

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

  notification_cancelled_payment_too_much_workload:     description: |       Уведомление, которое приходит в случае отмены заказа из-за слишком большой загруженности       В этом случае заказ будет переведен в статус ожидания возврата (отменен)     message: |       ❌ <b>К сожалению, сотрудник {cafe} отменил заказ {orderCode} из-за большой загруженности       Деньги по твоему заказу будут возвращены в ближайшие несколько минут</b>     buttons:       - text: ? Перейти в меню         callback:           prefix: start_menu

Часть 3. Взгляд в будущее

Аiogram v3 "provides useful mechanisms to modularize your code, and enables the creation of shareable modules via packages on PyPI"

Поэтому мы думаем над переработкой кода этих конфиг-секций и создания отдельной, более гибкой и расширяемой библиотеки.

Например, мы хотим вынести кастомные плагины, такие как buttons, image и прочие, чтобы механика отправки сообщения могла быть изменена. Можно также подумать над авто-визуализацией экранов телеграм-бота. Я думаю этот подход оставляет полный контроль за происходящим в руках программиста (в отличие от существующих no-code решений), однако позволяет сэкономить время на написании однообразного.

Мне будет интересно послушать про ваш опыт в этой области и предложения по поводу библиотеки. Жду ваших комментариев!

ТГ нашего проекта


ссылка на оригинал статьи https://habr.com/ru/articles/789606/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *