get_ical.py 26.7 KB
Newer Older
Pascal's avatar
Pascal committed
1
2
#!/usr/bin/python
#coding: utf8
3
import re, requests, json, datetime, time
Pascal's avatar
Pascal committed
4

Frederic HAN's avatar
Frederic HAN committed
5
def get_ical(code,start='2022-08-23',end='2023-07-17',year=10,fiche_etalon=''):
Pascal's avatar
Pascal committed
6
7
8
9
10
    url = 'https://adeprod.app.univ-paris-diderot.fr:443/jsp/custom/modules/plannings/anonymous_cal.jsp'
    params = {
            'calType': 'ical',
            'firstDate': start,
            'lastDate': end,
11
            'resources': '%s%s'%(fiche_etalon,code),
Pascal's avatar
Pascal committed
12
13
14
15
16
            'projectId': year
            }
    r = requests.get(url, params=params)
    return r.text

Pascal's avatar
Pascal committed
17
def modifdate(m,shift):
18
19
    """
    fonction auxiliaire qui corrige la chaine de deux lignes consecutives DTSTART DTEND
20
    ADE introduit un decalage qui depend de l'heure ete/hiver.
Frederic HAN's avatar
Frederic HAN committed
21
22
23
    Il y a en plus un probleme de 24h lorsque les heures voulues sont vers l'apres midi.
    - En 2021-2022 jusqu'au 15/10/21 le shift est de -10 et l'heure frontiere du jour perdu est à 12h.
      !le 16/10/21 le shift d'été est passé brutalement à -9 et l'heure frontiere à 13h!
24
25
26
27
    - En 2020-2021 le shift est de -11 en ete et l'heure frontiere du jour perdu est à 11h.
    - En 2019-2020 le shift est de -12 en ete et l'heure frontiere du jour perdu est à 10h.
      De plus les cours qui terminaient à 20h en 19-20 ne semble pas transmis par ADE...

Frederic HAN's avatar
Frederic HAN committed
28
29
30
31
    * REMARQUE dans tous ces cas on peut maintenant deviner la formule:

            heurefrontiere -shiftete = 22h

32
    * En hiver il faut encore retrancher 1 au shift.
33

34
35
36
    m est la valeur trouvee par:

        re.compile('DTSTART:(\d{8}T\d{6}Z).*?DTEND:(\d{8}T\d{6}Z)',re.DOTALL)
37
    """
Pascal's avatar
Pascal committed
38
39
    debut=datetime.datetime.strptime(m.group(1),'%Y%m%dT%H%M%SZ')
    fin=datetime.datetime.strptime(m.group(2),'%Y%m%dT%H%M%SZ')
Pascal's avatar
Pascal committed
40
41
    orig = "ORIGSTART:%d%02d%02dT%02d%02d%02dZ\r\nORIGEND:%d%02d%02dT%02d%02d%02dZ\r\n"%(debut.year,debut.month,debut.day,debut.hour,debut.minute,debut.second,fin.year,fin.month,fin.day,fin.hour,fin.minute,fin.second)

42
43
44
45
46
    # PB le shift change avec l'heure d'hiver/ete:
    D1 = datetime.datetime(debut.year,10,31)
    D2 = datetime.datetime(debut.year,3,31)
    lastdimancheoct = D1 - datetime.timedelta(days=(D1.weekday()+1)%7)
    lastdimanchemars = D2 - datetime.timedelta(days=(D2.weekday()+1)%7)
47
    # shift a ajouter aux heures recues en ete
48
    #shiftete_manuel = -10 # vaut -10 au semestre 1 2021-2022, avant 21/10/2021.
49
50
    #shiftete_manuel = -9 # vaut -9 au 22/10/2021. (base 2021-22)

51
    shiftete_manuel = -8 # vaut -8 au 3/6/2022 sur la base 2022-23
52

53
    # frontiere ou le bug de jour apparait
54
55
56
57
58
59
60
61
    # Remarque dans tous les exmples on a heurefrontiere = shiftete + 22
    #heurefrontiere_manuel = 12 # vaut 12h au semestre 1 2021-2022, avant 21/10/2021
    heurefrontiere_manuel = 13 # vaut 13, au 22/10/2021
    #
    # on utilise le shift etalon.
    shiftete = shift
    heurefrontiere = shiftete + 22
    if shift != shiftete_manuel:
