Как написать Telegram бота: практическое руководство

Telegram

В последнее время Telegram у всех на слуху. Нужно отдать должное отделу маркетинга этого приложения, шумиху подняли на славу. Одной из основных "фишек" Telegram является его якобы защищённость - по словам Павла Дурова вся переписка между пользователями шифруется. Более того, ни одна спец.служба мира не будет иметь доступ к вашим сообщениям. Но в данной статье речь не об этом. Сегодня хотелось бы поговорить о не менее крутой фишке в Telegram, а именно о ботах. Помимо того, что в сети уже полно информации о различного рода Telegram ботах (github бот, например), мессенджер открыл своё API для разработчиков, и теперь каждый может создать своего собственного бота с блэкджеком и плюшками.

В статье я приведу пример написания онлайн бота с использованием Python и Django фреймворка. То есть мы "запилим" полноценное веб-приложение, которое будет крутиться на удалённом хосте и принимать команды от пользователей. Весь исходный текст доступен в моём github репозитории.

Документация, описывающая процесс взаимодействия с ботами Telegram находится тут. Чтобы не изобретать велосипед, я нашел неплохую Python библиотеку, реализующую все основные функции ботов - telepot. Как я уже упоминал ранее, для того, чтобы обслуживать пользователей нашего бота мы будет разрабатывать веб-приложение, используя Django фреймворк.

Как создать Telegram бота?

Для начала нам необходимо зарегистрировать в Telegram нашего будущего бота. Это делается следующим образом:

  • Необходимо установить приложение Telegram на телефон или компьютер. Скачать приложение можно тут
  • Добавляем к себе в контакт-лист бота с именем BotFather
  • Запускаем процедуру "общения" с ботом нажатием кнопки Start. Далее перед нами предстанет список команд точно как на скриншоте.
  • Для того, чтобы создать нового бота необходимо выполнить команду /newbot и следовать инструкциям. Обратите внимание, что username для бота должен всегда содержать в конце слово bot. Например, DjangoBot или Django_bot.

Telegram bot
  • Для нашего бота я выбрал имя PythonPlanetBot, так как его основная функция заключается в парсинге RSS feed сайта Python Planet и выдача информации о последних постах пользователю :)
Python Planet бот

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

Use this token to access the HTTP API:

За которой следует т.н. token по которому мы будем манипулировать нашим ботом. Помимо функции создания telegram бота, BotFather также имеет ряд других возможностей:

  • Присвоить боту описание
  • Установить аватар
  • Поменять token

и так далее. Полное описание доступных команд можно увидеть на первом скриншоте.

Приступаем к кодированию

Как я ранее уже упоминал, мы будем писать веб-приложение на Django. Но стоит отметить, что это делать необязательно. Можно обойтись и обычным Python скриптом, правда в этом случае необходимо будет периодически опрашивать Telegram на предмет новых запросов от пользователей бота (используя метод getUpdates) и увеличивая offset для получения самых последних данных без повторений. В Telegram существует два взаимоисключающих метода получения команд/сообщений для вашего бота.

  • Использование вызова API метода getUpdates
  • Установка Webhook

Установка Webhook заключается в передаче боту специального URL адреса на который будет поступать POST запрос каждый раз, когда кто-то начнёт посылать сообщения боту. Именно этот вариант мы и будем использовать для взаимодействия между ботом и его пользователем. Для того, чтобы задать URL, необходимо использовать API метод setWebhook. Отмечу, что URL должен начинаться с https, то есть иметь защищённое SSL соединение с валидным сертификатом. Telegram разрешает использовать самоподписанный сертификат, правда для этого необходимо в методе setWebhook передавать также публичный ключ в PEM формате (ASCII base64). Либо же можно получить валидный бесплатный SSL сертификат от Let's Encrypt.

Подробнее о getUpdates и setWebhook можно почитать соответственно здесь и тут.

Итак, вернёмся к python библиотеке для работы с Telegram - telepot. На текущий момент самой последней её версий является 6.7. Устанавливаем её в виртуальное окружение python virtualenv:

pip install telepot

Самый простой вариант взаимодействия с Telegram ботом на Python выглядит следующим образом:

import telepot
token = '123456'
TelegramBot = telepot.Bot(token)
print TelegramBot.getMe()

Переменной token присваиваем значение токена, полученного при создании бота через BotFather. В итоге после выполнения этих команд мы получим:

{u'username': u'PythonPlanetBot', u'first_name': u'Python Planet Bot', u'id': 199266571}

Поздравляю! Мы вызывали самый простой API запрос getMe, который возвращает информацию о боте: username, id, first_name.

Добавим нашего бота к себе в контакт-лист и пошлём ему первую стандартную команду /start

Telegram Bot

Выполняем код:

TelegramBot.getUpdates()
[{u'message': {u'date': 1459927254, u'text': u'/start', u'from': {u'username': u'adilkhash', u'first_name': u'Adil', u'id': 31337}, u'message_id': 1, u'chat': {u'username': u'adilkhash', u'first_name': u'Adil', u'type': u'private', u'id': 7350}}, u'update_id': 649179764}]

