Статьи

Создание мобильного веб-приложения с помощью Google Reader API

Из этого туториала вы узнаете, как создать мобильное веб-приложение для чтения новостных лент с помощью Google Reader API. Это приложение будет «сфокусировано на особенностях», но основы, которую вы здесь изучите, должно быть достаточно, чтобы вы могли расширить свои собственные приложения. На момент написания этой статьи не было официального API Google Reader, поэтому в этом руководстве будет использоваться неофициальный API, обычно используемый при создании приложений Google Reader.


Google использует немного другой язык для некоторых внутренних частей API Google Reader. Некоторые из них связаны с их платформой, а некоторые, по-видимому, из-за дрейфа, так как приложение во внешнем интерфейсе стало более зрелым. Одна такая концепция называется «поток». Поток представляет контент, который нарезается кубиками или фильтруется по-разному. Все статьи из определенного канала являются частью потока, как и все статьи из папки.

Ниже приведена краткая таблица, показывающая различия в общепринятом клиентском жаргоне и соответствующей терминологии бэкэнда API Google Reader:

Сторона клиента API
Папка / Тег Tag / Label
Подписка Подписка
Люди, за которыми вы следуете друг
Помеченные Помеченные
Общий Broadcast

Если вы вошли в свою учетную запись Google Reader, попробуйте ввести в браузере следующие конечные точки:

Информация о пользователе:
https://www.google.com/reader/api/0/user-info?output=json

Список подписок:
https://www.google.com/reader/api/0/subscription/list?output=json

Список папок / тегов:
https://www.google.com/reader/api/0/tag/list?output=json

Список людей, на которых вы подписаны:
https://www.google.com/reader/api/0/friend/list?output=json

Список настроек Google Reader:
https://www.google.com/reader/api/0/preference/stream/list?output=json

Вышеуказанные конечные точки доступны только для чтения и будут использоваться в этом руководстве.


Для доступа к Google Reader мы создадим собственный класс PHP. Ниже приведен список функций, которые понадобятся этому классу (я подробнее расскажу о них позже):

  • login — получает код login Google и токен редактирования читателя.
  • get_subscriptions — возвращает список подписанных каналов.
  • get_tags — возвращает список папок / тегов.
  • get_friends — возвращает список людей, на которых вы подписаны.
  • get_stream_items — возвращает статью из потока.
  • set_article_read — устанавливает статью как прочитанную.
  • set_article_starredset_article_starred статью.
  • set_article_broadcast — добавляет статью в ваш общий список.
  • set_article_review — применяет тег «рецензия» к статье.
  • get_url — HTTP-запрос GET с аутентификацией Google.
  • get_anon_url — HTTP-запрос GET без аутентификации Google.
  • post_url — HTTP-запрос POST с аутентификацией Google.
  • post_anon_url — HTTP-запрос POST без аутентификации Google.

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

Как вы можете догадаться, следующие свойства будут установлены для идентификатора электронной почты и пароля вашей учетной записи Google Reader:

1
2
public $grEmail = »;
public $grPasswd = »;

Следующие свойства используются в функциях класса Curl (ознакомьтесь с исходным кодом для них):

1
2
3
public $userAgent = ‘tuts+rss+bot’;
public $proxy = 0;
public $proxyUrl = »;

Я включил поддержку прокси, потому что моему провайдеру (Godaddy) требуется исходящий трафик для прохождения через прокси. Если вам не нужна поддержка прокси, оставьте $ proxy равным 0 в gr-config.php.

Далее, у нас есть много начальных конечных точек для доступных API:

1
2
3
4
5
6
7
8
9
protected $_urlBase = ‘https://www.google.com’;
protected $_urlApi = ‘https://www.google.com/reader/api/0’;
protected $_urlAuth = ‘https://www.google.com/accounts/ClientLogin’;
protected $_urlToken = ‘https://www.google.com/reader/api/0/token’;
protected $_urlUserInfo = ‘https://www.google.com/reader/api/0/user-info’;
protected $_urlTag = ‘https://www.google.com/reader/api/0/tag’;
protected $_urlSubscription = ‘https://www.google.com/reader/api/0/subscription’;
protected $_urlStream = ‘https://www.google.com/reader/api/0/stream’;
protected $_urlFriend = ‘https://www.google.com/reader/api/0/friend’;

Следующие три свойства хранят информацию, собранную в процессе аутентификации:

1
2
3
public $userInfo = »;
protected $auth = »;
protected $token = »;

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


Следующие функции сделают все возможное для нашего приложения.

Нам нужно нажать API аутентификации Google, чтобы начать:

1
2
$data = ‘&Email=’.$this->grEmail.’&Passwd=’.$this->grPasswd.’&service=reader&source=’.$this->userAgent.’&continue=http://www.google.com’;
$result = $this->post_anon_url($this->_urlAuth,$data);

После запроса API аутентификации первое, что нужно собрать, это строка $ auth. Эта строка снабжает слой аутентификации Google нашими учетными данными. Получение $auth полезно практически для любого сервиса Google.

1
2
preg_match(‘/Auth=(\S*)/’, $result, $match);
$this->auth = $match[1];

Далее идет токен $, и нам нужно найти другую конечную точку для него. Токен специфичен для Google Reader и необходим для запросов, которые пишут или изменяют состояние.

1
$this->token = $this->get_url($this->_urlToken);

Третий — это пользовательский информационный объект, нам понадобится часть userId. Для некоторых параметров API требуется ваш идентификатор пользователя Google, который представляет собой большой номер, представляющий вашу учетную запись, а не идентификатор электронной почты, который вы используете для входа в систему. Информация о пользователе также содержит большое число, используемое для доступа к вашей общей ленте.

1
$this->userInfo = json_decode($this->get_url($this->_urlUserInfo));

Это простые функции для получения списка ваших каналов различными способами.

get_subscriptions возвращает список всех каналов, на которые вы подписаны:

1
2
3
4
function get_subscriptions() {
    $result = $this->get_url($this->_urlSubscription.’/list?output=json’);
    return json_decode($result);
}

get_tags возвращает список всех папок и тегов:

1
2
3
4
function get_tags() {
    $result = $this->get_url($this->_urlTag.’/list?output=json’);
    return json_decode($result);
}

get_friends возвращает список людей, на которых вы подписаны:

1
2
3
4
function get_friends() {
    $result = $this->get_url($this->_urlFriend.’/list?output=json’);
    return json_decode($result);
}

Это мясо класса и к чему все остальное приводит. С помощью этой функции мы возвращаем отдельные статьи на основе указанных критериев. Поток $stream берется из результатов функций списка, и это может быть лента друга, целой папки или определенного канала. $n — количество статей, возвращаемых за один раз. Для приложения я ограничил его всего 20, но если вы используете его для чего-то другого, кроме дисплея в реальном времени, вы можете увеличить его до 1000. После 1000 вам нужно использовать опцию продолжения, которая выходит за рамки этого статья.

get_stream_items принимает несколько параметров, и по умолчанию вам нужно только предоставить поток:

1
2
3
4
5
6
function get_stream_items(
    $stream = »,
    $xt_a = array(‘user/-/state/com.google/read’),
    $daysago = 3,
    $n = 20,
    $magic = True) {

$ot используется, чтобы сообщить Google Reader, что на этот раз вы не хотите возвращать статьи старше их. Google Reader уходит в эпоху, но мне нравится использовать $daysago потому что проще переходить по дням, чем по секундам. Исходя из этого, мы можем рассчитать $ot из текущего времени. $ck важен, но я никогда не получал хорошего объяснения по этому поводу, и без официального API Google Reader это может остаться загадкой. Я установил его на time () * 1000, и он просто работает, тогда как time () * 100 — нет.

1
2
$ot = time() — ($daysago * 86400);
$ck = time() * 1000;

$magic — это логическое значение, при котором статьи возвращаются с помощью рейтинга «magic» в Google Reader или, если он равен false, первыми. Вы также можете сначала отсортировать по возрасту, установив $ r = ‘o’, если хотите.

1
if ($magic == True) { $r = ‘a’;

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

1
2
$xt = »;
foreach($xt_a as $key=>$value) { $xt .= ‘&xt=’.$value;

Мы передаем окончательный URL в Google Reader и возвращаем результаты после установки параметров:

1
2
3
4
    $url = $this->_urlStream.’/contents/’.$stream.’?ot=’.$ot.’&r=’.$r.$xt.’&n=’.$n.’&ck=’.$ck.’&client=’.$this->userAgent;
    $result = $this->get_url($url);
    return json_decode($result);
}

Три из этих функций используются для обратной записи в Google Reader и пометки статей с определенным состоянием. Последний используется для установки тега. Вы можете легко сжать все четыре до одной функции и передать «=» в качестве параметра функции. Эта настройка — только мое предпочтение, я предпочитаю так, чтобы код, использующий класс, был чище и легче для чтения. Если вы планируете создать свою собственную функцию, вы можете отправить несколько «=» одновременно. Кроме того, вы можете передать несколько опций «i =» и «s =», вам просто нужно передать одинаковое количество и в правильном порядке. Я обычно пропускаю 10 за раз, когда отмечаю много статей как прочитанные.

Установите статью в состояние «чтение»:

1
2
3
4
5
function set_article_read($id,$stream) {
    $url = $this->_urlApi .
    $data = ‘a=user/-/state/com.google/read&async=true&s=’.$stream.’&i=’.$id.’&T=’.$this->token;
    return $this->post_url($url,$data);
}

Начать статью:

1
2
3
4
5
function set_article_starred($id,$stream) {
    $url = $this->_urlApi .
    $data = ‘a=user/-/state/com.google/starred&async=true&s=’.$stream.’&i=’.$id.’&T=’.$this->token;
    return $this->post_url($url,$data);
}

Поделитесь этой статьей с теми, кто подписан на вас:

1
2
3
4
5
function set_article_broadcast($id,$stream) {
    $url = $this->_urlApi .
    $data = ‘a=user/-/state/com.google/broadcast&async=true&s=’.$stream.’&i=’.$id.’&T=’.$this->token;
    return $this->post_url($url,$data);
}

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

1
2
3
4
5
function set_article_review($id,$stream) {
    $url = $this->_urlApi .
    $data = ‘a=user/’.$this->userInfo->userId.’/label/Review&async=true&s=’.$stream.’&i=’.$id.’&T=’.$this->token;
    return $this->post_url($url,$data);
}

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


Интерфейс приложения построен на основе превосходного шаблона HTML5 по адресу http://html5boilerplate.com/ . Я взял index.php из шаблона и разделил его на файлы заголовка и нижнего колонтитула. Главная страница — это index.php, которая принимает значение строки запроса &list обозначающее, какой список отображать. По умолчанию это список папок. Любой элемент в списке попадет в файл article.php с пропущенными &stream и &list . Класс помещается в файл gr.php, где gr-config.php фактически является файлом, который включает его в другие. Это может быть очевидно, но в gr-config.php мы также добавим параметры конфигурации для класса. Последний файл php — это set_article.php. Он будет вызываться с помощью AJAX-запроса из article.php и отвечает за вызов функций set.

Я включил два тега ниже во включаемый файл header.php, но они закомментированы:

1
2
<link rel=»apple-touch-startup-image» href=»splashimage.png»>
<meta name=»apple-mobile-web-app-capable» content=»yes» />

Они делают веб-приложение более похожим на обычное приложение, когда вы добавляете сайт на домашний экран iOS. apple-touch-startup-image предоставляет изображение для отображения во время загрузки главной страницы. apple-mobile-web-app-capable заставляет сайт работать на своем собственном экземпляре Safari, а не в общем экземпляре Safari. Единственная проблема заключается в том, что с параметром apple-mobile-web-app-capable установленным на yes, при нажатии ссылки на другой домен открывается ссылка в основном экземпляре Safari, что заставляет вас выполнять многозадачность обратно в приложение для чтения.


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

Вот настройки в начале страниц index.php, article.php и set_article.php:

1
2
3
4
session_set_cookie_params(300);
session_start();
include_once(‘gr-config.php’);
include_once(‘header.php’);

Меню представляет собой простой список HTML, который можно использовать для получения различных списков подписки:

1
2
3
4
5
6
7
<div id=»menu»>
    <ul>
        <li class=»tags» onclick=»window.location=’index.php?list=tags'»></li>
        <li class=»subs» onclick=»window.location=’index.php?list=subs'»></li>
        <li class=»friends» onclick=»window.location=’index.php?list=friends'»></li>
    </ul>
</div>

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

Теперь перейдем к первому реальному использованию класса. По сути, мы собираемся проверить, установлен ли &list и, если нет, по умолчанию для просмотра тегов. Тогда это просто оператор switch, чтобы узнать, какой список создать и использовать функцию gr.

Во-первых, мы используем $list для тегов, если ничего не передано, а затем передаем $list переключателю. Когда $list соответствует subs, мы просто выводим довольно прямой список всех возвращенных подписок. Это может быть много для активных пользователей, поэтому режим тегов используется по умолчанию.

1
2
3
4
5
6
7
8
if (isset($_GET[‘list’])) { $list = $_GET[‘list’];
switch ($list) {
   case ‘subs’:
      $subs = $gr->get_subscriptions();
      foreach($subs->subscriptions as $sub) {
         echo ‘<li onclick=»window.location = \’articles.php?list=’,$list,’&stream=’,$sub->id,’\'»>  ‘,$sub->title,'<div class=»right-arrow»></div></li>’;
      }
      break;

Данные возвращаются с полной строкой метки, а не только с именем папки. Установка $ remove позволит нам избавиться от ненужных частей строки метки и просто показать имя папки. К сожалению, нет никакого способа исключить Review из возвращенных меток, поэтому мне пришлось работать с ним с помощью PHP. Я также проверяю, чтобы мы отображали только метки:

01
02
03
04
05
06
07
08
09
10
11
12
case ‘tags’:
    $tags = $gr->get_tags();
    $remove = ‘user/’.$gr->userInfo->userId.’/label/’;
 
    foreach($tags->tags as $tag) {
        if (strpos($tag->id,’/label/’) > 0 && strpos($tag->id,’/Review’) == 0) {
            $title = str_replace($remove,»,$tag->id);
            echo ‘<li onclick=»window.location = \’articles.php?list=’,$list,’&stream=’,$tag->id,’\'»>  ‘,$title,'<div class=»right-arrow»></div></li>’;
        }
    }
     
    break;

URL друзей содержит много дополнительной информации из профиля пользователя. Одной хорошей вещью, которую мы можем использовать, является изображение профиля и включение его в качестве 32-пиксельного изображения:

1
2
3
4
5
6
7
8
9
case ‘friends’:
    $friends = $gr->get_friends();
    foreach($friends->friends as $friend) {
        if ($friend->contactId != -1) {
            echo ‘<li onclick=»window.location = \’articles.php?list=’,$list,’&stream=’,$friend->stream,’\'»><img class=»friends_icon» src=»http://s2.googleusercontent.com’.$friend->photoUrl.’?sz=32″>’,$friend->displayName,'<div class=»right-arrow»></div></li>’;
        }
    }
    break;
}

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

То же, что и на главной странице.

Еще один простой список HTML. Я не фанат использования window.history для кнопки «назад», поэтому для возврата я отправляю их обратно в список, который они просматривали.

01
02
03
04
05
06
07
08
09
10
<div id=»menu»>
   <ul>
      <li class=»back» onclick=»window.location = ‘/index.php?list=<?php echo $_GET[‘list’] ?>'»></li>
      <li> </li>
      <li class=»read» onclick=»set_article(‘read’)»></li>
      <li class=»starred» onclick=»set_article(‘starred’)»></li>
      <li class=»broadcast» onclick=»set_article(‘broadcast’)»></li>
      <li class=»review» onclick=»set_article(‘review’)»></li>
   </ul>
</div>

Функция get_stream_items из нашего класса является основным драйвером для страницы, и большинство значений по умолчанию хороши для этого. По умолчанию он исключает элементы, помеченные как прочитанные, но я расширил массив $xt_a чтобы также исключить статьи, помеченные меткой / тегом «review». Таким образом, элементы, которые мы помечаем для проверки, никогда не отображаются в этом списке, даже если мы их еще не читали. Мы собираемся загрузить статьи в скрытые теги div и показать только :first-child , а затем манипулировать остальными с помощью JavaScript.

1
2
3
4
$stream = $_GET[‘stream’];
$articles = $gr->get_stream_items($stream,array(‘user/-/state/com.google/read’,’user/’.$gr->userInfo->userId.’/label/Review’));
if (count($articles->items) > 0) {
   foreach ($articles->items as $article) {

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

1
2
echo ‘<div class=»article» id=»‘,$article->id,'» stream=’,$article->origin->streamId,’>’;
echo ‘<div class=»article-title»><a target=»_blank» href=»‘,$article->alternate[0]->href,'»>’,$article->title,'</a></div>’;

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

1
2
3
4
5
if (isset($article->summary->content)) {
    $content = str_replace(‘href=’,’target=»_blank» href=’,$article->summary->content);
} else {
    $content = str_replace(‘href=’,’target=»_blank» href=’,$article->content->content);
}

Наконец, покажите, что у нас есть:

1
2
3
    echo ‘<div class=»article-summary»>’,$content,'</div>’;
    echo ‘</div>’;
}

JavaScript сильно зависит от jQuery. Меню set_article() вызывает set_article() при нажатии на действие и вызывает set_article.php для обработки этого действия. set_article() сначала будет set_article() идентификатор и поток из статьи, которую мы просматриваем тегом div . Затем он получит общее количество, чтобы запустить перезагрузку, если мы достигнем конца доступных статей. Сценарий set_article.php вернет 1, если выполненное действие должно перейти к следующей статье. В противном случае он проверяет выполненное действие и подсвечивает значок «Звезда» или значок «Поделиться». Он использует $.eq() и переменную slice для перечисления возвращаемых статей.

Установка total переменной динамически, а не просто с помощью ’20’:

1
2
var total = $(‘.article’).size();
function set_article(action){

Добавление атрибутов в тег div обертки статьи:

1
2
id = $(‘.article’).eq(slice).attr(‘id’);
stream = $(‘.article’).eq(slice).attr(‘stream’);

Здесь мы передаем id и поток на страницу set_article.php и возвращаем 1 или 0. 1 означает, что действие требует удаления статьи из представления, а 0 означает, что это просто изменение состояния.

01
02
03
04
05
06
07
08
09
10
11
$.get(‘/set_article.php?action=’+action+’&id=’+id+’&stream=’+stream,function(data){
if (data==»1″) {
    $(‘.article’).eq(slice).hide();
    slice += 1;
    $(‘.article’).eq(slice).show();
 
    $(‘.starred’).css({backgroundPosition: ‘-62px -31px’});
    $(‘.broadcast’).css({backgroundPosition: ‘-93px -31px’});
}
else
{

Если он возвращает 0, то нам нужно посмотреть, какое действие было предпринято, поэтому правильное состояние значка также переключается:

01
02
03
04
05
06
07
08
09
10
11
12
13
         if (action == ‘starred’) {
             $(‘.starred’).css({backgroundPosition: ‘-62px 0px’});
         } else {
             $(‘.broadcast’).css({backgroundPosition: ‘-93px 0px’});
         }
     }
         
     if (slice >= total) {
        window.location.reload(true);
     }
          
  });
}

Это последний кусочек PHP, который позволит нам манипулировать статьей.

То же, что и на главной странице.

В классе проделана тяжелая работа, поэтому эта страница на самом деле является просто переключателем для запуска функции класса, соответствующей действию. Вы можете удивиться, почему Google требует переменную $stream . Из того, что я могу сказать, все это связано с тем, как Google хранит все данные считывателя и сокращает время транзакции при обработке этих функций редактирования.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (isset($_GET[‘action’])) {
   $action = $_GET[‘action’];
   $id = $_GET[‘id’];
   $stream = $_GET[‘stream’];
 
   switch ($action) {
      case ‘read’:
         $r = $gr->set_article_read($id,$stream);
         echo ‘1’;
         break;
      case ‘starred’:
         $r = $gr->set_article_starred($id,$stream);
         echo ‘0’;
         break;
      case ‘broadcast’:
         $r = $gr->set_article_broadcast($id,$stream);
         echo ‘0’;
         break;
      case ‘review’:
         $r = $gr->set_article_review($id,$stream);
         echo ‘1’;
         break;
   }
}

Большая часть CSS не является чем-то особенным. У меня есть два ключевых стиля, включенных ниже:

1
2
3
4
5
6
.article-summary img, .article-summary embed {
   max-height:50%;
   max-width:50%;
}
 
.article:first-child { display:block;

Объявления max-height:50% и max-width:50% очень полезны при перепрофилировании HTML, который может содержать изображения и встраивания. Это защищает изображения и YouTube от перегрузки экрана мобильного телефона.


Пока не будет официального API, эти места отлично подходят для получения дополнительной информации о создании приложений с помощью Google Reader: