Сообщить об ошибке.

Подключения Telegram-бота через webhook.

Использование Webhook с модулем python-telegram-bot.

Все примеры кода по пакету python-telegram-bot запускают Telegram бот с помощью Updater.start_polling(). Он использует метод Telegram API getUpdates для получения новых сообщений для бота. Это вполне нормально для небольших ботов и тестирования, но если бот популярен и получает/отправляет много трафика, то такой подход может замедлить время отклика бота.

Опрос Telegram сервера через webhook - это полезная технология для автоматизации процесса общения с пользователями. Как правило, этот функционал используется для экономии ресурсов на отправку/получение обновлений как собственного сервера, так и серверов Telegram.

Различие между polling и webhook является:

  • Опрос polling (через метод .get_updates) периодически подключается к серверам Telegram для проверки новых обновлений или отправки обработанных сообщений.
  • Webhook - это URL-адрес, который передается API Telegram. Каждый раз, когда приходит новое обновление для бота, сервер Telegram отправляет это обновление на указанный URL. Аналогично происходит отправка сообщений.

Содержание:


Что необходимо для подключения к Telegram через webhook.

  1. Публичный IP-адрес или домен. Обычно это означает, что запуск бота должен осуществляться на VPS сервере.
  2. Необходим SSL-сертификат.

    Вся связь с серверами Telegram должна быть зашифрована с помощью HTTPS с использованием SSL. В случае подключения polling, о шифровании трафика заботятся серверы Telegram, но если отправка/получение сообщений идет через Webhook, то шифровании должен заботиться клиент/бот.

    Есть два способа сделать это:

    • Подключить проверенный сертификат, выданный доверенным центром сертификации (CA)
    • Самостоятельно создать самоподписанный сертификат SSL. Это проще, и в этом нет никакого недостатка.

    Чтобы создать самоподписанный SSL-сертификат с помощью openssl, выполните следующую команду в терминале:

    $ openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -x509 -days 3650 -out cert.pem
    

    Утилита openssl запросит несколько подробностей. Необходимо убедится, что вы ввели правильное полное доменное имя или IP-адрес! Если у сервера есть домен, то введите полное доменное имя (например, sub.example.com). Если сервер имеет только IP-адрес, то вместо домена введите его IP-адрес. Если введено неверное полное доменное имя или IP-адрес, то бот не получит никаких обновлений от Telegram, при этом не будет никаких ошибок!

Встроенный HTTP-сервер для webhook.

Библиотека python-telegram-bot поставляет встроенный HTTP-сервер, основанный на http.server.HTTPServer. Реализация HTTPServer, которая плотно интегрирована в модуль расширения telegram.ext и может быть запущен с помощью Updater.start_webhook. Этот веб-сервер также заботится о расшифровке HTTPS-трафика. Это самый простой способ настроить webhook.

Однако у этого решения есть ограничение. Telegram в настоящее время поддерживает только четыре порта для веб-перехватчиков: 443, 80, 88 и 8443. В результате можно запускать не более четырех ботов на одном домене/IP-адресе.

Если это не проблема, то можно использовать код ниже или аналогичный, чтобы запустить бот с webhook. Адрес прослушивания должен быть либо '0.0.0.0', либо, если нет разрешения на это, общедоступный IP-адрес сервера. Порт может быть одним из 443, 80, 88 или 8443. Для url_path рекомендуется использовать токен бота, чтобы никто не мог отправлять поддельные обновления боту. Ключ и сертификат должны содержать путь к файлам, которые создали ранее. Аргумент webhook_url должен быть фактическим URL-адресом webhook. При подстановке полного URL-адреса используйте протокол https://, указывайте домен или IP-адрес, который был установлен в качестве полного доменного имени изготовленного сертификата, а также правильный порт и URL-путь.

updater.start_webhook(listen='0.0.0.0',
                      port=8443,
                      url_path='TOKEN',
                      key='private.key',
                      cert='cert.pem',
                      webhook_url='https://example.com:8443/TOKEN')

Обратный прокси-сервер + встроенный сервер webhook.

Чтобы решить эту проблему, можно использовать обратный прокси-сервер, такой как nginx или haproxy, а также можно использовать Heroku.

В этой модели обратный прокси (nginx), слушает публичный IP-адрес, принимает все запросы webhook и пересылает их на правильный экземпляр локально запущенных встроенных в python-telegram-bot серверов webhook. Обратный прокси также выполняет завершение SSL, то есть расшифровывает HTTPS-соединение, поэтому серверы webhook получают уже расшифрованный трафик. Эти серверы могут работать на любом порту, а не только на четырех разрешенных Telegram портах, т.к. сервера Telegram напрямую подключается только к обратному прокси-серверу.

В зависимости от того, какой прокси-сервер используется, реализация будет выглядеть немного иначе. Ниже перечислены несколько возможных настроек.

Использование webhook на Heroku.

