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

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

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

При открытии бота по умолчанию сразу доступна кнопка Старт, при нажатии, на которую будет начат диалог и в бот будет отправлена команда /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;
    }
?>

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

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


Комментарии

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


  • Ты не подскажешь как в цикле вывести инлайн кнопки,
    мне нужно 30 шт вывести из базы
    INSTAP1 29.08.2019 в 15:52
    • 30 in лучше выводить с постраничной навигацией по 10 шт например, у Телеграм если не ошибаюсь установлено ограничение. У меня была ситуация, когда на 32 строке кнопок был косяк, но постраничка решила проблему.

      А вывести не сложно если из базы получить массив то стандартными функциями (foreach, for, do while ...) php можно спокойно это сделать.
      iMakeBots 12.09.2019 в 11:41
  • что нужно дописать этому боту сюда-
     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 ?
    Грачик Абдулошвили 22.10.2019 в 15:43
    •  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);
         }
      iMakeBots 22.10.2019 в 23:23
  • Здравствуйте а как кнопкам меню задать стиль ширину и длину?
    my Profit 25.10.2019 в 17:49
    • К сожалению Телеграм это не позволяет регулировать.
      iMakeBots 25.10.2019 в 17:56
      • жалко что вам скрин нельзя прислать, может я не правильно спросил, а как их перенести по принципу <br> что б они в столбик были?
        my Profit 25.10.2019 в 18:15
        • Если я правильно понял ваш вопрос, то это можно сделать просто - разбить массив на вложенность кратное необходимому кол-во строк.
          iMakeBots 26.10.2019 в 16:42
  • можно еще подробней узнать о https://telegram.me/ИМЯ_БОТА?start=ВАШЕ_ЗНАЧЕНИЕ хочу реализовать реф систему
    my Profit 25.10.2019 в 19:02
    • Откройте ветку на форуме, там удобнее. Только вопрос по больше разверните, что именно вам нужно узнать.
      iMakeBots 26.10.2019 в 16:40
      • Как доставать это значение? ВАШЕ_ЗНАЧЕНИЕ
        my Profit 27.10.2019 в 09:08
        • Например если вы переходите по ссылке 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
          iMakeBots 27.10.2019 в 09:38
          • 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;
            my Profit 27.10.2019 в 10:05
            • 1. Просьба оформить топик на форуме все еще актуальна.
              2. У вас нет необходимого case поэтому условие проваливается в default, обратите внимание я вам выше писал, какое значение вам будет прилетать /start 123456789, где 123456789 в вашем случае это подставленный $chat_id

              /start 123456789 и /start - разные case
              iMakeBots 27.10.2019 в 12:10