ForcePilot/backend/package/yuxi/channels/adapters/feishu/message_actions.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

274 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from typing import Any
FEISHU_MESSAGE_ACTIONS: dict[str, dict[str, Any]] = {
"send": {
"status": "implemented",
"description": "?????????????????????,"
"api": "im/v1/messages",
},
"sendAttachment": {
"status": "implemented",
"description": "??????/??????/?????????????????????,"
"api": "im/v1/messages + content",
},
"sendCard": {
"status": "implemented",
"description": "???????????????????????????",
"api": "im/v1/messages + card",
},
"sendPoll": {
"status": "unsupported",
"description": "?????????????????????????????????,"
"api": "N/A",
},
"sendSticker": {
"status": "planned",
"description": "??????????????????,"
"api": "im/v1/stickers",
},
"reply": {
"status": "implemented",
"description": "??????????????????????????? root_id???,"
"api": "im/v1/messages/reply",
},
"edit": {
"status": "implemented",
"description": "????????????????????????",
"api": "im/v1/messages/{message_id} PATCH",
},
"delete": {
"status": "implemented",
"description": "??????/????????????",
"api": "im/v1/messages/{message_id} DELETE",
},
"read": {
"status": "implemented",
"description": "??????????????????",
"api": "im/v1/messages/{message_id}/read",
},
"react": {
"status": "implemented",
"description": "????????????????????????,"
"api": "im/v1/messages/{message_id}/reactions",
},
"listReactions": {
"status": "implemented",
"description": "????????????????????????",
"api": "im/v1/messages/{message_id}/reactions GET",
},
"pin": {
"status": "implemented",
"description": "????????????",
"api": "im/v1/pins",
},
"unpin": {
"status": "implemented",
"description": "??????????????????",
"api": "im/v1/pins/{message_id} DELETE",
},
"listPins": {
"status": "implemented",
"description": "????????????????????????",
"api": "im/v1/chats/{chat_id}/pins GET",
},
"broadcast": {
"status": "unsupported",
"description": "???????????????????????????API",
"api": "N/A",
},
"typing": {
"status": "implemented",
"description": "通过 Reactions API 实现 typing indicator含退避/年龄限制)",
"api": "im/v1/reactions",
},
"thread": {
"status": "implemented",
"description": "????????????/????????????",
"api": "im/v1/threads",
},
"streamChunk": {
"status": "implemented",
"description": "?????????????????????????????????????????????,"
"api": "im/v1/messages/{message_id} PATCH (streaming)",
},
"kick": {
"status": "unsupported",
"description": "?????? Bot ???????????????????????????,"
"api": "N/A",
},
"ban": {
"status": "unsupported",
"description": "?????? Bot ?????????????????????,"
"api": "N/A",
},
"mute": {
"status": "unsupported",
"description": "?????? Bot ?????????????????????",
"api": "N/A",
},
"leave": {
"status": "unsupported",
"description": "Bot ???????????????????????????",
"api": "N/A",
},
"addParticipant": {
"status": "planned",
"description": "?????????????????????,"
"api": "im/v1/chats/{chat_id}/members POST",
},
"removeParticipant": {
"status": "planned",
"description": "?????????????????????,"
"api": "im/v1/chats/{chat_id}/members/{member_id} DELETE",
},
"renameGroup": {
"status": "planned",
"description": "??????????????????",
"api": "im/v1/chats/{chat_id} PATCH",
},
"setGroupIcon": {
"status": "unsupported",
"description": "????????????????????? API ???????????????,"
"api": "N/A",
},
"channelInfo": {
"status": "implemented",
"description": "????????????/????????????",
"api": "im/v1/chats/{chat_id} GET",
},
"channelList": {
"status": "implemented",
"description": "????????????????????????????????????",
"api": "im/v1/chats GET",
},
"channelCreate": {
"status": "planned",
"description": "????????????",
"api": "im/v1/chats POST",
},
"channelEdit": {
"status": "planned",
"description": "??????????????????",
"api": "im/v1/chats/{chat_id} PATCH",
},
"memberInfo": {
"status": "implemented",
"description": "????????????/????????????",
"api": "contact/v3/users/{user_id} GET",
},
"roleInfo": {
"status": "unsupported",
"description": "?????????????????????API",
"api": "N/A",
},
"downloadFile": {
"status": "implemented",
"description": "????????????/??????",
"api": "im/v1/messages/{message_id}/resources/{file_key} GET",
},
"uploadFile": {
"status": "implemented",
"description": "????????????",
"api": "im/v1/files POST ???im/v1/images POST",
},
}
_FEISHU_ACTION_MAPPING: dict[str, str] = {
"send": "send",
"sendAttachment": "send_attachment",
"sendPoll": "create_poll",
"sendSticker": "sticker",
"reply": "reply",
"edit": "edit",
"delete": "delete",
"read": "read",
"react": "react",
"listReactions": "list_reactions",
"pin": "pin",
"unpin": "unpin",
"listPins": "list_pins",
"typing": "typing",
"broadcast": "broadcast",
"thread": "thread_create",
"streamChunk": "stream",
"kick": "kick",
"ban": "ban",
"mute": "mute",
"leave": "leave_group",
"addParticipant": "add_participant",
"removeParticipant": "remove_participant",
"renameGroup": "rename_group",
"setGroupIcon": "set_group_icon",
"channelInfo": "channel_info",
"channelList": "channel_list",
"channelCreate": "channel_create",
"channelEdit": "channel_edit",
"memberInfo": "member_info",
"roleInfo": "role_info",
"downloadFile": "download_file",
"uploadFile": "upload_file",
}
def register_feishu_actions() -> None:
from yuxi.channels.message_actions import ActionDeclaration, ActionRegistry, ActionStatus, MessageAction
declarations: dict[MessageAction, ActionDeclaration] = {}
for action_name, info in FEISHU_MESSAGE_ACTIONS.items():
mapped = _FEISHU_ACTION_MAPPING.get(action_name)
if mapped is None:
continue
try:
action_enum = MessageAction(mapped)
except ValueError:
continue
status_str = info.get("status", "unsupported")
status = {
"implemented": ActionStatus.SUPPORTED,
"planned": ActionStatus.PARTIAL,
"unsupported": ActionStatus.UNSUPPORTED,
}.get(status_str, ActionStatus.UNSUPPORTED)
declarations[action_enum] = ActionDeclaration(
action=action_enum,
status=status,
reason=info.get("description", ""),
impl=info.get("api", ""),
)
ActionRegistry.register_channel_actions("feishu", declarations)
def resolve_feishu_action_target(params: dict[str, Any]) -> str | None:
chat_id = params.get("chatId", params.get("chat_id", ""))
open_id = params.get("openId", params.get("open_id", ""))
msg_id = params.get("messageId", params.get("message_id", ""))
if msg_id:
return msg_id
if chat_id:
return chat_id
if open_id:
return open_id
return None
def resolve_feishu_chat_id(params: dict[str, Any]) -> str:
chat_id = params.get("chatId", params.get("chat_id", ""))
if chat_id:
if chat_id.startswith("oc_"):
return f"feishu:group:{chat_id}"
return chat_id
return ""
def resolve_feishu_member_id(params: dict[str, Any]) -> str:
open_id = params.get("openId", params.get("open_id", ""))
member_id = params.get("memberId", params.get("member_id", ""))
return open_id or member_id or ""
def resolve_feishu_message_id(params: dict[str, Any]) -> str:
return params.get("messageId", params.get("message_id", ""))