Created
April 25, 2017 18:19
-
-
Save adamwg/072f6dc46fc8c3a735d1afb947a40c37 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2.7 | |
import sys | |
import os.path | |
import argparse | |
import httplib2 | |
import icalendar | |
from oauth2client import tools | |
from oauth2client.client import flow_from_clientsecrets | |
from oauth2client.file import Storage | |
from copy import deepcopy | |
cal_id = "...@group.calendar.google.com" | |
calendar_url = "https://apidata.googleusercontent.com/caldav/v2/%s/events" % cal_id | |
def auth(): | |
parser = argparse.ArgumentParser(parents=[tools.argparser]) | |
flags = parser.parse_args() | |
flow = flow_from_clientsecrets( | |
'client_secrets.json', scope='https://www.googleapis.com/auth/calendar', | |
redirect_uri='urn:ietf:wg:oauth:2.0:oob') | |
storage = Storage('.creds') | |
return tools.run_flow(flow, storage, flags) | |
def get_creds(): | |
storage = Storage('.creds') | |
creds = storage.get() | |
if creds is None or creds.invalid: | |
creds = auth() | |
return creds | |
def remove_extensions(component): | |
for k in component.keys(): | |
if k.startswith('X-'): | |
del component[k] | |
if k.startswith('ORGANIZER'): | |
del component[k] | |
if k.startswith('ATTENDEE'): | |
del component[k] | |
def get_events(icsfile): | |
fd = open(icsfile) | |
txt = fd.read() | |
cal = icalendar.Calendar.from_ical(txt) | |
template = icalendar.Calendar() | |
for item in cal.items(): | |
template.add(*item) | |
remove_extensions(template) | |
for sc in cal.subcomponents: | |
if type(sc) != icalendar.Event: | |
remove_extensions(sc) | |
template.add_component(sc) | |
events = dict() | |
for sc in cal.subcomponents: | |
remove_extensions(sc) | |
try: | |
uid = sc.decoded('uid') | |
except KeyError: | |
continue | |
if uid not in events: | |
ev = deepcopy(template) | |
ev.add_component(sc) | |
events[uid] = ev | |
else: | |
events[uid].add_component(sc) | |
return events | |
def get_existing_events(http): | |
(resp, content) = http.request(calendar_url, "GET") | |
cal = icalendar.Calendar.from_ical(content) | |
remove_extensions(cal) | |
events = dict() | |
for sc in cal.subcomponents: | |
if type(sc) == icalendar.Event: | |
remove_extensions(sc) | |
try: | |
uid = sc.decoded('uid') | |
except KeyError: | |
continue | |
events[uid] = sc | |
return events | |
def get_sync(new_events, old_events): | |
to_delete = dict() | |
to_update = dict() | |
to_add = dict() | |
# Delete anything in old that's not in new. | |
for (id, event) in old_events.items(): | |
if not id in new_events: | |
to_delete[id] = event | |
for (id, event) in new_events.items(): | |
# Update anything that's in both. | |
if id in old_events: | |
to_update[id] = event | |
else: | |
to_add[id] = event | |
return (to_delete, to_update, to_add) | |
def delete_event(id, event, http): | |
url = "%s/%s.ics" % (calendar_url, id) | |
print "Deleting event %s" % id | |
print "Getting etag" | |
(resp, content) = http.request(url, "GET") | |
try: | |
etag = resp['etag'] | |
except KeyError: | |
print "Error getting etag" | |
print resp | |
return (None, None) | |
(resp, content) = http.request(url, "DELETE", headers={"if-match": etag}) | |
return (resp, content) | |
def add_event(id, event, http): | |
url = "%s/%s.ics" % (calendar_url, id) | |
print "Adding event %s" % id | |
(resp, content) = http.request(url, "PUT", body=event.to_ical(), | |
headers={"content-type": "text/calendar", | |
"if-none-match": "*"}) | |
return (resp, content) | |
def update_event(id, event, http): | |
url = "%s/%s.ics" % (calendar_url, id) | |
print "Updating event %s" % id | |
print "Getting etag" | |
(resp, content) = http.request(url, "GET") | |
try: | |
etag = resp['etag'] | |
except KeyError: | |
print "Error getting etag" | |
print resp | |
return None | |
print "Sending update with etag %s" % etag | |
(resp, content) = http.request(url, "PUT", body=event.to_ical(), | |
headers={"content-type": "text/calendar", | |
"if-match": etag}) | |
return (resp, content) | |
def main(): | |
if len(sys.argv) < 2: | |
print "Usage: %s [args] <icsfile>" % sys.argv[0] | |
return | |
icsfile = sys.argv[-1] | |
if not os.path.exists(icsfile): | |
print "Usage: %s [args] <icsfile>" % sys.argv[0] | |
return | |
del sys.argv[-1] | |
creds = get_creds() | |
http = creds.authorize(httplib2.Http()) | |
old_events = get_existing_events(http) | |
new_events = get_events(icsfile) | |
(to_delete, to_update, to_add) = get_sync(new_events, old_events) | |
print "delete %d / update %d / add %d" % (len(to_delete), len(to_update), | |
len(to_add)) | |
for (id, event) in to_delete.items(): | |
(resp, content) = delete_event(id, event, http) | |
if not resp: | |
continue | |
if int(resp['status']) > 299: | |
print "Delete failed" | |
print resp | |
for (id, event) in to_update.items(): | |
(resp, content) = update_event(id, event, http) | |
if int(resp['status']) > 299: | |
print "Update failed" | |
print resp | |
for (id, event) in to_add.items(): | |
(resp, content) = add_event(id, event, http) | |
if int(resp['status']) > 299: | |
print "Add failed" | |
print resp | |
print content | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment