Введение


На Хабре уже полно статей-туториалов с заголовками «Создание бота на Python», но многие из них используют готовые обертки над HTTP-интерфейсом Bot API Телеграма. Я же использую стандартную библиотеку для отправки и получения GET- и POST-запросов — requests. И так, рассмотрим создание примитивного Телеграм бота, который будет отвечать на все наши текстовые сообщения. Это будет заготовка для дальнейшего расширения.


image


Шаг 1. Создание бота


Для работы с ботом нужно сначала его создать внутри приложения Телеграм. Для этого напишем первородному боту телеграма @BotFather.

Пишем ему /newbot:


image

Выполняем его просьбу ввести имя вашего бот. Оно обязательно должно иметь на конце слово bot.


image

Если имя ввели правильно БотОтец любезно предоставит вам токен вашего бота для доступа к нему через API.

Вот теперь можно приступать к написанию самого бота.


Шаг 2. Авторизация бота


Так как API телеграма для работы с ботами представляет из себя HTTP-интерфейс, нам понадобится модуль для работы с HTTP — requests.


import requests

Принцип работы с ботом заключается получении обновлений, то есть получения действий, которые были совершены с вашим ботом. Для этого отправляем POST запрос в форме "URL + TOKEN + необязательная информация о частоте запросов, о количестве хранимых апдейтов и т.д.", который по умолчанию выдаст 100 последний не подтвержденных обновлений. Даже если бот не активен его обновления будут храниться на сервере до тех пор, пока вы их не обработаете, но не более 24 часов. Обработка обновления происходит посредством его подтверждения. Для подтверждения обновления в запросе /getUpdates передается параметр offset. Все обновления с id меньшим или равным переданному offset будут отмечены как подтверждённые и не будут больше возвращаться сервером. В ответ на запрос вы получите объект Update, который уже сериализован в JSON. Обработку JSON можно посмотреть в документации.

Инициализируем переменные для последующих запросов:


offset = 0 # параметр необходим для подтверждения обновления
URL = 'https://api.telegram.org/bot' # URL на который отправляется запрос
TOKEN = 'token' # токен вашего бота, полученный от @BotFather
data = {'offset': offset  1, 'limit': 0, 'timeout': 0}

Дальше отправляем запрос обновлений:


try: # обрабатываем исключения
    request = requests.post(URL  TOKEN  '/getUpdates', data=data) # собственно сам запрос
except:
    print('Error getting updates')
    return False

if not request.status_code == 200: return False # проверяем пришедший статус ответа
if not request.json()['ok']: return False

Обработка исключений обязательна, так как бот чувствителен к ошибкам и любой сбой приведет к «пробке» в очереди обновлений.


Шаг 3. Обработка обновлений и отправка сообщений


И так, проходимся по накопившимся обновлениям:


for update in request.json()['result']:
    offset = update['update_id'] #  подтверждаем текущее обновление

    if 'message' not in update or 'text' not in update['message']: # это просто текст или какая-нибудь картиночка?
        print('Unknown message')
        continue

    message_data = { # формируем информацию для отправки сообщения
        'chat_id': update['message']['chat']['id'], # куда отправляем сообщение
        'text': "I'm <b>bot</b>", # само сообщение для отправки
        'reply_to_message_id': update['message']['message_id'], # если параметр указан, то бот отправит сообщение в reply
        'parse_mode': 'HTML' # про форматирование текста ниже
    }

    try:
        request = requests.post(URL  TOKEN  '/sendMessage', data=message_data) # запрос на отправку сообщения
    except:
        print('Send message error')
        return False

    if not request.status_code == 200: # проверим статус пришедшего ответа
        return False

Немного о форматировании отправляемых сообщений.

Телеграм позволяет выделять некоторый текст в сообщении жирным шрифтом, курсивом, выделять ссылки и код. Существует два способа выделения текста: Markdown или HTML разметкой, что как раз и определяется в передаваемом /sendMessage параметре _parsemode.


Шаг 4. Постоянная прослушка


Осталось только организовать постоянную прослушку обновлений с бота и дело в шляпе. Запрос обновлений и их обработку можно оформить в функцию _checkupdates, а затем просто вызывать ее в бесконечном цикле с обработкой прерывании бота пользователем:


while True:
    try:
        check_updates()
    except KeyboardInterrupt: # порождается, если бота остановил пользователь
        print('Interrupted by the user')
        break

Вот и готов самый простой бот для Телеграма. Конечно, можно использовать уже готовые оболочки над Bot API, но зачем, если обращение напрямую к HTTP-интерфейсу и так не занимает много места. Пожалуй, на этом остановимся. В будущем можно будет реализовать отправку стикеров, обработку пришедших сообщений, и определенные ответы на определенные слова. Так же с ботами можно общаться посредством команд формата /команда, что очень удобно для каких-либо конкретных, шаблонных запросов к боту.

Поделиться с друзьями
-->

