Статьи

Использование обновлений Facebook и API подписки

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

Конечно, есть и другие способы использования обновлений в реальном времени, и мы действительно рассмотрим один из них в этой статье: пример приложения, которое отправляет электронное письмо людям, когда другие их «лишают друзей». (На самом деле это не совсем верно — оно говорит вам, когда кто-то больше не является вашим «другом», независимо от того, кто сделал друзей).

Начиная

Если вы когда-либо создавали приложение для Facebook, регистрация приложения будет для вас второй натурой. Перейдите на https://developers.facebook.com , нажмите «Приложения», затем нажмите «Создать новое приложение» и следуйте инструкциям. Сохраните идентификатор приложения и секрет, потому что это понадобится вам позже, чтобы установить соединение с Facebook.

Кроме того, приложение должно быть общедоступным для Facebook, чтобы «пинговать» его. Возможно, вы захотите использовать такой сервис, как AppFog, Heroku или PagodaBox, или просто разместить его на доступном сервере.

После того как вы зарегистрировались в Facebook и знаете, где вы будете размещать приложение, для начала стоит либо загрузить скелетный проект для вашей любимой платформы (в этой статье используется этот скелетон на основе Slim от Timothy Boronczyk), либо вы можете скачать статью пример кода из GitHub.

Во-первых, нам нужно сделать несколько дополнений в composer.json file скелета composer.json file . Мы установим три дополнительные библиотеки: Facebook SDK для доступа к их API, PHPMailer для простой отправки электронной почты и порт популярной библиотеки JavaScript Underscore.

 { "require": { ... "facebook/php-sdk": "dev-master", "phpmailer/phpmailer": "dev-master", "underscore/underscore.php": "dev-master" } } 

Затем создайте файл конфигурации, скопировав config/config.php.example в config/config.php , задайте свои конкретные учетные данные базы данных и внесите следующие дополнения:

 'facebook.app_id' => 'FACEBOOK-APP-ID', 'facebook.app_secret' => 'FACEBOOK-APP-SECRET', 'facebook.verify_token' => 'FACEBOOK-VERIFY-TOKEN', 'smtp.host' => 'SMTP-HOST', 'smtp.port' => SMTP-PORT, 'smtp.encryption' => 'tls', // or 'ssl' 'smtp.username' => 'SMTP-USERNAME', 'smtp.password' => 'SMTP-PASSWORD', 'smtp.from_address' => '[email protected]', 'smtp.from_name' => 'SitePoint Facebook Real-Time Tutorial', 

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

Теперь добавьте следующее в include/services.php , который обрабатывает инъекции зависимостей, чтобы у нашего приложения был легкий доступ к библиотекам Facebook SDK и PHPMailer:

 $c['facebook'] = function($c) { $config = $c['config']; return new Facebook(array( 'appId' => $config['facebook.app_id'], 'secret' => $config['facebook.app_secret'], 'cookie' => true, )); }; $c['phpmailer'] = function($c) { $config = $c['config']; $mail = new PHPMailer(); $mail->IsSMTP(); $mail->Host = $config['smtp.host']; $mail->Port = $config['smtp.port']; $mail->SMTPAuth = true; $mail->Username = $config['smtp.username']; $mail->Password = $config['smtp.password']; $mail->SMTPSecure = $config['smtp.encryption']; $mail->From = $config['smtp.from_address']; $mail->FromName = $config['smtp.from_name']; $mail->WordWrap = 100; $mail->IsHTML(true); return $mail; }; 

Наконец, нам нужно настроить схему нашей базы данных. (Я собираюсь использовать MySQL, хотя NotORM — ORM в комплекте со скелетным приложением — работает с любой базой данных, поддерживающей PDO.) Есть только одна таблица:

 CREATE TABLE users ( id INTEGER NOT NULL AUTO_INCREMENT, fb_id VARCHAR(64), name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, friends TEXT, fb_access_token VARCHAR(255), fb_access_token_expires INTEGER, PRIMARY KEY (id) , UNIQUE INDEX fb_id_UNIQUE (fb_id ASC) ); 

Авторизация приложения

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

В обратном вызове нам нужно сделать несколько вещей:

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

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

