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:
<cfhttp url="https://www.google.com/accounts/ClientLogin" method="post" result="result" charset="utf-8">
<cfhttpparam type="formfield" name="accountType" value="HOSTED_OR_GOOGLE">
<cfhttpparam type="formfield" name="Email" value="rcamden@gmail.com">
<cfhttpparam type="formfield" name="Passwd" value="whereiscfonadobedotcom">
<cfhttpparam type="formfield" name="service" value="writely">
<cfhttpparam type="formfield" name="source" value="camden-cfgoogledocs-0">
</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:
<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>
</cfloop>
Now you have authentication information. This can be passed in with your quest:
<cfhttp url="http://docs.google.com/feeds/documents/private/full" method="get" result="result" charset="utf-8">
<cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#">
</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:
<cfset packet = xmlParse(result.filecontent)>
<cfloop index="x" from="1" to ="#arrayLen(packet.feed.entry)#">
<cfset entry = packet.feed.entry[x]>
<cfset title = entry.title.xmltext>
<cfset updated = entry.updated.xmltext>
<cfset type = entry.category.xmlattributes.label>
<cfset sourceurl = entry.content.xmlattributes.src>
<cfoutput>[#type#] #title# (#updated#)<br></cfoutput>
</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:
<cfhttp url="#somesourceurl#" method="get" result="result" charset="utf-8">
<cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#">
</cfhttp>
<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.
Archived Comments
I lie. I got a CFC done. I'll post later. It allows for this though:
<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>
Very cool, Ray! I can't wait for it to show up on RIAForge - I'm using google docs to share some documentation for an admin site with a buddy and this should let me just display the contents of the doc right on the site!
No. This will not be a project. Never. I can't. Not another project. Please, help me stop the madness!
Geez, Ray.... not another project? What do you want to do instead.... sleep?
slacker! ;)
LOL @
value="whereiscfonadobedotcom"
agreed... where?
That was my way of being a little snide. ;) As it stands - CF is now on the home page!
Raymond - Any idea why I would be getting a "Connection Failure" result while trying your example with valid values???? Every time I try the page spins out for about 45 seconds and the "result.filecontent" contains the failure message.
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!
I'm not sure what you mean by 'pasting into an http page' - but I'd consider one simple CFM with a CFHTTP that mimics my code. If it still fails, you need to talk to GoDaddy.
Just a quick note that I found this great ... had to adapt it to MX6.1 for a project but not too bad. Instead of arrayLen(packet.feed.entry) I used packet.feed.totalResults.xmlText in production because it allows for a 0 entry not to throw a CF error since not all my users have Google Docs. This may have been added by Google after you wrote this so I wanted to give this heads up to everyone finding their way here.
I'm guessing this doesn't work anymore. I just tested the code as is.
At code block 3 I get:
Element AUTH is undefined in AUTHDATA.
It could be something else. What _does_ exist in authdata?
If I print out <cfoutput>#authdata[dtype]#</cfoutput>
I get:
https://www.google.com/acco...
You get that url? It has my email address in it. Did you try to access _my_ docs? You would need to use your own auth info to test this.
That was to demonstrate. I used my own. What I think is happening is that it's not authenticating.
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/acco..." 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>
Ah well shoot. You got me there. The code is 4 years old so it's very possible their API has been updated to require oAuth for example in order to work.
no prob. I figured that. It sucks not to be able to find coldfusion examples for much of the basics. I was searching for coldfusion demos on authenticating users with Google. I'll play around some more.
Hi Ray, I've been using the googleDocsService.cfc successfully (thanks for the great work) I do have a question I've been googling around for 2 hours and not finding answers:
When a google document gets displayed by the viewer, you see several options and icons on the top left of the viewer: paging zoom and print (PDF). Do you know if it is possible to give a viewer a param so the print function opens a new window as opposed to what by default does which is to open the print preview in the same window?
Please let me know
Thanks
Here's a sample URL https://docs.google.com/vie...
I'm not quite sure I get what you're asking. You want to change the behavior of the browser's Print menu?
Hi Raymond.
I have been using very similar code to list files on Google Drive but for some reason it does not list video, image or Lucidcharts...any idea why?
Does it have to do with the difference between
"https://docs.google.com/fee..."> AND
"https://docs.google.com/fee...">
I am using the URL ...feeds/documents/... but I can't seem to get the ...feeds/default/... URL to work.
From what I can tell, Drive seems to be an entirely different API - https://developers.google.c...
This means my code here is probably obsolete.