实现完整的Freshdesk和Freshchat集成支持,包含会话守卫、错误定义、消息去重、配置管理、webhook处理、出站消息发送、状态监控、安全校验、配对功能和流式回复支持
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
import logging
|
|
import time
|
|
|
|
from yuxi.channel.extensions.freshdesk.types import FreshdeskIdentity, FreshdeskInboundEvent
|
|
from yuxi.channel.message.models import GroupContext, MessageType, PeerInfo, UnifiedMessage
|
|
from yuxi.channel.routing.models import PeerKind
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class FreshdeskMonitor:
|
|
def __init__(self):
|
|
self._dedupe: dict[str, float] = {}
|
|
|
|
async def convert_event(
|
|
self,
|
|
event: FreshdeskInboundEvent,
|
|
identity: FreshdeskIdentity,
|
|
account_id: str,
|
|
mode: str,
|
|
) -> UnifiedMessage | None:
|
|
if self._is_echo(event, identity):
|
|
return None
|
|
|
|
if self._is_duplicate(event):
|
|
return None
|
|
|
|
if mode == "freshchat" and not event.conversation_id:
|
|
return None
|
|
if mode == "freshdesk" and not event.ticket_id:
|
|
return None
|
|
|
|
target_id = event.conversation_id or event.ticket_id or ""
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"freshdesk:{event.message_id}",
|
|
channel_type="freshdesk",
|
|
account_id=account_id,
|
|
content=event.content,
|
|
sender=PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=event.actor_id or "unknown",
|
|
display_name=event.actor_name.strip() or "Customer",
|
|
),
|
|
message_type=self._detect_type(event),
|
|
group=GroupContext(id=target_id),
|
|
raw=event.raw,
|
|
timestamp=event.created_at,
|
|
)
|
|
|
|
def _is_echo(self, event: FreshdeskInboundEvent, identity: FreshdeskIdentity) -> bool:
|
|
if event.actor_type == "agent" and event.actor_id == identity.freshdesk_agent_id:
|
|
return True
|
|
return False
|
|
|
|
def _is_duplicate(self, event: FreshdeskInboundEvent) -> bool:
|
|
dedupe_key = f"{event.source}:{event.event_id}"
|
|
now = time.time()
|
|
if dedupe_key in self._dedupe:
|
|
return True
|
|
self._dedupe[dedupe_key] = now
|
|
self._cleanup_expired(now)
|
|
return False
|
|
|
|
def _cleanup_expired(self, now: float):
|
|
expired = [k for k, ts in self._dedupe.items() if now - ts > 60]
|
|
for k in expired:
|
|
del self._dedupe[k]
|
|
|
|
def _detect_type(self, event: FreshdeskInboundEvent) -> MessageType:
|
|
if event.attachments:
|
|
for att in event.attachments:
|
|
content_type = att.get("content_type", "")
|
|
if content_type.startswith("image/"):
|
|
return MessageType.IMAGE
|
|
if content_type.startswith("video/"):
|
|
return MessageType.VIDEO
|
|
return MessageType.FILE
|
|
return MessageType.TEXT
|