Обложка статьи «Telegram-бот на Python: от первой строчки до запуска на Heroku»

Telegram-бот на Python: от первой строчки до запуска на Heroku

Перевод статьи «How to Create and Deploy a Telegram Bot?»

Напишем простой диалоговый Telegram-бот на Python, который в дальнейшем можно дополнить различными функциями, и запустим его на сервере Heroku.

    1. Настройка
    2. Создание Telegram-бота
    3. Деплой на Heroku
    4. Заключение

Настройка

Откройте Telegram, найдите @BotFather и начните беседу. Отправьте команду /newbot и следуйте инструкциям. Вы получите:

  • свой токен;
  • адрес Telegram API (https://api.telegram.org/bot);
  • ссылку на документацию.

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

Если вы в первый раз работаете с API, то разобраться поможет браузер. Откройте новую вкладку и перейдите по ссылке:

https://api.telegram.org/bot<ваш_токен>/getUpdates

Так вы отправите запрос на сервер Telegram, и он ответит вам в формате JSON:

{
  "ok":true,
  "result":[{
    "update_id":523349956,
    "message":{
      "message_id":51,
      "from":{
        "id":303262877,
        "first_name":"YourName"
      },
      "chat":{
        "id":303262877,
        "first_name":"YourName",
        "type":"private"
      },
      "date":1486829360,
      "text":"Hello"
    }
  }]
}

Если вы откроете раздел метода /sendMessage, то увидите, что он принимает два параметра: chat_id и text. Вы можете создавать цепочки параметров в адресной строке браузера, используя ? для первого параметра и & для всех последующих. Команда для отправки сообщения будет выглядеть так:

/sendMessage?chat_id=303262877&text=test

Попробуйте получить ответ от вашего бота, подставив в качестве chat_id значение, полученное после вызова /getUpdates (в нашем примере это 303262877). Текст сообщения может быть любым. Запрос должен выглядеть так:

https://api.telegram.org/bot<token>/sendMessage?chat_id=303262877&text=Hello

Установка Python

Для написания Telegram-бота на Python, нужно установить сам язык. Если вы пользуетесь Windows, скачать Python можно с официального сайта. Версия не важна, но в этой статье будет использоваться Python 3.x. Если же у вас Linux или macOS, то обе версии уже установлены.

Тем, кто только начал изучение этого языка, будет также полезна дорожная карта Python-разработчика.

Установка pip

Это менеджер пакетов. В версиях выше Python 2.7.9 и Python 3.4, а также на macOS/Linux он уже есть. Проверить это можно командой pip --version в терминале. Если же по каким-то причинам он отсутствует, установить его можно при помощи команды:

$ sudo apt-get install python-pip

Загвоздка в том, что разные версии Python используют разные pip. Если у вас macOS, вы можете попробовать следовать советам со Stack Overflow. В случае с Windows вам нужно скачать get-pip.py, открыть командную строку, перейти в директорию со скачанным файлом и выполнить команду:

$ python get-pip.py

Установим пакет requests с помощью pip:

$ pip install requests

Создание Telegram-бота

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

Первое сообщение можно расценивать как самое свежее, но getUpdates возвращает все обновления за последние 24 часа. Напишем скрипт, чтобы получить самое последнее обновление:

import requests

url = "https://api.telegram.org/bot<ваш_токен>/"

def get_updates_json(request):  
    response = requests.get(request + 'getUpdates')
    return response.json()

def last_update(data):  
    results = data['result']
    total_updates = len(results) - 1
    return results[total_updates]

Словарь обновлений состоит из двух элементов: ok и results. Нас интересует вторая часть — список всех обновлений, полученных ботом за последние 24 часа. Теперь нужно добавить ещё 2 функции. Первая будет доставать chat_id из обновления, а вторая — отправлять сообщение:

def get_chat_id(update):  
    chat_id = update['message']['chat']['id']
    return chat_id

def send_mess(chat, text):  
    params = {'chat_id': chat, 'text': text}
    response = requests.post(url + 'sendMessage', data=params)
    return response
chat_id = get_chat_id(last_update(get_updates_json(url)))
send_mess(chat_id, 'Your message goes here')

Помните, как мы объединяли параметры при помощи ? и &? Вы можете сделать то же самое, добавив словарь в качестве второго дополнительного параметра в функциях get/post из пакета requests.

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

Чтобы бот слушал сервер и получал обновления, нужно запустить основной цикл. После import requests добавьте from time import sleep и замените две последние строки на следующий код:

def main():  
    update_id = last_update(get_updates_json(url))['update_id']
    while True:
        if update_id == last_update(get_updates_json(url))['update_id']:
           send_mess(get_chat_id(last_update(get_updates_json(url))), 'test')
           update_id += 1
        sleep(1)       

if __name__ == '__main__':  
    main()

Хотя мы и добавили таймаут в 1 секунду, пример выше можно использовать только в обучающих целях, поскольку он использует частые опросы (short polling), что плохо влияет на сервер Telegram.

Есть ещё два способа получения обновлений через API — длинные опросы (long polling) и вебхуки (webhooks). Если мы будем использовать способ получения обновлений через getUpdates без параметров, то запросы будут происходить слишком часто.

Переключимся на длинные опросы. Сперва изменим первую функцию, добавив в неё параметр timeout. Сам по себе он не уменьшит частоту проверки обновлений и будет работать только в том случае, когда обновлений нет. Чтобы помечать уже просмотренные обновления, нужно добавить параметр сдвига offset:

def get_updates_json(request):  
    params = {'timeout': 100, 'offset': None}
    response = requests.get(request + 'getUpdates', data=params)
    return response.json()

Теперь Telegram-бот на Python должен работать нормально, но его всё ещё можно улучшить. Давайте инкапсулируем все функции в один класс. Должно получиться что-то вроде этого:

import requests  
import datetime

class BotHandler:

    def __init__(self, token):
        self.token = token
        self.api_url = "https://api.telegram.org/bot{}/".format(token)

    def get_updates(self, offset=None, timeout=30):
        method = 'getUpdates'
        params = {'timeout': timeout, 'offset': offset}
        resp = requests.get(self.api_url + method, params)
        result_json = resp.json()['result']
        return result_json

    def send_message(self, chat_id, text):
        params = {'chat_id': chat_id, 'text': text}
        method = 'sendMessage'
        resp = requests.post(self.api_url + method, params)
        return resp

    def get_last_update(self):
        get_result = self.get_updates()

        if len(get_result) > 0:
            last_update = get_result[-1]
        else:
            last_update = get_result[len(get_result)]

        return last_update

Последний штрих — объявим переменные и научим бота приличным манерам. Сделаем так, чтобы бот приветствовал вас раз в день, при этом фраза должна зависеть от времени суток. Для этого добавьте следующий код в ваш скрипт:

greet_bot = BotHandler(token)  
greetings = ('здравствуй', 'привет', 'ку', 'здорово')  
now = datetime.datetime.now()


def main():  
    new_offset = None
    today = now.day
    hour = now.hour

    while True:
        greet_bot.get_updates(new_offset)

        last_update = greet_bot.get_last_update()

        last_update_id = last_update['update_id']
        last_chat_text = last_update['message']['text']
        last_chat_id = last_update['message']['chat']['id']
        last_chat_name = last_update['message']['chat']['first_name']

        if last_chat_text.lower() in greetings and today == now.day and 6 <= hour < 12:
            greet_bot.send_message(last_chat_id, 'Доброе утро, {}'.format(last_chat_name))
            today += 1

        elif last_chat_text.lower() in greetings and today == now.day and 12 <= hour < 17:
            greet_bot.send_message(last_chat_id, 'Добрый день, {}'.format(last_chat_name))
            today += 1

        elif last_chat_text.lower() in greetings and today == now.day and 17 <= hour < 23:
            greet_bot.send_message(last_chat_id, 'Добрый вечер, {}'.format(last_chat_name))
            today += 1

        new_offset = last_update_id + 1

if __name__ == '__main__':  
    try:
        main()
    except KeyboardInterrupt:
        exit()

Теперь вы можете улучшать бота так, как захотите. Можно, например, настроить отправку медиафайлов или добавить собственные кнопки.

Деплой на Heroku

Последним шагом будет развёртывание бота на сервере. Зарегистрируйтесь на GitHub, если у вас ещё нет там аккаунта, и установите Git. Для этого на Linux выполните следующую команду:

$ sudo apt-get install git-all

На macOS и Windows его нужно скачать и установить вручную. И не забудьте зарегистрироваться на Heroku. Установите virtualenv:

$ pip install virtualenv

Создайте новую папку и перейдите в неё в терминале или командной строке. Инициализируйте в ней virtualenv:

$ virtualenv my_env

Имя не имеет значения, но лучше сделать его интуитивно понятным. Перейдите в папку my_env. Теперь нужно склонировать git-репозиторий. Введите команду:

$ git clone https://github.com/имя_вашего_профиля/имя_вашего_репозитория

Поместите скрипт в папку, полученную в результате выполнения команды git clone. Вернитесь в папку my_env и запустите virtualenv:

  • На Windows:
    $ scripts\activate.bat
  • На Linux/macOS:
    $ source bin/activate

Если вы успешно запустили virtualenv, приглашение командной строки должно начинаться с (my_env). Перейдите в папку репозитория и ещё раз установите модуль requests:

$ pip install requests

Теперь нужно создать список зависимостей Heroku. Это несложно. Введите:

$ pip freeze > requirements.txt

Создайте Procfile. В этом файле следует разместить инструкции по работе со скриптом. Имя файла обязательно должно быть Procfile (Procfile.windows в случае с Windows). У него не должно быть других расширений. Содержимое файла должно быть таким (замените my_bot на имя вашего скрипта):

web: python my_bot.py

Добавьте файл __init__.py в вашу папку. Он может быть пустым, но должен там быть. Отправьте коммит с изменениями в репозиторий:

$ git init
$ git add .
$ git commit -m ‘короткое сообщение, описывающее изменения в коммите’
$ git push -u https://github.com/имя_вашего_профиля/имя_вашего_репозитория

Теперь развернём Telegram-бота на Heroku. Можно использовать и панель управления на сайте, но мы потренируемся делать всё через консоль.

Если вы пользуетесь macOS или Windows, установите интерфейс командной строки, следуя гайду. Если у вас Ubuntu, используйте следующие команды:

$ sudo add-apt-repository "deb https://cliassets.heroku.com/branches/stable/apt ./"
$ curl -L https://cli-assets.heroku.com/apt/release.key |
$ sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install heroku

Теперь выполните следующие команды:

$ heroku login
$ heroku create
$ git push heroku master
$ heroku ps:scale web=1
$ heroku open

С этого момента приложение должно работать на сервере Heroku. Если что-то пойдёт не так, проверить логи можно следующим образом:

$ heroku logs --tail

Заключение

Поздравляем! Теперь вы знаете, как писать ботов для Telegram на Python. Лучше изучить возможности Telegram-ботов поможет видеокурс — в нём используется PHP, но суть та же.