ForcePilot/backend/package/yuxi/channel/extensions/helpscout/monitor.py
Kris 432d5db970 feat(helpscout): 新增Help Scout客服工单渠道插件
完成Help Scout渠道的完整功能实现,包含API客户端、webhook处理、轮询同步、自动回复、工单标签分配、客户资料管理等核心能力,附带完整的配置Schema与插件元信息
2026-05-21 10:49:56 +08:00

183 lines
6.3 KiB
Python

from __future__ import annotations
import logging
logger = logging.getLogger(__name__)
def parse_webhook_event_to_unified(event_type: str, payload: dict, account: dict) -> dict | None:
conv = payload.get("conversation", {})
if event_type == "convo.agent.reply.created":
logger.debug("Agent reply created: conv=%s — skipped", conv.get("id"))
return None
if event_type == "convo.ai-answers.created":
logger.debug("AI Answers conversation created: conv=%s — skipped", conv.get("id"))
return None
if event_type == "convo.status":
return _build_status_event(conv)
if event_type == "convo.created":
return _build_convo_created_event(conv, account)
if event_type == "convo.customer.reply.created":
return _build_customer_reply_event(conv, account)
if event_type == "convo.note.created":
return _build_note_event(conv, account)
if event_type in ("beacon.chat.created", "beacon.chat.customer.replied"):
return _build_beacon_chat_event(conv, account, event_type)
if event_type == "satisfaction.ratings":
return _build_satisfaction_event(payload, account)
if event_type == "convo.assigned":
return _build_assigned_event(payload)
if event_type == "convo.tags":
return _build_tags_event(payload)
if event_type == "customer.created":
return _build_customer_event(payload, action="created")
if event_type == "customer.updated":
return _build_customer_event(payload, action="updated")
logger.debug("Unhandled webhook event type: %s", event_type)
return None
def _build_convo_created_event(conv: dict, account: dict) -> dict:
customer = conv.get("primaryCustomer", {})
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv['id']}:created",
"event_type": "convo.created",
"conversation_id": str(conv["id"]),
"conversation_status": conv.get("status", "active"),
"conversation_subject": conv.get("subject", ""),
"sender": {
"id": str(customer.get("id", "")),
"type": "customer",
"email": customer.get("email", ""),
},
"mailbox_id": str(conv.get("mailboxId", account.get("mailbox_id"))),
"timestamp": conv.get("createdAt", ""),
}
def _build_customer_reply_event(conv: dict, account: dict) -> dict:
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv['id']}:reply:{conv.get('threadId', '')}",
"event_type": "convo.customer.reply.created",
"conversation_id": str(conv["id"]),
"conversation_status": conv.get("status", "active"),
"conversation_subject": conv.get("subject", ""),
"sender": {
"id": str(conv.get("customer", {}).get("id", "")),
"type": "customer",
"email": conv.get("customer", {}).get("email", ""),
},
"mailbox_id": str(conv.get("mailboxId", account.get("mailbox_id"))),
"text": conv.get("body", "") or conv.get("preview", ""),
"timestamp": conv.get("createdAt", ""),
}
def _build_note_event(conv: dict, account: dict) -> dict | None:
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv['id']}:note:{conv.get('threadId', '')}",
"event_type": "convo.note.created",
"conversation_id": str(conv["id"]),
"note_text": conv.get("body", ""),
"is_internal": True,
"timestamp": conv.get("createdAt", ""),
}
def _build_status_event(conv: dict) -> dict:
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv['id']}:status:{conv.get('status', '')}",
"event_type": "convo.status",
"conversation_id": str(conv["id"]),
"new_status": conv.get("status", ""),
"timestamp": conv.get("updatedAt", ""),
}
def _build_beacon_chat_event(conv: dict, account: dict, event_type: str) -> dict:
return {
"channel": "helpscout",
"dedup_key": f"beacon:{conv['id']}:{event_type}",
"event_type": event_type,
"conversation_id": str(conv["id"]),
"conversation_type": "chat",
"sender": {
"id": str(conv.get("customer", {}).get("id", "")),
"type": "customer",
"email": conv.get("customer", {}).get("email", ""),
},
"text": conv.get("body", "") or conv.get("preview", ""),
"timestamp": conv.get("createdAt", ""),
}
def _build_satisfaction_event(payload: dict, account: dict) -> dict:
rating = payload.get("rating", {})
return {
"channel": "helpscout",
"dedup_key": f"satisfaction:{rating.get('id', '')}",
"event_type": "satisfaction.ratings",
"conversation_id": str(rating.get("conversationId", "")),
"rating": rating.get("rating", 0),
"feedback": rating.get("feedback", ""),
"timestamp": rating.get("createdAt", ""),
}
def _build_assigned_event(payload: dict) -> dict:
conv = payload.get("conversation", {})
assignee = payload.get("assignee", {})
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv.get('id')}:assigned:{assignee.get('id')}",
"event_type": "convo.assigned",
"conversation_id": str(conv.get("id", "")),
"assignee_id": str(assignee.get("id", "")),
"assignee_type": assignee.get("type", ""),
"timestamp": conv.get("updatedAt", ""),
}
def _build_tags_event(payload: dict) -> dict:
conv = payload.get("conversation", {})
tags = payload.get("tags", [])
return {
"channel": "helpscout",
"dedup_key": f"conv:{conv.get('id')}:tags",
"event_type": "convo.tags",
"conversation_id": str(conv.get("id", "")),
"tags": tags,
"timestamp": conv.get("updatedAt", ""),
}
def _build_customer_event(payload: dict, action: str) -> dict:
customer = payload.get("customer", {})
return {
"channel": "helpscout",
"dedup_key": f"customer:{customer.get('id')}:{action}",
"event_type": f"customer.{action}",
"customer_id": str(customer.get("id", "")),
"email": customer.get("email", ""),
"first_name": customer.get("firstName", ""),
"last_name": customer.get("lastName", ""),
"timestamp": customer.get(f"{'created' if action == 'created' else 'updated'}At", ""),
}