Posted in ColdFusion | Posted on 12-07-2007 | 5,540 views
Earlier this morning a user forwarded me a job request from someone looking for code to integrate with Google Docs. In the past I've really detested Google's APIs, but I figured I'd take a look and see how bad it is. Turns out it wasn't as bad as I thought. I wrote up a quick demo. This is not some new project. This was just written for fun and as a proof of concept.
The first thing you have to before calling the Google Docs API (any Google service I believe) is to authenticate. Now if you remember my complaints about the Calendar API - they had some funky login process that took 3 steps, including one that "may" occur. I love API's with "may" in them. The authentication API now is somewhat more simpler. Consider:
2 <cfhttpparam type="formfield" name="accountType" value="HOSTED_OR_GOOGLE">
3 <cfhttpparam type="formfield" name="Email" value="rcamden@gmail.com">
4 <cfhttpparam type="formfield" name="Passwd" value="whereiscfonadobedotcom">
5 <cfhttpparam type="formfield" name="service" value="writely">
6 <cfhttpparam type="formfield" name="source" value="camden-cfgoogledocs-0">
7</cfhttp>
This is a fairly typical form post. Some things to note: The accoutnType is supposed to be either Google or Hosted, but they also allow for HOSTED_OR_GOOGLE if you don't know which one you are using. Kudos to Google for doing something helpful. The second thing to note is that the names for Email and Passwd must match the case I've shown above. Why? I don't know. Case-sensitivity is the Lindsey Lohan of Comp Sci. The service is, obviously, the service you are connecting too. Writely is the service value for docs. The source value is simply a name you give for your automated program. I shoulda called my super-megatron-9000 or something, but camden-cfgoogledocs-0 worked as well.
If done correctly, this returns a string of values in this form:
LSID=Longfreakinglist
SID=Evenmorecrap
Auth=Stuff
I wrote up some quick code to treat the result like a list and store the results:
2<cfset authdata = structNew()>
3
4<cfloop index="line" list="#content#" delimiters="#chr(10)#">
5 <cfset dtype = listFirst(line, "=")>
6 <cfset value = listRest(line, "=")>
7 <cfset authdata[dtype] = value>
8</cfloop>
Now you have authentication information. This can be passed in with your quest:
2 <cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#">
3</cfhttp>
I've asked for a generic list of documents and I've passed the authorization header. This returns an Atom feed. Now as we know, CFFEED can parse this nicely. But CFFEED requires a URL or a real file. So I just treated it like a normal XML packet:
2<cfloop index="x" from="1" to ="#arrayLen(packet.feed.entry)#">
3 <cfset entry = packet.feed.entry[x]>
4 <cfset title = entry.title.xmltext>
5 <cfset updated = entry.updated.xmltext>
6 <cfset type = entry.category.xmlattributes.label>
7 <cfset sourceurl = entry.content.xmlattributes.src>
8 <cfoutput>[#type#] #title# (#updated#)<br></cfoutput>
9</cfloop>
What I've done here is loop over the entry results. I grabbed some of the values - not all - and I output them. The type variable will signify if the document is a written doc, spreadsheet, or presentation.
Oddly - there is no way to directly download or export a document. You do get access to the "content" URL in the result. Notice I store the above. If you want, you can get the contents like so:
2 <cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#">
3</cfhttp>
4<cfoutput>#result.filecontent#</cfoutput>
This returns a textual version of the document. When I say text I mean HTML as well. The result I got mirrored nicely in HTML what I had seen when writing the document.
Not too bad. Someone could wrap this up into a CFC in a few minutes. I didn't cover the entire API here - see this doc for more information:
http://code.google.com/apis/documents/overview.html
The API has some nice filtering options. You can also upload as well.


<cfset docs = createObject("component", "docs")>
<cfset docs.authenticate("rcamden@gmail.com","xxxxxx")>
<cfset mydocs = docs.getDocumentList()>
<cfdump var="#mydocs#">
<cfset content = docs.download(mydocs.sourceurl[1])>
<cfoutput>result is #content#</cfoutput>
slacker! ;)
value="whereiscfonadobedotcom"
agreed... where?
I'm on a Godaddy-hosted CF instance and cannot use CreateObject. I've pasted your example into an http page with no luck. I also changed your sample cfc to <cfinvokes... to get around the CreateObject limitation.
Happy New Year!
At code block 3 I get:
Element AUTH is undefined in AUTHDATA.
I get:
https://www.google.com/accounts/ErrorMsg?Email=rca...
I'm simply getting Error Bad Authentication. After refreshing a few times, Google sends Captcha codes back.
Not sure why this is happening, email and password are correct on my end. I'm just trying to google authenticate for gmail
<cfset email = URLENCODEDFORMAT("*******@gmail.com")>
<cfset userpwd = URLENCODEDFORMAT("*********")>
<cfhttp url="https://www.google.com/accounts/ClientLogin" method="post" result="result" charset="utf-8">
<cfhttpparam type="formfield" name="Email" value="#email#">
<cfhttpparam type="formfield" name="Passwd" value="#userpwd#">
<cfhttpparam type="formfield" name="accountType" value="HOSTED">
<cfhttpparam type="formfield" name="service" value="apps">
</cfhttp>
<cfset content = result.filecontent>
<cfset authdata = structNew()>
<cfloop index="line" list="#content#" delimiters="#chr(10)#">
<cfset dtype = listFirst(line, "=")>
<cfset value = listRest(line, "=")>
<cfset authdata[dtype] = value>
<cfoutput>#dtype# <p />#value# <p />#authdata[dtype]#</cfoutput>
</cfloop>
[Add Comment] [Subscribe to Comments]