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

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