650 lines
29 KiB
Markdown
650 lines
29 KiB
Markdown
|
---
|
|||
|
title: "🤖 Howto Make Telegram Bot"
|
|||
|
date: 2023-05-28T04:02:05+03:00
|
|||
|
draft: true
|
|||
|
tags: [python, tutorial, telegram, bot]
|
|||
|
---
|
|||
|
|
|||
|
# Как создать чат-бота для Telegram с помощью Python
|
|||
|
|
|||
|
> Статья является полной копией страницы
|
|||
|
https://pythonru.com/primery/python-telegram-bot.
|
|||
|
|
|||
|
Это пошаговое руководство по созданию бота для Telegram.
|
|||
|
Бот будет показывать курсы валют, разницу между курсом раньше и сейчас,
|
|||
|
а также использовать современные встроенные клавиатуры.
|
|||
|
|
|||
|
Время переходить к делу и узнать наконец, как создавать ботов в Telegram.
|
|||
|
|
|||
|
## Шаг № 0: немного теории об API Telegram-ботов
|
|||
|
|
|||
|
Начать руководство стоит с простого вопроса: как создавать чат-ботов в Telegram?
|
|||
|
|
|||
|
Ответ очень простой: для чтения сообщений отправленных пользователями
|
|||
|
и для отправки сообщений назад используется API HTML. Это требует использования URL:
|
|||
|
|
|||
|
```text
|
|||
|
https://api.telegram.org/bot/METHOD_NAME
|
|||
|
```
|
|||
|
|
|||
|
`METHOD_NAME` — это метод, например, `getUpdates`, `sendMessage`, `getChat` и т.п.
|
|||
|
|
|||
|
Токен — уникальная строка из символов, которая нужна для того,
|
|||
|
чтобы установить подлинность бота в системе.
|
|||
|
Токен генерируется при создании бота.
|
|||
|
|
|||
|
Токен выглядит приблизительно так:
|
|||
|
|
|||
|
```text
|
|||
|
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
|||
|
```
|
|||
|
|
|||
|
Для выполнения запросов используются как GET, так и POST запросы.
|
|||
|
Многие методы требуют дополнительных параметров
|
|||
|
(методу `sendMessage`, например, нужно передать `chat_id` и `текст`).
|
|||
|
Эти параметры могут быть переданы как строка запроса URL,
|
|||
|
`application/x-www-form-urlencoded` и `application-json` (кроме загрузки файлов).
|
|||
|
Еще одно требование — кодировка `UTF-8`.
|
|||
|
|
|||
|
После отправки запроса к API, вы получаете ответ в формате JSON.
|
|||
|
Например, если извлечь данные с помощью метода `getME`, ответ будет такой:
|
|||
|
|
|||
|
```text
|
|||
|
GET https://api.telegram.org/bot/getMe
|
|||
|
```
|
|||
|
|
|||
|
```text
|
|||
|
{
|
|||
|
ok: true,
|
|||
|
result: {
|
|||
|
id: 231757398,
|
|||
|
first_name: "Exchange Rate Bot",
|
|||
|
username: "exchangetestbot"
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Список всех типов данных и методов API Telegram-бота можно найти здесь
|
|||
|
https://core.telegram.org/bots/api.
|
|||
|
|
|||
|
Следующий вопрос: как получать пользовательские сообщения?
|
|||
|
|
|||
|
Есть два варианта.
|
|||
|
|
|||
|
Первый — вручную создавать запросы с помощью метода `getUpdates`.
|
|||
|
В качестве объекта вы получите массив объектов `Update`.
|
|||
|
Этот метод работает как технология длинных опросов (long polling),
|
|||
|
когда вы отправляете запрос, обрабатываете данные и начинаете повторяете процесс.
|
|||
|
Чтобы избежать повторной обработки одних и тех же данных рекомендуется использовать параметр
|
|||
|
`offset`.
|
|||
|
|
|||
|
Второй вариант — использовать `webhooks`.
|
|||
|
Метод `setWebhook` нужно будет применить только один раз.
|
|||
|
После этого Telegram будет отправлять все обновления на конкретный URL-адрес,
|
|||
|
как только они появятся.
|
|||
|
Единственное ограничение — необходим HTTPS, но можно использовать и сертификаты,
|
|||
|
заверенные самостоятельно.
|
|||
|
|
|||
|
Как выбрать оптимальный метод? Метод `getUpdates` лучше всего подходит, если:
|
|||
|
|
|||
|
1. Вы не хотите или не можете настраивать HTTPS во время разработки.
|
|||
|
2. Вы работаете со скриптовыми языками, которые сложно интегрировать в веб-сервер.
|
|||
|
3. У бота высокая нагрузка.
|
|||
|
4. Вы меняете сервер бота время от времени.
|
|||
|
|
|||
|
Метод с Webhook лучше подойдет в таких случаях:
|
|||
|
|
|||
|
1. Вы используете веб-языки (например, PHP).
|
|||
|
2. У бота низкая нагрузка, и нет смысла делать запросы вручную.
|
|||
|
3. Бот на постоянной основе интегрирован в веб-сервер.
|
|||
|
4. В этом руководстве будет использоваться метод `getUpdates`.
|
|||
|
|
|||
|
Еще один вопрос: как создать зарегистрировать бота?
|
|||
|
|
|||
|
`@BotFather` используется для создания ботов в Telegram.
|
|||
|
Он также отвечает за базовую настройку
|
|||
|
(описание, фото профиля, встроенная поддержка и так далее).
|
|||
|
|
|||
|
В этом руководстве будет использоваться библиотека
|
|||
|
[pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI).
|
|||
|
|
|||
|
## Шаг № 1: реализовать запросы курсов валют
|
|||
|
|
|||
|
Начать стоит с написания Python-скрипта,
|
|||
|
который будет реализовывать логику конкретных запросов курсов валют.
|
|||
|
Использовать будем `PrivatBank API`.
|
|||
|
|
|||
|
```text
|
|||
|
https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5
|
|||
|
```
|
|||
|
|
|||
|
Пример ответа:
|
|||
|
|
|||
|
```text
|
|||
|
[
|
|||
|
{
|
|||
|
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` со следующим кодом:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
```
|
|||
|
|
|||
|
Были реализованы три метода:
|
|||
|
|
|||
|
1. `load_exchange`: загружает курсы валют по указанному URL-адресу
|
|||
|
и возвращает их в формате словаря(dict).
|
|||
|
2. `get_exchange`: возвращает курсы валют по запрошенной валюте.
|
|||
|
3. `get_exchanges`: возвращает список валют в соответствии с шаблоном
|
|||
|
(требуется для поиска валют во встроенных запросах).
|
|||
|
|
|||
|
## Шаг № 2: создать Telegram-бота с помощью `@BotFather`
|
|||
|
|
|||
|
Необходимо подключиться к боту `@BotFather`, чтобы получить список чат-команд в Telegram.
|
|||
|
Далее нужно набрать команду `/newbot` для инструкций выбора название и имени бота.
|
|||
|
После успешного создания бота вы получите следующее сообщение:
|
|||
|
|
|||
|
```text
|
|||
|
Done! Congratulations on your new bot. You will find it at telegram.me/.
|
|||
|
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:
|
|||
|
(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](https://core.telegram.org/bots/api#authorizing-your-bot)
|
|||
|
и ознакомиться с документацией библиотеки, чтобы лучше понимать то, о чем пойдет речь дальше.
|
|||
|
|
|||
|
## Шаг № 3: настроить и запустить бота
|
|||
|
|
|||
|
Начнем с создания файла `config.py` для настройки:
|
|||
|
|
|||
|
```python
|
|||
|
TOKEN = '' # заменить на токен своего бота
|
|||
|
TIMEZONE = 'Europe/Moscow'
|
|||
|
TIMEZONE_COMMON_NAME = 'Moscow'
|
|||
|
```
|
|||
|
|
|||
|
В этом файле указаны: токен бота и часовой пояс,
|
|||
|
в котором тот будет работать
|
|||
|
(это понадобится в будущем для определения времени обновления сообщений.
|
|||
|
API Telegram не позволяет видеть временную зону пользователя,
|
|||
|
поэтому время обновления должно отображаться с подсказкой о часовом поясе).
|
|||
|
|
|||
|
Создадим файл `bot.py`. Нужно импортировать все необходимые библиотеки,
|
|||
|
файлы с настройками и предварительно созданный `pb.py`.
|
|||
|
Если каких-то библиотек не хватает, их можно установить с помощью `pip`.
|
|||
|
|
|||
|
```python
|
|||
|
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`.
|
|||
|
Для этого конструктору нужно передать токен:
|
|||
|
|
|||
|
```python
|
|||
|
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)`:
|
|||
|
|
|||
|
```python
|
|||
|
@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»**.
|
|||
|
|
|||
|
```python
|
|||
|
@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 @ 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'`.
|
|||
|
|
|||
|
Результат выше выглядит вот так:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
## Шаг № 6: добавить обработчик команды `/exchange`
|
|||
|
|
|||
|
Обработчик команды `/exchange` отображает меню выбора валюты
|
|||
|
и встроенную клавиатуру с 3 кнопками: `USD`, `EUR` и `RUR`
|
|||
|
(это валюты, поддерживаемые API банка).
|
|||
|
|
|||
|
```python
|
|||
|
@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`:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
## Шаг № 7: написать обработчик для кнопок встроенной клавиатуры
|
|||
|
|
|||
|
В библиотеке pyTelegramBotAPI есть декоратор `@bot.callback_query_handler`,
|
|||
|
который передает объект `CallbackQuery` во вложенную функцию.
|
|||
|
|
|||
|
```python
|
|||
|
@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`:
|
|||
|
|
|||
|
```python
|
|||
|
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`:
|
|||
|
|
|||
|
```python
|
|||
|
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»).
|
|||
|
|
|||
|
```python
|
|||
|
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`.
|
|||
|
|
|||
|
```python
|
|||
|
def serialize_ex(ex_json, diff=None):
|
|||
|
result = '' + ex_json['base_ccy'] + ' -> ' + ex_json['ccy'] + ':\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': ..., 'sale_diff': ...}`.
|
|||
|
Это будет происходить во время сериализации после нажатия кнопки `Update`.
|
|||
|
Когда курсы валют отображаются первый раз, он нам не нужен.
|
|||
|
|
|||
|
Вот как будет выглядеть результат выполнения после нажатия кнопки USD:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
## Шаг № 8: реализовать обработчик кнопки обновления
|
|||
|
|
|||
|
Теперь можно создать обработчик кнопки `Update`.
|
|||
|
После дополнения метода `iq_callback_method` он будет выглядеть следующим образом:
|
|||
|
|
|||
|
```python
|
|||
|
@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`.
|
|||
|
Реализуем это:
|
|||
|
|
|||
|
```python
|
|||
|
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'
|
|||
|
)
|
|||
|
```
|
|||
|
|
|||
|
Как это работает? Очень просто:
|
|||
|
|
|||
|
1. Загружаем текущий курс валюты (`exchange_now = pb.get_exchange(data['c'])`).
|
|||
|
2. Генерируем текст нового сообщения путем
|
|||
|
сериализации текущего курса валют с параметром `diff`,
|
|||
|
который можно получить с помощью новых методов (о них дальше).
|
|||
|
Также нужно добавить подпись — `get_edited_signature`.
|
|||
|
3. Вызываем метод `edit_message_text`,
|
|||
|
если оригинальное сообщение не изменилось.
|
|||
|
Если это ответ на встроенный запрос, передаем другие параметры.
|
|||
|
|
|||
|
Метод `get_ex_from_iq_data` разбирает JSON из `callback_data`:
|
|||
|
|
|||
|
```python
|
|||
|
def get_ex_from_iq_data(exc_json):
|
|||
|
return {
|
|||
|
'buy': exc_json['b'],
|
|||
|
'sale': exc_json['s']
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Метод `get_exchange_diff` получает старое и текущее значение курсов валют
|
|||
|
и возвращает разницу в формате `{'buy_diff': ..., 'sale_diff': ...}`:
|
|||
|
|
|||
|
```python
|
|||
|
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…»:
|
|||
|
|
|||
|
```python
|
|||
|
def get_edited_signature():
|
|||
|
return 'Updated ' + \
|
|||
|
str(datetime.datetime.now(P_TIMEZONE).strftime('%H:%M:%S')) + \
|
|||
|
' (' + TIMEZONE_COMMON_NAME + ')'
|
|||
|
```
|
|||
|
|
|||
|
Вот как выглядит сообщение после обновления, если курсы валют не изменились:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
И вот так — если изменились:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
## Шаг № 9: реализовать встроенный режим
|
|||
|
|
|||
|
Реализация встроенного режима значит,
|
|||
|
что если пользователь введет `@ + имя бота` в любом чате,
|
|||
|
это активирует поиск введенного текста и выведет результаты.
|
|||
|
После нажатия на один из них бот отправит результат от вашего имени (с пометкой «via bot»).
|
|||
|
|
|||
|
```python
|
|||
|
@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`:
|
|||
|
|
|||
|
```python
|
|||
|
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 + пробел` вы увидите следующее:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
Попробуем набрать `usd`, и результат мгновенно отфильтруется:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
Проверим предложенный результат:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
Кнопка «Update» тоже работает:
|
|||
|
|
|||
|
**::TODO::**
|
|||
|
|
|||
|
Отличная работа! Встроенный режим работает!
|