diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 340e5b51a..bc96cc948 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -24,6 +24,18 @@ TELEGRAM_WEBHOOK_SECRET= GATEWAY_BASE_URL=http://localhost:8000 GATEWAY_TELEGRAM_INTAKE_MODE=webhook +# WhatsApp Gateway +# GATEWAY_WHATSAPP_INTAKE_MODE: cloud for Meta Cloud API, baileys for self-hosted bridge, disabled to skip WhatsApp intake +GATEWAY_WHATSAPP_INTAKE_MODE=disabled +WHATSAPP_SHARED_BUSINESS_TOKEN= +WHATSAPP_SHARED_PHONE_NUMBER_ID= +WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER= +WHATSAPP_SHARED_WABA_ID= +WHATSAPP_GRAPH_API_VERSION=v25.0 +WHATSAPP_WEBHOOK_VERIFY_TOKEN= +WHATSAPP_WEBHOOK_APP_SECRET= +WHATSAPP_BRIDGE_URL=http://whatsapp-bridge:3000 + # Platform Web Search (SearXNG) # Set this to enable built-in web search. Docker Compose sets it automatically. # Only uncomment if running the backend outside Docker (e.g. uvicorn on host). diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index c77b95fde..afccb190b 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -553,6 +553,23 @@ class Config: raise ValueError( "GATEWAY_TELEGRAM_INTAKE_MODE must be one of: webhook, longpoll, disabled" ) + WHATSAPP_SHARED_BUSINESS_TOKEN = os.getenv("WHATSAPP_SHARED_BUSINESS_TOKEN") + WHATSAPP_SHARED_PHONE_NUMBER_ID = os.getenv("WHATSAPP_SHARED_PHONE_NUMBER_ID") + WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER = os.getenv( + "WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER" + ) + WHATSAPP_SHARED_WABA_ID = os.getenv("WHATSAPP_SHARED_WABA_ID") + WHATSAPP_GRAPH_API_VERSION = os.getenv("WHATSAPP_GRAPH_API_VERSION", "v25.0") + WHATSAPP_WEBHOOK_VERIFY_TOKEN = os.getenv("WHATSAPP_WEBHOOK_VERIFY_TOKEN") + WHATSAPP_WEBHOOK_APP_SECRET = os.getenv("WHATSAPP_WEBHOOK_APP_SECRET") + WHATSAPP_BRIDGE_URL = os.getenv("WHATSAPP_BRIDGE_URL", "http://whatsapp-bridge:3000") + GATEWAY_WHATSAPP_INTAKE_MODE = os.getenv( + "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled" + ).lower() + if GATEWAY_WHATSAPP_INTAKE_MODE not in {"cloud", "baileys", "disabled"}: + raise ValueError( + "GATEWAY_WHATSAPP_INTAKE_MODE must be one of: cloud, baileys, disabled" + ) # Stripe checkout for pay-as-you-go page packs STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY") diff --git a/surfsense_backend/app/gateway/accounts.py b/surfsense_backend/app/gateway/accounts.py index 3e0d86e46..7379336a7 100644 --- a/surfsense_backend/app/gateway/accounts.py +++ b/surfsense_backend/app/gateway/accounts.py @@ -7,10 +7,10 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.config import config from app.db import ( + ExternalChatAccount, ExternalChatAccountMode, ExternalChatHealthStatus, ExternalChatPlatform, - ExternalChatAccount, ) from app.utils.oauth_security import TokenEncryption @@ -50,3 +50,31 @@ async def get_or_create_system_telegram_account( await session.flush() return account + +async def get_or_create_system_whatsapp_account( + session: AsyncSession, +) -> ExternalChatAccount: + result = await session.execute( + select(ExternalChatAccount).where( + ExternalChatAccount.platform == ExternalChatPlatform.WHATSAPP, + ExternalChatAccount.is_system_account.is_(True), + ) + ) + account = result.scalars().first() + if account is not None: + return account + account = ExternalChatAccount( + platform=ExternalChatPlatform.WHATSAPP, + mode=ExternalChatAccountMode.CLOUD_SHARED, + is_system_account=True, + cursor_state={ + "phone_number_id": config.WHATSAPP_SHARED_PHONE_NUMBER_ID, + "display_phone_number": config.WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER, + "waba_id": config.WHATSAPP_SHARED_WABA_ID, + }, + health_status=ExternalChatHealthStatus.UNKNOWN, + ) + session.add(account) + await session.flush() + return account +