62
63
        print("[WARNING] shift %d != %d. la valeur du shift n'est pas celle connue pour l'annee la plus recente"%(shift,shiftete_manuel))
        #print("[WARNING] on utilise l'heurefrontiere 22+shiftete; à VERIFIER!")
64
65
66



67

68
69
    if debut.month > 7:
        # l'annee vaut pour aout-decembre. (Semestre 1)
70

71
        #if debut.year == 2021:
72
            # annee 2021-2022
73
74
75
            #on a un etalon pour 2021-2022 on evite la config manuelle
            #heurefrontiere = 12
            #shiftete = -10
76

77
78
79
80
81
82
83
84
85
        if debut.year == 2020:
            # annee 2020-2021
            heurefrontiere = 11
            shiftete = -11
        if debut.year == 2019:
            # annee 2019-2020
            heurefrontiere = 10
            shiftete = -12

86
87
        if debut >= lastdimancheoct:
            #print("hiver")
88
            shift = shiftete -1
89
90
        else:
            #print("ete")
91
            shift = shiftete
92
93
    if debut.month<8:
        # l'annee vaut pour janvier-juillet (Semestre2)
94
        #if debut.year == 2022:
95
            # annee 2021-2022
96
97
98
99
            #on a un etalon pour 2021-2022 on evite la config manuelle
            #heurefrontiere = 13
            #shiftete = -9

100
101
102
103
104
105
106
107
108
        if debut.year == 2021:
            # annee 2020-2021
            heurefrontiere = 11
            shiftete = -11
        if debut.year == 2020:
            #annee 2019-2020
            heurefrontiere = 10
            shiftete = -12

109
110
111
112
113
114
115
        #Ces 4 reservations sont les jeudis de 10h45-12h45
        #2021-04-01|21:45:00|2021-03-31|23:45:00|ALGORITHMIQUE|432C
        #2021-02-11|22:45:00|2021-02-11|00:45:00|ALGORITHMIQUE|432C
        #2021-02-18|22:45:00|2021-02-18|00:45:00|ALGORITHMIQUE|432C
        #2021-03-04|22:45:00|2021-03-04|00:45:00|ALGORITHMIQUE|432C
        if debut<lastdimanchemars:
            #print("hiver")
116
            shift = shiftete -1
117
        else:
118
            #print("ete")
119
            shift = shiftete
120

Pascal's avatar
Pascal committed
121
    # parfois il manque un jour a la date de fin, Exemple:
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    # ORIGSTART:20210915T201500Z
    # ORIGEND:20210914T221500Z
    #
    # il existe des creneaux dont le debut et la fin sont la veille!
    #
    #Ex 6736.ics (avec shift = -10)
    #debut et fin peuvent etre le jour d'avant
    #ORIGSTART:20210912T221500Z
    #ORIGEND:20210912T234500Z
    #
    #Ex 6658.ics
    #ORIGSTART:20210823T180000Z
    #ORIGEND:20210823T184500Z
    #SUMMARY:étalon pour Calendar
    # pour IP1 le lundi 13/9 a 13h45 (paris)
    #ORIGSTART:20210912T234500Z
    #ORIGEND:20210913T014500Z
    #
    #
141
    # En fait il semble que si l'heure voulue etait apres:
Frederic HAN's avatar
Frederic HAN committed
142
    # 12h au semestre1 2021-2022 (13h à partir du 16/10/21), et 11h en 2020-2021,
143
    # ADE a perdu un jour
144
145
    # en voulant faire une certaine modif de decalage.
    #
146
    if debut.hour >= heurefrontiere -shift  or debut.hour <= 20 - shift -24:
Frederic HAN's avatar
Frederic HAN committed
147
        # si l'heure voulue >= heurefrontiere et <= 20h
148
        # dans ce cas il faut augmenter d'un jour avant d'appliquer le shift
Frederic HAN's avatar
Frederic HAN committed
149
        #TODO: on peut maintenant reserver jusque 21h!
150
        debut = debut + datetime.timedelta(hours=24)
Pascal's avatar
Pascal committed
151

152
    if fin.hour >= heurefrontiere -shift or fin.hour <= 20 - shift -24:
Frederic HAN's avatar
Frederic HAN committed
153
        # si l'heure voulue >= heurefrontiere et <= 20h
154
155
        # dans ce cas il faut augmenter d'un jour
        fin = fin + datetime.timedelta(hours=24)
Pascal's avatar
Pascal committed
156

157
    # On a ajusté les decalages de jours, on peut appliquer le shift a tous.
