Как появилась идея? Что вдохновило?
Всем привет. Пару месяцев назад Slack официально ушел из России и поэтому мы быстренько переехали на self-hosted Mattermost.
Не знаю, что меня на это сподвигнуло, но я решил, что нужно написать срочно бота для Telegram, который будет присылать уведомления о созданных тредах в Mattermost по всем каналам в наш рабочий чат. Спустя неделю появилась мысль добавить уведомление о том, что кого-то их моих коллег отметили в любом треде в Mattermost, и еще спустя неделю - возможность отправлять ответы из Telegram, а конкретно те же mention.
У нас классическая техническая поддержка с заявками, только в качестве тредов наши выездные инженеры указывают номера объектов, например, 23-123, где первые 2 цифры - это номер региона, например 23 - Краснодарский край, а еще имена тредов могут повторяться, так как инженер может приехать на один и тот же объект несколько раз в рамках одного канала.
Теперь при создании нового треда мы получаем в рабочем чате следующее уведомление:
Создан тред 23-123 (Краснодарский край) на канале Тестовый
obj=13hmsesemtrh3xbs38ju8765dh
Что вошло в прототип и сколько времени на него было потрачено?
Первый прототип - без отправки mention из Telegram бота в Mattermost был готов думаю через 3-5 дней после появления идеи. У меня есть основная работа и это в трудовые обязанности не входит, поэтому писал код только в свободное от работы время, коллеги поддерживали морально.
Оставшуюся часть пробовал в течении 3-4 недель по часу-два пару раз в неделю, но лишь позавчера решил сесть и переписать всё под сперва вебхуки, и затем под getUpdates, заняло еще около 12 часов, если суммировать последние пару дней разработки.
Какой технологический стек вы использовали? Почему?
Стэк: Flask, Python, Sqlite, PyTelegramBotAPI aka Telebot, gunicorn, systemd, nginx, certbot
Для выявления имен объектов используется паттерн:
pattern = r'\d{2}-\d{3,5}'
obj=<post_id> на второй строчке нужны, чтобы Mattermost, получая от Telegram Bot POST-запрос знал к какому Root_id отправлять реплаи, ответы на mention - это нужно, как я ранее написал, потому что названия тредов могут повторяться в рамках одного канала.
Примечание: postid треда = rootid для записей внутри треда.
Если треды уникальны - можно обойтись без этого параметра, а также, в файле main.py имеется соответствующая булева переменная: uniquethreadnames = False, где False - имена тредов могут повторяться, а True - имена тредов 100% уникальны в рамках канала.
С какими самыми неожиданными трудностями пришлось столкнуться?
Так как бот работает на Flask, то я не смог подружить Flask и удобный всем bot.polling(), так как при запуске через wsgi (я использовал связку gunicorn+systemd+nginx+certbot) запускается только Flask, а bot.polling запустится, если завершить процесс Flask.
Я пробовал запускать bot.polling() в отдельном потоке через threads, тестируя через ngrok, и было любопытно, что так всё работало, а когда загружал исходники на сервере на VPS, то там это не работало и судя по отсутствию логов я понимал, что bot.polling() в отдельном потоке не запускался.
Я, также, пробовал использовать Вебхуки из примеров либы PyTelegramBotAPI, а также, переделанный вариант без указания порта и путей до SSL-ключей.
Последний вариант успешно запускался, также, на ngrok, но на рабочем сервере VPS я получал бесконечные 429 Too Many Requests, которые я пытался решить через time.sleep(5) где только мог, но это не помогало.
Наконец, добрался до крайнего варианта - использования Telegram getUpdates через POST-запросы, с установкой статичного вебхука для конкретного узла (в данном случае '/') и на удивление это заработало даже на рабочем сервере на VPS, так это выглядит:
@app.route('/', methods=['POST'])
def handle_update():
if request.method == 'POST':
r = request.get_json()
if 'message' in r and 'text' in r['message']:
message_api = r['message']['text']
if 'reply_to_message' in r['message']:
if r['message']['reply_to_message']:
reply_text = r['message']['reply_to_message']['text']
Для отслеживания событий, когда кто-то отметил вас в треде Mattermost или в случаях создания нового треда, используется следующий вебхук:
@app.route('/callback', methods=['POST'])
Который получает POST-запросы от Mattermost.
Для создания Исходящих Вебхуков, нужно зайти в Интеграции > Исходящие вебхуки.
Далее указываем любое название, выбираем тип: application/json, выбираем канал и указываем ключевые слова, по которым вебхук должен отправлять нам запросы.
Для mention Триггер: Когда первое слово соответствует слову события полностью и указываем свой @username в Mattermost
Для уведомления о создании тредов: Первое слово начинается со слова триггера и указываем слова, в данном случае: 23-,01-,05- и т.д.
Для отправки запросов на сервер Mattermost для отправки ответных сообщений из Telegram Bot, идем в Интеграции > Входящие вебхуки и создаем один вебхук, указывая имя, описание и любой канал; снимаем галочку Прикрепить к этому каналу, что позволит работать этому вебхуку во всех каналах.
Отправка сообщений в бот Telegram происходит обычным bot.sendmessage
Работа бота:
Какой совет вы бы сами могли дать идущим по вашим стопам?
ChatGPT реально помогает в разработке, но еще не способен на 100% заменить живого разработчика.
У вас всё получится, надо лишь начать.
Полагаю, что данный бот можно переделать под себя, изменив паттерны для входящих запросов (Исходящие вебхуки) как на стороне Mattermost, так и на стороне кода.
При первом старте бота, создается база данных sqlite с двумя таблицами; для корректной работы бота их нужно заполнить:
Для таблицы mention_mapping:
trigger_word - @username в Mattermost, tgusername - @username в Telegram
Таблица mask_regions - для данного примера, но вы можете создать свою. Здесь: mask - код региона (23-), region - название региона (Краснодарский Край).
Исходный код в открытом доступе на GitHub