Commit initial d'une première version fonctionnelle du script permettant de récupérer les dates des inscriptions pour Betton.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
venv/
|
||||
*.ics
|
||||
234
betton.py
Executable file
234
betton.py
Executable file
@@ -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('^.*<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()
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
requests
|
||||
coloredlogs
|
||||
ics
|
||||
Reference in New Issue
Block a user