Клавиатура как способ взаимодействия с ботом

Очень полезная возможность, которая упрощает взаимодействие пользователя с ботом. Рассмотрим этот объект пользовательской клавиатуры с опциями ответа, какие типы нам доступны на текущую версию API.

Примеры размещения кнопок

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

При открытии бота по умолчанию сразу доступна кнопка Старт, при нажатии, на которую будет начат диалог и в бот будет отправлена команда /start. Уже здесь мы можем поймать эту команду, обработать и выполнить какие-то действия. Вместе с этой командой в бот отправляется минимальная информация о пользователе:

  1. id - уникальный идентификатор пользователя в Telegram
  2. last_name - значение поля фамилия, может быть пустым
  3. first_name - значение поля имя, может быть пустым
  4. username - уникальное значение текстовый идентификатор, может быть пустым
  5. language_code - кодовое обозначение выбранного языка интерфейса приложения (en, ru ...)
  6. is_bot - флаг, определяющий пользователя как бота, в случае с ботом равен 1, в случае с пользователем пустое значение

* * *

Полезный инструмент 

У каждого бота есть ссылка, которая открывает диалог с ним в Telegram — https://telegram.me/ИМЯ_БОТА. К этой ссылке можно добавить параметры start или startgroup со значениями. Значение может быть длиной от 1 до 64 символов и состоять из букв латинского алфавита (больших - A-Z и маленьких - a-z), цифр - 0-9, нижнего подчеркивания _ и тире -.

https://telegram.me/ИМЯ_БОТА?start=ВАШЕ_ЗНАЧЕНИЕ

Telegram Bot API рекомендует использовать base64url для кодирования параметров с двоичным и другими типами контента. 

По ссылке с параметром start будет открыт диалог с ботом, с кнопкой Старт в месте поля ввода. Если используется параметр startgroup, пользователю будет предложено выбрать группу для добавления бота.

Как только пользователь подтвердит действие (нажмет кнопку Пуск в своем приложении или выберет группу для добавления бота), ваш бот получит сообщение от этого пользователя в таком формате:

/start ВАШЕ_ЗНАЧЕНИЕ

Для удобства пользователя, можно через настройки Commands вашего бота у @BotFather, задать список поддерживаемых ботом команд. Тогда при вводе знака слеш /, пользователь увидит все команды и сможет сразу их отправить, просто нажав на одну из них. Каждая команда состоит из собственно командного слова, начинающегося со слеша и короткого описания:

/invoice Выписать счет

* * *

Клавиатуры и их возможности

Для управления ботом вы как создатель бота предполагаете какой-то сценарий. Пользователю чтобы получить от бота желаемый результат нужно отправить команду. Бот, когда получит команду обработает ее и отреагирует по установленному вами сценарию. Но представьте, что пользователь написал команду с ошибкой, бот в этом случае должен предупредить что команда не распознана и попросить попробовать еще раз. Это не совсем удобно пользователю, и он может пойти искать другого бота с аналогичными возможностями. Вы можете потерять потенциального клиента, это не совсем хорошо.

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

С каждым сообщением бот может отправлять разный набор кнопок как по количеству, так и по назначению. Кнопки можно расположить непосредственно под сообщением (InlineKeyboardButton), или зафиксировать под тестовым полем для ввода сообщения (KeyboardButton). Различия в них очень существенные на мой взгляд. Давайте рассмотрим некоторые их возможности.

KeyboardButton

Этот объект представляет одну кнопку клавиатуры, располагающуюся  под текстовым полем для отправки сообщения. Поставляется она в интерфейс приложения в наборе с другими аналогичными  кнопками через объект ReplyKeyboardMarkup. В качестве параметров с ним можно передать:

  1. text - текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
  2. request_contact - если параметр установлен в true, пользователь отправит в бот свой номер телефона на который зарегистрирован аккаунт
  3. request_location - если параметр установлен в true, пользователь отправит в бот свое текущее местоположение

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

Помимо набора кнопок в объект ReplyKeyboardMarkup можно передать еще ряд параметров:

  1. keyboard - массив кнопок (объекты KeyboardButton)
  2. resize_keyboard - если предать true, то клавиатура подгонится по высоте до возможного минимума.
  3. one_time_keyboard - возможность скрывать клавиатуру после ее использования
  4. selective - если надо показать клавиатуру только определенным пользователям

При необходимости удалить клавиатуру используйте объект ReplyKeyboardRemove.

InlineKeyboardButton

Это объект одной кнопки встроенной клавиатуры. Располагается набор таких кнопок сразу под сообщением. С помощью этого объекта можно передать кнопке url, или использовать его для голосования как одним из вариантов. В любом случае можно сделать много интересного и полезного. Рассмотрим его возможности на практике чуть ниже. Передается в наборе с другими аналогичными кнопками через объект InlineKeyboardMarkup. Принимает параметры:

  1. text - текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
  2. url - адрес на который будет направлен пользователь
  3. callback_data - строка 1-64 символа будет передан боту через объект CallbackQuery
  4. switch_inline_query - после нажатия будет предложен выбор чата где будет использован бот во встроенном режиме, пример @gif dog
  5. switch_inline_query_current_chat - после нажатия вставит команду для использования бота во встроенном режиме в текущем чате
  6. callback_game - описание игры, которая будет запущена при нажатии пользователем кнопки.
  7. pay - кнопка будет использована как кнопка оплаты

* * *

Теперь немного практики

Предлагаю рассмотреть поближе возможности клавиатур. Для примера я сделаю 4 кнопки: 2 KeyboardButton и 2 InlineKeyboardButton. Для этого будем работать с теми же инструментами, которые были в предыдущей статье. Добавим только несколько новых методов. 

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

Чтобы обрабатывать команды от всех типов кнопок нам надо составить условия проверки. В моем примере от кнопки KeyboardButton команда приходит в виде обычного текстового сообщения вместе с объектом Message под ключом text, а от кнопки InlineKeyboardButton в объекте CallbackQuery под ключом data. 

<?php
    // если это просто объект message
    if (array_key_exists('message', $arrData)) {
        // получаем id чата
        $chat_id = $arrData['message']['chat']['id'];
        // текстовое значение 
        $message = $arrData['message']['text'];
    // если это объект callback_query
    } elseif (array_key_exists('callback_query', $arrData)) {
        $chat_id = $arrData['callback_query']['message']['chat']['id'];
        $message = $arrData['callback_query']['data'];
    }
?>

Кнопки будем отправлять в параметре reply_markup в методе sendMessage. Набор кнопок идет в таком формате:

Массив данных (
    'Тип клавиатуры' => 
        Массив строк кнопок (
            Массив кнопок строки (
                Массив параметров кнопки (
            
                )
            )
         ),
    // если предусмотрено 
    'Дополнительный параметр',
)

Создадим два метода для разных клавиатур, на входе принимают массив строк кнопок, на выходе строку в формате JSON.

<?php
    private function getInlineKeyBoard($data)
    {
        $inlineKeyboard = array(
            "inline_keyboard" => $data,
        );
        return json_encode($inlineKeyboard);
    }

    private function getKeyBoard($data)
    {
        $keyboard = array(
            "keyboard" => $data,
            "one_time_keyboard" => false,
            "resize_keyboard" => true
        );
        return json_encode($keyboard);
    }
?>

Сам набор кнопок будет выглядеть вот так.

<?php
    $justKeyboard = $this->getKeyBoard([[["text" => "Голосовать"], ["text" => "Помощь"]]]);
    $inlineKeyboard = $this->getInlineKeyBoard([[
        ['text' => hex2bin('F09F918D') . ' 0', 'callback_data' => 'vote_1_0_0'],
        ['text' => hex2bin('F09F918E') . ' 0', 'callback_data' => 'vote_0_0_0']
    ]]);
?>

Во встроенных кнопках (inlineKeyboardButton) в качестве значения параметра callback_data будем передавать служебную информацию в виде action_type_count1_count2, где 

  1. action - действие, в нашем случае это vote
  2. type - тип кнопки: 1 - левая, 0 - правая
  3. count1 - текущее значение левой кнопки
  4. count2 - текущее состояние правой кнопки

В значении параметра text передаем бинарный код эмодзи в кодировке UTF-8 (список эмодзи), которые преобразуются из шестнадцатеричных данных в двоичные данные функцией hex2bin, и еще выведем текущее числовое значение счетчика кнопки.

Теперь нам остается только описать механизм определения команды и механизм ее обработки. Поэтому в методе init() создадим конструкцию оператора switch

