Django telegram bot как связать
В данной статье я расскажу о том как написать Telegram бота с нуля. При этом, делая это не на коленке, а достаточно “по-взрослому”: имея “на борту” Django (для управления контентом, настройками бота, расписанием выполнения задач и пр.), Celery, python-telegram-bot в качестве обертки над Telegram API, Redis, PostgreSQL для хранения всего, что нужно боту. При этом для разработки используя Docker, а деплой в продакшн реализуя через Dokku с python buildback-ом от Heroku.
В качестве примера разработаем простенького бота, который порадует всех любителей поэзии: раз в сутки он будет присылать случайный стих, который будет парсить с одного из сайтов со стихами. Кроме этого, пользователи бота смогут по кнопке получать сколько угодно случайных стихов здесь и сейчас. Понравившиеся стихи смогут добавлять в избранное с возможностью последующего просмотра и прочтения.
Первым делом оставлю ссылки на репозитории, где все можно посмотреть, потыкать, клонировать и применить в своих проектах:
Telegram поддерживает два режима работы:
Как легко догадаться, webhook-и намного более эффективны. Аналитики подсчитали, что, в среднем, 98.5% запросов в режиме polling-а происходит “вхолостую”, тогда как эффективность webhook-ов, само собой, равняется 100%, потому что обращение происходит тогда и только тогда, когда появляется событие для этого обращения.
Polling — быстрое идеальное решение для разработки и отладки, поэтому его мы и используем для этих целей.
Webhook — идеален для продакшена.
Первое с чего следует начать разработку бота — с его регистрации.
Для регистрации бота добавляем в Телеграм @BotFather — головного бота, где происходит регистрация новых ботов.
Пишем команду /newbot и задаем наименование нашего бота. В моем случае: “Поэтическая душа”.
После этого бот попросит ввести имя на латинском для никнейма бота, которое должно заканчиваться на _bot. В моем случае будет: poetry_soul_bot.
После этого бот выдаст информацию о том, по какой ссылке можно начать диалог с ботом и токен, который необходимо сохранить и добавить в .env файл проекта (об этом позже).
На этом — регистрация бота завершена. Можно переходить к разработке.
.env файлик содержит в себе переменные окружения, которые нужны нам для локальной разработки. При деплое на прод мы единожды при развертывании приложения пропишем несколько переменных окружения на сервере и более про это забудем. Основные переменные, которые нам понадобятся:
Dockerfile — докерфайл проекта, базовые директива для докера, применяться будет только для локальной разработки.
docker-compose.yml — тут описаны сервисы, которые будут подняты в контейнерах докером на локальной машине. По умолчанию это: db (PostgreSQL), redis, web, bot, celery, celery-beat (последние два нужны для периодических задач, которые, возможно, понадобится выполять нашему боту).
entrypoint.sh — последний файлик для локальной работы проекта, говорит докеру, что делать после того как поднимутся все контейнеры. Тут говорим, что надо сделать миграции, создать админа по кредам из .env файла, прогрузить различного рода фикстуры, если таковые имеются в проекте, собрать статику и запустить сервер.
Procfile — данный файл необходим для Dokku при деплое на прод. Он содержит одну или более строк описывающих типы процессов и ассоциированных им команд. При деплое приложения в Dokku будет создан Docker образ, затем Dokku извлечет файл Procfile и команды, которые в нем описаны, будут переданы в docker run. В нашем случае мы говорим, что необходимо сделать миграции, поднять gunicorn сервер, запустить celery воркера и celery beat.
requirements.txt — тут все понятно, список зависимостей нашего бота, которые будут ставится как при развертывании на локальной машине, так и при деплое на продашкн.
run_pooling.py — для кейса, когда приложение работает в pooling режиме (на локальном ПК).
runtime.txt — говорит Dokku, какую версию Python мы хотим использовать.
staticfiles — в данной папке вся статика проекта (админка).
media — по умолчанию используем данную папку для хранения медиа файлов. Предположим, мы из админки закачиваем какие-то картинки, которые надо будет показывать пользователям, вот они падают в данную папку потому, что в settings.py проекта прописано: MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’)
logs — очень важная папка, потому что по логам мы будем отлавливать все, что происходит с ботом. В settings.py у нас задан логгер по умолчанию, который будет писать в файлик main.log все, что надо, ротируя его каждые 5 Мб.
dtb — головная папка Django проекта. Тут, скорее всего, ничего менять не придется, кроме settings.py.
tgbot — вся бизнес-логика бота находится в данной папке.
tgbot/admin.py — регистрирует модели приложения для использования в админке.
tgbot/models.py — содержит все модели, используемые ботом. Как минимум для базовой работы необходимы модели:
- Config — для задания различных параметров бота в формате “параметр” — “значение”;
- User — для пользователей бота;
- Location, Arcgis — для работы с геокодированием, если будет надо боту;
- UserActionLog — фиксация действий пользователя.
tgbot/tasks.py — celery задачи, которые можно устанавливать на исполнение по расписанию в очень удобной форме в админке проекта благодаря django-celery-beat.
tgbot/utils.p — различные функции-хелперы.
tgbot/fixtures — здесь могут быть различные фикстуры с данными для моделей проекта.
tgbot/migrations — тут все понятно, миграции проекта.
tgbot/templates — кастомные шаблоны для админки.
tgbot/handlers/admin.py — модуль-обработчик событий для пользователей с правами администратора. Наша модель пользователей, описанная в модуле tgbot/models.py содержит возможность задавать различные права пользователям. Так вот, если пользователь — администратор, то можно описать для него тот или иной функционал, который будет реализовываться при вызове им команд бота. Логика как раз описывается в данном модуле.
tgbot/handlers/files.py — модуль-обработчик событий, связанных с отправкой боту файлов. Опять же, по-умолчанию, заточен на администраторов. Когда мы посылаем какой-нибудь файл боту (фото, видео, документ и пр) — файл сохраняется и ему присваивается уникальные ID. В последующем, если мы хотим отослать файл пользователю, можно в соответствующий API метод передавать полный URL до этого файла (например, на внешнем хранилище), а можно передать просто Telegram ID этого файла. Вот как раз тут описана функция, которая возвращает ID файла отосланного боту, если его отослал пользователь с правами администратора.
tgbot/handlers/handlers.py — головной модуль-обработчик всех пользовательских событий, которые могут возникнуть у пользователей в процессе взаимодействия с ботом. Логика большинства функций довольно простая: прилетает в метод два параметра — update и context, из которых можно извлечь все: id чата, информацию о пользователе, что отослано и т.п. Далее происходит та или иная бизнес-логика, после чего, либо вызывается api метод edit_message, либо send_message, либо какая-то другая отправка, либо ничего, в зависимости от нужд логики.
tgbot/handlers/utils.py — функции-хелперы для работы обработчиков. Базовый набор: функция отсылки пользователя действия “печатаю”, декоратор для логирования и обертка над api методом send_message с обработками исключений.
tgbot/handlers/manage_data.py — в данном модуле определяются переменные для параметра callback_data, задаваемого при использовании клавиатуры. Т.е. в keyboard_utils модуле мы создаем клавиатуры, которые состоят из кнопок, а у кнопок есть два главных параметра: текст и обратный вызов, т.е. какая строка будет отправлена боту, когда пользователь нажмет на кнопку.
tgbot/handlers/static_text.py — в данный модуль выносим все текстовые данные, которые используем в боте.
Теперь, имея представление о структуре нашего проекта, мы можем немного углубиться в код, чтобы понять как и что происходит.
Прежде всего, небольшая вводная. Разворачивая проект в Docker-е на локальной машине, не забывайте, что после того как внесли правки в код, необходимо перезагрузить контейнер с ботом. Т.е. вы делаете:
docker-compose up -d — build — в результате поднимутся все контейнеры.
Далее просматриваете их:
И, найдя в списке контейнер с ботом пишите (когда это необходимо):
docker restart XXX , где XXX — ID контейнера.
Соответственно, если вы изменяете Celery-задачи, то не забывает перезагружать контейнер соответствующий для тестирования.
Для нашего проекта, не считая стандартных моделей из скелетона, необходимо добавить две новых: для хранения полученных стихов и для хранения списка избранных стихов пользователем.
Модуль /tgbot/poetry.py содержит в себе класс, который описывает головную логику проекта. Давайте рассмотрим его.
У класса есть два свойства — адрес источника и домен источника для последующей компоновки ссылок на стихи.
Инициализируя объект класса мы привязываем его к конкретному пользователю, который обратился за тем или иным функционалом.
Метод load_poem делает следующее: если в него передан id, то он просто выбирает из базы стих по id и возвращает его, если ничего не передано, то используя библиотеки requests и bs4, метод парсит источник со стихами выбирая случайный стих.
Метод add_to_fav получает id стиха, который пользователь хочет добавить в избранное и делает это.
Метод get_authors — нужен в нескольких местах нашего бота. Когда пользователь выбирает посмотреть содержимое избранное, то сначала бот показывает ему алфавитный список всех авторов, которые есть в избранном. Кликнув по букве, бот показывает уже полный список всех авторов на эту букву, наконец, кликнув на автора — пользователь получает список стихов. Так вот, данный метод либо просто возвращает всех авторов из избранного пользователя, либо список из первых букв фамилий авторов. В обоих случаях список отсортирован по алфавиту.
Метод get_poems — тут все просто, получает фамилию автора и возвращает все его стихи из избранного пользователя.
Метод get_poem_by_id — имеет красноречивое название.
Метод format_poem — статический, получает объект модели Poem и возвращает строку в разметке Markdown, которая будет красиво выводится ботом (название — жирным, автор — курсивом).
Центральный модуль нашей логики — /tgbot/handlers/dispatcher.py, а именно его метод setup_dispatcher.
Здесь происходит связка событий с нашими реакциями на события. Например, следующая строчка говорит о том, что мы хотим добавить в диспетчер обработчик команд (это, когда пользователь печатает слеш и что-то там), который при вводе команды /start вызовет соответствующую функцию command_start из модуля commands:
А следующая строчка говорит о том, что мы хотим добавить в диспетчер обработчик нажатия на кнопку пользователем CallbackQueryHandler, который должен вызывать функцию send_more из модуля hnd, когда бот получит callback-команду SEND_MORE, описанную в модуле md:
Теперь рассмотрим как бот реагирует на все это дело на примере функции send_more модуля hnd:
Как видим, функция получает два параметра — update и context.
Context — объект типа telegram.ext.callbackcontext.CallbackContext и он нам не особо интересен, т.к. в нашей функции все, что мы делаем, просто в конце с помощью этого объекта вызываем нужный нам мето: edit_message_text, send_message и т.п.
А вот update — интересный объект, ниже привожу все данные, которые прилетают в нем:
Как видно, в update-е содержится избыточная информация по всему, что нас может интересовать: кто написал, кому написал, что написал, какая была клавиатура, все даты и времена, какая кнопка была нажата и т.п.
В ряде случаев нам необходимо передать не просто некий callback от пользователя, но еще и тот или иной его выбор. Предположим, если речь идет о клавиатуре с алфавитом. В функции обработчике нам необходимо знать, какую буквы выбрал пользователь. Мы, конечно, можем для каждой буквы написать свой обработчик, но это будет ужасно. Поэтому, можно поступить проще. Посмотрим пример формирования клавиатуры для такого случая:
Как видно, мы указываем в callback_data не просто фиксированное значение, а указываем еще после символ решетки и текущую букву кнопки. После чего, в диспетчере мы определяем обработчик события как всегда, обычном способом:
А вот уже в функции обработчике получаем доступ к тому, что было передано после решетки вот так:
Как видно, мы получаем запрос из update.callback_query, а затем обращаемся к свойству data, которое и хранит нужную нам информацию, вычленяя из нее необходимую букву и производя необходимые манипуляции.
Что такое Dokku? Если вкратце и совсем просто Dokku — это open source Heroku, сделанный на базе Docker. Когда я прочел это определение, то следующим вопросом лично у меня был: “А что такое Heroku?”. Да, я слышал о Heroku неоднократно, но никогда не вдавался в подробности. Так вот, Heroku — это облачная PaaS (платформа как услуга)-платформа, поддерживающая множество языков программирования. Heroku, так же как и Dokku, обеспечивает быстрый и легкий деплой приложений “под ключ”: базы данных, логирования, мониторинг, контейниризация и т.д. и т.п. Heroku — модный и дорогой, Dokku — маленький, бесплатный и, возможно, не столь user friendly, но тем не менее, очень простой в изучении.
Можно сказать, что Dokku — это обертка над Docker, Heroku Buildpacks, Nginx и Git. Docker — обеспечивает Dokku контейнерами; Dokku использует свой собственный базовый образ Docker с ubuntu, всеми необходимыми пакетами, Heroku buildpack-ами и т.п. Heroku Buildpack — набор скриптов, задача которых определить, соответствует ли приложение заданному типу, скомпилировать и выпустить его. Билдпак, который Dokku запускает внутри контейнера, создает всю необходимую среду выполнения, устанавливает все зависимости и на выходе приложение готово к работе на 100%. Что касается Nginx, то Dokku передает внутрь приложения номер порта 5000, а для внешних использует порты 49200+, а трафик из/в контейнер проксирует Nginx, который нам не надо конфигурировать вообще никак, потому что Dokku все сделает сам. Наконец, в аспекте GIT-а тоже все очень удобно: Dokku использует git-hooks, отслеживает, когда код пушится в GIT и запускает скрипт, который и делает всю магию: создает docker образ из базового, запускает скрипт инициализации среды, запускает само приложение и рестартует Nginx.
Как я раньше разворачивал проекты, если не использовал Docker (хотя, с ним тоже были свои сложности)? Заход на чистый сервер, обновление пакетов, установка всех необходимых пакетов, создание пользователей, морока с правами, установка git, инициализация и пул проекта, установка virtual env, установка всех зависимостей проекта, установка python сервера и настройка, установка nginx и настройка для связи с Python сервером, установка БД и конфигурация приложения, установка redis, celery и их конфигурация, установка supervisor-а, который бы менеджил все это дело… В общем, очень много всяких муторных действий. Ниже я опишу как все это сделать буквально за пяток команд по 10–15 букв в каждой с использованием Dokku.
В рамках данного раздела с установкой и настройкой Dokku хотелось бы акцентировать еще на одном нюансе — работа со статикой. Если в нашем проекте все отлично и нам не о чем беспокоиться, потому что мы используем whitenoise , то в случае деплоя проекта, который не использует whotenoise, а хочет управлять статикой через nginx (хотя, даже официальная дока Heroku советует использовать whitenoise)— у нас возникнут небольшие проблемы. Дело в том, что хоть у нас и будет выполняться команда collectstatic, но nginx не будет знать, где находится наша статика — это проблема №1. Проблема №2 — статика будет внутри нашего контейнера с приложением, поэтому нам надо как-то смонтировать volume-ы, чтобы nginx имел доступ к статике проекта. Делается это все довольно просто. Ниже пара пунктов реализации:
Бонусом к очень удобной и беспроблемной настройке теперь идет прекрасный и очень удобный деплой: вносите правки в ваш код, делаете комит — пуш, и во время пуша в dokku происходит ребилд проекта, установка всего необходимого, прогоны тестов (если они у вас есть) и сборка проекта. Невероятно круто и очень удобно.
Ознакомившись и разобравшись с написанным, склонировав проект-скелетон, вы готовы начать писать своего бота. Мы изучили основы работы ботов, детально изучили структуру проекта, аспекты реализации функционала и удобные техники разворачивания решения на продакшне.
Ну а если вы не хотите со всем этим морочиться и хотите заказать разработку, то я, как член команды компании Webtronics готов вам в этом помочь. Заходите к нам на сайт, связывайтесь удобным образом и уверен, что мы сможем быть полезны друг другу :)
Материал — руководство по настройке авторизации пользователя через Telegram-аккаунт на вашем сайте с помощью пакета django-telegram-login.
Дисклеймер
django-telegram-login работает для Python 2 и 3 для актуальных версий Django (протестировано на 1.11 и 2.0).
Статья содержит примеры по каждому из пунктов туториала. В конце статьи предоставлены полноценные примеры.
Настройки для Telegram
Рекомендую прочесть новость и документацию по виджетам перед ознакомлением с материалом ниже.
localtunnel
Telegram требует доменное имя для вашего веб-сайта, потому что будет делать запросы на ваш сайт, передавать данные пользователя. Поэтому вам нужен тоннель — веб-сайт в сети, который будет переправлять все запросы на ваш localhost.
Установите пакет localtunnel с помощью npm (убедитесь, что он у вас установлен).
Запустите localtunnel на порту, на котором вы будете поднимать свой сервер Django.
В ответ вы получите ссылку, например, такую kqmxkdqitb.localtunnel.me. Вы можете делиться ею с любым другим пользователем интернета и он будет иметь доступ к вашему сайту.
Запустите сервер и убедитесь, что можете достучаться до сервера через тоннель.
Как сказано в официальной документации Telegram, для вашего веб-сайта нужен Telegram-бот, который будет обрабатывать запросы пользователей на авторизацию. Он должен быть привязан к вашему сайту через адрес.
В дальнейшем нам понадобится юзернейм бота (HabrahabrTelegramLoginBot) и его токен (540908629:AAEUGcF_M68nzHmfHtMSAWNncKBVJ75rOZI). На основе токена происходит проверка корректности данных пользователя, которые приходят от Telegram.
Установка и настройки Django
Скачайте пакет через pip следующей командой.
Добавьте приложение в installed apps.
Структурируйте информацию про бота. Например, добавьте константы в файл настроек (settings.py).
И используйте их в таком виде.
django-telegram-login позволяет использовать неограниченное количество ботов для авторизации в Telegram. Виджеты не берут параметры бота из настроек, а принимают их в себя как аргументы. Поэтому нет проблем конструировать много разных виджетов.
Как работает виджет
(размер картинки отличается от оригинальных размеров виджета)
Виджет
Виджет — это внешний JavaScript, который принимает ваши аргументы (data-size — размер виджета, например), обрабатывает их и отвечает данными пользователя при корректной работе. Также он генерирует «кнопку» Log in as.
(Пример виджета с документации Telegram)
Как настроить виджет используя django-telegram-login
Вам не нужно заходить в документацию Telegram, чтобы конфигурировать скрипт и каждый раз копировать его в вашу HTML-страницу. Используйте генератор виджетов от django-telegram-login.
Как вы видите в вырезке кода сверху — удобные константные решения для указания размера виджета, константа для отключения фото пользователя.
corner_radius
Опциональный параметр corner_radius принимает целочисленное число от 1 до 20 (это значение по умолчанию). Прямоугольная — 1, закругленная — 20.
user_photo
С помощью константы DISABLE_USER_PHOTO, переданной в параметр user_photo, можно отключить фотографию пользователя возле виджета.
create_callback_login_widget и create_redirect_login_widget, генераторы виджетов, принимают эти константы и на их основе генерируют код для виджета по вашим требованиям. Давайте посмотрим, что они возвращают в переменных telegram_login_widget.
Как мы обсуждали выше, виджет — это всего лишь внешний JavaScript. Поэтому telegram_login_widget в случае callback вернет следующее.
Как видите, генератор виджета подставил в скрипт юзернейм вашего бота, размер виджета и ссылку на ваш сайт. Функцию onTelegramAuth в data-onauth рассмотрим ниже.
Логика конструирования виджетов
На примере функции create_redirect_login_widget, которая знает как строить виджет, можно увидеть — в тело виджета вставляются переданные в нее переменные redirect_url, bot_name и user_photo.
На выходе получается «сырой текст», который можно отобразить в HTML-шаблоне.
Отображение сгенерированного скрипта на HTML странице
Сгенерируйте ваш виджет в какой-нибудь view и передайте в контекст.
В самом шаблоне HTML используйте переменную telegram_login_widget из context с помощью Jinja (типа того).
Тег autoescape используется, чтобы «сказать» HTML отобразить telegram_login_widget как разметку, а не как сырой текст. В итоге на странице отобразится следующее.
Пользователь жмет на кнопку, получает запрос на подтверждение авторизации.
Если пользователь делает это первый раз, сначала появится авторизация в Telegram.
После этого и в дальнейших нажатиях на кнопку Log in as — Telegram будет высылать данные пользователя каждый раз, пока пользователь не отключится в чате c Telegram (если отключится, придется подтверждать вход еще раз).
В функцию onTelegramAuth попадает ответ от Telegram с данными пользователя.
Используйте аргумент user, чтобы получить данные.
Telegram возвращает следующие данные: first_name, last_name, username, photo_url, auth_date (unix datetime) and hash (два последних значения технические, для верификации). Можно реализовать передачу данных от onTelegramAuth на вашу view через AJAX запрос.
redirect виджет поле нажатия на кнопку Log as in перенаправляет пользователя на указанную ссылку (TELEGRAM_LOGIN_REDIRECT_URL) и вставляет в параметры запроса (параметры ссылки) данные пользователя.
Что касается этого типа виджета — вам необходимо пройти аутентификацию данных.
Функция verify_telegram_authentication на основе полученных данных из параметров запроса и токена бота верифицирует корректность данных (проверяет, что вы не получили не существующие или фальшивые данные). Алгоритм аутентификации описан здесь.
Примеры
Все необходимые настройки — settings.py
Так может выглядеть ваш файл views.py с полной реализацией авторизации пользователя через Telegram.
Шаблон для callback логина — callback.html
Шаблон для redirect логина — redirect.html
Спасибо за внимание. Отзывы и предложения присылайте на личную почту. Если вы заинтересовались django-telegram-login, заходите на страницу проекта на Github.
In the part 1 I have described the main idea of this series of articles and described how to create a telegram bot. Without a webhook (web server endpoint) our bot does nothing and so it is useless. In this article I will describe how to set up a web server on the local machine, we will loop throw the project setup, main bot logic, settings, underwater rocks, debug, etc. It will be a long read as it is difficult to take some part and put it into a separate article.
There is one frustrating gap in the tutorial: it says nothing about the virtual environment and so the next command
may work not in the way you want it to. What for you need a virtual environment? This question is out of the article, but in a few words, it allows you to separate projects development, isolate them from system site directories. There are lots of ways how to set up a virtual environment but I mainly stick to 2 of them:
Virtualenvwrapper creates virtual environments in one(usually hidden) directory and when you start, for example, a Django project you may already have a virtual environment but may not have a project directory.
Using virtualenv package you need to choose a virtual environment directory on your own. Usually, developers put virtual environment directory inside the project and so you need to create a project directory first.
Django tutorial suggests that you use virtualenvwrapper virtual environment and your project directory does not yet exist. Let’s assume that you are using virtualenvwrapper(and virtual env already activated and Django already installed) and you are in the projects directory and it already has 2 projects: A and B. Then the command
will create a project directory C and all necessary subdirectories and files.
Result projects directory:
If you are using virtualenv package then to achieve the same result as above you need to go to the projects directory and create a new project directory C. In the project directory C you need to create a virtual environment. Usually, developers call it venv (or env but I prefer venv). I have several different python languages on my laptop and, for example, for python 3.6 I use next command to create a virtual environment:
Manually activate the virtual environment(on Linux):
Create a Django project(with name C or whatever you want) inside the project directory:
. at the end of the command above says to put the project in the current directory
As you may have already noticed I use a terminal to enter commands. I suppose that you also use a terminal and located in the projects directory.
- Create a project directory telegram_bot_tutorial
- Create a virtual environment with name venv and with python 3.6
- Activate virtual environment
- Install Django
- Create a project with the name tb_tutorial. I chose a different name from the repo name so that it would be clear where is a repo and where is a project/app.
- Save installed packages names in the file, the conventional file name is requirements.txt
At this point, we have installed only Django package but for some reason, you may see in the file some other packages. That is because there are dependency packages that were installed with the Django package. There is no need to point exact names and versions of dependency packages as they will be installed together with the pointed Django version. For this reason delete all packages from the file, except Django package and add to the file new packages when you install new separate packages. To get versions of the current packages just run the command:
Copy newly added separate package and paste in the file requirements.txt In my case at this point there is only 1 raw in the file:
- We are going to write an application(project) which we will then deploy. For this reason, we need to divide our settings file on development and production. development settings are for local usage and production settings are for deployed application usage(on the server). Currently, we have a settings.py file which is a module with the name settings. There is an environment variable DJANGO_SETTINGS_MODULE that points to a settings module. You may find it in the manage.py file
It says that by default(if no other is specified) take settings from tb_tutorial.settings module. Let’s create a python package with the name settings. Copy our settings.py file in the settings package and rename the file settings.py into base.py. Now we have a settings directory with an __init__.py and base.py files. In the settings package create 2 more files: develop.py and production.py. Leave production.py file empty for now and add to develop.py file next rows:
In the base.py file in the INSTALLED_APPS variable(which is a list) at the end of the list add an element ‘tb_tutorial’. It says that our project also is an application, so there is no need to create a separate application for our small project with 1 application.
In the manage.py file set DJANGO_SETTINGS_MODULE to take settings by default from tb_tutorial.settings.develop:
Now we are ready to apply migrations (there is no need for it but you can do it) and run the project
Install MongoDB on your local machine. You will be needed to debug your code on the local machine. On production, we will use 3-rd party service for setting a MongoDB. Installing the database on the local machine is out of this article.
To work with MongoDB in Python(Django) project we will be needed a pymongo python package
In the settings package in the base.py file (module) import MongoClient
In the same module find Database settings (DATABASES) and substitute with the next settings:
We set local settings for the MongoDB and sqlite databases. In this tutorial, we will not use sqlite.
In the settings package in the develope.py module add next row at the end of the module:
We set the main collection and pointed out that it is our database. In NoSQL database everything is about collections. Tables in NoSQL database are collections too. I could specify also a table in the settings package, but I decided not to do it as I may extend my project to host several webhooks and in such case, I would want each bot(webhook) to use its own table (collection). For this purpose, I will set tables at models.py file, so my project structure will be almost the same as I would use Django ORM.
In the models.py module lets set a collection for our bot:
Setting webhook means to create an URL endpoint on which some client(telegram router in our case) can send the data. It means(in Django) that we need to set an URL endpoint and attach the view to the URL. Let us create a view first. It will be quite a large view, as I put all the logic in it. You should divide code into functions, classes and methods if you want your application to be more scalable and easily editable.
I will use requests library to send messages back to the chats so we need to install a necessary package
Here is how our view looks like:
The view file is pretty straightforward but I would like to add some additional comments:
At this step, we have logic for our bot. We need to set up an URL for our bot and links it to our view
We often use the word “webhook” and I would like to write a few words about it:
Now we have all variables to configure a telegram router to send messages to our web server. The resulting URL for setting a webhooks will look something like this:
Copy the URL that you configured(with your token and your URL address), and paste it in the browser to send a GET request to telegram server. If everything is OK you receive a successful message:
Now you will receive messages on your local web server. Send messages to your bot and check how it works.
Congratulations! You have set a telegram bot webhook and able to receive messages on your local web server. We are one step from our goal: to set up a webhook on the remote server on the internet. I will describe step by step instructions in my next article. Stay tuned.
Возможно, ты уже успел прочитать предыдущие заметки о том, как я писал телеграм бот на python с админкой на django. Сегодня хочу поделиться еще одной заметкой о том, что мне вообще дал этот проект и еще немножко технических деталей.
Отображение реакций
Вкратце: @ractionbot - это бот, который добавляет кнопки с реакциями под постами в телеграм канале. Можно менять реакции, добавлять несколько каналов и т.д. Но недавно появилась идея показывать автору канала не только число реакций, но и пользователей, которые эти реакции оставили.
Реализовать такую функциональность можно было бы несколькими способами:
- отправлять список пользователей прямо внутри диалога с ботом. В реализации очень просто, но к удобству много вопросов. Если у поста больше хотя бы 50 реакций, то список будут сложно прочитать. Про случаи, когда у поста несколько тысяч реакций, вообще молчу.
- отправлять пользователю csv файл со списком. Но тут вообще все сложно. Нужно создавать файл, загружать его на сервера телеграм, потом отправлять пользователю. После скачивания файл тоже нужно как-то посмотреть. Вероятно на телефоне должен быть установлен MS Excel или что-то подобное.
- отображать веб страничку со списком пользователей. Этот вариант мне нравится больше всего. Пользователю не нужны никакие дополнительные действия. А у меня тоже уже есть сервер, на котором запущен django с админкой.
Остановился на последнем варианте. Быстренько сверстал простую страничку, написал выборку пользователей, готово. В этом моменте ничего сложного нет. На любой возникший вопрос я читаю документацию django на оф. сайте, а не просто копирую код со Stack Overflow. Ведь цель всего этого проекта - получить опыт и знания в новой области (python, django, telegram bot api).
Gandi
- открываем вкладку Domains и выбираем нужный домен (у меня их там несколько)
- на вкладке Nameservers нажимаем Change и добавляем сервера DO
- нажимаем Save и ждем пару часов пока записи применятся
Digital Ocean
Пока записи в nameservers применяются, мы можем настроить Digital Ocean
- открываем вкладку Networking > Domains
- добавляем домен и настраиваем записи
- HOSTNAME - @, WILL DIRECT TO - выбираем нужный дроплет
О проекте
Очень важно при изучении новых технологий подкреплять их практикой. Брать новые сложные проекты и делать их шаг за шагом. Пока писал этот бот - выучил много нового:
- python. Никогда раньше не писал на python, а теперь пишу. Он очень удобен для быстрого решения небольших задач.
- django. Наверное, написание админки самостоятельно заняло бы слишком много времени. Django дал возможность быстро подредактировать шаблоны, выборки и отображения чтобы получить функциональную админ панель для бота. Теперь вижу список пользователей, каналов, постов. Могу отправлять нотификации и много чего другого с минимумом усилий.
- github actions. Практически сразу же настроил CI\CD для проекта и тебе советую. Исправление багов и поставка на "прод" происходит очень легко и без ручного вмешательства.
- digital ocean. Разобрался, как настроить дроплет, добавить его в github и как разворачивать приложения с помощью docker.
- dns. Купил доменное имя, связал с приложением и это оказалось совсем не сложно. Для будущих проектов эти знания еще не раз пригодятся.
Цифры
За время от первого запуска проекта до сегодня могу поделиться такими цифрами:
- диалог с @ractionbot начали 567 администраторов
- бот был добавлен в 110 каналов
- опубликовано 12459 постов
- и 3005 пользователей оставило 10111 реакций
У моего канала тоже пока совсем немного подписчиков. Если тебе интересно следить за развитием подобных проектов, то подпишись на Yura Hunter. Там я публикую посты, которые не дотягивают до большой заметки на Hexlet.
12 June 2017 , Python, 37428 views, Разворачиваем Django приложение в production на примере Telegram бота
This is the second part of my small tutorial about creating a Telegram bot using Python and Django. Today I am going to show you how to deploy our Django app on Digital Ocean VPS hosting, we are going to use Linux Ubuntu 14.04 LTS version.
What you are going to learn:
- How to deploy a django app (or any WSGI app) on Digital Ocean using SSH access
- How to work with gunicorn and nginx
- How to control your processes using supervisord
- Setting up virtualenv using pyenv
- Adding our app to system autorestart (init.d)
Even despite we are going to work with Django app, this instruction will be applicable for any Python WSGI web application, so do not hesitate to use it with Pyramid, Flask, Bottle or any other popular web application framework in Python ecosystem. In this post I am going to use VPS (Virtual Private Server) on popular Digital Ocean hosting. By the way, if you sign up using my link, you will get $10 credit instantly on your account, money can be used to buy small servers and use them for your own purposes.
Moreover, it is not neccessary to deploy your app on public servers, everything can be performed on your local ubuntu machine or with admin tools like VirtualBox or Vagrant (but in this case you will not be able to set up a webhook). Let's get started.
Creating a VPS
I assume that you already signed up on Digital Ocean. So, log in and then press Create Droplet at the very top.
Now we are going to choose the cheapest droplet for our purpose ($5 per month). Select Ubuntu 14.04.5 LTS or the newest one.
Scroll down and choose a data center. Usually I prefer Frankfunt due to lowest ping rate from my place. When everything is ready, press Create button. Your droplet (VPS) is going to be ready within 60 seconds, you will find remote access credentials on your mailbox.
Server Configuration
When server is ready, you have to log in and change your root password.
Let's update packages.
And then create a sudo user.
When sudo user has been added, log in under this user called django.
Let's now install packages that we are going to use for deployment.
At the very beginning I have mentioned that we are going to use pyenv to install the latest Python version (I usually do not work with system python when it comes to custom scripts and web apps). If you do not know what is Pyenv and how to work with it, take a look at my post in Russian (will translate it ASAP).
It takes some time to download, compile and install newest version of Python, please be patient :) Ubuntu 14.04 has Python 2.7.6 which is quite old and has some security problems with TLS and it does not support TLS 1.2.
Now we have to clone a repo. If you forgot the link, here it is.
Configure isolated python virtual environment using pyenv.
It is time to install dependencies using pip.
I have made some changes which are not covered in the previous post about Telegram bot. I moved some variable settings to .env file which is controlled by a reusable django app called django-envrion. Take a look at the changes.
Create .env file from .env-template file and then you have to provide your own credentials.
$ cd blog_telegram && mv .env-template .env && vi .env
You have to change DEBUG to False, put your Telegram bot token and provide additional hostname (if you have several host, they should be separated by comma).
Example shows that I have 2 hosts: loopback and additional subdomain host for telegram bot (which is going to be used to set up a web hook).
Setting up valid SSL certificate
In my last post about Telegram I have mentioned that there are 2 ways on how to interact with Telegram bot:
- Using getUpdates API call
- Setting up a webhook using setWebhook API method
Moreover, if you decide to use a webhook, you have to obtain a valid SSL certificate or create your own self-signed certificate using tools like OpenSSL. We are going to obtain a valid one using free service called Let's Encrypt. I have a Russian article which describes the process of issuing SSL certificate (will translate ASAP).
You have to answer some questions regarding a website and when everything is ready, your fresh certificate will be located at /etc/letsencrypt/live/bot.khashtamov.com/. Pay attention to "-d" option in letsencrypt-auto script, put your own domain name.
Setting up a webserver Nginx
It is time to set up a proxy webserver for our Django app. I decided to take nginx which is one of the most robust web servers and is considered as a best practice when it comes to deployment to production environment. Nginx will proxy all incomming requests to our application which is going to be served by WSGI server called Gunicorn. Let's take a look at the config file:
telegram_bot.conf content should be:
Do not forget to replace a hostname! Now we have to restart a server.
What we just did?
For test purposes and to make sure that everything is configured correctly, we can start django development server on 8001 port:
Open a browser and navigate to your configured host
URL Not found error is OK because we only have 1 valid URL for our bot to receive commands (and django admin url of course).
Gunicorn and Supervisor set up
Now let's set up our production-ready WSGI server gunicorn which is going to be controlled by process manager supervisord.
What is Supervisor?
Supervisor is a process manager utility. It monitors processes and makes sure they work correctly. For example, if our gunicorn server dies supervisor tries to restart it and provides feedback. Pay attention that if you want to control a process via supervisor, make sure that your program is running in foreground mode.
Let's write a config file for gunicorn:
Save it as gunicorn.conf. We have already installed gunicorn via pip (it is our dependency in requirements.txt).
Our supervisord config file content:
Save it to ~/planetpython_telegrambot/supervisord.conf and start it via:
If your settings are correct, there should be no error messages.
Supervisor is controlled by supervisorctl utility.
If you need help, then type:
If supervisor is running, your telegram bot is already online.
Starting supervisor after system restart
What will happen if your VPS is restarted (failure inside data center, system update, admin error etc)? Nothing. Your web apps will not be restarted. We have to fix it.
We are going to use upstart init script.
You have to put your file (I called it telegram_bot.conf) to /etc/init/ directory. In order to check if it works, restart your droplet:
Everything should work correctly.
Setting up Webhook
Order to initiate a webhook, we have to run a custom python script with the following content:
Please replace BOT_TOKEN to your valid telegram bot token and run:
So now your bot should receive messages and process them correctly.
Useful links
💌 Join the mailing list
If you like the content I produce, please join my mailing list to stay tuned.
Читайте также: