#!/usr/bin/env python3 import configparser import argparse import requests from sys import exit import logging import coloredlogs import json import re from io import StringIO import getpass import os from bs4 import BeautifulSoup from zoneinfo import ZoneInfo from datetime import datetime from math import floor from ics import Calendar, Event def getKey(dictionnary, key): logger = logging.getLogger(__name__) try: return dictionnary[key] except: logger.error('Missing key: %s' % key) exit(-1) def authenticate(baseURL, city, login, password): logger = logging.getLogger(__name__) url = baseURL + '/%s/espace-citoyens' % city logger.info('Retrieve base site') html = requests.get(url, allow_redirects=False) if html.status_code != 200: logger.error('Impossible to retrieve Web site: %d' % html.status_code) exit(-1) cookies = html.cookies found = False p = re.compile('^.*
[0-9]+).*$' % var) for line in content.readlines(): m = p.match(line) if m != None: found = True value = int(m.group('value')) values[var] = value break if not found: logger.error('Impossible to find value for variable: %s' % var) exit(-1) else: logger.debug('Found value for var %s: %d' % (var, value)) # This URL should be retrieved more automatically ... calendar = requests.get(baseURL+'/%s/espace-citoyens/DemandeEnfance/NouvelleDemandeReservationGetCalendrier' % city, params=values, cookies=cookies) if calendar.status_code != 200: logger.info('Impossible to retrieve calendar: %d' % html.status_code) exit(-1) calendar = json.load(StringIO(calendar.content.decode('utf8'))) weeks = getKey(calendar, 'listeSemainesAffichees') typeResas = getKey(calendar, 'listeUnitesInscr') dictResa = {} for typeResa in typeResas: idResa = getKey(typeResa, 'idUnite') codeResa = getKey(typeResa, 'codeUnite') descResa = getKey(typeResa, 'libUnite') dictResa[idResa] = (descResa, codeResa) cal = Calendar() for week in weeks: numSemaine = int(getKey(week, 'numSemaine')) days = getKey(week, 'listeJoursAffiches') for day in days: resas = getKey(day, 'listeUnitesJour') date = int(getKey(day, 'idJour')) year = int(date/10000) month = int((date - year*10000) / 100) day = date - year*10000 - month*100 for resa in resas: checked = getKey(resa, 'nbConsoBase') != None if checked: typeResa = getKey(resa, 'idUnite') e = Event() e.name = dictResa[typeResa][0] code = dictResa[typeResa][1] if 'Matin' in code: begin = datetime(year,month,day,7,30,0, tzinfo=ZoneInfo("Europe/Paris")) end = datetime(year,month,day,12,0,0, tzinfo=ZoneInfo("Europe/Paris")) elif 'AM' in code: begin = datetime(year,month,day,13,30,0, tzinfo=ZoneInfo("Europe/Paris")) end = datetime(year,month,day,18,0,0, tzinfo=ZoneInfo("Europe/Paris")) elif 'Repas' in code: begin = datetime(year,month,day,12,00,0, tzinfo=ZoneInfo("Europe/Paris")) end = datetime(year,month,day,13,30,0, tzinfo=ZoneInfo("Europe/Paris")) else: logger.warning('Impossible to determine the type of reservation: %s' % code) begin = datetime(year,month,day) end = datetime(year,month,day) e.begin = begin e.end = end cal.events.add(e) with open(output, 'w') as f: f.writelines(cal.serialize_iter()) def main(): logger = logging.getLogger(__name__) coloredlogs.install() parser = argparse.ArgumentParser() parser.add_argument("-l", "--login", dest='login', type=str, required=False, help="Login") parser.add_argument("-C", "--city", dest='city', type=str, required=False, help="City.") parser.add_argument("-c", "--config", dest='configFileName', required=False, default=None, help="Configuration file.") parser.add_argument("-p", "--password", dest='password', nargs='?', required=False, default=None, help="Password.") parser.add_argument("-d", "--debug", dest='debug', action='store_true', required=False, help="Activate debug.") subparsers = parser.add_subparsers(title='subcommands', dest='command', help='subcommand help') parserauth = subparsers.add_parser('A', help='Test authentication.') parserlist = subparsers.add_parser('L', help='List all possible reservations types.') parserdump = subparsers.add_parser('D', help='Dump all reservation of some kind') parserdump.add_argument("-i", "--index", dest='index', type=int, required=True, help="Index of reservations to dump.") parserdump.add_argument("-o", "--output", dest='calendar', required=True, default='cal.ics', help="Output calendar file.") args = parser.parse_args() logger.debug("Initial arguments: %s" % args) if args.debug: logger.info('Setting logging to debug mode') coloredlogs.set_level(level=logging.DEBUG) if args.configFileName != None: try: configFile = open(args.configFileName, 'r') except Exception as e: logger.info('Impossible to open configuration file. Error: %s' % e) exit(-1) config = configparser.ConfigParser() config.read_file(configFile) sections = config.sections() if 'Login' in sections: options = config.options('Login') if 'login' in options: args.login = config.get('Login','login') if 'password' in options: args.password = config.get('Login','password') if 'Espace' in sections: options = config.options('Espace') if 'ville' in options: args.city = config.get('Espace', 'ville') logger.debug("Final arguments: %s" % args) if args.city == None: logger.error('City must be provided.') parser.print_help() exit(-1) if args.login == None: logger.error('Login must be provided.') parser.print_help() exit(-1) if args.password == None: args.password = getpass.getpass() baseURL = 'https://www.espace-citoyens.net' # Authentication mainpage, cookies = authenticate(baseURL, args.city, args.login, args.password) if (args.command == None) or (args.command == 'A'): exit(0) # Switch between commands logger.info('Retrieve reservation kinds') resaTypes = getReservationsKind(baseURL, mainpage, cookies) if (args.command == 'L'): resaNum = 1 for resaType in resaTypes.keys(): print("%d - %s" % (resaNum, resaType)) resaNum+=1 elif (args.command == 'D'): resaType = list(resaTypes)[args.index-1] logger.info('Retrieve reservations for "%s"' % resaType) url = resaTypes[resaType] dumpReservation(resaType, baseURL, args.city, url, cookies, args.calendar) if __name__ == "__main__": main()