...

воскресенье, 17 апреля 2016 г.

Разработка чат-бота для Facebook Messenger

В настоящее время наблюдается, действительно, бум чат-мессенджеров. Один за другим платформы для обмена мгновенными сообщениями объявляют о запуске платформы для разработки ботов.
Не стал и исключением Facebook. 12 апреля на конференции F8 Facebook представила платформу для разработки ботов для своего мессенджера.
В данной статье хочу поделиться опытом разработки чат-бота для Facebook на PHP.

Общая информация


Чат-боты в Facebook построены на основе личных сообщений с публичной страницей от имени пользователя.
Поэтому для создания бота нужно будет создать само приложение для доступа к API, и публичную страницу, с которой будут общаться пользователи.

Создание страницы


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

Регистрация и настройка приложения


Переходим к регистрации своего приложения в аккаунте разработчика.
Заходим по ссылке http://ift.tt/JVFvIS
Нажимаем на добавление нового приложения, выбираем другой настройку вручную:

Далее заполняем форму:

После создания приложения, в левом меню выбираем вкладку Messenger и кликаем на нее.
Нажимаем «Начать».
В первую очередь выбираем страницу, созданную для бота, и копируем token. Сохраняем его где-нибудь, он нам пригодится дальше.

Дальше мы будем настраивать webhook для обработки входящих сообщений.
На этом шаге, вам нужно закачать следующий скрипт на сервер, где будет размещен бот:

<?php
$verify_token = ""; // Verify token 
if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe' && $_REQUEST['hub_verify_token'] == $verify_token) { 
echo $_REQUEST['hub_challenge']; 
}


В переменную $verify_token необходимо добавить какой-то текст.
Скрипт загружаем на сервер. Допустим, наш скрипт доступен по адресу: domain.com/fbbot

Возвращаемся ко вкладке Messenger в настройках приложения FB.
Ищем блок «Webhooks» и кнопку «Setup Webhooks». Кликаем на нее.

В поле «Обратный URL-адрес» указываем адрес нашего бота — domain.com/fbbot
SSL — сертификат является обязательным. Самоподписанный сертификат не подойдет.

В поле «Подтвердить маркер» указываем тот текст, который указали в переменной $verify_token в скрипте.
В поле «Поля подписки» выбираем, какие уведомления мы хотим получать на наш webhook:

  • message_deliveries — уведомления о доставке сообщения
  • messages — сообщения, написанные пользователем боту
  • messaging_optins — callback при получении сообщения через кнопку на сайте (Send-to-Messenger Plugin)
  • messaging_postbacks — переходы по кнопкам из предыдущих сообщений бота (будет понятно далее)

Выбираем нужные и нажимаем кнопку «Подтвердить и сохранить».

Связываем приложение и страницу


Набираем в консоли:
curl -ik -X POST "http://ift.tt/1W8LfrU"


-token- заменяем на токен вашей страницы.

Типы сообщений в FB Messenger


Сообщения могут быть либо просто текстовые, либо Structured Text, которые в свою очередь могут быть:
  • button — кнопки
  • generic — элементы
  • receipt — счет на оплату

Кнопки (button)


Данный тип предназначен для отправки сообщений, на которые требуется реакция пользователя.
Выглядят они примерно так:

Кнопки могут быть двух типов:

  1. Отправляющие ответ боту
  2. Переходящие по адресу в интернете

Важный момент: в одном таком сообщении может быть максимум 3 кнопки, при попытке отправить сообщение с бОльшим количеством кнопок — оно просто не дойдет до получателя.

Элементы (generic)


Данный тип предназначен для отправки карточек товаров или других элементов, имеющих похожую структуру.
Каждый элемент может иметь: Заголовок, подзаголовок, описание, изображение и кнопки.

В одном сообщении может содержаться до 10 элементов. При наличии более одного элемента, появляется горизонтальная прокрутка.
Важный момент: в одном таком сообщении может быть максимум 3 кнопки, при попытке отправить сообщение с бОльшим количеством кнопок — оно просто не дойдет до получателя.

Счет на оплату (receipt)


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

Важный момент: номер счета должен быть уникальным.

Пишем код


На момент написания бота, на GitHub еще не было реализации API на PHP, поэтому пришлось писать PHP SDK самостоятельно.

Устанавливаем PHP SDK для работы с FB Messenger API при помощи composer:

