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

В ходе разработки проекта надо было подключить fb api для того что бы менеджеры сайта смогли общаться с клиентами сайта не только из админки, но и из своих профилей fb messenger.

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

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

Я не буду углубляться на тему создания чата на сайте. По этому вопросу много написано в просторах интернета.

Допустим у нас есть сайт, с уже созданным чатом (node.js, socket.io).

Давайте разделим статью на 2 части.

1. Ресурсы, которые нужны.
2. Написание кода.

1. Ресурсы, которые нужны.

Нам понадобиться приложение fb с определенными правами, который мы должны создать с верифицированным профилем fb. Для создания приложения можно зайти по этой ссылке. После создания приложения вы попадете в основные настройки. Там заполните все как говорит fb.

После надо настроить авторизацию через Facebook. Заходим в раздел и заполняем url обратного вызова. (Для локального тестирования можно задать домен так https://localhost/some-url)

И последняя часть настройки это надстройка URL-а обратного вызова для webhook. (это для того что бы messenger отправлял нам сообщения по этому адресу.)

Здесь обратите внимание на Маркер поддверждения(token). Этот маркер генерируйте вы, это не маркер авторизации.

Оно используется так.

Когда вы задаете webhook url, fb запрашивает этот url с маркером доступа который вы там написали. Что бы понять существует ли этот url, и принадлежит оно вам.

Для этого первым делом создайте маршрут на своем сайте который будет принимать запросы от messenger.

2. Написание кода.

Приступим к написанию кода.

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

Подробно об авторизации ножете прочитать в документации.

Напишем код для авторизации. (код написан в php laravel).

public function fbAuthorize(Request $request)
    {
        if (!auth()->user())
            return redirect()->to('/');

        $user_id = $request->get('user_id');

        $code = $request->get('code');
        $userMess = 'Internal Server Error. Please connect our support';
        try {
            if (!empty($code)) {
                $userResponse = $this->fbService->getUserAccessToken($code);
                if (!empty($userResponse)) {
                    $pageResponse = $this->fbService->getPageToken($userResponse->access_token);
                    if (!empty($pageResponse)) {
                        $pageProfile = $this->fbService->getPageProfilePicture($pageResponse->data[0]->id, $pageResponse->data[0]->access_token);
                        $profileImage = !empty($pageProfile->data) ? $pageProfile->data->url : '';
                        $data = [
                            'access_token'   => $pageResponse->data[0]->access_token,
                            'page_id'        => $pageResponse->data[0]->id,
                            'page_name'      => $pageResponse->data[0]->name,
                            'page_image_url' => $profileImage,
                            'type'           => 'page'
                        ];
                        $this->fbService->createOrUpdateWithAuth($data, $user_id);

                        $this->fbService->subscribeWebHooks($pageResponse->data[0]->id, $pageResponse->data[0]->access_token);

                        $userMess = 'You fb page connected';
                    }
                }
            }
            return redirect()
                ->back()
                ->with('success', $userMess);
        } catch (Exception $e) {
            return redirect()
                ->back()
                ->with('error', $e->getMessage());
        }
    }

Как видно из фрагмента кода, у нас есть написанный сервиз для запросов fb api.
Давайте первым делом поймем эту часть кода, потом перейдем дальше к написанию сервиса (наш сервис просто имеет несколько готовых методов для отправки curl запроса в fb).

Как происходит авторизация на fb.

Get запрос на определенный url fb с параметрами.

https://www.facebook.com/v12.0/dialog/oauth?client_id={$fbAppId}&redirect_url={$redirectUrl}&scope=pages_messaging,pages_read_engagement&state={$customParams}

Обратите внимание на последний параметр в url, названный state. Это необязательный параметр. fb дает возможность добавить его если вам нужно какое то значение после перенаправления авторизации на ваш сайт.

После этого запроса fb предложит вам подключить страницу к приложению.

Далее происходит редирект на указанных url с параметром code.

Теперь давайте подробно пройдемся по сервису написанному для fb, который я выше упомянул.

Как видно в коде, сверху там я написал вот эти методы.

1. getUserAccessToken
2. getPageAccessToken
3. subscribeWebhooks

Fb api имеет множество маркеров доступа (token), и для того что бы мы могли отравить сообщение нашей странице нам нужен именно маркер доступа для страницы (с определенными правами - scope).

Для этого кода уже мы получили параметр code от первого get запроса, нам нужно с его помощью запросить маркер доступа пользователя (user access token).

Подробно по ссылке.

Вот код для получения user access token. Код написан с помощю пакета guzzle http.

public function getUserAccessToken($code)
    {
        try {
            $options = [
                'headers' => [],
                'query' => [
                    'code' => $code,
                    'client_id' => $this->fbAppId,
                    'client_secret' => $this->fbAppSecret,
                    'redirect_uri' => env('APP_URL') . '/api/fb-authorize'
                ]
            ];
            $uri = "{$this->fbAppUrl}/oauth/access_token";
            $response = $this->httpGet($uri, $options);
            $response = $response->getBody()->getContents();
            return json_decode($response);
        } catch (\Exception $e) {
            return ['success' => false, 'code' => $e->getCode(), 'message' => $e->getMessage()];
        }
    }

Здесь нечего сложного нет. Просто отправляем curl запрос на fb api, и получаем маркер доступа пользователя. Вот ещё пример запроса из документации fb для получения маркера пользователя.

curl -X GET "https://graph.facebook.com/oauth/access_token
  ?client_id={your-app-id}
  &client_secret={your-app-secret}
  &grant_type=client_credentials"

Далее когда уже у нас есть маркер пользователя, надо получить еще маркер доступа к странице(user access token).

Второй метод именно для этого. Вот пример кода.

public function getPageToken($userToken) {
        $options = [
            'headers' => [],
            'query' => [
                'access_token' => $userToken,
                'scope'        => 'pages_messaging'
            ]
        ];
        $uri = $this->fbAppUrl.'/me/accounts';
        $response = $this->httpGet($uri, $options);
        $response = $response->getBody()->getContents();
        return json_decode($response);
    }

Здесь с помощю guzzle http анологичным образом делаем curl запрос и получаем token страницы. Примeр запроса из документации fb.

curl -i -X GET "https://graph.facebook.com/me/accounts?access_token={user-access-token}

Обратите внимание на curl запрос /me/accounts. В документации вы увидите /{your-user-id}/accounts. Не знаю документация старая или по другой причине, если по fb id сделать запрос, то получите ошибку.


И второй момент, после этого запроса fb отпровляет нам не только маркер страницы(page access token) но и id страницы. Сохроните id то же. Дальше оно нам потребуется.

На этом этапе у нас уже все готово что бы отправлять сообщение на нашу страницу.

Теперь нам нужно подписаться на события сообщения. Что бы когда мы отправим сообщение из нашего messenger-а на страницу то fb api отпровлял это сообщение нам на сайт по указаннаму url адресу (webhook url), который мы задали в настройках нашего приложения.

Вот сам код для подписки на события сообщений.

public function subscribeWebHooks($pageId, $pageToken)
    {
        $options = [
            'headers' => [],
            'query' => [
                'access_token' => $pageToken,
                'subscribed_fields' => 'messages,messaging_optouts,messaging_payments,messaging_account_linking,messaging_feedback,messaging_postbacks,message_deliveries,messaging_pre_checkouts,messaging_referrals,message_reactions,messaging_customer_information,messaging_optins,message_reads,messaging_checkout_updates,message_echoes,messaging_handovers,inbox_labels'
            ]
        ];
        $uri = $this->fbAppUrl.'/'.$pageId.'/subscribed_apps';
        $response = $this->httpPost($uri, $options);
        $response = $response->getBody()->getContents();
        return $response;
    }

Как видно из кода, мы отпровляем get запрос на конечную точку fb /page_id/subscribed_apps . Вот по этому выше я подчеркнул что нужно сохронить еще и id страницы, не только token .

Вот документация по подпискам.

Теперь когда мы отправим сообщение из нашего messenger-а на нашу страницу то наш api отправит сообщение на наш сайт.

Здесь важное примечание по webhook url который мы задали в настройках нашего приложения.

В первой части я сказал что эта конечная точка должна уже быть на вашем сайте. Потому что когда вы задаете url подписки, fb отправляет get запрос на этот url с маркером подтверждения, который вы напишете в определенном месте. Смотрите картинку выше.

То есть нам нужно 2 конечные точки под одним и тем же именем ( get, post )
Первая для проверки, вторая уже для самого приема сообщений от fb api

Вот код который я написал в node.js для этого. (вы можете написать это на любом языке.)

код для http сервера.

const http = require("http");
const app = require("express")();
const cors = require("cors");
const bodyParser = require("body-parser");

app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const server = http.createServer(app);
server.listen(port, function() {
  console.log("listening port CHECK: " + port);
});

module.exports.app = app;
module.exports.server = server;

Вот сам код для конечной точки.

const { app } = require('./config/httpServer');
app.get('/webhook', (req, res) => {
  console.log('GET /webhook: ', req);

  if (req.query['hub.mode'] && req.query['hub.verify_token'] === facebookHubVerifyToken) {
	  res.status(200).send(req.query['hub.challenge']);
  } else {
  	res.status(403).end();
  }
});

/**
	* Handling all messages from Messenger (Facebook)
*/
app.post('/webhook', messangerAppController);

Теперь нам осталось только отправка самого сообщения из нашего сайта к messenger и обратно.

Для того что бы нам отправить сообщение в пользователю messenger нам надо знать его psid. Это уникальный id каждего пользователся messenger, не fb id . Для того что бы мы взяли psid нам надо что бы пользователь первым написал нам на страницу в messenger.

Как видно из кода у нас на node есть контроллер для обработки входящих сообщений. Давайте детально рассмотрим его.

/**
 * @param req
 * @param res
 * @returns {Promise<void>}
 */
const messangerAppController = async function (req, res) {
    if (req.body.object !== 'page') {
        res.status(400).end();
        return;
    }

    if (!Array.isArray(req.body.entry)) {
        res.status(200).end();
        console.log('What is it?');
        return;
    }
    req.body.entry.forEach((entry) => {
        entry.messaging.forEach(async (event) => {
            if (event.message && event.message.text) {
                const sid = event.sender.id;
                const message = event.message.text;
                const replyId = event.message.reply_to ? event.message.reply_to.mid : false
                try {
                    let facebook = await Facebook.findOne({ where: { psid: sid } });
                    if (facebook && replyId) {
                        
                        const newMessage = await Message.create({
                            chat_id: chat.id,
                            from_support: user.id,
                            type_id: Message.TYPE_MESSAGE,
                            unread: 1,
                            content: message
                        });
                        //Здесь уже обработка кода как вам угодно.
                    } else {
                        let fb = await Facebook.connectUser(sid, message);
                        let fbAuth = await FbAuth.findOne({where: {id: fb.fb_auth_id}});
                        let fb_page_token = fbAuth && fbAuth.access_token ? fbAuth.access_token : false;
                        sendMessage(fb_page_token, sid, 'You successfully connected your Messenger.');
                    }
                } catch (error) {
                    const errorMessage = `Error: ${error.message}`;
                    sendMessage(sid, errorMessage);
                    console.log('Messenger Webhook: ', errorMessage);
                }
            }
        });
    });
    res.status(200).end();
}

module.exports = messangerAppController;

Как видно из строки 20 мы взяли psid пользователя messenger. Это значит что после подключения api к странице нам надо отправить первое сообщение на страницу fb что бы из webhook узнать psid пользователя messenger, для дальнейшего использования.(отправки сообщения в messenger) .

Допустим, у нас есть несколько менеджеров службы поддержки на нашем сайте.

На сайте давайте создадим страницу для подключения messenger-а менеджера.

После клика на кнопку сгенерируем уникальный код для него. И говорим что бы он отправил этот код из своего messenger-а на нашу подключенную страницу.

После отправки мы получим psid данного пользователя fb . И уже сможем отправить ему сообщение.


То же самое написано в коде выше.

И последним делом посмотрим код отправки самого сообщения (функция sendMessage в коде выше) к данному пользователю fb.

function sendMessage(accessToken, recipient, messege = '') {
    request({
        url: 'https://graph.facebook.com/v12.0/me/messages',
        qs: {
            access_token: accessToken
        },
        method: 'POST',
        json: {
            recipient: {id: recipient},
            message: {text: messege}
        }
    }, function (error, response) {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }
    });
}
module.exports.sendMessage = sendMessage;

Мне кажется этот код понятен, с помощью маркера доступа к странице отправляем сообщение пользователю messenger.

И в конец вот документация по отправке сообщений в messenger. Для подробного изучения всех возможностей который нам дает Fb Graph Api.

Ну вот и все. Мы подключили fb messenger.

Спасибо что прочитали.

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


  1. streloc84
    27.01.2022 18:32
    +2

    А знаете почему нет полных и исчерпывающих инструкций по взаимодействию с fb? Потому как они клали с прибором на обратную совместимость! Раз в несколько месяцев они обязательно меняют api, ключи или еще что-нибудь - после чего все надо переделывать.


    1. rgalstyan Автор
      28.01.2022 14:22

      Меняют но не так часто. У меня есть апи на версии 7. И работает.


  1. mSnus
    28.01.2022 09:44
    +1

    Сервиз

    Примар

    Для того что бы мы взяли psid нам надо что бы

    Статью надо немного довычитать. Где-то опечатки, где-то кусок потерялся.


    1. rgalstyan Автор
      28.01.2022 14:27

      Спасибо за отзыв