Создаем телеграм-бот с вебхуками на Heroku

Разрабатывая телеграм - бота для проведения викторины, я даже не задумывался, каким количеством человек он будет использоваться и, как оказалось, очень зря …

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

Одной из моих рабочих задач, как программиста, была автоматизация проведения викторины. Конечно, уже существуют специализированные бесплатные приложения, заточенные под эти задачи, но нужно было такое, в котором не было бы ничего лишнего, оно было всегда под рукой и такое привычное, чтобы не нужно было с ним разбираться. Выбор пал на телеграм-бота и для того чтобы он справлялся с большей нагрузкой было принято решение использовать асинхронную библиотеку aiogram.

Начнём с создания эхо бота на aiogram, тут нет ничего сложного, возьмём пример из документации:

import logging from aiogram import Bot, Dispatcher, executor, types # Токен, выданный BotFather в телеграмме API_TOKEN = 'BOT TOKEN HERE' # Configure logging logging.basicConfig(level=logging.INFO) # Initialize bot and dispatcher bot = Bot(token=API_TOKEN) dp = Dispatcher(bot) @dp.message_handler() async def echo(message: types.Message): await message.answer(message.text) if __name__ == '__main__': executor.start_polling(dp, skip_updates=True)

Однако преимущество aiogram над python-telegram-bot и pyTelegramBotAPI в том, что он асинхронный, а значит, может обрабатывать несколько запросов почти единовременно. Стандартная база данных sqlite отлично подходит для несложных проектов и уже входит в стандартную библиотеку питона, поэтому для начала я решил использовать её.

Через несколько часов работы приложение было написано, и мы с коллегами решили протестировать на себе его работоспособность. Бот запускался с использование технологии long polling, и запускался он на локальном компьютере. Для небольшого количества человек этого вполне достаточно: 3-4 человека в секунду бот выдерживает без особых проблем.

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

Решением этой проблемы стал переход на вебхуки. И для обеспечения бесперебойной работы разместим его на удалённом сервере. Отличным решением для этого является heroku: здесь можно управлять запуском приложения как с компьютера, так и с мобильного приложения, отслеживать логи и, что является наиболее важным для нас, настраивать вебхуки.

Алгоритм для реализации эхо бота в данном случае занимает больше времени, но он достаточно прост:

1) Регистрируемся на сайте https://dashboard.heroku.com/

2) На странице Personal создаём новое приложение:

Создаем телеграм-бот с вебхуками на Heroku

Выбираем имя нашего приложения (у меня это «aiogram-echo-bot-webhook» - запомним его, оно нам ещё понадобится!), меняем сервер на Europe и нажимаем кнопку «create app»:

Создаем телеграм-бот с вебхуками на Heroku

Отлично, мы подготовили контейнер для нашего приложения! Передать туда код самого приложения можно несколькими способами, например, через Heroku CLI или через GitHub. Разберём деплой через гитхаб, так как при любой возможности лучше использовать контроль версий :)

Перед деплоем на Heroku хорошо бы переписать наше приложение на вехуки[1]:

import logging import os from aiogram import Bot from aiogram.dispatcher import Dispatcher from aiogram.utils.executor import start_webhook from aiogram import Bot, types TOKEN = os.getenv('BOT_TOKEN') bot = Bot(token=TOKEN) dp = Dispatcher(bot) HEROKU_APP_NAME = os.getenv('HEROKU_APP_NAME') # webhook settings WEBHOOK_HOST = f'https://{HEROKU_APP_NAME}.herokuapp.com' WEBHOOK_PATH = f'/webhook/{TOKEN}' WEBHOOK_URL = f'{WEBHOOK_HOST}{WEBHOOK_PATH}' # webserver settings WEBAPP_HOST = '0.0.0.0' WEBAPP_PORT = os.getenv('PORT', default=8000) async def on_startup(dispatcher): await bot.set_webhook(WEBHOOK_URL, drop_pending_updates=True) async def on_shutdown(dispatcher): await bot.delete_webhook() @dp.message_handler() async def echo(message: types.Message): await message.answer(message.text) if __name__ == '__main__': logging.basicConfig(level=logging.INFO) start_webhook( dispatcher=dp, webhook_path=WEBHOOK_PATH, skip_updates=True, on_startup=on_startup, on_shutdown=on_shutdown, host=WEBAPP_HOST, port=WEBAPP_PORT, )

Что здесь происходит?

TOKEN, HEROKU_APP_NAME мы считываем из переменных окружения, которые скоро добавим в наш проект.

WEBHOOK_HOST – доменное имя нашего приложения

