Periodically import an ical calendar into owncloud
Posted on 2015-01-09 in Programmation Last modified on: 2015-09-21
I always thought that owncloud should be able to periodically import calendars found on the web. In my case, I have a schedule as an ical feed I would like synchronize with my owncloud. I finally wrote a script to do that.
Initially, I intended to do it in Bash and to use curl to do requests to owncloud. However, an ical feed contains line breaks. I had a lot of problems to manage them correctly. So I wrote my script in python the excellent requests library.
This script :
- reads in ~/.owncloud the address and the ids of the owncloud server,
- fetches the events described in the ical feed I ask it to synchronize. Theses events are stored in an instance of Vevent. I had the possibility to filter out some events,
- deletes the events present in my owncloud but not in the ical feeds,
- put (create or update) the events in owncloud.
I think this script is simple and clear enough to be easily read. In case of troubles, you can post a comment.
You can also download this script and see it on my mercurial on bitbucket.
1 #!/usr/bin/python3 2 3 import requests 4 from requests.auth import HTTPBasicAuth 5 import re 6 import xml.etree.ElementTree as ET 7 8 9 class Vevent: 10 UID_REGEXP = re.compile('^UID:.*') 11 SUMMARY_REGEXP = re.compile('^SUMMARY:.*') 12 13 def __init__(self, lines): 14 self._lines = lines 15 self._uid = self._get_line_from_regexp(self.UID_REGEXP) 16 self._summary = self._get_line_from_regexp(self.SUMMARY_REGEXP) 17 18 def _get_line_from_regexp(self, regexp): 19 for line in self._lines: 20 if regexp.match(line): 21 index = self._lines.index(line) 22 return line + self._lines[index + 1] 23 24 def get_vevent_for_put(self): 25 return '\r\n'.join(self._lines) 26 27 def get_as_vcal_for_put(self): 28 str_vevent = 'BEGIN:VCALENDAR\r\n' + self.get_vevent_for_put() \ 29 + '\r\nEND:VCALENDAR' 30 # If you experience problem with the line below, try 31 # return strvevent 32 # instead 33 return str_vevent.encode('utf-8') 34 35 @property 36 def uid(self): 37 return self._uid 38 39 @property 40 def summary(self): 41 return self._summary 42 43 def __repr__(self): 44 return '{}\n{}'.format(self._uid, self._summary) 45 46 47 def get_login(): 48 owncloud_omis_url = '' 49 login = '' 50 password = '' 51 with open('/home/jenselme/.owncloud') as owncloud: 52 owncloud_omis_url, login, password = [line for line in 53 owncloud.read().split('\n') if line] 54 return owncloud_omis_url, login, password 55 56 def fetch_all_vevents(urls, filter_dict): 57 vevents = [] 58 for name, url in urls.items(): 59 current_vevents = get_vevents(url) 60 if name in filter_dict: 61 current_vevents = [vevent for vevent in current_vevents 62 if filter_dict[name].match(vevent.summary)] 63 vevents.extend(current_vevents) 64 return vevents 65 66 def get_vevents(get_url): 67 calendar = requests.get(get_url).content.decode('utf-8') 68 calendar_lines = [line for line in calendar.split('\r\n') if line] 69 # Remove VCALENDAR lines 70 del calendar_lines[0] 71 del calendar_lines[-1] 72 73 vevent_lines = [] 74 vevents = [] 75 for line in calendar_lines: 76 vevent_lines.append(line) 77 if VEVENT_END.match(line) and vevent_lines: 78 vevents.append(Vevent(vevent_lines)) 79 vevent_lines = [] 80 return vevents 81 82 def delete_all_removed_vevents(fetch_vevents, destination_url, request_params): 83 destination_uids = get_destination_calendar_uids(destination_url, request_params) 84 source_uids = [vevent.uid for vevent in fetch_vevents] 85 removed_from_source_uids = [uid for uid in destination_uids if uid not in source_uids] 86 delete_all(removed_from_source_uids, destination_url, request_params) 87 88 def get_destination_calendar_uids(destination_url, request_params): 89 resp = requests.request('PROPFIND', destination_url, **request_params) 90 xml_str = resp.content.decode('utf-8') 91 root = ET.fromstring(xml_str) 92 hrefs_uid = [] 93 for response in root: 94 for child in response: 95 if child.tag == '{DAV:}href': 96 hrefs_uid.append(child.text) 97 uids = [] 98 for href in hrefs_uid: 99 uid = href.split('/')[-1] 100 if uid: 101 uids.append(uid) 102 return uids 103 104 def delete_all(uids, url, request_params): 105 for uid in uids: 106 delete_url = '{}/{}'.format(url, uid) 107 requests.delete(delete_url, **request_params) 108 109 def put_all_vevents_as_vcalendars(vevents, calendar_put_url, request_params): 110 for vevent in vevents: 111 put_url = '{}/{}.ics'.format(calendar_put_url, vevent.uid) 112 data = vevent.get_as_vcal_for_put() 113 requests.put(put_url, data=data, **request_params) 114 115 116 if __name__ == '__main__': 117 # Global variables 118 VEVENT_END = re.compile('^END:VEVENT$') 119 SOCIOLOGIE_ORGANISATION = re.compile('.*Sociologie des Organisations.*') 120 EDT_FETCH_URLS = {'omis-org': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=804&projectId=15&calType=ical&firstDate=2015-01-06&lastDate=2015-06-30', 121 'tc-electif': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=1043&projectId=15&calType=ical&firstDate=2014-11-24&lastDate=2015-04-30', 122 'ENT': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=208&projectId=15&calType=ical&firstDate=2014-10-20&lastDate=2015-05-31', 123 'omis-gB': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=507&projectId=15&calType=ical&firstDate=2014-09-15&lastDate=2015-05-31', 124 'tc2-td3': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=194&projectId=15&calType=ical&firstDate=2014-09-01&lastDate=2015-06-30', 125 'omis-i': 'https://ade6.centrale-marseille.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=814&projectId=15&calType=ical&firstDate=2014-09-01&lastDate=2015-05-31'} 126 127 owncloud_omis_url, login, password = get_login() 128 129 request_params = {'verify': False, 'auth': HTTPBasicAuth(login, password)} 130 fetched_vevents = fetch_all_vevents(EDT_FETCH_URLS, {'omis-org': SOCIOLOGIE_ORGANISATION}) 131 delete_all_removed_vevents(fetched_vevents, owncloud_omis_url, request_params) 132 put_all_vevents_as_vcalendars(fetched_vevents, owncloud_omis_url, request_params)