Importer périodiquement un flux ical dans owncloud
Posted on 2015-01-09 in Programmation
J'ai toujours trouvé dommage que owncloud ne soit pas capable d'importer périodiquement des calendriers que l'on trouve sur le web. Dans mon cas, mon emploi du temps est un flux ical régulièrement mis à jour et j'aimerais bien que owncloud soit capable de l'importer. Je me suis finalement résigné à faire mon propre script pour régler ce problème.
Je pensais initialement le faire en Bash et utiliser curl pour faire les requêtes vers owncloud. Cependant, un flux ical peut contenir des sauts de lignes et les tenter de gérer correctement m'a fait souffrir. Du coup, je me suis rabattu sur python et son excellente bibliothèque requests.
Concrètement, ce script :
- lit dans ~/.owncloud l'adresse et les identifiants du serveur owncloud,
- récupère les événements définis dans les flux icals que je lui demande de synchroniser. Ces événements sont stockés dans une instance de la classe Vevent crée à cet effet. J'ai ajouté la possibilité de filtrer certains événements (pour exclure des cours que je n'ai pas),
- supprime les événements présents dans owncloud mais absent des flux icals,
- poste (crée ou met à jour) les événements.
Il ne reste plus qu'à mettre le script dans un cron pour faire l'import périodiquement.
Normalement le script est assez simple et clair pour être facilement compréhensible. En cas de problème, postez un commentaire.
Vous pouvez aussi télécharger ce script et le voir sur le dépôt mercurial.
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)