ForcePilot/backend/package/yuxi/channel/extensions/bluebubbles/webhook.py
Kris b018e3eda4 feat(bluebubbles): 新增BlueBubbles(iMessage)渠道插件完整实现
该提交新增了完整的BlueBubbles渠道插件,支持通过BlueBubbles Server集成iMessage功能,包含以下核心能力:
1.  支持私聊和群聊会话管理,自动区分会话类型
2.  完整的消息收发支持,包括文本、图片、语音、文件、视频消息
3.  支持消息反应、已读回执、消息编辑与撤回
4.  内置去重、防抖处理机制
5.  支持Webhook和WebSocket两种事件接收方式
6.  完善的权限与安全校验机制
7.  历史消息同步与抓包功能
8.  TTS语音合成与发送支持
9.  群组管理能力,包括重命名、修改头像、增减成员等
2026-05-21 10:40:33 +08:00

94 lines
2.9 KiB
Python

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