Telegram onlineKeyboard. Evaluation of the quality of service after the call

I am developing an open source PBX MikoPBX.

Recently got acquainted with the project tg2sip. The gateway allows you to connect Telegram account to a PBX, receive and make calls.

After setting up the gateway, we decided that it would be nice to send a keyboard to the client after the end of the telephone conversation to assess the quality of service.

When trying to implement the function, we encountered difficulties:

  • The user cannot send/forward the keyboard to another user

  • The bot cannot write to the user if he is not subscribed to him

How to be? I will describe the solution under the cut. Let’s get started…


Gateway tg2sip allows you to work only with audio calls. Messaging is not supported. The search for a library to work with Telegram has begun.

Since in our projects we actively use PHP 7.4then the choice fell on the project MadelineProto. It allows:

  • Work as a bot

  • Work as a Telegram client, analogue of a desktop application

  • Work asynchronously (non-blocking I/O). Developed based on amphp

The search led to telegram documentation InlineQueryResultArticle.

The only possible way for a user to send a keyboard to another user is to use inline bot.

The algorithm is the following:

  1. Open any chat

  2. Enter the name of the bot

  3. After the name of the bot, enter an arbitrary string – request

  4. Bot sends “results”

  5. User clicks on one of the results

  6. Sending a message to an interlocutor

Here is an example of working with inline bot:

Now you need to implement this in your script.

Launching the bot

An example of working with the library MadelineProto:

<?php
/** Инициализация бота telegram */
$MadelineProto []= new API('bot.madeline');
/** Подключаем класс обработчик для бота telegram */
$handlers      []= BotEventHandler::class;
try {
    API::startAndLoopMulti($MadelineProto, $handlers);
}catch (Throwable $e){
}
BotEventHandler class
<?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);
    }

}

This inline The bot can offer one “result” – inline keyboard. After pressing the button, the keyboard will be modified by the Bot.

When you run the script for the first time, Madeline will ask you for authorization information. The mechanism is described in detail in documentation.

Launch Telegram client

Script example:

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

try {
    API::startAndLoopMulti($MadelineProto, $handlers);
}catch (Throwable $e){
}

The script differs from the bot only in the name of the session file session.madeline' At the first start, you will need to enter authorization data.

TgUserEventHandler class
<?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'],
                ];
                // Отправляем первый из результатов собеседнику. 
                yield $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);
    }
}

It is important to note:

  • The bot cannot write to a user who is not subscribed to it

  • If the keyboard is sent via sendInlineBotResult, then, formally, the Bot will interact with the current user, and not with the interlocutor. This means that when the interlocutor presses the button, the bot will receive your identifier as user_id, not the interlocutor

These facts require us to take additional steps to transfer information to the bot about “interlocutor“. An example implementation is given in the script above.

A little about callback methods:

  • onStart – called after object creation “TgUserEventHandler“, here you can perform initialization

  • onUpdateNewMessage – called when a new message is received, for example, when a call ends, the message “Outgoing call” is received and its status is “Canceled”

Now everything is together – the client and the Bot

<?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){
}

The client and the bot will work in the same process asynchronously without interfering with each other. An example of what it might look like:

Results

We have successfully solved the problem, gained invaluable experience. Telegram really modern, convenient, and powerful communication tool.

Applications are only limited by your imagination:

  • Sales – displaying the status of orders, updating information on the order, requesting a call back from the client

  • Delivery – displaying the current status, performing actions on the order

  • Telephony – evaluation of the quality of service after a phone call

Useful materials

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *