Как стать автором
Обновить

Исследуем Google Reader API

Время на прочтение6 мин
Количество просмотров2.3K
Возникла необходимость сделать интерфейс к гуглоридеру, отличный от стандартного. Использование стандартного ajax reader api не удовлетворило из-за коммерческих ограничений. API, описанное во множестве импортных блогов отказывалось нормально работать, поэтому на вооружение было взято расширение firebug всеми любимого браузера. В результате получилась небольшая подборка полезных для работы с ридером URLов, которыми хочу поделиться с общественностью.

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-кода по своему усмотрению и поделиться с другими, вы бы ввели гугло-пароль и гугло-логин на стороннем сайте, предоставляющем данную функциональность, предоставляющем свои исходники, обещающем нигде не хранить пароли-логины (только куки и юзериды)?
Теги:
Хабы:
Всего голосов 8: ↑8 и ↓0+8
Комментарии10

Публикации

Истории

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань