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

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

Загальна інформація
Чат-боти у Facebook побудовані на основі особистих повідомлень з публічної сторінкою від імені користувача.
Тому для створення бота потрібно буде створити сам додаток для доступу до API, і публічну сторінку, з якою будуть спілкуватися користувачі.

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

Реєстрація та налаштування програми
Переходимо до реєстрації свого застосування в обліковому розробника.
Заходимо за посиланням developers.facebook.com/apps
Натискаємо на додавання нового додатка, вибираємо інший налаштування вручну:



Далі заповнюємо форму:



Після створення програми, в лівому меню вибираємо вкладку 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 "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=-token-"

-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 ":"
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 — github.com/fguillot/picoFeed

Виконуємо:
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);
}
}
}
}
}
}
}

/**
* Help Message Send
*
* @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 Data Feed
*
* @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->завантажити($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' => 'https://job4joy.com/marketplace/rss/',
'Web Development' => 'https://job4joy.com/marketplace/rss/?id=3',
'Software Development & IT' => 'https://job4joy.com/marketplace/rss/?id=5',
'Design & Multimedia' => 'https://job4joy.com/marketplace/rss/?id=2',
'Mobile Application' => 'https://job4joy.com/marketplace/rss/?id=7',
'Host & Server Management' => 'https://job4joy.com/marketplace/rss/?id=6',
'Writing' => 'https://job4joy.com/marketplace/rss/?id=8',
'Customer Service' => 'https://job4joy.com/marketplace/rss/?id=10',
'Marketing' => 'https://job4joy.com/marketplace/rss/?id=11',
'Business Services' => 'https://job4joy.com/marketplace/rss/?id=12',
'Translation & Languages' => 'https://job4joy.com/marketplace/rss/?id=14',
]
];


Публікація в каталозі для всіх
Поки бот доступний тільки для власника аккаунта. Щоб бот був доступний для всіх, потрібно На сторінці App Review — опублікувати додаток:



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

Тепер залишається тільки дочекатись модерації.

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

Висновок
У статті ми розглянули базові аспекти створення чат-бота для Facebook.
Якщо тема виявиться популярною, то буду готовий розповісти про досвід розробки ботів під інші популярні месенджери.

Корисні посилання
  1. Getting Started with FB Chatbots — developers.facebook.com/docs/messenger-platform/quickstart
  2. Web hook Reference — developers.facebook.com/docs/messenger-platform/webhook-reference
  3. FB Messenger PHP API — github.com/pimax/fb-messenger-php
  4. Приклади використання PHP API — github.com/pimax/fb-messenger-php-example
  5. Job4Joy FB Bot — github.com/pimax/job4joy_fb


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.