История одного пет-проекта, или Бот с уведомлениями о новых сериях

Kate

Administrator
Команда форума
Всем привет. В этой статье хочу поделиться своей историей о разработке и запуске пет-проекта — бота с напоминаниями о выходе новых эпизодов просматриваемых вами сериалов. История может быть интересна всем планирующим начать свои проекты, связанные с разработкой ботов, настройкой интеграций с API-каталогами фильмов и сериалов.

Идея​

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

В результате я хотел получить следующее:

  1. Не держать в голове список всех сериалов, которые я смотрю.
  2. В день выхода новой серии получать об этом напоминание.

Технологии​

Язык и фреймворк​

Сам я преимущественно back-end разработчик, и работу с пользовательским интерфейсом хотел свести к минимуму, поэтому идеальным выбором для меня стала разработка бота. В качестве платформы я выбрал Telegram, поскольку пользуюсь этим мессенджером сам, к тому же API оказалось хорошо документированным.

Начиная данный проект, я решил не ставить акцент на изучении нового языка, поэтому остановился на своём основном — PHP. Тем не менее начало разработки проекта совпало с прохождением в моей нынешней компании airSlate, внутреннего курса «devops для программистов», в связи с чем я хотел воспользоваться новыми знаниями и закрепить их на практике.

В качестве фреймворка я выбрал Laravel, на этот момент у меня уже был опыт работы с ним, он довольно прост и лёгок для старта нового проекта, и в то же время фреймворк быстро развивается, имеет большое комьюнити и очень широкий инструментарий.

Для работы с API Telegram есть отличный сторонний клиент — BotMan, он из коробки поддерживает интеграцию с Laravel и предоставляет абстракцию над разными мессенджерами, поддержка каждого мессенджера реализована в виде драйвера. Библиотека сразу же включает драйвера для наиболее популярных мессенджеров, также есть возможность реализовать свой драйвер.

Сервер​

Сервер — Nginx. Telegram поддерживает два интерфейса для коммуникации с ботом:

  • getUpdates — работает с помощью pull-метода, при котором ваше приложение самостоятельно ходит на API Telegram и стягивает все обновления присланные вашему боту с момента предыдущего запроса. Такой механизм можно реализовать, например, при помощи крона;
  • setWebhook — push-метод, в котором вы указываете для Telegram эндпойнт вашего приложения, на который Telegram будет присылать все обновления по мере их поступления.
В своём приложении я воспользовался методом webhook. Для него есть несколько требований, в частности коммуникация должна происходить по защищённому каналу с помощью протокола HTTPS, для этого нужно настраивать Nginx с использованием SSL-сертификата. Тема получения SSL-сертификата подробно расписана в доке Telegram.

Я так и не смог настроить webhook с помощью самоподписанного сертификата, на каком-то этапе эта схема ломалась, скорее всего, что-то упустил, поэтому воспользовался для этой цели бесплатными услугами провайдера сертификатов Let’s Encrypt Let’s Encrypt. Срок действия выпускаемых сертификатов — 90 дней, с помощью инструмента Certbot можно настроить автоматическое обновление. Также во всей этой схеме понадобилось привязать доменное имя к IP-адресу сервера, можно найти много бесплатных поставщиков имён, я использую Freenom, он выдаёт бесплатные имена в зоне .tk.

База данных​

Природа данных, с которыми я планировал работать, отлично подходила для использования реляционной БД, поэтому в качестве основного хранилища я взял MySQL. Дополнительно подключил Redis, он понадобился для нескольких целей. Самая очевидная и банальная — кеширование. Но всё-таки основной целью стала организация очередей на базе redis. Laravel из коробки предоставляет мощный инструмент для организации очередей, в котором в качестве драйвера можно использовать redis. С помощью очередей я организовал асинхронную обработку некоторых задач и самый основной функционал приложения — отложенную отправку напоминаний. Для мониторинга работы очередей я использую ещё один инструмент Laravel — horizon. С помощью него можно мониторить ваши очереди по заготовленным метрикам, перезапускать упавшие очереди и даже настраивать масштабирование и балансировку воркеров. При этом у horizon приятный и удобный веб-интерфейс.

Конфиги​

Работу с переменными окружения я вынес в consul. Это мощный инструмент, который можно использовать не только как key-value хранилище (особенно полезное в распределённых системах), но и для service discovery, проверки работоспособности узлов и не только. Для моих нужд это слишком функциональный продукт, но мне было любопытно с ним поработать самостоятельно. Использование его для управления переменными окружения просто — в контейнере с приложением вам нужно установить и запустить consul-template, он динамически считывает ключи из консула и пишет их в конфигурационный файл вашего приложения (или файл с переменными окружения). В команде запуска consul-template достаточно указать путь к файлу с переменными окружения, который читает Laravel, а также указать файл-шаблон, который содержит соответствия названий переменных к ключам в самом консуле.

После этого вы можете пользоваться веб-интерфейсом консула для установки и изменения значений переменных. Меняя переменные в консуле, он автоматически перезаписывает их в .env файле приложения, при правильной настройке каждое такое изменение может приводить к перезапуску воркеров, чтобы приложение всегда использовало актуальные значения. Доступ к консулу нужен с веба, поэтому дополнительно потребовалось настроить ACL и сгенерировать токены.

Тут можно подробней ознакомиться с ACL, а здесь есть репозиторий с примером настройки ACL, из важного — конфигурационный файл консула, описание правил агента в .hcl файле и команды установки токена и правил.

Докер​

Каждый из сервисов я завернул в докер-контейнер, поднимаю все сервисы с помощью docker-compose. В итоге у меня получилось 5 контейнеров:

  • Nginx, который работает как прокси и передаёт обработку всех запросов в основной контейнер приложения с php-fpm.
  • Сам контейнер с приложением, в нем используется инструмент для управления процессами — supervisor. Сам супервизор запускается основным процессом контейнера и используется для запуска php-fpm, cron, horizon, consul-template и воркеров приложения. Супервизор автоматически перезагружает процессы в случае их завершения и в целом это очень удобный и гибкий инструмент. Каждый процесс, который должен управляться супервизором, нужно описывать в специальных конфигурационных файлах. В документации можно найти много примеров и подробное описание всех опций.
  • MySQL.
  • Redis — запускается в режиме перманентного хранения. Было важно, чтобы после перезагрузки контейнера он не терял выполнение отложенных задач. Запуск редиса в таком режиме прост, в docker compose файле, в секции настройки редиса необходимо указать команду: command: ["redis-server", "--appendonly", "yes"].
  • Consul.

Деплой​

Исходный код приложения хостится на GitHub, там же с помощью GH Actions настроен CI пайплайн: запуск статических анализаторов, билд-докер образа с приложением и отправка готового образа в докерхаб.

Деплой приложения на сервер происходит в ручном режиме, изначально я просто ходил на сервер по SSH и делал git pull, сейчас улучшил этот процесс с помощью самописного скрипта на Go, который запущен на сервере в виде systemd-сервиса. Этот скрипт открывает новый эндпойнт, триггер которого выполняет следующие действия:

  1. Скачивает новый образ с приложением.
  2. Останавливает и удаляет старый контейнер с приложением.
  3. Создаёт и запускает контейнер на основе нового образа.
Поскольку эндпойнт публичный, я его закрыл проверкой API ключа, передаваемого в Authorization заголовке запроса.

С исходным кодом и примером использования скрипта можно подробней ознакомиться в репозитории. И вот ряд ссылок, которые помогут вам разобраться с systemd и настроить свой сервис:

Таким образом, я планирую автоматизировать деплой приложения. GH Actions позволяет добавить workflow, который запускается вручную, он отвечает за отправку запроса на эндпойнт этого сервиса и запуск деплоя. В интерфейсе GitHub это выглядит так:

screen_2021-08-30_at_11.43.15.png


В перспективе этот процесс можно будет автоматизировать и отправку запроса на деплой добавить в общий пайплайн, сразу после загрузки обновлённого докер образа в докерхаб.

Соответственно получится настроить CI/CD: выпуск нового релиза через интерфейс гитхаба будет приводить к автоматическому деплою приложения.

Хостится бот в EC2-сервисе от Amazon. На данный момент мне достаточно инстанса общего назначения типа t3.small — это два виртуальных ЦПУ по 2,5 ГГц и 2 ГБ памяти. Ежемесячная плата составляет $20.

API​

В момент возникновения идеи создания бота я рассчитывал на использование API IMDB. Однако эти планы увенчались провалом, поскольку IMDB не предоставляет бесплатного API к своим данным. Тем не менее легальный способ получения информации о рейтинге фильмов и сериалов все же есть, он не очень удобен, и ниже я расскажу о нем подробней.

Основным же источником данных для моего бота является API TMDB — БД с информацией о фильмах, сериалах, актерах и многом другом. API полностью бесплатный, без ограничения количества запросов. Доступ к запросам открывается после получения персонального токена. API подробно документирован, единственной проблемой является отсутствие IMDB рейтинга. В TMDB существует свой рейтинг фильмов и сериалов, но он сильно не дотягивает до IMDB по причине меньшей популярности сервиса и, как следствие, гораздо меньшего количества голосов.

TMDB я использую для индексирования информации о сериалах. Когда пользователь хочет подписаться на новый сериал, он отправляет название сериала боту, бот проверяет наличие этого сериала в своей БД MySQL. В случае отсутствия информации бот отправляет запрос на API (в TMDB есть специальный эндпойнт поиска сериалов по названию). Еще есть возможность получить информацию о запланированных сериях. После успешной подписки на сериал бот сразу же добавляет в Redis-очередь отложенные задачи с отправкой напоминаний о выходе серий. Для дополнительной гарантии отправки напоминаний я сохраняю их в отдельную таблицу в MySQL и по крону проверяю наличие вовремя неотправленных напоминаний и отправляю их.

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

На карточке сериала я отображаю ссылку на трейлер в YouTube. Ранее я не сталкивался с их API и не знал, чего ожидать. Как оказалось, использование их API ограниченно суточной квотой в 10 000 юнитов. Каждый из запросов имеет свою стоимость. Так, каждый запрос на search API имеет стоимость в 100 юнитов (1 из самых дорогих запросов). Соответственно, по бесплатной квоте я могу выполнить максимум 100 запросов в день — этого недостаточно. У YouTube есть таблица стоимости каждого запроса YouTube Data API (v3) — Quota Calculator, с ее помощью вы как раз можете рассчитать, на что вам хватить бесплатной суточной квоты.

Этот способ не подошёл, на выручку снова пришел API TMDB — у них уже есть эндпойнт, который возвращает ID трейлера сериала в YouTube. При наличии ID видео в YouTube сформировать ссылку на видео очень просто, достаточно добавить его идентификатор в плейсхолдер www.youtube.com/watch?v={video_id</a>}.

Как же получить рейтинг сериала в IMDB? Для персонального и некоммерческого использования IMDB предоставляет разные наборы данных. В каждоми — сжатый gzip-файл в TSV-формате. Каждый из файлов содержит свой набор данных, рейтинги находятся в файле title.ratings.tsv.gz. Содержимое файлов систематически актуализируется.

Раз в неделю у меня срабатывает кронджоба, которая скачивает файл с рейтингами сериалов. Поиск рейтинга нужного сериала можно осуществить таким образом:

  1. Вытаскиваем IMDB ID из файла title.basics.tsv.gz по названию сериала: grep -i "Ted Lasso" title.basics.tsv | grep -i tvSeries | cut -f 1.
  2. Используя IMDB ID, получаем его рейтинг: grep -i "tt0944947" title.ratings.tsv | cut -f 2.
К счастью, TMDB предоставляет IMDB ID сериала, поэтому для получения рейтинга достаточно выполнить только второй шаг.

Продвижение и монетизация​

