- PVSM.RU - https://www.pvsm.ru -

Особенности разработки Telegram бота с Google API в Docker

Коротко о боте: получает список YouTube-каналов пользователя и уведомляет о новых видео с возможностью напомнить о нем позже.

В статье расскажу об особенностях написания этого бота и взаимодействия с Google API. Я люблю краткость, поэтому в статье будет мало «воды».
На какие вопросы ответит статья:

  • Где взять внешний адрес сайта для Webhook
  • Где взять HTTPS-сертификат как его использовать, чтобы Telegram ему доверял
  • Как передавать данные и обрабатывать нажатия на Inline-кнопки
  • Как получить вечный OAuth токен для Google API
  • Как передать данные пользователя через OAuth callback url
  • Как получить бесплатный домен 3 уровня

Стэк:

  1. Back-end: Node.js + Express.js [1]
  2. БД: Mongo.js + mongoose [2]
  3. Пакетный менеджер: Yarn [3] (он действительно быстрый)
  4. Telegram-бот фреймворк: Telegraf [4]
  5. Продакшн: Docker + Docker Compose + Vscale.io

Особенности при разработке бота

Получать команды от Telegram можно с помощью Long-polling и Webhook. Судя по отзывам в интернете Long-polling через некоторое время перестает работать — Telegram возвращает 500 ошибку, поэтому я решил сразу делать через Webhook.

Нужен внешний адрес сайта для Webhook

Webhook — это адрес, на который Telegram будет отправлять команды и сообщения от пользователей, поэтому он должен быть внешний, а как тогда разрабатывать локально?
Тут приходят на помощь сервисы такие как: ngrok [5] и Localtunnel [6] (ссылка 1 [7], ссылка 2 [8]).

Оба этих сервиса генерируют случайный домен 3 уровня. Если хочется статический, то в ngrok надо будет заплатить, а в Localtunnel — нет.
Мне нужно было формировать OAuth Callback Url, который привязывался к идентификатору клиента OAuth 2.0 в Google API, поэтому удобнее если он будет статический. По этой причине я использовал именно Localtunnel.

Оба этих сервиса предоставляют HTTPS с нормальным валидным сертификатом, поэтому проблем с Telegram не будет.

В продакшене нужен будет HTTPS

Telegram позволяет использовать самоподписанные сертификаты. Инструкция есть [9] на их официальном сайте. Но тогда браузеры не будут доверять ему, а веть этот же сертификат будет использоваться для OAuth Callback Url, поэтому нужен был валидный сертификат.
На помощь приходит Let’s Encrypt [10].

Сгенерировать сертификат не проблема в интернете полно инструкций. Единственное, на сколько я понял, его надо генерировать на сервере где он будет использоваться (поправьте, если это не так).
На ubunte я воспользовался пакетом letsencrypt и выполнил команду.

letsencrypt certonly -n -d domain1.com -d domain2.ru --email admin@domain.ru --standalone --noninteractive --agree-tos

Какой сертификат и как его передать Telegram

Для работы Webhook Telegram нужно передать сертификат УЦ, чтобы Telegram начал ему доверять.
В случае с самоподписанным сертификатом — это нужно делать обязательно и передать нужно открытый ключ.

В случае с Let’s Encrypt ничего передавать не нужно, но нужно правильно настроить HTTPS на веб-сервере.
Let’s Encrypt сгенерирует 4 сертификата:

  • cert.pem — открытый ключ
  • chain.pem — сертификат УЦ
  • fullchain.pem — открытый ключ + сертификат УЦ
  • privkey.pem — закрытый ключ

Именно privkey.pem+fullchain.pem нужны для HTTPS, если вы используете HAProxy (скорее всего и для других нужно настраивать аналогично), чтобы Telegram начал доверять нашему боту.

Передать этот сертификат через Telegraf можно следующим образом:

let cert = { source: '/path/public.pem' };
app.telegram.setWebhook(config.webHookUrl + '/' + config.webHookSecretPath, cert);

Передача данных при нажатии Inline-кнопок

Отправить сообщение с кнопкой не проблема (можно использовать Telegraf Markup & Extra [11]). Сложности начинаются с передачей данных и отловом нажатия на эту кнопку.
Согласно документации InlineKeyboardButton [12] максимальных размер данных всего 64 байта. В большинстве случаев этого хватает, просто учтите при разработке своего бота.
Дальше нужно эти данные обработать, все callback приходят в одну функцию, поэтому разбирать на какой тип кнопки нажали приходится в ней. А значит вместе с данными нужно еще и тип этот передавать. Напрашивается роутер. В Telegraf этот роутер уже частично реализовали — это класс Router [13]. Его можно создать, но парсить команду и параметры нужно будет самому (пример [14]). Разделитель параметров тоже нужно придумывать самому. На мой взгляд это прошлый век, но с этим можно жить.

Взаимодействие с Google API

