Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.
1#!/bin/zsh2# Jeton Bearer QUB (Cognito id-token, valide 1 h) obtenu en pilotant Safari par3# AppleScript — solution de repli (ou de secours) à scripts/qub-token.mjs, qui4# passe par Scrapfly et échoue parfois sur l'anti-bot de connect.qub.ca.5#6# S'exécute SUR LE LAPTOP (Safari + cliclick). Imprime le jeton sur stdout, les7# étapes sur stderr. Identifiants : QUB_EMAIL / QUB_PASSWORD dans l'environnement8# ou dans ~/.config/vrai-prix/qub.env (chmod 600).9#10# scripts/qub-token-safari.sh # → jeton11# scripts/ingest-via-safari.sh [--since=…] # jeton + ingestion sur le nœud12set -u13PAGE='https://www.journaldequebec.com/argent/immobilier/transactions-immobilieres'14[[ -f ~/.config/vrai-prix/qub.env ]] && source ~/.config/vrai-prix/qub.env15: "${QUB_EMAIL:?QUB_EMAIL manquant}" "${QUB_PASSWORD:?QUB_PASSWORD manquant}"16command -v cliclick >/dev/null || { echo "cliclick requis (brew install cliclick)" >&2; exit 2; }1718log() { print -u2 -- "[qub-token-safari] $*"; }19js() { local code=${1//\"/\\\"}; osascript -e "tell application \"Safari\" to do JavaScript \"$code\" in front document" 2>/dev/null; }20url() { osascript -e 'tell application "Safari" to return URL of front document' 2>/dev/null; }2122# jeton via /api/checklogin (XHR synchrone, même origine) — vide si non connecté23grab() {24 js '(function(){try{var x=new XMLHttpRequest();x.open("GET","/api/checklogin",false);x.send();var j=JSON.parse(x.responseText);return j.userToken||""}catch(e){return ""}})()'25}2627osascript -e "tell application \"Safari\"28 activate29 make new document with properties {URL:\"$PAGE\"}30end tell" >/dev/null31sleep 63233tok=$(grab)34if [[ -n "$tok" ]]; then log "session déjà connectée"; print -- "$tok"; exit 0; fi3536log "non connecté : ouverture de connect.qub.ca"37js 'Array.from(document.querySelectorAll("a")).filter(e=>/Me connecter|Se connecter/i.test(e.innerText))[0].click()' >/dev/null38sleep 73940# écran « Sélectionner un compte » → formulaire classique41if js 'document.body.innerText.includes("Changer de compte")' | grep -q true; then42 js 'Array.from(document.querySelectorAll("a")).filter(e=>/Changer de compte/i.test(e.innerText))[0].click()' >/dev/null43 sleep 544fi4546if ! js 'String(!!document.querySelector("input[type=email]"))' | grep -q true; then47 log "formulaire de connexion introuvable ($(url))"; exit 348fi4950# remplissage (setter natif + événements React), puis fermeture du remplissage51# automatique Touch ID qui s'ouvre au focus, puis VRAI clic souris sur le bouton52# (un click() JavaScript n'est pas honoré par le formulaire connect.qub.ca)53js "(function(){function set(el,v){Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set.call(el,v);el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}))}set(document.querySelector('input[type=email]'),'$QUB_EMAIL');set(document.querySelector('input[type=password]'),'$QUB_PASSWORD');return 'ok'})()" >/dev/null54sleep 155osascript -e 'tell application "System Events" to key code 53' # Échap : popup Touch ID56sleep 157pos=$(js '(function(){var b=document.querySelector("button[type=submit]");var r=b.getBoundingClientRect();return [Math.round(r.left+r.width/2),Math.round(r.top+r.height/2),window.outerHeight-window.innerHeight].join(",")})()')58bounds=$(osascript -e 'tell application "Safari" to get bounds of front window')59x0=${${(s:, :)bounds}[1]}; y0=${${(s:, :)bounds}[2]}60bx=${${(s:,:)pos}[1]}; by=${${(s:,:)pos}[2]}; chrome=${${(s:,:)pos}[3]}61osascript -e 'tell application "Safari" to activate'; sleep 0.562cliclick c:$((x0 + bx)),$((y0 + chrome + by))63log "formulaire soumis, attente de l'OAuth…"6465for i in {1..10}; do66 sleep 367 if [[ "$(url)" == *journaldequebec.com* ]]; then68 tok=$(grab)69 if [[ -n "$tok" ]]; then log "jeton obtenu (essai $i)"; print -- "$tok"; exit 0; fi70 fi71done72log "échec : $(url)"; exit 473