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)