Боту нужно получать список каналов пользователя, а для этого нужен токен. Вот так вы сможете его получить:

  1. Создаем проект через Google Developers Console [15]
  2. Выбираем нужный API. В моем случае это Youtube Data API
  3. Создаем идентификатор клиента OAuth 2.0. В поле «Разрешенные URI перенаправления» можно указать localhost с нужным портом
  4. В результате нам дадут ClientId и ClientSecret
  5. Ставим Google APIs Node.js Client [16]
  6. Генерируем на основе этой пары ссылку для пользователя с помощью метода googleapis.auth.OAuth2.generateAuthUrl. Об этом подробно и с примерами написано в описании пакета [17]
  7. После того как пользователь перейдет по ссылке и даст разрешение, мы получим access-токен

Почему access-токен живет только 1 час

По идее Google с access_type: «offline» должен предоставлять refresh-токен, но так просто он этого не сделает. Мне надо было обновлять список каналов пользователя в фоне, поэтому погуглив я нашел такой вариант: в метод generateAuthUrl передать опцию approval_prompt: 'force'. Тогда Google будет запрашивать у пользователя автономный доступ к аккаунту и, если пользователь согласится, то даст нам нужный refresh-токен. С помощью него мы в любой момент времени сможем обновлять access-токен и получать список каналов.

Как передать данные пользователя через callback url?

Для этого в метод generateAuthUrl можно передать опцию state. Передавать можно только строку, поэтому все объекты нужно сериализовать/кодировать/шифровать.
По переданным данным можно сохранить токен в БД и потом получать его уже по ИД пользователя.

Google не даст токен если callback url это IP

Тут есть 2 пути: купить домен [18] или попробовать зарегистрировать бесплатный.

Сначала я искал бесплатный, поэтому поделюсь с вами ссылкой на один из таких сервисов 4nmv [19] — он дает домен 3 уровня бесплатно и позволяет настроить для его любые ns-записи. Наверняка есть и другие сервисы (поделитесь ссылочками, пожалуйста), но меня устроил и этот.

Но потом я всё же купил домен 2 уровня .com за 69 руб в GoDaddy для солидности.

Продакшн

В продакшне я использую Docker и Docker Compose. Они позволяют быстро поднимать и обновлять бота на разных хостингах (если это будет необходимо).

Docker логгер

Разрабатывая на Node.js я писал ошибки и отладочные сообщения в консоль и когда развернул в docker их смотреть стало не удобно.
На помощь может прийти сервис Papertrail [20], он позволяет сохранять любые сообщения откуда угодно в том числе в формате syslog.
Чтобы передать все сообщения из всех контейнеров можно использовать образ gliderlabs/logspout [21]. Настраивается он очень просто. Вот вырезка из docker-compose.yml

  logger:
    image: gliderlabs/logspout:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: 'syslog+tls://logsX.papertrailapp.com:PORT'
    restart: always

Как запустить Yarn в Docker-контейнере

...
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
RUN $HOME/.yarn/bin/yarn install
...

Результат

Бот запущен в конце декабря 2016 года и доступен по имени @youtube_subs_watcher_bot [22]
Исходники на GitHub [23]

Автор: lomaster

Источник [24]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/230888

Ссылки в тексте:

[1] Express.js: http://expressjs.com/

[2] mongoose: http://mongoosejs.com/

[3] Yarn: https://yarnpkg.com/

[4] Telegraf: http://telegraf.js.org/

[5] ngrok: https://ngrok.com/

[6] Localtunnel: https://localtunnel.github.io/www/

[7] ссылка 1: https://habrahabr.ru/post/104048/

[8] ссылка 2: https://habrahabr.ru/post/117875/

[9] Инструкция есть: https://core.telegram.org/bots/self-signed

[10] Let’s Encrypt: https://letsencrypt.org/

[11] Telegraf Markup & Extra: http://telegraf.js.org/extra.html

[12] InlineKeyboardButton: https://core.telegram.org/bots/api#inlinekeyboardbutton

[13] Router: http://telegraf.js.org/advanced.html#router

[14] пример: https://github.com/lomaster1/youtube-subscription-watcher-telegram-bot/blob/master/bot/router.js

[15] Google Developers Console: https://console.developers.google.com/apis/

[16] Google APIs Node.js Client: https://www.npmjs.com/package/googleapis

[17] пакета: https://www.npmjs.com/package/googleapis#generating-an-authentication-url

[18] купить домен: https://www.reg.ru/?rlink=reflink-717

[19] 4nmv: https://4nmv.ru/

[20] Papertrail: https://papertrailapp.com/

[21] gliderlabs/logspout: https://github.com/gliderlabs/logspout

[22] @youtube_subs_watcher_bot: https://telegram.me/youtube_subs_watcher_bot

[23] Исходники на GitHub: https://github.com/lomaster1/youtube-subscription-watcher-telegram-bot

[24] Источник: https://habrahabr.ru/post/319016/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best