├── .gitignore ├── .vercelignore ├── .vscode └── settings.json ├── README.md ├── pyproject.toml ├── requirements.txt ├── src ├── api │ ├── main.py │ └── routes │ │ ├── appointments.py │ │ ├── health_check.py │ │ └── twillio_incoming_calls.py ├── core │ └── config.py ├── main.py └── utils │ ├── credentials.json │ ├── google_calendar.py │ └── helper.py ├── uv.lock └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | __pycache__/ 4 | lib/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | .git 2 | .venv 3 | .myenv 4 | .git/ 5 | .venv/ 6 | .myenv/ 7 | __pycache__/ 8 | .pytest_cache/ 9 | .env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "e:\\Rida\\PIAIC\\Quarter4\\Fastapi-Tools\\Customer_Engagement_Calling_Bot\\.venv\\Scripts\\python.exe" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | 1. uv pip install twilio 3 | 2. pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib 4 | ``` 5 | 6 | ```bash 7 | uvicorn src.main:app --reload 8 | ``` 9 | 10 | ```bash 11 | # Click this link to Unblock Secret and bypass push protection 12 | https://github.com/RidaNaz/Calling_Bot/security/secret-scanning/unblock-secret/2swy8X8AhUnLESz5h7K0JS5r07c 13 | ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "personalized_receptionist" 3 | version = "0.1.0" 4 | description = "personalized_receptionist using crewAI" 5 | authors = [ 6 | { name = "ridanaz", email = "rnaz3414@gmail.com" } 7 | ] 8 | requires-python = ">=3.10,<3.13" 9 | dependencies = [ 10 | ] 11 | 12 | [project.scripts] 13 | kickoff = "personalized_receptionist.main:kickoff" 14 | plot = "personalized_receptionist.main:plot" 15 | 16 | [build-system] 17 | requires = ["hatchling"] 18 | build-backend = "hatchling.build" 19 | 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi[all] 2 | python-dotenv 3 | twilio 4 | pydantic-settings 5 | uvicorn 6 | google-api-python-client 7 | google-auth-httplib2 8 | google-auth-oauthlib 9 | -------------------------------------------------------------------------------- /src/api/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from .routes import twillio_incoming_calls 3 | from .routes import health_check 4 | from .routes import appointments 5 | 6 | api_router = APIRouter() 7 | api_router.include_router( 8 | health_check.router, prefix="/health_check", tags=["health_check"] 9 | ) 10 | 11 | api_router.include_router( 12 | twillio_incoming_calls.router, prefix="/voice", tags=["incoming_calls"] 13 | ) 14 | 15 | api_router.include_router( 16 | appointments.router, prefix="/appointments", tags=["appointments"] 17 | ) -------------------------------------------------------------------------------- /src/api/routes/appointments.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from datetime import datetime 3 | from pydantic import BaseModel 4 | from datetime import datetime 5 | 6 | class Appointment(BaseModel): 7 | client_name: str 8 | email : str 9 | phone_number: str 10 | appointment_date: datetime 11 | 12 | router = APIRouter() 13 | appointments = [] # Temporary in-memory storage 14 | 15 | @router.post("/schedule") 16 | async def schedule_appointment(appointment: Appointment): 17 | if appointment.appointment_date < datetime.now(): 18 | raise HTTPException(status_code=400, detail="Cannot schedule for past dates.") 19 | 20 | appointments.append(appointment) 21 | return {"message": "Appointment scheduled successfully", "appointment": appointment} 22 | 23 | @router.get("/list") 24 | async def list_appointments(): 25 | return appointments -------------------------------------------------------------------------------- /src/api/routes/health_check.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | router = APIRouter() 4 | 5 | @router.get("/") 6 | async def health_check() -> bool: 7 | return True -------------------------------------------------------------------------------- /src/api/routes/twillio_incoming_calls.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request 2 | from datetime import datetime, timedelta 3 | from ...utils.google_calendar import create_appointment 4 | 5 | router = APIRouter() 6 | 7 | @router.post("/schedule") 8 | async def schedule_appointment(request: Request): 9 | data = await request.json() 10 | name = data.get("name") 11 | email = data.get("email") 12 | appointment_time = data.get("appointment_time") # Format: "2025-02-09T15:00:00" 13 | 14 | start_time = datetime.fromisoformat(appointment_time) 15 | end_time = start_time + timedelta(minutes=30) # 30-minute appointment 16 | 17 | link = create_appointment( 18 | summary=f"Appointment with {name}", 19 | description="Virtual Consultation", 20 | start_time=start_time.isoformat(), 21 | end_time=end_time.isoformat(), 22 | attendees_emails=[email] 23 | ) 24 | 25 | return {"message": "Appointment scheduled successfully!", "link": link} -------------------------------------------------------------------------------- /src/core/config.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Literal 2 | from pydantic import ( 3 | AnyUrl, 4 | BeforeValidator, 5 | computed_field, 6 | ) 7 | from pydantic_settings import BaseSettings, SettingsConfigDict 8 | 9 | from ..utils.helper import parse_cors 10 | 11 | 12 | class Settings(BaseSettings): 13 | model_config = SettingsConfigDict( 14 | env_file="../../.env", env_ignore_empty=True, extra="ignore" 15 | ) 16 | API_V1_STR: str = "/api/v1" 17 | DOMAIN: str = "localhost" 18 | ENVIRONMENT: Literal["local", "staging", "production"] = "local" 19 | 20 | @computed_field # type: ignore[prop-decorator] 21 | @property 22 | def server_host(self) -> str: 23 | # Use HTTPS for anything other than local development 24 | if self.ENVIRONMENT == "local": 25 | return f"http://{self.DOMAIN}" 26 | return f"https://{self.DOMAIN}" 27 | 28 | BACKEND_CORS_ORIGINS: Annotated[list[AnyUrl] | str, BeforeValidator(parse_cors)] = ( 29 | [] 30 | ) 31 | 32 | PROJECT_NAME: str 33 | 34 | settings = Settings() # type: ignore -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from dotenv import load_dotenv 4 | from twilio.rest import Client 5 | 6 | from fastapi import FastAPI 7 | from fastapi.routing import APIRoute 8 | from starlette.middleware.cors import CORSMiddleware 9 | 10 | from .core.config import settings 11 | from .api.main import api_router 12 | 13 | load_dotenv() 14 | 15 | def custom_generate_unique_id(route: APIRoute) -> str: 16 | return f"{route.tags[0]}-{route.name}" 17 | 18 | app = FastAPI( 19 | title=settings.PROJECT_NAME, 20 | openapi_url=f"{settings.API_V1_STR}/openapi.json", 21 | generate_unique_id_function=custom_generate_unique_id, 22 | ) 23 | 24 | 25 | TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID") 26 | TWILIO_AUTH_TOKEN= os.getenv("TWILIO_AUTH_TOKEN") 27 | auth_token = os.environ["TWILIO_AUTH_TOKEN"] 28 | 29 | client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) 30 | 31 | # Set all CORS enabled origins 32 | if settings.BACKEND_CORS_ORIGINS: 33 | app.add_middleware( 34 | CORSMiddleware, 35 | allow_origins=[ 36 | str(origin).strip("/") for origin in settings.BACKEND_CORS_ORIGINS 37 | ], 38 | allow_credentials=True, 39 | allow_methods=["*"], 40 | allow_headers=["*"], 41 | ) 42 | 43 | app.include_router(api_router, prefix=settings.API_V1_STR) 44 | 45 | async def log_operation(task_id: int): 46 | # Placeholder function to simulate an async background task 47 | print(f"Logging operation for item_id: {task_id}") -------------------------------------------------------------------------------- /src/utils/credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "callingbot-450716", 4 | "private_key_id": "39f702857a3932d28faa6137738ee285ec79c805", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDn2ZtmjnsYx2iK\n5joa6KIgzb1ivMwUmmg1qDz+fNT4TaP7+FMRH4SYUWgKAqk7dDQDpVQ16bUazGf1\n/wYfbuKwOkHE3bgh4fwLv1uZJ3VqMYV51DgZ4tXf1aET6PJwQm9NpyEaNww3f4U2\nQ+MW93AYd/tGFjNtgalP3x2InAIioibGRK3e2PJG1wUcrOqenfh9ntLgdvulvlUP\n3u/9yFYugZqBjyrt+nPH8KbQoIUqFIcjr2lzErNUrzJod+hHt0MAJi6V8hdy3AYN\nyzYbl8xRdiA5uQjhZHaCRHArSGQhp6SzNgj8iYr80l9WgaEA0BNeoqpN4mPann8b\nAt987UdFAgMBAAECggEAIdQOGM1muBOWVRfo9/aj+0HRpXezFjl+RX/14FLh9xXK\n/K97nUa6P8g29Cj1vMtEcJi9m/0s08tpysqKP+lbsYdag8ku/eCdeDBUQtkvSE6P\n8Vnt0pcWiil/LmrBfvAukce2LH9GLM3SJ7wJhRcW17cf5lbEa7Csvc7p3f4XUKqN\ngGNMi+WPuJ6PfaksSMasLdlDF6ncnd9VtXKI/ESgkPHNDuNrT5vEopXS73zZTbGh\nBFMiohb+ZW4HY0UWmAEjRaKbY44bUaRf9GB1ta+GLS6hoxB8XNfTO0+6IaYE7yij\n7+GaTIYzb+aNVdMJJSJ2O3xyIKqC1Ekpi00FkZj3uQKBgQD3EMst6DvyDdJTTGdV\nR/R3egcu34zBm4apuj9t5NyByvg6W0q3FM8nF0mUKnCd++N2bVeSVkhLL4UJ92nS\nwBBRo1bpW8JmkQ6Z2qALp4Vx4ubL96B6/5NnzNO+Rpk1Y3kbuV7rl7pY/quJZdm0\nzP/DpXAiQDE1Gp+HXSD8bbhXSQKBgQDwO/SV6vOC4NuEMeeaF4dB1OkMixYO6tyz\n5o9R1TLVUTVFfjGZiSi5EtAj9KDECNAqUG6A/OAGiWadKMcyFhzhE/CN0l8bgFc3\nGrivyzpqGfBcuDXfF7k9525k3648lf6+nOnv9xCBqBtiZwE4ptz/iUHorVfHIL//\nfIsuzPxEHQKBgQDTe1qP5zQ407/vcX0aMMd/5cJ0SwLPTvOMIVLeHILrzvN/8Fka\nMgPH8FUZMOLJOK3R13KYXqbvRLPafh/lOGY/m1Nv2Q2kghI/fDZ3A2RkwdbRTRvf\nbRmeRmQRgZjvbEBkjN5FR9FJEGWA7N/XtYpQzDIZ/1yfmjRnyD3OFZUvkQKBgQCl\ngbjlHER+E6pIrhUQzUrrKOMSODSPPHGnI53z0/08h8E6ylEjpSJAN1zcmKm1xRid\nPmKTTSSOfFUMy05JDIEC9DjTjG7nxIjRtwAsZMgu1wYI6wR6WZ7OrkYZCx3AMdqa\nugJVx5JAsW+EYNCvlg5FouucgOgrkT2Vp9k/+guGZQKBgQDzKMAdltuvv6TRhdI1\nqt7uoY/tmnBdO+L6eAFBFNkoN+ECgQUVZ8olKVJN3/8cF2yjCwVfx9T8sao521jQ\nJeNUrbglI45b2VwbfXZmqRA15e0Cb1LE6l9I1CfmdXXrXTde6ihcFsTyJladODJB\nagaH2LhVsMNDi0CKL0dXD5vCvQ==\n-----END PRIVATE KEY-----\n", 6 | "client_email": "calling-bot@callingbot-450716.iam.gserviceaccount.com", 7 | "client_id": "104042809244170168475", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://oauth2.googleapis.com/token", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/calling-bot%40callingbot-450716.iam.gserviceaccount.com", 12 | "universe_domain": "googleapis.com" 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/google_calendar.py: -------------------------------------------------------------------------------- 1 | from google.oauth2 import service_account 2 | from googleapiclient.discovery import build 3 | 4 | # Path to your credentials.json file 5 | SCOPES = ['https://www.googleapis.com/auth/calendar'] 6 | SERVICE_ACCOUNT_FILE = "src/utils/credentials.json" 7 | 8 | credentials = service_account.Credentials.from_service_account_file( 9 | SERVICE_ACCOUNT_FILE, scopes=SCOPES 10 | ) 11 | 12 | service = build('calendar', 'v3', credentials=credentials) 13 | 14 | def create_appointment(summary, description, start_time, end_time, attendees_emails): 15 | event = { 16 | 'summary': summary, 17 | 'description': description, 18 | 'start': {'dateTime': start_time, 'timeZone': 'Asia/Karachi'}, 19 | 'end': {'dateTime': end_time, 'timeZone': 'Asia/Karachi'}, 20 | # 'attendees': [{'email': email} for email in attendees_emails], 21 | 'reminders': { 22 | 'useDefault': False, 23 | 'overrides': [{'method': 'email', 'minutes': 30}, {'method': 'popup', 'minutes': 10}], 24 | }, 25 | } 26 | 27 | event = service.events().insert(calendarId='nazrida007@gmail.com', body=event).execute() 28 | print("Event Created:", event) 29 | return event.get('htmlLink') 30 | -------------------------------------------------------------------------------- /src/utils/helper.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | """ Parse CORS """ 4 | 5 | 6 | def parse_cors(v: Any) -> list[str] | str: 7 | if isinstance(v, str) and not v.startswith("["): 8 | return [i.strip() for i in v.split(",")] 9 | elif isinstance(v, list | str): 10 | return v 11 | raise ValueError(v) -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "src/main.py", 5 | "use": "@vercel/python" 6 | } 7 | ], 8 | "routes": [ 9 | { 10 | "src": "/(.*)", 11 | "dest": "src/main.py" 12 | } 13 | ] 14 | } --------------------------------------------------------------------------------