Как стать автором
Обновить

Об универсализации кода при написании чат-ботов

Время на прочтение8 мин
Количество просмотров3.3K

Добрый день. Меня зовут Виктор Храпко. Это моя первая статья на Хабре. Прошу очень уж строго не судить.

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

Для решения проблемы уже был установлен личный кабинет на сайте, была установлена куча многоканальных телефонов, функционирует Viber бот.

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

Решил для эксперимента добавить еще одного чат-бота, где не требовалась бы регистрация абонента. На вопрос где его разместить тут же прозвучало Viber. Ну видимо он у всех на слуху. Но есть же еще и Telegram и FaceBook Мессенджер и Скайп и т.д.

Тут я вспомнил рассказ авиаконструктора Антонова. Они проектировали новый самолет на замену всем известному АН-2. В АН-2 всего девять мест. АН-14 был сделан на 10 мест. Комиссии очень понравился самолет, но экономисты что-то там посчитали и сказали, что нужно 11 мест для рентабельности. И тогда Антонов дал приказ конструировать самолет на 15 мест. Мол комиссия в следующий раз скажет — хорошо бы 13 мест. А мы скажем — а у нас 15

Справедливости ради скажу, что в оригинале я такого самолета так и не увидел, но по моему даже летал на его чешской модификации Л-410. Вспомнить точно не могу. Было это более 30 лет тому назад.

Так вернемся к ботам. Я решил, что там где Viber там должны быть и другие мессенжеры.

И вопрос руководства о других мессенджерах будет закрыт так же как у Антонова.

Целью было составить все так, чтобы добавление нового чат бота в новом мессенджере занимало минимум программирования.

Встал выбор языка. Так как толком из языков программирования для Веб я знаю только PHP, то и думать долго не пришлось.

Как обычно начал работу с поиска в Гугл со словами чат бот PHP.

Благо Гугл не подвел и я собрал небольшой цикл статеек, которые мне здорово помогли

По Viber боту

По Telegram

Для FaceBook Мессенджера

К сожалению по Скайпу внятной информации не нашел. Только в одном месте нашел, что с 2019 года чат боты не регистрируются.

Ну да и бог с ним, решил я. Как-нибудь попробуем первые три.

Я не буду здесь описывать процесс регистрации самих ботов на сайтах этих мессенжеров.

Небольшая тонкость была у Telegram. Когда я пытался там подписаться на @BotFather, то я нашел два или три бота с одинаковым именем и иконкой. Но ответами, описанными в статье порадовал только один.

У FaceBook Мессенджера по-моему интерфейс разработчика меняется через день. Я нашел по моему четыре статьи и в каждой из них был показан другой интерфейс. Ну при известном опыте можно освоится.

Анализ показал, что разработка бота состоит из трех частей.

Первая — Получить так называемый токен от мессенджера

Вторая — Указать сайту мессенджера где же в интернете располагается твой обработчик событий (webhook)

Третья — собственно реализация самого чат бота.

Наиболее легко это выглядело у Телеграм бота

Токен получаем при создании бота

Выполняем из броузера команду

https://api.telegram.org/botТОКЕН/setwebhook?url=Путь к боту

Если не ошибся с путем, то выдаст сообщение, где есть заветное слово OK

У Viberа уже нужно было разместить файл бота на сервере и выполнить специальный файлик. Он есть в первоисточнике, о котором я говорил выше. Результатом тоже должна быть строка содержащая OK.

Для FaceBook нужно было также разместить файл бота на сайте и из интерфейса FaceBook установить путь к WebHook.

В теле ботов для Viber и ФМ есть специальная ветка, отвечающая за регистрацию вебхука.

Итак приступаем к программированию.

Наша задача состоит в том, чтобы

  1. Прочитать данные, которые пришлет нам мессенжер.

  2. Выделить из этих данных сам текст сообщения.

  3. Создать ответ на полученное сообщение.

  4. Отправить ответ.

Для начала создадим несколько сервисных функций на PHP

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

function loadData($filename)
{
  $input = json_decode(file_get_contents('php://input'), true);
//Пишем в файл лог сообщений
  file_put_contents($filename, '$input: '.json_encode($input, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)."\n", FILE_APPEND);
  return $input;
}