Pascal's avatar
Pascal committed
158
159
    debut=debut + datetime.timedelta(hours=shift)
    fin = fin +datetime.timedelta(hours=shift)
Pascal's avatar
Pascal committed
160

161

162
    return orig+"DTSTART;TZID=Europe/Paris:%d%02d%02dT%02d%02d%02d\r\nDTEND;TZID=Europe/Paris:%d%02d%02dT%02d%02d%02d"%(debut.year,debut.month,debut.day,debut.hour,debut.minute,debut.second,fin.year,fin.month,fin.day,fin.hour,fin.minute,fin.second)
Pascal's avatar
Pascal committed
163

164
165
166
167
168
169
170
171
172
173

def modifsalle(matchsalle):
    """
    En entrée un match de:
        re.compile('LOCATION:([a-zA-Z]* - \w+ -) ([0-9]+)')
    Ex: Halle - 331C -
    en sortie: "TP" si c'est une salle de TP connue. sinon une chaine vide.
    """
    salle=matchsalle.group(1)
    effectif=matchsalle.group(2)
174
175
176
177
178
179
    sallesTP = []
    amphis = []

    ###########
    # Salles TP
    ###########
180
181
    tpHalle = ['331C', '446C', '452C', '557C', '443C', '449C', '532C', '548C']
    tpHalle +=['432C', '436C', '443C', '531C', '537C', '538C', '554C', '551C']
182
    sallesTP += [ "Halle - %s -"%i for i in tpHalle ]
183
184

    tpMoulins = ['688C', '785C', '789C', '791C', '797C']
185
    sallesTP += [ "Moulins - %s -"%i for i in tpMoulins ]
186
187

    tpGermain = [ '2001', '2003', '2004', '2005', '2006', '2027', '2028', '2031', '2032']
188
    sallesTP += [ "Germain - %s -"%i for i in tpGermain ]
189
190
191
192

    tpCondorcet = ['073A', '075A', '077A', '156A', '172A', '173A', '174A', '176A', '192A']
    tpCondorcet+= ['193A', '202A', '213A', '241A', '250A', '276A', '285A', '292A', '293A']
    tpCondorcet+= ['322A', '313A', '338A']
193
194
195
196
197
198
199
200
201
202
203
204
205
206
    sallesTP += ["Condorcet - %s -"%i for i in tpCondorcet]

    tpLavoisier = ['210', '240']
    sallesTP += ["Lavoisier - %s -"%i for i in tpLavoisier]

    tpLamarck = ['122B', '132B', '142B', '149B', '154B', '201B', '213B', '222B', '232B', '249B', '254B']
    tpLamarck += ['179B', '181B', '189B', '281B', '289B', '521A', '525A']
    sallesTP += ["Lamarck - %s -"%i for i in tpLamarck]

    #NB: Manque les TP de Gouges.

    #########
    # Amphis
    #########
207
208
209
    amphisHalle = ['11E', '7C', '10E', '12E', '3B', '4C', '5C', '6C', '9E', '13E', '1A', '2A', '8C'] # amphis
    amphisHalle+= ['234C', '247E', '580F'] # gdes salles 84 places
    amphisHalle+= ['027C', '064E', '226C', '227C', '264E', '265E', '418C', '419C', '470E', '471E'] # gd salles 70 places
210
211
    amphis += ["Halle - %s -"%i for i in amphisHalle]

Frederic HAN's avatar
Frederic HAN committed
212
    amphisGouges = [ 'Gouges - 1 -', 'Gouges - 2 -']
213
    amphis += amphisGouges
Frederic HAN's avatar
Frederic HAN committed
214
215


216
    if salle in sallesTP:
217
        salle = salle + "TP-"
218
219

    if salle in amphis:
220
        salle = salle + "CM?-"
221
222


223
    salle="LOCATION:"+salle+" (%sp)"%effectif
224

225
226
    return salle

227

Frederic HAN's avatar
Frederic HAN committed
228
from codesapogee import apogee,filtreECUE
229
230
231


def modifvevent(matchevent, shift, icalname):
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    """
    En entrée, event est un match d'un VEVENT,
    en sortie le VEVENT modifé:
    - correction des heures en appelant modifdate
    - détection des salles de TP et ajout du tag
    """
    event=matchevent.group(0)
    # il vaut mieux celle ci au cas ou les nouvelles lignes ne soient pas \r\n selon l'OS.
    # pour rebeginend on suppose qu'ils sont ordonnes DTSTART avant DTEND
    #fonction de recherche pour les fichiers emis par ADE.
    rebeginend=re.compile('DTSTART:(\d{8}T\d{6}Z).*?DTEND:(\d{8}T\d{6}Z)',re.DOTALL)
    # on corrige les heures
    event=rebeginend.sub(lambda m: modifdate(m,shift), event)
    #
    resalle=re.compile('LOCATION:([a-zA-Z]* - [0-9A-Z]+ -) ([0-9]+)')
    event=resalle.sub(lambda m: modifsalle(m), event)
