Пишем сервис уведомления о новых твитах для SmartWatch

Добрый день, сегодня я расскажу как написать простую программу для уведомления о новых твитах для SmartWatch. Статья будет из двух частей — клиентской части (расширения LiveWare) и серверной части — скрипт на php.
Попутно я также расскажу как пользоваться Google Cloud Messaging (GCM) для эффективной отправки push-уведомлений на Android-смартфон.

Проблема расширения от Sony для уведомления о твитах в том, что оно проверяет их каждые 15 минут. Если вы активный пользователь Twitter то для вас это маловато. Мы будем проверять обновления каждую минуту (таковы максимальные ограничения Twitter API.

Итак, начнем с Twitter API.
Для удобного использование в PHP, скачаем готовый frontend для API, из всех, мне показался наиболее простым (подбираем инструменты под задачи) данный проект.

Может быть, конечно, я php-шник старой школы, но вообще, если бы я делал данный скрипт в году 2005, то я не особо бы мудрствовал, со всякими API и просто CURL’ом, подсовывая куки или логин/пароль забирал бы страницу https://twitter.com/i/connect и с помощью preg_match парсил бы ее содержание, вытаскивая последние ответы. Но так как мы в 2013, и сейчас модно preg_match, обернутый в метод, либо json_decode обзывать громким словом API, то будем по-мейнстриму пользоваться им. Который, кстати, как указывает документация ограничивает нас в частоте запросов, а именно не позволяет делать более 15 запросов в 15 минут. Если бы парсили страницу, то никакие ограничения нам не были страшны, но будем хорошими и покорными.

Итак, frontend скачали, но для пользования Twitter API, надо получить у твитера ключи. Для этого идем на https://dev.twitter.com/ и логинимся. Потом переходим к созданию приложения, тут пишем понравившейся вам название, описание и сайт, к

newappp

Но имя придется ввести уникальное. После успешного создания приложения, вы попадете на страницу Details. В самом низу страницы необходимо нажать кнопку Create access token. Теперь на страницу OAuth Tool появятся необходимые для доступа ключи.

Воспользовавшись примером, который идет с twitter-api-php напишем следующий скрипт, для получения последнего отклика:

//подключаем frontend
require_once('TwitterAPIExchange.php');
//ключи, вставьте здесь свои
$settings = array(
    'oauth_access_token' => "",
    'oauth_access_token_secret' => "4",
    'consumer_key' => "",
    'consumer_secret' => ""
);
//URL который запрашиваем, список смотрите в документации к Twitter API
$url = 'https://api.twitter.com/1.1/statuses/mentions_timeline.json';
//параметры запроса
$getfield = '?count=1';
//типа запроса, в нашем случае мы просто получаем данные, так что GET
$requestMethod = 'GET';
//создаем объект
$twitter = new TwitterAPIExchange($settings);
//вызываем метод performRequest, сразу декодируя json-ответ, помещая его в именованый массив $responce
$response = json_decode($twitter->setGetfield($getfield)
		    ->buildOauth($url, $requestMethod)
		    ->performRequest()
		    , true);
//в случае если превышен лимит запросов - завершаем работу скрипта		    
if (isset($response["errors"][0]["code"])) 
  die("Rate limit exceeded");
//если хотите увидеть весь список полученных переменных в ответе, раскоментируйте слудующую строку
//var_dump($responce);
//нам нужен только один твит		    
$twi_response = $response[0];

Если все пройдет удачно, то в переменной $twi_response будет лежать последний твит-ответ. Для интереса раскоментируйте строку var_dump($responce); и удивитесь, сколько мусора приходит вместе с собственно твитом.

ОК, твит мы получили, осталось передать его на телефон. Для этого воспользуемся сервисом Google — Google Cloud Messaging (GCM), который позволяет отправлять push-сообщения в приложение на Android-смартфоне. Максимальная длина — 4096 байт, чего нам более чем достаточно. Как обычно, для пользования GCM, надо зарегистрироваться на сайте разработчика. Предварительно можно было бы порекомендовать почитать документацию, но она почему-то описывает старый интерфейс консоли, а примеры у меня не заработали, так что толку для начинающего разработчика от нее никакого.

Идем в консоль, если нет проекта, то либо создайте, либо пройдите по этой ссылке. Далее открываете раздел APIs, и включайте Google Cloud Messaging for Android, далее заходите в раздел Registered Apps и жмите Register App, выбрав тип Web App, подтверждаете создание. После создания, откройте приложения и в разделе Browser Key будет необходимый ключ.
А теперь — функция для отправки сообщения в GCM:

function sendNotification( $apiKey, $registrationIdsArray, $messageData )
{   
    $headers = array("Content-Type:" . "application/json", "Authorization:" . "key=" . $apiKey);
    $data = array(
        'data' => $messageData,
        'registration_ids' => $registrationIdsArray
    );

    $ch = curl_init();

    curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); 
    curl_setopt( $ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send" );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode($data) );

    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}

Для ее использования, где-то в коде надо добавить две необходимые переменные:

$registrationId = '';
$apiKey = '';

$apiKey мы берем со страницы Google Cloud Console, а вот $registrationId пока взять неоткуда, так как это уникальный идентификатор приложения для Android и он генерируется при регистрации приложения, а так как приложения у нас еще нет, то и идентификатор взять негде.

Итак, твит мы получили, функция для отправки сообщения в GCM готова, осталось скормить ей наш твит:

//посылаем твит в GCM
$gcm_response = json_decode(sendNotification( 
		$apiKey, 
		array($registrationId), 
		array('message' => $twi_response["text"], 'user' => $twi_response["user"]["screen_name"])), true);
//если интересен ответ, то раскоментируйте следующую строку
//var_dump(gcm_response);
//если запрос удачен - выводим Success, если нет - сообщение об ошибке.
  if ($gcm_response["success"])
    echo "Success";
  else
    echo "Fail: ".$gcm_response["results"][0]["error"];

Пока у нас нет верного registrationId, отправка всегда будет завершаться неудачей.

Итак, при вызове скрипта, вначале будет запрашиваться последний твит, а затем, в случае удачного запроса, пересылаться в GCM на телефон. Для того, чтобы один и тот же твит не приходил несколько раз, можно хранить его id в файле, а можно создать базу твитов и если его id в ней присутствовать — не отсылать. Я решил сделать так, с заделом на то что в будущем буду ей пользоваться:

//формируем SQL-запрос
$query = "select * from tweets where `id` = '".$twi_response["id_str"]."';" or die("Error " . mysqli_error($link));
//запрашиваем базу
$result = mysqli_query($link, $query);
//в случае если mysql вернула пустой результат, значит твита в базе еще нет, то есть он новый
if (mysqli_num_rows($result) == 0) {  
//шлем сообщение в GCM
  $gcm_response = json_decode(sendNotification( 
		$apiKey, 
		array($registrationId), 
		array('message' => $twi_response["text"], 'user' => $twi_response["user"]["screen_name"])), true);
//  var_dump(gcm_response);
  if ($gcm_response["success"]) {
//если сообщение успешно отослано - можно добавлять его к нам в базу, что мы и делаем
    mysqli_query($link, "insert into tweets_g1 (`id`, `user`, `text`) values ('".$twi_response["id_str"]."', '".$twi_response["user"]["screen_name"]."', '".$twi_response["text"]."');");
    echo "Success\n";
    }
  else
    echo "Fail: ".$gcm_response["results"][0]["error"];
  }
else {
  echo "Present\n";
  }

Вот теперь сообщения о новых твитах приходят один раз. Можно добавлять как ежеминутное задание в Cron.

Итоговый вид скрипта:

require_once('TwitterAPIExchange.php');
$link = mysqli_connect("HOST","USER","PASSWORD","DB") or die("Error " . mysqli_error($link));
mysqli_query($link, "set names utf8");

function sendNotification( $apiKey, $registrationIdsArray, $messageData )
{   
    $headers = array("Content-Type:" . "application/json", "Authorization:" . "key=" . $apiKey);
    $data = array(
        'data' => $messageData,
        'registration_ids' => $registrationIdsArray
    );

    $ch = curl_init();

    curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); 
    curl_setopt( $ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send" );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
    curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode($data) );

    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}

