Возникла необходимость сделать интерфейс к гуглоридеру, отличный от стандартного. Использование стандартного 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-кода по своему усмотрению и поделиться с другими, вы бы ввели гугло-пароль и гугло-логин на стороннем сайте, предоставляющем данную функциональность, предоставляющем свои исходники, обещающем нигде не хранить пароли-логины (только куки и юзериды)?