248
249
250
251
252
253
254
255

    resummary = re.compile('SUMMARY:(.*)')
    m = resummary.search(event)

    code = icalname

    if m != None:
        summary = m.group(1)
Frederic HAN's avatar
Frederic HAN committed
256
257
258
259
260
        if summary.endswith('\r'):
            summary =   summary.replace('\r','')
            chariot="\r"
        else:
            chariot=""
261
262

        if code in apogee:
263
            summary2 = summary.replace('\\','')
264

265
266
            if summary2 in apogee[code]:
                event = resummary.sub(lambda m: "SUMMARY:%s (%s)%s"%(summary,apogee[code][summary2],chariot), event)
267

268
269
    return event

270
271
272



273
def fix_timezone(ical,icalname=""):
Pascal's avatar
Pascal committed
274
    """
275
276
277
278
279
280
    La fonction transforme un calendrier en utilisant des fonctions auxilaires pour:
        - réparer les données de début et fin.
          DTSTART:20210915T183000Z
          DTEND:20210915T203000Z
        - Vérifier que le calendrier n'a pas de créneaux incohérents.
        - Détecter les salles de TP et amphis car ces données ne sont pas présentes dans le calendrier émis par ADE.
Pascal's avatar
Pascal committed
281
    """
282
    reshift = re.compile('(DTSTART:20\d{2}0823T)(\d{2})(\d{4})Z')
283
284
    # si l'on veut recuperer tout le vevent
    revevent=re.compile('BEGIN:VEVENT.*?END:VEVENT',re.DOTALL)
285
286
    # Fonction de recherche pour les fichiers modifies avec l'heure de Paris:
    rebeginendparis=re.compile('DTSTART;TZID=Europe/Paris:(\d{8}T\d{6}).*?DTEND;TZID=Europe/Paris:(\d{8}T\d{6})',re.DOTALL)
Pascal's avatar
Pascal committed
287
288
289
    index = 2

    m = reshift.search(ical)
290
    decalageUTC=0
291
    if not m:
292
        shift=-8-decalageUTC
293
294
        print('[WARNING] %s: no reference for etalon 0823T, set shift to %d'%(icalname, shift))
        # NB le shift passe de -10 a -9 le 22/10/2021
295
        # NB le shift sur la base 2022-23 semble etre à -8 ke 3/6/2022.
296
297
    else:
        shift = (-decalageUTC+ 8 - int(m.group(index)))   # le vrai shift sans le modulo.
298
299
        #if (shift != -decalageUTC -10):
        #    print("[WARNING] %s: %s -> shift = %d  != %d)"%(icalname, m.group(0),shift,-decalageUTC-10))
Pascal's avatar
Pascal committed
300

301
    #ical = rebeginend.sub(lambda m: modifdate(m,shift), ical)
302
    ical = revevent.sub(lambda m: modifvevent(m,shift,icalname), ical)
Pascal's avatar
Pascal committed
303

304
305
306
307
    # Quelques verifs
    Levent=revevent.finditer(ical)
    tmpcount=0
    for mevent in Levent:
308
        L=rebeginendparis.findall(mevent.group(0))
309
310
        if len(L)==1:
            m=L[0]
311
312
            debut=datetime.datetime.strptime(m[0],'%Y%m%dT%H%M%S')
            fin=datetime.datetime.strptime(m[1],'%Y%m%dT%H%M%S')
313
            tmpcount+=1
314
            if fin-debut < datetime.timedelta(hours=0):
315
                #probleme il reste un creneau negatif ou de plus de 8h.
316
                print("[FATAL] %s: creneau mal corrige %s"%(icalname, str(m)))
317
                return None
318
319
            if fin-debut > datetime.timedelta(hours=8):
                #tres rare mais ca existe, Ex en L1 info/biole 19/10/21
320
                print("[WARNING] %s: creneau de plus de 8h %s"%(icalname, str(m)))