Ссылки на бота я разместил в различных каталогах ботов. Одни из них находятся в самом мессенджере, другие выполнены в виде сайтов. Из всех каталогов стоит выделить только BotList, с которого поступает стабильное и наибольшее количество новых подписчиков. Есть возможность платного размещения — $10 в месяц. Безусловно, это более эффективный способ, нежели органика, так как активность новых юзеров выше.

На данный момент в боте около 1000 подписчиков. Стоит упомянуть, что я поддерживаю три версии бота для разных языков: украинский, русский и английский. Английский бот насчитывает наибольшее количество подписок. Создание разных ботов было вызвано тем, что Telegram разрешает указать название/описание бота только на одном языке, но сейчас я задумываюсь над объединением ботов в один, с автоматическим определением локализации и возможностью ее изменения в настройках, поскольку гораздо легче продвигать одну версию.

Я вижу несколько моделей монетизации бота:

  1. Размещение реферальных ссылок на онлайн-кинотеатры вроде Megogo, Netlfix, Amazon, где подписчик сможет посмотреть сериал. У некоторых платформ неплохие вознаграждения за реферала. Вознаграждение за покупку подписки на сайте может засчитываться в вашу пользу в течении ~30 дней после перехода реферала по ссылке.
  2. Cписок платформ, на которых можно посмотреть сериал. Если бот сможет генерить достаточно большое количество лидов для онлайн кинотеатров, тогда, возможно, они станут заинтересованы в размещении своих кинотеатров в топе всего списка.
В обоих случаях, я считаю, нужно ещё улучшать и расширять функционал бота, чтобы он становился привлекательней и полезней своим пользователям.

Функционал и дальнейшие планы

Текущий функционал бота позволяет:

  • подписаться на сериал для получения напоминаний о выходе новых серий;
  • просматривать и управлять своими подписками на сериалы ⁃ искать сериалы по ключевым словам;
  • просматривать каталог доступных сериалов по заготовленным фильтрам: по обновлениям и популярности;
  • просматривать каталог сериалов по расширенным фильтрам, можно фильтровать по жанру, году выпуска, количеству голосов и применять сортировку.
После автоматизации напоминаний о выходе новых серий, я заметил за собой привычку просматривать онлайн-кинотеатры в поиске интересных новинок. И снова пришла идея вынести эту рутину в бота, для этого я добавил раздел просмотра каталога сериалов. Следующим этапом планирую добавить подписку на фильтры. Например, можно будет создать фильтр «Топ-20 сериалов, выпущенных с 2020 года с рейтингом от 7.0» и отсортированных по дате выпуска (от новых к старым). После создания такого фильтра будет возможность подписаться на обновления списка сериалов в этом фильтре, и как только в списке появится новый сериал, вам придет об этом уведомление.

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

Помимо прочего, в бэклоге скопился большой список задач. Из интересных:

  • добавление inline бота
  • возможность искать и подписываться на премьеры фильмов
  • рекомендация сериалов на основе ваших предпочтений
  • создание списков фильмов и сериалов с возможностью делится ими с другими подписчиками
  • добавление бота в другие мессенджеры
Помимо бизнес-задач есть и технические, например, добавление prometheus/grafana для мониторинга за состоянием бота и туда же вынести некоторые бизнес-метрики (количество новых пользователей/ подписок...). В планах — обновить фреймворк и версию PHP, добавить ELK-стек для наблюдения за логами.

С момента публикации бота, уже несколько раз в личку в Telegram писали его подписчики с запросами на добавление функционала. Так, одним из последних запросов было создание inline бота. Telegram позволяет добавлять таких ботов прямо в групповые каналы и основным требованием подписчика была возможность отправки напоминаний о выходе новых серий в его группы.

И конечно же, разрабатывается все это дело супер медленными шагами, поскольку заниматься ботом получается только на досуге. Не всегда есть желание проводить вечера за ноутбуком, но новые идеи мотивируют не бросать это дело и продолжать развивать проект. Как бы там ни было, но даже маленький, но систематический вклад приводит, пускай и не к большому, но рабочему результату.

Добавляю ссылки на 3 версии бота: на английском, украинском и русском.

Благодарю всех за внимание и желаю всем успешной разработки собственных проектов!

 
Сверху