ForcePilot/backend/package/yuxi/channel/extensions/flock/message.py
Kris 0bad70ec19 feat(flock): 新增Flock团队协作IM渠道插件
实现了完整的Flock渠道接入能力,包含消息收发、Webhook事件监听、账号配置、安全校验、媒体文件处理等功能,支持私聊和群组聊天,适配ForcePilot插件规范。
2026-05-21 10:47:01 +08:00

148 lines
4.2 KiB
Python

from __future__ import annotations
import logging
from datetime import datetime
from yuxi.channel.message.models import (
GroupContext,
MessageType,
PeerInfo,
UnifiedMessage,
)
from yuxi.channel.routing.models import PeerKind
from .constants import PREFIX_GROUP
from .types import (
FlockChatType,
FlockEvent,
FlockEventType,
InboundFlockMessage,
)
logger = logging.getLogger(__name__)
def parse_event_listener(body: dict) -> FlockEvent:
name = body.get("name", "")
event_data = body.get("event", {})
chat = event_data.get("chat", "")
message = event_data.get("message", {})
return FlockEvent(
name=name,
chat=chat,
message=message,
event=event_data,
)
def parse_outgoing_webhook(body: dict) -> InboundFlockMessage:
user_id = body.get("userId", "")
channel_id = body.get("channelId", "")
text = body.get("text", "")
timestamp = int(body.get("timestamp", 0))
chat_type = FlockChatType.GROUP if channel_id.startswith(PREFIX_GROUP) else FlockChatType.DIRECT
return InboundFlockMessage(
message_id=f"outgoing_{user_id}_{timestamp}",
event_type=FlockEventType.OUTGOING_WEBHOOK,
from_user=user_id,
to=channel_id or user_id,
chat_type=chat_type,
text=text,
timestamp=timestamp,
raw_payload=body,
)
def flock_event_to_inbound(event: FlockEvent) -> InboundFlockMessage:
msg = event.message
message_id = msg.get("uid", "")
from_user = msg.get("from", "")
to = msg.get("to", "")
text = msg.get("text", "")
flockml = msg.get("flockml", "")
timestamp = int(msg.get("timestamp", 0))
thread_id = msg.get("threadId")
mentions = msg.get("mentions", [])
attachments = msg.get("attachments", [])
chat_type = FlockChatType.GROUP if to.startswith(PREFIX_GROUP) else FlockChatType.DIRECT
event_type_map = {
"chat.receiveMessage": FlockEventType.MESSAGE_RECEIVE,
"client.slashCommand": FlockEventType.SLASH_COMMAND,
"client.chatAction": FlockEventType.CHAT_ACTION,
"app.install": FlockEventType.APP_INSTALL,
"app.uninstall": FlockEventType.APP_UNINSTALL,
}
event_type = event_type_map.get(event.name, FlockEventType.MESSAGE_RECEIVE)
return InboundFlockMessage(
message_id=message_id,
event_type=event_type,
from_user=from_user,
to=to,
chat_type=chat_type,
text=text,
flockml=flockml,
timestamp=timestamp,
thread_id=thread_id,
mentions=mentions,
attachments=attachments,
raw_payload={"name": event.name, "event": event.event},
)
def inbound_to_unified(msg: InboundFlockMessage, account_id: str) -> UnifiedMessage:
from .utils import strip_flock_prefix
display_name = strip_flock_prefix(msg.from_user)
sender = PeerInfo(
kind=PeerKind.DIRECT if msg.chat_type == FlockChatType.DIRECT else PeerKind.GROUP,
id=msg.from_user,
display_name=display_name,
)
group = None
if msg.chat_type == FlockChatType.GROUP:
group = GroupContext(
id=msg.to,
thread_id=msg.thread_id,
)
bot_mentioned = False
if msg.mentions:
bot_mentioned = True
message_type = MessageType.TEXT
media_urls: list[str] = []
if msg.attachments:
message_type = MessageType.FILE
for att in msg.attachments:
if isinstance(att, dict):
src = att.get("src", "") or (att.get("views", {}).get("image", {}).get("original", {}).get("src", ""))
if src:
media_urls.append(src)
ts = datetime.fromtimestamp(msg.timestamp / 1000.0) if msg.timestamp else datetime.now()
return UnifiedMessage(
msg_id=msg.message_id,
channel_type="flock",
account_id=account_id,
content=msg.text,
sender=sender,
message_type=message_type,
media_urls=media_urls,
group=group,
timestamp=ts,
raw_payload=msg.raw_payload,
message_thread_id=msg.thread_id,
was_mentioned=len(msg.mentions) > 0,
explicitly_mentioned_bot=bot_mentioned,
mentioned_user_ids=list(msg.mentions),
)