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

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

На какие вопросы ответит статья:

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

Стэк:

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

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


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

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


Webhook — это адрес, на который Telegram будет отправлять команды и сообщения от пользователей, поэтому он должен быть внешний, а как тогда разрабатывать локально?

Тут приходят на помощь сервисы такие как: ngrok и Localtunnel (ссылка 1, ссылка 2).

Оба этих сервиса генерируют случайный домен 3 уровня. Если хочется статический, то в ngrok надо будет заплатить, а в Localtunnel — нет.

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

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

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


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

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

На 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). Сложности начинаются с передачей данных и отловом нажатия на эту кнопку.

Согласно документации InlineKeyboardButton максимальных размер данных всего 64 байта. В большинстве случаев этого хватает, просто учтите при разработке своего бота.

Дальше нужно эти данные обработать, все callback приходят в одну функцию, поэтому разбирать на какой тип кнопки нажали приходится в ней. А значит вместе с данными нужно еще и тип этот передавать. Напрашивается роутер. В Telegraf этот роутер уже частично реализовали — это класс Router. Его можно создать, но парсить команду и параметры нужно будет самому (пример). Разделитель параметров тоже нужно придумывать самому. На мой взгляд это прошлый век, но с этим можно жить.

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


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

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

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

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

Продакшн


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

Docker логгер


Разрабатывая на Node.js я писал ошибки и отладочные сообщения в консоль и когда развернул в docker их смотреть стало не удобно.

На помощь может прийти сервис Papertrail, он позволяет сохранять любые сообщения откуда угодно в том числе в формате syslog.

Чтобы передать все сообщения из всех контейнеров можно использовать образ gliderlabs/logspout. Настраивается он очень просто. Вот вырезка из 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

> Исходники на GitHub
Поделиться с друзьями
-->

