From 68cc5fba81631a2e3752b4522f7aca5f8f995695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Tronel?= Date: Wed, 27 Aug 2025 11:39:06 +0200 Subject: [PATCH] =?UTF-8?q?Commit=20initial=20d'une=20premi=C3=A8re=20vers?= =?UTF-8?q?ion=20fonctionnelle=20du=20script=20permettant=20de=20r=C3=A9cu?= =?UTF-8?q?p=C3=A9rer=20les=20dates=20des=20inscriptions=20pour=20Betton.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + betton.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + 3 files changed, 239 insertions(+) create mode 100644 .gitignore create mode 100755 betton.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f84e71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv/ +*.ics diff --git a/betton.py b/betton.py new file mode 100755 index 0000000..02917af --- /dev/null +++ b/betton.py @@ -0,0 +1,234 @@ +#!/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 zoneinfo import ZoneInfo +from datetime import datetime +from math import floor +from ics import Calendar, Event + +def getKey(dictionnary, key): + try: + return dictionnary[key] + except: + logger.error('Missing key: %s' % key) + exit(-1) + + +def main(): + logger = logging.getLogger(__name__) + coloredlogs.install() + parser = argparse.ArgumentParser() + parser.add_argument("-l", "--login", dest='login', type=str, required=False, help="Username or login (usually a phone number with international prefix).") + 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("-o", "--output", dest='calendar', required=True, default='cal.ics', help="Output calendar file.") + parser.add_argument("-d", "--debug", dest='debug', action='store_true', required=False, help="Activate debug.") + args = parser.parse_args() + + logger.info("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') + + logger.info("Final arguments: %s" % args) + + if args.login == None: + logger.info('Login must be provided.') + parser.print_help() + exit(-1) + + if args.password == None: + args.password = getpass.getpass() + + # Récupération des cookies de base + baseURL = 'https://www.espace-citoyens.net' + url = baseURL + '/betton/espace-citoyens' + + logger.info('Retrieve base site') + html = requests.get(url, allow_redirects=False) + if html.status_code != 200: + logger.info('Impossible to retrieve Web site: %d' % html.status_code) + + cookies = html.cookies + + found = False + p = re.compile('^.*
Accueil de loisirs mercredi<.*$') + content = StringIO(html.content.decode('utf8')) + + for line in content.readlines(): + m = p.match(line) + if m != None: + if not foundCategory: + # If we found the kind of reservation we change the regexp + p = re.compile('^.*href="(?P[^"]+)".*fleche.png".*$') + foundCategory = True + else: + found = True + resa = baseURL+m.group('url') + break + + if found: + logger.debug("Found mercredi: %s" % resa) + else: + logger.error("Impossible to find accueil du mercredi") + exit(-1) + + + html = requests.get(resa, cookies=cookies, allow_redirects=False) + if html.status_code != 200: + logger.info('Impossible to retrieve reservation page: %d' % html.status_code) + + variables = ['idPer', 'idIns', 'idLie', 'idClg'] + values = {} + for var in variables: + found = False + content = StringIO(html.content.decode('utf8')) + p = re.compile('^.*var %s = (?P[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)) + + calendar = requests.get(baseURL+'/betton/espace-citoyens/DemandeEnfance/NouvelleDemandeReservationGetCalendrier', params=values, cookies=cookies) + if calendar.status_code != 200: + logger.info('Impossible to retrieve calendar: %d' % html.status_code) + + 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.error('Impossible to determine the type of reservation: %s' % code) + exit(-1) + e.begin = begin + e.end = end + cal.events.add(e) + + with open(args.calendar, 'w') as f: + f.writelines(cal.serialize_iter()) + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..710d9f5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests +coloredlogs +ics