Процесс общения с telegram ботом происходит по HTTPS; для передачи данных используется JSON. Метод getUpdates возвращает список/массив из объектов типа Update. Внутри Update находится объект Message. Для стандартного взаимодействия с ботом нас фактически интересует именно объект Message, у которого мы считываем атрибут text, хранящий в себе текст, переданный боту и объект chat, в котором лежит информация о пользователе, инициировавшем общение с нашим Telegram ботом. Также имеется параметр update_id, который служит в качестве offset параметра при вызове метода getUpdates. То есть update_id+1 вернёт все сообщения, поступившие после последнего update_id, при этом все предыдущие сообщения будут удалены.

TelegramBot.getUpdates(649179764+1)
[{u'message': {u'date': 1459928527, u'text': u'hello bro', u'from': {u'username': u'adilkhash', u'first_name': u'Adil', u'id': 31337}, u'message_id': 13, u'chat': {u'username': u'adilkhash', u'first_name': u'Adil', u'type': u'private', u'id': 7350}}, u'update_id': 649179765}]

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

Простая функция парсинга RSS фида Planet Python выглядит вот так:

# -*- coding: utf8 -*-
from xml.etree import cElementTree
import requests
def parse_planetpy_rss():
    """Parses first 10 items from http://planetpython.org/rss20.xml
    """
    response = requests.get('http://planetpython.org/rss20.xml')
    parsed_xml = cElementTree.fromstring(response.content)
    items = []
    for node in parsed_xml.iter():
        if node.tag == 'item':
            item = {}
            for item_node in list(node):
                if item_node.tag == 'title':
                    item['title'] = item_node.text
                if item_node.tag == 'link':
                    item['link'] = item_node.text
            items.append(item)
    return items[:10]

Здесь я использую python библиотеку requests для работы с HTTP в самом простейшем варианте без обработки ошибок. Django "вьюшка" выглядит следующим образом:

TOKEN = '<Our_Bot_Token>'
TelegramBot = telepot.Bot(TOKEN)
def _display_help():
    return render_to_string('help.md')
def _display_planetpy_feed():
    return render_to_string('feed.md', {'items': parse_planetpy_rss()})
class CommandReceiveView(View):
    def post(self, request, bot_token):
        if bot_token != TOKEN:
            return HttpResponseForbidden('Invalid token')
        commands = {
            '/start': _display_help,
            'help': _display_help,
            'feed': _display_planetpy_feed,
        }
        try:
            payload = json.loads(request.body.decode('utf-8'))
        except ValueError:
            return HttpResponseBadRequest('Invalid request body')
        else:
            chat_id = payload['message']['chat']['id']
            cmd = payload['message'].get('text')  # command
            func = commands.get(cmd.split()[0].lower())
            if func:
                TelegramBot.sendMessage(chat_id, func(), parse_mode='Markdown')
            else:
                TelegramBot.sendMessage(chat_id, 'I do not understand you, Sir!')
        return JsonResponse({}, status=200)
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(CommandReceiveView, self).dispatch(request, *args, **kwargs)

CommandReceiveView ждёт POST запрос на себя, парсит его и отвечает исходя из заданной команды. Полноценное Django приложение можно найти по этой ссылке. Стоит отметить в коде использование ещё одного API вызова - sendMessage. Этот метод отправляет сообщение заданному пользователю, используя при этом chat_id и сам текст сообщения. Chat_id - это уникальный идентификатор чата между пользователем и ботом (его идентификатор есть в ответе на запрос getUpdates). У Telegram ботов есть одно ограничение, они не могут посылать сообщения пользователям, которые предварительно не инициировали общение с ним. По-видимому это сделано дабы избежать массового создания спам-ботов.

Я предполагаю, что вы уже клонировали мой репозиторий, настроили окружение и установили все необходимые зависимости: Django, requests, telepot. Если же вы не знаете как это сделать, то совсем скоро я напишу цикл статей о разработке веб-приложений на Python, включая разбор экосистемы: разработка, настройка, деплой. Если вам это интересно, то отпишитесь, пожалуйста, в комментариях к этой статье. Хочется получить обратную связь :)

Итак, веб-приложение на Django запущено. Как же начать тестировать бота? А всё очень просто - необходимо симулировать действия Telegram сервиса. Для этого нам понадобится HTTP клиент и тело запроса. В качестве HTTP клиента я часто использую Chrome плагин под названием Postman, а тело запроса мы возьмём напрямую из данных, полученных с помощью API вызова getUpdates.

После запуска runserver, URL на который необходимо посылать запрос выглядит следующим образом:

http://127.0.0.1:8000/planet/b...BOT_TOKEN/

где BOT_TOKEN - это токен нашего бота. Смотрим скриншот:

Postman REST Client

 

telegram-bot-postman

А давайте-ка отправим команду feed для получения списка новостей из Planet Python:

Postman и Telegram
Postman и Telegram

На скриншотах видно, что бот адекватно отреагировал на нашу команду вывести список последних 10 постов.

Следующим шагом является деплой нашего Django приложения на удалённый хост и последующий вызов метода setWebhook для передачи URL на который будет посылаться POST запрос от сервиса Telegram каждый раз при поступлении команд боту от пользователей. Об этом мы поговорим в следующей заметке.