117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
#
|
||
# Copyright (c) 2024–2025, Daily
|
||
#
|
||
# SPDX-License-Identifier: BSD 2-Clause License
|
||
#
|
||
|
||
"""server.py.
|
||
|
||
Webhook server to handle webhook coming from Daily, create a Daily room and start the bot.
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import shlex
|
||
import subprocess
|
||
from contextlib import asynccontextmanager
|
||
|
||
import aiohttp
|
||
import uvicorn
|
||
from dotenv import load_dotenv
|
||
from fastapi import FastAPI, HTTPException, Request
|
||
from fastapi.responses import JSONResponse
|
||
from utils.daily_helpers import create_daily_room
|
||
|
||
load_dotenv()
|
||
|
||
# ----------------- API ----------------- #
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
# Create aiohttp session to be used for Daily API calls
|
||
app.state.session = aiohttp.ClientSession()
|
||
yield
|
||
# Close session when shutting down
|
||
await app.state.session.close()
|
||
|
||
|
||
app = FastAPI(lifespan=lifespan)
|
||
|
||
|
||
@app.post("/start")
|
||
async def handle_incoming_daily_webhook(request: Request) -> JSONResponse:
|
||
"""Handle incoming Daily call webhook."""
|
||
print("Received webhook from Daily")
|
||
|
||
# Get the dial-in properties from the request
|
||
try:
|
||
data = await request.json()
|
||
if "test" in data:
|
||
# Pass through any webhook checks
|
||
return JSONResponse({"test": True})
|
||
|
||
if not all(key in data for key in ["From", "To", "callId", "callDomain"]):
|
||
raise HTTPException(
|
||
status_code=400, detail="Missing properties 'From', 'To', 'callId', 'callDomain'"
|
||
)
|
||
|
||
# Extract the caller's phone number
|
||
caller_phone = str(data.get("From"))
|
||
print(f"Processing call from {caller_phone}")
|
||
|
||
# Create a Daily room with dial-in capabilities
|
||
try:
|
||
room_details = await create_daily_room(request.app.state.session, caller_phone)
|
||
except Exception as e:
|
||
print(f"Error creating Daily room: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Failed to create Daily room: {str(e)}")
|
||
|
||
room_url = room_details["room_url"]
|
||
token = room_details["token"]
|
||
print(f"Created Daily room: {room_url} with token: {token}")
|
||
|
||
body_json = json.dumps(data)
|
||
|
||
bot_cmd = f"python3 -m bot -u {room_url} -t {token} -b {shlex.quote(body_json)}"
|
||
|
||
try:
|
||
# CHANGE: Keep stdout/stderr for debugging
|
||
# Start the bot in the background but capture output
|
||
subprocess.Popen(
|
||
bot_cmd,
|
||
shell=True,
|
||
# Don't redirect output so we can see logs
|
||
# stdout=subprocess.DEVNULL,
|
||
# stderr=subprocess.DEVNULL
|
||
)
|
||
print(f"Started bot process with command: {bot_cmd}")
|
||
except Exception as e:
|
||
print(f"Error starting bot: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Failed to start bot: {str(e)}")
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
print(f"Unexpected error: {str(e)}")
|
||
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")
|
||
|
||
# Grab a token for the user to join with
|
||
return JSONResponse({"room_url": room_url, "token": token})
|
||
|
||
|
||
@app.get("/health")
|
||
async def health_check():
|
||
"""Simple health check endpoint."""
|
||
return {"status": "healthy"}
|
||
|
||
|
||
# ----------------- Main ----------------- #
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# Run the server
|
||
port = int(os.getenv("PORT", "7860"))
|
||
print(f"Starting server on port {port}")
|
||
uvicorn.run("server:app", host="0.0.0.0", port=port, reload=True)
|