ForcePilot/backend/package/yuxi/channel/extensions/freshdesk/reconciler.py
Kris 3e861d6cd1 feat(freshdesk): 新增Freshdesk/Freshchat渠道插件
实现完整的Freshdesk和Freshchat集成支持,包含会话守卫、错误定义、消息去重、配置管理、webhook处理、出站消息发送、状态监控、安全校验、配对功能和流式回复支持
2026-05-21 10:47:20 +08:00

110 lines
3.9 KiB
Python

import asyncio
import logging
from yuxi.channel.extensions.freshdesk.constants import RECONCILE_INTERVAL_SECONDS
from yuxi.channel.extensions.freshdesk.types import FreshdeskInboundEvent
logger = logging.getLogger(__name__)
class FreshdeskReconciler:
def __init__(self, client, account, dedupe, queue):
self._client = client
self._account = account
self._dedupe = dedupe
self._queue = queue
self._running = False
async def start(self):
self._running = True
asyncio.create_task(self._loop())
async def stop(self):
self._running = False
async def _loop(self):
while self._running:
try:
if self._account.mode in ("freshdesk", "both"):
await self._reconcile_tickets()
if self._account.mode in ("freshchat", "both"):
await self._reconcile_conversations()
except Exception as e:
logger.error("Reconciler error: %s", e)
await asyncio.sleep(RECONCILE_INTERVAL_SECONDS)
async def _reconcile_tickets(self):
try:
tickets = await self._client.fd_list_tickets(
filter="updated_at",
sort_by="updated_at",
order="desc",
per_page=10,
)
for ticket in tickets:
event_id = f"reconcile_ticket_{ticket['id']}_{ticket.get('updated_at', '')}"
if self._dedupe.is_duplicate(event_id):
continue
event = self._build_ticket_event(ticket)
if self._queue:
await self._queue.put(event)
except Exception:
logger.exception("Ticket reconciliation failed")
async def _reconcile_conversations(self):
try:
conversations = await self._client.fc_list_conversations(
sort_by="updated_at",
per_page=10,
)
convs = conversations if isinstance(conversations, list) else conversations.get("conversations", [])
for conv in convs:
event_id = f"reconcile_conv_{conv.get('conversation_id', '')}_{conv.get('updated_at', '')}"
if self._dedupe.is_duplicate(event_id):
continue
event = self._build_conversation_event(conv)
if self._queue:
await self._queue.put(event)
except Exception:
logger.exception("Conversation reconciliation failed")
def _build_ticket_event(self, ticket: dict) -> FreshdeskInboundEvent:
return FreshdeskInboundEvent(
source="freshdesk",
event_id=f"reconcile_ticket_{ticket['id']}",
event_type="ticket_reconcile",
ticket_id=str(ticket["id"]),
conversation_id=None,
conversation_status=None,
message_id=str(ticket["id"]),
actor_type="contact",
actor_id=str(ticket.get("requester_id", "")),
actor_name="",
content=ticket.get("description_text", ticket.get("description", "")),
content_type="text",
message_parts=[],
attachments=[],
raw=ticket,
)
def _build_conversation_event(self, conv: dict) -> FreshdeskInboundEvent:
conv_id = str(conv.get("conversation_id", conv.get("id", "")))
return FreshdeskInboundEvent(
source="freshchat",
event_id=f"reconcile_conv_{conv_id}",
event_type="conversation_reconcile",
conversation_id=conv_id,
conversation_status=conv.get("status", "new"),
message_id=conv_id,
actor_type="user",
actor_id=str(conv.get("user_id", "")),
actor_name="",
content="",
content_type="text",
message_parts=[],
attachments=[],
ticket_id=None,
raw=conv,
)