#!/usr/bin/python """ Python library/class for working with Google Calendar API. API docs at: http://code.google.com/apis/gdata/common-elements.html Do you need to specify your time zone offset to use this? yes? Use http://www.isc.tamu.edu/~astro/jupiter/offset.html to find yours. My Home: http://code.google.com/p/pygooglecalendar/ Please maintain me ..........!!!!! """ import urllib import urllib2 import httplib import datetime import time import xml.dom.minidom class SmartRedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_301( self, req, fp, code, msg, headers) result.status = code return result def http_error_201(self,req,fp,code,msg,headers): print "this is OOOK" return 1 def http_error_302(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_302( self, req, fp, code, msg, headers) result.status = code gsessionid = headers.dict['location'].split('?')[1].split('=')[1] return gsessionid class googCal: global gsessionid sid = "" lsid = "" auth = "" gses="" email="" defaultcalendar="https://www.google.com/calendar/feeds/default/private/full" time_zone_offset=-4 #not sure if I need this def convertTime(self,datetime_obj,ignore_time=False): """Convert Python date or datetime object to xs:date or xs:dateTime respectively. (don't do offsets yet) http://tinyurl.com/nx62o gives up this ref info: xs:date - an ISO 8601 date: A prefix of the minus sign ('-') mean "BC" (before common era). The common format is 'YYYY-MM-DD'. xs:dateTime - an ISO 8601 date plus a time. The date is separated from the time by a 'T': 2005-03-10T02:00:00-08:00 The time format is 'hh:mm:ss' with at optional fractional seconds and optional timezone offset. The timezone is Greenwich Mean Time unless you specify the offset. """ datestr=str(datetime_obj) if ' ' in datestr and ignore_time:datestr=datestr.split(' ')[0] if ' ' in datestr: return datestr.replace(' ','T')+"-%s:00" % str(-self.time_zone_offset).zfill(2) else: #is a date return datestr+'T00:00:00' # ? +"-%s:00" % str(-time_zone_offset).zfill(2) def getAuth(self,email,password): url = "https://www.google.com/accounts/ClientLogin?Email=%s&Passwd=%s&source=bob-bob-1&service=cl" % (email, password) data = urllib2.urlopen(url).read().split('\n') self.sid = data[0].split('=')[1] self.lsid = data[1].split('=')[1] self.auth = data[2].split('=')[1] self.email = email print "Authorized" #TODO: make alias for "login" like add_event so people know how to add event... or just #rename login? def __make_reminder_element(self,reminder_min_before): """Make reminder element to be instered inside gdwhen tags See: http://code.google.com/apis/gdata/common-elements.html#gdReminder TODO: Add those other attributes, then rename function.""" return """""" % reminder_min_before def test_and_convert(self,adate,ignore_time=False): """datetime object conversions for convienince""" if type(adate)==datetime.datetime or type(adate)==datetime.date: return self.convertTime(adate,ignore_time=ignore_time) else: #just a string return adate #as is def login(self,title,content,authorName,authorEmail,where,start,end='',all_day_event=False,reminder_min_before=0): """ Actually makes an event, not sure why this is called "login"? DateTime formats expected to be YYYY-MM-DD or YYYY-MM-DDT17:00:00-08:00 If starttime is only sent as a date and with no end time, it is an all day event. http://code.google.com/apis/gdata/common-elements.html#gdWhen TODO: add reccurence (easier to just call this multiple times with each different date for now. http://code.google.com/apis/gdata/common-elements.html#gdRecurrence """ #Convert potential datetime objects into whatever junk xml needs start=self.test_and_convert(start,ignore_time=all_day_event) end=self.test_and_convert(end,ignore_time=all_day_event) #make reminder part if reminder_min_before: reminder=self.__make_reminder_element(reminder_min_before) else: reminder='' #make date part if all_day_event: if 'T' in start:start=start.split('T')[0] gdwhen="""%s""" % (start,reminder) else: gdwhen="""%s""" % (start,end,reminder) authString = "GoogleLogin auth=%s" % (self.auth) authurl = self.defaultcalendar txData = """ %s %s %s %s %s """ % (title,content,authorName,authorEmail,where,gdwhen) txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(authurl, txData, txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) #TODO: make this into a function so I DRY try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): #See http://groups.google.com/group/google-calendar-help-dataapi/browse_thread/thread/e135de85c795febe/d3ddd4206cef321e #for more info on this issue :-( print 'hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print authurl time.sleep(25) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'trying just one more time to recover!' time.sleep(240) self.gses = opener.open(req) try: req = urllib2.Request(authurl + "?gsessionid=" + self.gses , txData,txHeaders) except TypeError: print 'Wierd type error' #I have no clue on why this happens print authurl print self.gses req = urllib2.Request(str(authurl) + "?gsessionid=" + str(self.gses) , txData,txHeaders) #TODO: make this into a function so I DRY try: data = opener.open(req) except urllib2.URLError: print 'hit a different crappy google error, please standby while I try to recover' print uri print req #this actually works most of the time time.sleep(60) data = opener.open(req) def query(self,google_type_syntax_search_query_string,startmin='',startmax=''): """ Handle queries made of the calendar working off of the spec here: http://code.google.com/apis/gdata/protocol.html#query-requests TODO: Allow other queries param.s what the flip is category?? """ authString = "GoogleLogin auth=%s" % (self.auth) authurl = self.defaultcalendar txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(authurl, '', txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print authurl time.sleep(25) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'trying just one more time to recover!' time.sleep(240) self.gses = opener.open(req) #start-min start-max if startmin and startmax: start=self.test_and_convert(startmin,ignore_time=False) end=self.test_and_convert(startmax,ignore_time=False) uri_parms_string=urllib.urlencode( {'gsessionid':self.gses, 'q':google_type_syntax_search_query_string, 'start-min':start, 'start-max':end, 'max-results':999999}) else: uri_parms_string=urllib.urlencode( {'gsessionid':self.gses, 'q':google_type_syntax_search_query_string, 'max-results':999999}) uri=authurl + "?" + uri_parms_string #print uri req = urllib2.Request(url=uri,headers=txHeaders) #don't specify data so it makes GET request. try: data = opener.open(req).read() except urllib2.URLError: print 'hit a different crappy google error, please standby while I try to recover' print uri print req #We could try to recover here? time.sleep(60) data = opener.open(req).read() #print data u = unicode(data,"utf-8" )#should already be encoded as this but minidom throws errors for me data = u.encode("utf-8") #print #now parse XML #dresults=feedparser.parse(data) #works but doesn't get attributes like GDWhen whatever #import pprint #print pprint.pformat(dresults) def parse_entries(raw_xml): dom = xml.dom.minidom.parseString(data) #print dom.toprettyxml().encode("utf-8") #pull out all entry 's entries=dom.getElementsByTagName('entry') result_entries=[] for entry in entries: dentry={} dentry['id']=entry.getElementsByTagName('id')[0].firstChild.data dentry['published']=entry.getElementsByTagName('published')[0].firstChild.data dentry['updated']=entry.getElementsByTagName('updated')[0].firstChild.data dentry['title']=entry.getElementsByTagName('title')[0].firstChild.data try: dentry['content']=entry.getElementsByTagName('content')[0].firstChild.data except AttributeError: dentry['content']='' dentry['startTime']=entry.getElementsByTagName('gd:when')[0].getAttribute('startTime') dentry['endTime']=entry.getElementsByTagName('gd:when')[0].getAttribute('endTime') dentry['location']=entry.getElementsByTagName('gd:where')[0].getAttribute('valueString') result_entries.append(dentry) return result_entries return parse_entries(data) def deleteevent(self,publicurl): """ Delete the specified event added by David Hautbois - david.hautbois@free.fr """ txData = '' authString = "GoogleLogin auth=%s" % (self.auth) authurl = self.getediturl(publicurl) txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml', 'X-HTTP-Method-Override': 'DELETE' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(authurl, '', txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) #TODO: make this into a function so I DRY try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): #See http://groups.google.com/group/google-calendar-help-dataapi/browse_thread/thread/e135de85c795febe/d3ddd4206cef321e #for more info on this issue :-( print 'hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print authurl time.sleep(25) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'trying just one more time to recover!' time.sleep(240) self.gses = opener.open(req) try: req = urllib2.Request(authurl + "?gsessionid=" + self.gses , txData,txHeaders) except TypeError: print 'Wierd type error' #I have no clue on why this happens print authurl print self.gses req = urllib2.Request(str(authurl) + "?gsessionid=" + str(self.gses) , txData,txHeaders) #TODO: make this into a function so I DRY try: data = opener.open(req) except urllib2.URLError: print 'hit a different crappy google error, please standby while I try to recover' #print uri print req #this actually works most of the time time.sleep(60) data = opener.open(req) def getediturl(self,publicurl): """ added by David Hautbois - david.hautbois@free.fr Get the edit ID from an event ID The edit ID is nessecary to delete an event """ authString = "GoogleLogin auth=%s" % (self.auth) authurl = "https://www.google.com/calendar/feeds/default/private/full" txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml', 'Debug': 'getediturl' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(authurl, '', txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print authurl time.sleep(25) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'trying just one more time to recover!' time.sleep(240) self.gses = opener.open(req) req = urllib2.Request(url=publicurl,headers=txHeaders) #don't specify data so it makes GET request. try: data = opener.open(req).read() except urllib2.URLError: print 'hit a different crappy google error, please standby while I try to recover' print req #We could try to recover here? time.sleep(60) data = opener.open(req).read() #print data u = unicode(data,"utf-8" )#should already be encoded as this but minidom throws errors for me data = u.encode("utf-8") #print data #now parse XML #dresults=feedparser.parse(data) #works but doesn't get attributes like GDWhen whatever #import pprint #print pprint.pformat(dresults) dom = xml.dom.minidom.parseString(data) #print dom.toprettyxml().encode("utf-8") #pull out all entry 's entries=dom.getElementsByTagName('entry') result_entries=[] for entry in entries: editurl=entry.getElementsByTagName('link')[2].attributes["href"].value #Replace http by https return "https" + editurl.lstrip('http') def getcalendars(self): """ Added by David Hautbois - david.hautbois@free.fr Get the list of the user's calendar Return the id, the private URL and the access level """ print "Entering in getcalendars method" authString = "GoogleLogin auth=%s" % (self.auth) authurl = "https://www.google.com/calendar/feeds/" + self.email txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(authurl, '', txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) try: self.gses = opener.open(req) print req._Request__original except (urllib2.HTTPError,urllib2.URLError): print 'getcalendars : hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print authurl time.sleep(10) try: self.gses = opener.open(req) print self.gses except (urllib2.HTTPError,urllib2.URLError): print 'getcalendars : trying just one more time to recover!' time.sleep(10) self.gses = opener.open(req) req = urllib2.Request(url=authurl + "?gsessionid=" + self.gses,headers=txHeaders) #don't specify data so it makes GET request. try: data = opener.open(req).read() except urllib2.URLError: print 'getcalendars : hit a different crappy google error, please standby while I try to recover' print uri print req #We could try to recover here? #time.sleep(10) data = opener.open(req).read() #print data u = unicode(data,"utf-8" )#should already be encoded as this but minidom throws errors for me data = u.encode("utf-8") #print #now parse XML #dresults=feedparser.parse(data) #works but doesn't get attributes like GDWhen whatever #import pprint #print pprint.pformat(dresults) def parse_entries(raw_xml): dom = xml.dom.minidom.parseString(data) #print dom.toprettyxml().encode("utf-8") #pull out all entry 's entries=dom.getElementsByTagName('entry') result_entries=[] for entry in entries: dentry={} dentry['title']=entry.getElementsByTagName('title')[0].firstChild.data dentry['id']=entry.getElementsByTagName('id')[0].firstChild.data dentry['accesslevel']=entry.getElementsByTagName('gCal:accesslevel')[0].attributes["value"].value result_entries.append(dentry) return result_entries return parse_entries(data) def setdefaultcalendar(self,calendarid): self.defaultcalendar = "https://www.google.com/calendar/feeds" + calendarid[calendarid.rfind("/"):] + "/private/full" #-------------------------------------------------------------------------------------------------------------------------- def updateevent(self,publicurl,title,content,authorName,authorEmail,where,start,end='',all_day_event=False,reminder_min_before=0): """ Actually update an event DateTime formats expected to be YYYY-MM-DD or YYYY-MM-DDT17:00:00-08:00 If starttime is only sent as a date and with no end time, it is an all day event. http://code.google.com/apis/gdata/common-elements.html#gdWhen """ editurl = self.getediturl(publicurl) authString = "GoogleLogin auth=%s" % (self.auth) #Convert potential datetime objects into whatever junk xml needs start=self.test_and_convert(start,ignore_time=all_day_event) end=self.test_and_convert(end,ignore_time=all_day_event) #make reminder part if reminder_min_before: reminder=self.__make_reminder_element(reminder_min_before) else: reminder='' #make date part if all_day_event: if 'T' in start:start=start.split('T')[0] gdwhen="""%s""" % (start,reminder) else: gdwhen="""%s""" % (start,end,reminder) txData = """ %s %s %s %s %s %s """ % (publicurl,editurl,title,content,authorName,authorEmail,where,gdwhen) txHeaders = { 'User-Agent': 'mine', 'Authorization': authString, 'Content-Type': 'application/atom+xml', 'X-HTTP-Method-Override': 'PUT', 'Debug': 'update' } httplib.HTTPConnection.debuglevel = 1 #! req = urllib2.Request(editurl, txData, txHeaders) opener = urllib2.build_opener(SmartRedirectHandler) #TODO: make this into a function so I DRY try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): #See http://groups.google.com/group/google-calendar-help-dataapi/browse_thread/thread/e135de85c795febe/d3ddd4206cef321e #for more info on this issue :-( print 'hit a crappy google error, please standby while I try to recover' #debug: print req._Request__original print req.data print req.headers print editurl time.sleep(25) try: self.gses = opener.open(req) except (urllib2.HTTPError,urllib2.URLError): print 'trying just one more time to recover!' time.sleep(240) self.gses = opener.open(req) try: req = urllib2.Request(editurl + "?gsessionid=" + self.gses + "&request=1", txData,txHeaders) except TypeError: print 'Wierd type error' #I have no clue on why this happens print editurl print self.gses req = urllib2.Request(str(editurl) + "?gsessionid=" + str(self.gses), txData,txHeaders) #TODO: make this into a function so I DRY try: data = opener.open(req) except urllib2.URLError: print 'hit a different crappy google error, please standby while I try to recover' print editurl print req #this actually works most of the time time.sleep(60) data = opener.open(req) if __name__=='__main__': #a = googCal() #a.getAuth('username@gmail.com','password') #queries=["mom's birthday","birthday","move"] #for query in queries: # print a.query(query) #test with dates #for query in queries: #start=datetime.datetime(2006,9,20,0,0,0) #end=datetime.datetime(2006,9,20,23,59,59) #print query #print a.query(query,startmin=start,startmax=end) #search all day? #print a = googCal() #a.getAuth('user@gmail.com','password') #print all events #print a.query("*") #Delete the event #a.deleteevent("http://www.google.com/calendar/feeds/5mmu2823ffcungf7ef6lstdv88%40group.calendar.google.com/private/full/hpp8lu2tamc4fhba2mbjajdkhk") #print a.getcalendars() #a.setdefaultcalendar("http://www.google.com/calendar/feeds/david.hautbois%40gmail.com/5mmu2823ffcungf7ef6lstdv88%40group.calendar.google.com") #print a.query("*") #print a.query("title") #print a.getediturl("http://www.google.com/calendar/feeds/5mmu6793ffcungf7ef6lstdv88%40group.calendar.google.com/private/full/hpp8lu2tamc4fhba2mbjajdkhk") #a.login("title","content","David","username@gmail.com","location","2007-03-20T20:00:00","2007-03-20T23:00:00") #print a.getediturl("http://www.google.com/calendar/feeds/5mmu2393ffcungf7ef6lstdv55%40group.calendar.google.com/private/full/1uj1kg26dkg17es10a1m7lr2rc") #myevent="http://www.google.com/calendar/feeds/5mmu2893ffcungf7ef6lstdv55%40group.calendar.google.com/private/full/1uj1kg26dkg17es10a1m7lr2rc" #a.updateevent(myevent,"title1","aaa","David","david.hautbois@gmail.com","location","2007-03-19T20:00:00","2007-03-19T21:00:00")