Сначала добавьте маршрут для главной страницы:

 $app->get('/', function () use ($app, $c) { $url = $c['facebook']->getLoginUrl(array( 'scope' => 'email', 'redirect_uri' => $app->request()->getUrl() . '/confirm', )); $app->view()->setData(array( 'login_url' => $url, )); $app->render('index.html'); }); 

Все, что делает этот маршрут, — это получение URL-адреса входа в систему — указание, что нам нужен адрес электронной почты пользователя в качестве дополнительного разрешения — и установка обратного вызова для подтверждения. Важные вещи происходят в обратном вызове.

Затем добавьте маршрут для подтверждения обратного вызова:

 $app->get('/confirm', function () use ($app, $c) { $config = $c['config']; $facebook = $c['facebook']; // exchange the code for an access token $url = sprintf( 'https://graph.facebook.com/oauth/access_token?client_id=%s&redirect_uri=%s&client_secret=%s&code=%s', $config['facebook.app_id'], urlencode($app->request()->getUrl() . '/confirm'), $config['facebook.app_secret'], $app->request()->get('code') ); $response = file_get_contents($url); $params = null; parse_str($response, $params); $token = $params['access_token']; // exchange the access token for a long-term token $url = sprintf( 'https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=%s&client_secret=%s&fb_exchange_token=%s', $config['facebook.app_id'], $config['facebook.app_secret'], $token ); $response = file_get_contents($url); $params = null; parse_str($response, $params); $token = $params['access_token']; $tokenExpires = $params['expires']; // get the user's information $facebook->setAccessToken($token); $fb_user = $facebook->api('/me'); $friends = $facebook->api('/me/friends'); // create the database entry $c['db']->users->insert(array( 'fb_id' => $fb_user['id'], 'name' => $fb_user['name'], 'email' => $fb_user['email'], 'friends' => serialize($friends['data']), 'fb_access_token' => $token, 'fb_access_token_expires' => $tokenExpires )); }); 

В этом простом примере есть несколько ограничений:

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

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

Настройка подписок

API подписок Facebook предоставляет REST-интерфейс для подписок приложения. Подписка — это просто сообщение Facebook, какие обновления в реальном времени мы хотим получать. Мы указываем объект — в нашем случае, пользователи — и поля — друзья — нас интересует.

Обратите внимание, что мы подписываемся на типы объектов, а не на конкретные объекты; подписка для пользователей означает, что мы получим обновление для всех, кто авторизовал приложение, а не только для конкретного пользователя. Кроме того, обновления в реальном времени ограничены определенными типами объектов и подмножеством их полей. Обратитесь к документации обновлений в реальном времени для получения дополнительной информации.

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

Всякий раз, когда мы добавляем или изменяем подписку, Facebook проверяет подписку, отправляя запрос GET на обратный вызов со следующими параметрами:

  • hub.mode — строка «подписаться»
  • hub.challenge — случайная строка
  • hub.verify_token — значение verify_token, которое мы указали при создании подписки

Наш обратный вызов просто проверяет, что hub.mode содержит строку «подписаться», что hub.verify_token совпадает с тем, который мы указали, и все в порядке просто выводит значение hub.challenge .

 $app->get('/subscriptions', function () use ($app, $c) { $req = $app->request(); $verify = $c['config']['facebook.verify_token']; if ($req->get('hub_mode') == 'subscribe' && $req->get('hub_verify_token') == $verify) { echo $req->get('hub_challenge'); } }); 

Чтобы настроить подписку через портал разработчика, перейдите в свое приложение и нажмите «Обновления в реальном времени» в разделе «Настройки» с левой стороны. Там, где написано «Пожалуйста, выберите объект, на который вы хотите подписаться», выберите «пользователь» и нажмите «Подтвердить». Теперь вы увидите следующую форму:

Realtime-обновления-01

Введите «friends» в качестве полей объекта, «YOUR-URL> / subscription» для конечной точки обратного вызова и введите случайную строку для токена проверки (также запишите эту случайную строку в файле конфигурации).

В качестве альтернативы, чтобы настроить подписку с помощью API подписок, сделайте запрос POST по адресу https://graph.facebook.com/<APP_ID>/subscription . Например:

 $facebook->api( $config['facebook']['app_id'] . '/subscriptions', 'POST', array( 'object' => 'user', 'fields' => 'friends', 'callback_url' => 'http://www.example.com/subscriptions', 'verify_token' => $config['facebook']['verify_token'] ) ); 

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

Сами обновления подписки размещаются в нашем приложении в формате JSON. Для их обработки нам нужно настроить обратный вызов, который проверяет подлинность запроса, а затем обновляет наши локальные данные.

Код в следующих разделах находится в обратном вызове POST:

 $app->post('/subscriptions', function () use ($app, $c) { 

Проверка POST-запроса

Настоятельно рекомендуется проверить, что запрос POST действительно пришел из Facebook. К счастью, это относительно просто. При отправке запроса Facebook генерирует подпись на основе тела запроса и секрета нашего приложения и включает его в заголовок X-HUB-SIGNATURE. Мы просто проверяем, установлен ли заголовок, сами генерируем подпись и сравниваем их.

 $req = $app->request(); $headers = $req->headers(); $signature = $headers['X_HUB_SIGNATURE']; $body = $req->getBody(); $expected = 'sha1=' . hash_hmac('sha1', $body, $facebook->getApiSecret()); if ($signature != $signature) { exit(); } 

Сигнатура генерируется путем взятия тела запроса, добавления к нему «sha1 =» в начале и последующего хеширования его с помощью SHA1 с использованием секрета приложения. Как только мы доберемся до этого места, мы узнаем, что запрос поступил от Facebook, поэтому мы можем приступить к его обработке.

Обработка обновления

Обновление говорит нам, что изменилось, но не то, что изменилось. Ответственность за запрос нового API лежит на разработчике. Вот пример уведомления о том, что у пользователя, идентифицированного 123456789, есть изменения в друзьях:

 { "object": "user", "entry": [ { "uid": "123456789", "id": "123456789", "time": 1374846331, "changed_fields": [ "friends" ] } ] } 

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

 $updates = json_decode($body, true); if ($updates['object'] == 'user') { foreach ($updates['entry'] as $entry) { $uid = $entry['uid']; foreach ($entry['changed_fields'] as $field) { if ($field == 'friends') { ... 

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

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

 $user = $db->users('fb_id = ?', $uid)->fetch(); if ($user) { $data = unserialize($user['friends']); 

Несериализованные данные друзей выглядят примерно так:

  массив (123) {
   [0] =>
   массив (2) {
     [ "Имя"] =>
     Строка (11) "Мик Джаггер"
     [ "ID"] =>
     строка (7) "123456789"
   }
   [1] =>
   массив (2) {
     [ "Имя"] =>
     Строка (10) "Кит Ричардс"
     [ "ID"] =>
     строка (8) "987654321"
   }
 ... 

Все, что нам нужно, — это массив идентификаторов, так что вот где Underscore входит, в частности, функция pluck() :

 $friendIDs = __::pluck($data, 'id'); 

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

 $facebook->setAccessToken($user['fb_access_token']); $response = $facebook->api('/me/friends'); $friendsData = $response['data']; $newFriendIDs = __::pluck($friendsData, 'id'); $removedIDs = array_diff($friendIDs, $newFriendIDs); if (count($removedIDs)) { $html = '<p>The following people have un-friended you:</p>'; $html .= '<ul>'; foreach ($removedIDs as $id) { $friend = $facebook->api($id); $html .= '<ul>' . $friend['name'] . '</li>'; } $html .= '</ul>'; $mail = $c['phpmailer']; $mail->AddAddress($user['email'], $user['name']); $mail->Subject = 'Someone has un-friended you on Facebook!'; $mail->Body = $html; $mail->Send(); } 

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

 $user->update(array( 'friends' => serialize($friendsData), )); 

Резюме

В этой статье мы рассмотрели API обновлений и подписок в реальном времени Facebook и увидели, насколько они полезны для поддержания актуальности локально хранимых данных, позволяя нам получать обновленные данные в случае изменений. Мы продемонстрировали, как их можно использовать, создав пример приложения, которое отслеживает список друзей пользователя и сообщает ему, когда произошли изменения. Обязательно сообщите мне в комментариях, если у вас есть другие идеи о том, как они могут быть полезны!

Изображение через Fotolia