"""Service Microsoft Teams — envoi de messages via Incoming Webhook. Webhook = simple, pas d'auth Azure AD requise. Format : Adaptive Card (titre + corps) ou simple "text" en fallback. À noter : Teams a déprécié les "Office 365 Connectors" (anciens webhooks chans Teams). Les nouveaux webhooks utilisent "Workflows" (Power Automate) qui acceptent un payload Adaptive Card. Ce service envoie un payload compatible avec les deux. """ import json import logging from datetime import datetime from typing import Dict, Any import requests log = logging.getLogger("patchcenter.teams") HTTP_TIMEOUT = 10 PROXY_URL = None # à override via Settings si besoin def _proxies(): if PROXY_URL: return {"http": PROXY_URL, "https": PROXY_URL} return None def _post(webhook_url: str, payload: Dict[str, Any]) -> Dict[str, Any]: """Poste un payload sur le webhook Teams. Retourne {ok, status, detail}.""" try: r = requests.post(webhook_url, json=payload, timeout=HTTP_TIMEOUT, proxies=_proxies()) ok = (200 <= r.status_code < 300) return { "ok": ok, "status": r.status_code, "detail": (r.text or "")[:500] if not ok else "Message envoyé", } except requests.exceptions.RequestException as e: return {"ok": False, "status": 0, "detail": f"Réseau: {e}"} def _adaptive_card(title: str, body_lines: list, color: str = "good") -> Dict[str, Any]: """Construit un payload Adaptive Card minimal (compatible Workflows). color : 'good' (vert) | 'warning' (orange) | 'attention' (rouge) | 'default'.""" facts = [] body_blocks = [ {"type": "TextBlock", "text": title, "weight": "Bolder", "size": "Medium", "color": color, "wrap": True} ] for ln in body_lines: if isinstance(ln, dict) and "title" in ln and "value" in ln: facts.append({"title": ln["title"], "value": str(ln["value"])}) else: body_blocks.append({"type": "TextBlock", "text": str(ln), "wrap": True, "spacing": "Small"}) if facts: body_blocks.append({"type": "FactSet", "facts": facts}) body_blocks.append({"type": "TextBlock", "text": f"_{datetime.now().strftime('%Y-%m-%d %H:%M')} — PatchCenter_", "size": "Small", "isSubtle": True, "spacing": "Medium"}) return { "type": "message", "attachments": [{ "contentType": "application/vnd.microsoft.card.adaptive", "contentUrl": None, "content": { "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "type": "AdaptiveCard", "version": "1.4", "body": body_blocks, } }] } def send_test_message(webhook_url: str, channel_name: str, sender: str) -> Dict[str, Any]: """Envoie un message de test pour valider la config du webhook.""" payload = _adaptive_card( title=f"✅ Test PatchCenter — canal '{channel_name}'", body_lines=[ "Ce message confirme que le webhook Teams est correctement configuré.", {"title": "Envoyé par", "value": sender}, {"title": "Source", "value": "PatchCenter > Settings > Teams"}, ], color="good", ) return _post(webhook_url, payload) def send_intervention_start(webhook_url: str, hostname: str, application: str, intervenant: str, planned_at: str = None) -> Dict[str, Any]: """Annonce le DÉBUT d'une intervention de patching.""" body = [ {"title": "Serveur", "value": hostname}, {"title": "Application", "value": application or "—"}, {"title": "Intervenant", "value": intervenant or "—"}, ] if planned_at: body.append({"title": "Prévu", "value": planned_at}) payload = _adaptive_card( title=f"🟧 Début intervention patching — {hostname}", body_lines=body, color="warning", ) return _post(webhook_url, payload) def send_intervention_end(webhook_url: str, hostname: str, application: str, intervenant: str, status: str = "ok") -> Dict[str, Any]: """Annonce la FIN d'une intervention. status = 'ok' | 'ko'.""" color = "good" if status == "ok" else "attention" icon = "✅" if status == "ok" else "❌" payload = _adaptive_card( title=f"{icon} Fin intervention patching — {hostname}", body_lines=[ {"title": "Serveur", "value": hostname}, {"title": "Application", "value": application or "—"}, {"title": "Intervenant", "value": intervenant or "—"}, {"title": "Statut technique", "value": status.upper()}, "_(En attente de validation par le responsable applicatif)_", ], color=color, ) return _post(webhook_url, payload) def send_planning_reminder(webhook_url: str, hostname: str, application: str, jour: str, heure: str, intervenant: str) -> Dict[str, Any]: """Rappel de planning (envoyable du jeudi/vendredi précédent jusqu'au jour J).""" payload = _adaptive_card( title=f"🗓 Rappel planning patching — {hostname}", body_lines=[ {"title": "Serveur", "value": hostname}, {"title": "Application", "value": application or "—"}, {"title": "Date prévue", "value": jour or "—"}, {"title": "Heure prévue", "value": heure or "—"}, {"title": "Intervenant", "value": intervenant or "—"}, "_Merci de confirmer la disponibilité applicative._", ], color="default", ) return _post(webhook_url, payload)