Это пошаговое руководство по созданию бота для Telegram. Бот будет показывать курсы валют, разницу между курсом раньше и сейчас, а также использовать современные встроенные клавиатуры.
Время переходить к делу и узнать наконец, как создавать ботов в Telegram.
Шаг №0: немного теории об API Telegram-ботов
Начать руководство стоит с простого вопроса: как создавать чат-ботов в Telegram?
Ответ очень простой: для чтения сообщений отправленных пользователями и для отправки сообщений назад используется API HTML. Это требует использования URL:
https://api.telegram.org/bot/METHOD_NAME
Токен — уникальная строка из символов, которая нужна для того, чтобы установить подлинность бота в системе. Токен генерируется при создании бота. METHOD_NAME — это метод, например, getUpdates
, sendMessage
, getChat
и так далее.
Токен выглядит приблизительно так:
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
Для выполнения запросов используются как GET, так и POST запросы. Многие методы требуют дополнительных параметров (методу sendMessage
, например, нужно передать chat_id и текст). Эти параметры могут быть переданы как строка запроса URL, application/x-www-form-urlencoded и application-json (кроме загрузки файлов). Еще одно требование — кодировка UTF-8.
После отправки запроса к API, вы получаете ответ в формате JSON. Например, если извлечь данные с помощью метода getME
, ответ будет такой:
GET https://api.telegram.org/bot<token>/getMe
{
ok: true,
result: {
id: 231757398,
first_name: "Exchange Rate Bot",
username: "exchangetestbot"
}
}
Если значение ‘ok’ — true, значит запрос был успешным и результат отобразится в поле ‘field’. Если false — в поле ‘description’ будет сообщение об ошибке.
Список всех типов данных и методов API Telegram-бота можно найти здесь (ENG) или с переводом здесь (ру) .
Следующий вопрос: как получать пользовательские сообщения?
Есть два варианта.
Первый — вручную создавать запросы с помощью метода getUpdates
. В качестве объекта вы получите массив объектов Update
. Этот метод работает как технология длинных опросов (long polling), когда вы отправляете запрос, обрабатываете данные и начинаете повторяете процесс. Чтобы избежать повторной обработки одних и тех же данных рекомендуется использовать параметр offset
.
Второй вариант — использовать webhooks. Метод setWebhook
нужно будет применить только один раз. После этого Telegram будет отправлять все обновления на конкретный URL-адрес, как только они появятся. Единственное ограничение — необходим HTTPS, но можно использовать и сертификаты, заверенные самостоятельно.
Как выбрать оптимальный метод? Метод getUpdates
лучше всего подходит, если:
- Вы не хотите или не можете настраивать HTTPS во время разработки.
- Вы работаете со скриптовыми языками, которые сложно интегрировать в веб-сервер.
- У бота высокая нагрузка.
- Вы меняете сервер бота время от времени.
Метод с Webhook лучше подойдет в таких случаях:
- Вы используете веб-языки (например, PHP).
- У бота низкая нагрузка, и нет смысла делать запросы вручную.
- Бот на постоянной основе интегрирован в веб-сервер.
В этом руководстве будет использоваться метод getUpdates
.
Еще один вопрос: как создать зарегистрировать бота?
@BotFather используется для создания ботов в Telegram. Он также отвечает за базовую настройку (описание, фото профиля, встроенная поддержка и так далее).
Существует масса библиотек, которые облегчают процесс работы с API Telegram-бота. Вот некоторые из них:
- Python
pyTelegramBotAPI (TeleBot)
Telepot
Aiogram - PHP
Telegram Bot API — PHP SDK + Laravel Integration - Java
TelegramBots - NodeJS
Telegram Node Bot - Ruby
Telegram Bot - C#
Telegram Bot API LIbrary
По своей сути, все эти библиотеки — оболочки HTML-запросов. Большая часть из них написана с помощью принципов ООП. Типы данных Telegram Bot API представлены в виде классов.
В этом руководстве будет использоваться библиотека pyTelegramBotApi.
Шаг №1: реализовать запросы курсов валют
Весь код был проверен на версии Python==3.7 c использование библиотек:
pyTelegramBotAPI==3.6.6
pytz==2019.1
requests==2.7.0
Начать стоит с написания Python-скрипта, который будет реализовывать логику конкретных запросов курсов валют. Использовать будем PrivatBank API. URL: https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5.
Пример ответа:
[
{
ccy:"USD",
base_ccy:"UAH",
buy:"25.90000",
sale:"26.25000"
},
{
ccy:"EUR",
base_ccy:"UAH",
buy:"29.10000",
sale:"29.85000"
},
{
ccy:"RUR",
base_ccy:"UAH",
buy:"0.37800",
sale:"0.41800"
},
{
ccy:"BTC",
base_ccy:"USD",
buy:"11220.0384",
sale:"12401.0950"
}
]
Создадим файл pb.py
со следующим кодом:
import re
import requests
import json
URL = 'https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5'
def load_exchange():
return json.loads(requests.get(URL).text)
def get_exchange(ccy_key):
for exc in load_exchange():
if ccy_key == exc['ccy']:
return exc
return False
def get_exchanges(ccy_pattern):
result = []
ccy_pattern = re.escape(ccy_pattern) + '.*'
for exc in load_exchange():
if re.match(ccy_pattern, exc['ccy'], re.IGNORECASE) is not None:
result.append(exc)
return result
Были реализованы три метода:
load_exchange
: загружает курсы валют по указанному URL-адресу и возвращает их в формате словаря(dict).get_exchange
: возвращает курсы валют по запрошенной валюте.get_exchanges
: возвращает список валют в соответствии с шаблоном (требуется для поиска валют во встроенных запросах).
Шаг №2: создать Telegram-бота с помощью @BotFather
Необходимо подключиться к боту @BotFather, чтобы получить список чат-команд в Telegram. Далее нужно набрать команду /newbot
для инструкций выбора название и имени бота. После успешного создания бота вы получите следующее сообщение:
Done! Congratulations on your new bot. You will find it at telegram.me/<username>.
You can now add a description, about section and profile picture for your bot, see /help for a list of commands.
By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it.
Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
<token> (here goes the bot’s token)
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
Его нужно сразу настроить. Необходимо добавить описание и текст о боте (команды /setdescription
и /setabouttext
), фото профиля (/setuserpic
), включить встроенный режим (/setinline
), добавить описания команд (/setcommands
). Потребуется использовать две команды: /help
и /exchange
. Стоит описать их в /setcommands
.
Теперь, когда настройка закончена, можно переходить к написанию кода. Прежде чем двигаться дальше, рекомендуется почитать об API и ознакомиться с документацией библиотеки, чтобы лучше понимать то, о чем пойдет речь дальше.
Шаг №3: настроить и запустить бота
Начнем с создания файла config.py
для настройки:
TOKEN = '<bot token>' # заменить на токен своего бота
TIMEZONE = 'Europe/Kiev'
TIMEZONE_COMMON_NAME = 'Kiev'
В этом файле указаны: токен бота и часовой пояс, в котором тот будет работать (это понадобится в будущем для определения времени обновления сообщений. API Telegram не позволяет видеть временную зону пользователя, поэтому время обновления должно отображаться с подсказкой о часовом поясе).
Создадим файл bot.py
. Нужно импортировать все необходимые библиотеки, файлы с настройками и предварительно созданный pb.py
. Если каких-то библиотек не хватает, их можно установить с помощью pip
.
import telebot
import config
import pb
import datetime
import pytz
import json
import traceback
P_TIMEZONE = pytz.timezone(config.TIMEZONE)
TIMEZONE_COMMON_NAME = config.TIMEZONE_COMMON_NAME
Создадим бота с помощью библиотеки pyTelegramBotAPI. Для этого конструктору нужно передать токен:
bot.py
bot = telebot.TeleBot(config.TOKEN)
bot.polling(none_stop=True)
Шаг №4: написать обработчик команды /start
Теперь чат-бот на Python работает и постоянно посылает запросы с помощью метода getUpdates
. Параметр none_stop
отвечает за то, чтобы запросы отправлялись, даже если API возвращает ошибку при выполнении метода.
Из переменной бота возможно вызывать любые методы API Telegram-бота.
Начнем с написания обработчика команды /start
и добавим его перед строкой bot.polling(none_stop=True)
:
@bot.message_handler(commands=['start'])
def start_command(message):
bot.send_message(
message.chat.id,
'Greetings! I can show you exchange rates.\n' +
'To get the exchange rates press /exchange.\n' +
'To get help press /help.'
)
Как можно видеть, pyTelegramBotApi использует декораторы Python для запуска обработчиков разных команд Telegram. Также можно перехватывать сообщения с помощью регулярных выражений, узнавать тип содержимого в них и лямбда-функции.
В нашем случае если условие commands=['start']
равно True
, тогда будет вызвана функция start_command
. Объект сообщения (десериализованный тип Message
) будет передан функции. После этого вы просто запускаете send_message
в том же чате с конкретным сообщением.
Это было просто, не так ли?
Шаг №5: создать обработчик команды /help
Давайте оживим обработчик команды /help
с помощью встроенной кнопки со ссылкой на ваш аккаунт в Telegram. Кнопку можно озаглавить “Message the developer”.
@bot.message_handler(commands=['help'])
def help_command(message):
keyboard = telebot.types.InlineKeyboardMarkup()
keyboard.add(
telebot.types.InlineKeyboardButton(
'Message the developer', url='telegram.me/artiomtb'
)
)
bot.send_message(
message.chat.id,
'1) To receive a list of available currencies press /exchange.\n' +
'2) Click on the currency you are interested in.\n' +
'3) You will receive a message containing information regarding the source and the target currencies, ' +
'buying rates and selling rates.\n' +
'4) Click “Update” to receive the current information regarding the request. ' +
'The bot will also show the difference between the previous and the current exchange rates.\n' +
'5) The bot supports inline. Type @<botusername> in any chat and the first letters of a currency.',
reply_markup=keyboard
)
Как видно в примере выше, был использован дополнительный параметр (reply_markup
) для метода send_message
. Метод получил встроенную клавиатуру (InlineKeyboardMarkup
) с одной кнопкой (InlineKeyboardButton
) и следующим текстом: “Message the developer” и url='telegram.me/artiomtb'
.
Код выше выглядит вот так:
Шаг №6: добавить обработчик команды /exchange
Обработчик команды /exchange
отображает меню выбора валюты и встроенную клавиатуру с 3 кнопками: USD, EUR и RUR (это валюты, поддерживаемые API банка).
@bot.message_handler(commands=['exchange'])
def exchange_command(message):
keyboard = telebot.types.InlineKeyboardMarkup()
keyboard.row(
telebot.types.InlineKeyboardButton('USD', callback_data='get-USD')
)
keyboard.row(
telebot.types.InlineKeyboardButton('EUR', callback_data='get-EUR'),
telebot.types.InlineKeyboardButton('RUR', callback_data='get-RUR')
)
bot.send_message(
message.chat.id,
'Click on the currency of choice:',
reply_markup=keyboard
)
Вот как работает InlineKeyboardButton
. Когда пользователь нажимает на кнопку, вы получаете CallbackQuery (в параметре data
содержится callback-data
) в getUpdates
. Таким образом вы знаете, какую именно кнопку нажал пользователь, и как ее правильно обработать.
Вот как работает ответ /exchange:
Шаг №7: написать обработчик для кнопок встроенной клавиатуры
В библиотеке pyTelegramBot Api есть декоратор @bot.callback_query_handler
, который передает объект CallbackQuery
во вложенную функцию.
@bot.callback_query_handler(func=lambda call: True)
def iq_callback(query):
data = query.data
if data.startswith('get-'):
get_ex_callback(query)
Давайте реализуем метод get_ex_callback
:
def get_ex_callback(query):
bot.answer_callback_query(query.id)
send_exchange_result(query.message, query.data[4:])
Метод answer_callback_query
нужен, чтобы убрать состояние загрузки, к которому переходит бот после нажатия кнопки. Отправим сообщение send_exchange_query
. Ему нужно передать Message
и код валюты (получить ее можно из query.data
. Если это, например, get-USD, передавайте USD).
Реализуем send_exchange_result
:
def send_exchange_result(message, ex_code):
bot.send_chat_action(message.chat.id, 'typing')
ex = pb.get_exchange(ex_code)
bot.send_message(
message.chat.id, serialize_ex(ex),
reply_markup=get_update_keyboard(ex),
parse_mode='HTML'
)
Все довольно просто.
Сперва отправим состояние ввода в чат, так чтобы бот показывал индикатор «набора текста», пока API банка получает запрос. Теперь вызовем метод get_exchange
из файла pb.py
, который получит код валюты (например, USD). Также нужно вызвать два новых метода в send_message: serialize_ex
, сериализатор валюты и get_update_keyboard
(который возвращает клавиатуре кнопки “Update” и “Share”).
def get_update_keyboard(ex):
keyboard = telebot.types.InlineKeyboardMarkup()
keyboard.row(
telebot.types.InlineKeyboardButton(
'Update',
callback_data=json.dumps({
't': 'u',
'e': {
'b': ex['buy'],
's': ex['sale'],
'c': ex['ccy']
}
}).replace(' ', '')
),
telebot.types.InlineKeyboardButton('Share', switch_inline_query=ex['ccy'])
)
return keyboard
Запишем в get_update_keyboard
текущий курс валют в callback_data
в форме JSON. JSON сжимается, потому что максимальный разрешенный размер файла равен 64 байтам.
Кнопка t
значит тип, а e
— обмен. Остальное выполнено по тому же принципу.
У кнопки Share
есть параметр switch_inline_query
. После нажатия кнопки пользователю будет предложено выбрать один из чатов, открыть этот чат и ввести имя бота и определенный запрос в поле ввода.
Методы serialize_ex
и дополнительный serialize_exchange_diff
нужны, чтобы показывать разницу между текущим и старыми курсами валют после нажатия кнопки Update
.
def serialize_ex(ex_json, diff=None):
result = '<b>' + ex_json['base_ccy'] + ' -> ' + ex_json['ccy'] + ':</b>\n\n' + \
'Buy: ' + ex_json['buy']
if diff:
result += ' ' + serialize_exchange_diff(diff['buy_diff']) + '\n' + \
'Sell: ' + ex_json['sale'] + \
' ' + serialize_exchange_diff(diff['sale_diff']) + '\n'
else:
result += '\nSell: ' + ex_json['sale'] + '\n'
return result
def serialize_exchange_diff(diff):
result = ''
if diff > 0:
result = '(' + str(diff) + ' <img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="↗️" src="https://s.w.org/images/core/emoji/2.3/svg/2197.svg">" src="https://s.w.org/images/core/emoji/2.3/svg/2197.svg">" src="https://s.w.org/images/core/emoji/2.3/svg/2197.svg">" src="https://s.w.org/images/core/emoji/72x72/2197.png">" src="https://s.w.org/images/core/emoji/72x72/2197.png">)'
elif diff < 0:
result = '(' + str(diff)[1:] + ' <img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="<img draggable="false" data-mce-resize="false" data-mce-placeholder="1" data-wp-emoji="1" class="emoji" alt="↘️" src="https://s.w.org/images/core/emoji/2.3/svg/2198.svg">" src="https://s.w.org/images/core/emoji/2.3/svg/2198.svg">" src="https://s.w.org/images/core/emoji/2.3/svg/2198.svg">" src="https://s.w.org/images/core/emoji/72x72/2198.png">" src="https://s.w.org/images/core/emoji/72x72/2198.png">)'
return result
Как видно, метод serialize_ex
получает необязательный параметр diff
. Ему будет передаваться разница между курсами обмена в формате {'buy_diff': <float>, 'sale_diff': <float>}
. Это будет происходить во время сериализации после нажатия кнопки Update
. Когда курсы валют отображаются первый раз, он нам не нужен.
Вот как будет выглядеть бот после нажатия кнопки USD:
Шаг №8: реализовать обработчик кнопки обновления
Теперь можно создать обработчик кнопки Update
. После дополнения метода iq_callback_method
он будет выглядеть следующим образом:
@bot.callback_query_handler(func=lambda call: True)
def iq_callback(query):
data = query.data
if data.startswith('get-'):
get_ex_callback(query)
else:
try:
if json.loads(data)['t'] == 'u':
edit_message_callback(query)
except ValueError:
pass
Если данные обратного вызова начинаются с get-
(get-USD
, get-EUR
и так далее), тогда нужно вызывать get_ex_callback
, как раньше. В противном случае стоит попробовать разобрать строку JSON и получить ее ключ t
. Если его значение равно u
, тогда нужно вызвать метод edit_message_callback
. Реализуем это:
def edit_message_callback(query):
data = json.loads(query.data)['e']
exchange_now = pb.get_exchange(data['c'])
text = serialize_ex(
exchange_now,
get_exchange_diff(
get_ex_from_iq_data(data),
exchange_now
)
) + '\n' + get_edited_signature()
if query.message:
bot.edit_message_text(
text,
query.message.chat.id,
query.message.message_id,
reply_markup=get_update_keyboard(exchange_now),
parse_mode='HTML'
)
elif query.inline_message_id:
bot.edit_message_text(
text,
inline_message_id=query.inline_message_id,
reply_markup=get_update_keyboard(exchange_now),
parse_mode='HTML'
)
Как это работает? Очень просто:
- Загружаем текущий курс валюты (
exchange_now = pb.get_exchange(data['c'])
). - Генерируем текст нового сообщения путем сериализации текущего курса валют с параметром
diff
, который можно получить с помощью новых методов (о них дальше). Также нужно добавить подпись —get_edited_signature
. - Вызываем метод
edit_message_text
, если оригинальное сообщение не изменилось. Если это ответ на встроенный запрос, передаем другие параметры.
Метод get_ex_from_iq_data
разбирает JSON из callback_data
:
def get_ex_from_iq_data(exc_json):
return {
'buy': exc_json['b'],
'sale': exc_json['s']
}
Метод get_exchange_diff
получает старое и текущее значение курсов валют и возвращает разницу в формате {'buy_diff': <float>, 'sale_diff': <float>}
:
def get_exchange_diff(last, now):
return {
'sale_diff': float("%.6f" % (float(now['sale']) - float(last['sale']))),
'buy_diff': float("%.6f" % (float(now['buy']) - float(last['buy'])))
}
get_edited_signature
генерирует текст “Updated…”:
def get_edited_signature():
return '<i>Updated ' + \
str(datetime.datetime.now(P_TIMEZONE).strftime('%H:%M:%S')) + \
' (' + TIMEZONE_COMMON_NAME + ')</i>'
Вот как выглядит сообщение после обновления, если курсы валют не изменились:
И вот так — если изменились:
Шаг №9: реализовать встроенный режим
Реализация встроенного режима значит, что если пользователь введет @ + имя бота в любом чате, это активирует поиск введенного текста и выведет результаты. После нажатия на один из них бот отправит результат от вашего имени (с пометкой “via bot”).
@bot.inline_handler(func=lambda query: True)
def query_text(inline_query):
bot.answer_inline_query(
inline_query.id,
get_iq_articles(pb.get_exchanges(inline_query.query))
)
Обработчик встроенных запросов реализован.
Библиотека передаст объект InlineQuery
в функцию query_text
. Внутри используется функция answer_line
, которая должна получить inline_query_id
и массив объектов (результаты поиска).
Используем get_exchanges
для поиска нескольких валют, подходящих под запрос. Нужно передать этот массив методу get_iq_articles
, который вернет массив из InlineQueryResultArticle
:
def get_iq_articles(exchanges):
result = []
for exc in exchanges:
result.append(
telebot.types.InlineQueryResultArticle(
id=exc['ccy'],
title=exc['ccy'],
input_message_content=telebot.types.InputTextMessageContent(
serialize_ex(exc),
parse_mode='HTML'
),
reply_markup=get_update_keyboard(exc),
description='Convert ' + exc['base_ccy'] + ' -> ' + exc['ccy'],
thumb_height=1
)
)
return result
Теперь при вводе “@exchangetestbost + пробел” вы увидите следующее:
Попробуем набрать usd, и результат мгновенно отфильтруется:
Проверим предложенный результат:
Кнопка “Update” тоже работает:
Отличная работа! Вы реализовали встроенный режим!
Выводы
Поздравляем! Теперь вы знаете, как сделать бота для Telegram, добавить встроенную клавиатуру, обновление сообщений и встроенный режим. Можете похлопать себя по спине и поднять тост за нового бота.
Источник: How to make a bot: a guide to your first Python chat bot for Telegram