├── .gitignore ├── LICENSE ├── README.md ├── ecoledirecte.py ├── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | venv 4 | __pycache__ 5 | username.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anatole Debierre 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 | # notes-ed 2 | Ce programme vous permettra de récupérer des informations sur vos notes et de calculer votre moyenne, avec une simple 3 | connexion à EcoleDirecte. 4 | 5 | ## Téléchargement 6 | 7 | ### Pré-requis 8 | Vous devez avoir Python 3 installé ainsi que pip (installé en même temps que Python). 9 | ### Installation 10 | La méthode habituelle. 11 | 12 | ```console 13 | $ git clone https://github.com/a2br/notes-ed.git 14 | $ cd ./notes-ed 15 | ``` 16 | 17 | Une fois le repo installé et une fois que vous êtes dedans, installez les modules requis. 18 | 19 | ```console 20 | $ py -m pip install -r .\requirements.txt 21 | ``` 22 | ### Mettre à jour 23 | Pour mettre votre clone à jour, 24 | ```console 25 | $ git pull 26 | ``` 27 | 28 | ## Utilisation 29 | 30 | Le script ne marche qu'avec les comptes `E` (Eleve). Les comptes famille ne sont pas supportés. Ouvrez le script depuis le terminal ou en cliquant sur l'icône dans le File Explorer. 31 | ```console 32 | $ py ./main.py 33 | ``` 34 | ![Démo](https://i.ibb.co/c34xvYT/notes-ed-demo-2.gif) 35 | 36 | #### Remplir automatiquement le nom d'utilisateur 37 | Il y a une option pour vous éviter de toujours ré-entrer votre nom d'utilisateur. Pour l'utiliser, créez un fichier appelé `username.txt` à la racine du projet. Entrez-y votre nom d'utilisateur. C'est tout ! Maintenant, il ne vous faudra qu'entrer votre mot de passe pour accéder à vos statistiques. 38 | 39 | ### Valeurs montrées 40 | Le script, pour chaque matière (et la section générale), montrera : 41 | - le code de la matière 42 | - le coefficient de la matière 43 | - la moyenne (arithmétique pondérée), la même qui figurera dans le bulletin 44 | - la note médiane : vous avez autant de notes plus hautes et plus basses qu'elle 45 | - le rang par rapport au reste de la classe : une valeur qui n'est pas affichée par EcoleDirecte. Si `#00`, le rang est inconnu ou incalculable 46 | 47 | ## Autres outils ED 48 | 49 | ### Python 50 | [`archive-ed`](https://github.com/a2br/archive-ed/) permet de sauvegarder vos notes, même quand elles ne sont plus accessibles ! Le développement est mis sur pause pour le moment, les fonctionnalités sont limitées. 51 | 52 | ### JavaScript / TypeScript 53 | [`ecoledirecte.js`](https://github.com/a2br/ecoledirecte.js) ([npm](https://npmjs.com/package/ecoledirecte.js)) est un module Node permettant d'interagir avec EcoleDirecte depuis Node.js. Il est basé sur [`ecoledirecte-api-types`](https://github.com/a2br/ecoledirecte-api-types) ([npm](https://npmjs.com/package/ecoledirecte-api-types)), qui regroupe les types de l'API EcoleDirecte. Son utilisation est recommandée si vous construisez un projet avec TypeScript. 54 | -------------------------------------------------------------------------------- /ecoledirecte.py: -------------------------------------------------------------------------------- 1 | from requests import request as req 2 | from main import calm_exit 3 | from rich import print 4 | 5 | # Se connecte à EcoleDirecte 6 | def login(username: str, password: str, token: str = None): 7 | payload = 'data={ "identifiant": "' + username + \ 8 | '", "motdepasse": "' + password + '", "acceptationCharte": true }' 9 | try: 10 | response = req( 11 | "POST", "https://api.ecoledirecte.com/v3/login.awp", data=payload).json() 12 | token = response['token'] or token 13 | return response, token 14 | except Exception as exception: 15 | if type(exception).__name__ == "ConnectionError": 16 | print("[reverse bold red]La connexion a échoué[/]") 17 | print("[red]Vérifiez votre connexion Internet.[/]") 18 | else: 19 | print("[reverse bold red]Une erreur inconnue est survenue.[/]") 20 | calm_exit() 21 | 22 | 23 | # Récupère les notes 24 | def fetch_notes(account, token: str): 25 | payload = 'data={"token": "' + token + '"}' 26 | response = req("POST", "https://api.ecoledirecte.com/v3/eleves/" + 27 | str(account['id']) + "/notes.awp?verbe=get&", data=payload).json() 28 | token = response['token'] or token 29 | return response, token 30 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import locale 2 | import os 3 | import sys 4 | 5 | from rich import print 6 | from rich.console import Console 7 | from rich.table import Table 8 | import inquirer 9 | 10 | import ecoledirecte as ed 11 | 12 | locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8') 13 | console = Console() 14 | 15 | 16 | def calm_exit(): 17 | console.input(password=True) 18 | exit() 19 | 20 | 21 | def get_credentials(): 22 | # Récupère le nom d'utilisateur 23 | username = "" 24 | usernamePath = sys.argv[0] + "\\..\\username.txt" 25 | if os.path.isfile(usernamePath): 26 | with open(usernamePath) as file: 27 | username = file.readline() 28 | if not username: 29 | username = console.input("Identifiant: ") 30 | else: 31 | print(f"Connexion en tant que [bold]{username}[/]") 32 | # Récupère le mot de passe 33 | password = console.input("Mot de passe: ", password=True) 34 | # Retourne les valeurs 35 | return username, password 36 | 37 | 38 | # Crée un menu de sélection 39 | def choose(message: str, choices: list): 40 | questions = [ 41 | inquirer.List('a', message, choices) 42 | ] 43 | answer = inquirer.prompt(questions)['a'] 44 | return answer 45 | 46 | # Sélectionne ou demande le compte à retourner 47 | def select_account(accounts: list): 48 | # Filtre les comptes de type E 49 | e_accounts = list(filter(lambda account: bool( 50 | account['typeCompte'] == "E"), accounts)) 51 | # Met en page les choix 52 | choices = list( 53 | map(lambda account: (str(account['id']) + " | " + account['prenom'] + " " + account['nom']), 54 | e_accounts)) 55 | # Choix automatique 56 | choice = None 57 | if len(choices) > 1: 58 | choice = choose("Sélectionnez un compte disponible: ", choices=choices) 59 | elif len(choices) < 1: 60 | choice = None 61 | elif len(choices) == 1: 62 | choice = choices[0] 63 | if not choice: 64 | # Pas de compte supporté 65 | print("[reverse bold red]Aucun compte compatible trouvé[/]") 66 | print("[red]Essayez de vous connecter avec un compte Elève.[/]") 67 | calm_exit() 68 | 69 | account = next(filter(lambda account: ( 70 | str(account['id']) == choice[0:4]), e_accounts), None) 71 | if not account: 72 | # Aucun compte supporté 73 | print("[reverse bold red]Aucun compte compatible trouvé[/]") 74 | print("[red]Essayez de vous connecter avec un compte Elève.[/]") 75 | calm_exit() 76 | return account 77 | 78 | # Affiche la moyenne pour chaque période (et chaque matière) 79 | def handle_notes(data): 80 | periodes = data['periodes'] 81 | notes = data['notes'] 82 | 83 | for periode in periodes: 84 | matieres = periode['ensembleMatieres']['disciplines'] 85 | notes_list = [] # Liste des notes (=> calcul de la médiane) 86 | notes_periode = 0 # Somme des notes de la période 87 | diviseur_periode = 0 # Somme des coefficients 88 | infos_matieres = {} 89 | missing_subject_weight = False 90 | 91 | for matiere in matieres: 92 | notes_list_matiere = [] 93 | notes_matiere = 0 94 | diviseur_matiere = 0 95 | coef_matiere = float(matiere['coef']) or 1 96 | if not float(matiere['coef']): 97 | missing_subject_weight = True 98 | notesM = list(filter(lambda note: (note['codePeriode'] == periode['idPeriode']) and 99 | (note['codeMatiere'] == matiere['codeMatiere']), notes)) 100 | for note in notesM: 101 | try: 102 | if not note["nonSignificatif"]: 103 | notes_matiere += (locale.atof(note['valeur']) / locale.atof(note['noteSur'])) * \ 104 | locale.atof(note['coef']) 105 | diviseur_matiere += locale.atof(note['coef']) 106 | notes_list.append(locale.atof( 107 | note['valeur']) / locale.atof(note['noteSur'])) 108 | notes_list_matiere.append(locale.atof( 109 | note['valeur']) / locale.atof(note['noteSur'])) 110 | except: 111 | pass 112 | 113 | moyenne_matiere = None 114 | notes_list_matiere.sort() 115 | 116 | if diviseur_matiere: 117 | moyenne_matiere = (notes_matiere / diviseur_matiere) 118 | notes_periode += moyenne_matiere * coef_matiere 119 | diviseur_periode += coef_matiere 120 | infos_matieres[matiere['codeMatiere']] = { 121 | 'moyenne': moyenne_matiere if diviseur_matiere else None, 122 | 'mediane': notes_list_matiere[round((len(notes_list_matiere) - 1) / 2)] if notes_list_matiere else None, 123 | 'rang': matiere['rang'], 124 | 'coef': coef_matiere 125 | } 126 | 127 | notes_list.sort() 128 | 129 | if diviseur_periode: 130 | # Création du tableau 131 | table = Table(title=periode['periode']) 132 | table.add_column("Matière", style='cyan', justify='left') 133 | table.add_column("Coef", style='white', justify='center') 134 | table.add_column("Moyenne", style='magenta', justify='center') 135 | table.add_column("Médiane", style='hot_pink', justify='center') 136 | table.add_column("Rang", style='green', justify='right') 137 | 138 | for codeMatiere in infos_matieres: 139 | matiere = infos_matieres[codeMatiere] 140 | if codeMatiere: 141 | table.add_row(codeMatiere, str(matiere['coef']), 142 | str(round( 143 | matiere['moyenne'] * 20, 1) if matiere['moyenne'] else None).zfill(4), 144 | str(round( 145 | matiere['mediane'] * 20, 1) if matiere['mediane'] else None).zfill(4), 146 | f"#{str(matiere['rang']).zfill(2)}") 147 | moyenne_periode = notes_periode / diviseur_periode 148 | table.add_row("GENERAL", "0", str(round(moyenne_periode * 20, 1)), 149 | str(round((notes_list[round((len(notes_list) - 1) / 2)]) * 20, 1)), "#00", style='red') 150 | console.print(table) 151 | print("Moyenne exacte:", moyenne_periode * 20) 152 | if missing_subject_weight: 153 | print("Certaines matières de cette période n'avaient pas de coefficient. La moyenne générale générée est donc probablement erronée.") 154 | print() 155 | 156 | 157 | def main(): 158 | username, password = get_credentials() 159 | print("Connexion...") 160 | loginRes, token = ed.login(username, password) 161 | if not token: 162 | print(loginRes['message']) 163 | calm_exit() 164 | 165 | account = select_account(loginRes['data']['accounts']) 166 | print(f"[blue]Bonjour, [bold]{account['prenom']}[/].[/]") 167 | 168 | print("Collecte des notes...") 169 | notesRes, token = ed.fetch_notes(account, token) 170 | if notesRes['code'] != 200: 171 | print(notesRes['message']) 172 | calm_exit() 173 | print("Traitement des notes...") 174 | handle_notes(notesRes['data']) 175 | print("[reverse green]Terminé.[/] Pressez [reverse]ENTER[/] pour quitter.") 176 | calm_exit() 177 | 178 | 179 | if __name__ == "__main__": 180 | try: 181 | main() 182 | except KeyboardInterrupt: 183 | exit() 184 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | inquirer~=2.7.0 2 | requests~=2.25.1 3 | rich~=9.8.1 --------------------------------------------------------------------------------