ForcePilot/backend/package/yuxi/channel/extensions/bluebubbles/webhook.py

94 lines
2.9 KiB
Python
Raw Normal View History

import hashlib
import hmac
import json
import logging
from http import HTTPStatus
from fastapi import APIRouter, Request, HTTPException, Response
from yuxi.channel.extensions.bluebubbles.monitor import process_inbound_message
logger = logging.getLogger(__name__)
def _constant_time_compare(a: str, b: str) -> bool:
return hmac.compare_digest(a.encode() if isinstance(a, str) else a, b.encode() if isinstance(b, str) else b)
class BlueBubblesWebhookHandler:
def __init__(
self,
webhook_secret: str = "",
account_id: str = "default",
dedupe_store=None,
debounce_manager=None,
on_message=None,
send_read_receipt=None,
):
self.webhook_secret = webhook_secret
self.account_id = account_id
self.dedupe_store = dedupe_store
self.debounce_manager = debounce_manager
self.on_message = on_message
self.send_read_receipt = send_read_receipt
async def handle_webhook(self, request: Request) -> Response:
if self.webhook_secret:
sig = request.headers.get("X-BB-Signature", "")
body = await request.body()
expected = hmac.new(
self.webhook_secret.encode(),
body,
hashlib.sha256,
).hexdigest()
if not _constant_time_compare(sig, expected):
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Invalid signature")
try:
body = await request.body()
data = json.loads(body)
except json.JSONDecodeError:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid JSON")
if isinstance(data, list):
for item in data:
await process_inbound_message(
item,
self.account_id,
dedupe_store=self.dedupe_store,
debounce_manager=self.debounce_manager,
on_message=self.on_message,
send_read_receipt=self.send_read_receipt,
)
else:
await process_inbound_message(
data,
self.account_id,
dedupe_store=self.dedupe_store,
debounce_manager=self.debounce_manager,
on_message=self.on_message,
send_read_receipt=self.send_read_receipt,
)
return Response(status_code=HTTPStatus.OK)
def create_webhook_router(
path: str = "/bluebubbles-webhook",
webhook_secret: str = "",
account_id: str = "default",
**kwargs,
) -> APIRouter:
router = APIRouter()
handler = BlueBubblesWebhookHandler(
webhook_secret=webhook_secret,
account_id=account_id,
**kwargs,
)
@router.post(path)
async def _webhook(request: Request):
return await handler.handle_webhook(request)
return router