├── LICENSE ├── README.md ├── .gitignore └── API_EcoleDirecte.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Raphaël Merlet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EcoleDirecte_API-Interface 2 | Une interface python permettant de récupérer simplement des informations de l'API (devoirs, notes,...) 3 | (voir documentation et code pour plus d'informations) 4 | 5 | # Info : n'ayant plus accès à mon compte école directe (changement d'établissement), ce projet va certainement rester tel quel, ainsi, d'éventuels mise à jour de l'api ne pourront probablement pas être pris en compte 6 | 7 | # Fonctionnement : 8 | ## Login : 9 | Pour commencer, vous devrez vous connecter à votre compte en utilisant votre nom d'utilisateur ainsi que votre mot de passe. 10 | 11 | Pour cela, vous devrez initialiser la classe EcoleDirecte comme ceci : 12 | ```py 13 | = EcoleDirecte(username: str, password: str (, save_json: bool)) 14 | ``` 15 | Si la connexion est aboutie, vous pouvez ensuite vérifier quelques informations de connexion telles que : 16 | ```py 17 | TokenDeConnexion = .token 18 | ReponseHTTPConnexion = .response 19 | Username = .response["data"]["accounts"][0]["identifiant"] 20 | ``` 21 | ## Méthodes disponibles : 22 | ```py 23 | .fetch_schedule() -> dict[str, list[dict]] # l'emploi du temps de cette semaine 24 | .fetch_work((raw_data: bool)) -> list[dict] # liste des devoirs à faire 25 | .fetch_grades() -> dict[matiere: str, dict] # dictionnaire des notes par période et par matière 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # custom ignore 132 | .vscode/ 133 | login.json 134 | -------------------------------------------------------------------------------- /API_EcoleDirecte.py: -------------------------------------------------------------------------------- 1 | """ 2 | API python qui permet de récupérer plusieurs informations d'école directe 3 | au moyen de requêtes http à l'API interne du site. 4 | 5 | Si vous avez besoin d'aide ou trouvé un bug, n'hésitez pas à me le faire savoir 6 | 7 | INFORMATIONS DISPONIBLES : 8 | ------------------- 9 | - emploi du temps 10 | - devoirs 11 | - notes (en cours) 12 | - contenus de séance (à faire) 13 | 14 | FONCTIONNEMENT 15 | ------------------- 16 | - 1 : récupération du token par l'authentification à école directe avec l'identifiant et le mot de passe 17 | - par l'init de la classe EcoleDirecte(username, password) 18 | """ 19 | 20 | __version__ = "0.2" 21 | __author__ = "Merlet Raphaël" 22 | 23 | from getpass import getpass # input pour mot de passe sécurisé 24 | import json 25 | import requests as req # module pour requêtes HTTP 26 | from rich import print # print permettant de mieux voir les réponses json (dictionnaires) 27 | from datetime import date # pour créer dates formatées 28 | # modules pour décodage base64 en string 29 | import base64 30 | import html 31 | import re 32 | 33 | def create_week_list() -> list[str]: 34 | """ 35 | retourne une liste des jour de la semaine actuelle au format AAAA-MM-JJ 36 | """ 37 | # nombre de jour par mois (année non bissextile, dans l'ordre de janvier à décembre) 38 | JMOIS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 39 | CDATE = date.today() 40 | offset_from_monday = CDATE.weekday() 41 | # find important number of day in the month (actual month or precedent month) 42 | jour_dans_mois = JMOIS[CDATE.month-1] if offset_from_monday < CDATE.day else JMOIS[CDATE.month-2] 43 | # create week days list 44 | week = [ 45 | '{2}-{1}-{0}'.format( 46 | (CDATE.day + i) % jour_dans_mois + 1, 47 | ((CDATE.month + 48 | ((CDATE.day + i) // jour_dans_mois)) % 12 49 | if (CDATE.month + ((CDATE.day + i) // jour_dans_mois)) != 12 else 12), 50 | CDATE.year + 51 | (((CDATE.month + ((CDATE.day + i) // jour_dans_mois)) // 12 52 | if (CDATE.month + ((CDATE.day + i) // jour_dans_mois)) != 12 else 0))) 53 | for i in range(-(offset_from_monday+1), 6-offset_from_monday) 54 | ] 55 | # fills zeroes for day and month 56 | week = [ 57 | '{}-{}-{}'.format(*[ 58 | part.zfill(2) for part in day.split('-') 59 | ]) 60 | for day in week 61 | ] 62 | print(week) 63 | return week 64 | 65 | class EcoleDirecte(): 66 | """ 67 | Interface API d'EcoleDirecte 68 | """ 69 | def __init__(self, username: str, password: str, save_json=False) -> None: 70 | """ 71 | Permet de se connecter à école directe afin de récupérer le token, nécessaire afin de récupérer les infos de l'API 72 | ---------- 73 | 74 | Envoie une requête HTTP à école directe afin de s'authentifier et récupérer le token. 75 | 76 | PARAMETRES : 77 | ---------- 78 | - username : str 79 | - nom d'utilisateur/de compte école directe 80 | - password : str 81 | - mot de passe 82 | - save_json : bool 83 | - si True, sauvegardera la réponse du login dans login.json (non nécessaire) 84 | - default = False 85 | 86 | SORTIE : 87 | ---------- 88 | - Aucune 89 | """ 90 | # Création payload 91 | data = { 92 | "identifiant": username, 93 | "motdepasse": password, 94 | "acceptationCharte": True 95 | } 96 | payload = 'data=' + json.dumps(data) 97 | # essai de requête au login d'école directe 98 | try: 99 | self.response = req.post( 100 | "https://api.ecoledirecte.com/v3/login.awp", 101 | data=payload) 102 | self.json = self.response.json() 103 | if save_json: 104 | with open("login.json", "w+", encoding="UTF-8") as file: 105 | json.dump(self.json, file) 106 | 107 | self.response.raise_for_status() 108 | self.token = self.json['token'] 109 | self.id = self.json["data"]["accounts"][0]["id"] 110 | except Exception as e: 111 | if type(e).__name__ == "ConnectionError": 112 | print("[reverse bold red]La connexion a échoué[/]") 113 | print("[red]Vérifiez votre connexion Internet.[/]") 114 | else: 115 | print(f"Une erreur inconnue est survenue (identifiant ou mot de passe incorrect ?) : {e}") 116 | exit() 117 | 118 | def fetch_schedule(self) -> dict[str, list[dict]]: 119 | """ 120 | Retourne l'emploi du temps de la semaine sous la forme d'un dictionnaire au format {jour: listeCours[Cours]} 121 | pour chaque jour, la liste des cours sera triée dans l'ordre (du plus tôt au plus tard) 122 | 123 | PARAMETRES : 124 | ---------- 125 | Aucun 126 | 127 | SORTIE : 128 | ---------- 129 | - schedule : dict[str, list[dict]] 130 | - dictionnaire des jours de la semaine, avec la liste des cours de chaque jour dans l'ordre 131 | """ 132 | week = create_week_list() 133 | data = { # payload for request 134 | "dateDebut": week[0], # monday 135 | "dateFin": week[-1], # sunday 136 | "avecTrous": False, 137 | } 138 | payload = 'data=' + json.dumps(data) 139 | response = req.post("https://api.ecoledirecte.com/v3/E/" + 140 | str(self.id) + "/emploidutemps.awp?verbe=get&", data=payload, headers=self.header) 141 | responseJson = response.json() 142 | self.token = responseJson["token"] 143 | coursList = responseJson['data'] 144 | # create schedule dictionnary 145 | def get_key(course): 146 | hour, minutes = course['start_date'].split(' ')[1].split(':') 147 | value = int(hour) * 60 + int(minutes) 148 | return value 149 | 150 | schedule = {day: sorted([course for course in coursList if day in course['start_date']], key=get_key) 151 | for day in week 152 | } 153 | 154 | return schedule 155 | 156 | def fetch_work(self, raw_data=False) -> list[dict]: 157 | """ 158 | Retourne la liste des devoirs donnés, sous la forme de dictionnaires formatés/simplifiés ou non. 159 | 160 | Peut prendre quelques secondes à s'exécuter (décodage de base64 assez long) 161 | 162 | PARAMETRES : 163 | ---------- 164 | - raw_data : bool 165 | - si vrai, les dictionnaires des devoirs continendront toutes les informations reçues par la réponse de l'API + la description 166 | - sinon, les devoirs seront simplifiés : 'matiere', 'effectue', 'date' et 'description' uniquement 167 | 168 | SORTIE : 169 | ---------- 170 | - work : list[dict[str, str]] 171 | - liste des devoirs à faire 172 | 173 | """ 174 | def format_data(data): 175 | """Format the tasks and adds it their description 176 | task format : raw if raw_data flag is set else dict[matiere, effectue, date; description]""" 177 | tasks = [] 178 | for date in data.keys(): # each date, which is the key for the works for each day 179 | # send a request for the day to get the description for each work 180 | rtask = req.post("https://api.ecoledirecte.com/v3/Eleves/" + 181 | str(self.id) + f"/cahierdetexte/{date}.awp?verbe=get&", data=payload, headers=self.header).json() 182 | devoirs = rtask["data"]["matieres"] 183 | # Sort the response to keep only the work and nothing else 184 | devoirs = [task for task in devoirs if "aFaire" in task.keys()] 185 | for task in devoirs: # each work of the day 186 | # get the description 187 | a = base64.b64decode(task["aFaire"]["contenu"]) 188 | descriptionHTML = a.decode("UTF-8") 189 | description = html.unescape(descriptionHTML) 190 | # Conversion Regex en string normale 191 | desc = re.sub(r"<\/?[a-z]+>|\n", "", description) 192 | # add the task to the list 193 | if raw_data: 194 | tasks.append({ 195 | **task, 196 | 'description': desc 197 | }) 198 | else: 199 | tasks.append({ 200 | 'matiere': task['matiere'], 201 | 'effectue': task['aFaire']['effectue'], 202 | 'date': date, 203 | 'description': desc, 204 | }) 205 | 206 | return tasks 207 | 208 | payload = 'data={}' 209 | rep = req.post("https://api.ecoledirecte.com/v3/Eleves/" + 210 | str(self.id) + "/cahierdetexte.awp?verbe=get&", data=payload, headers=self.header) 211 | response = rep.json() 212 | self.token = response["token"] 213 | return format_data(response["data"]) 214 | 215 | def fetch_grades(self, raw_data=False) -> dict[str]: 216 | """ 217 | Renvoie un dictionnaire des notes, triées par période (semestre...), puis par matière (+ moyenne générale par période et par matière) 218 | 219 | PARAMETRE : 220 | - raw_data : bool 221 | - si vrai, renverra la réponse entière pour chaque note, sinon chaque objet-note contiendra uniquement : 222 | - le nom 223 | - la note obtenue et son coefficient 224 | - la moyenne et la note minimale et maximale de la classe 225 | - default = False 226 | 227 | SORTIE : 228 | - Notes : 229 | - dict 230 | - Période(str) : Matiere[ 231 | - average: float 232 | - class_avg : float 233 | - class_max : float 234 | - class_min : float 235 | - codeMatiere : dict 236 | - sousMatiere: 237 | - notes: list[dict[raw_dict ou 238 | - name: str, 239 | - grade: float, 240 | - coef: float, 241 | - class_avg: float, 242 | - class_min: float, 243 | - class_max: float 244 | """ 245 | payload = 'data={}' 246 | rep = req.post(f"https://api.ecoledirecte.com/v3/Eleves/" + 247 | str(self.id) + "/notes.awp?verbe=get", data=payload, headers=self.header) 248 | response = rep.json() 249 | self.token = response["token"] 250 | data = response['data'] 251 | 252 | code_mats = list(set([g['codeMatiere'] for g in data['notes']])) 253 | matieres = { 254 | code: [ 255 | g['libelleMatiere'] 256 | for g in data['notes'] 257 | if g['codeMatiere'] == code 258 | ] 259 | for code in code_mats 260 | } 261 | 262 | 263 | def format_grade(grade: dict) -> dict: 264 | """to format grade if asked to do so, with name, grade, coef, and class avg/min/max""" 265 | return grade if raw_data else { 266 | 'name': grade['devoir'], 267 | 'grade': grade['valeur'], 268 | 'coef': grade['coef'], 269 | 'class_avg': grade['moyenneClasse'], 270 | 'class_min': grade['minClasse'], 271 | 'class_max': grade['maxClasse'] 272 | } 273 | 274 | notes = {} 275 | for p in data['periodes']: 276 | namep = p['periode'] 277 | notes[namep] = {} 278 | for code, names in matieres.items(): 279 | notes[namep][code] = { 280 | matiere: [ 281 | format_grade(grade) 282 | for grade in data['notes'] 283 | if grade['codePeriode'] == p['codePeriode'] 284 | and grade['codeMatiere'] == code 285 | and grade['libelleMatiere'] == matiere 286 | ] 287 | for matiere in names 288 | } 289 | notes[namep]['average'] = p['ensembleMatieres']['moyenneGenerale'] 290 | notes[namep]['class_avg'] = p['ensembleMatieres']['moyenneClasse'] 291 | notes[namep]['class_max'] = p['ensembleMatieres']['moyenneMax'] 292 | notes[namep]['class_min'] = p['ensembleMatieres']['moyenneMin'] 293 | 294 | return notes 295 | 296 | @property 297 | def header(self): 298 | return {'X-Token': self.token} 299 | 300 | if __name__=='__main__': # test 301 | print("===============================================================") 302 | print(f"EcoleDirecte API v{__version__}") 303 | print(f"Made by {__author__}") 304 | print("Source : https://github.com/Ilade-s/EcoleDirecte_API-Interface") 305 | print("===============================================================") 306 | 307 | username = input("username/nom d'utilisateur : ") 308 | password = getpass("password/mot de passe : ", ) 309 | interface = EcoleDirecte(username, password) 310 | 311 | print("Connecté à :", interface.json["data"]["accounts"][0]["identifiant"]) 312 | """ 313 | print("fetching schedule...") 314 | 315 | schedule = interface.fetch_schedule() 316 | 317 | print(schedule) 318 | 319 | print('fetching work...') 320 | 321 | work = interface.fetch_work() 322 | 323 | print(work) 324 | """ 325 | notes = interface.fetch_grades() 326 | 327 | print(notes) 328 | --------------------------------------------------------------------------------