На Хабре много статей про разработку Телеграм ботов на Python, в том числе при помощи Django. Однако, большинство из них направлено на первичное ознакомление с API Телеграм.
В этой статье я хочу рассказать как можно упростить разработку ботов, сократив количество кода в разы, и не создавать велосипед.
Всем, привет! Меня зовут Александр Алескин, и я участвовал в создании десятков Telegram ботов. Достаточно часто я встречался с одними и теми же проблемами при разработке из-за нехватки инструментов: в Телеграме нет web-форм -невозможно от пользователя одновременно запросить несколько атрибутов; поддержка локализации; учет состояния пользователя для отображения информации; над разными типами данных происходят схожие действия, но нужно писать отдельные обработчики под каждый тип и т.д.
На самом деле все эти проблемы достаточно легко решаются по одиночке. Однако, при расширении функциональности бота это неминуемо приводит к дублированию кода в той или иной степени, а также к путанице среди функций.
В этой статье пойдет речь об инструментах, которые позволяют стандартизировать разработку ботов. Большинство из них широко применяется в Web-разработке и присутствуют в Django (или Django Rest Framework). Данные инструменты кастомизированы для использования под Телеграм ботов в библиотеке Telegram Django Bot, о которой и будет рассказано далее.
Telegram-Django-Bot библиотека
В основе Telegram-Django-Bot лежит использование следующих двух библиотек:
-
Python-Telegram-Bot – библиотека для взаимодействия с Telegram API (22k звезд на github).
-
Django – фреймворк для разработки веб-сервисов (в ТОП-10 фреймворков в мире).
По факту Telegram-Django-Bot плотно связывает между собой эти библиотеки и предоставляет схожий с Django Rest Framework инструмент для управления данными.
Viewsets и Формы
Ключевой сущностью Telegram-Django-Bot является TelegramViewSet
– абстрактный класс для управления моделями (таблицами) базы данных, описанных через Django ORM. Данный класс позволяет создавать, отображать, удалять и отображать элементы сущности. Как и в Django Rest Framework Viewsets по необходимости вы можете добавлять свои методы для обработки данных. Однако, в отличие от web аналога, данный класс формирует ответ не в виде json или xml, а на естественном языке в виде текста и кнопок для дальнейшего отображения пользователю в Телеграмме. То есть совмещает обработку данных и формирование интерфейса для пользователей.
Такой подход шаблонизирует обработку данных и общение бота с пользователем. В итоге это позволяет значительно сократить количество кода.
Легче всего продемонстрировать этот подход на примере Telegram_django_bot_template. Данный репозиторий создан как шаблон для создания ботов и содержит небольшие примеры по использованию.
Для более подробного и реального примера можно рассмотреть живого бота @Disk_Drive_Bot (исходный код бота). Основная цель бота – хранение файлов в Телеграме, так же как это делает Яндекс диск или Гугл драйв.
Так, например, в коде шаблона задается управление некоторой сущностью BotMenuElem (таблицей в базе данных) в 40 строчках кода:
class BotMenuElemViewSet(TelegramViewSet): viewset_name = 'BotMenuElem' # Название Вьюсета для отображения model_form = BotMenuElemForm # Форма, где указываются поля для забора от клиента и их валидация. Дочерний класс от Django.forms.ModelForm queryset = BotMenuElem.objects.all() # указываем, что за модель базы данных foreign_filter_amount = 1 # количество переменных окружения (дополнительный атрибут, детали в документации) prechoice_fields_values = { # Часто используемые варианты значений атрибутов или просто формат отображения некоторых значений атрибутов 'is_visable': ( # варианты для переменной is_visable в формате (значение, текст) (True, '👁 Visable'), (False, '🚫 Disabled'), ) } def get_queryset(self): # переопределение вспомогательной функции для учета бизнес логики queryset = super().get_queryset() if self.foreign_filters[0]: # хотим, чтобы если указана переменная окружения, то отображали только те элементы, в которых поле команда начинались со значения переменной окружения queryset = queryset.filter(command__contains=self.foreign_filters[0]) return queryset def create(self, field=None, value=None, initial_data=None): # переопределение функции создания элемента initial_data = { # просто хотим указать стартовые значения для некоторых атрибутов 'is_visable': True, 'callbacks_db': '[]', 'buttons_db': '[]', } return super().create(field, value, initial_data) def show_list(self, page=0, per_page=10, columns=1): # переопределение функции отображения списка элементов reply_action, (mess, buttons) = super().show_list(page, per_page, columns) buttons += [ # к дефолтному отображению элементов в виде текста и кнопок добавляем кнопки «создание» и «назад» [InlineKeyboardButtonDJ( text=_('➕ Add'), callback_data=self.gm_callback_data('create') )], [InlineKeyboardButtonDJ( text=_('🔙 Back'), callback_data=settings.TELEGRAM_BOT_MAIN_MENU_CALLBACK )], ] return reply_action, (mess, buttons) # для ответа пользователю возвращаем формат ответа и атрибуты ответа (в данном случае текст сообщения и кнопки)
И эти 40 строчек позволяют создавать, изменять, удалять и просматривать список элементов BotMenuElem
в боте. В результате такого описания BotMenuElemViewSet
получаем необходимую бизнес логику и пользовательский интерфейс в боте:
Магия заключается в том, что базовая логика взаимодействия и отображения элементов описана в родительском классе TelegramViewSet
. В нем присутствуют следующие стандартные функции:
Метод |
Описание |
---|---|
create |
Создание модели |
change |
Изменения атрибутов |
delete |
Удаление модели |
show_elem |
Отображение элемента |
show_list |
Отображение списка элементов |
Для удобства кастомизации бизнес логики эти функции вызывают вспомогательные методы внутри. Так, например, в BotMenuElemViewSet
переопределяется только одна вспомогательная функция get_queryset
под особенности контекста.
Среди функций TelegramViewSet
особый интерес представляют create
и update
, так как они осуществляют плотную работу с формами (структурой из нескольких атрибутов, которые указывает пользователей). Так как Телеграм не умеет работать с формами, то здесь используется последовательный подход (как и ConversationHandler: сначала пользователей указывает одно поле, затем второе и т.д.:
Для этого, как и в Django, создается форма данных, которая указывается в BotMenuElemViewSet:
# base/forms.py from telegram_django_bot import forms as td_forms class BotMenuElemForm(td_forms.TelegramModelForm): class Meta: model = BotMenuElem fields = ['command', "is_visable", "callbacks_db", "message", "buttons_db"] # base/views.py class BotMenuElemViewSet(TelegramViewSet): model_form = BotMenuElemForm …
Роутинг
Структурность кода одна из важных составляющих для поддержания проекта. Для этого Django предлагает разбивать проект на приложения (apps), каждое из которых отвечают за свою задачу (например, оплата или интеграция с каким-нибудь другим сервисом).
Telegram-Django-Bot делает возможным использование этого механизма при разработке ботов через универсальный обработчик RouterCallbackMessageCommandHandler
. Данный класс необходимо добавить в перечень обработчиков веб-сервера Python-Telegram-Bot:
from telegram_django_bot.tg_dj_bot import TG_DJ_Bot from telegram_django_bot.routing import RouterCallbackMessageCommandHandler # look in the template: https://github.com/alexanderaleskin/telergam_django_bot_template/blob/main/run_bot.py#L19 # in 13.x version of Python-Telegram-Bot: updater = Updater(bot=TG_DJ_Bot(settings.TELEGRAM_TOKEN)) updater.dispatcher.add_handler(RouterCallbackMessageCommandHandler()) # or in 20.x version : bot = TG_DJ_Bot(settings.TELEGRAM_TOKEN) application = ApplicationBuilder().bot(bot).build() application.add_handler(RouterCallbackMessageCommandHandler())
После чего можно прописывать пути к конкретным обработчикам (endpoints) и viewsets в стандартной для Django нотации:
# файл bot_conf/utrls.py (основной) urlpatterns = [ re_path('', include(('base.utrls', 'base'), namespace='base')), # подключаем пути из модуля base ] # файл base/utrls.py from django.urls import re_path, include from .views import start, BotMenuElemViewSet, UserViewSet # импорт veiwset и обычной функции urlpatterns = [ # добавляем пути re_path('start', start, name='start'), re_path('main_menu', start, name='start'), re_path('sb/', BotMenuElemViewSet, name='BotMenuElemViewSet'), re_path('us/', UserViewSet, name='UserViewSet'), ] # Теперь по нажатию на кнопку с callback_data='sb/<suffix>' будут вызываться методы BotMenuElemViewSet
Как и в Django, необходимо указать главный файл с маршрутизацией запросов в настройках проекта (в settings.py фале):
TELEGRAM_ROOT_UTRLCONF = 'bot_conf.utrls'
Как результат таких манипуляций приложение может быть разбито на несколько папок, в каждой из которых храниться своя независимая бизнес логика (а если вы пишите не один бот, а несколько, то такой подход позволяет легко подключать отдельные модули).
Дополнительные инструменты библиотеки
В библиотеке есть также ряд инструментов, которые часто нужны в продакшн ботах:
-
Встроенные модели в базе данных:
-
ActionLog – хранит действие пользователей (нажатия на кнопки, нажатие на команды и тд). Данная модель позволяет анализировать активность и действия пользователей;
-
TeleDeepLink – хранит информацию откуда пришел пользователей (если пользователей перешел в бота по deeplink). Это информация помогает строить воронку продаж и отслеживать эффективность рекламы;
-
BotMenuElem — достаточно часто в боте есть блоки, где указывается статическая информация (например, сообщение о контактах поддержки, база знаний или стартовое сообщение). Данная модель дает возможность создавать такие блоки меню в боте, при этом указывать ссылки на другие элементы или функции/вьюсеты через кнопки. В примере темплейта как раз используется эта модель (стандартное создание предполагается через администраторскую панель).
-
Trigger – при наступлении определенных событий (смотрит в ActionLog) отправляет определенные сообщения пользователям (BotMenuElem). Так, например, можно отправлять сообщения пользователям, которые стартанули бота и не продолжили взаимодействия, или попросить пользователей, которые пользуются больше 7 дней к примеру, дать обратную связь о взаимодействии с ботом;
-
-
Дополнительные функции:
-
telegram_django_bot.utils.handler_decor – обертка для своих обработчиков (Пример использования; вьюсеты автоматически оборачиваются в декоратор). Позволяет логировать действие пользователей, ловить и логировать все ошибки, а также обрабатывать создание пользователей;
-
Telegram_django_bot.utils.CalendarPagination– класс для генерации календаря в меню бота в виде активных кнопок (по нажатию можно перейти в другие разделы);
-
Telegram_django_bot.tg_dj_bot.TG_DJ_Bot – дочерний класс telegram.Bot, который добавляет ряд функций в том числе task_send_message_handler для безопасной рассылки сообщений группе пользователей (обрабатывает ошибки при отправке сообщений).
-
-
Локализация – используется инструменты локализации Django.
Заключение
Telegram-Django-Bot ускоряет создание Телеграм ботов за счет стандартизации действий. Важным инструментом библиотеки является TelegramViewSet
класс, использование которого позволяет в несколько строк описать как обрабатывать и отображать данные пользователю.
Посмотреть, как это работает, а также запустить самому можно на примерах Telegram_django_bot_template и Disk_Drive_Bot.
Очень интересна ваша обратная связь, а также предложения по улучшению. И особо приветствуются реквесты в репозитории!
ссылка на оригинал статьи https://habr.com/ru/articles/734786/
Добавить комментарий