<?php
    switch ($message) {
        // если начало диалога
        case '/start':
            $dataSend = array(
               'text' => "Приветствую, давайте начнем нашу практику.  Нажмите на кнопку Голосовать.",
                'chat_id' => $chat_id,
                // отправляем клавиатуру
                'reply_markup' => $justKeyboard,
            );
            $this->requestToTelegram($dataSend, "sendMessage");
            break;
        // если пришла команда Голосовать
        case 'Голосовать':
            $dataSend = array(
                'text' => "Выберите один из вариантов",
                'chat_id' => $chat_id,
                // отправляем встроенную клавиатуру
                'reply_markup' => $inlineKeyboard,
            );
            $this->requestToTelegram($dataSend, "sendMessage");
            break;
        // если запросили Помощь
        case 'Помощь':
            $dataSend = array(
                'text' => "Просто нажмите на кнопку Голосовать.",
                'chat_id' => $chat_id,
            );
            $this->requestToTelegram($dataSend, "sendMessage");
            break;
        // если вызвана встроенная кнопка
        case (preg_match('/^vote/', $message) ? true : false):
            // получаем новую клавиатуру
            $params = $this->setParams($message);
            $dataSend = array(
                'reply_markup' => $params[0],
                'message_id' => $arrData['callback_query']['message']['message_id'],
                'chat_id' => $chat_id,
            );
            // изменяем клавиатуру под выбранным сообщением
            $this->changeVote($dataSend, $params[1], $arrData['callback_query']['id']);
            break;
        // незапланированное действие обрабатываем как поумолчанию
        default:
           $dataSend = array(
                'text' => "Не запланированная реакция, может просто  нажмете на кнопку Голосовать.",
                'chat_id' => $chat_id,
            );
            $this->requestToTelegram($dataSend, "sendMessage");
            break;
    }
?>

Давайте разберемся, что в этом коде происходит. Оператор switch принимает значение $message, которое может быть, как просто текст сообщения (в том числе команды), так и значение объекта callbackQuery.

  1. При получении команды /start - мы выводим приветственное слово и набор кнопок: Голосовать, Помощь.
  2. При получении команды Помощь, выводим простое текстовое сообщение.
  3. При получении команды Голосовать, выводим текстовое сообщение и набор встроенных кнопок с параметром callback_data.
  4. При получении не запланированного значения, выводим текстовое сообщение.
  5. И самое интересное это когда принимаем значение в начале которого стоит action, в нашем случае это vote. Мы формируем новую клавиатуру изменяем в ней текстовое значение, увеличиваем счетчик у нажатой кнопки и подставляем новую служебную информацию в параметрах callback_data

Для создания новой клавиатуры мы используем метод setParams(), а для отправки изменений мы используем метод changeVote(). Здесь остановим внимание на методах Bot API

  1. editMessageReplyMarkup - меняем клавиатуру у заданного сообщения
  2. answerCallbackQuery - позволяет выводить уведомление о проделанной работе CallbackQuery, мы его используем, уведомляя пользователя что он удачно проголосовал.
<?php
    private function changeVote($data, $emoji, $callback_query_id)
    {
        // готовим текст уведомления взависимости от результата 
        // обновления клавиатуры у сообщения
        $text = $this->requestToTelegram($data, "editMessageReplyMarkup")
            ? "Вы проголосовали " . hex2bin($emoji)
            : "Непредвиденная ошибка, попробуйте позже.";

        // отправляем уведомление при любом результате
        $this->requestToTelegram([
            'callback_query_id' => $callback_query_id,
            'text' => $text,
            'cache_time' => 3,
        ], "answerCallbackQuery");
    }

    private function setParams($data)
    {
        // разбиваем в массив по знаку _
        $params = explode("_", $data);
        // увеличиваем значение или левой или правой кнопки 
        $params[1] ? $params[2]++ : $params[3]++;
        // готови клавиатуру с новыми значениями callback_data
        $arr[] = $this->getInlineKeyBoard([[
            [
                'text' => hex2bin('F09F918D') . ' ' . $params[2], 
                'callback_data' => 'vote_1_' . $params[2] . '_' . $params[3]
            ],
            [
                'text' => hex2bin('F09F918E') . ' ' . $params[3], 
                'callback_data' => 'vote_0_' . $params[2] . '_' . $params[3]
            ]
        ]]);
        // готовим эмодзи для уведомления
        $arr[] = $params[1] ? 'F09F918D' : 'F09F918E';
        return $arr;
    }
?>

* * *

Подводим итоги

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

botKeyboard.php.zip
2.5
20 комментариев
Авторизуйтесь через Telegram, чтобы оставить комментарий.
Откройте бот @iMakeBot, нажмите кнопку Старт/Start. Следуйте инструкциям бота.

  • INSTAP1 [3 года назад]

    Ты не подскажешь как в цикле вывести инлайн-кнопки, мне нужно 30 шт вывести из базы

  • iMakeBots [3 года назад → INSTAP1]

    30 шт лучше выводить с постраничной навигацией по 10 шт например, у Телеграм если не ошибаюсь установлено ограничение.

    У меня была ситуация, когда на 32 строке кнопок был косяк, но постраничка решила проблему.

    А вывести не сложно если из базы получить массив то стандартными функциями (foreach, for, do while ...) php можно спокойно это сделать.

  • Грачик Абдулошвили [2 года назад]

    что нужно дописать этому боту сюда:

    private function setFileLog($data)
    {
       $fh = fopen('log.txt', 'a') or die('can't open file');
       ((is_array($data)) || (is_object($data))) ? fwrite($fh, print_r($data, TRUE) . "n") : fwrite($fh, $data . "n");
       fclose($fh);
    }

    чтобы он вел лог отдельно для каждого чат_айди в папке log?

  • iMakeBots [2 года назад → Грачик Абдулошвили]
    private function setFileLog($chat_id, $data)
    {
       $fh = fopen('./log/' . $chat_id . '.log', 'a') or die('cant open file');
       ((is_array($data)) || (is_object($data))) ? fwrite($fh, print_r($data, TRUE) . "\n") : fwrite($fh, $data . "\n");
       fclose($fh);
    }
  • my Profit [2 года назад]

    Здравствуйте а как кнопкам меню задать стиль ширину и длину?

  • iMakeBots [2 года назад → my Profit]

    К сожалению Телеграм это не позволяет регулировать.

  • my Profit [2 года назад → iMakeBots]

    жалко что вам скрин нельзя прислать, может я не правильно спросил, а как их перенести по принципу <br> что б они в столбик были?

  • iMakeBots [2 года назад → my Profit]

    Если я правильно понял ваш вопрос, то это можно сделать просто - разбить массив на вложенность кратное необходимому кол-во строк.

  • my Profit [2 года назад]

    можно еще подробней узнать о

    https://telegram.me/ИМЯ_БОТА?start=ВАШЕ_ЗНАЧЕНИЕ

    хочу реализовать реф систему

  • iMakeBots [2 года назад → my Profit]

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

  • my Profit [2 года назад → iMakeBots]

    Как доставать это значение? ВАШЕ_ЗНАЧЕНИЕ

  • iMakeBots [2 года назад → my Profit]

    Например если вы переходите по ссылке https://t.me/your_bot/start=123456789

    Значение будет лежать:
    1. Если приводить к объекту json_decode(file_get_contents('php://input'))->message->text
    2. Если приводить к ассоциативному массиву json_decode(file_get_contents('php://input'), true)['message']['text']

    Это значение будет /start 123456789

  • my Profit [2 года назад → iMakeBots]
    case 'Рефералы':
        $reftitle = "Ваша реферальная ссылка: http://t.me/мой бот?start=".$chat_id;
        $dataSend = array(
            'text' => $reftitle,
            'chat_id' => $chat_id,
         );
        $this->requestToTelegram($dataSend, "sendMessage");
        break;

    ТАК я формирую ссылку

    но когда я перехожу по ссылке мне выдает ошибку

    default:
        $dataSend = array(
            'text' => "Не запланированная реакция.",
            'chat_id' => $chat_id,
        );
        $this->requestToTelegram($dataSend, "sendMessage");
        break;

    Старт выглядит также как у вас

    case '/start':
        $startinfo  ="hi";	
        $dataSend = array(
            'text' => $startinfo,
            'chat_id' => $chat_id,
            'reply_markup' => $justKeyboard,
        );
        $this->requestToTelegram($dataSend, "sendMessage");
        break;
  • iMakeBots [2 года назад → my Profit]

    1. Просьба оформить топик на форуме все еще актуальна.

    2. У вас нет необходимого case поэтому условие проваливается в default, обратите внимание я вам выше писал, какое значение вам будет прилетать /start 123456789, где 123456789 в вашем случае это подставленный $chat_id

    /start 123456789 и /start - разные case

  • Евгений [1 год назад]

    Спасибо, очень полезная статья)

  • Алексей Леонтьев [9 месяцев назад]

    Автор, спасибо тебе огромное! Благодаря тебе я сделал кнопки, а то неделю с ними провозился! Просто я копировал этого бота из вк (у меня там уже работает бот на РНР) и поставил не думая $message = $text. Спасибо!!!

  • Жека [6 месяцев назад]

    Подскажите, а можно inline кнопкой просто отправить текст, как делает это KeyboardButton

  • iMakeBots [6 месяцев назад → Жека]

    На кнопку навесить обработчик, который направит текст по событию

  • Жека [6 месяцев назад → iMakeBots]

    Если не сложно, можете написать пример как это реализовать?)

  • iMakeBots [4 месяца назад → Жека]

    В статье выше есть как раз этот пример с голосованием.