loko/loko/scripts/create_outlook_drafts.py

267 lines
9.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Script pour la création de brouillons d'e-mails pour Outlook (PWA / Web sous Ubuntu).
Consomme le fichier 'drafts_payload.json' (ou le dossier généré lors de la création d'utilisateurs).
Modes disponibles :
- pwa (défaut) : Ouvre directement les deep links de composition dans Chromium avec la session Outlook connectée.
- html : Ouvre la page web interactive locale 'open_drafts.html'.
- eml : Exporte des fichiers brouillons standards .eml (RFC822 avec X-Unsent: 1).
Usage :
python scripts/create_outlook_drafts.py chemin/vers/drafts_payload.json
python scripts/create_outlook_drafts.py chemin/vers/drafts_payload.json --step
python scripts/create_outlook_drafts.py chemin/vers/drafts_payload.json --delay 3
python scripts/create_outlook_drafts.py chemin/vers/drafts_payload.json --mode html
python scripts/create_outlook_drafts.py chemin/vers/drafts_payload.json --mode eml
"""
import argparse
import email.message
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.parse
import webbrowser
from pathlib import Path
import re
from typing import Optional
def find_chromium_cmd() -> list:
"""Détecte la commande Chromium disponible pour ouvrir la session PWA sous Ubuntu."""
candidates = [
"/snap/bin/chromium",
"chromium",
"chromium-browser",
"google-chrome",
"google-chrome-stable",
]
for cmd in candidates:
if shutil.which(cmd):
return [cmd, "--profile-directory=Default"]
return []
def find_pwa_app_id() -> Optional[str]:
"""Recherche l'App-ID de la PWA Outlook dans ~/.local/share/applications/"""
app_dir = Path.home() / ".local" / "share" / "applications"
if app_dir.exists():
for desktop_file in app_dir.glob("*outlook*.desktop"):
try:
content = desktop_file.read_text(encoding="utf-8")
match = re.search(r"--app-id=([a-zA-Z0-9]+)", content)
if match:
return match.group(1)
except Exception:
continue
# Fallback sur l'App ID standard Outlook PWA Chromium
return "eoficlgicibekocmfdomjbfnjmehnhcd"
def create_eml_draft(to_email: str, subject: str, body: str, output_path: Path):
"""Génère un fichier .eml standard marqué comme brouillon (X-Unsent: 1)."""
msg = email.message.EmailMessage()
msg["To"] = to_email
msg["Subject"] = subject
msg["X-Unsent"] = "1" # Marqueur standard de brouillon pour Outlook, Thunderbird, Apple Mail
msg.set_content(body)
with open(output_path, "wb") as f:
f.write(msg.as_bytes())
def main():
parser = argparse.ArgumentParser(
description="Crée des brouillons d'e-mails dans Outlook PWA / Web ou génère des fichiers brouillons."
)
parser.add_argument(
"input",
type=str,
help="Chemin vers le fichier drafts_payload.json ou le dossier de sortie de création d'utilisateurs.",
)
parser.add_argument(
"--mode",
choices=["pwa", "html", "eml"],
default="pwa",
help="Mode de traitement : 'pwa' (ouvre dans Outlook PWA), 'html' (ouvre la page locale), 'eml' (exporte des fichiers .eml).",
)
parser.add_argument(
"--delay",
type=float,
default=6.0,
help="Délai en secondes entre l'ouverture de chaque brouillon en mode automatique (défaut : 6.0s).",
)
parser.add_argument(
"--step",
action="store_true",
default=False,
help="Mode pas-à-pas : demande confirmation avant d'ouvrir chaque brouillon.",
)
parser.add_argument(
"--only",
type=str,
default=None,
help="Filtre pour n'ouvrir que les brouillons correspondant à ce texte (nom, email ou login).",
)
parser.add_argument(
"--dry-run",
action="store_true",
default=False,
help="Affiche les actions prévues sans ouvrir de navigateur ni créer de fichiers.",
)
args = parser.parse_args()
input_path = Path(args.input)
if not input_path.exists():
sys.stderr.write(f"Erreur : '{args.input}' est introuvable.\n")
sys.exit(1)
# Résoudre le fichier drafts_payload.json
payload_file = input_path
if input_path.is_dir():
payload_file = input_path / "drafts_payload.json"
if not payload_file.exists():
sys.stderr.write(f"Erreur : Aucun fichier drafts_payload.json trouvé dans '{input_path}'.\n")
sys.exit(1)
try:
with open(payload_file, "r", encoding="utf-8") as f:
drafts = json.load(f)
except Exception as exc:
sys.stderr.write(f"Erreur lors de la lecture de {payload_file} : {exc}\n")
sys.exit(1)
if not isinstance(drafts, list) or not drafts:
print("Aucun brouillon trouvé dans la charge utile.")
return
if args.only:
pattern = args.only.lower()
drafts = [
d
for d in drafts
if pattern in d.get("to", "").lower()
or pattern in d.get("first_name", "").lower()
or pattern in d.get("last_name", "").lower()
or pattern in d.get("username", "").lower()
]
print(f"Filtre '{args.only}' appliqué : {len(drafts)} brouillon(s) sélectionné(s).")
print(f"Chargement de {len(drafts)} brouillon(s) depuis {payload_file.name}...")
# Mode HTML
if args.mode == "html":
html_file = payload_file.parent / "open_drafts.html"
if not html_file.exists():
sys.stderr.write(f"Page HTML introuvable à {html_file}.\n")
sys.exit(1)
print(f"Ouverture de la page interactive : {html_file.resolve()}")
if not args.dry_run:
webbrowser.open(f"file://{html_file.resolve()}")
return
# Mode EML
if args.mode == "eml":
eml_dir = payload_file.parent / "eml_drafts"
if not args.dry_run:
eml_dir.mkdir(parents=True, exist_ok=True)
print(f"Génération des fichiers .eml dans {eml_dir.resolve()}...")
for item in drafts:
username = item.get("username", "user")
out_eml = eml_dir / f"{username}_draft.eml"
print(f" -> {out_eml.name} ({item.get('to')})")
if not args.dry_run:
create_eml_draft(
to_email=item.get("to", ""),
subject=item.get("subject", ""),
body=item.get("body", ""),
output_path=out_eml,
)
print("Fichiers .eml générés avec succès.")
return
# Mode PWA (Défaut)
chromium_cmd = find_chromium_cmd()
pwa_app_id = find_pwa_app_id()
if chromium_cmd and pwa_app_id:
print(f"Session PWA Outlook détectée (App ID : {pwa_app_id})")
print("Les brouillons vont être envoyés directement dans votre fenêtre Outlook ouverte.")
elif chromium_cmd:
print(f"Navigateur détecté pour la session PWA : {' '.join(chromium_cmd)}")
else:
print("Utilisation du navigateur système par défaut.")
print("\n" + "=" * 60)
print(f"Ouverture des brouillons dans Outlook ({len(drafts)} message(s))")
print("Outlook sauvegardera automatiquement chaque message dans le dossier 'Brouillons'.")
print("=" * 60 + "\n")
for idx, item in enumerate(drafts, start=1):
to_email = item.get("to", "")
subject = item.get("subject", "")
first_name = item.get("first_name", "")
last_name = item.get("last_name", "")
# Reconstruire le deep link pour garantir l'encodage %20 des espaces (supprime les '+' intempestifs)
body = item.get("body", "")
params = urllib.parse.urlencode(
{
"to": to_email,
"subject": subject,
"body": body,
},
quote_via=urllib.parse.quote,
)
deeplink = f"https://outlook.cloud.microsoft/mail/deeplink/compose?{params}"
print(f"[{idx}/{len(drafts)}] {first_name} {last_name} <{to_email}>")
if args.step:
choice = input(" -> Appuyez sur Entrée pour ouvrir dans Outlook (ou 'q' pour quitter, 's' pour passer) : ").strip().lower()
if choice == "q":
print("Arrêt demandé par l'utilisateur.")
break
elif choice == "s":
print(" Passé.")
continue
if args.dry_run:
print(f" [DRY-RUN] Ouverture de : {deeplink[:90]}...")
else:
if chromium_cmd:
try:
cmd = chromium_cmd + [deeplink]
res = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=15,
)
if res.returncode == 0:
print(" -> Brouillon transmis avec succès à votre session Outlook !")
else:
webbrowser.open(deeplink)
print(" -> Brouillon ouvert via le navigateur par défaut.")
except Exception:
webbrowser.open(deeplink)
print(" -> Brouillon ouvert via le navigateur par défaut.")
else:
webbrowser.open(deeplink)
print(" -> Brouillon ouvert dans votre navigateur !")
if not args.step and idx < len(drafts) and not args.dry_run:
print(f" Attente de {args.delay}s pour laisser Outlook enregistrer le brouillon...")
time.sleep(args.delay)
print("\nTous les brouillons ont été traités.")
if __name__ == "__main__":
main()