main.py 8.36 KB
Newer Older
1 2 3 4
#!/usr/bin/python3
#-*- mode:python3 -*-

"""
Maxime Bombar's avatar
Maxime Bombar committed
5
Dependencies :
6 7 8 9
   - BeautifulSoup
   - icalendar
"""

10
import sys
11 12 13

import bs4 as BeautifulSoup
import urllib3
Maxime Bombar's avatar
Maxime Bombar committed
14

15 16
import datetime

Maxime Bombar's avatar
Maxime Bombar committed
17
from icalendar import Calendar, Event
18

19
import re
20 21 22

import argparse

Maxime Bombar's avatar
Maxime Bombar committed
23 24
import uuid

25 26 27 28 29 30
# today's week beginning date
today = datetime.datetime.strptime(
    datetime.datetime.now().strftime('%y-W%W') + '-1', "%y-W%W-%w"
).strftime('%y-%m-%d')


Maxime Bombar's avatar
Maxime Bombar committed
31 32 33 34 35 36
def new_calendar():
    c = Calendar()
    c.add('prodid', '-//Maxime Bombar//Agreg Maths ENS Cachan//FR') # RFC compliance
    c.add('version', '2.0') # RFC compliance
    return c

37 38 39 40 41
def get_schedule(a):
    """
    returns a list of the schedule for the 5 days of the week
    from schedule website
    """
42 43 44 45
    schedule = []
    for day in a:
        schedule.append(day.find_all("big"))
    return schedule
46

47 48 49 50 51 52 53 54 55 56 57
def get_string(tag):
    """ Get string from BeautifulSoup tag, itering on children if there are more than 1"""
    if len(list(tag.children)) > 1:
        s = ''
        for x in tag.children:
            if x.string.replace('\xa0', ''):
                s += x.string
                s += ' '
    else:
        s = tag.string
    return s
58 59

def pretty_schedule(schedule):
60
    """Prettify the schedule"""
61 62 63
    days = []
    for day in schedule:
        cls = []
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
        i = 0
        while i<len(day):
            j = 1
            s = get_string(day[i])
            if s and regex.match(s) or s == 'Problème': # Beginning of a class
                tmp = [day[i]]
                try:
                    while get_string(day[i+j]) and not regex.match(get_string(day[i+j])):
                        if get_string(day[i+j]).replace('\xa0', ''):
                            tmp.append(day[i+j])
                        j+=1
                except IndexError:
                    pass
                cls.append(tmp)
            i+=j
79 80
        days.append(cls)
    return format(days)
81

82

83 84
def format(days):
    """
Maxime Bombar's avatar
Maxime Bombar committed
85
    days: list of list.
86 87 88 89 90 91 92 93
    Each day is a sublist which contains the classes for this day.
    Format everything from BeautifulSoup tags to strings, into a list of list of dict
    """
    pretty_days = []
    for i in range(len(days)):
        day = []
        for cls in days[i]:
            clas = {}
Maxime Bombar's avatar
Maxime Bombar committed
94
            try:
95
                clas['time'] = get_string(cls[0])
Maxime Bombar's avatar
Maxime Bombar committed
96 97 98
            except:
                pass
            try:
99
                clas['type'] = get_string(cls[1])
Maxime Bombar's avatar
Maxime Bombar committed
100 101 102
            except:
                pass
            try:
103
                clas['topic'] = get_string(cls[2])
Maxime Bombar's avatar
Maxime Bombar committed
104 105 106
            except:
                pass
            try:
107
                clas['prof'] = get_string(cls[3])
Maxime Bombar's avatar
Maxime Bombar committed
108 109 110
            except:
                pass
            try:
111
                clas['room'] = get_string(cls[4])
Maxime Bombar's avatar
Maxime Bombar committed
112 113
            except:
                pass
114 115 116 117 118
            for i in range(len(cls[5:])):
                try:
                    clas['title_%s' % i] = get_string(cls[5:][i]).replace('č', 'è')
                except:
                    pass
119 120 121 122 123 124 125 126 127
            day.append(clas)
        pretty_days.append(day)
    return pretty_days