WEBHOOK_PATH – часть пути, на который мы будем принимать запросы. Его следует придумать таким, чтобы не было возможности его угадать, во избежание фальсификации запросов. В нашем случае используется токен бота, так как его, также, следует держать в секрете.

WEBHOOK_URL – полный url адрес, на который будут принимать запросы.

WEBAPP_HOST – хост нашего приложения, оставляем локальный.

WEBAPP_PORT – порт, на котором работает наше приложение, так же считывается с переменных окружения, которое предоставляет Heroku, его мы не заполняем.

Асинхронная функция on_startup устанавливает вебхук для нашего телеграм бота, на который будут отсылаться уведомления о получении новых сообщений. И on_shutdown, наоборот, удаляет этот вебхук при выключении.

Далее мы переключаем вывод логов только на вывод только чисто информативной информации. И, собственно, запускаем наш диспетчер, при этом при запуске опускаются все сообщения, которые были получены в то время, когда бот не работал, что указано в параметре «skip_updates».

Почти всё готово, но чтобы дать инструкции Heroku, как именно развернуть наше приложение, нужно создать файл «Procfile» и вставляем туда следующий код:

web: python main.py

Здесь: web – значит, что наше приложение будет web приложением, а то, что идёт после «:» это строка, которую необходимо выполнить в первую очередь. Запустить наш файл main.py с помощью питона.

И ещё один файл, который необходим для запуска, это requirements.txt, в котором мы указываем все зависимости нашего проекта. Его создаём, выполнив команду «pip freeze > requirements.txt».

Также можно указать, какую конкретную версию питона использовать: для этого создадим файл «runtime.txt» и впишем туда версию питона по шаблону «python-3.9.7».

Теперь подготовим переменные среды на Heroku: для этого переходим на вкладку «Settings» и жмём кнопку «Reveal Config Vars».

Создаем телеграм-бот с вебхуками на Heroku

Здесь добавляем два поля:

BOT_TOKEN – токен, полученный у BotFather

HEROKU_APP_NAME – имя приложения созданного на heroku, которое мы с вами запоминали.

Создаем телеграм-бот с вебхуками на Heroku

Отлично! Перейдём обратно к деплою: создадим репозиторий на гитхаб и зальём туда все файлы. На странице с нашим приложением в Heroku переходим во вкладку «Deploy». Кликаем на вкладку «Github». После того как вошли в свой аккаунт гитхаб заполняем имя репозитория. Помним, что репозиторий должен быть не пустым!

Создаем телеграм-бот с вебхуками на Heroku

Кликаем на кнопку «connect».

Создаем телеграм-бот с вебхуками на Heroku

Для того чтобы наше приложение обновлялось каждый раз, как мы заливаем новые изменения в ветку «master», можем нажать кнопку «Enable Automatic Deploys»:

Создаем телеграм-бот с вебхуками на Heroku

В первый раз, всё-таки придётся деплоить самим, для этого нажимаем кнопку ниже:

Создаем телеграм-бот с вебхуками на Heroku

И дожидаемся окончания деплоя. При положительном результате вывод будет примерно таким:

Создаем телеграм-бот с вебхуками на Heroku

Также можно посмотреть логи вверху окна - кнопка «More»->«View logs»:

Создаем телеграм-бот с вебхуками на Heroku

Переходим в наш бот и отправляем ему пару сообщений, если бот отвечает, значит всё в порядке, если нет, то переходим в логи и смотрим, в чём может быть ошибка.

На этом можно было бы остановиться, эхо-бот готов, но в реальном проекте нам понадобится сохранять различные данные из приложения. Для этого нужна база данных, как и в прошлый раз мы можем воспользоваться стандартной sqlite, но так как мы используем асинхронную библиотеку, то и запросы в бд должны быть асинхронными. Поэтому устанавливаем библиотеку databases для sqlite «pip install databases[sqlite]».

Разобьём код по модулям и подключимся к базе данных: создаём файл config.py и выносим туда все переменные (WEBHOOK_HOST, WEBHOOK_PATH и т.д.).

И ещё один модуль «db.py», в котором пишем следующий код:

from databases import Database database = Database('sqlite:///bot.db') где bot.db – путь к файлу базы данных

Создадим таблицу:

CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, telegram_id INTEGER NOT NULL, text text NOT NULL );

И дополняем модуль main.py:

