Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере

в 12:48, , рубрики: python, telegram, Блог компании Selectel, мессенджеры, опыт, паттерны, чат-бот
Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 1

Я часто взаимодействую с ботами в Telegram. Чаще как пользователь, но создать собственного бота или потрогать чужого я не боюсь. При разработке собственного решения чувствуется, что бот не похож на GUI- или веб-приложение, но программисты тщательно превозмогают это чувство и делают так, как проще с точки зрения программирования.

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

Дисклеймер. Автор не является специалистом по UX. Изложенные тезисы не претендуют на звание лучших практик, а скорее показывают опыт автора, приобретенный на практике.

Шаблон


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

  • Python 3.9.
  • Пакет python-telegram-bot версии 20.0a2 (python -m pip install python-telegram-bot==20.0a2).
  • Созданный бот в Telegram и токен доступа. Для создания обратитесь к BotFather.

Хотя примеры будут на конкретном языке программирования с использованием специфической библиотеки, идея не зависит от деталей реализации. Примеры можно перенести на другие языки программирования.

Фреймворк python-telegram-bot основан на обработчиках. Ядро получает обновления (Update) от Telegram Bot API и вызывает соответствующий обработчик из списка зарегистрированных. Если подходящего обработчика нет, то событие игнорируется.

Рассмотрим шаблон на примере простого echo-бота, который отвечает вашим же текстом.

import logging
from telegram import Update
from telegram.ext import *

# Логирование
logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)

# Функция-обработчик
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text(update.message.text)

# Создание объекта Бот
application = Application.builder().token("здесь ваш токен").build()

# Регистрация обработчика на текстовые сообщения, но не команды
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))

# Запуск бота
application.run_polling()

Далее в примерах я буду приводить только функцию-обработчик и строку для регистрации обработчика.

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

Команды


Команды в Telegram — это сообщения, начинающиеся со слэша (/). Примеры команд:

/start
/subscribe@ExampleArticleBot

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

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 2

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

/weather@ExampleArticleBot Москва

Неудобно и отбивает всякое желание пользоваться ботом. Единственная команда, которая может получать аргументы, — это /start, и только при переходе по ссылке, которая выглядит следующим образом:

https://t.me/<имя_бота>?start=<строка>

В этом случае у пользователя появится кнопка START, даже если пользователь уже активировал бота. При нажатии кнопки в чат отправится сообщение /start, но бот получит сообщение /start <строка>. Создадим обработчик аргументов команды start:

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if update.message.text == "/start":
        await update.message.reply_text("Start without arguments")
        return

    # Удаляем /start
    arg = update.message.text[7:]
    await update.message.reply_text(arg)

# Регистрация обработчика
application.add_handler(CommandHandler("start", start))

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 3

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

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

Текстовые сообщения


Специфичные команды можно представить в виде кодовых слов. Например, вместо /start запрограммировать бота реагировать на «Поехали!». Это отличное решение для ботов, которые в группах реагируют только на сообщения администраторов. Но есть в ложке меда бочка дегтя:

  • Документация по командам бота должна распространяться отдельно.
  • Программист должен учесть возможную вариативность сообщений.
  • Бот должен иметь модификатор «имеет доступ к сообщениям», что может снизить доверие к боту.

Неожиданный сюжетный поворот: бот способен получать ответы на свои сообщения даже если в группе он «не имеет доступ к сообщениям». В python-telegram-bot для этого есть абстракция ConversationHandler.

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 4

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

