#Requires AutoHotkey v2.0 #SingleInstance Force Persistent ; ============================================================================ ; RDV Monitor - Outil de surveillance de rendez-vous TELUS Santé ; ============================================================================ ; Configuration globale global URL_ONGLET1 := "https://accesrendezvous.telussante.com/" global TIMEOUT_CHARGEMENT := 30000 ; 30 secondes global DELAI_REFRESH_MIN := 5000 ; 5 secondes global DELAI_REFRESH_MAX := 20000 ; 20 secondes global INTERVAL_CHECK := 2000 ; 2 secondes entre chaque vérification ; Texte cible pour détecter l'absence de rendez-vous global TEXTE_AUCUN_RDV := "Désolé, il est impossible de trouver des rendez-vous qui correspondent à votre recherche actuelle" ; Variables d'état global EtatActuel := 0 global MonitoringActif := false global TimerID := 0 global AlarmeSonne := false ; Données utilisateur global UserData := Map() UserData["Prenom"] := "" UserData["Nom"] := "" UserData["NAM"] := "" UserData["NumSeq"] := "" UserData["Sexe"] := "" UserData["Courriel"] := "" UserData["Telephone"] := "" UserData["CodePostal"] := "" ; Hotkey global pour quitter ^Escape::ExitApp ; ============================================================================ ; Interface utilisateur principale (État 0) ; ============================================================================ class MonitorGUI { static mainGui := "" static alarmGui := "" static Show() { if (this.mainGui != "") this.mainGui.Destroy() this.mainGui := Gui("+AlwaysOnTop", "RDV Monitor - TELUS Santé") this.mainGui.SetFont("s10", "Segoe UI") this.mainGui.BackColor := "White" ; Titre this.mainGui.SetFont("s14 bold") this.mainGui.Add("Text", "xm w400 Center", "Surveillance Rendez-vous TELUS Santé") this.mainGui.SetFont("s10 norm") ; Section informations personnelles this.mainGui.Add("GroupBox", "xm y+15 w420 h200", "Informations personnelles") this.mainGui.Add("Text", "xp+15 yp+25 w100", "Prénom:") this.mainGui.Add("Edit", "x+10 w280 vPrenom", UserData["Prenom"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "Nom de famille:") this.mainGui.Add("Edit", "x+10 w280 vNom", UserData["Nom"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "N° Assurance Maladie:") this.mainGui.Add("Edit", "x+10 w280 vNAM", UserData["NAM"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "N° Séquentiel:") this.mainGui.Add("Edit", "x+10 w280 vNumSeq", UserData["NumSeq"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "Sexe:") this.mainGui.Add("DropDownList", "x+10 w280 vSexe Choose1", ["Masculin", "Féminin"]) ; Section contact this.mainGui.Add("GroupBox", "xm y+25 w420 h130", "Contact") this.mainGui.Add("Text", "xp+15 yp+25 w100", "Courriel:") this.mainGui.Add("Edit", "x+10 w280 vCourriel", UserData["Courriel"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "Téléphone:") this.mainGui.Add("Edit", "x+10 w280 vTelephone", UserData["Telephone"]) this.mainGui.Add("Text", "xm+15 y+10 w100", "Code postal:") this.mainGui.Add("Edit", "x+10 w280 vCodePostal", UserData["CodePostal"]) ; Boutons this.mainGui.Add("Button", "xm y+30 w200 h40", "🚀 Lancer le monitoring").OnEvent("Click", (*) => this.StartMonitoring()) this.mainGui.Add("Button", "x+20 w200 h40", "❌ Quitter").OnEvent("Click", (*) => ExitApp()) ; Info this.mainGui.SetFont("s8 italic") this.mainGui.Add("Text", "xm y+15 w420 Center", "Ctrl+Escape pour quitter à tout moment") this.mainGui.OnEvent("Close", (*) => ExitApp()) this.mainGui.Show() } static StartMonitoring() { ; Sauvegarder les données saved := this.mainGui.Submit(false) UserData["Prenom"] := saved.Prenom UserData["Nom"] := saved.Nom UserData["NAM"] := saved.NAM UserData["NumSeq"] := saved.NumSeq UserData["Sexe"] := saved.Sexe UserData["Courriel"] := saved.Courriel UserData["Telephone"] := saved.Telephone UserData["CodePostal"] := saved.CodePostal ; Validation basique if (UserData["Prenom"] = "" || UserData["Nom"] = "" || UserData["NAM"] = "") { MsgBox("Veuillez remplir au minimum: Prénom, Nom et N° Assurance Maladie", "Champs requis", "Icon!") return } this.mainGui.Hide() global MonitoringActif := true global EtatActuel := 1 ; Démarrer la boucle de monitoring SetTimer(MonitorLoop, INTERVAL_CHECK) ; Afficher fenêtre de statut StatusGUI.Show() } static ShowAlarm() { global AlarmeSonne := true if (this.alarmGui != "") this.alarmGui.Destroy() this.alarmGui := Gui("+AlwaysOnTop +ToolWindow", "🎉 RENDEZ-VOUS TROUVÉ!") this.alarmGui.SetFont("s16 bold", "Segoe UI") this.alarmGui.BackColor := "00FF00" this.alarmGui.Add("Text", "xm y20 w350 Center", "🎉 RENDEZ-VOUS DISPONIBLE! 🎉") this.alarmGui.SetFont("s12 norm") this.alarmGui.Add("Text", "xm y+10 w350 Center", "Des créneaux ont été trouvés!") this.alarmGui.Add("Text", "xm y+5 w350 Center", "Consultez la page Chrome immédiatement.") this.alarmGui.Add("Button", "xm+100 y+20 w150 h50", "✓ J'ai vu").OnEvent("Click", (*) => this.StopAlarm()) this.alarmGui.OnEvent("Close", (*) => this.StopAlarm()) this.alarmGui.Show("w370 h180") ; Démarrer l'alarme sonore SetTimer(PlayAlarmSound, 1000) } static StopAlarm() { global AlarmeSonne := false global MonitoringActif := false global EtatActuel := 0 SetTimer(PlayAlarmSound, 0) SetTimer(MonitorLoop, 0) if (this.alarmGui != "") this.alarmGui.Destroy() StatusGUI.Hide() this.Show() ; Retour à l'état 0 avec champs préremplis } } ; ============================================================================ ; Fenêtre de statut pendant le monitoring ; ============================================================================ class StatusGUI { static gui := "" static Show() { if (this.gui != "") this.gui.Destroy() this.gui := Gui("+AlwaysOnTop +ToolWindow", "RDV Monitor - En cours") this.gui.SetFont("s10", "Segoe UI") this.gui.Add("Text", "xm w300 vStatus", "Initialisation...") this.gui.Add("Text", "xm y+5 w300 vEtat", "État: 1") this.gui.Add("Button", "xm y+15 w300", "⏹ Arrêter").OnEvent("Click", (*) => this.Stop()) this.gui.OnEvent("Close", (*) => this.Stop()) this.gui.Show() } static Update(status, etat) { if (this.gui != "") { try { this.gui["Status"].Value := status this.gui["Etat"].Value := "État: " . etat } } } static Hide() { if (this.gui != "") this.gui.Hide() } static Stop() { global MonitoringActif := false global EtatActuel := 0 SetTimer(MonitorLoop, 0) this.Hide() MonitorGUI.Show() } } ; ============================================================================ ; Fonctions de détection des onglets ; ============================================================================ DetecterOnglet() { ; Récupérer le contenu de la page Chrome active try { if !WinExist("ahk_exe chrome.exe") return 0 WinActivate("ahk_exe chrome.exe") Sleep(200) ; Récupérer le titre de la fenêtre titre := WinGetTitle("A") ; Vérifier si c'est bien TELUS Santé if !InStr(titre, "TELUS") && !InStr(titre, "Rendez-vous") return 0 ; Utiliser l'accessibilité pour lire le contenu contenu := GetPageContent() ; Détecter Onglet 3 (page de résultats) - présence de "Résultats" ou message d'erreur if (InStr(contenu, "Résultats de recherche") || InStr(contenu, "Désolé, il est impossible") || InStr(contenu, "rendez-vous disponibles")) return 3 ; Détecter Onglet 2 (recherche) - présence de "Rayon" et "Code postal" ensemble if (InStr(contenu, "Rayon") && InStr(contenu, "Raison de visite")) return 2 ; Détecter Onglet 1 (inscription) - présence de "Numéro d'assurance maladie" if (InStr(contenu, "Numéro d'assurance maladie") || InStr(contenu, "Langue de communication")) return 1 return 0 } catch { return 0 } } GetPageContent() { ; Copier le contenu visible de la page via Ctrl+A, Ctrl+C contenu := "" try { clipSaved := A_Clipboard A_Clipboard := "" Send("^a") Sleep(100) Send("^c") Sleep(200) if ClipWait(2) { contenu := A_Clipboard } Send("{Escape}") ; Désélectionner A_Clipboard := clipSaved } return contenu } VerifierEtatCible() { ; Vérifier si le texte "Désolé, il est impossible..." est absent contenu := GetPageContent() ; Si le texte n'est PAS présent, des rendez-vous sont disponibles if !InStr(contenu, TEXTE_AUCUN_RDV) return true ; État cible atteint! return false } ; ============================================================================ ; Actions sur les formulaires ; ============================================================================ OuvrirChrome() { StatusGUI.Update("Ouverture de Chrome...", "1") Run("chrome.exe " . URL_ONGLET1) Sleep(2000) } RemplirOnglet1() { StatusGUI.Update("Remplissage formulaire inscription...", "2") try { WinActivate("ahk_exe chrome.exe") Sleep(500) ; Utiliser Tab pour naviguer dans le formulaire ; Cliquer au début du formulaire (premier champ) ; Prénom EnvoyerTexteChamp("Prénom", UserData["Prenom"]) ; Nom EnvoyerTexteChamp("Nom de famille", UserData["Nom"]) ; NAM EnvoyerTexteChamp("Numéro d'assurance maladie", UserData["NAM"]) ; Numéro séquentiel EnvoyerTexteChamp("Numéro séquentiel", UserData["NumSeq"]) ; Sexe - dropdown SelectionnerDropdown("Sexe", UserData["Sexe"]) ; Courriel EnvoyerTexteChamp("Courriel", UserData["Courriel"]) ; Téléphone EnvoyerTexteChamp("Téléphone", UserData["Telephone"]) ; Langue de communication - Français SelectionnerDropdown("Langue de communication", "Français") ; Cocher SMS CocherCase("SMS") ; Cocher termes et conditions CocherCase("J'ai lu") Sleep(500) ; Soumettre le formulaire Send("{Tab}{Enter}") } catch as e { StatusGUI.Update("Erreur: " . e.Message, "2") } } RemplirOnglet2() { StatusGUI.Update("Remplissage formulaire recherche...", "3") try { WinActivate("ahk_exe chrome.exe") Sleep(500) ; Code postal EnvoyerTexteChamp("Code postal", UserData["CodePostal"]) ; Rayon - 25 km SelectionnerDropdown("Rayon", "25") ; Date - aujourd'hui (valeur par défaut, ne pas toucher) ; Raison de visite - urgent SelectionnerDropdown("Raison de visite", "Urgent") Sleep(500) ; Soumettre Send("{Tab}{Enter}") } catch as e { StatusGUI.Update("Erreur: " . e.Message, "3") } } EnvoyerTexteChamp(label, valeur) { ; Rechercher le champ par son label et y entrer la valeur Send("^f") Sleep(100) Send(label) Sleep(200) Send("{Escape}") Sleep(100) Send("{Tab}") Sleep(100) Send(valeur) Sleep(100) } SelectionnerDropdown(label, valeur) { Send("^f") Sleep(100) Send(label) Sleep(200) Send("{Escape}") Sleep(100) Send("{Tab}") Sleep(100) ; Ouvrir le dropdown et chercher la valeur Send("{Space}") Sleep(200) Send(valeur) Sleep(100) Send("{Enter}") Sleep(100) } CocherCase(textePartiel) { Send("^f") Sleep(100) Send(textePartiel) Sleep(200) Send("{Escape}") Sleep(100) Send("{Tab}") Sleep(100) Send("{Space}") Sleep(100) } RefreshPage() { StatusGUI.Update("Rafraîchissement de la page...", "4") WinActivate("ahk_exe chrome.exe") Sleep(200) Send("{F5}") } ; ============================================================================ ; Boucle principale de monitoring ; ============================================================================ MonitorLoop() { global EtatActuel, MonitoringActif if !MonitoringActif return ongletDetecte := DetecterOnglet() switch EtatActuel { case 1: ; Onglet non repéré - ouvrir Chrome StatusGUI.Update("Recherche de la page...", "1") if (ongletDetecte = 0) { OuvrirChrome() EtatActuel := "1B" SetTimer(TimeoutEtat1B, TIMEOUT_CHARGEMENT) } 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 page 1...", "1B") if (ongletDetecte = 1) { SetTimer(TimeoutEtat1B, 0) EtatActuel := 2 } case 2: ; Onglet 1 repéré - remplir formulaire StatusGUI.Update("Onglet 1 détecté, remplissage...", "2") if (ongletDetecte = 1) { RemplirOnglet1() EtatActuel := "2B" SetTimer(TimeoutEtat2B, TIMEOUT_CHARGEMENT) } else if (ongletDetecte = 2) { EtatActuel := 3 } else if (ongletDetecte = 3) { EtatActuel := 4 } case "2B": ; Attente chargement onglet 2 StatusGUI.Update("Attente chargement page 2...", "2B") if (ongletDetecte = 2) { SetTimer(TimeoutEtat2B, 0) EtatActuel := 3 } case 3: ; Onglet 2 repéré - remplir formulaire recherche StatusGUI.Update("Onglet 2 détecté, recherche...", "3") if (ongletDetecte = 2) { RemplirOnglet2() EtatActuel := "3B" SetTimer(TimeoutEtat3B, TIMEOUT_CHARGEMENT) } else if (ongletDetecte = 3) { EtatActuel := 4 } case "3B": ; Attente chargement onglet 3 StatusGUI.Update("Attente résultats...", "3B") if (ongletDetecte = 3) { SetTimer(TimeoutEtat3B, 0) EtatActuel := 4 } case 4: ; Onglet 3 repéré - vérifier état cible StatusGUI.Update("Vérification des résultats...", "4") if (ongletDetecte = 3) { if VerifierEtatCible() { ; RENDEZ-VOUS TROUVÉ! EtatActuel := 5 } else { ; Pas de RDV, rafraîchir après délai aléatoire delai := Random(DELAI_REFRESH_MIN, DELAI_REFRESH_MAX) StatusGUI.Update("Aucun RDV. Refresh dans " . Round(delai/1000) . "s...", "4") Sleep(delai) RefreshPage() EtatActuel := "3B" SetTimer(TimeoutEtat3B, TIMEOUT_CHARGEMENT) } } else { ; Page perdue, retour à l'état 1 EtatActuel := 1 } case 5: ; État cible atteint - ALARME! StatusGUI.Update("🎉 RENDEZ-VOUS TROUVÉ!", "5") SetTimer(MonitorLoop, 0) MonitorGUI.ShowAlarm() } } ; ============================================================================ ; Timeouts ; ============================================================================ TimeoutEtat1B() { global EtatActuel SetTimer(TimeoutEtat1B, 0) StatusGUI.Update("Timeout - Réessai...", "1B") EtatActuel := 1 } TimeoutEtat2B() { global EtatActuel SetTimer(TimeoutEtat2B, 0) StatusGUI.Update("Timeout - Réessai...", "2B") EtatActuel := 1 } TimeoutEtat3B() { global EtatActuel SetTimer(TimeoutEtat3B, 0) StatusGUI.Update("Timeout - Réessai...", "3B") EtatActuel := 1 } ; ============================================================================ ; Alarme sonore ; ============================================================================ PlayAlarmSound() { global AlarmeSonne if AlarmeSonne { SoundBeep(1000, 300) Sleep(100) SoundBeep(1500, 300) } } ; ============================================================================ ; Démarrage de l'application ; ============================================================================ MonitorGUI.Show()