Как наши студенты создали MVP: инструкция по сбору и отправке сообщений из Telegram в Slack

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

Решение не претендует на эксклюзивность и эталонность и будет интересно тем, кто только задумался над тем, как транслировать данные между двумя мессенджерами.

Отметим, что несмотря на свою простоту и минималистичность, приложение без сбоев работает больше 8 месяцев и делает ровно то, что от него требовалось.

Задача

У Яндекс.Практикума есть сообщество выпускников в слаке. Одна из важных задач сообщества в том, чтобы регулярно оповещать участников о разных IT-мероприятиях ― хакатонах, конференциях, митапах и т.д.

Сначала эту задачу вручную выполняли админы сообщества. Затем, в конце 2020 года, мы решили попробовать автоматизировать её.

Чтобы совместить полезное (высвободить ресурс админов) с ещё более полезным (превратить выполнение боевой задачи в учебный проект), основной фронт работы по проектированию программы и написанию кода был отдан студентам бэкенд-факультета. Наставником на проекте был один из членов команды Практикума, сам в свое время отучившийся на бэкенд-факультете.

В общих чертах задача выглядела так:

  • дан список телеграм-каналов, в которых публикуют, в том числе, сообщения об IT-мероприятиях;
  • есть универсальные каналы (постят сообщения для широкой IT-аудитории), есть специализированные каналы для конкретного IT-направления, например Data Science;
  • нужно в режиме 24/7 собирать только релевантную информацию из этих каналов и ежедневно в 12.00 постить её в сообществе выпускников в слаке;
  • поскольку у каждого факультета практикума есть свой отдельный канал внутри сообщества, сообщения должны отправляться в соответствующий канал;
  • исключение ― сообщения о хакатонах и прочих соревнованиях ― независимо от тематики мероприятия сообщения должны отправляться в один канал.

Инструменты

  1. Язык разработки ― Python.
  2. Стандартные библиотеки:
  • datetime ― для работы с датой и временем публикации сообщений в ТГ-каналах;
  • json ― запись релевантных сообщений из ТГ-каналов в json-файл и чтение из него при отправке сообщений в сообщество выпускников;
  • re ― фильтрация текстов сообщений в ТГ-каналах через регулярные выражения;

3. Сторонние библиотеки:

  • Pyrogram ― ведущая библиотека проекта, используем для работы с Telegram Client API;
  • requests ― для отправки в слак POST-запросов с сообщениями о мероприятиях.

4. Развертывание и запуск приложения ― Docker.

5. Удалённый сервер ― виртуальная машина на Ubuntu в Yandex.Cloud.

Начинаем разработку: Telegram-клиент

Ядро нашего приложения ― Telegram-клиент, который в режиме 24/7 слушает интересующие каналы.

В отличие от бота, который не добавишь в публичные ТГ-каналы, клиент является клоном пользователя и все действия совершает от его имени. Пользователю достаточно вступить в канал, тогда клиент сможет фиксировать всё происходящее в канале.

Что нужно:

  1. На сайте telegram зарегистрировать приложение, получить API ID и API hash.
  2. С помощью модуля Client библиотеки pyrogram создать клиента.
Создание Telegram-клиента
Создание Telegram-клиента

Собираем исходные данные

В отдельный файл мы собрали следующую информацию:

  1. ID всех интересующих ТГ-каналов.
  2. Списки каналов по темам, список универсальных каналов, список всех каналов.
  3. ID всех каналов в слаке, куда будут перенаправляться сообщения из ТГ-каналов.
  4. Стоп-слова, отсекающие рекламный контент.

Ключевые слова, по которым клиент будет отбирать релевантные сообщения из каналов. Например, 'collaborations': r'хакатон|соревн|олимпиад|отборочный тур'.

Настраиваем слушателя событий

Слушателя событий в Pyrogram можно объявить с помощью декоратора. Один из них ― on_message ― позволяет перехватывать сообщения.

Разумеется, нам не нужно, чтобы наше приложение реагировало вообще на абсолютно все сообщения, которые проходят в аккаунте юзера, от имени которого работает клиент.

Гибко настроить то, на какие сообщения реагировать, помогают многочисленные фильтры Pyrogram. Фильтры можно комбинировать.

Для нашего приложения мы подобрали следующую комбинацию фильтров:

Комбинация фильтров
Комбинация фильтров

Разберём, что означает этот комбинированный фильтр.

  1. Фильтр chat указывает, что клиент реагирует на сообщения не во всех чатах, группах, каналах подряд, а только в списке определенных каналов, который мы обозначили переменной ALL_CHANNELS.
  2. Фильтр edited реагирует на сообщения, которые редактировали после создания. С помощью оператора ~ мы инвертируем этот фильтр, т.е. клиент не будет реагировать на отредактированные сообщения. Главным образом, этот фильтр появился из-за сообщений с кнопками для голосования. Каждое голосование порождало у сообщения статус «отредактированное», а значит, оно могло неопределенное количество раз улавливаться слушателем.
  3. С точки зрения оформления интересующие нас сообщения в ТГ-каналах можно разделить на две группы: классические текстовые сообщения и сообщения в виде картинки, под которой расположен текст. Второй вид сообщений не являются текстовыми, их тип caption. Поэтому фильтр filters.text | filters.caption ловит и те, и другие сообщения (оператор | означает логическое ИЛИ).
  4. Содержание сообщений мы фильтруем по регулярным выражениям, для этого предназначен фильтр regex. Мы используем его дважды ― чтобы отловить сообщения с подходящим содержанием и чтобы отсечь сообщения с неподходящим содержанием.

Таким образом, по результатам обработки перехваченного сообщения у нас есть:

  1. Список из одной или нескольких тем, к которым относится сообщение.
  2. Словарь с данными о сообщении.

Заключительный шаг - записать полученную информацию. На уровне MVP мы не стали создавать базу данных, тем более какая-то аналитика по истории сообщений в общем-то и не нужна. Решили записывать сообщения в json-файл. Изначально он выглядит так:

{

"QA_events": [],

"DS_events": [],

"backend_events": [],

"frontend_events": [],

"another_events": [],

"collaborations": []

}

Названия ключей в json полностью совпадают с названиями тем, к которым может быть отнесено сообщение. Мы запускаем цикл по списку тем, который сформирован для сообщения, и записываем данные в массив по соответствующему ключу в json-файле.

После отправки сообщений из json-файла списки по ключам очищаются.

Настраиваем прием сообщений от telegram-клиента в Slack

Чтобы сообщения попадали в сообщество выпускников в слаке, нужно настроить “принимающую сторону” - слак-бота, который будет постить сообщения. Для этого необходимо:

  1. Создать приложение https://api.slack.com/apps?new_app=1

Достаточно задать имя и указать workspace, в котором будет жить приложение.

  1. В разделе Add features and functionality настроек приложения выбрать Bot, т.е. наше приложение будет ботом.
  2. В разделе Install your app настроек приложения нажать Install to Workspace и установить приложение в workspace. Для бесплатного тарифного плана слака есть ограничение - не более 10 приложений.
  3. Ознакомиться с доступными API-методами https://api.slack.com/methods и выбрать нужные. Например, для отправки ботом сообщений в канал понадобится метод chat.postMessage.
  4. Посмотреть в описании метода, какие разрешения (required scopes) нужны для того, чтобы бот смог его использовать. Например, для использования метода chat.postMessage нужно разрешение chat:write.
  5. Наделить бота необходимым разрешением.

Для этого в настройках приложения (все приложения воркспейса доступны тут https://api.slack.com/apps) нужно:

  • выбрать раздел OAuth & Permissions
  • далее Bot Token Scopes
  • нажать Add an OAuth Scope и выбрать нужное разрешение

В этом же разделе доступен токен бота - Bot User OAuth Access Token.

Программируем отправку сообщений в Slack

Логика отправки сообщений в слак достаточно проста.

Все каналы-получатели объединяются в словарь, ключами в котором являются темы. Нетрудно заметить, что эти ключи совпадают с ключами в json-файле.

SLACK_CHANNELS = {

'QA_events': 'C0172LTCDR8',

'DS_events': 'C01714KE2CF',

'backend_events': 'C01BPFF6CHJ',

'frontend_events': 'C01F01MKGER',

'another_events': 'G01H3EM2HCN',

'collaborations': 'C018H32RTCN',

}

По этим ключам запускаем цикл и на каждой итерации мы смотрим, есть ли что-то по этому ключу в json-файле. Если есть, то запускается вложенный цикл, в рамках которого каждое сообщение по ключу отправляется в соответствующий тематический канал в slack.

Непосредственно отправка сообщения в слак реализуется с помощью POST-запроса со следующими характеристиками:

  1. url - https://slack.com/api/chat.postMessage
  2. в заголовке запроса передаем токен бота
  3. в json передаем данные о сообщении.

Запускаем приложение на сервере

На удаленном сервере в Яндекс.Облаке мы обернули наше приложение в докер-контейнер и запустили в нем скрипт с телеграм-клиентом. Благодаря pyrogram-методу run телеграм-клиент работает до тех пор, пока не будет принудительно остановлен.

Вопрос с тем, как ежедневно в 12.00 “дергать” скрипт, отвечающий за извлечение сообщений из json-файла и их отправку в слак, решили просто - запрограммировали задачу для линукс-планировщика cron.

Программирование задачи
Программирование задачи

Каждый день в 12.00 он из докер-контейнера alumni_client запускает python-скрипт sender.py, который и отправляет сообщения в слак.

Вот как выглядит сообщение в мессенджере:

Как наши студенты создали MVP: инструкция по сбору и отправке сообщений из Telegram в Slack, image #4
3394 views·13 shares