- concierge-live.js : variante (a), la JEEP identifiee par Uconnect recoit une ligne d'info sans question ; attente de 15 min avant de questionner une voiture inconnue ; releves km ; reponse sur une ligne ; simulation sans ecriture d'etat. - concierge-write.js (EVCS) : niveau et releve km, tests. - lecture-tableau.py : lecture d'une photo de tableau de bord par la Spark, confirmation avant envoi au concierge, % ignore si branchee depuis > 10 min. - stoik-transfert.py : rapports Stoik de la boite AIRTRIA vers la Gmail, une fois. - AGENTS.md et reference-concierge.md alignes. - .gitignore : deux scripts ajoutes, DREAMS de l'agent bilans exclu.
78 lines
4.7 KiB
Python
78 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
# stoik-transfert.py — fait suivre vers la Gmail de Thierry chaque rapport Stoïk (protect@stoik.io)
|
|
# reçu sur la boîte AIRTRIA avec un PDF joint, UNE SEULE FOIS par mail (03/10/2026, « GO transfert Stoïk »).
|
|
# Motif : ATELIER_OP (autre site, sans chemin réseau vers spark) récupère les PDF via Gmail pour son
|
|
# bilan sécurité. Lancé par le cron airtria-tri-quotidien (19:50), après airtria-menage.py.
|
|
# python3 stoik-transfert.py → transfère les nouveaux rapports
|
|
# python3 stoik-transfert.py --essai → liste ce qui serait envoyé, n'envoie rien, n'écrit pas l'état
|
|
# python3 stoik-transfert.py --amorcer → marque les rapports déjà présents comme transférés (mise en service)
|
|
# Lecture IMAP seule (BODY.PEEK, sélection readonly) : rien n'est marqué lu, déplacé ni supprimé.
|
|
# État : stoik-transfert-state.json (Message-ID déjà transférés). Secrets dans /home/node/.openclaw/.env.
|
|
import imaplib, ssl, socket, email, email.header, email.utils, json, os, sys, subprocess, tempfile, shutil
|
|
|
|
WS = "/home/node/.openclaw/workspace"
|
|
STATE = os.path.join(WS, "stoik-transfert-state.json")
|
|
GMAIL = "tallagrand.thierry@gmail.com"
|
|
ESSAI = "--essai" in sys.argv
|
|
AMORCE = "--amorcer" in sys.argv # marque les rapports existants comme déjà transférés, sans envoyer
|
|
|
|
env = dict(l.strip().split("=", 1) for l in open("/home/node/.openclaw/.env") if "=" in l and not l.startswith("#"))
|
|
PWD = env["AIRTRIA_IMAP_PASSWORD"].strip().strip('"').strip("'")
|
|
|
|
class _IMAPSurSocket(imaplib.IMAP4):
|
|
def __init__(self, sock): self._s = sock; imaplib.IMAP4.__init__(self, "", 993)
|
|
def open(self, host, port, timeout=None): self.host, self.port, self.sock = host, port, self._s; self.file = self.sock.makefile("rb")
|
|
|
|
def dec(v):
|
|
try: return str(email.header.make_header(email.header.decode_header(v or "")))
|
|
except Exception: return str(v or "")
|
|
|
|
deja = set(json.load(open(STATE))) if os.path.exists(STATE) else set()
|
|
ctx = ssl.create_default_context() # certificat vérifié sur le nom SNI
|
|
m = _IMAPSurSocket(ctx.wrap_socket(socket.create_connection(("10.10.10.22", 993), 120),
|
|
server_hostname="airtria-dsm2.direct.quickconnect.to"))
|
|
m.login("TMZF5455", PWD)
|
|
m.select("INBOX", readonly=True)
|
|
typ, d = m.uid("search", None, 'FROM "stoik.io"')
|
|
a_envoyer = []
|
|
for u in d[0].split():
|
|
typ, r = m.uid("fetch", u, "(BODY.PEEK[])")
|
|
msg = email.message_from_bytes(r[0][1])
|
|
mid = (msg.get("Message-ID") or f"uid-{u.decode()}").strip()
|
|
if mid in deja: continue
|
|
pdfs = [(dec(p.get_filename()), p.get_payload(decode=True)) for p in msg.walk()
|
|
if p.get_filename() and dec(p.get_filename()).lower().endswith(".pdf")]
|
|
if not pdfs: continue
|
|
a_envoyer.append((mid, email.utils.parsedate_to_datetime(msg["Date"]), dec(msg["Subject"]), pdfs))
|
|
m.logout()
|
|
|
|
if AMORCE:
|
|
deja.update(mid for mid, *_ in a_envoyer); json.dump(sorted(deja), open(STATE, "w"), indent=1)
|
|
print(f"stoik-transfert : {len(a_envoyer)} rapport(s) existant(s) marqué(s) comme déjà transférés, rien envoyé."); sys.exit(0)
|
|
if not a_envoyer:
|
|
print("stoik-transfert : aucun nouveau rapport Stoïk avec PDF."); sys.exit(0)
|
|
envoyes = 0
|
|
for mid, dt, sujet, pdfs in a_envoyer:
|
|
noms = [f"{dt.strftime('%Y-%m-%d')}_{n}" for n, _ in pdfs]
|
|
if ESSAI:
|
|
print(f"[ESSAI] enverrait : {dt.strftime('%d/%m/%Y')} | {sujet} | {', '.join(noms)}"); continue
|
|
tmp = tempfile.mkdtemp(prefix="stoik-")
|
|
try:
|
|
att = []
|
|
for nom, (_, data) in zip(noms, pdfs):
|
|
chemin = os.path.join(tmp, nom); open(chemin, "wb").write(data); att.append(f"--attach={chemin}")
|
|
corps = (f"Rapport Stoïk reçu le {dt.strftime('%d/%m/%Y')} sur la boîte airtria.fr (protect@stoik.io), "
|
|
f"transféré automatiquement par le tri du soir.\n\nÀ enregistrer sur le poste d'ATELIER_OP dans "
|
|
f"~/Documents/SYNERIC/securite/stoik/ pour son bilan sécurité.")
|
|
res = subprocess.run(["gog", "gmail", "send", "-a", GMAIL, "--to", GMAIL,
|
|
"--subject", f"[Stoïk → ATELIER_OP] {sujet} ({dt.strftime('%d/%m/%Y')})",
|
|
"--body", corps] + att, capture_output=True, text=True, timeout=180)
|
|
if res.returncode != 0:
|
|
print(f"stoik-transfert : ÉCHEC d'envoi pour « {sujet} » : {res.stderr.strip()[:300]}"); continue
|
|
deja.add(mid); envoyes += 1
|
|
json.dump(sorted(deja), open(STATE, "w"), indent=1)
|
|
print(f"stoik-transfert : envoyé « {sujet} » du {dt.strftime('%d/%m/%Y')} ({len(att)} PDF)")
|
|
finally:
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
if not ESSAI: print(f"stoik-transfert : {envoyes} rapport(s) transféré(s).")
|