Комментарии (16)


  1. tmnhy
    17.02.2017 10:50

    Почему long polling, а не хуки?


    1. leoismyname
      17.02.2017 12:28

      В случае ботов webhook не видится мне каким-то удобным и оптимальным решением.

      Быстрый старт для кода выше – укажите токен, запустите python bot.py (предполагаю, что как-то так), для решения на основе webhook, как минимум, помимо перечисленного – описание регистрации webhook в Telegram, наличие домена с сертификатом (теперь уже без разницы, самоподписанный или нет), дополнительный код, который будет обрабатывать входящие запросы.


      1. tmnhy
        17.02.2017 12:40
        +1

        > В случае ботов webhook не видится мне каким-то удобным и оптимальным решением.

        Для «wow я умею в bot api», согласен. А если что-то сложнее?
        Тогда, вам и ТСу вопрос. Сколько диалогов, одновременно входящих сообщений такой код выдержит? Какие задержки будут для клиентов бота, когда их станет больше 10?

        Вам не кажется, что через хуки, всем этим это рулить будет много проще. Да, доп. затраты есть (домен, сертификат, роутинг до бота, код сервера), но они на самом деле совсем мизерные, по сравнению с кодом мало-мальски полезного бота.


        1. leoismyname
          17.02.2017 12:53

          Что-то я не понял, о каких соединениях идет речь? Ответом на LP-запрос будет массив сообщений. Не вижу никаких проблем при работе с 10 клиентами.


          1. tmnhy
            17.02.2017 12:58

            Я к тому, что боту могут написать одновременно несколько человек. Пока пример кода ТСа будет обрабатывать массив LP, отвечая запросом на каждый апдейт, кто-то написав боту в это время будет ждать.


            1. leoismyname
              17.02.2017 13:31

              Хоть 10, хоть 100 – для текущего кода разницы нет, так как нет и никакой обработки. Даже если он в ответе на LP получит 500 сообщений, так или иначе он их получит и будет отвечать, пусть не одним, а несколькими запросами, но обновления будут.

              А что с webhook? Написали 500 человек боту, это инициирует отправку 500 запросов к коду? Что если на 100 запросе код перестал отвечать (упала база или просто не смог)? Как получить те сообщения, на которые код не успел ответить? Сколько времени потребуется, чтобы найти все такие сообщения? Или Телеграм сам их будут повторно рассылать спустя какой-то интервал времени? По-моему тут больше проблем и вопросов появляется, чем преимуществ.


              1. tmnhy
                17.02.2017 13:37

                Или Телеграм сам их будут повторно рассылать спустя какой-то интервал времени?

                Если HTTP 200 не получит, то да, будет рассылать какое-то время.

                Хоть 10, хоть 100 – для текущего кода разницы нет, так как нет и никакой обработки.

                Реальность, она несколько отличается от хеллоуворлда, там как раз обработка есть и не всегда это миллисекунды. Если это интерактивный бот, а не спамер, то вполне себе есть разница, получить ответ в течение секунды или минуты.

                И я не против LP, но не так как у ТСа.


    1. leoismyname
      17.02.2017 12:31

      Вот ты мне и скажи, а почему хуки?


      1. tmnhy
        17.02.2017 12:46
        +1

        Ну, помимо «мне лень делать сервер» я не вижу препятствий для них.


  1. tmnhy
    17.02.2017 10:58

    И, у вас «ус отклеился», при отправке сообщения проверка на код 200 в блок except попала, она не сработает никогда. Если надо через exception и возвращаемый код обрабатывать, то assert вам в помощь:

    try:
        resp = request.post()
        assert resp.status_code == 200
    except:
        print('Send message error')
        return
    


    Неаккуратно как-то, а так да, «Студент лентяй» соответствует ;)


    1. NullaCham
      18.02.2017 20:54

      Да, очень глупая ошибка, прошу прощения.


      1. tmnhy
        18.02.2017 21:06

        Пожалуйста, у вас ещё в примере кода плюсики потерялись.


  1. tmnhy
    17.02.2017 11:23
    +5

    Ну, и по существу…

    Начали хорошо:

    На Хабре уже полно статей-туториалов с заголовками «Создание бота на Python»

    А вот дальше… Половина статьи — описание @BotFather, зачем?
    Вторая половина статьи — описание двух POST-запросов, а бот — это несколько больше чем пара запросов из API.

    И питон тут причем? Разве на других ЯП нет библиотек, аналогичных requests?


  1. CrocodileRed
    18.02.2017 10:00

    Отличный пример того, как не нужно делать.


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


    Кроме того, получать апдейты в цикле — не круто по определению.


  1. mizhgun
    18.02.2017 20:34

    И с какой версии Python requests стала стандартной? По вашей же логике, requests — это обертка над urllib3 (и той, в общем случае, нестандартной). Не то, чтобы я придираюсь, просто не наблюдаю в данном случае никакой добавленной ценности от использования requests по сравнению с готовыми обертками, кроме «смотрите, как могу в GET и POST на Pythone».


  1. AlexMt
    19.02.2017 01:40

    while True:
        try:
            check_updates()
        except KeyboardInterrupt: # порождается, если бота остановил пользователь
            print('Interrupted by the user')
            break
    


    Вы серьёзно хотите заваливать сервера telegram сообщениями на всю возможную ширину канала, процессорного времени и памяти, которые есть на сервере?

    Или всё-таки в этой петле не хватает хотя бы time.sleep(1)?