Комментарии (29)


  1. Devgru
    08.01.2017 02:27
    +2

    Спасибо, хорошее саммари.

    Пока делал похожий сетап (pm2 с гитхуком на своём сервере вместо докера) столкнулся с очень странной проблемой: бот работает, если запускать HTTPS-сервер с сертификатом letsencrypt но НЕ передавать сертификат самому боту или же если использовать самоподписанный сертификат и передавать его и HTTPS-серверу и боту.


  1. aaaler
    08.01.2017 13:09
    +3

    Docker логгер

    у вас очень олдскульный метод описан, коллега.
    докер уже умеет писать логи почти куда угодно :


    json-file Default logging driver for Docker. Writes JSON messages to file.
    syslog Syslog logging driver for Docker. Writes log messages to syslog.
    journald Journald logging driver for Docker. Writes log messages to journald.
    gelf Graylog Extended Log Format (GELF) logging driver for Docker. Writes log messages to a GELF endpoint like Graylog or Logstash.
    fluentd Fluentd logging driver for Docker. Writes log messages to fluentd (forward input).
    awslogs Amazon CloudWatch Logs logging driver for Docker. Writes log messages to Amazon CloudWatch Logs.
    splunk Splunk logging driver for Docker. Writes log messages to splunk using HTTP Event Collector.
    etwlogs ETW logging driver for Docker on Windows. Writes log messages as ETW events.
    gcplogs Google Cloud Logging driver for Docker. Writes log messages to Google Cloud Logging.


    1. lomaster
      08.01.2017 13:32
      +2

      Спасибо, попробую


  1. Carburn
    08.01.2017 13:31

    Какой хостинг вы использовали для бота?


    1. lomaster
      08.01.2017 13:31
      +1

      Vscale.io


      1. Carburn
        08.01.2017 14:10

        Почему не бесплатный?


        1. Dreyk
          08.01.2017 16:20

          а есть бесплатный хостинг с поддержкой докера?


          1. ingrysty
            08.01.2017 17:34
            +1


            1. Dreyk
              08.01.2017 19:08
              +1

              AWS бесплатен только на год


              так что постоянного решения нет.


              поэтому я и удивляюсь вопросу "почему не на бесплатном хостинге?"


            1. Dreyk
              08.01.2017 21:38

              хотя Arukas заявляют, что


              No worries, we'll keep the free-plan below as it is even after Beta phase is ended.

              так что это может считаться


          1. el777
            08.01.2017 22:09
            +1

            Heroku.
            Правда поддержка Docker у них пока в бете. Но все остально работает давно, хорошо и надежно.
            Проблема с ними только 1 — как только выйдете за бесплатные лимиты, ценник станет конский.


            1. Dreyk
              09.01.2017 00:58

              о, спасибо, поковыряю


            1. Carburn
              09.01.2017 08:38

              По-моему в Heroku лимит есть только по ОЗУ 512*2 МБ…


              1. el777
                09.01.2017 16:33
                +1

                Это PaaS — там лимит со всех сторон.
                PostgreSQL — Бесплатно, но только 10К строк, если хочешь нормальный PG — то +$9 в месяц.
                Такая ж история с Redis.
                Бесплатно 1 воркер с ограничением по CPU.
                Для отладки, теста и обкатки идеи — решение отличное. Хотя есть компании, которые в нем живут и радуются.


          1. el777
            08.01.2017 22:11
            +1

            У хероку есть один плюс: они сами дают тебе рабочий домен в *.herokuapp.com и https с валидным сертификатом. То есть париться насчет SSL для подключения к телеграму вообще не надо.


            1. Dreyk
              09.01.2017 00:58

              у меня так и крутится бот телеграмовский на бесплатном хероку, только не в докере


              1. el777
                09.01.2017 16:35

                Аналогично. При первом запросе медленный ответ — пока просыпается. Потом быстро.


          1. Carburn
            09.01.2017 08:34

            Если размер докер контейнера меньше 300 МБ, то Heroku подойдет, как сказали.


          1. lomaster
            09.01.2017 09:09

            Еще кстати OpenShift с докером начинает работать, но там как то всё сложно.


            1. Carburn
              09.01.2017 16:03

              Если PaaS и так поддерживает Node.js, то докер не нужен.


              1. Dreyk
                10.01.2017 13:29

                докер нужен, как сказано в статье, для бістрого развертівания. Например, если хочешь переехать с openshift на heroku, то без докера — это займет какое-то время на настройку окружения и сервисов, а вот с докером это одна команда


        1. lomaster
          09.01.2017 09:11

          Не нашел нормального. Позже поищу еще или в комментах подскажут.
          AWS free tier у меня уже кончился.


  1. feverqwe
    08.01.2017 16:50
    +1

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


    1. Dreyk
      08.01.2017 16:56
      +1

      что плавно подводит нас к написанию статьи о монетизации ботов в телеграме))


    1. lomaster
      09.01.2017 09:06

      У меня подписок на ~30 каналов. Изначально, я хотел просто получать уведомления в Telegram. Вашего бота я тоже начинал использовать, но чет вручную забивать эти 30 каналов мне стало влом. Да и откладывать просмотр хотелось. Вот и написал своего, попрактиковался в node.js.
      И поддерживать согласен, по мере возможностей. даже развивать. Долго искал чтобы было еще и не накладно (финансово), но пока не получилось.


  1. Chilace
    09.01.2017 08:13
    +1

    Изначально бот не видит подписки с ограниченным доступом, в настройках канала надо временно включить показ информации о подписках. Вопрос — после закрытия информации о подписках увидит ли бот новые подписки?
    При выводе списка подписок он ограничен 50 пунктами. Вопрос — бот отслеживает все подписки или только те, что в списке?


    1. lomaster
      09.01.2017 09:08
      +1

      1. Бот обновляет подписки пользователей раз в 2 часа, если вы подписались или отписались бот это увидит.
      2. Бот обрабатывает пока что только первые 50 подписок.


      1. Chilace
        13.01.2017 09:51

        1. Почему же бот не увидел подписки с ограниченным доступом в первый раз?
        2. -
        3. Было бы неплохо добавить команду с функционалом /logout, чтобы он сам удалялся отсюда: Приложения, связанные с аккаунтом.
        4. Каким образом на данный момент можно отказаться от уведомлений бота?


        1. lomaster
          13.01.2017 10:04

          1. Не знаю
          3. Согласен, добавлю позже
          4. Выполнить команду /stop