composer require "pimax/fb-messenger-php" "dev-master"


Создаем файл index.php:
<?php

$verify_token = ""; // Verify token
$token = ""; // Page token

if (file_exists(__DIR__.'/config.php')) {
    $config = include __DIR__.'/config.php';
    $verify_token = $config['verify_token'];
    $token = $config['token'];
}

require_once(dirname(__FILE__) . '/vendor/autoload.php');

use pimax\FbBotApp;
use pimax\Messages\Message;
use pimax\Messages\MessageButton;
use pimax\Messages\StructuredMessage;
use pimax\Messages\MessageElement;
use pimax\Messages\MessageReceiptElement;
use pimax\Messages\Address;
use pimax\Messages\Summary;
use pimax\Messages\Adjustment;

$bot = new FbBotApp($token);

if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe' && $_REQUEST['hub_verify_token'] == $verify_token)
{
     // Webhook setup request
    echo $_REQUEST['hub_challenge'];
} else {

     $data = json_decode(file_get_contents("php://input"), true);
     if (!empty($data['entry'][0]['messaging']))
     {
            foreach ($data['entry'][0]['messaging'] as $message)
            {
// Получено сообщение
// Основной код будет в этом блоке
// ...
            }
   }
}


Пробуем отправить сообщение пользователю в ответ при получении любого сообщения от него.
Для этого в блок получения сообщения, добавляем:
$bot->send(new Message($message['sender']['id'], ‘Hi there!'));

Проверяем. Находим в мессенджере нашего бота и пробуем отправить ему любое сообщение.
В ответ мы должны получить от него «Hi there!».
Важно: Пока приложение не прошло модерацию бот будет работать только для автора приложения.

Если все работает как надо, идем дальше.

В блок получения сообщения добавляем:

// Пропускаем обработку отметок о доставке сообщения
if (!empty($message['delivery'])) {
    continue;
}

$command = "";
// Получено сообщение от пользователя, записываем как команду
if (!empty($message['message'])) {
    $command = $message['message']['text'];
    // ИЛИ Зафиксирован переход по кнопке, записываем как команду
} else if (!empty($message['postback'])) {
    $command = $message['postback']['payload'];
}

// Обрабатываем команду
switch ($command) {

    // When bot receive "text"
    case 'text':
        $bot->send(new Message($message['sender']['id'], 'This is a simple text message.'));
        break;

    // When bot receive "button"
    case 'button':
      $bot->send(new StructuredMessage($message['sender']['id'],
          StructuredMessage::TYPE_BUTTON,
          [
              'text' => 'Choose category',
              'buttons' => [
                  new MessageButton(MessageButton::TYPE_POSTBACK, 'First button'),
                  new MessageButton(MessageButton::TYPE_POSTBACK, 'Second button'),
                  new MessageButton(MessageButton::TYPE_POSTBACK, 'Third button')
              ]
          ]
      ));
    break;

    // When bot receive "generic"
    case 'generic':

        $bot->send(new StructuredMessage($message['sender']['id'],
            StructuredMessage::TYPE_GENERIC,
            [
                'elements' => [
                    new MessageElement("First item", "Item description", "", [
                        new MessageButton(MessageButton::TYPE_POSTBACK, 'First button'),
                        new MessageButton(MessageButton::TYPE_WEB, 'Web link', 'http://facebook.com')
                    ]),

                    new MessageElement("Second item", "Item description", "", [
                        new MessageButton(MessageButton::TYPE_POSTBACK, 'First button'),
                        new MessageButton(MessageButton::TYPE_POSTBACK, 'Second button')
                    ]),

                    new MessageElement("Third item", "Item description", "", [
                        new MessageButton(MessageButton::TYPE_POSTBACK, 'First button'),
                        new MessageButton(MessageButton::TYPE_POSTBACK, 'Second button')
                    ])
                ]
            ]
        ));

    break;

    // When bot receive "receipt"
    case 'receipt':

        $bot->send(new StructuredMessage($message['sender']['id'],
            StructuredMessage::TYPE_RECEIPT,
            [
                'recipient_name' => 'Fox Brown',
                'order_number' => rand(10000, 99999),
                'currency' => 'USD',
                'payment_method' => 'VISA',
                'order_url' => 'http://facebook.com',
                'timestamp' => time(),
                'elements' => [
                    new MessageReceiptElement("First item", "Item description", "", 1, 300, "USD"),
                    new MessageReceiptElement("Second item", "Item description", "", 2, 200, "USD"),
                    new MessageReceiptElement("Third item", "Item description", "", 3, 1800, "USD"),
                ],
                'address' => new Address([
                    'country' => 'US',
                    'state' => 'CA',
                    'postal_code' => 94025,
                    'city' => 'Menlo Park',
                    'street_1' => '1 Hacker Way',
                    'street_2' => ''
                ]),
                'summary' => new Summary([
                    'subtotal' => 2300,
                    'shipping_cost' => 150,
                    'total_tax' => 50,
                    'total_cost' => 2500,
                ]),
                'adjustments' => [
                    new Adjustment([
                        'name' => 'New Customer Discount',
                        'amount' => 20
                    ]),

                    new Adjustment([
                        'name' => '$10 Off Coupon',
                        'amount' => 10
                    ])
                ]
            ]
        ));

    break;

    // Other message received
    default:
        $bot->send(new Message($message['sender']['id'], 'Sorry. I don’t understand you.'));
}


Пробуем отправить боту сообщения:
  • text
  • button
  • generic
  • receipt

Если все сделано по инструкции, то должны получить в мессенджер сообщения всех типов.

Реальный пример Бота для фриланс-биржи Job4Joy


Итак, наша цель, реализовать бота, который по нашему запросу будет выдавать новые проекты в соответствующей категории.
Данные будем получать по RSS, используя picoFeed — http://ift.tt/1WxqGG5

Выполняем:

composer require fguillot/picofeed @stable
composer require "pimax/fb-messenger-php" "dev-master"


Создаем файл index.php следующего содержания (комментарии приведены в коде):
<?php

$verify_token = ""; // Verify token
$token = ""; // Page token
$config = []; // config

if (file_exists(__DIR__.'/config.php')) {
    $config = include __DIR__.'/config.php';
    $verify_token = $config['verify_token'];
    $token = $config['token'];
}

require_once(dirname(__FILE__) . '/vendor/autoload.php');

use PicoFeed\Reader\Reader;
use pimax\FbBotApp;
use pimax\Messages\Message;
use pimax\Messages\MessageButton;
use pimax\Messages\StructuredMessage;
use pimax\Messages\MessageElement;

$bot = new FbBotApp($token);

if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe' && $_REQUEST['hub_verify_token'] == $verify_token)
{
    // Webhook setup request
    echo $_REQUEST['hub_challenge'];
} else {

    $data = json_decode(file_get_contents("php://input"), true);
    if (!empty($data['entry'][0]['messaging']))
    {
        foreach ($data['entry'][0]['messaging'] as $message)
        {
            if (!empty($data['entry'][0])) {

                if (!empty($data['entry'][0]['messaging']))
                {
                    foreach ($data['entry'][0]['messaging'] as $message)
                    {
                        if (!empty($message['delivery'])) {
                            continue;
                        }

                        $command = "";

                        if (!empty($message['message'])) {
                            $command = $message['message']['text'];
                        } else if (!empty($message['postback'])) {
                            $command = $message['postback']['payload'];
                        }

                        if (!empty($config['feeds'][$command]))
                        {
                            getFeed($config['feeds'][$command], $bot, $message);
                        } else {
                            sendHelpMessage($bot, $message);
                        }
                    }
                }
            }
        }
    }
}

/**
 * Send Help Message
 *
 * @param $bot Bot instance
 * @param array $message Received message
 * @return bool
 */
function sendHelpMessage($bot, $message)
{
    $bot->send(new StructuredMessage($message['sender']['id'],
        StructuredMessage::TYPE_BUTTON,
        [
            'text' => 'Choose category',
            'buttons' => [
                new MessageButton(MessageButton::TYPE_POSTBACK, 'All jobs'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Web Development'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Software Development & IT')
            ]
        ]
    ));

    $bot->send(new StructuredMessage($message['sender']['id'],
        StructuredMessage::TYPE_BUTTON,
        [
            'text' => ' ',
            'buttons' => [
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Design & Multimedia'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Mobile Application'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Host & Server Management')
            ]
        ]
    ));


    $bot->send(new StructuredMessage($message['sender']['id'],
        StructuredMessage::TYPE_BUTTON,
        [
            'text' => ' ',
            'buttons' => [
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Writing'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Mobile Application'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Marketing')
            ]
        ]
    ));

    $bot->send(new StructuredMessage($message['sender']['id'],
        StructuredMessage::TYPE_BUTTON,
        [
            'text' => ' ',
            'buttons' => [
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Business Services'),
                new MessageButton(MessageButton::TYPE_POSTBACK, 'Translation & Languages')
            ]
        ]
    ));


    return true;
}

/**
 * Get Feed Data
 *
 * @param $url Feed url
 * @param $bot Bot instance
 * @param $message Received message
 * @return bool
 */
function getFeed($url, $bot, $message)
{
    try {
        $reader = new Reader;
        $resource = $reader->download($url);

        $parser = $reader->getParser(
            $resource->getUrl(),
            $resource->getContent(),
            $resource->getEncoding()
        );

        $feed = $parser->execute();
        $items = array_reverse($feed->getItems());

        if (count($items)) {
            foreach ($items as $itm)
            {
                $url = $itm->getUrl();
                $message_text = substr(strip_tags($itm->getContent()), 0, 80);

                $bot->send(new StructuredMessage($message['sender']['id'],
                    StructuredMessage::TYPE_GENERIC,
                    [
                        'elements' => [
                            new MessageElement($itm->getTitle(), $message_text, '', [
                                new MessageButton(MessageButton::TYPE_WEB, 'Read more', $url)
                            ]),

                        ]
                    ]
                ));
            }

        } else {
            $bot->send(new Message($message['sender']['id'], 'Not found a new projects in this section.'));
        }
    }
    catch (Exception $e) {
        writeToLog($e->getMessage(), 'Exception');
    }

    return true;
}

/**
 * Log
 *
 * @param mixed $data Data
 * @param string $title Title
 * @return bool
 */
function writeToLog($data, $title = '')
{
    $log = "\n------------------------\n";
    $log .= date("Y.m.d G:i:s") . "\n";
    $log .= (strlen($title) > 0 ? $title : 'DEBUG') . "\n";
    $log .= print_r($data, 1);
    $log .= "\n------------------------\n";

    file_put_contents(__DIR__ . '/imbot.log', $log, FILE_APPEND);

    return true;
}

И файл config.php следующего содержания:

<?php

return [
    'token' => '',   // Токен страницы
    'verify_token' => '',  // Проверочный токен
    'feeds' => [
        'All jobs' => 'http://ift.tt/1Sdu9EG',
        'Web Development' => 'http://ift.tt/1WxqI0P',
        'Software Development & IT' => 'http://ift.tt/1Sdu9EI',
        'Design & Multimedia' => 'http://ift.tt/1WxqI0R',
        'Mobile Application' => 'http://ift.tt/1SdubMH',
        'Host & Server Management' => 'http://ift.tt/1WxqGG7',
        'Writing' => 'http://ift.tt/1Sdu9EK',
        'Customer Service' => 'http://ift.tt/1WxqGG9',
        'Marketing' => 'http://ift.tt/1SducA8',
        'Business Services' => 'http://ift.tt/1WxqGGb',
        'Translation & Languages' => 'http://ift.tt/1SdubMJ',
    ]
];

Публикация в каталоге для всех


Пока бот доступен только для владельца аккаунта. Чтобы бот был доступен для всех, нужно На странице App Review — опубликовать приложение:

После этого нужно запросить модерацию мессенджера. Для этого переходим на вкладку — Messenger.
В блоке «App Review for Messenger» нажимаем кнопку «Request Permissions».
В появившемся окне выбираем «pages_messaging» и нажимаем «Add items».

Теперь остается только дождаться модерации.

На момент написания этой статьи, модерация нашего первого бота не завершена, хотя прошло уже более двух рабочих дней с момента подачи заявки.

Заключение


В статье мы рассмотрели базовые аспекты создания чат-бота для Facebook.
Если тема окажется популярной, то готов буду рассказать об опыте разработки ботов под другие популярные мессенджеры.

Полезные ссылки


  1. Getting Started with FB Chatbots — http://ift.tt/1WxqI0X
  2. Web hook Reference — http://ift.tt/1WxqIhb
  3. FB Messenger PHP API — http://ift.tt/1WxqGGf
  4. Примеры использования PHP API — http://ift.tt/1WxqIhd
  5. Job4Joy FB Bot — http://ift.tt/1WxqIhf

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

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий