Telegram inlineKeyboard. Оценка качества обслуживания после звонка

от автора

Я занимаюсь разработкой АТС с открытым исходным кодом MikoPBX.

Недавно познакомился с проектом tg2sip. Шлюз позволяет подключить Telegram аккаунт к офисной АТС, принимать и совершать звонки.

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

При попытке реализовать функцию столкнулись со сложностями:

  • Пользователь не может отправлять / пересылать клавиатуру другому пользователю

  • Бот не может писать пользователю, если тот на него не подписан

Как же быть? Решение опишу под катом. Приступим…


Шлюз tg2sip позволяет работать только с аудио звонками. Работа с сообщениями не поддерживается. Начались поиски библиотеки для работы с Telegram.

Так как в своих проектах мы активно используем PHP 7.4, то выбор пал на проект MadelineProto. Он позволяет:

  • Работать в качестве бота

  • Работать в качестве клиента Telegram, аналог desktop приложения

  • Работать асинхронно (non-blocking I/O). Разработан на основе amphp

Поиски привели к документации telegram InlineQueryResultArticle.

Единственно возможный способ для пользователя отправить клавиатуру другому пользователю — это использовать inline бота.

Алгоритм следующий:

  1. Открываем любой чат

  2. Вводим имя бота

  3. После имени бота вводим произвольную строку — запрос

  4. Бот присылает «результаты»

  5. Пользователь кликает по одному из результатов

  6. Отправляется сообщение собеседнику

Вот пример работы с inline ботом:

Теперь необходимо это реализовать в своем скрипте.

Запускаем бота

Пример работы с библиотекой MadelineProto:

<?php /** Инициализация бота telegram */ $MadelineProto []= new API('bot.madeline'); /** Подключаем класс обработчик для бота telegram */ $handlers      []= BotEventHandler::class; try {     API::startAndLoopMulti($MadelineProto, $handlers); }catch (Throwable $e){ }
Класс BotEventHandler
<?php class BotEventHandler extends EventHandler {      public function onAny($update)     {         if('updateBotInlineQuery' === $update['_']){             // Обработка inline запроса.             yield $this->getCallbackKeyboardMessage($update);         }elseif ('updateInlineBotCallbackQuery' === $update['_']) {             // Обновление / изменение inline клавиатуры после нажатия.             yield $this->updateInlineKeyboard($update);         }     }      private function updateInlineKeyboard($update){         $bytes = $update['data']->__toString();         // Тут можно обработать данные нажатой кнопки.         $replyKeyboardMarkup = [             '_' => 'replyInlineMarkup',             'resize' => true,             'single_use' => true,             'selective' => true,             'rows' => [                 ['_' => 'keyboardButtonRow', 'buttons' => [                     ['_' => 'keyboardButtonCallback', 'text' => "Нажми меня снова!", 'data' => "changed:$bytes", 'requires_password' => false],                 ]                 ],             ]         ];          $params = [             'no_webpage' => true,             'id' =>  $update['msg_id'],             'message' => 'Обновленная клавиатура: '.time(),             'reply_markup' => $replyKeyboardMarkup,             'parse_mode' => 'html'         ];          yield $this->messages->editInlineBotMessage($params);     }      private function getCallbackKeyboardMessage($update)     {         $query  = $data['query']??'';         $replyKeyboardMarkup = [             '_' => 'replyInlineMarkup',             'resize' => true,             'single_use' => true,             'selective' => true,             'rows' => [                 ['_' => 'keyboardButtonRow', 'buttons' => [                     ['_' => 'keyboardButtonCallback', 'text' => "Нажми меня", 'data' => "callback:$query", 'requires_password' => false],                 ]                 ],             ]         ];          $message = 'Текст сообщения:';         $params = [             'query_id' => $update['query_id'],             'results' => [                 [                     '_' => 'inputBotInlineResult',                     'id' => '1',                     'type' => 'article',                     'title' => 'Заголовок сообщения',                     'send_message' => [                         '_' => 'inputBotInlineMessageText',                         'no_webpage' => true,                         'message' => $message,                         'reply_markup' => $replyKeyboardMarkup                     ]                 ],             ],             'cache_time' => 1,         ];         yield $this->messages->setInlineBotResults($params);     }  }

Этот inline Бот может предложить один «результат» — inline клавиатуру. После нажатия на кнопку, клавиатура будет модифицирована Ботом.

При первом запуске скрипта Madeline запросит информацию для авторизации. Подробно механизм описан в документации.

Запускаем Telegram клиент

Пример скрипта:

<?php // Идентификатор бота const BOT_ID = 5118292901; /** Инициализация клиента telegram */ $MadelineProto []= new API('session.madeline'); /** Подключаем класс обработчик для "клиента" telegram */ $handlers      []= TgUserEventHandler::class;  try {     API::startAndLoopMulti($MadelineProto, $handlers); }catch (Throwable $e){ }

Скрипт отличается от бота только именем файла сессии session.madeline' при первом запуске потребуется ввести данные авторизации.

Класс TgUserEventHandler
<?php class TgUserEventHandler extends EventHandler {     private int $myId;      public function onStart()     {         // Получаем свой ID.          $this->myId = $this->getAPI()->getSelf()['id'];     }      public function onUpdateNewMessage(array $update)     {         $reason = $update['message']["action"]["reason"]['_']??'';         $fromId = $update['message']["from_id"]["user_id"]??0;         // Обрабатываем оповещение о завершении звонка.          if($reason === 'phoneCallDiscardReasonHangup'  && $this->myId === $fromId){             yield $this->sendKeyboard($update);         }     }      private function sendKeyboard(array $update)     {         // Собираем информацию о собеседнике         $userId = $update["message"]["peer_id"]["user_id"]??'';         $userData = yield $this->users->getUsers(['id' => [$userId]]);         if(yield $userData){             // ID собеседника передадим в inline запросе.             $data = $userData[0]['id'];             $peer    = yield $this->getAPI()->getID($update);             // Отправляем inline запрос боту             $messages_BotResults = yield $this->getResultsFromBot($peer, $data);             $results = yield $messages_BotResults['results'];             if((yield $results) && count($results)>0){                 $msg = [                     'peer' => $peer,                     'query_id' => $messages_BotResults['query_id'],                     'id' => $messages_BotResults['results'][0]['id'],                 ];                 // Отправляем первый из результатов собеседнику.                  $this->messages->sendInlineBotResult($msg);             }         }     }      private function getResultsFromBot(int $peer, string $query)     {         $params  = [             'bot'   => BOT_ID,             'peer'  => $peer,             'query' => $query,             'offset'=> '0'         ];         return yield $this->messages->getInlineBotResults($params);     } }

Важно отметить:

  • Бот не может писать пользователю, который на него не подписан

  • Если клавиатура отправляется через sendInlineBotResult, то, формально, Бот будет взаимодействовать с текущим пользователем, а не с собеседником. Это значит, когда собеседник нажмет кнопку, бот в качестве user_id получит ваш идентификатор, а не собеседника

Эти факты требуют от нас дополнительных действий для передаче боту информации о «собеседнике«. Пример реализации приведен в скрипте выше.

Немного о callback методах:

  • onStart — вызывается после создания объекта «TgUserEventHandler«, тут можно выполнить инициализацию

  • onUpdateNewMessage — вызывается при получении нового сообщения, к примеру, при завершении звонка приходит сообщение «Исходящий звонок» и его статус «Отменен»

Теперь все вместе — клиент и Бот

<?php require_once 'vendor/autoload.php';  const BOT_ID = 5118292901; /** Инициализация клиента telegram */ $MadelineProto []= new API('session.madeline'); /** Подключаем класс обработчик для "клиента" telegram */ $handlers      []= TgUserEventHandler::class;  /** Инициализация бота telegram */ $MadelineProto []= new API('bot.madeline'); /** Подключаем класс обработчик для бота telegram */ $handlers      []= BotEventHandler::class; try {     API::startAndLoopMulti($MadelineProto, $handlers); }catch (Throwable $e){ }

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

Итоги

Задачу мы успешно решили, бесценный опыт получили. Telegram действительно современный, удобный, и мощный инструмент для коммуникаций.

Области применения ограничиваются только фантазией:

  • Продажи — отображение статуса заказов, обновление информации по заказу, запрос обратного звонка клиентом

  • Доставка — отображение текущего статуса, выполнение действий над заказом

  • Телефония — оценка качества обслуживания после телефонного звонка

Полезные материалы

  • Бесплатная АТС с открытым исходным кодом MikoPBX

  • Документация Madeline

  • Документация Telegram API

  • Проект tg2sip


ссылка на оригинал статьи https://habr.com/ru/post/652721/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *