#Requires AutoHotkey v2.0 #SingleInstance Force Persistent ; ============================================================================ ; RDV Monitor v2 - Outil de surveillance de rendez-vous TELUS Santé ; Utilise Chrome DevTools Protocol via extension ou lecture directe ; ============================================================================ ; Configuration globale global URL_ONGLET1 := "https://accesrendezvous.telussante.com/" global TIMEOUT_CHARGEMENT := 30000 ; 30 secondes global DELAI_REFRESH_MIN := 1000 ; 1 seconde global DELAI_REFRESH_MAX := 10000 ; 10 secondes global INTERVAL_CHECK := 3000 ; 3 secondes entre chaque vérification global FICHIER_PROFILS := A_ScriptDir . "\profils_rdv.ini" ; Fichier de sauvegarde des profils ; Texte cible pour détecter l'absence de rendez-vous global TEXTE_AUCUN_RDV := "Désolé, il est impossible de trouver des rendez-vous" ; Textes distinctifs pour chaque onglet global TEXTE_ONGLET1 := "Numéro d'assurance maladie" global TEXTE_ONGLET2 := "Raison de visite" global TEXTE_ONGLET3 := "Résultats" ; Variables d'état global EtatActuel := 0 global MonitoringActif := false global AlarmeSonne := false global DernierOnglet := 0 global CompteurErreurs := 0 global StopEnvoiTouches := false ; Flag pour arrêter immédiatement les envois de touches ; Liste des résultats ignorés (session seulement) global ResultatsIgnores := Map() ; Clé = "Clinique|Date" -> true ; Données utilisateur global UserData := Map() UserData["Prenom"] := "" UserData["Nom"] := "" UserData["NAM"] := "" UserData["NumSeq"] := "" UserData["Sexe"] := "Masculin" UserData["Courriel"] := "" UserData["Telephone"] := "" UserData["CodePostal"] := "" ; Liste des profils sauvegardés global ListeProfils := [] global ProfilActuel := "" ; Hotkey global pour quitter ^Escape:: { global MonitoringActif := false SetTimer(MonitorLoop, 0) SetTimer(PlayAlarmSound, 0) ExitApp() } ; ============================================================================ ; Interface utilisateur principale (État 0) ; ============================================================================ class MonitorGUI { static mainGui := "" static alarmGui := "" static ddlProfil := "" static Show() { ; Charger les profils au démarrage ChargerProfils() if (this.mainGui != "") this.mainGui.Destroy() this.mainGui := Gui("+AlwaysOnTop", "RDV Monitor - TELUS Santé") this.mainGui.SetFont("s10", "Segoe UI") this.mainGui.BackColor := "FFFFFF" ; Titre this.mainGui.SetFont("s14 bold c4B286D") this.mainGui.Add("Text", "xm w420 Center", "🏥 Surveillance Rendez-vous TELUS Santé") this.mainGui.SetFont("s10 norm c000000") ; Section sélection de profil this.mainGui.Add("GroupBox", "xm y+15 w420 h60", "Profil sauvegardé") this.mainGui.Add("Text", "xp+15 yp+25 w80", "Charger:") listeNoms := this.GetListeNomsProfils() this.ddlProfil := this.mainGui.Add("DropDownList", "x+5 w230 vProfilSelect", listeNoms) this.ddlProfil.OnEvent("Change", (*) => this.ChargerProfilSelectionne()) this.mainGui.Add("Button", "x+5 w75 h25", "🗑 Suppr").OnEvent("Click", (*) => this.SupprimerProfil()) ; Section informations personnelles this.mainGui.Add("GroupBox", "xm y+10 w420 h220", "Informations personnelles") yPos := 25 this.mainGui.Add("Text", "xp+15 yp+" . yPos . " w120", "Prénom *:") this.mainGui.Add("Edit", "x+5 w265 vPrenom", UserData["Prenom"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "Nom de famille *:") this.mainGui.Add("Edit", "x+5 w265 vNom", UserData["Nom"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "N° Assurance Maladie *:") this.mainGui.Add("Edit", "x+5 w265 vNAM Uppercase", UserData["NAM"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "N° Séquentiel:") this.mainGui.Add("Edit", "x+5 w265 vNumSeq", UserData["NumSeq"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "Sexe *:") sexeIndex := (UserData["Sexe"] = "Féminin") ? 2 : 1 this.mainGui.Add("DropDownList", "x+5 w265 vSexe Choose" . sexeIndex, ["Masculin", "Féminin"]) ; Section contact this.mainGui.Add("GroupBox", "xm y+20 w420 h130", "Contact") this.mainGui.Add("Text", "xp+15 yp+25 w120", "Courriel *:") this.mainGui.Add("Edit", "x+5 w265 vCourriel", UserData["Courriel"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "Téléphone *:") this.mainGui.Add("Edit", "x+5 w265 vTelephone", UserData["Telephone"]) this.mainGui.Add("Text", "xm+15 y+8 w120", "Code postal *:") this.mainGui.Add("Edit", "x+5 w265 vCodePostal Uppercase", UserData["CodePostal"]) ; Boutons this.mainGui.SetFont("s11 bold") this.mainGui.Add("Button", "xm y+20 w130 h45", "🚀 Lancer").OnEvent("Click", (*) => this.StartMonitoring()) this.mainGui.Add("Button", "x+10 w130 h45", "💾 Sauvegarder").OnEvent("Click", (*) => this.SauvegarderProfil()) this.mainGui.Add("Button", "x+10 w130 h45", "❌ Quitter").OnEvent("Click", (*) => ExitApp()) ; Info this.mainGui.SetFont("s8 norm italic cGray") this.mainGui.Add("Text", "xm y+15 w420 Center", "* Champs obligatoires | Ctrl+Escape pour quitter à tout moment") this.mainGui.OnEvent("Close", (*) => ExitApp()) this.mainGui.Show() } static GetListeNomsProfils() { liste := ["-- Nouveau profil --"] for profil in ListeProfils { liste.Push(profil["Nom"] . " " . profil["Prenom"] . " (" . profil["NAM"] . ")") } return liste } static ChargerProfilSelectionne() { index := this.ddlProfil.Value if (index <= 1) ; "Nouveau profil" ou rien return ; Index -1 car le premier élément est "Nouveau profil" profilIndex := index - 1 if (profilIndex > ListeProfils.Length) return profil := ListeProfils[profilIndex] ; Remplir les champs this.mainGui["Prenom"].Value := profil["Prenom"] this.mainGui["Nom"].Value := profil["Nom"] this.mainGui["NAM"].Value := profil["NAM"] this.mainGui["NumSeq"].Value := profil["NumSeq"] this.mainGui["Sexe"].Value := (profil["Sexe"] = "Féminin") ? 2 : 1 this.mainGui["Courriel"].Value := profil["Courriel"] this.mainGui["Telephone"].Value := profil["Telephone"] this.mainGui["CodePostal"].Value := profil["CodePostal"] global ProfilActuel := profil["NAM"] } static SauvegarderProfil() { ; Récupérer les données du formulaire saved := this.mainGui.Submit(false) if (Trim(saved.Prenom) = "" || Trim(saved.Nom) = "" || Trim(saved.NAM) = "") { MsgBox("Veuillez remplir au minimum Prénom, Nom et NAM pour sauvegarder.", "Champs requis", "Icon!") return } nouveauProfil := Map() nouveauProfil["Prenom"] := Trim(saved.Prenom) nouveauProfil["Nom"] := Trim(saved.Nom) nouveauProfil["NAM"] := StrUpper(Trim(saved.NAM)) nouveauProfil["NumSeq"] := Trim(saved.NumSeq) nouveauProfil["Sexe"] := saved.Sexe nouveauProfil["Courriel"] := Trim(saved.Courriel) nouveauProfil["Telephone"] := Trim(saved.Telephone) nouveauProfil["CodePostal"] := StrUpper(Trim(saved.CodePostal)) ; Vérifier si le profil existe déjà (par NAM) profilExiste := false for i, profil in ListeProfils { if (profil["NAM"] = nouveauProfil["NAM"]) { ListeProfils[i] := nouveauProfil profilExiste := true break } } if (!profilExiste) ListeProfils.Push(nouveauProfil) ; Sauvegarder dans le fichier SauvegarderProfils() ; Rafraîchir le dropdown this.ddlProfil.Delete() this.ddlProfil.Add(this.GetListeNomsProfils()) MsgBox("Profil sauvegardé avec succès!", "Sauvegarde", "Icon!") } static SupprimerProfil() { index := this.ddlProfil.Value if (index <= 1) { MsgBox("Sélectionnez un profil à supprimer.", "Suppression", "Icon!") return } profilIndex := index - 1 if (profilIndex > ListeProfils.Length) return profil := ListeProfils[profilIndex] result := MsgBox("Supprimer le profil de " . profil["Prenom"] . " " . profil["Nom"] . "?", "Confirmer suppression", "YesNo Icon?") if (result = "Yes") { ListeProfils.RemoveAt(profilIndex) SauvegarderProfils() ; Rafraîchir le dropdown this.ddlProfil.Delete() this.ddlProfil.Add(this.GetListeNomsProfils()) this.ddlProfil.Value := 1 ; Vider les champs this.mainGui["Prenom"].Value := "" this.mainGui["Nom"].Value := "" this.mainGui["NAM"].Value := "" this.mainGui["NumSeq"].Value := "" this.mainGui["Sexe"].Value := 1 this.mainGui["Courriel"].Value := "" this.mainGui["Telephone"].Value := "" this.mainGui["CodePostal"].Value := "" } } static StartMonitoring() { ; Sauvegarder les données saved := this.mainGui.Submit(false) UserData["Prenom"] := Trim(saved.Prenom) UserData["Nom"] := Trim(saved.Nom) UserData["NAM"] := StrUpper(Trim(saved.NAM)) UserData["NumSeq"] := Trim(saved.NumSeq) UserData["Sexe"] := saved.Sexe UserData["Courriel"] := Trim(saved.Courriel) UserData["Telephone"] := Trim(saved.Telephone) UserData["CodePostal"] := StrUpper(Trim(saved.CodePostal)) ; Validation erreurs := [] if (UserData["Prenom"] = "") erreurs.Push("Prénom") if (UserData["Nom"] = "") erreurs.Push("Nom de famille") if (UserData["NAM"] = "") erreurs.Push("N° Assurance Maladie") if (UserData["Courriel"] = "") erreurs.Push("Courriel") if (UserData["Telephone"] = "") erreurs.Push("Téléphone") if (UserData["CodePostal"] = "") erreurs.Push("Code postal") if (erreurs.Length > 0) { MsgBox("Veuillez remplir les champs suivants:`n- " . StrJoin(erreurs, "`n- "), "Champs requis", "Icon!") return } this.mainGui.Hide() global MonitoringActif := true global EtatActuel := 1 global CompteurErreurs := 0 global StopEnvoiTouches := false ; Réinitialiser le flag ; Afficher fenêtre de statut StatusGUI.Show() ; Démarrer la boucle de monitoring SetTimer(MonitorLoop, INTERVAL_CHECK) } static ShowAlarm() { global AlarmeSonne := true if (this.alarmGui != "") this.alarmGui.Destroy() this.alarmGui := Gui("+AlwaysOnTop +ToolWindow", "🎉 RENDEZ-VOUS TROUVÉ!") this.alarmGui.SetFont("s18 bold", "Segoe UI") this.alarmGui.BackColor := "00CC00" this.alarmGui.Add("Text", "xm y25 w400 Center cWhite", "🎉 RENDEZ-VOUS DISPONIBLE! 🎉") this.alarmGui.SetFont("s12 norm") this.alarmGui.Add("Text", "xm y+15 w400 Center cWhite", "Des créneaux ont été trouvés!") this.alarmGui.Add("Text", "xm y+5 w400 Center cWhite", "Consultez la page Chrome immédiatement.") this.alarmGui.SetFont("s14 bold") this.alarmGui.Add("Button", "xm+125 y+25 w150 h55", "✓ J'ai vu").OnEvent("Click", (*) => this.StopAlarm()) this.alarmGui.OnEvent("Close", (*) => this.StopAlarm()) this.alarmGui.Show("w420 h200") ; Mettre Chrome au premier plan try WinActivate("ahk_exe chrome.exe") ; Démarrer l'alarme sonore SetTimer(PlayAlarmSound, 800) } static StopAlarm() { global AlarmeSonne := false global MonitoringActif := false global EtatActuel := 0 SetTimer(PlayAlarmSound, 0) SetTimer(MonitorLoop, 0) if (this.alarmGui != "") { this.alarmGui.Destroy() this.alarmGui := "" } StatusGUI.Hide() this.Show() ; Retour à l'état 0 avec champs préremplis } } ; Helper function StrJoin(arr, sep) { result := "" for i, v in arr { if (i > 1) result .= sep result .= v } return result } ; ============================================================================ ; Fenêtre de statut pendant le monitoring ; ============================================================================ class StatusGUI { static gui := "" static logText := "" static Show() { if (this.gui != "") this.gui.Destroy() this.gui := Gui("+AlwaysOnTop +ToolWindow", "RDV Monitor - Surveillance active") this.gui.SetFont("s10", "Segoe UI") this.gui.BackColor := "F0F0F0" this.gui.SetFont("s11 bold c4B286D") this.gui.Add("Text", "xm w320 Center", "🔍 Surveillance en cours...") this.gui.SetFont("s10 norm c000000") this.gui.Add("Text", "xm y+15 w80", "Statut:") this.gui.Add("Text", "x+5 w230 vStatus cBlue", "Initialisation...") this.gui.Add("Text", "xm y+8 w80", "État:") this.gui.Add("Text", "x+5 w230 vEtat", "1 - Démarrage") this.gui.Add("Text", "xm y+8 w80", "Onglet:") this.gui.Add("Text", "x+5 w230 vOnglet", "Non détecté") ; Log this.gui.Add("GroupBox", "xm y+15 w400 h150", "Journal") this.gui.Add("Edit", "xp+10 yp+20 w380 h120 vLog ReadOnly +VScroll", "") this.gui.SetFont("s10 bold") this.gui.Add("Button", "xm y+15 w320 h35", "⏹ Arrêter la surveillance").OnEvent("Click", (*) => this.Stop()) this.gui.OnEvent("Close", (*) => this.Stop()) this.gui.Show() this.logText := "" } static Update(status, etat, onglet := "") { if (this.gui != "") { try { this.gui["Status"].Value := status this.gui["Etat"].Value := etat if (onglet != "") this.gui["Onglet"].Value := onglet } } } static Log(msg) { if (this.gui != "") { try { timestamp := FormatTime(, "HH:mm:ss") this.logText := "[" . timestamp . "] " . msg . "`n" . this.logText ; Garder seulement les 10 dernières lignes lines := StrSplit(this.logText, "`n") if (lines.Length > 10) { this.logText := "" Loop 10 { this.logText .= lines[A_Index] . "`n" } } this.gui["Log"].Value := this.logText } } } static Hide() { if (this.gui != "") { this.gui.Destroy() this.gui := "" } } static Stop() { global MonitoringActif := false global EtatActuel := 0 global StopEnvoiTouches := true ; Arrêter immédiatement les envois de touches SetTimer(MonitorLoop, 0) SetTimer(PlayAlarmSound, 0) ; Arrêter aussi l'alarme si active Sleep(100) ; Petit délai pour laisser les envois en cours se terminer this.Hide() MonitorGUI.Show() } } ; ============================================================================ ; Fonctions de détection des onglets via lecture du contenu ; ============================================================================ DetecterOnglet() { global DernierOnglet try { ; Vérifier si Chrome est ouvert if !WinExist("ahk_exe chrome.exe") { DernierOnglet := 0 return 0 } ; Activer Chrome WinActivate("ahk_exe chrome.exe") Sleep(100) ; Vérifier le titre titre := WinGetTitle("A") if !InStr(titre, "TELUS") && !InStr(titre, "Rendez-vous") && !InStr(titre, "Accès") { DernierOnglet := 0 return 0 } ; Lire le contenu de la page pour détection contenu := LireContenuPage() if (contenu = "") { return DernierOnglet ; Garder le dernier état connu } ; ===== Détection par contenu unique ===== ; Onglet 3: Page de résultats ; Textes uniques: "Liste des rendez-vous" ou "Calendrier" (boutons de vue) ; ou "impossible de trouver" (aucun RDV) if (InStr(contenu, "Liste des rendez-vous") || InStr(contenu, "Calendrier") || InStr(contenu, "impossible de trouver") || InStr(contenu, TEXTE_AUCUN_RDV)) { DernierOnglet := 3 return 3 } ; Onglet 2: Formulaire de recherche ; A "Raison de visite" mais PAS "Liste des rendez-vous" if (InStr(contenu, "Raison de visite") && !InStr(contenu, "Liste des rendez-vous")) { DernierOnglet := 2 return 2 } ; Fallback Onglet 2: "Rayon" et "Code postal" if (InStr(contenu, "Rayon") && InStr(contenu, "Code postal") && !InStr(contenu, "Liste des rendez-vous")) { DernierOnglet := 2 return 2 } ; Onglet 1: Formulaire d'inscription if (InStr(contenu, "Numéro d'assurance maladie") || InStr(contenu, "Langue de communication") || InStr(contenu, "Préférences de communication")) { DernierOnglet := 1 return 1 } return DernierOnglet } catch as e { StatusGUI.Log("Erreur détection: " . e.Message) return DernierOnglet } } LireURLChrome() { ; Lire l'URL de Chrome via la barre d'adresse url := "" try { WinActivate("ahk_exe chrome.exe") Sleep(200) ; Sauvegarder le presse-papiers clipSaved := ClipboardAll() A_Clipboard := "" ; Focus sur la barre d'adresse et copier l'URL Send("^l") ; Focus barre d'adresse Sleep(200) Send("^c") ; Copier l'URL Sleep(200) if ClipWait(1) { url := A_Clipboard } ; Escape pour quitter la barre d'adresse Send("{Escape}") Sleep(100) ; Restaurer le presse-papiers A_Clipboard := clipSaved } catch { url := "" } return url } LireContenuPage() { contenu := "" try { ; S'assurer que Chrome est actif if !WinExist("ahk_exe chrome.exe") return "" ; Activer Chrome et cliquer pour focus WinActivate("ahk_exe chrome.exe") Sleep(100) ControlClick("Chrome_RenderWidgetHostHWND1", "ahk_exe chrome.exe", , "Left", 1, "x300 y300") Sleep(150) ; Sauvegarder le presse-papiers clipSaved := ClipboardAll() A_Clipboard := "" ; Sélectionner tout et copier Send("^a") Sleep(100) Send("^c") Sleep(200) ; Attendre le presse-papiers if ClipWait(1) { contenu := A_Clipboard } ; Désélectionner Send("{Escape}") Sleep(50) ; Restaurer le presse-papiers A_Clipboard := clipSaved } catch { contenu := "" } return contenu } VerifierEtatCible() { ; Lire le contenu et vérifier si le message "Désolé..." est ABSENT contenu := LireContenuPage() ; Si on ne peut pas lire, retourner false par sécurité if (contenu = "") return false ; Si le texte d'absence de RDV n'est PAS présent = RDV trouvés! if !InStr(contenu, TEXTE_AUCUN_RDV) { ; Vérification supplémentaire: s'assurer qu'on est bien sur la page de résultats if (InStr(contenu, "Résultats") || InStr(contenu, "disponible")) { return true } } return false } ; ============================================================================ ; Extraction et affichage des résultats de RDV ; ============================================================================ ExtraireResultats(contenu) { ; Extraire les résultats de la page (clinique + date/heure) resultats := [] ; Debug: log la taille du contenu StatusGUI.Log("Extraction: " . StrLen(contenu) . " chars") ; Debug: chercher si "jeudi" ou "déc" existe dans le contenu if InStr(contenu, "jeudi") StatusGUI.Log("DEBUG: 'jeudi' trouvé dans contenu") if InStr(contenu, "déc") StatusGUI.Log("DEBUG: 'déc' trouvé dans contenu") if InStr(contenu, "11:20") StatusGUI.Log("DEBUG: '11:20' trouvé dans contenu") ; Formats possibles: ; "jeu., déc. 04, 18:40" (abrégé) ; "jeudi, déc. 04, 11:20" (complet) ; "jeudi, déc. 04, 11:20En personne" (collé) pos := 1 derniereClinique := "Clinique inconnue" ; Pattern plus flexible pour capturer les dates/heures ; Accepte: jour complet ou abrégé, mois abrégé avec point, jour numérique, heure ; Exemple: "jeudi, déc. 04, 11:20" ou "jeu., déc. 04, 18:40" while (pos := RegExMatch(contenu, "i)(lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche|lun\.?|mar\.?|mer\.?|jeu\.?|ven\.?|sam\.?|dim\.?),?\s*(janv?\.?|févr?\.?|mars|avr\.?|mai|juin|juil\.?|août|sept\.?|oct\.?|nov\.?|déc\.?)\s*(\d{1,2}),?\s*(\d{1,2}:\d{2})", &matchDate, pos)) { dateHeure := matchDate[0] ; Chercher le nom de clinique/professionnel le plus proche avant cette date posMeilleur := 0 ; Chercher différents marqueurs de clinique/professionnel for marqueur in ["Ma clinique:", "Mon professionnel", "Clinique", "Centre médical", "Centre"] { searchPos := 1 while (found := InStr(contenu, marqueur, false, searchPos)) { if (found < pos && found > posMeilleur) posMeilleur := found else if (found >= pos) break searchPos := found + 1 } } if (posMeilleur > 0) { ; Extraire le nom jusqu'au prochain retour ligne ou marqueur de fin finNom := posMeilleur + 100 for delim in ["`n", "`r", "Médecin", "En personne", "Virtuel"] { posDelim := InStr(contenu, delim, false, posMeilleur) if (posDelim > posMeilleur && posDelim < finNom) finNom := posDelim } derniereClinique := Trim(SubStr(contenu, posMeilleur, finNom - posMeilleur)) ; Nettoyer le nom derniereClinique := RegExReplace(derniereClinique, "^(Ma clinique:|Mon professionnel de la santé:)\s*", "") derniereClinique := Trim(derniereClinique) } resultats.Push({clinique: derniereClinique, dateHeure: dateHeure}) pos += StrLen(dateHeure) } StatusGUI.Log("Trouvé: " . resultats.Length . " RDV") return resultats } FiltrerResultatsNonIgnores(resultats) { ; Retourner seulement les résultats qui ne sont pas dans la liste ignorée global ResultatsIgnores nouveaux := [] for r in resultats { cle := r.clinique . "|" . r.dateHeure if !ResultatsIgnores.Has(cle) { nouveaux.Push(r) } } return nouveaux } ; GUI pour afficher les résultats trouvés class ResultatsGUI { static gui := "" static alarmGui := "" static checkboxes := [] static resultats := [] static choixUtilisateur := "" ; "continuer" ou "retour" static alarmeStopped := false static Show(resultats) { this.resultats := resultats this.checkboxes := [] this.choixUtilisateur := "" this.alarmeStopped := false ; D'ABORD: Afficher le popup d'alarme qui sonne en boucle this.ShowAlarmPopup() ; Attendre que l'utilisateur clique sur le bouton d'alarme while (!this.alarmeStopped) { Sleep(100) } ; ENSUITE: Afficher la liste des résultats this.ShowResultsList() } static ShowAlarmPopup() { ; Popup d'alarme qui sonne sans cesse this.alarmGui := Gui("+AlwaysOnTop +ToolWindow", "🔔 ALERTE RDV!") this.alarmGui.SetFont("s18 bold", "Segoe UI") this.alarmGui.BackColor := "FF6600" ; Orange vif this.alarmGui.Add("Text", "xm y25 w400 Center cWhite", "🔔 RENDEZ-VOUS TROUVÉ! 🔔") this.alarmGui.SetFont("s12 norm") this.alarmGui.Add("Text", "xm y+15 w400 Center cWhite", this.resultats.Length . " créneau(x) disponible(s)!") this.alarmGui.SetFont("s14 bold") btnVoir := this.alarmGui.Add("Button", "xm+100 y+25 w200 h55", "✓ Voir les résultats") btnVoir.OnEvent("Click", (*) => this.StopAlarm()) this.alarmGui.OnEvent("Close", (*) => this.StopAlarm()) this.alarmGui.Show("w420 h180") ; Démarrer l'alarme sonore en boucle SetTimer((*) => this.JouerAlarme(), 800) } static StopAlarm() { SetTimer((*) => this.JouerAlarme(), 0) ; Arrêter l'alarme if (this.alarmGui != "") { this.alarmGui.Destroy() this.alarmGui := "" } this.alarmeStopped := true } static ShowResultsList() { ; Créer la GUI de liste this.gui := Gui("+AlwaysOnTop +Resize", "🎉 Rendez-vous trouvés!") this.gui.SetFont("s10") this.gui.BackColor := "FFFACD" ; Jaune pâle this.gui.Add("Text", "w500 Center", "🎉 RENDEZ-VOUS DISPONIBLES! 🎉") this.gui.SetFont("s9") this.gui.Add("Text", "w500", "Cochez les résultats à ignorer puis cliquez sur 'Continuer la recherche',`nou cliquez sur 'Retour' pour arrêter le monitoring.") this.gui.Add("Text", "w500 h1 Background808080") ; Séparateur ; Case "Tout cocher" this.gui.SetFont("s9 bold") cbTout := this.gui.Add("Checkbox", "w480 vCBTout", "☑ Tout cocher / décocher") cbTout.OnEvent("Click", (*) => this.ToggleTout()) this.gui.SetFont("s9 norm") this.gui.Add("Text", "w500 h1 BackgroundC0C0C0") ; Petit séparateur ; Calculer la hauteur du ListView (max 10 items visibles, puis scrollbar) nbItems := this.resultats.Length hauteurLV := Min(nbItems, 10) * 20 + 30 ; ~20px par ligne + header hauteurLV := Max(hauteurLV, 80) ; Minimum 80px ; ListView avec scrollbar pour les résultats lv := this.gui.Add("ListView", "w500 h" . hauteurLV . " Checked vLVResultats -Multi", ["Clinique", "Date/Heure"]) lv.ModifyCol(1, 280) ; Colonne clinique: 280px lv.ModifyCol(2, 200) ; Colonne date/heure: 200px (toujours visible) ; Ajouter les résultats au ListView for i, r in this.resultats { ; Tronquer le nom de clinique si trop long (max 40 chars) clinique := r.clinique if (StrLen(clinique) > 40) clinique := SubStr(clinique, 1, 37) . "..." lv.Add("", clinique, r.dateHeure) } this.gui.Add("Text", "w500 h1 Background808080 y+10") ; Séparateur ; Boutons this.gui.SetFont("s10 Bold") btnContinuer := this.gui.Add("Button", "w200 h35", "Continuer la recherche") btnContinuer.OnEvent("Click", (*) => this.OnContinuer()) btnRetour := this.gui.Add("Button", "x+20 w150 h35", "Retour") btnRetour.OnEvent("Click", (*) => this.OnRetour()) this.gui.Show() } static ToggleTout() { ; Basculer toutes les cases du ListView try { etatTout := this.gui["CBTout"].Value lv := this.gui["LVResultats"] Loop lv.GetCount() { if (etatTout) lv.Modify(A_Index, "Check") else lv.Modify(A_Index, "-Check") } } } static JouerAlarme() { if (this.alarmeStopped) return ; Mélodie d'alarme SoundBeep(800, 200) Sleep(50) SoundBeep(1000, 200) Sleep(50) SoundBeep(1200, 200) Sleep(50) SoundBeep(1000, 200) } static OnContinuer() { global ResultatsIgnores ; Ajouter les résultats cochés (dans le ListView) à la liste ignorée try { lv := this.gui["LVResultats"] Loop lv.GetCount() { ; Vérifier si la ligne est cochée (bit 0x2000 = checked) rowNum := A_Index ; Utiliser GetNext avec "C" pour trouver les items cochés if (this.IsRowChecked(lv, rowNum)) { r := this.resultats[rowNum] cle := r.clinique . "|" . r.dateHeure ResultatsIgnores[cle] := true StatusGUI.Log("Ignoré: " . SubStr(r.clinique, 1, 30) . "...") } } } this.choixUtilisateur := "continuer" this.gui.Destroy() } static IsRowChecked(lv, rowNum) { ; Vérifier si une ligne du ListView est cochée ; Parcourir tous les items cochés et voir si rowNum en fait partie checkedRow := 0 Loop { checkedRow := lv.GetNext(checkedRow, "C") if (checkedRow = 0) break if (checkedRow = rowNum) return true } return false } static OnRetour() { this.choixUtilisateur := "retour" this.gui.Destroy() } static GetChoix() { return this.choixUtilisateur } } ; ============================================================================ ; Actions sur les formulaires ; ============================================================================ OuvrirChrome() { StatusGUI.Log("Ouverture Chrome...") try { Run("chrome.exe `"" . URL_ONGLET1 . "`"") } catch { Run("chrome.exe " . URL_ONGLET1) } Sleep(3000) } RemplirOnglet1() { global StopEnvoiTouches StatusGUI.Log("Remplissage formulaire 1...") ; Vérifier si on doit arrêter if (StopEnvoiTouches) return try { WinActivate("ahk_exe chrome.exe") Sleep(500) if (StopEnvoiTouches) return ; Rafraîchir la page avec Ctrl+F5 pour s'assurer que le formulaire est vide Send("^{F5}") Sleep(3000) ; Attendre le rechargement complet ; Aller au début de la page Send("^{Home}") Sleep(500) ; Localiser le premier champ via recherche Send("^f") Sleep(400) SendText("Prénom") Sleep(500) Send("{Enter}") Sleep(300) Send("{Escape}") Sleep(400) ; 2 Tabs pour atteindre le champ Prénom Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) ; ===== CHAMP 1: Prénom ===== SendText(UserData["Prenom"]) Sleep(600) ; ===== CHAMP 2: Nom de famille ===== (2 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) SendText(UserData["Nom"]) Sleep(600) ; ===== CHAMP 3: Numéro d'assurance maladie ===== (2 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) SendText(UserData["NAM"]) Sleep(600) ; ===== CHAMP 4: Numéro séquentiel ===== (2 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) SendText(UserData["NumSeq"]) Sleep(600) ; ===== CHAMP 5: Année de naissance (dropdown) ===== (2 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) ; Laisser la valeur par défaut, ne rien faire ; ===== CHAMP 6: Sexe (radio buttons) ===== ; Ctrl+F "Sexe", Esc, Tab Tab, puis Space (Homme) ou Right (Femme) Send("^f") Sleep(400) SendText("Sexe") Sleep(500) Send("{Escape}") Sleep(400) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) if (UserData["Sexe"] = "Féminin") { Send("{Right}") ; Aller à Femme Sleep(300) } else { Send("{Space}") ; Sélectionner Homme (déjà focusé) Sleep(300) } Sleep(400) ; ===== CHAMP 7: Courriel ===== (1 tab après Sexe) Send("{Tab}") Sleep(400) SendText(UserData["Courriel"]) Sleep(600) ; ===== CHAMP 8: Téléphone ===== (1 tab) Send("{Tab}") Sleep(400) SendText(UserData["Telephone"]) Sleep(600) ; ===== CHAMP 9: Langue de communication ===== (2 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) ; Français est déjà sélectionné par défaut, on ne touche pas ; ===== CHAMP 10: Préférences de communication (SMS) ===== (1 tab) Send("{Tab}") Sleep(400) Send("{Right}") ; Passer de "Courriel seulement" à "SMS" Sleep(500) ; ===== CHAMP 11: Case "J'ai lu et j'accepte..." ===== ; Utiliser Ctrl+F pour trouver la case car le nombre de tabs dépend de la sélection précédente Send("^f") Sleep(400) SendText("Comment TELUS Santé utilise mes données") Sleep(500) Send("{Enter}") Sleep(300) Send("{Escape}") Sleep(400) Send("{Tab}") ; 1 tab pour atteindre la checkbox Sleep(400) Send("{Space}") ; Cocher la case Sleep(600) ; ===== BOUTON: Continuer ===== (4 tabs) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) Send("{Enter}") StatusGUI.Log("Formulaire 1 soumis") } catch as e { StatusGUI.Log("Erreur form 1: " . e.Message) } } RemplirOnglet2() { global StopEnvoiTouches StatusGUI.Log("Remplissage formulaire 2...") ; Vérifier si on doit arrêter if (StopEnvoiTouches) return try { WinActivate("ahk_exe chrome.exe") Sleep(1000) if (StopEnvoiTouches) return ; Aller au début de la page Send("^{Home}") Sleep(500) ; Localiser le champ Code postal via recherche Send("^f") Sleep(400) SendText("Code postal") Sleep(500) Send("{Escape}") Sleep(400) ; 1 Tab pour aller au champ de saisie Send("{Tab}") Sleep(400) ; ===== CHAMP 1: Code postal ===== SendText(UserData["CodePostal"]) Sleep(800) ; Attendre l'autocomplétion Send("{Escape}") ; Fermer suggestions si présentes Sleep(400) ; ===== CHAMP 2: Rayon (km) - dropdown ===== ; Tab, puis Bas Bas Enter pour sélectionner 25km Send("{Tab}") Sleep(400) Send("{Down}") Sleep(300) Send("{Down}") Sleep(300) Send("{Enter}") Sleep(600) ; ===== CHAMPS 3 et 4: Date et Raison de visite ===== ; Tab Tab Tab Tab Enter pour passer et valider Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(300) Send("{Tab}") Sleep(400) Send("{Enter}") StatusGUI.Log("Formulaire 2 soumis") } catch as e { StatusGUI.Log("Erreur form 2: " . e.Message) } } RechercherEtRemplir(label, valeur) { ; Utiliser Ctrl+F pour trouver le label, puis Tab pour aller au champ Send("^f") Sleep(400) SendText(label) Sleep(500) Send("{Escape}") Sleep(400) Send("{Tab}") Sleep(400) SendText(valeur) } FindTextAndClick(texte) { ; Trouver un élément par son texte dans Chrome et cliquer dessus ; Utilise Acc (Accessibility) pour parcourir les éléments WinActivate("ahk_exe chrome.exe") WinWaitActive("ahk_exe chrome.exe", , 2) Sleep(300) try { hwnd := WinGetID("ahk_exe chrome.exe") ; Obtenir l'objet Acc de la fenêtre oAcc := Acc_ObjectFromWindow(hwnd) if (oAcc) { ; Chercher l'élément avec le texte spécifié element := Acc_FindElement(oAcc, texte) if (element) { ; Obtenir la position de l'élément loc := Acc_Location(element) if (loc) { clickX := loc.x + (loc.w // 2) clickY := loc.y + (loc.h // 2) CoordMode("Mouse", "Screen") MouseClick("Left", clickX, clickY) Sleep(300) StatusGUI.Log("Clic Acc sur '" . texte . "'") return true } } } } catch { } ; Fallback: utiliser Ctrl+F et Tab StatusGUI.Log("Acc non trouvé, fallback pour '" . texte . "'") Send("^f") Sleep(400) SendText(texte) Sleep(500) Send("{Escape}") Sleep(400) Send("{Tab}") Sleep(200) Send("{Space}") Sleep(300) return false } ; ============= Fonctions Acc (Accessibility) ============= Acc_ObjectFromWindow(hwnd, idObject := 0) { static IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}" if DllCall("oleacc\AccessibleObjectFromWindow" , "Ptr", hwnd , "UInt", idObject , "Ptr", -16 + NumPut("Int64", 0x11CF3C3D36876180, NumPut("Int64", 0x719B3800AA00810C, GUID := Buffer(16))) , "Ptr*", &pAcc := 0) = 0 return ComValue(9, pAcc) return false } Acc_FindElement(oAcc, searchText, maxDepth := 15) { ; Recherche récursive d'un élément par son texte if (maxDepth <= 0) return false try { ; Vérifier le nom de cet élément name := "" try name := oAcc.accName(0) if (name && InStr(name, searchText)) return oAcc ; Parcourir les enfants try { childCount := oAcc.accChildCount if (childCount > 0) { children := Buffer(childCount * (A_PtrSize + 8)) if DllCall("oleacc\AccessibleChildren" , "Ptr", ComObjValue(oAcc) , "Int", 0 , "Int", childCount , "Ptr", children , "Int*", &obtained := 0) = 0 { Loop obtained { offset := (A_Index - 1) * (A_PtrSize + 8) vt := NumGet(children, offset, "UShort") if (vt = 9) { ; VT_DISPATCH pChild := NumGet(children, offset + 8, "Ptr") if (pChild) { child := ComValue(9, pChild) result := Acc_FindElement(child, searchText, maxDepth - 1) if (result) return result } } else if (vt = 3) { ; VT_I4 childId := NumGet(children, offset + 8, "Int") try { childName := oAcc.accName(childId) if (childName && InStr(childName, searchText)) return {acc: oAcc, childId: childId} } } } } } } } catch { } return false } Acc_Location(element) { try { if (IsObject(element) && element.HasProp("acc")) { ; C'est un enfant avec ID element.acc.accLocation(&x := 0, &y := 0, &w := 0, &h := 0, element.childId) } else { ; C'est un objet Acc direct element.accLocation(&x := 0, &y := 0, &w := 0, &h := 0, 0) } return {x: x, y: y, w: w, h: h} } catch { return false } } CliquerSurTexteChromeHighlight(texte) { ; Utiliser Ctrl+F pour trouver le texte dans Chrome ; Chrome surligne le texte en orange (#FF9632 ou similaire) ; On utilise PixelSearch pour trouver cette couleur et cliquer dessus ; S'assurer que Chrome est actif WinActivate("ahk_exe chrome.exe") WinWaitActive("ahk_exe chrome.exe", , 2) Sleep(300) ; Ouvrir la recherche et taper le texte Send("^f") Sleep(500) SendText(texte) Sleep(1000) ; Attendre que Chrome trouve et surligne le texte ; Récupérer les dimensions de la fenêtre Chrome try { hwnd := WinGetID("ahk_exe chrome.exe") WinGetPos(&winX, &winY, &winW, &winH, hwnd) ; Zone de recherche: toute la fenêtre Chrome (sauf la barre d'outils ~150px) searchX1 := winX + 10 searchY1 := winY + 150 ; Ignorer la barre d'outils et barre de recherche searchX2 := winX + winW - 10 searchY2 := winY + winH - 50 ; Couleurs possibles du highlight Chrome (orange) ; Le highlight actif est généralement orange vif: #FF9632, #FFA500, #FF8C00 ; En BGR pour AHK: 0x3296FF, 0x00A5FF, 0x008CFF CoordMode("Pixel", "Screen") CoordMode("Mouse", "Screen") ; Chercher la couleur orange du highlight (plusieurs variantes) highlightColors := [0x3296FF, 0x00A5FF, 0x008CFF, 0x32FFFF, 0x00D4FF, 0x0080FF] found := false foundX := 0 foundY := 0 for color in highlightColors { if PixelSearch(&pX, &pY, searchX1, searchY1, searchX2, searchY2, color, 40) { foundX := pX foundY := pY found := true break } } if (found) { ; Sauvegarder les coordonnées avant de fermer la recherche clickX := foundX clickY := foundY ; Fermer la barre de recherche Send("{Escape}") Sleep(300) ; Réactiver Chrome et cliquer WinActivate("ahk_exe chrome.exe") WinWaitActive("ahk_exe chrome.exe", , 1) Sleep(200) ; Déplacer la souris et cliquer MouseMove(clickX, clickY) Sleep(100) MouseClick("Left", clickX, clickY) Sleep(400) StatusGUI.Log("Clic sur '" . texte . "' à " . clickX . "," . clickY) } else { ; Fallback: essayer avec une tolérance plus grande if PixelSearch(&pX, &pY, searchX1, searchY1, searchX2, searchY2, 0x3296FF, 80) { clickX := pX clickY := pY Send("{Escape}") Sleep(300) WinActivate("ahk_exe chrome.exe") WinWaitActive("ahk_exe chrome.exe", , 1) Sleep(200) MouseMove(clickX, clickY) Sleep(100) MouseClick("Left", clickX, clickY) Sleep(400) StatusGUI.Log("Clic (fallback) sur '" . texte . "'") } else { ; Dernier fallback: fermer recherche et Tab+Space Send("{Escape}") Sleep(300) WinActivate("ahk_exe chrome.exe") Sleep(200) Send("{Tab}") Sleep(200) Send("{Space}") Sleep(300) StatusGUI.Log("Fallback Tab+Space pour '" . texte . "'") } } } catch as e { Send("{Escape}") Sleep(200) StatusGUI.Log("Erreur CliquerSurTexte: " . e.Message) } } RefreshPage() { StatusGUI.Log("Rafraîchissement...") try { WinActivate("ahk_exe chrome.exe") Sleep(200) Send("{F5}") } } ; ============================================================================ ; Boucle principale de monitoring ; ============================================================================ MonitorLoop() { global EtatActuel, MonitoringActif, CompteurErreurs static tempsAttente := 0 static dernierRefresh := 0 if !MonitoringActif return ongletDetecte := DetecterOnglet() ongletStr := (ongletDetecte = 0) ? "Non détecté" : "Onglet " . ongletDetecte switch EtatActuel { case 1: ; Démarrage - ouvrir Chrome si nécessaire StatusGUI.Update("Recherche page TELUS...", "1 - Initialisation", ongletStr) if (ongletDetecte = 0) { OuvrirChrome() EtatActuel := "1B" tempsAttente := A_TickCount } else { ; Aller directement à l'état approprié switch ongletDetecte { case 1: EtatActuel := 2 case 2: EtatActuel := 3 case 3: EtatActuel := 4 } } case "1B": ; Attente chargement onglet 1 StatusGUI.Update("Attente chargement...", "1B - Chargement", ongletStr) if (ongletDetecte = 1) { EtatActuel := 2 CompteurErreurs := 0 } else if (A_TickCount - tempsAttente > TIMEOUT_CHARGEMENT) { StatusGUI.Log("Timeout, réessai...") CompteurErreurs++ if (CompteurErreurs > 3) { StatusGUI.Log("Trop d'erreurs, pause 30s") Sleep(30000) CompteurErreurs := 0 } EtatActuel := 1 } case 2: ; Onglet 1 détecté - remplir formulaire inscription StatusGUI.Update("Remplissage inscription...", "2 - Formulaire 1", ongletStr) if (ongletDetecte = 1) { Sleep(1000) ; Attendre que la page soit stable RemplirOnglet1() EtatActuel := "2B" tempsAttente := A_TickCount } else if (ongletDetecte = 2) { EtatActuel := 3 } else if (ongletDetecte = 3) { EtatActuel := 4 } case "2B": ; Attente chargement onglet 2 StatusGUI.Update("Attente page recherche...", "2B - Transition", ongletStr) if (ongletDetecte = 2) { EtatActuel := 3 CompteurErreurs := 0 } else if (ongletDetecte = 3) { EtatActuel := 4 } else if (A_TickCount - tempsAttente > TIMEOUT_CHARGEMENT) { StatusGUI.Log("Timeout form 1, réessai...") CompteurErreurs++ EtatActuel := 1 } case 3: ; Onglet 2 détecté - remplir formulaire recherche StatusGUI.Update("Remplissage recherche...", "3 - Formulaire 2", ongletStr) if (ongletDetecte = 2) { Sleep(500) RemplirOnglet2() EtatActuel := "3B" tempsAttente := A_TickCount } else if (ongletDetecte = 3) { EtatActuel := 4 } case "3B": ; Attente chargement onglet 3 (page résultats) StatusGUI.Update("Attente résultats...", "3B - Transition", ongletStr) if (ongletDetecte = 3) { EtatActuel := 4 CompteurErreurs := 0 } else if (A_TickCount - tempsAttente > TIMEOUT_CHARGEMENT) { StatusGUI.Log("Timeout 3B après 30s, réessai...") CompteurErreurs++ EtatActuel := 1 } case 4: ; Onglet 3 détecté - vérifier résultats StatusGUI.Update("Analyse des résultats...", "4 - Vérification", ongletStr) if (ongletDetecte = 3) { Sleep(500) ; Attendre que la page soit complètement chargée ; Lire le contenu de la page contenu := LireContenuPage() if VerifierEtatCible() { ; RDV trouvés - extraire les résultats resultats := ExtraireResultats(contenu) ; Si aucun résultat extrait, aller directement à l'état 5 if (resultats.Length = 0) { StatusGUI.Log("🎉 RDV trouvés! (extraction échouée)") EtatActuel := 5 } else { ; Filtrer les résultats déjà ignorés resultatsNouveaux := FiltrerResultatsNonIgnores(resultats) if (resultatsNouveaux.Length > 0) { ; Il y a des nouveaux résultats! StatusGUI.Log("🎉 " . resultatsNouveaux.Length . " nouveau(x) RDV!") EtatActuel := 5 } else { ; Tous les résultats sont ignorés, continuer la recherche StatusGUI.Log("RDV trouvés mais tous ignorés (" . resultats.Length . "), refresh...") delai := Random(DELAI_REFRESH_MIN, DELAI_REFRESH_MAX) Sleep(delai) RefreshPage() EtatActuel := "3B" tempsAttente := A_TickCount } } } else { ; Pas de RDV, programmer un refresh delai := Random(DELAI_REFRESH_MIN, DELAI_REFRESH_MAX) StatusGUI.Update("Aucun RDV. Refresh dans " . Round(delai/1000) . "s", "4 - Attente", ongletStr) StatusGUI.Log("Aucun RDV, refresh dans " . Round(delai/1000) . "s") Sleep(delai) RefreshPage() EtatActuel := "3B" tempsAttente := A_TickCount } } else if (ongletDetecte = 0) { StatusGUI.Log("Page perdue, retour début") EtatActuel := 1 } case 5: ; État cible atteint - Afficher les résultats StatusGUI.Update("🎉 RENDEZ-VOUS TROUVÉ!", "5 - SUCCÈS!", ongletStr) SetTimer(MonitorLoop, 0) ; Lire et extraire les résultats contenu := LireContenuPage() resultats := ExtraireResultats(contenu) resultatsNouveaux := FiltrerResultatsNonIgnores(resultats) ; Si aucun résultat extrait, créer un résultat générique if (resultats.Length = 0) { resultatsNouveaux := [{clinique: "RDV disponible (voir page)", dateHeure: FormatTime(, "HH:mm")}] } if (resultatsNouveaux.Length > 0) { ; Afficher la GUI des résultats ResultatsGUI.Show(resultatsNouveaux) ; Attendre le choix de l'utilisateur while (ResultatsGUI.GetChoix() = "") { Sleep(100) } if (ResultatsGUI.GetChoix() = "continuer") { ; Continuer la recherche StatusGUI.Log("Reprise de la recherche...") RefreshPage() EtatActuel := "3B" tempsAttente := A_TickCount SetTimer(MonitorLoop, INTERVAL_CHECK) } else { ; Retour - arrêter le monitoring StatusGUI.Log("Monitoring arrêté par l'utilisateur") MonitoringActif := false MonitorGUI.ShowAlarm() } } else { ; Pas de nouveaux résultats (tous ignorés) StatusGUI.Log("Tous les RDV sont ignorés, reprise...") RefreshPage() EtatActuel := "3B" tempsAttente := A_TickCount SetTimer(MonitorLoop, INTERVAL_CHECK) } } } ; ============================================================================ ; Alarme sonore ; ============================================================================ PlayAlarmSound() { global AlarmeSonne if AlarmeSonne { ; Mélodie d'alarme SoundBeep(800, 200) Sleep(50) SoundBeep(1000, 200) Sleep(50) SoundBeep(1200, 200) Sleep(50) SoundBeep(1000, 200) } } ; ============================================================================ ; Démarrage de l'application ; ============================================================================ ; ============================================================================ ; Gestion des profils (fichier INI) ; ============================================================================ ChargerProfils() { global ListeProfils := [] if !FileExist(FICHIER_PROFILS) return try { ; Lire le nombre de profils nbProfils := IniRead(FICHIER_PROFILS, "General", "NombreProfils", "0") nbProfils := Integer(nbProfils) Loop nbProfils { section := "Profil" . A_Index profil := Map() profil["Prenom"] := IniRead(FICHIER_PROFILS, section, "Prenom", "") profil["Nom"] := IniRead(FICHIER_PROFILS, section, "Nom", "") profil["NAM"] := IniRead(FICHIER_PROFILS, section, "NAM", "") profil["NumSeq"] := IniRead(FICHIER_PROFILS, section, "NumSeq", "") profil["Sexe"] := IniRead(FICHIER_PROFILS, section, "Sexe", "Masculin") profil["Courriel"] := IniRead(FICHIER_PROFILS, section, "Courriel", "") profil["Telephone"] := IniRead(FICHIER_PROFILS, section, "Telephone", "") profil["CodePostal"] := IniRead(FICHIER_PROFILS, section, "CodePostal", "") if (profil["NAM"] != "") ; Ne charger que les profils valides ListeProfils.Push(profil) } } catch { ; Fichier corrompu ou erreur de lecture ListeProfils := [] } } SauvegarderProfils() { ; Supprimer l'ancien fichier pour éviter les données résiduelles if FileExist(FICHIER_PROFILS) FileDelete(FICHIER_PROFILS) ; Écrire le nombre de profils IniWrite(ListeProfils.Length, FICHIER_PROFILS, "General", "NombreProfils") ; Écrire chaque profil for i, profil in ListeProfils { section := "Profil" . i IniWrite(profil["Prenom"], FICHIER_PROFILS, section, "Prenom") IniWrite(profil["Nom"], FICHIER_PROFILS, section, "Nom") IniWrite(profil["NAM"], FICHIER_PROFILS, section, "NAM") IniWrite(profil["NumSeq"], FICHIER_PROFILS, section, "NumSeq") IniWrite(profil["Sexe"], FICHIER_PROFILS, section, "Sexe") IniWrite(profil["Courriel"], FICHIER_PROFILS, section, "Courriel") IniWrite(profil["Telephone"], FICHIER_PROFILS, section, "Telephone") IniWrite(profil["CodePostal"], FICHIER_PROFILS, section, "CodePostal") } } ; ============================================================================ ; Démarrage de l'application ; ============================================================================ ; Afficher l'interface principale MonitorGUI.Show()