def pretty_print(days, date):
    """
    Display the week schedule
    """
Maxime Bombar's avatar
Maxime Bombar committed
128
    for i in range(len(days)):
129 130 131
        day_date = datetime.datetime.strptime(
            datetime.datetime.strptime(
                date, '%y-%m-%d'
Maxime Bombar's avatar
Maxime Bombar committed
132
            ).strftime('%y-W%W') + '-%s' % (i%6+1), '%y-W%W-%w'
133
        ).strftime("%A %B %d, %Y")
Maxime Bombar's avatar
Maxime Bombar committed
134

135 136 137
        print("----------------------------------")
        print("   %s:" % day_date)
        print("----------------------------------")
Maxime Bombar's avatar
Maxime Bombar committed
138
        for clas in days[i]:
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
            try:
                print(clas['time'])
            except KeyError:
                pass
            try:
                print(clas['type'])
            except KeyError:
                pass
            try:
                print(clas['topic'])
            except KeyError:
                pass
            try:
                print(clas['prof'])
            except KeyError:
                pass
            try:
                print(clas['room'])
            except KeyError:
                pass
            try:
                for cat in clas:
                    if 'title' in cat:
                        print(clas[cat])
            except KeyError:
                pass
165 166 167
            print("\n")
        print('\n')

Maxime Bombar's avatar
Maxime Bombar committed
168 169 170 171 172 173 174 175 176
def get_time(clas):
    time = clas['time']
    l = re.findall(r"[\w']+", time)
    return l[0], l[1]

def fill_calendar(c, days, date):
    """
    Fill calendar with events for each class
    """
Maxime Bombar's avatar
Maxime Bombar committed
177
    for i in range(len(days)):
Maxime Bombar's avatar
Maxime Bombar committed
178 179 180
        day_date = datetime.datetime.strptime(
            datetime.datetime.strptime(
                date, '%y-%m-%d'
Maxime Bombar's avatar
Maxime Bombar committed
181
            ).strftime('%y-W%W') + '-%s' % (i%6+1), '%y-W%W-%w'
Maxime Bombar's avatar
Maxime Bombar committed
182
        )
Maxime Bombar's avatar
Maxime Bombar committed
183 184


Maxime Bombar's avatar
Maxime Bombar committed
185
        for clas in days[i]:
Maxime Bombar's avatar
Maxime Bombar committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
            e = Event()

            start, end = get_time(clas)

            try:
                h_start = int(start.split('h')[0])
            except:
                h_start = 0
            try:
                m_start = int(start.split('h')[1])
            except:
                m_start = 0
            try:
                h_end = int(end.split('h')[0])
            except:
                h_end = 0
            try:
                m_end = int(end.split('h')[1])
            except:
                m_end = 0

Maxime Bombar's avatar
Maxime Bombar committed
207 208 209 210
            try:
                e.add('location', clas['room'])
            except:
                pass
Maxime Bombar's avatar
Maxime Bombar committed
211
            summary = ''
212 213 214 215 216 217
            for cat in clas:
                if cat not in ['time', 'room']:
                    try:
                        summary += clas[cat] + '\n'
                    except:
                        pass
Maxime Bombar's avatar
Maxime Bombar committed
218

Maxime Bombar's avatar
Maxime Bombar committed
219
            e.add('summary', summary)
Maxime Bombar's avatar
Maxime Bombar committed
220 221 222 223 224

            dtstart = day_date.replace(hour=h_start, minute=m_start)
            dtend = day_date.replace(hour=h_end, minute=m_end)
            e.add('dtstart', dtstart)
            e.add('dtend', dtend)
Maxime Bombar's avatar
Maxime Bombar committed
225 226
            e.add('dtstamp', datetime.datetime.now())
            e.add('uid', uuid.uuid1().hex)
Maxime Bombar's avatar
Maxime Bombar committed
227

Maxime Bombar's avatar
Maxime Bombar committed
228
            c.add_component(e)
229

230 231 232 233 234 235 236 237 238 239 240 241