321
322
323
            if fin-debut > datetime.timedelta(hours=13):
                # il y a maintenant des reservations de 8h-20h45 ...
                print("[FATAL] %s: creneau de plus de 13h %s"%(icalname, str(m)))
324
                return None
325
            if debut.weekday() == 6 or fin.weekday() == 6:
326
                print("[FATAL] %s: creneau sur un dimanche %s"%(icalname, str(m)))
327
                return None
328
        else:
329
            print("[FATAL] %s: vevent contient %d start/end: %s"%(icalname, len(L),mevent.group(0)))
330
            return None
331
332


333
    print("(%s): %d fiches trouvees/verifiees"%(icalname, tmpcount))
334

335

Pascal's avatar
Pascal committed
336
337
338
    return ical


339
def cherchedeplacements(fcalen = 'calendars.json', outdir='data', oldoutdir='data.28', labelobsolete='obsolète'):
340
341
342
343
    """
    Cherche les différences dans les deux versions de tous les calendriers de chaque formation pour:

    - Créer pour chaque formation un calendrier 1234.ics où 1234 est le code de la formation.
344
      (ie en general (*) le code à gauche dans formations.json)
345
    - ce 1234.ics contient les anciens événements qui ont été modifiés ou supprimés.
346
347
348
349
350
    - si l'on déclare 1234.ics comme un groupe de la formation avec le label 'obsolète' , on pourra voir ces modifications.

    (*) En fait certain groupes ont une formation mère mais sont présents et affichés dans une autre.
    (ex en l3 mathinfo 2 codes formations meres: 2418 et 7426). On utilise donc le fichier
    calendars.json où l'on crée des listes de tous les labels attachés à la formation.
351
352
353
    """

    revevent=re.compile('BEGIN:VEVENT.*?END:VEVENT',re.DOTALL)
354
355
356
    revariable = re.compile('(DTSTAMP:.*\n)|(LAST-MODIFIED:.*\n)|(CREATED:.*\n)|(SEQUENCE:.*\n)|(ORIGSTART:.*\n)|(ORIGEND:.*\n)|(UID:.*\n)|(LAST-MODIFIED:.*\n)|\s\s.*|(DESCRIPTION:.*)')
    #revariable=re.compile('(?!DTSTART.*)*|(?!DTEND.*)*')
    rexeport = re.compile('\(.*?\)|\n',re.DOTALL)
357

358
359
    # on recupere les noms des boutons via le code, on utilise donc calendars.json
    noms = {}
360
    Lforma = {}
361
362
363
364
    obsoletetrouve = [] # la liste des formations qui ont un bouton obsolete.
    # deux ensembles pour s'assurer qu'un code de groupe n'a pas ete saisi dans un code pour bouton obsolete
    setobsolete = set()
    setgroupe = set()
365
366
    with open(fcalen) as calenfile:
        for l in json.load(calenfile):
367
368
369
370
371
372
373
            noms[ str(l["code"]) ] = l["label"]   # un dictionnaire pour trouver le label d'une formation
            # Une formation = année + parcours. Ex: l1 math
            if not l["parcours"]+l["year"] in Lforma:
                Lforma[l["parcours"]+l["year"]] = []
            if l["label"] == labelobsolete:
                # On place le code de la formation en premier
                Lforma[l["parcours"]+l["year"]] = [str(l["code"])] + Lforma[l["parcours"]+l["year"]]
374
375
                obsoletetrouve.append(l["parcours"]+l["year"])  # OK on note qu'il y a bien un bouton obsolete pour cette formation
                setobsolete.update({l["code"]})
376
377
378
            else:
                # ensuite on place les codes des groupes
                (Lforma[l["parcours"]+l["year"]]).append(str(l["code"]))
379
380
381
                setgroupe.update({l["code"]})

        assert( setobsolete.isdisjoint(setgroupe)),"Pb %s un code de fiche est present dans un code pour archive obsolete"%(setobsolete.intersection(setgroupe))
382
383
384


        for c in Lforma:
385
386
387
            # Une securité s'il n'y a pas de bouton obsolete pour cette formation
            # pour ne pas ecraser son calendrier
            if c in obsoletetrouve:
388
389
                # on etudie une formation
                changements = ""
390
391
                fiches=Lforma[c][1:]
                codeformation = Lforma[c][0]
392
                oldevents={}
393
                nborig = 0
394
395
                for code in fiches:
                    try:
396
397
398
399
400
401
402
403
404
                            fnew = open("%s/%s.ics"%(outdir,code),"r")
                            calnew = fnew.read()
                            fnew.close()
                            fold = open("%s/%s.ics"%(oldoutdir,code),"r")
                            calold= fold.read()
                            fold.close()
                            for mevent in revevent.finditer(calold):
                                event=mevent.group(0)
                                fiable = rexeport.sub('',revariable.sub('',event))
405
                                # on ajoute le groupe dans le hash au cas ou une fiche commune soit modifiee
406
407
408
                                #fiable=fiable.replace('\n','')
                                fiable=fiable.replace('\,','')
                                fiable=fiable.replace(',','')
409
                                fiable = fiable.replace("SUMMARY:","SUMMARY:[%s]"%(noms[code]))
410
                                oldevents[hash(fiable)] = event.replace("SUMMARY:","SUMMARY:[%s]"%(noms[code]))
411
                            nborig += len(oldevents)   # on regarde le total des evenements d'une page.
412
413
414
                            for mevent in revevent.finditer(calnew):
                                event=mevent.group(0)
                                fiable = rexeport.sub('',revariable.sub('',event))
415
416
417
                                #fiable=fiable.replace('\n','')
                                fiable=fiable.replace('\,','')
                                fiable=fiable.replace(',','')
418
419
                                # on ajoute le groupe dans le hash au cas ou une fiche commune soit modifiee
                                fiable = fiable.replace("SUMMARY:","SUMMARY:[%s]"%(noms[code]))
420
                                oldevents.pop(hash(fiable),None)
421
                    except:
422
                            print("[WARNING] echec ds diff de (%s) %s. (manque archive?) => on crée un cal. vide"%(code, c))
423

424
425
                # On ajoute un calendrier pour toutes les fiches.
                with open("%s/%s.ics"%(outdir, codeformation),'w') as f:
426
427
428
429
430
                        f.write("BEGIN:VCALENDAR\n")
                        f.write("METHOD:REQUEST\n")
                        f.write("PRODID:-//ADE/version 6.0\n")
                        f.write("VERSION:2.0\n")
                        f.write("CALSCALE:GREGORIAN\n")
431
432
433
434
                        if len(oldevents)<50 or len(oldevents)*4<nborig:
                            for key in oldevents:
                                f.write(revariable.sub('',oldevents[key])+"\n")
                        else:
435
                            print("[WARNING] (%s) plus de 50 chts et plus de 1/4 de modifications; on ne les enregistre pas."%(c))
436
                            print("[WARNING] si les données de référence (data.28) sont fiables il faut vérifier data\n(et voir s'il n'y a pas un problème de shift/calibrage ADE?)")
437

438
439
440
441
                        f.write("END:VCALENDAR\n")



442
443
444
445
446
447
448
449
450
451
def tests():
    """
    Une fonction de test pour traiter un exemple avec shift de -11h si l'on n'arrive pas
    a obtenir de tels exemples.
    """
    f=open("test_6651L1MATH3delta_moins11.ics")
    ical=f.read()
    ical=fix_timezone(ical)
    f.close()
    print("Test L1 MATH3 semaine du 11/10/21")
452
    dest=open("data/6651.ics","w")
453
454
    dest.write(ical)
    dest.close()
455
456
457
458
459
    f2=open("test_5510L3MIASHS1delta_moins11.ics")
    ical=f2.read()
    ical=fix_timezone(ical)
    f2.close()
    print("Test L3 MIASHS GR1 semaine du 15/11/21")
460
    dest2=open("data/5510.ics","w")
461
462
    dest2.write(ical)
    dest2.close()
463
464


Frederic HAN's avatar
Frederic HAN committed
465
466
467
468
469
470
471
def calendar_list(fname = 'formations.json'):
    """
    Le fichier formation contient le code d'une formation annuelle et de ses sous fiches ADE.
    On ajoute au calendrier de la sous fiche celui de la formation parente car certains evenements
    peuvent y etre saisis.
    """
    rep=[]
Pascal's avatar
Pascal committed
472
    with open(fname) as cfile:
Frederic HAN's avatar
Frederic HAN committed
473
474
475
476
477
478
479
480
481
        for c in json.load(cfile):
            fiches=(c["fiches"]).split(',')
            for j in fiches:
                if j !="":
                    rep.append(str(c['code'])+','+j)
                else:
                    rep.append(str(c['code']))

        return rep
Pascal's avatar
Pascal committed
482

483
def get_calendars(formations, outdir,
Frederic HAN's avatar
Frederic HAN committed
484
                  start='2022-08-23',end='2023-07-17',year=10,fiche_etalon='',
Pascal's avatar
Pascal committed
485
                  dryrun=False, verbose=False):
