实现完整的Freshdesk和Freshchat集成支持,包含会话守卫、错误定义、消息去重、配置管理、webhook处理、出站消息发送、状态监控、安全校验、配对功能和流式回复支持
110 lines
3.9 KiB
Python
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,
|
|
)
|