async def save(user_id, text): await database.execute(f"INSERT INTO messages(telegram_id, text) " f"VALUES (:telegram_id, :text)", values={'telegram_id': user_id, 'text': text}) async def read(user_id): messages = await database.fetch_all('SELECT text ' 'FROM messages ' 'WHERE telegram_id = :telegram_id ', values={'telegram_id': user_id}) return messages @dp.message_handler() async def echo(message: types.Message): await save(message.from_user.id, message.text) messages = await read(message.from_user.id) await message.answer(messages)

Здесь мы после получения сообщения сохраняем его в базу данных, и затем просто возвращаем все сообщения, полученные от этого пользователя.

Не забываем обновить requirements.txt

Пушим всё на гитхаб, процесс сборки можно посмотреть на вкладке «Activity»

Создаем телеграм-бот с вебхуками на Heroku

Проверяем в боте: отправляем пару сообщений, бот возвращает нам список сохранённых в базу.

Создаем телеграм-бот с вебхуками на Heroku

Казалось бы, всё хорошо, но вдруг произошла непредвиденная ошибка и приложение необходимо перезапустить: зайдём на вкладку «Resources».

Нажимаем на карандаш, жмём переключатель, для выключения приложения и подтверждаем «confirm».

Создаем телеграм-бот с вебхуками на Heroku

Вновь включаем таким же способом и пробуем отправить боту сообщения:

Создаем телеграм-бот с вебхуками на Heroku

Ужас, мы потеряли все данные! Но почему, ведь они хранятся в базе данных? Это происходит потому что деплой происходит в изолированных контейнерах и при каждом новом запуске создаётся новый контейнер, а как мы помним исходный файл с БД у нас был пустым.

В нашем случае данные нужны будут и после выключения, поэтому нам нужна изолированная от приложения база данных. К счастью на Heroku, помимо множества приложений, можно бесплатно развернуть и базу данных, например postgres.

Переходим в «elements»:

Создаем телеграм-бот с вебхуками на Heroku

Выбираем «Heroku Postgres»:

Создаем телеграм-бот с вебхуками на Heroku

И устанавливаем:

Создаем телеграм-бот с вебхуками на Heroku

Выбираем бесплатный план и вводим имя приложения, для которого подключаем БД, для того чтобы потом мы могли считывать строку подключения с переменных среды:

Создаем телеграм-бот с вебхуками на Heroku

Переходим в переменные среды нашего приложения и видим, что там появился ключ «DATABASE_URL», который мы и будем использовать для подключения.

Создаем телеграм-бот с вебхуками на Heroku

Для подключения к БД postgresql, установим пакет databases[postgresql] «pip install databases[postgresql]». Создаём исходные таблицы, но синтаксис создания таблицы немного поменяется:

CREATE TABLE messages ( id SERIAL PRIMARY KEY, telegram_id INTEGER NOT NULL, text text NOT NULL );

Также следует немного изменить метод «read» следующим образом:

async def read(user_id): results = await database.fetch_all('SELECT text ' 'FROM messages ' 'WHERE telegram_id = :telegram_id ', values={'telegram_id': user_id}) return [next(result.values()) for result in results]

Вновь обновляем requirements.txt и пушим на гит.

Дожидаемся окончания деплоя, если приложение не запущено, то запускаем его и отправляем проверочные сообщения боту:

Создаем телеграм-бот с вебхуками на Heroku

Получаем сообщения, всё отлично! Как и в прошлый раз возникает непредвиденная ситуация, из-за которой приходится перезапускать приложение. Перезапускаем, отправляем ещё одно сообщение и …

Видим, что все данные сохранились в БД!

Создаем телеграм-бот с вебхуками на Heroku

Вот всё и готово! А дальше всё зависит только от ваших предпочтений: для чего и под какие задачи разработать функционал бота, но вы можете быть уверенны, что его производительность будет на высоте, а данные надёжно сохранены.

Исходный код приложения размещён в репозитории:

1212
11 комментариев

На секунду показалось, что хабр читаю.

6
Ответить

Heroku уже использовать не вариант - они закрыли бесплатные тарифные планы, а российской карточкой их не оплатить. Лучше попробовать на Российском аналоге развернуть - облаке Amvera. Там развертывание через Push в GIT есть как и в Heroku.

2
Ответить

Неплохо, но так и не понял, зачем хероку? Не проще было это на впске за 10 долларов запустить?

1
Ответить

КАК запустить на впс?!!! Не могу понять настройку веб-хуков!!!

Ответить

Очень круто! Ничего не понятно 😂
Можно к вам обращаться по поводу доработок своих ботов?

Ответить
Автор

Спасибо за проявленный интерес к нашей статье. Всегда рады поделиться опытом!

Ответить

Неплохой материал, как вы вставили блоки кода на vc?

Ответить