Функция формирования ответа от чат-бота

function getAnswer($t)
{
// Ф-ция реализующая сам ответ.
// Я намеренно не привожу ее текст.
// Он зависит от поставленной перед вами задачи
// В простейшем случае можно ответить тем, что получили
  return $t;
}

Функция отсылки данных на сервер мессенджера

Опять же пересылаем JSON на указанный адрес с помощью CURL

function sendCurl($url, $data)
{
  $request_data = json_encode($data);
//here goes the curl to send data to user
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $request_data);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
  $response = curl_exec($ch);
  $err = curl_error($ch);
  curl_close($ch);
  if($err) {return $err;}
  else {return $response;}
}

Ну и функция, которая как бы собирает все вместе.

Что здесь происходит.

Мы передаем сюда имя файла лога $filename

Адрес по которому мы будем пересылать ответ $url

Входные данные $input

Три анонимные функции

$getMessage — функция для получения самого текста из JSON структуры

$changeFormat — функция форматирования текста ответа связанная с различным форматом сиволов для форматирования строки. Например Viber хочет три обратных кавычки для моноширинного шрифта, а остальные мессенджеры одну. Эта функция убирает лишние кавычки так как ответы у меня сделаны для Viber по умолчанию.

$createSendData — функция формирования JSON структуры для ответа


Так как ответ бота может быть довольно длинным или иногда хочется выделить часть ответа как отдельное сообщение, то в строку ответа вставляются разделительные знаки в виде ||.

Это разбивает строку ответа на несколько строк. И все они в цикле передаются на сайт мессенджера.

function sendAnswer($filename, $url, $input, $getMessage, $changeFormat, $createSendData)
{
  $message = $getMessage($input);
  if ($message <> '') {
    $text = getAnswer($message);
    $text = $changeFormat($text);
    do {
      $i = strpos($text, "||");
      if ($i == false) { $i = strlen($text); }
      $data = $createSendData($input, substr($text, 0, $i));
      file_put_contents($filename, '$answer: '.json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)."\n", FILE_APPEND);
      $res = sendCurl($url, $data);
      file_put_contents($filename, '$res: '.json_encode($res, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)."\n", FILE_APPEND);
      $text = substr($text, $i + 2);
    } while ($text <> '');
  }
}

Я разместил все вышеприведенные функции в файл data.php. Каждый разработчик может называть файлы по своему. Никаких принципиальных требований нету. Просто в теле самого бота всегда присутствует строка include("data.php");

Теперь осталось реализовать сам бот

Пример для Telegram в файле telegram_bot.php

<?php

include("data.php");

$token   = 'ВАШ ТОКЕН';
$url     = 'https://api.telegram.org/bot' . $token . '/sendMessage';
$logfile = 'telegram_in.txt';

$input = loadData($logfile);

$getMessage = function ($input) {
  return isset($input['message']['text']) ? $input['message']['text'] : '';
};

$changeFormat = function($text) {
  return str_replace('```', '`', $text);
};

$createSendData = function($input, $text) {
  $data['chat_id'] = $input['message']['chat']['id'];
  $data['parse_mode'] = 'markdown';
  $data['text'] = $text;
  return $data;
};

sendAnswer($logfile, $url, $input, $getMessage, $changeFormat, $createSendData);

?>

Что здесь есть. Несколько очевидных констант описывающих ваш токен, адрес куда слать ответ и имя лог файла.

Вызов функции loadData для загрузки JSON структуры от мессенджера.

И три анонимные функции о которых я говорил ранее.

$getMessage — проверяет есть ли в полученной от мессенджера JSON структуре значение $input['message']['text']. Если есть, то оно и возвращается в качестве текста от мессенджера. Иначе возвращается пустая строка, а ф-ция SendAnswer игнорирует пустые строки

$changeFormat — так как ответы моего чат бота по умолчанию заточены под Viber, то приходится делать небольшую модификацию текста ответа. В данном случае я заменяю тройную обратную кавычку на одинарную, как этого требует Telegram

