"""Webhook server for phone system integration (runs on port 8081).""" import logging from datetime import datetime from fastapi import FastAPI from fastapi.responses import PlainTextResponse from database import get_db, log_activity from wled_client import build_wled_url, call_wled logger = logging.getLogger(__name__) webhook_app = FastAPI(title="Busylight Webhook") @webhook_app.get("/", response_class=PlainTextResponse) async def webhook_info(): return """Busylight Webhook API (Telefonanlage) Endpoints: GET/POST /{durchwahl}/callstart - Anruf gestartet (WLED -> Rot) GET/POST /{durchwahl}/callend - Anruf beendet (WLED -> zurueck auf Zeiterfassungs-Status) Beispiel: curl http://localhost:8081/123/callstart curl http://localhost:8081/123/callend """ def _get_user_by_extension(extension, conn): """Find user and their assignment by phone extension.""" return conn.execute(""" SELECT u.azure_id, u.display_name, a.segment, d.hostname, s.last_aida_status, s.last_teams_status, s.phone_active FROM users u JOIN assignments a ON a.user_azure_id = u.azure_id JOIN wled_devices d ON d.id = a.wled_device_id LEFT JOIN state s ON s.user_azure_id = u.azure_id WHERE u.phone_extension = ? AND u.active = 1 """, (extension,)).fetchone() def _get_color(status, conn): """Get color for a status.""" row = conn.execute( "SELECT r, g, b, effect FROM color_rules WHERE status = ?", (status,) ).fetchone() if row: return dict(row) return {"r": 200, "g": 200, "b": 200, "effect": ""} @webhook_app.get("/{extension}/callstart", response_class=PlainTextResponse) @webhook_app.post("/{extension}/callstart", response_class=PlainTextResponse) async def call_start(extension: str): conn = get_db() try: user = _get_user_by_extension(extension, conn) if not user: logger.warning("Webhook callstart: Durchwahl %s nicht gefunden", extension) return f"Unknown extension: {extension}" aida = user["last_aida_status"] or "" # Nur wenn anwesend oder homeoffice if aida not in ("anwesend", "homeoffice"): logger.info("Webhook callstart: %s ignoriert (AIDA=%s)", user["display_name"], aida) return f"Ignored: {user['display_name']} not in office (aida={aida})" # WLED auf Telefon-Farbe setzen color = _get_color("phone_active", conn) url = build_wled_url( user["hostname"], color["r"], color["g"], color["b"], segment=user["segment"], effect=color.get("effect", ""), ) success, result_text = call_wled(url) # phone_active Flag setzen conn.execute(""" UPDATE state SET phone_active=1, updated_at=? WHERE user_azure_id=? """, (datetime.now(), user["azure_id"])) conn.commit() log_activity( user["azure_id"], user["display_name"], aida, "phone_active", url, result_text, ) logger.info("Webhook callstart: %s -> %s", user["display_name"], result_text) return f"OK: {user['display_name']} call started" finally: conn.close() @webhook_app.get("/{extension}/callend", response_class=PlainTextResponse) @webhook_app.post("/{extension}/callend", response_class=PlainTextResponse) async def call_end(extension: str): conn = get_db() try: user = _get_user_by_extension(extension, conn) if not user: logger.warning("Webhook callend: Durchwahl %s nicht gefunden", extension) return f"Unknown extension: {extension}" aida = user["last_aida_status"] or "" if aida not in ("anwesend", "homeoffice"): return f"Ignored: {user['display_name']} not in office" # Farbe zuruecksetzen auf Zeiterfassungs-Status # (Teams-Farbe kommt automatisch beim naechsten Polling) if aida == "anwesend": color = _get_color("available", conn) elif aida == "homeoffice": color = _get_color("homeoffice", conn) else: color = _get_color("abwesend", conn) url = build_wled_url( user["hostname"], color["r"], color["g"], color["b"], segment=user["segment"], effect=color.get("effect", ""), ) success, result_text = call_wled(url) # phone_active Flag zuruecksetzen conn.execute(""" UPDATE state SET phone_active=0, updated_at=? WHERE user_azure_id=? """, (datetime.now(), user["azure_id"])) conn.commit() log_activity( user["azure_id"], user["display_name"], aida, "phone_end", url, result_text, ) logger.info("Webhook callend: %s -> %s (%s)", user["display_name"], result_text, aida) return f"OK: {user['display_name']} call ended" finally: conn.close()