#!/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).")