ForcePilot/backend/package/yuxi/channel/extensions/workplace/webhook.py
Kris 7215418610 feat(channel): 添加企业微信、微博、WhatsApp 和 Workplace 渠道扩展
新增企业微信、微博、WhatsApp、Workplace 四个渠道扩展。

企业微信渠道扩展主要模块:config, gateway, webhook, webhook_bot, outbound, streaming, pairing, security, crypto, dedupe, persistent_dedupe, card, directory, events, externalcontact, media, mentions, menu, message, oauth, status

微博渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, passive_reply, broadcast, message, menu, media, subscription, template, status

WhatsApp 渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, actions, monitor, status

Workplace 渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, dedupe, actions, challenge, groups, media, mentions, menu, monitor, persona, quick_reply, signature, subscriptions, template, threading, users, status
2026-05-21 12:01:56 +08:00

89 lines
3.0 KiB
Python

import hashlib
import hmac
import json
import logging
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import JSONResponse, PlainTextResponse
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/channel/workplace", tags=["workplace-webhook"])
@router.get("/webhook")
async def verify_webhook(
hub_mode: str = Query(None, alias="hub.mode"),
hub_challenge: str = Query(None, alias="hub.challenge"),
hub_verify_token: str = Query(None, alias="hub.verify_token"),
):
if hub_mode != "subscribe":
raise HTTPException(status_code=400, detail="Invalid hub.mode")
from yuxi.channel.extensions.workplace.config import WorkplaceConfigAdapter
adapter = WorkplaceConfigAdapter()
account = await adapter.resolve_account("default")
expected_token = account.get("verify_token", "")
if not expected_token or hub_verify_token != expected_token:
logger.warning("Webhook verify_token mismatch")
raise HTTPException(status_code=403, detail="Verify token mismatch")
logger.info("Webhook verified successfully")
return PlainTextResponse(hub_challenge or "", status_code=200)
@router.post("/webhook")
async def receive_webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-Hub-Signature-256", "")
from yuxi.channel.extensions.workplace.config import WorkplaceConfigAdapter
adapter = WorkplaceConfigAdapter()
account = await adapter.resolve_account("default")
app_secret = account.get("app_secret", "")
if app_secret and not _verify_signature(body, signature, app_secret):
logger.warning("Webhook signature verification failed")
raise HTTPException(status_code=403, detail="Invalid signature")
try:
payload = json.loads(body)
except json.JSONDecodeError:
logger.error("Webhook payload is not valid JSON")
raise HTTPException(status_code=400, detail="Invalid JSON")
from yuxi.channel.extensions.workplace.gateway import _get_webhook_queue
queue = _get_webhook_queue()
if queue is not None:
entries = payload.get("entry", [])
enqueued = 0
for entry in entries:
for messaging_event in entry.get("messaging", []):
await queue.put(messaging_event)
enqueued += 1
for change in entry.get("changes", []):
await queue.put(change)
enqueued += 1
if enqueued == 0:
await queue.put(payload)
else:
logger.debug("Webhook enqueued %d events", enqueued)
return JSONResponse({"status": "ok"})
def _verify_signature(body: bytes, signature: str, app_secret: str) -> bool:
if not signature or not app_secret:
return False
expected = hmac.new(
app_secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
expected_header = f"sha256={expected}"
return hmac.compare_digest(expected_header.encode("utf-8"), signature.encode("utf-8"))