Особенности разработки 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
    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
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 33

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

      Пока делал похожий сетап (pm2 с гитхуком на своём сервере вместо докера) столкнулся с очень странной проблемой: бот работает, если запускать HTTPS-сервер с сертификатом letsencrypt но НЕ передавать сертификат самому боту или же если использовать самоподписанный сертификат и передавать его и HTTPS-серверу и боту.
        +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.
          +2
          Спасибо, попробую
          0
          Какой хостинг вы использовали для бота?
            +1
            Vscale.io
              0
              Почему не бесплатный?
                0

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

                  +1
                    +1

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


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


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

                      0

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


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

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

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

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

                          0
                          По-моему в Heroku лимит есть только по ОЗУ 512*2 МБ…
                            +1
                            Это PaaS — там лимит со всех сторон.
                            PostgreSQL — Бесплатно, но только 10К строк, если хочешь нормальный PG — то +$9 в месяц.
                            Такая ж история с Redis.
                            Бесплатно 1 воркер с ограничением по CPU.
                            Для отладки, теста и обкатки идеи — решение отличное. Хотя есть компании, которые в нем живут и радуются.
                          +1
                          У хероку есть один плюс: они сами дают тебе рабочий домен в *.herokuapp.com и https с валидным сертификатом. То есть париться насчет SSL для подключения к телеграму вообще не надо.
                            0

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

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

                                    последующие

                                      0
                                      Это очень странно и долго.
                                      У меня так тупит на первом ответе только.
                                      Дальше очень быстро.
                            0
                            Если размер докер контейнера меньше 300 МБ, то Heroku подойдет, как сказали.
                              0
                              Еще кстати OpenShift с докером начинает работать, но там как то всё сложно.
                                0
                                Если PaaS и так поддерживает Node.js, то докер не нужен.
                                  0

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

                              0
                              Не нашел нормального. Позже поищу еще или в комментах подскажут.
                              AWS free tier у меня уже кончился.
                          +1
                          Оффтоп, а почему тематика бота такая выбрана? Вас не волнует что может набежать много человек и вам придется это все для них поддерживать? Это я к тому, что тоже имею бота с подпиской на ютуб, и там 1k человек, и теперь приходится с этим жить.
                            +1

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

                              0
                              У меня подписок на ~30 каналов. Изначально, я хотел просто получать уведомления в Telegram. Вашего бота я тоже начинал использовать, но чет вручную забивать эти 30 каналов мне стало влом. Да и откладывать просмотр хотелось. Вот и написал своего, попрактиковался в node.js.
                              И поддерживать согласен, по мере возможностей. даже развивать. Долго искал чтобы было еще и не накладно (финансово), но пока не получилось.
                              +1
                              Изначально бот не видит подписки с ограниченным доступом, в настройках канала надо временно включить показ информации о подписках. Вопрос — после закрытия информации о подписках увидит ли бот новые подписки?
                              При выводе списка подписок он ограничен 50 пунктами. Вопрос — бот отслеживает все подписки или только те, что в списке?
                                +1
                                1. Бот обновляет подписки пользователей раз в 2 часа, если вы подписались или отписались бот это увидит.
                                2. Бот обрабатывает пока что только первые 50 подписок.
                                  0
                                  1. Почему же бот не увидел подписки с ограниченным доступом в первый раз?
                                  2. -
                                  3. Было бы неплохо добавить команду с функционалом /logout, чтобы он сам удалялся отсюда: Приложения, связанные с аккаунтом.
                                  4. Каким образом на данный момент можно отказаться от уведомлений бота?
                                    0
                                    1. Не знаю
                                    3. Согласен, добавлю позже
                                    4. Выполнить команду /stop

                              Only users with full accounts can post comments. Log in, please.