$registrationId = "";
$apiKey = "";
$settings = array(
    'oauth_access_token' => "",
    'oauth_access_token_secret' => "",
    'consumer_key' => "",
    'consumer_secret' => ""
);

$url = 'https://api.twitter.com/1.1/statuses/mentions_timeline.json';
$getfield = '?count=1';
$requestMethod = 'GET';
$twitter = new TwitterAPIExchange($settings);
$response = json_decode($twitter->setGetfield($getfield)
		    ->buildOauth($url, $requestMethod)
		    ->performRequest()
		    , true);

if (isset($response["errors"][0]["code"])) 
  die("Rate limit exceeded");

$twi_response = $response[0];                

$query = "select * from tweets where `id` = '".$twi_response["id_str"]."';" or die("Error in the consult.." . mysqli_error($link));
$result = mysqli_query($link, $query);
if (mysqli_num_rows($result) == 0) {  
  $gcm_response = json_decode(sendNotification( 
		$apiKey, 
		array($registrationId), 
		array('message' => $twi_response["text"], 'user' => $twi_response["user"]["screen_name"])), true);
  if ($gcm_response["success"]) {
    mysqli_query($link, "insert into tweets_g1 (`id`, `user`, `text`) values ('".$twi_response["id_str"]."', '".$twi_response["user"]["screen_name"]."', '".$twi_response["text"]."');");
    echo "Success";
    }
  else
    echo "Fail: ".$gcm_response["results"][0]["error"];
  }
else {
  echo "Present";
  }  

mysqli_close($link);

Что можно улучшить:

  • добавить бесконечный цикл с ожиданием, в случае если от Twitter API получен ответ о том что превышен лимит запросов;
  • создавать lock-файл в начале работы, чтобы не было ситуации когда одновременно работают два скрипта;
  • использовать возможность API Streams, и не мучить твитер запросами, а держать соединение открытым и пересылать сообщения по мере их получения. Но это уже не на PHP, ИМХО. И уж точно не за один вечер.

В следующий раз мы улучшим скрипт, и напишем программу для Android, которая будет пересылать твиты на SmartWatch.

0.00 avg. rating (0% score) - 0 votes
  • themylogin

    ЧУВАК, ЗАБЛЮРЬ ТОКЕНЫ И УБЕРИ ИХ ИЗ КОДА!

    • Александр Сергеев

      СПОКЭ, все под контролем, я для них специально новое приложение создавал, которым сам не пользуюсь. Гугловские не светил

      • themylogin

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

        • Александр Сергеев

          про Б я не догадался. убираю

        • Александр Сергеев

          хотелось бы еще каких-нить комментариев по поводу качества быдлокода

          • themylogin

            Да нормальный код, отформатирован только хуёво. Странно, что ты сразу работу со streaming API не сделал, там же всё то же самое абсолютно.

          • Александр Сергеев

            да все не могу разобраться нормально с отображением кода. попробую сегодня streaming api осилить