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

158 lines
4.6 KiB
Python

from __future__ import annotations
import logging
from typing import Any
from yuxi.channels.models import DeliveryResult
logger = logging.getLogger(__name__)
try:
import lark_oapi
HAS_LARK_SDK = True
except ImportError:
HAS_LARK_SDK = False
lark_oapi = None # type: ignore
FEISHU_EMOJI_MAP = {
"THUMBSUP": "👍",
"THUMBSDOWN": "👎",
"HEART": "❤️",
"SMILE": "😊",
"GRINNING": "😁",
"LAUGHING": "😆",
"CRY": "😢",
"ANGRY": "😡",
"SURPRISED": "😲",
"THINKING": "🤔",
"CLAP": "👏",
"OK": "👌",
"FIST": "",
"PRAY": "🙏",
"FIRE": "🔥",
"PARTY": "🎉",
"CHECK": "",
"CROSS": "",
"QUESTION": "",
"EXCLAMATION": "",
}
def emoji_type_to_emoji(emoji_type: str) -> str:
return FEISHU_EMOJI_MAP.get(emoji_type, emoji_type)
async def remove_reaction(
client: Any,
message_id: str,
reaction_id: str,
) -> DeliveryResult:
if not HAS_LARK_SDK:
return DeliveryResult(success=False, error="lark-oapi SDK not available")
try:
request = (
lark_oapi.api.im.v1.DeleteMessageReactionRequest.builder()
.message_id(message_id)
.reaction_id(reaction_id)
.build()
)
resp = await client.im.v1.message.reaction.delete(request)
if resp.success():
return DeliveryResult(success=True)
return DeliveryResult(success=False, error=resp.msg)
except Exception as e:
logger.error(f"[Feishu] remove_reaction failed: {e}")
return DeliveryResult(success=False, error=str(e))
async def list_reactions(
client: Any,
message_id: str,
*,
page_size: int = 20,
page_token: str = "",
) -> dict[str, Any]:
if not HAS_LARK_SDK:
return {"reactions": [], "total": 0}
try:
request = (
lark_oapi.api.im.v1.ListMessageReactionRequest.builder()
.message_id(message_id)
.page_size(page_size)
.page_token(page_token)
.build()
)
resp = await client.im.v1.message.reaction.list(request)
if resp.success():
items = []
for item in resp.data.get("items", []):
emoji_type = item.get("reaction_type", {}).get("emoji_type", "")
items.append(
{
"reaction_id": item.get("reaction_id", ""),
"emoji_type": emoji_type,
"emoji": emoji_type_to_emoji(emoji_type),
"count": item.get("count", 0),
"user_id": item.get("user_id", ""),
}
)
return {
"reactions": items,
"total": resp.data.get("total", 0),
"page_token": resp.data.get("page_token", ""),
"has_more": resp.data.get("has_more", False),
}
return {"reactions": [], "total": 0, "error": resp.msg}
except Exception as e:
logger.error(f"[Feishu] list_reactions failed: {e}")
return {"reactions": [], "total": 0, "error": str(e)}
async def clear_all_bot_reactions(
client: Any,
message_id: str,
bot_open_id: str,
) -> DeliveryResult:
if not HAS_LARK_SDK:
return DeliveryResult(success=False, error="lark-oapi SDK not available")
result = await list_reactions(client, message_id, page_size=50)
items = result.get("reactions", [])
removed = 0
errors = 0
for item in items:
reaction_id = item.get("reaction_id", "")
user_id = item.get("user_id", "")
if user_id != bot_open_id:
continue
if not reaction_id:
continue
try:
del_req = (
lark_oapi.api.im.v1.DeleteMessageReactionRequest.builder()
.message_id(message_id)
.reaction_id(reaction_id)
.build()
)
del_resp = await client.im.v1.message.reaction.delete(del_req)
if del_resp.success():
removed += 1
else:
errors += 1
logger.debug("[Feishu] Failed to remove reaction %s: %s", reaction_id, del_resp.msg)
except Exception as e:
errors += 1
logger.debug("[Feishu] Error removing reaction %s: %s", reaction_id, e)
if errors > 0:
logger.warning("[Feishu] clear_all_bot_reactions: removed=%d, errors=%d", removed, errors)
return DeliveryResult(success=removed > 0, error=f"Removed {removed}/{removed + errors}")
return DeliveryResult(success=True)