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

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

Для решения проблемы уже был установлен личный кабинет на сайте, была установлена куча многоканальных телефонов, функционирует 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.

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

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

Комментарии (10)


  1. drmartin
    00.00.0000 00:00
    +1

    Об


    1. khrapko Автор
      00.00.0000 00:00
      +1

      Всегда проблемы с грамотностью были. Плохо в школе учился. Спасибо за подсказку. Исправил. По моему даже правило вспомнил, что если следующее слово начинается с гласной то тогда ОБ, а иначе О


  1. dprotopopov
    00.00.0000 00:00
    +1

    Про паттерны проектирования не читали? Например в вашем случае Адаптер https://ru.wikipedia.org/wiki/Адаптер_(шаблон_проектирования)

    Для универсализации достаточно просто описать интерфейс и убедить других его использовать - иначе это мертворождённое дитя

    За попытку - зачёт


    1. modestguy
      00.00.0000 00:00

      Вот да...интерфейс или контракт выделить и пиши себе классы с реализациями методов, поддерживающих интерфейс) Видимо ещё вступает на путь программирования) все впереди;)


      1. khrapko Автор
        00.00.0000 00:00

        Видимо ещё вступает на путь программирования) все впереди;)

        К большому моему сожалению уже заканчиваю. Все таки 64 года.
        Просто была попытка простенько написать что то не применяя ООП, и не используя разные там классы и сторонние библиотеки.
        При этом напоминаю, я говорил впереди. Никогда я ничего не писал для Веб и Интернет


    1. khrapko Автор
      00.00.0000 00:00

      Я знал, что мне будут рассказывать про классы и паттерны. В том то и дело, что я хотел максимально упростить структуру программы и не использовать тут ООП


  1. ReDev1L
    00.00.0000 00:00

    О какой универсальности мы говорим, если про PSR autoloader, http, logger мы не слышали и пишем скрипты на коленке? Ну серьёзно, это ведь не времена php 5.2 с magic quotes.

    Ваше решение - велосипед, на packagist наберётся 100 пакетов под различные мессенджеры. Вам время свое не жалко?


    1. khrapko Автор
      00.00.0000 00:00

      Спасибо. Я точно не слышал о том, что вы пишете. Ни про PSR autoloader ни про logger ни про http.
      К сожалению мое время прошло в других битвах и других технологиях.
      Вот не знаю не потрачу я больше времени на изучение того, что вы написали, чем на написание этих 10 строчек кода на коленке.
      Я накропал это все, чтобы кто-нибуть показал как лучше. Может ссылки какие дал.
      Повторяю, я старался избегать использования классов.
      Постараюсь поискать эти 100 пакетов на packaglist


      1. FanatPHP
        00.00.0000 00:00

        Я накропал это все, чтобы кто-нибуть показал как лучше. Может ссылки какие дал.

        В этом случае обычно пишут на qna.habr.com


        1. khrapko Автор
          00.00.0000 00:00

          Я думал, что там задают вопросы как сделать что-то.
          А я привожу тут готовый работающий код.
          Ну правда написанный на коленке.
          Без фреймворков и классов.
          И код весь занимает 9 килобайт в четырех файлах
          По совету товарищей полез на Packagist
          Ну на первом месте там стоит botman/botman.
          Наверное классная штуковина.
          Но как в этом разобраться. Та там 57 файлов в 24 фолдерах.
          Видно я таки совсем старой формации, когда говорю тут о байтах этих.
          Помню в молодости соревновались, кто самую маленькую прогу напишет на ассемблере. Ну и чтобы она полезная была. Я написал прогу из 6 байт. На втором месте по моему 11 было.
          Моя увеличивала скорость автоповтора нажатой клавиши. Спросите зачем. Да потому, что не было это на тех компах, что мы использовали. Потом это в БИОСе всюду было встроено.
          Та, что на втором - это был переключатель клавиатуры.
          Ну в общем как говорил поручик Ржевский - эх молодость, байтом сюда, байтом туда.
          И теперь то терабайтов и не хватает.