November 4, 2009 6:53 PM
Google Reader does not have an official API. However, Nial Kennedy and the project pyrfeed have excellent documentation on it. I had developed a Google Reader client in python that the web version of Readefine uses1. I needed to leverage the same AS3 code base and write an AS3 Google Reader client on top of it for the desktop version. I managed to do it in a day while ensuring a common code-base for the AIR and Flex versions thanks to conditional compilation. This article explains Google Reader's API along with the AS3 implementation and how it merged in with the web-version.

Interestingly, there is AS3 code for Google Reader access in snackr's code. I wrote my own though because I needed it to seamlessly merge with my existing Flex code. Regardless, this is the screen-shot of how Readefine Desktop looks with Google Reader access and all chrome minimized:

readefined

For more screen-shots, visit Readefine Desktop.

Google Reader API

Google Reader is excellently architected. Its view in HTML and Javascript is independent of the actual methods to get and update Google Reader2.

There are three main parts to the API: authentication, getting data and updating data.

Kindly read the articles referenced in the first paragraph of the article before proceeding. The information regarding the API here augments that information.

Authentication

ClientLogin is what you have to use and it is well documented by Google. You have to do an HTTP POST to a URL while setting the service as reader and passing the username and password.

Ideally, an OAuth or AuthSub implementation would help third-party applications to leverage Google Reader without having users enter their precious skynet password into their application. Let us hope Google releases that in the near future.

Getting Data

The flow for a Google Reader Client is as follows (after auth):

1) Get user info by asking /api/0/user-info.

2) Get subscription list. Each subscription contains a "category" if the user had created folders. It also has a field called firstitemmsec that denotes in milliseconds the time from which entries for that feed should be picked up.

firstitemmsec initially stumped me until I added a new subscription. I noticed that Google Reader has entries for a feed spanning back to a month (probably -infinity). So the reader has to know to show you articles only from the time you subscribed to a feed.

ot is the parameter that takes firstitemmsec / 1000 when you are fetching the reading list or a particular feed.

3) Get unread counts. The total will be with the feed id user/userid_obtained_in_step1/state/com.google/reading-list. This has an upper bound of 1000, so 1000 is always 1000+.

4) Get the reading list or a particular feed while being careful to pass in the correct ot. Each article in the feed will have special fields if it is read, starred, kept-unread, etc.

5) To get the next set of articles, pass in a "cont" GET parameter to the call in 4.

Every now and then, you have to fetch unread counts because it changes often depending upon the amount of feeds you are subscribed to.

Updating Data

Marking as read, star, like, share, etc. all fall into this category. This is slightly more tricky than getting data because it involves a token.

This is what I understood:

1) Get a token by posting to /api/0/token. Save the current timestamp.

2) Every API call that requires a token must be queued so that there is only one outstanding call at a time.

3) Before making an update API call, check your saved timestamp to see if it has been more than 20 minutes3. If it has, get a new token, then continue with the API call.

Google Reader Client in AIR

I have a ServiceLocator singleton class with methods like greaderGet(), greaderStar(), etc. In that method, depending upon a compilation variable, I either call the AIR code or my python server side code.

public function googleReaderUnreadCount():void
{
    CONFIG::FLEX
    {
        var s:HTTPService = getGReaderService();;
        var token:AsyncToken = s.send({a: 'c'});
        
        token.addResponder(new GReaderCommand(GReaderCommand.GREADER_UNREAD));
    }
    CONFIG::AIR
    {
        greaderClient.getUnreadCount();
    }
}

Since I'm using an AsyncToken, I have a GReaderCommand, a class that implements an IResponder. The command's result() method expects data to be an Object that you normally get when resultFormat is "object" in an HTTPService.

GReaderClient is the AIR class that talks to Google Reader using a URLLoader. It takes in the parameters object that was passed to HTTPService's send(). Each method knows its GReaderCommand type. It then creates a map of requests to loader (for getting the parameters) and a map of urlloaders to command.

After performing any API call to Google Reader, there is a single common result handler that marshall's the response based on how GReaderCommand expects it.

private function handleReaderResultEvent(event: Event): void 
{						
    var urlloader:URLLoader = event.target as URLLoader;
    var req:URLRequest = clearFromQueue(urlloader);
    var result:String = String(urlloader.data);
    urlloader.removeEventListener(Event.COMPLETE, handleReaderResultEvent);
    urlloader.removeEventListener(IOErrorEvent.IO_ERROR, handleReaderFaultEvent);
    if ( !req )
        return;
    var gCommand:GReaderCommand = clearCommandFromQueue(req);
    if ( gCommand )
    {
        marshallReaderResponse(gCommand, result);
    }
}

The marshallReaderResponse creates objects that GReaderCommand's result() method expects which may be object or e4x. In the case of object when the response from Reader is XML, I use an XMLDocument along with a SimpleXMLDecoder. If the response from Reader is in JSON, I parse it using JSONDecoder and build the appropriate object.

Creating an object from XML:

xmlDecoder = new SimpleXMLDecoder(true);
try
{
    xmlDoc = new XMLDocument(result);
}
catch(err:Error)
{
    xmlDoc = null;
}
if ( !xmlDoc )
    obj = xmlDecoder.decodeXML(xmlDoc);

The marshall method finally calls the command's result() method with the data.

Here is the full source for my GReaderClient.as. It is incomplete without GReaderCommand, but this you should serve you as a good starting point in developing an Adobe AIR Google Reader client.

For more information on Readefine Desktop, visit readefine.anirudhsasikumar.net.

CategoryAIR Comment(s)


[1] Since cross-domain requests are not allowed from flash player unless explicitly allowed.
[2] Except for quickadd which seems to return HTML and JSON data in the end in a script block.
[3] Time interval obtained after careless observation. Might be incorrect.

Copyright © 2004-2011 Anirudh Sasikumar. All rights reserved.
Last Updated: November 18, 2011 3:04 PM