Фонд золотых цитат: генерируйте стикеры из сообщений в Telegram
Рассказываем, как сделать стикер-бота без лишних телодвижений, и отдаем свое творение на тест.
Все началось с одной из учебных групп в Telegram. Студенты там очень любят делать стикеры из сообщений своего преподавателя. Я выяснил, что делаются они в полуавтоматическом режиме: сообщение пересылается в бота, который рисует «пузырек» сообщения, а результат пересылается в официального стикер-бота.
Схема рабочая, но напрашивается идея минимизировать количество пересылок. Тем более, что в Telegram существуют боты, создающие пользовательские стикерпаки. Концепт идеи прост: пользователь пересылает сообщение в диалог с ботом, бот создает стикер.
Чтобы было интереснее, введем дополнительные ограничения:
- никаких баз данных, даже встроенных;
- никаких промежуточных файлов, стараемся делать все в памяти.
Такой подход усложняет разработку бота, но значительно упрощает его эксплуатацию:
- вся информация хранится в Telegram, у бота нет данных — не нужно думать о резервном копировании;
- для запуска бота нужен только код и файл конфигурации;
- бот может быть запущен даже на Raspberry Pi.
Сервер с «малинкой» можно получить в Selectel в течение часа после заказа. Всего за 499 рублей в месяц.
Для разработки я выбрал язык Python версии 3.8. Сперва сделаем основу бота, которая получает сообщения и выводит доступную информацию.
Основа
Итак, регистрируем нового бота или используем старого. Все операции с ними производятся через официального BotFather. Для начала хватит идентификатора бота (username) и токена для API.
Представленный в статье код адаптирован для объяснения в контексте статьи. Ссылка на оригинальный исходный код будет в конце.Для Bot API уже есть обертка, названная python-telegram-bot. В статье используется версия 13.4.1. Создаем простой обработчик текстовых сообщений:
Создаем бота и регистрируем обработчик.
Теперь боту можно переслать любое сообщение, и он выведет в stdout данные, которые ему доступны.
Вывод обработчика без чувствительных данных:
В представленном выводе доступна следующая информация:
- forward_from — информация об авторе пересланного сообщения;
- text — текст пересланного сообщения.
Для того, чтобы нарисовать «пузырек» сообщения, не хватает лишь аватарки. Получаем ее парой отдельных вызовов:
Вызов get_user_profile_photos() возвращает двумерный массив записей типа File. Первое измерение задает количество аватарок у пользователя, но не больше limit. Второе измерение задает аватарку разных размеров. В нашем случае достаточно забрать первую попавшуюся картинку, но для оптимизации стоит сразу выбирать картинку подходящего разрешения.
Объект file имеет метод download_as_bytearray(), что позволяет загрузить аватарку в память без использования промежуточных файлов.
Теперь, когда есть необходимая информация, можно нарисовать «пузырек».
Рисуем стикер
Пример созданного изображения
Для рисования используем библиотеку Pillow версии 8.4.0. Шрифт — OpenSans, такой же используется в официальных приложениях Telegram.
Мессенджер накладывает ограничение на стикеры: как минимум одна сторона должна быть размером 512 пикселей. Так как мы генерируем сообщение, то можно зафиксировать ширину, а высоту рассчитывать в зависимости от количества текста.
Функция textwrap.wrap() разбивает строку на массив строк, пытаясь сделать перенос по пробелам. Расчет высоты картинки прост:
- отступ от начала — BUBBLE_PADDING, в моем случае 10px;
- имя отправителя — font_height;
- сообщение — font_height * len(text);
- отступ до конца — BUBBLE_PADDING.
Если сообщение большое, то высота картинки может получиться больше 512 пикселей. В этом случае наши полномочия — лапки, выбрасываем исключение. Если размер меньше, то можем продолжать. Проверяем наличие аватарки у пользователя и адаптируем ее к нашему стикеру.
Теперь у нас есть сообщение и аватарка. Создаем «холст» и начинаем рисовать. Обязательно выбираем цветовой режим RGBA и делаем прозрачный (alpha = 0) основным цветом «холста».
Финальный штрих — сохранить изображение. Так как мы все держим в памяти, то сохраняем также в виртуальный байтовый поток.
Осталось совсем немного: загрузить стикер в Telegram и передать его пользователю.
Заполнение набора стикеров
Те, кто создавал собственные наборы, знают, что для всех операций со стикерами необходимо обращаться к боту Stickers. Однако, в Bot API есть набор вызовов для взаимодействия со стикерами, в том числе функция создания набора. Созданный ботом набор стикеров имеет следующие особенности:
- уникальное имя набора (используется в ссылках вида https://t.me/addstickers/<имя>) обязательно должно заканчиваться на _by_%BOT_USERNAME%;
- набор стикеров принадлежит пользователю и может быть отредактирован через бота Stickers;
- для управления набором стикеров через бота требуется его уникальное имя и идентификатор пользователя.
Как упоминалось ранее, бот должен работать без базы данных. Таким образом, уникальное имя набора должно быть вычисляемым. Самый простой способ — использовать идентификатор пользователя в имени набора. Однако это некорректно: любой пользователь набора стикеров может «вычислить» автора.
Имя бота в уникальном имени набора неявно используется для аутентификации действий бота. Так, зная идентификатор пользователя, произвести деструктивные действия с набором не получится.
Эта «особенность» исправляется хэшированием. Мне показалось подходящим использовать UUIDv5, который использует SHA-1 для хэширования. Правда, UUIDv5 не соответствует сразу двум ограничениям Telegram:
- может начинаться с цифры;
- имеет запрещенные символы — дефисы.
Первая проблема решается префиксом, а вторая — удалением запрещенных символов. Таким образом, UUIDv5 от идентификатора пользователя — отличное вычисляемое решение. А чтобы усложнить угадывание автора, можно добавить «соль» к идентификатору.
Теперь у нас все есть, создаем набор с первым стикером.
Если функция вернула True, то стикерпак создан. Если мы хотим добавить еще один стикер, то сперва набор нужно найти.
Стикеры добавляются в набор мгновенно, но у пользователей отображаются в течение нескольких часов. Наиболее оперативный способ обновить набор — удалить из сохраненных наборов и добавить заново.
В качестве ответа бот будет отправлять только что добавленный стикер, подтверждая, что он действительно загружен.
Вот и все, бот готов.
Конечно, это далеко не продуктовый вариант, так как Emoji не поддерживаются, существует ограничение на 120 стикеров на человека и совершенно нет кастомизации сообщений. Но для начала сойдет.
Заключение
Еще один маленький шажок для автоматизации рутинных процессов.
Генерация стикеров — не самый популярный случай, но, если вдруг захочется автоматизировать, теперь вы знаете как. Для быстрого тестирования можете использовать моего бота: ohmyquotebot (если что, он не будет жить вечно).
Бот не отвечает на команду /start, так что не волнуйтесь и просто пересылайте ему сообщение, из которого хотите сделать стикер.
Исходный код доступен на GitHub.