diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 2d6c27e26..170bece32 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -119,11 +119,14 @@ CLICKUP_CLIENT_ID=your_clickup_client_id_here CLICKUP_CLIENT_SECRET=your_clickup_client_secret_here CLICKUP_REDIRECT_URI=http://localhost:8000/api/v1/auth/clickup/connector/callback -# Discord OAuth Configuration +# Discord OAuth / Gateway Configuration +# The Discord connector and Discord gateway use the same Discord application/bot. DISCORD_CLIENT_ID=your_discord_client_id_here DISCORD_CLIENT_SECRET=your_discord_client_secret_here DISCORD_REDIRECT_URI=http://localhost:8000/api/v1/auth/discord/connector/callback DISCORD_BOT_TOKEN=your_bot_token_from_developer_portal +GATEWAY_DISCORD_ENABLED=FALSE +GATEWAY_DISCORD_REDIRECT_URI=http://localhost:8000/api/v1/gateway/discord/callback # Atlassian OAuth Configuration (Jira & Confluence) ATLASSIAN_CLIENT_ID=your_atlassian_client_id_here diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index fdf8834c1..98c1d5dec 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -574,6 +574,10 @@ class Config: GATEWAY_SLACK_CLIENT_SECRET = os.getenv("SLACK_CLIENT_SECRET") GATEWAY_SLACK_SIGNING_SECRET = os.getenv("GATEWAY_SLACK_SIGNING_SECRET") GATEWAY_SLACK_REDIRECT_URI = os.getenv("GATEWAY_SLACK_REDIRECT_URI") + GATEWAY_DISCORD_ENABLED = ( + os.getenv("GATEWAY_DISCORD_ENABLED", "FALSE").upper() == "TRUE" + ) + GATEWAY_DISCORD_REDIRECT_URI = os.getenv("GATEWAY_DISCORD_REDIRECT_URI") # 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 5daf75c69..2d924e200 100644 --- a/surfsense_backend/app/gateway/accounts.py +++ b/surfsense_backend/app/gateway/accounts.py @@ -40,6 +40,19 @@ def slack_account_credentials(account: ExternalChatAccount) -> dict: return data if isinstance(data, dict) else {} +def discord_account_credentials(account: ExternalChatAccount) -> dict: + """Decrypt Discord gateway credentials stored as encrypted JSON.""" + if not account.encrypted_credentials: + return {} + raw = TokenEncryption(config.SECRET_KEY or "").decrypt_token(account.encrypted_credentials) + try: + data = json.loads(raw) + except json.JSONDecodeError: + # Backward-compatible fallback if a token string was stored directly. + return {"bot_token": raw} + return data if isinstance(data, dict) else {} + + async def get_or_create_system_telegram_account( session: AsyncSession, ) -> ExternalChatAccount: @@ -108,3 +121,18 @@ async def get_slack_account_by_team( ) return result.scalars().first() + +async def get_discord_account_by_guild( + session: AsyncSession, + *, + guild_id: str, +) -> ExternalChatAccount | None: + result = await session.execute( + select(ExternalChatAccount).where( + ExternalChatAccount.platform == ExternalChatPlatform.DISCORD, + ExternalChatAccount.is_system_account.is_(True), + ExternalChatAccount.cursor_state["guild_id"].astext == guild_id, + ) + ) + return result.scalars().first() + diff --git a/surfsense_backend/app/gateway/inbox.py b/surfsense_backend/app/gateway/inbox.py index 5769c8cc4..cd0e2f9b7 100644 --- a/surfsense_backend/app/gateway/inbox.py +++ b/surfsense_backend/app/gateway/inbox.py @@ -16,6 +16,10 @@ def slack_event_dedupe_key(event_id: int | str) -> str: return f"slack_event:{event_id}" +def discord_message_dedupe_key(message_id: int | str) -> str: + return f"discord_message:{message_id}" + + async def persist_inbound_event( session: AsyncSession, *,