# Точка входа в диалог
async def weather(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    await update.message.reply_text("В каком городе хотите посмотреть погоду?")
    return 1


# Обработка ответа
async def show_weather(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    city = update.message.text
    await update.message.reply_text(
        f"Вы хотите посмотреть погоду в городе {city}.n"
        f"n"
        f"Но я не умею показывать погоду, извините :("
    )
    return ConversationHandler.END

# Задаем точки входа и ветви диалога
handler = ConversationHandler(
    entry_points=[CommandHandler("weather", weather)],
    states={
        1: [MessageHandler(filters.TEXT & ~filters.COMMAND, show_weather)]
    },
    fallbacks=[]
)
application.add_handler(handler)

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

Если вас интересует тема Telegram-ботов, посмотрите, что у нас есть еще на эту тему:

Как сделать бота для Telegram на облачных функциях
Как сгенерировать стикеры из сообщений в Telegram

Кнопки

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 5

ReplyKeyboard в действии

В Telegram существует два вида кнопок, которые могут быть созданы сообщением от бота. Первый вид — ReplyKeyboard, заменяющий клавиатуру на сенсорных устройствах. Нажатие на эту кнопку отправляет в чат текст кнопки.

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        ["Кнопка 1", "Кнопка 2"],
        ["Большая привлекательная кнопка кнопка"]
    ]
    await update.message.reply_text(
        "Какую кнопку будем нажимать?",
        reply_markup=ReplyKeyboardMarkup(
            keyboard,
            one_time_keyboard=False,
            input_field_placeholder="Ваш выбор?"
        )
    )

application.add_handler(CommandHandler("start", start))

Такие кнопки можно использовать в том числе в групповых чатах. Современные клиенты Telegram автоматически отправляют сообщение в виде ответа на сообщение бота. При локализации бота придется менять обработчики для поддержки сообщений на разных языках. Также кнопки этой клавиатуры отвечают на последнее сообщение, которое создало клавиатуру.

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 6

Если хочется разные действия для нескольких сообщений одновременно, то на помощь приходит InlineKeyboard — клавиатура под сообщением.

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            InlineKeyboardButton("Кнопка 1", callback_data="button-1"),
            InlineKeyboardButton("Кнопка 2", callback_data="button-2")
        ],
        [InlineKeyboardButton("Большая привлекательная кнопка кнопка", url="https://habr.com/")]
    ]
    await update.message.reply_text(
        "Какую кнопку будем нажимать?",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def weather(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            InlineKeyboardButton("Санкт-Петербург", callback_data="LED"),
            InlineKeyboardButton("Москва", callback_data="SVO"),
            InlineKeyboardButton("Иркутск", callback_data="IKT")
        ]
    ]
    await update.message.reply_text(
        "Где хотите посмотреть погоду?",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )

application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("weather", weather))

Кнопки встроенной клавиатуры разнообразны и могут содержать один из следующих элементов:

  • callback_data — строка для специальных обработчиков, рассмотрим подробнее позже.
  • url — ссылка на любой ресурс. Кнопка со ссылкой отмечается стрелкой в верхнем правом углу.
  • inline_query — запускает inline-режим в указанном чате с текущим ботом. Наиболее известный бот с inline-режимом — gif.
  • callback_game — ссылка на игру.
  • web_app — ссылка на WebApp-приложение, доступно только в личных сообщениях.
  • login_url — ссылка на аутентификацию в сервисе через Telegram.
  • pay — ссылка на оплату счета через кошелек в Telegram.

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 7

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

Максимальное количество кнопок под сообщением — 100, вне зависимости от компоновки. При превышении этого числа Telegram не выводит ошибку, но «лишние» кнопки не отображает.

Вернемся к обработке действия с callback_data. Нажатие на кнопку генерирует событие callback_query.

Обработка нажатия кнопки

Паттерны взаимодействия с ботами в Telegram: неочевидные практики на Python и баг в мессенджере - 8

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [InlineKeyboardButton("❤️", callback_data="like-trex")]
    ]
    await update.message.reply_text(
        "Нажми лайк, чтобы поддержать Тирекса!",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    # Убираем кнопки
    await update.callback_query.message.edit_reply_markup(None)

    # Отмечаем, что мы обработали событие и выводим текст
    text = f"Спасибо, {update.callback_query.from_user.full_name}, что поддержал Тирекса!"
    await update.callback_query.answer(text, show_alert=True)


application.add_handler(CommandHandler("start", start))
application.add_handler(CallbackQueryHandler(query))

При нажатии на кнопку в верхнем правом углу появляется пиктограмма часов. Это значит, что событие передано боту. Обработчик callback_query может ответить разными способами:

  • Обработать событие «тихо». На кнопке исчезнет пиктограмма часов.
  • Ответить всплывающим текстом. Этот способ варьируется в зависимости от клиента, но идея заключается в появлении текста поверх чата на короткий промежуток времени.
  • Ответить всплывающим окном. Текст отображается всплывающим окном с кнопкой «ОК».
  • Открыть чат с пользователем по ссылке или запустить игру в Telegram по ссылке.

Не пытайтесь сделать в обработчике много действий с сообщением подряд. В одном из своих проектов я выяснил, что открепление сообщения и удаление кнопок под сообщением в одно время «роняет» Telegram Desktop на Windows и Linux. Я оставил сообщение об ошибке для разработчиков Telegram.

В одной из следующих статей я расскажу о том, как сделал бота для массового заказа шавермы в Selectel. А пока вы можете подписать на бота компании — он рассказывает о предстоящих мероприятиях компании.

Автор: Владимир

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js