$createSendData — эта функция как раз и создает отчет в виде JSON структуры.

Далее идет вызов функции sendAnswer, которая и собирает все эти функции вместе

Как видно текст бота получился размером в 30 строк с учетом кучи форматирующих пустых строк.

Пример для FaceBook Мессенджера

<?php

include("data.php");

$token   = 'ВАШ ТОКЕН';
$url     = 'https://graph.facebook.com/v2.7/me/messages?access_token='.$token;
$logfile = 'facebook_in.txt';

if (!empty($_REQUEST['hub_mode'])) {
  $verify_token = "1";
  if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe' && $_REQUEST['hub_verify_token'] == $verify_token) {  
    echo $_REQUEST['hub_challenge'];
  }
}
else {
  $input = loadData($logfile);

  $getMessage = function($input) {
    return isset($input['entry'][0]['messaging'][0]['message']['text']) ? $input['entry'][0]['messaging'][0]['message']['text'] : '';
  };

  $changeFormat = function($text) {
    return str_replace('```', '`', $text);
  };

  $createSendData = function($input, $text) {
    $data['recipient']['id'] = $input['entry'][0]['messaging'][0]['sender']['id'];
    $data['message']['text'] = $text;
    return $data;
  };

  sendAnswer($logfile, $url, $input, $getMessage, $changeFormat, $createSendData);
}

?>

Здесь тело бота разбито как бы на две части. Первая для if(!empty($_REQUEST['hub_mode'])) отвечает за установку вебхука. А вторая в точности как и для Телеграм. Тот же loadData. Те же три анонимные функции. И тот же sendAnswer. Просто входная и выходная JSON структуры отличаются. Но по-прежнему выбрать из них текст сообщения довольно легко.

Ну и пример для Viber.

<?php  

include("data.php");

$token     = "ВАШ ТОКЕН";
$url       = "https://chatapi.viber.com/pa/send_message";
$logfile   = "viber_in.txt";
$send_name = "Имя бота";

$input = loadData($logfile);

if ($input['event'] == 'webhook')  
{
  $webhook_response['status'] = 0;
  $webhook_response['status_message'] = "ok";
  $webhook_response['event_types'] = 'delivered';
  echo json_encode($webhook_response);
  die;
}
else {
  $getMessage = function ($input) {
    $message = $input['event'];
    if ($message == "message") { $message = ($input['message']['type'] == "text") ? $input['message']['text'] : ''; };
    return $message;
  };

  $changeFormat = function($text) {
    return $text;
  };

  $createSendData = function($input, $text) {
    global $token, $send_name;
    $data['text'] = $text;
    $data['type'] = "text";
    $data['receiver'] = isset($input['sender']['id']) ? $input['sender']['id'] : $input['user']['id'];
    $data['auth_token'] = $token;
    $data['sender']['name'] = $send_name;
    return $data;
  };

  sendAnswer($logfile, $url, $input, $getMessage, $changeFormat, $createSendData);
}

?>

В константы здесь добавлена еще одна $send_name. Это имя, которым будет именоваться бот, присылая свои фразы.

Так как установка Вебхука в Viberе очень похожа на сами сообщения, то тут вызов функции loadData вынесен перед определяющим функциональность условным оператором if ($input['event'] == 'webhook')

А вторая часть опять же идентична предыдущим двум.

Здесь есть еще тонкость, что могут произойти события не только message. Есть еще события subscribed, unsubscribed, conversation_started. Я их рассматриваю как частный случай события message. Мой чат бот настроен на ответ на такие ключевые слова.

Какой же вывод из всех этих текстов.

Если мы знаем JSON структуру, приходящую от мессенджера к нашему чат-боту, и JSON структуру ответа чат-бота, то создание нового чат-бота превращается в тривиальную задачу.

Гораздо большую трудоемкость займет реализация функции getAnswer.

У меня ответы бота в базе данных и они оттуда выдаются на основе полученного запроса.

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

Теги:
Хабы:
Всего голосов 5: ↑3 и ↓2+1
Комментарии11

Публикации

Истории

Работа

PHP программист
204 вакансии

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн