Я занимаюсь разработкой АТС с открытым исходным кодом MikoPBX.
Недавно познакомился с проектом tg2sip. Шлюз позволяет подключить Telegram аккаунт к офисной АТС, принимать и совершать звонки.
После настройки шлюза, решили, что было бы неплохо после завершения телефонного разговора отправить клиенту клавиатуру для оценки качества обслуживания.
При попытке реализовать функцию столкнулись со сложностями:
-
Пользователь не может отправлять / пересылать клавиатуру другому пользователю
-
Бот не может писать пользователю, если тот на него не подписан
Как же быть? Решение опишу под катом. Приступим…
Шлюз tg2sip позволяет работать только с аудио звонками. Работа с сообщениями не поддерживается. Начались поиски библиотеки для работы с Telegram.
Так как в своих проектах мы активно используем PHP 7.4, то выбор пал на проект MadelineProto. Он позволяет:
-
Работать в качестве бота
-
Работать в качестве клиента Telegram, аналог desktop приложения
-
Работать асинхронно (non-blocking I/O). Разработан на основе amphp
Поиски привели к документации telegram InlineQueryResultArticle.
Единственно возможный способ для пользователя отправить клавиатуру другому пользователю — это использовать inline бота.
Алгоритм следующий:
-
Открываем любой чат
-
Вводим имя бота
-
После имени бота вводим произвольную строку — запрос
-
Бот присылает «результаты»
-
Пользователь кликает по одному из результатов
-
Отправляется сообщение собеседнику
Вот пример работы с 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/
Добавить комментарий