def parse_schedule(request):
    """
    Parse the schedule found in the request
    """
    html = request.data
    soup = BeautifulSoup.BeautifulSoup(html, 'lxml') # Create the soup
    a = soup.find_all('table', cellspacing="0")[2:]
    schedule = get_schedule(a)
    days = pretty_schedule(schedule)
    return days

242 243 244 245
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Fetch calendar from agreg.cmla.ens-cachan.fr', add_help=True)
    parser.add_argument(
        "-d", "--date",
246
        help="Date From in format yy-mm-dd",
247 248 249 250
        action="store",
        default = today
    )

251 252 253 254 255
    parser.add_argument(
        "-p", "--pretty",
        help="Pretty print",
        action="store_true",
        )
256

Maxime Bombar's avatar
Maxime Bombar committed
257 258 259 260 261
    parser.add_argument(
        "-i", "--ics",
        help="export ics",
        action="store_true",
    )
Maxime Bombar's avatar
Maxime Bombar committed
262

Maxime Bombar's avatar
Maxime Bombar committed
263 264 265 266 267 268
    parser.add_argument(
        "-a", "--all",
        help="export the whole year",
        action="store_true",
    )

269 270 271 272 273 274 275 276 277
    parser.add_argument(
        "-o", "--option",
        help="specifies the option. Available are pr, cs, ae and in",
        action="store",
        default = "in",
)


    options = ["pr", "cs", "ae", "in"]
Maxime Bombar's avatar
Maxime Bombar committed
278

279
    args = parser.parse_args()
Maxime Bombar's avatar
Maxime Bombar committed
280

281 282 283 284
    if args.option not in options:
        parser.print_help(sys.stderr)
        sys.exit(42)

285 286 287
    date = datetime.datetime.strptime(
        datetime.datetime.strptime(
            args.date, '%y-%m-%d'
Maxime Bombar's avatar
Maxime Bombar committed
288
        ).strftime('%y-W%W') + '-1', "%y-W%W-%w")
289 290

    http = urllib3.PoolManager()
Maxime Bombar's avatar
Maxime Bombar committed
291

Maxime Bombar's avatar
Maxime Bombar committed
292
    regex = re.compile('[0-9]*h[0-9]*-[0-9]*h[0-9]*') # regex time
Maxime Bombar's avatar
Maxime Bombar committed
293

Maxime Bombar's avatar
Maxime Bombar committed
294 295
    if not args.all:
        date = date.strftime('%y-%m-%d')
296
        request = http.request('GET', 'http://agreg.cmla.ens-cachan.fr/edt.php?option=%(option)s&sem=%(date)s&suff=' % {'option': args.option, 'date': date} )
297

298
        days = parse_schedule(request)
Maxime Bombar's avatar
Maxime Bombar committed
299

Maxime Bombar's avatar
Maxime Bombar committed
300 301
        if args.pretty:
            pretty_print(days, date)
302

Maxime Bombar's avatar
Maxime Bombar committed
303
        else:
Maxime Bombar's avatar
Maxime Bombar committed
304
            c = new_calendar() # RFC Compliance
Maxime Bombar's avatar
Maxime Bombar committed
305 306
            fill_calendar(c, days, date)
            print(c.to_ical().decode('utf-8'))
Maxime Bombar's avatar
Maxime Bombar committed
307

308
    else:
Maxime Bombar's avatar
Maxime Bombar committed
309
        # fetch every week !
Maxime Bombar's avatar
Maxime Bombar committed
310
        c = new_calendar() # RFC Compliance
Maxime Bombar's avatar
Maxime Bombar committed
311 312 313
        while date.month < 7 or date.year == 2018:
            date_str = date.strftime('%y-%m-%d')

314
            request = http.request('GET', 'http://agreg.cmla.ens-cachan.fr/edt.php?option=%(option)s&sem=%(date)s&suff=' % {'option': args.option, 'date': date_str} )
Maxime Bombar's avatar
Maxime Bombar committed
315

316
            days = parse_schedule(request)
Maxime Bombar's avatar
Maxime Bombar committed
317 318 319 320 321

            fill_calendar(c, days, date_str)

            date += datetime.timedelta(weeks=1)

322
        print(c.to_ical().decode('utf-8'))