From baaee7803d714092a4b8a32b22fc137b23cfc40b Mon Sep 17 00:00:00 2001 From: JiPaix <26584973+JiPaix@users.noreply.github.com> Date: Thu, 28 May 2026 18:29:35 +0200 Subject: [PATCH 1/3] rework find_user_id: configurable range, live stats, early stop - rename USERNAME/CNT_ID/PHPSESSID to EMAIL/CUSTOMER_REF/php_session_id - expose SEARCH_START/SEARCH_END instead of hardcoded 490000..500000 - add background progress reporter (checked/hits/errors/rate/ETA) - stop the whole scan via os._exit as soon as a valid user id is found - add user-facing setup comments at the top of the file --- find_user_id/find_user_id.py | 282 ++++++++++++++++++++++++++++++----- 1 file changed, 245 insertions(+), 37 deletions(-) diff --git a/find_user_id/find_user_id.py b/find_user_id/find_user_id.py index 531d4f3..ccf0f93 100644 --- a/find_user_id/find_user_id.py +++ b/find_user_id/find_user_id.py @@ -2,42 +2,110 @@ from concurrent.futures import ThreadPoolExecutor import time import threading - -# ---------------------------- -# CONFIG -# ---------------------------- -USERNAME = "foobar@network.com" -PASSWORD = "passwd" -CNT_ID = "1234567890" - +import os + +# ============================================================ +# PARAMETERS TO FILL IN BY THE USER +# ============================================================ +# +# This script automatically searches for the internal user ID +# associated with your Linky meter on Total Energies, in order +# to then retrieve your real-time consumption data. +# +# Before running the script, you need to fill in the 3 fields +# below with YOUR OWN information: +# +# 1) EMAIL -> the email address you use to log +# in to your Total Energies customer +# account. +# +# 2) PASSWORD -> the password associated with that +# account. +# (keep the quotes around it) +# +# 3) CUSTOMER_REF -> your customer reference number +# (9 digits). You can find it: +# - on any Total Energies invoice +# - or on the home page of your online +# customer portal, just below your address. +# +# IMPORTANT: keep the quotes " " around each value. +# Correct example : EMAIL = "myaddress@gmail.com" +# Incorrect example : EMAIL = myaddress@gmail.com <-- will not work +# +EMAIL = "your.email@example.com" # <-- put your Total Energies email here +PASSWORD = "YourPassword" # <-- put your password here +CUSTOMER_REF = "123456789" # <-- put your customer reference here (9 digits) + + +# ------------------------------------------------------------ +# SEARCH RANGE +# ------------------------------------------------------------ +# +# The script will test numerical IDs one by one in order to +# find the one matching your meter. +# +# By default, it tests from 1 to 10,000,000 (ten million), which +# can take a very long time. If you prefer to split the search +# into several shorter runs, you can change the bounds: +# +# - First run : SEARCH_START = 1 / SEARCH_END = 990_000 +# - Second run : SEARCH_START = 990_001 / SEARCH_END = 2_000_000 +# - Third run : SEARCH_START = 2_000_001 / SEARCH_END = 3_000_000 +# - etc. +# +# The "_" character in numbers is just there to make them more +# readable (10_000_000 = 10000000). You can use it or not. +# +SEARCH_START = 1 # search starting point +SEARCH_END = 10_000_000 # search ending point + + +# ============================================================ +# TECHNICAL CONFIGURATION +# (only change this if you know what you are doing) +# ============================================================ BASE_URL = "https://esoftlink.esoftthings.com" - THREADS = 30 -RELOGIN_INTERVAL = 2 * 60 * 60 # 2 heures +RELOGIN_INTERVAL = 2 * 60 * 60 # 2 hours +PROGRESS_INTERVAL = 5 # seconds between two progress prints + -# ---------------------------- +# ------------------------------------------------------------ # GLOBAL STATE -# ---------------------------- +# ------------------------------------------------------------ session = requests.Session() -PHPSESSID = None +php_session_id = None lock = threading.Lock() +stats_lock = threading.Lock() +hit_lock = threading.Lock() last_login_time = 0 -# ---------------------------- -# LOGIN FUNCTION -# ---------------------------- +# Statistics +checked_count = 0 +found_count = 0 +error_count = 0 +start_time = 0 + +# Signal to stop when a valid user ID is found +found_event = threading.Event() + + +# ------------------------------------------------------------ +# LOGIN TO THE SERVER +# ------------------------------------------------------------ def login(): - global PHPSESSID, last_login_time, session + global php_session_id, last_login_time, session - print("[+] Re-login...") + print("[+] Logging in...") s = requests.Session() r = s.post( f"{BASE_URL}/login_check", data={ - "_username": USERNAME, + "_username": EMAIL, "_password": PASSWORD }, allow_redirects=False, @@ -47,32 +115,65 @@ def login(): cookies = s.cookies.get_dict() if "PHPSESSID" not in cookies: - print("Login failed - no PHPSESSID") + print("Login failed - no PHPSESSID received") return False with lock: session = s - PHPSESSID = cookies["PHPSESSID"] + php_session_id = cookies["PHPSESSID"] last_login_time = time.time() - print(f"[+] PHPSESSID refreshed") + print(f"[+] Session refreshed successfully") return True -# ---------------------------- -# CHECK LOGIN EXPIRATION -# ---------------------------- + +# ------------------------------------------------------------ +# CHECK SESSION EXPIRATION +# ------------------------------------------------------------ def ensure_login(): global last_login_time if time.time() - last_login_time > RELOGIN_INTERVAL: login() -# ---------------------------- -# WORKER -# ---------------------------- +# ------------------------------------------------------------ +# SHUTDOWN WHEN A VALID USER ID IS FOUND +# ------------------------------------------------------------ +def shutdown_with_hit(user_id, response_text): + """Prints the found user ID, the final statistics, then + immediately terminates the program.""" + elapsed = time.time() - start_time + + print("\n" + "=" * 60) + print(f"[!!! HIT FOUND] UserId = {user_id}") + print("-" * 60) + print(response_text) + print("-" * 60) + with stats_lock: + print(f"[DONE] Checked : {checked_count}") + print(f"[DONE] Errors : {error_count}") + print(f"[DONE] Time to find : {int(elapsed//60)}m{int(elapsed%60)}s") + print("=" * 60) + + # os._exit immediately terminates the whole process (and therefore + # all threads), without waiting for the pool workers to finish their + # in-flight requests. + os._exit(0) + + +# ------------------------------------------------------------ +# CHECK A USER ID +# ------------------------------------------------------------ def check_user(user_id): + global checked_count, found_count, error_count + + # If a valid ID has already been found by another thread, + # we no longer perform any requests. + if found_event.is_set(): + return + ensure_login() user_raw = str(user_id) @@ -86,7 +187,10 @@ def check_user(user_id): continue seen.add(user_str) - url = f"{BASE_URL}/api/subscription/{user_str}/{CNT_ID}/measure/live.json" + if found_event.is_set(): + return + + url = f"{BASE_URL}/api/subscription/{user_str}/{CUSTOMER_REF}/measure/live.json" try: with lock: @@ -94,25 +198,129 @@ def check_user(user_id): r = s.get(url, timeout=5) + with stats_lock: + checked_count += 1 + if '"error"' not in r.text: - print(f"\nUserId={user_str}") - print(r.text) - print("-" * 40) + with hit_lock: + if found_event.is_set(): + return + found_event.set() + with stats_lock: + found_count += 1 + shutdown_with_hit(user_str, r.text) except requests.RequestException: - pass + with stats_lock: + error_count += 1 time.sleep(0.01) -# ---------------------------- +# ------------------------------------------------------------ +# PROGRESS REPORTER +# ------------------------------------------------------------ +def progress_reporter(total_users): + last_checked = 0 + last_time = time.time() + + while True: + time.sleep(PROGRESS_INTERVAL) + + with stats_lock: + current_checked = checked_count + current_found = found_count + current_errors = error_count + + now = time.time() + elapsed = now - start_time + delta_checked = current_checked - last_checked + delta_time = now - last_time + + rate_total = current_checked / elapsed if elapsed > 0 else 0 + rate_recent = delta_checked / delta_time if delta_time > 0 else 0 + + # Progress over the total (2 requests per user, except duplicates) + progress_pct = (current_checked / (total_users * 2)) * 100 if total_users > 0 else 0 + + remaining = (total_users * 2) - current_checked + eta_sec = remaining / rate_recent if rate_recent > 0 else 0 + eta_h = int(eta_sec // 3600) + eta_m = int((eta_sec % 3600) // 60) + + print( + f"[STATS] checked={current_checked} " + f"({progress_pct:.2f}%) | " + f"hits={current_found} | " + f"errors={current_errors} | " + f"rate={rate_recent:.1f}/s (avg {rate_total:.1f}/s) | " + f"elapsed={int(elapsed//60)}m{int(elapsed%60)}s | " + f"ETA={eta_h}h{eta_m:02d}m" + ) + + last_checked = current_checked + last_time = now + + +# ============================================================ # START -# ---------------------------- +# ============================================================ +print("=" * 60) +print(f"[CONFIG] Email : {EMAIL}") +print(f"[CONFIG] Customer ref : {CUSTOMER_REF}") +print(f"[CONFIG] Base URL : {BASE_URL}") +print(f"[CONFIG] Threads : {THREADS}") +print(f"[CONFIG] Re-login every {RELOGIN_INTERVAL // 60} min") +print("=" * 60) + if not login(): + print("[FATAL] Initial login failed - aborting.") exit(1) -print("[+] Scanning...") +total_users = SEARCH_END - SEARCH_START + +print(f"[+] Scanning user IDs from {SEARCH_START} to {SEARCH_END} ({total_users} users)") +print(f"[+] ~{total_users * 2} requests total (raw + zero-padded to 6 digits)") +print(f"[+] Progress will be reported every {PROGRESS_INTERVAL}s") +print("=" * 60) +start_time = time.time() + +# Starts the progress reporter in the background +reporter_thread = threading.Thread( + target=progress_reporter, + args=(total_users,), + daemon=True +) +reporter_thread.start() + +submitted = 0 with ThreadPoolExecutor(max_workers=THREADS) as executor: - for user_id in range(490000, 500000): + for user_id in range(SEARCH_START, SEARCH_END): + # If a hit has been found in the meantime, we stop feeding + # the queue. + # (shutdown_with_hit calls os._exit, so in practice we only + # get here if the hit has not yet been processed.) + if found_event.is_set(): + break + executor.submit(check_user, user_id) + submitted += 1 + + if submitted % 10000 == 0: + print(f"[QUEUE] {submitted}/{total_users} user IDs submitted to the pool") + +print("=" * 60) +print("[+] All tasks submitted. Waiting for workers to finish...") + +# Note: we only get here if NO hit was found on the whole range, +# since shutdown_with_hit terminates the process via os._exit as +# soon as the first valid ID is found. +elapsed_total = time.time() - start_time +with stats_lock: + print("=" * 60) + print("[DONE] Scan finished - NO HIT FOUND on the full range.") + print(f"[DONE] Total checked : {checked_count}") + print(f"[DONE] Total errors : {error_count}") + print(f"[DONE] Total time : {int(elapsed_total//3600)}h{int((elapsed_total%3600)//60)}m{int(elapsed_total%60)}s") + print("=" * 60) From a0e5950c5d00011091fcb133cd826b15cb3f565c Mon Sep 17 00:00:00 2001 From: JiPaix <26584973+JiPaix@users.noreply.github.com> Date: Thu, 28 May 2026 18:30:50 +0200 Subject: [PATCH 2/3] add french localized version of find_user_id --- find_user_id/find_user_id_fr.py | 324 ++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 find_user_id/find_user_id_fr.py diff --git a/find_user_id/find_user_id_fr.py b/find_user_id/find_user_id_fr.py new file mode 100644 index 0000000..c91c46f --- /dev/null +++ b/find_user_id/find_user_id_fr.py @@ -0,0 +1,324 @@ +import requests +from concurrent.futures import ThreadPoolExecutor +import time +import threading +import os + +# ============================================================ +# PARAMETRES A REMPLIR PAR L'UTILISATEUR +# ============================================================ +# +# Ce script recherche automatiquement l'identifiant interne associé +# à votre compteur Linky chez Total Energies, afin de pouvoir +# récupérer ensuite vos données de consommation en temps réel. +# +# Avant de lancer le script, vous devez remplir les 3 champs +# ci-dessous avec VOS propres informations : +# +# 1) EMAIL -> l'adresse email que vous utilisez pour +# vous connecter à votre espace client +# Total Energies. +# +# 2) MOT_DE_PASSE -> le mot de passe associé à ce compte. +# (gardez bien les guillemets autour) +# +# 3) REFERENCE_CLIENT -> votre numéro de référence client +# (9 chiffres). Vous le trouverez : +# - sur n'importe quelle facture Total Energies +# - ou sur la page d'accueil de votre espace +# client en ligne, juste sous votre adresse. +# +# IMPORTANT : conservez bien les guillemets " " autour de chaque valeur. +# Exemple correct : EMAIL = "monadresse@gmail.com" +# Exemple incorrect : EMAIL = monadresse@gmail.com <-- ne fonctionnera pas +# +EMAIL = "votre.email@exemple.com" # <-- mettez ici votre email Total Energies +MOT_DE_PASSE = "VotreMotDePasse" # <-- mettez ici votre mot de passe +REFERENCE_CLIENT = "123456789" # <-- mettez ici votre référence client (9 chiffres) + + +# ------------------------------------------------------------ +# PLAGE DE RECHERCHE +# ------------------------------------------------------------ +# +# Le script va tester un par un des identifiants numériques pour +# trouver celui qui correspond à votre compteur. +# +# Par défaut, il teste de 1 à 10 000 000 (dix millions), ce qui +# peut être très long. Si vous préférez découper la recherche en +# plusieurs essais plus courts, vous pouvez modifier les bornes : +# +# - Premier essai : DEBUT_RECHERCHE = 1 / FIN_RECHERCHE = 990_000 +# - Deuxième essai : DEBUT_RECHERCHE = 990_001 / FIN_RECHERCHE = 2_000_000 +# - Troisième essai: DEBUT_RECHERCHE = 2_000_001 / FIN_RECHERCHE = 3_000_000 +# - etc. +# +# Le caractère "_" dans les nombres sert juste à les rendre plus +# lisibles (10_000_000 = 10000000). Vous pouvez l'utiliser ou non. +# +DEBUT_RECHERCHE = 1 # point de départ de la recherche +FIN_RECHERCHE = 10_000_000 # point d'arrivée de la recherche + + +# ============================================================ +# CONFIGURATION TECHNIQUE +# (à ne modifier que si vous savez ce que vous faites) +# ============================================================ +URL_BASE = "https://esoftlink.esoftthings.com" +NB_THREADS = 30 +INTERVALLE_RECONNEXION = 2 * 60 * 60 # 2 heures +INTERVALLE_PROGRESSION = 5 # secondes entre deux affichages de progression + + +# ------------------------------------------------------------ +# ETAT GLOBAL +# ------------------------------------------------------------ +session = requests.Session() +id_session_php = None +verrou = threading.Lock() +verrou_stats = threading.Lock() +verrou_succes = threading.Lock() + +derniere_connexion = 0 + +# Statistiques +nb_verifies = 0 +nb_trouves = 0 +nb_erreurs = 0 +temps_debut = 0 + +# Signal d'arrêt quand un identifiant valide est trouvé +evenement_trouve = threading.Event() + + +# ------------------------------------------------------------ +# CONNEXION AU SERVEUR +# ------------------------------------------------------------ +def connexion(): + global id_session_php, derniere_connexion, session + + print("[+] Reconnexion en cours...") + + s = requests.Session() + + r = s.post( + f"{URL_BASE}/login_check", + data={ + "_username": EMAIL, + "_password": MOT_DE_PASSE + }, + allow_redirects=False, + timeout=10 + ) + + cookies = s.cookies.get_dict() + + if "PHPSESSID" not in cookies: + print("Echec de la connexion - aucun PHPSESSID reçu") + return False + + with verrou: + session = s + id_session_php = cookies["PHPSESSID"] + derniere_connexion = time.time() + + print(f"[+] Session rafraîchie avec succès") + + return True + + +# ------------------------------------------------------------ +# VERIFICATION DE L'EXPIRATION DE LA SESSION +# ------------------------------------------------------------ +def verifier_connexion(): + global derniere_connexion + + if time.time() - derniere_connexion > INTERVALLE_RECONNEXION: + connexion() + + +# ------------------------------------------------------------ +# ARRET DU PROGRAMME LORSQU'UN IDENTIFIANT VALIDE EST TROUVE +# ------------------------------------------------------------ +def arret_avec_succes(identifiant, reponse): + """Affiche l'identifiant trouvé, les statistiques finales, + puis termine immédiatement le programme.""" + duree = time.time() - temps_debut + + print("\n" + "=" * 60) + print(f"[!!! IDENTIFIANT TROUVE] UserId = {identifiant}") + print("-" * 60) + print(reponse) + print("-" * 60) + with verrou_stats: + print(f"[FIN] Vérifiés : {nb_verifies}") + print(f"[FIN] Erreurs : {nb_erreurs}") + print(f"[FIN] Temps écoulé : {int(duree//60)}m{int(duree%60)}s") + print("=" * 60) + + # os._exit termine immédiatement tout le process (et donc tous les threads), + # sans attendre que les workers du pool aient fini leurs requêtes en cours. + os._exit(0) + + +# ------------------------------------------------------------ +# VERIFICATION D'UN IDENTIFIANT +# ------------------------------------------------------------ +def verifier_utilisateur(id_utilisateur): + global nb_verifies, nb_trouves, nb_erreurs + + # Si un identifiant valide a déjà été trouvé par un autre thread, + # on n'effectue plus aucune requête. + if evenement_trouve.is_set(): + return + + verifier_connexion() + + id_brut = str(id_utilisateur) + id_complete = str(id_utilisateur).zfill(6) + + deja_vus = set() + + for identifiant in (id_brut, id_complete): + + if identifiant in deja_vus: + continue + deja_vus.add(identifiant) + + if evenement_trouve.is_set(): + return + + url = f"{URL_BASE}/api/subscription/{identifiant}/{REFERENCE_CLIENT}/measure/live.json" + + try: + with verrou: + s = session + + r = s.get(url, timeout=5) + + with verrou_stats: + nb_verifies += 1 + + if '"error"' not in r.text: + with verrou_succes: + if evenement_trouve.is_set(): + return + evenement_trouve.set() + with verrou_stats: + nb_trouves += 1 + arret_avec_succes(identifiant, r.text) + + except requests.RequestException: + with verrou_stats: + nb_erreurs += 1 + + time.sleep(0.01) + + +# ------------------------------------------------------------ +# AFFICHAGE DE LA PROGRESSION +# ------------------------------------------------------------ +def rapport_progression(total_utilisateurs): + derniers_verifies = 0 + dernier_temps = time.time() + + while True: + time.sleep(INTERVALLE_PROGRESSION) + + with verrou_stats: + verifies_actuels = nb_verifies + trouves_actuels = nb_trouves + erreurs_actuelles = nb_erreurs + + maintenant = time.time() + duree = maintenant - temps_debut + delta_verifies = verifies_actuels - derniers_verifies + delta_temps = maintenant - dernier_temps + + cadence_totale = verifies_actuels / duree if duree > 0 else 0 + cadence_recente = delta_verifies / delta_temps if delta_temps > 0 else 0 + + # Progression sur le total (2 requêtes par utilisateur, sauf doublons) + pourcentage = (verifies_actuels / (total_utilisateurs * 2)) * 100 if total_utilisateurs > 0 else 0 + + restant = (total_utilisateurs * 2) - verifies_actuels + eta_sec = restant / cadence_recente if cadence_recente > 0 else 0 + eta_h = int(eta_sec // 3600) + eta_m = int((eta_sec % 3600) // 60) + + print( + f"[STATS] vérifiés={verifies_actuels} " + f"({pourcentage:.2f}%) | " + f"trouvés={trouves_actuels} | " + f"erreurs={erreurs_actuelles} | " + f"cadence={cadence_recente:.1f}/s (moy {cadence_totale:.1f}/s) | " + f"écoulé={int(duree//60)}m{int(duree%60)}s | " + f"ETA={eta_h}h{eta_m:02d}m" + ) + + derniers_verifies = verifies_actuels + dernier_temps = maintenant + + +# ============================================================ +# DEMARRAGE DU PROGRAMME +# ============================================================ +print("=" * 60) +print(f"[CONFIG] Email : {EMAIL}") +print(f"[CONFIG] Référence client : {REFERENCE_CLIENT}") +print(f"[CONFIG] URL de base : {URL_BASE}") +print(f"[CONFIG] Threads : {NB_THREADS}") +print(f"[CONFIG] Reconnexion toutes les {INTERVALLE_RECONNEXION // 60} min") +print("=" * 60) + +if not connexion(): + print("[FATAL] La connexion initiale a échoué - arrêt du programme.") + exit(1) + +total_utilisateurs = FIN_RECHERCHE - DEBUT_RECHERCHE + +print(f"[+] Scan des identifiants de {DEBUT_RECHERCHE} à {FIN_RECHERCHE} ({total_utilisateurs} utilisateurs)") +print(f"[+] Environ {total_utilisateurs * 2} requêtes au total (version brute + version complétée à 6 chiffres)") +print(f"[+] La progression sera affichée toutes les {INTERVALLE_PROGRESSION} secondes") +print("=" * 60) + +temps_debut = time.time() + +# Démarre l'affichage de progression en arrière-plan +thread_rapport = threading.Thread( + target=rapport_progression, + args=(total_utilisateurs,), + daemon=True +) +thread_rapport.start() + +nb_soumis = 0 +with ThreadPoolExecutor(max_workers=NB_THREADS) as executor: + for id_utilisateur in range(DEBUT_RECHERCHE, FIN_RECHERCHE): + # Si un identifiant valide a été trouvé entre-temps, on arrête + # d'alimenter la file d'attente. + # (arret_avec_succes appelle os._exit, donc en pratique on n'arrive + # ici que si l'identifiant n'a pas encore été traité.) + if evenement_trouve.is_set(): + break + + executor.submit(verifier_utilisateur, id_utilisateur) + nb_soumis += 1 + + if nb_soumis % 10000 == 0: + print(f"[FILE] {nb_soumis}/{total_utilisateurs} identifiants envoyés dans la file") + +print("=" * 60) +print("[+] Toutes les tâches ont été soumises. Attente de la fin des workers...") + +# Note : on n'arrive ici que si AUCUN identifiant valide n'a été trouvé sur +# toute la plage, puisque arret_avec_succes termine le programme via +# os._exit dès le premier identifiant valide. +duree_totale = time.time() - temps_debut +with verrou_stats: + print("=" * 60) + print("[FIN] Scan terminé - AUCUN IDENTIFIANT TROUVE sur la plage complète.") + print(f"[FIN] Total vérifiés : {nb_verifies}") + print(f"[FIN] Total erreurs : {nb_erreurs}") + print(f"[FIN] Temps total : {int(duree_totale//3600)}h{int((duree_totale%3600)//60)}m{int(duree_totale%60)}s") + print("=" * 60) From b0a12c314fe270d841ea200232d501cb58321449 Mon Sep 17 00:00:00 2001 From: JiPaix <26584973+JiPaix@users.noreply.github.com> Date: Thu, 28 May 2026 18:51:48 +0200 Subject: [PATCH 3/3] remove "hit" and "error" from live reporting --- find_user_id/find_user_id.py | 4 ---- find_user_id/find_user_id_fr.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/find_user_id/find_user_id.py b/find_user_id/find_user_id.py index ccf0f93..49bb556 100644 --- a/find_user_id/find_user_id.py +++ b/find_user_id/find_user_id.py @@ -229,8 +229,6 @@ def progress_reporter(total_users): with stats_lock: current_checked = checked_count - current_found = found_count - current_errors = error_count now = time.time() elapsed = now - start_time @@ -251,8 +249,6 @@ def progress_reporter(total_users): print( f"[STATS] checked={current_checked} " f"({progress_pct:.2f}%) | " - f"hits={current_found} | " - f"errors={current_errors} | " f"rate={rate_recent:.1f}/s (avg {rate_total:.1f}/s) | " f"elapsed={int(elapsed//60)}m{int(elapsed%60)}s | " f"ETA={eta_h}h{eta_m:02d}m" diff --git a/find_user_id/find_user_id_fr.py b/find_user_id/find_user_id_fr.py index c91c46f..4b9a08a 100644 --- a/find_user_id/find_user_id_fr.py +++ b/find_user_id/find_user_id_fr.py @@ -227,8 +227,6 @@ def rapport_progression(total_utilisateurs): with verrou_stats: verifies_actuels = nb_verifies - trouves_actuels = nb_trouves - erreurs_actuelles = nb_erreurs maintenant = time.time() duree = maintenant - temps_debut @@ -249,8 +247,6 @@ def rapport_progression(total_utilisateurs): print( f"[STATS] vérifiés={verifies_actuels} " f"({pourcentage:.2f}%) | " - f"trouvés={trouves_actuels} | " - f"erreurs={erreurs_actuelles} | " f"cadence={cadence_recente:.1f}/s (moy {cadence_totale:.1f}/s) | " f"écoulé={int(duree//60)}m{int(duree%60)}s | " f"ETA={eta_h}h{eta_m:02d}m"