Боты для Telegram

В настоящее время существует достаточно много разных популярных мессенджеров. Достоинством мессенджера Telegram является наличие богатого API, позволяющего взаимодействовать с мессенжером не людям, а программам, то есть писать боты.

Краткое введение в Telegram Bot API

Можете изучить полную документацию на Telegram Bot API, но непосредственно этим API мы пользоваться не будем. Но нужно понять, как это устроено внутри.

На самом простом уровне это API, использующее HTTP с JSON-ответом. Вы можете промоделировать работу бота, просто используя GET-запросы в браузере, то есть загружая страницы с определённым адресом используя браузер.

Самый простой пример тестовой страницы, которую можно загрузить при помощи API:

https://api.telegram.org/bot123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11/getMe

Вы можете скопировать этот адрес и вставить его в адресную строку, нажать Enter и получить JSON-документ с ответом. Ответ будет печальным: Unathorized. Это связано с тем, что для доступа к АPI нужен специальный ключ (токен), который мы заменили на последовательность цифр 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. Токен идентифицирует вашего бота и его необходимо “предъявлять” при каждом обращении вашего бота к API. Мы же использовали просто какую-то последовательность символов для примера. Поэтому прежде чем запустить бота, необходимо этот токен получить, для чего используется специальный бот, называемый @BotFather. Откройте диалог с этим ботом в телеграме, наберите команду /start и ответьте на два вопроса:

  1. Название бота (произвольное текстовое имя),
  2. Имя пользователя для бота (уникальное, из латинских букв, цифр, символа подчёркивания, должно заканчиваться на bot).

В ответ вы получите от @BotFather токен.

Дальнейшее описание Telegram Bot API не нужно для выполнения заданий (т.к. мы будем использовать упрощающую разработку ботов библиотеку), но полезно для понимания.

Пример простого запроса getMe для проверки токена, который можно просто ввести в адресную строку браузера, заменив <token> на токен вашего бота:

https://api.telegram.org/bot<token>/getMe

В ответ вы должны получить небольшой JSON с информацией о вашем боте.

Если написать боту сообщение (например, с телефона или используя web.telegram.org), то это сообщение будет храниться на серверах Telegram. Чтобы получить сообщения, адресованные боту, необходимо выполнить запрос getUpdates:

https://api.telegram.org/bot<token>/getUpdates

Вы получите JSON, содержащий все сообщения, написанные боту, за последние 24 часа. Повторно вызвав этот запрос, мы снова получим все сообщения с сервера. Чтобы не получать сообщения повторно, можно передать запросу параметр offset, например

https://api.telegram.org/bot<token>/getUpdates?offset=<update_id>

где <update_id> — минимальное значение параметра update_id, начиная с которого вы хотите получить сообщения. Оно должно быть на 1 больше значения параметра update_id последнего сообщения, которое вы получили. Все сообщения с меньшим значением update_id вам больше не будут отдаваться.

Для того, чтобы отправить сообщение, вам необходимо сделать запрос sendMessage с двумя обязательными параметрами в адресной строке: chat_id —идентификатор чата для отправки сообщения и text — сообщение, отправляемое пользователю. Например:

https://api.telegram.org/bot<token>/sendMessage?chat_id=123456789&text=Hello,%20world!

Таким образом, простейшая схема реализации бота следующая. Бот — это постоянно запущенное приложение, которое регулярно опрашивает сервер, посылая запросы getUpdates и “засыпая” на некоторое время после этого. Если ответ сервера содержит какие-то новые сообщения, то их нужно обработать и отправить запросы sendMessage для отправки ответных сообщений.

Библиотека pytelegrambotapi (telebot)

Установка библиотеки

Как мы видим, реализация бота содержит много стандартной работы, типа запуск цикла обработки сообщений с учётом значения offset, разбор полученного JSON, ответ на все сообщения, отправка запросов, для ответа на сообщения. Для этого есть ряд библиотек, содержащих реализацию этой рутины, одна из наиболее популярных из таких библиотек — telebot.

Её необходимо установить при помощи pip, в pip она называется pytelegrambotapi. Соответствующая консольная команда может выглядеть, например, так:

pip3 install --user pytelegrambotapi

Сразу же установите библиотеку requests, она пригодится для выполнения ряда заданий.

pip3 install --user requests

Под Windows команда называется просто pip. Параметр --user необходим, если вы запускаете pip под обычным пользователем и хотите установить библиотеку в пользовательский каталог.

Простой обработчик сообщений

Пример простого бота, который на любое сообщение всегда отвечает одним словом “Привет!”:

import telebot

TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'

bot = telebot.TeleBot(TOKEN)

@bot.message_handler(content_types=['text'])
def get_text_messages(message):
    bot.send_message(message.from_user.id, 'Привет!')

bot.infinity_polling()

В этом примере создаётся объект с именем bot класса telebot.TeleBot, которому в конструктор передаётся значение токена в качестве параметра. Затем объявляется функция get_text_message, принимающая в качестве параметра объект message, в котором будет хранится информация о полученном сообщении.

Эта функция регистрируется в качестве обработчика для сообщений типа “text”, что делается при помощи так называемых “декораторов” — это строчка, начинающаяся со значка “@” перед нашей функцией.

