267 lines
9.6 KiB
Python
Executable file
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()
|