Pascal's avatar
Pascal committed
486

Pascal's avatar
Pascal committed
487
    for code in calendar_list(formations):
Pascal's avatar
Pascal committed
488

Frederic HAN's avatar
Frederic HAN committed
489
490
491
        #on prend le nom de la derniere fiche
        nomfiche = code.split(',')[-1]
        fname = '%s/%s.ics'%(outdir,nomfiche)
Pascal's avatar
Pascal committed
492
        if verbose:
Pascal's avatar
Pascal committed
493
            print('[FETCH] %s -> %s'%(code,fname))
Pascal's avatar
Pascal committed
494
        calendar = get_ical(code,start,end,year,fiche_etalon)
Pascal's avatar
Pascal committed
495
496
497
498
        # check we actually got a calendar
        if not calendar.startswith('BEGIN:VCALENDAR'):
            print('[ERROR] %s -> no ressource %s'%(code,calendar[:20]))
            # keep old file
499
            continue
500
        calendar = fix_timezone(calendar,nomfiche)
501
502
503
504
505
506
507
508
509
510
        # Ajout de sous calendriers par ECUE dans certaines formations
        if nomfiche in filtreECUE:
            subcalendars = mkfiltreECUE(nomfiche, calendar, filtreECUE[nomfiche])
            for sc in subcalendars:
                fnamesub = "%s/%s.ics"%(outdir,sc)
                tmp = subcalendars[sc]
                f = open(fnamesub,'w')
                f.write(tmp)
                f.close()

511
512
513
        # trop de requettes peuvent donner une erreur ??
        #requests.exceptions.ConnectionError: HTTPSConnectionPool(host='adeprod.app.univ-paris-diderot.fr', port=443): Max retries exceeded with url: /jsp/custom/modules/plannings/anonymous_cal.jsp?calType=ical&firstDate=2021-08-23&lastDate=2021-12-19&resources=2769%2C6593%2C6602&projectId=4 (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f87c1e449d0>: Failed to establish a new connection: [Errno -2] Name or service not known'))

514
        time.sleep(0.8) # 0.5 etait meme court?
Pascal's avatar
Pascal committed
515
        if calendar is None:
516
            print("PB calendrier %s vide"%code)
Pascal's avatar
Pascal committed
517
518
519
520
521
522
523
524
525
            return
        if dryrun:
            print(calendar[:300])
        else:
            with open(fname,'w') as f:
                f.write( calendar )
        if verbose:
            print('[DONE]')

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
def mkfiltreECUE(code,ical,filtre):
    """
    Une fonction pour filtrer et creer des sous calendriers à partir d'un calendrier.
    code: le code du calendrier d'origine, ie num fiche ADE
    calendrier: le calendrier apres corrections
    filtre: un dictionnaire de filtres: code ECUE/ expression reg
    """
    subcals = {}
    debutcal =  "BEGIN:VCALENDAR\n"
    debutcal += "METHOD:REQUEST\n"
    debutcal += "PRODID:-//ADE/version 6.0\n"
    debutcal += "VERSION:2.0\n"
    debutcal += "CALSCALE:GREGORIAN"
    fincal = "\nEND:VCALENDAR"
    for ecue in filtre:
        tmp = str(code)+"."+ ecue
        subcals[tmp]= debutcal
    revevent=re.compile('BEGIN:VEVENT.*?END:VEVENT',re.DOTALL)
    rechercheUE =[ re.compile(filtre[ecue],re.DOTALL) for ecue in filtre]
    Levent=revevent.finditer(ical)

    for mevent in Levent:
        event = mevent.group(0)
549
        event = event.replace('\r','')
550
551
552
553
554
555
556
557
558
559
560
        for i in range(len(filtre)):
            s=rechercheUE[i]
            ecue=list(filtre)[i]
            if s.match(event) != None:
                subcals[str(code)+"."+ecue] += "\n"+event

    for sc in subcals:
        subcals[sc] += fincal

    return subcals

561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576

def get_external(fcalen, outdir,
        dryrun=False, verbose=False):
    """
    download external (non ADE) ressources
    fcalen must be a list of ::

      { 'id' : xxx, 'url' : www, ... }

    """
    def curl(url):
        r = requests.get(url)
        return r.text

    with open(fcalen) as calenfile:
        for c in json.load(calenfile):
Pascal's avatar
Pascal committed
577
            fname = '%s/%s.ics'%(outdir,c['id'])
578
579
580
581
582
583
584
            cal = curl(c['url'])
            if dryrun:
                print(cal[:200])
            else:
                with open(fname,'w') as f:
                    f.write(cal)
            if verbose:
Pascal's avatar
Pascal committed
585
                print('[DONE %s]'%fname)
586
587


Frederic HAN's avatar
Frederic HAN committed
588
def main(start, end, year=10, fiche_etalon='1607,',
589
590
591
        formations='formations.json',
        calendars='calendars.json',
        external='calendars_external.json',
592
        outdir='data',
593
        oldoutdir='data.28',
594
595
596
597
        dryrun=False, verbose=False):

    if formations:
        get_calendars(formations, outdir,
Pascal's avatar
Pascal committed
598
                start, end, year, fiche_etalon,
599
600
601
                dryrun=dryrun, verbose=verbose)

    if calendars:
602
        cherchedeplacements(calendars, outdir, oldoutdir)
603

604
605
606
607
    if external:
        get_external(external, outdir,
                dryrun=dryrun, verbose=verbose)

Pascal's avatar
Pascal committed
608
609
# paramètres ADE ufr maths
presets = {
610
       '2022-23': dict(start="2022-08-23", end="2023-07-17", year=10, fiche_etalon="1607,"),
Pascal's avatar
Pascal committed
611
612
613
614
615
       '2021-22': dict(start="2021-08-23", end="2022-07-17", year=4, fiche_etalon="2769,"),
       '2020-21': dict(start="2020-08-31", end="2021-07-14", year=15),
       '2019-20': dict(start="2019-08-31", end="2020-07-14", year=6),
       }

Pascal's avatar
Pascal committed
616
617
import argparse

Pascal's avatar
Pascal committed
618
parser = argparse.ArgumentParser(description='fetch icalendars from ADE.')
Pascal's avatar
Pascal committed
619
620
621
622
parser.add_argument('--dryrun', '-n', action='store_true',
        help='dry-run, do not write files')
parser.add_argument('--verbose', '-v', action='store_true',
        help='verbose, show progress')
Frederic HAN's avatar
Frederic HAN committed
623
parser.add_argument('--start', type=str, default='2022-08-23',
Pascal's avatar
Pascal committed
624
                    help='first day, format yyyy-mm-dd')
Frederic HAN's avatar
Frederic HAN committed
625
parser.add_argument('--end', type=str, default='2023-07-17',
Pascal's avatar
Pascal committed
626
                    help='last day, format yyyy-mm-dd')
Frederic HAN's avatar
Frederic HAN committed
627
parser.add_argument('--year', type=int, default=10,
Pascal's avatar
Pascal committed
628
                    help='ADE year')
Frederic HAN's avatar
Frederic HAN committed
629
parser.add_argument('--fiche_etalon', type=str, default='1607,',
Pascal's avatar
Pascal committed
630
                    help='fiches à ajouter à chaque récupération')
Pascal's avatar
Pascal committed
631
632
633
634
parser.add_argument('--formations', type=str, default='formations.json',
                    help='json file describing calendar views')
parser.add_argument('--calendars', type=str, default='calendars.json',
                    help='json file describing calendar views')
635
parser.add_argument('--outdir', type=str, default='data',
636
637
638
                    help='directory to save ics files'),
parser.add_argument('--oldoutdir', type=str, default='data.28',
                    help='directory to save old ics files for diff button')
639
640
parser.add_argument('--external', type=str, default='calendars_external.json',
                    help='json file listing external (non ADE) ressources')
Pascal's avatar
Pascal committed
641
parser.add_argument('--presets', choices=presets.keys(), help='automatic choice of --start, --end, --outdir options')
Pascal's avatar
Pascal committed
642
643
644

if __name__ == '__main__':
    args = parser.parse_args()
Pascal's avatar
Pascal committed
645
646
    if args.presets:
        args.outdir = 'data/%s'%args.presets
647
        args.oldoutdir = 'data.28/%s'%args.presets
Pascal's avatar
Pascal committed
648
649
        args.__dict__.update(presets[args.presets])
    main(args.start, args.end, args.year, args.fiche_etalon,
Pascal's avatar
Pascal committed
650
651
            formations=args.formations,
            calendars=args.calendars,
652
            external=args.external,
Pascal's avatar
Pascal committed
653
654
655
            outdir=args.outdir,
            dryrun=args.dryrun,
            verbose=args.verbose)