Files
Espace-citoyen/betton.py

235 lines
8.3 KiB
Python
Executable File

#!/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('^.*<form action="(?P<url>[^"]+)".*method="post".*$')
content = StringIO(html.content.decode('utf8'))
for line in content.readlines():
m = p.match(line)
if m != None:
found = True
logonURL = baseURL+m.group('url')
break
if not found:
logger.error('Impossible to retrieve logon URL')
exit(-1)
logger.debug('Found logon: %s' % (logonURL))
found = False
p = re.compile('^.*name="__RequestVerificationToken" type="hidden" value="(?P<token>[^"]+)".*$')
content = StringIO(html.content.decode('utf8'))
for line in content.readlines():
m = p.match(line)
if m != None:
found = True
token = m.group('token')
break
if not found:
logger.error('Impossible to retrieve verification token')
exit(-1)
logger.debug('Found token: %s' % (token))
payload = { 'username':args.login, 'password':args.password, '__RequestVerificationToken':token }
auth = requests.post(logonURL, data=payload, cookies=cookies, allow_redirects=False)
if auth.status_code != 302:
logger.info('Impossible to login: %d' % html.status_code)
else:
logger.info('Authentication successful')
mainpage = baseURL+auth.headers['Location']
html = requests.get(mainpage, cookies=cookies, allow_redirects=False)
if html.status_code != 200:
logger.info('Impossible to retrieve main page: %d' % html.status_code)
foundCategory = False
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<url>[^"]+)".*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<value>[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()