Возникла необходимость сделать интерфейс к гуглоридеру, отличный от стандартного. Использование стандартного ajax reader api не удовлетворило из-за коммерческих ограничений. API, описанное во множестве импортных блогов отказывалось нормально работать, поэтому на вооружение было взято расширение firebug всеми любимого браузера. В результате получилась небольшая подборка полезных для работы с ридером URLов, которыми хочу поделиться с общественностью.
disclaimer: эта подборка не претендует на полноту, а лишь подытоживает некоторый результат, который позволил решить поставленную задачу, а именно: получить список подписок, непрочитанные элементы по подписке, отметить элементы как прочитанные, отметить подписки как прочитанные. Вся остальная информация с легкостью может быть получена с использованием официального руководства.
Для реализации взаимодействия клиентского ajax-интерфейса с google.com/reader/ будем использовать посредника на php+curl.
Механизм аутентификации с использованием AccountLogin приводить не буду, ибо это и без того очень хорошо описано другими. Резюмирую, что после авторизации нам понадобится идентификатор пользователя и небольшая кука, содержащая SID и еще кое-какую инфу. Этой кукой мы будем представляться гуглу, а ид пользователя будем использовать в GET- и POST- запросах.
В примерах используется простенький php класс для работы с curl:
В код класса «намертво» вшита кука, это плохо, если класс используется где-то ещё, но поскольку в данном обзоре ничто иное не рассматривается, универсальностью можно легко пренебречь.
Этот гет-запрос возвратит следующий json-объект
Этот запрос удобно делать один раз при начальной инициализации пользовательского интерфейса.
cm_get('id','str') — идентификатор подписки, который пришел гет-запросом от пользователя. Функция cm_get предохраняет от инъекций, тут оно не нужно и можно писать просто $_GET['id'], но пусть будет (на всякий случай).
$userid — ид пользователя, полученный при логине
cm_get('ot','str') — это поле firstitemmsec из предыдущего гета, уменьшенное в 1000 раз, потому что приходит оно в миллисекундах, а требуется в секундах
Ответ гугла:
Ну тут в общем всё понятно. Непонятно только, почему иногда (для некоторых лент) элементы имеют немного другую структуру. Но тут я еще сам не до конца разобрался, поэтому разводить тут рассуждения не вижу смысла, но с удовольствием почитал бы в комментах мысли других хабралюдей об этом явлении.
И чуть не забыл. Время отдаваемое гуглом в атрибутах updated, published и других является utc-временем, поэтому не забываем отнимать timeZoneOffset на клиенте:
ну тут, я полагаю комментировать нечего, массив unreadcounts и всё тут.
Токен нужен для проведения операций изменяющих БД. Не спрашивайте меня зачем, я не знаю.
Для получения токена используйте
Таким образом, мы получаем базовую функциональность для работы с гугл-ридером.
Пишу только затем, чтобы понять, у одного меня такая проблема, или есть еще недовольные. В общем суть проблемы: они изменили обработчик кнопки j (следующая запись). Теперь вновь открытая запись «прилипает» к верху видимой области скроллируемого дива с фидами. Раньше было лучше. Собственно, это было последней каплей. Из объективных причин: нужна была функция отметки прочитанными только загруженных сообщений, сокрытия прочитанных, отметки прочитанными сразу нескольких лент, фильтрации неугодного контента, а также необходимость чтения в обход корпоративного прокси, когда кончается месячный трафик =)
Если бы вы имели возможность прямо в клиентском интерфейсе изменить какую-то часть js-кода по своему усмотрению и поделиться с другими, вы бы ввели гугло-пароль и гугло-логин на стороннем сайте, предоставляющем данную функциональность, предоставляющем свои исходники, обещающем нигде не хранить пароли-логины (только куки и юзериды)?
disclaimer: эта подборка не претендует на полноту, а лишь подытоживает некоторый результат, который позволил решить поставленную задачу, а именно: получить список подписок, непрочитанные элементы по подписке, отметить элементы как прочитанные, отметить подписки как прочитанные. Вся остальная информация с легкостью может быть получена с использованием официального руководства.
Для реализации взаимодействия клиентского ajax-интерфейса с google.com/reader/ будем использовать посредника на php+curl.
Представляемся гуглу
Механизм аутентификации с использованием AccountLogin приводить не буду, ибо это и без того очень хорошо описано другими. Резюмирую, что после авторизации нам понадобится идентификатор пользователя и небольшая кука, содержащая SID и еще кое-какую инфу. Этой кукой мы будем представляться гуглу, а ид пользователя будем использовать в GET- и POST- запросах.
Класс для работы с curl
В примерах используется простенький php класс для работы с curl:
class curl{ var $c; var $url; function curl($url){ $this->c = curl_init(); //curl_setopt($this->c,CURLOPT_PROXY,'192.168.x.x:8000'); // это прокси (если надо) //curl_setopt($this->c,CURLOPT_PROXYUSERPWD,'user:pass'); //curl_setopt($this->c,CURLOPT_PROXYUSERPWD,'user2:pass2'); curl_setopt($this->c,CURLOPT_COOKIE,'cookie here'); // представляемся гуглу curl_setopt($this->c,CURLOPT_URL,$url); $this->url = $url; } function post($data){ curl_setopt($this->c,CURLOPT_POST,1); curl_setopt($this->c,CURLOPT_POSTFIELDS,$data); } function go($return=0){ if($return){ curl_setopt($this->c,CURLOPT_RETURNTRANSFER,$return); $response = curl_exec($this->c); curl_close($this->c); return $response; }else{ curl_exec($this->c); curl_close($this->c); } } }
В код класса «намертво» вшита кука, это плохо, если класс используется где-то ещё, но поскольку в данном обзоре ничто иное не рассматривается, универсальностью можно легко пренебречь.
Список RSS-лент
$subscriptions = new curl('http://www.google.com/reader/api/0/subscription/list?output=json');
Этот гет-запрос возвратит следующий json-объект
{ "subscriptions": [ { "id": "feed/http://habrahabr.ru/rss/blog/i_am_clever/", "title": "\u042f \u0443\...", "sortid": "DB54FDDE", "categories": [ { "id": "user/-/label/\u0425\u0430\...", "label": "\u0425\u0430\..." },... ], "firstitemmsec": "1198498116053" },... ] }
- «id»: «feed/http://habrahabr.ru/rss/blog/i_am_clever/» — это значение будет использоваться далее для идентификации подписки
- «categories» — это массив меток данной подписки. Необходим для построения структуры папок и запросов лент, объединённых метками.
Этот запрос удобно делать один раз при начальной инициализации пользовательского интерфейса.
Элементы подписки
$items = new curl('http://www.google.com/reader/api/0/stream/contents/'. cm_get('id','str'). '?n=20&r=n&xt=user/'. $userid.'/state/com.google/read&ot='. cm_get('ot'). '&output=json&client=scroll&ck='.time());
cm_get('id','str') — идентификатор подписки, который пришел гет-запросом от пользователя. Функция cm_get предохраняет от инъекций, тут оно не нужно и можно писать просто $_GET['id'], но пусть будет (на всякий случай).
$userid — ид пользователя, полученный при логине
cm_get('ot','str') — это поле firstitemmsec из предыдущего гета, уменьшенное в 1000 раз, потому что приходит оно в миллисекундах, а требуется в секундах
Ответ гугла:
{ "id":"feed/http://habrahabr.ru/rss/blog/linux/", "title":"\u0425\u0430\...", "self":[ { "href":"длинный урл" } ], "alternate":[ {"href":"http://habrahabr.ru/rss/blogs/linux/", "type":"text/html"}], "updated":1224688061, "items":[ { "crawlTimeMsec":"1224688061884", "id":"tag:google.com,2005:reader/item/3dfa07f7c9a2dab0", "categories":[ "user/10093198974819760184/state/com.google/reading-list", "user/10093198974819760184/state/com.google/fresh", "linux","server","java","jboss" ], "title":"Linux для всех...", "published":1224685301, "updated":1224685301, "alternate":[{ "href":"http://habrahabr.ru/blogs/linux/42958/", "type":"text/html"}], "summary":{ "direction":"ltr", "content":"много букв" }, "author":"Kaaboeld", "annotations":[], "origin":{ "streamId":"feed/http://habrahabr.ru/rss/blog/linux/", "title":"\u0425\u0430...", "htmlUrl":"http://habrahabr.ru/rss/blogs/linux/" } },... ] }
Ну тут в общем всё понятно. Непонятно только, почему иногда (для некоторых лент) элементы имеют немного другую структуру. Но тут я еще сам не до конца разобрался, поэтому разводить тут рассуждения не вижу смысла, но с удовольствием почитал бы в комментах мысли других хабралюдей об этом явлении.
И чуть не забыл. Время отдаваемое гуглом в атрибутах updated, published и других является utc-временем, поэтому не забываем отнимать timeZoneOffset на клиенте:
// начало UNIX-времён var adate = new Date('1970/01/01'); // убираем минуты локального времени adate.addMinutes(-(new Date).getTimezoneOffset()); // добавляем секунды которые прислал гугл adate.addSeconds(Math.floor(x.items[i].crawlTimeMsec/1000));
Количество непрочитанных элементов
$x = new curl('http://www.google.com/reader/api/0/unread-count?all=true&output=json'); {unread: { "max": 1000, "unreadcounts": [ { "id": "feed/http://bash.org.ru/rss/", "count": 764, "newestItemTimestampUsec": "1223899958891011" },... ] } }
ну тут, я полагаю комментировать нечего, массив unreadcounts и всё тут.
Отмечаем ленту как прочитанную
$x = new curl('http://www.google.com/reader/api/0/mark-all-as-read?client=scroll'); $x->post('T='.$T. // токен '&s='.$feed. // ид фида //'&t=asd'.//$_POST['title'][$i]. // опциональный параметр '&ts='.time() // время );
Токен нужен для проведения операций изменяющих БД. Не спрашивайте меня зачем, я не знаю.
Для получения токена используйте
$x = new curl('http://www.google.com/reader/api/0/token?ck='.time().'&client=scroll');
Отмечаем элемент ленты как прочитаный
$x = new curl('http://www.google.com/reader/api/0/edit-tag?client=scroll'); $x->post('T='.cm_post('token','str'). '&a=user/'.$userid.'/state/com.google/read'. '&async=true'. '&i='.$items[$i]. // ид элемента '&s='.$feeds[$i]); // ид ленты
Таким образом, мы получаем базовую функциональность для работы с гугл-ридером.
Отступление. Зачем мне это понадобилось
Пишу только затем, чтобы понять, у одного меня такая проблема, или есть еще недовольные. В общем суть проблемы: они изменили обработчик кнопки j (следующая запись). Теперь вновь открытая запись «прилипает» к верху видимой области скроллируемого дива с фидами. Раньше было лучше. Собственно, это было последней каплей. Из объективных причин: нужна была функция отметки прочитанными только загруженных сообщений, сокрытия прочитанных, отметки прочитанными сразу нескольких лент, фильтрации неугодного контента, а также необходимость чтения в обход корпоративного прокси, когда кончается месячный трафик =)
И небольшой вопрос
Если бы вы имели возможность прямо в клиентском интерфейсе изменить какую-то часть js-кода по своему усмотрению и поделиться с другими, вы бы ввели гугло-пароль и гугло-логин на стороннем сайте, предоставляющем данную функциональность, предоставляющем свои исходники, обещающем нигде не хранить пароли-логины (только куки и юзериды)?