Запускаем несколько ботов на одной машине: CherryPy only

По моему мнению (а оно может не совпадать с вашим), боты на вебхуках надёжнее ботов, использующих поллинг. Связано это в первую очередь с моими воспоминаниями из августа-сентября 2015 года, когда каждую ночь падали боты из-за Gateway Timeout'ов. Затем в Bot API добавили поддержку самоподписанных сертификатов и я начал использовать их, благо это бесплатно.

В уроке номер четыре я писал, что т.к. портов для использования вебхуков всего 4, то, казалось бы, можно на одной машине запустить всего четырёх ботов, и что есть решение этой проблемы. Вот об этом сейчас и пойдет речь. Я планирую разбить материал на две части: в первой мы научимся запускать сколько угодно ботов при помощи одних лишь серверов CherryPy и самоподписанных сертификатов, во второй же вместо основного сервера поставим nginx, а вместо самоподписанных сертификатов - бесплатные от Let's Encrypt.

Общая схема взаимодействия

Общая схема взаимодействия

Подготавливаем "роутер"

Давайте для начала создадим наш "роутер", то есть, сервер CherryPy, который будет принимать все сообщения и раскидывать их по нужным ботам. Условимся также, что наш сервер будет иметь IP 122.122.122.122 и вебхуки от первого бота будут приходить на адрес https://122.122.122.122/AAAA, а от второго на https://122.122.122.122/ZZZZ. Предполагается, что вы уже прочитали 4-й урок и структура вебхук-ботов вас не пугает.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cherrypy
import requests
import telebot
WEBHOOK_HOST = 'Здесь.Ваш.IP.Адрес'
WEBHOOK_PORT = 443 # 443, 80, 88 или 8443
WEBHOOK_LISTEN = '0.0.0.0' # Слушаем отовсюду
WEBHOOK_SSL_CERT = 'webhook_cert.pem' # Путь к сертификату
WEBHOOK_SSL_PRIV = 'webhook_pkey.pem' # Путь к закрытому ключу
WEBHOOK_URL_BASE = "https://{!s}:{!s}".format(WEBHOOK_HOST, WEBHOOK_PORT)
BOT_1_TOKEN = "Токен первого бота"
BOT_2_TOKEN = "Токен второго бота"
# Вводим здесь IP-адреса и порты, куда перенаправлять входящие запросы.
# Т.к. всё на одной машине, то используем локалхост + какие-нибудь свободные порты.
# https в данном случае не нужен, шифровать незачем.
BOT_1_ADDRESS = "http://127.0.0.1:7771"
BOT_2_ADDRESS = "http://127.0.0.1:7772"
bot_1 = telebot.TeleBot(BOT_1_TOKEN)
bot_2 = telebot.TeleBot(BOT_2_TOKEN)
# Описываем наш сервер
class WebhookServer(object):
# Первый бот (название функции = последняя часть URL вебхука)
@cherrypy.expose
def AAAA(self):
if 'content-length' in cherrypy.request.headers and \
'content-type' in cherrypy.request.headers and \
cherrypy.request.headers['content-type'] == 'application/json':
length = int(cherrypy.request.headers['content-length'])
json_string = cherrypy.request.body.read(length).decode("utf-8")
# Вот эта строчка и пересылает все входящие сообщения на нужного бота
requests.post(BOT_1_ADDRESS, data=json_string)
return ''
else:
raise cherrypy.HTTPError(403)
# Второй бот (действуем аналогично)
@cherrypy.expose
def ZZZZ(self):
if 'content-length' in cherrypy.request.headers and \
'content-type' in cherrypy.request.headers and \
cherrypy.request.headers['content-type'] == 'application/json':
length = int(cherrypy.request.headers['content-length'])
json_string = cherrypy.request.body.read(length).decode("utf-8")
requests.post(BOT_2_ADDRESS, data=json_string)
return ''
else:
raise cherrypy.HTTPError(403)
if __name__ == '__main__':
bot_1.remove_webhook()
bot_1.set_webhook(url='https://122.122.122.122/AAAA',
certificate=open(WEBHOOK_SSL_CERT, 'r'))
bot_2.remove_webhook()
bot_2.set_webhook(url='https://122.122.122.122/ZZZZ',
certificate=open(WEBHOOK_SSL_CERT, 'r'))
cherrypy.config.update({
'server.socket_host': WEBHOOK_LISTEN,
'server.socket_port': WEBHOOK_PORT,
'server.ssl_module': 'builtin',
'server.ssl_certificate': WEBHOOK_SSL_CERT,
'server.ssl_private_key': WEBHOOK_SSL_PRIV,
'engine.autoreload.on': False
})
cherrypy.quickstart(WebhookServer(), '/', {'/': {}})

Подготавливаем ботов

Создадим их две штуки, каждый из которых будет на команду /start представляться по своему номеру.

Номер раз:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cherrypy
import telebot
BOT_TOKEN = "токен нашего бота"
bot = telebot.TeleBot(BOT_TOKEN)
@bot.message_handler(commands=["start"])
def command_start(message):
bot.send_message(message.chat.id, "Привет! Я бот номер 1")
class WebhookServer(object):
# index равнозначно /, т.к. отсутствию части после ip-адреса (грубо говоря)
@cherrypy.expose
def index(self):
length = int(cherrypy.request.headers['content-length'])
json_string = cherrypy.request.body.read(length).decode("utf-8")
update = telebot.types.Update.de_json(json_string)
bot.process_new_updates([update])
return ''
if __name__ == '__main__':
cherrypy.config.update({
'server.socket_host': '127.0.0.1',
'server.socket_port': 7771,
'engine.autoreload.on': False
})
cherrypy.quickstart(WebhookServer(), '/', {'/': {}})

Второй делается аналогично, только ставим порт 7772 и меняем сообщение по команде /start. Запускаем "роутер", запускаем ботов. Если мы всё сделали правильно, то при создании чата с первым ботом, сначала вебхук получит "роутер", перешлет его первому серверу, который отправит сообщение непосредственно в Telegram, в точности так же, как на схеме выше.