Декоратор: это специальная функция (или метод), которая получает другую функцию в качестве параметра, и создаёт новую функцию, используя переданную ей функцию. Строка, начинающаяся с “@” — это на самом деле вызов метода bot.message_handler, который зарегистрирует функцию get_text_messages, как обработчик для сообщений указанного типа. Подробней про декораторы можно прочитать, например, здесь.

Теперь при поступлении сообщений объект bot будет вызывать функцию get_text_messages для каждого сообщения отдельно, тем самым принимая на себя работу по получению и парсингу полученных сообщений. Вам нужно всего лишь определить, что делает бот для ответа на полученное сообщение. Вы можете отправлять сообщения, вызывая метод send_message с параметрами: идентификатор чата и текст отправляемого сообщения.

Наконец, запускается метод polling, который представляет собой бесконечный (none_stop=True) цикл запроса обновлений с сервера, который и будет вызывать функцию get_text_messages при получении новых сообщений.

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

Обработчик команд

Команды для бота — это сообщения, которые начинаются с символа «/», например, /start. Это обычные сообщения, но telebot умеет распознавать сообщения, как команды, поэтому отдельную функцию можно зарегистрировать, как обработчик одной или нескольких команд. Для этого в декоратор @bot.message_handler нужно передать параметр commands, принимающий значение списка строк, являющихся названиями команды. Например:

@bot.message_handler(commands=['start'])
def start_command(message):
    ...

При этом поле message.text хранит полный текст команды, начиная с самой команды. Например, из этого поля можно извлечь параметры команды.

Также используя @BotFather можно задать список команд для бота, при помощи действий Edit Bot — Edit Commands. Далее нужно задать список команд бота и их описание. Тогда эти команды появятся по кнопке меню бота.

Реализация клавиатуры

Команды, набираемые пользователем, сложно декодировать, да и пользователь может ошибиться в их написании. Поэтому в Телеграме у ботов есть возможность использования клавиатуры для выбора стандартных действий пользователя. Есть два типа клавиатуры: ReplyKeyboardMarkup (более древняя) и InlineKeyboardMarkup (более современная и богатая возможностями), на второй и остановимся.

В клавиатуру можно добавлять кнопки, у кнопки есть два параметра: text — это надпись, отображаемая на кнопке, и callback_data — это информация, которая будет передана в обработчик нажатия на кнопку. Сначала создадим все кнопки, потом добавим их в объект-клавиатуру. Метод row у клавиатуры добавляет к клавиатуре ряд из нескольких кнопок, если вызвать метод row ещё раз, то будет добавлен следущий ряд кнопок, и т.д. После этого при отправке сообщения укажем параметр reply_markup, указав созданную клавиатуру.

@bot.message_handler(content_types=['text'])
def get_text_messages(message):
    keyboard = telebot.types.InlineKeyboardMarkup()
    button1 = telebot.types.InlineKeyboardButton(text="Кнопка1", callback_data="button1")
    button2 = telebot.types.InlineKeyboardButton(text="Кнопка2", callback_data="button2")
    keyboard.row(button1, button2)
    bot.send_message(message.from_user.id, "Привет!",  reply_markup=keyboard)

Теперь нам необходимо сделать обработчики кнопок. Это отдельные функции, которые необходимо зарегистрировать при помощи декоратора callback_query_handler. Эта функция будет получать объект callback_obj, которых хранит информацию о нажатии кнопки. В частности, помимо обычных данных о пользователе, чате и т.д. у этого объекта есть поле data, в котором хранится та самая информация, которая была привязана к кнопке. Пример такого обработчика:

@bot.callback_query_handler(func=lambda call: True)
def callback_function1(callback_obj):
    if callback_obj.data == "button1":
        bot.send_message(callback_obj.from_user.id, "Вы нажали на кнопку 1")
    else:
        bot.send_message(callback_obj.from_user.id, "Вы нажали на кнопку 2")
    bot.answer_callback_query(callback_query_id=callback_obj.id)

Обратите внимание на вызов метода answer_callback_query. Он сообщает серверу телеграма, что обработка данного callback-запроса завершена. Если этот метод не вызвать, то на кнопке будут изображены часики, а сама кнопка будет недоступна для повторного нажатия.

Есть и другой способ определить, какая кнопка была нажата. Обратите внимание на параметр декоратора: func=lambda call: True. Это лямбда-функция, которая в данном случае всегда возвращает True. Это означает, что данный обработчик будет применяться ко всем callback-запросам. Но если эта функция будет возвращать не всегда True, то так можно установить обработчик для отдельных callback-запросов, а именно, будет вызван тот обработчик, для которого эта функция вернёт True.

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

@bot.callback_query_handler(func=lambda call: call.data == "button1")
def callback_function1(callback_obj):
    bot.send_message(callback_obj.from_user.id, "Вы нажали на кнопку 1")
    bot.answer_callback_query(callback_query_id=callback_obj.id)

@bot.callback_query_handler(func=lambda call: call.data == "button2")
def callback_function2(callback_obj):
    bot.send_message(callback_obj.from_user.id, "Вы нажали на кнопку 2")
    bot.answer_callback_query(callback_query_id=callback_obj.id)