На Heroku использовать webhook можно на свободном плане, т.к. он будет автоматически управлять временем простоя. Для пользователя Heroku будет настроен обратный прокси и создана среда исполнения. Из этой среды необходимо будет извлечь порт, который бот должен прослушивать. Heroku управляет SSL на стороне прокси-сервера, следовательно не нужно создавать сертификат самостоятельно.

import os

TOKEN = "TOKEN"
PORT = int(os.environ.get('PORT', '8443'))
updater = Updater(TOKEN)
# add handlers
updater.start_webhook(listen="0.0.0.0",
                      port=PORT,
                      url_path=TOKEN,
                      webhook_url="https://<appname>.herokuapp.com/" + TOKEN)
updater.idle()

Использование nginx с одним доменом/портом для всех ботов

Все боты устанавливают свой URL-адрес на один и тот же домен и порт, но с другим url_path. Встроенный в python-telegram-bot сервер обычно запускается по адресу localhost или 127.0.0.1, порт может быть любым.

Примечание: если нет домена, связанного с сервером, то example.com может быть заменен IP-адресом.

Пример кода для запуска бота:

updater.start_webhook(
    listen='127.0.0.1',
    port=5000,
    url_path='TOKEN1',
    webhook_url='https://example.com/TOKEN1',
    cert=open('cert.pem', 'rb')
)

Пример конфигурации для nginx с двумя настроенными ботами (представлены важные части конфига):

server {
    listen              443 ssl;
    server_name         example.com;
    ssl_certificate     cert.pem;
    ssl_certificate_key private.key;

    location /TOKEN1 {
        proxy_pass http://127.0.0.1:5000/TOKEN1/;
    }

    location /TOKEN2 {
        proxy_pass http://127.0.0.1:5001/TOKEN2/;
    }
}

Использование haproxy с одним поддоменом на бота.

При таком подходе, каждому боту присваивается свой собственный поддомен. Если сервер имеет домен example.com, то можно создать поддомены например: bot1.example.com, bot2.example.com и т. д. Понадобится один сертификат для каждого бота с полным доменным именем, установленным для соответствующего поддомена. Встроенный в python-telegram-bot сервер обычно запускается по адресу localhost или 127.0.0.1, порт может быть любым.

Примечание: Необходимо иметь домен привязанный к IP-адресу сервера.

Пример кода для запуска бота:

updater.start_webhook(
    listen='127.0.0.1',
    port=5000,
    url_path='TOKEN',
    webhook_url='https://bot1.example.com/TOKEN,
    cert=open('cert_bot1.pem', 'rb')
)

Пример конфигурации для haproxy с двумя настроенными ботами (сведен к важным частям конфига) . Опять же: полное доменное имя обоих сертификатов должно соответствовать значению в ssl_fc_sni. Кроме того, файлы .pem представляют собой объединенные файлы private.key и cert.pem.

frontend  public-https
    bind        0.0.0.0:443 ssl crt cert_key_bot1.pem crt cert_key_bot2.pem
    option      httpclose

    use_backend bot1 if  { ssl_fc_sni bot1.example.com }
    use_backend bot2 if  { ssl_fc_sni bot2.example.com }

backend bot1
    mode            http
    option          redispatch
    server          bot1.example.com 127.0.0.1:5000 check inter 1000

backend bot2
    mode            http
    option          redispatch
    server

Индивидуальное решение, построенное на потоках.

Не обязательно использовать встроенный веб-сервер. Если решите пойти этим путем, то не следует использовать класс Updater. Модульрасширения telegram.ext был разработан с учетом этой опции, следовательно можно использовать класс Dispatcher для получения/отправки и фильтрации/сортировки сообщений. Правда, кое-что придется делать вручную.

Часть настройки, вызванная один раз:

from queue import Queue
from threading import Thread

from telegram import Bot
from telegram.ext import Dispatcher

def setup(token):
    # Создание экземпляров бота, 
    # очереди обновления и диспетчера
    bot = Bot(token)
    update_queue = Queue()
    
    dispatcher = Dispatcher(bot, update_queue)
    
    ##### Регистрируем обработчики здесь #####
    
    
    # Запуск потоков
    thread = Thread(target=dispatcher.start, name='dispatcher')
    thread.start()
    
    return update_queue
    # возможно, надо будет вернуть диспетчера, чтобы остановить его
    # при выключении сервера или зарегистрировать больше обработчиков:
    # return (update_queue, dispatcher)

Вызывается на webhook с расшифрованым объектом обновления (используйте Update.de_json(json.loads(text), bot) для декодирования сообщений):

def webhook(update):
    update_queue.put(update)

Альтернативное решение, без потоков.

Часть настройки, вызываемая один раз:

from telegram import Bot
from telegram.ext import Dispatcher

def setup(token):
    # Создание бота, очереди обновлений 
    # и экземпляров диспетчера
    bot = Bot(token)
    
    dispatcher = Dispatcher(bot, None, workers=0)
    
    ##### Регистрируем обработчики здесь #####
    
    return dispatcher

Вызывается на webhook с расшифрованым объектом обновления (используйте Update.de_json(json.loads(text), bot) для декодирования сообщений):

def webhook(update):
    dispatcher.process_update(update)