From 463614e53b85f064d98670f365f9808613800177 Mon Sep 17 00:00:00 2001 From: "zhaoguanggang1@tal.com" Date: Thu, 5 Mar 2026 21:22:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E6=94=AF=E6=8C=81=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E4=B8=8E=E6=9C=BA=E5=99=A8=E4=BA=BA=E8=81=8A=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/channels/feishu/README.zh.md | 19 +++++++-- pkg/channels/feishu/feishu_64.go | 68 +++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/docs/channels/feishu/README.zh.md b/docs/channels/feishu/README.zh.md index 310827723..fffab71f5 100644 --- a/docs/channels/feishu/README.zh.md +++ b/docs/channels/feishu/README.zh.md @@ -32,6 +32,19 @@ 1. 前往 [飞书开放平台](https://open.feishu.cn/)创建应用程序 2. 获取 App ID 和 App Secret -3. 配置事件订阅和Webhook URL -4. 设置加密(可选,生产环境建议启用) -5. 将 App ID、App Secret、Encrypt Key 和 Verification Token(如果启用加密) 填入配置文件中 +3. **权限**:在「权限管理」中勾选并发布: + - `im:message`(接收与发送消息) + - `im:message.p2p_msg:readonly`(**获取用户发给机器人的单聊消息**,否则私聊收不到) + - `im:message:send_as_bot`(以应用身份发消息) + - 群聊需 `im:message.group_at_msg:readonly`;如需「用户进入单聊」事件可勾选 `im:chat.access_event.bot_p2p_chat:read` +4. **事件订阅**:在「事件订阅」中启用「长连接」(WebSocket),并订阅 **「接收消息」**(`im.message.receive_v1`) +5. 设置加密(可选,生产环境建议启用) +6. 将 App ID、App Secret、Encrypt Key 和 Verification Token(若启用加密)填入配置文件 +7. **修改权限或事件后需重新发布应用版本**,否则不生效 + +## 机器人不回复时排查 + +- 看运行日志:发一条消息后是否出现 **「Feishu message received」**。 + - **没有**:飞书未把消息推给应用。请确认已订阅「接收消息」、已开通 `im:message.p2p_msg:readonly` 并重新发布版本。 + - **有「Dropping message: sender not in allow list」**:当前用户不在白名单;将 `allow_from` 设为 `[]` 表示允许所有人,或加入对应用户 ID。 + - **有「Feishu message received」但仍无回复**:消息已进入应用,问题在后续 agent 或发回飞书环节,请查看 agent 与通道的错误日志。 diff --git a/pkg/channels/feishu/feishu_64.go b/pkg/channels/feishu/feishu_64.go index 00f73064d..a8ef39cee 100644 --- a/pkg/channels/feishu/feishu_64.go +++ b/pkg/channels/feishu/feishu_64.go @@ -68,7 +68,8 @@ func (c *FeishuChannel) Start(ctx context.Context) error { } dispatcher := larkdispatcher.NewEventDispatcher(c.config.VerificationToken, c.config.EncryptKey). - OnP2MessageReceiveV1(c.handleMessageReceive) + OnP2MessageReceiveV1(c.handleMessageReceive). + OnP2ChatAccessEventBotP2pChatEnteredV1(c.handleBotP2PChatEntered) runCtx, cancel := context.WithCancel(ctx) @@ -317,8 +318,55 @@ func (c *FeishuChannel) sendMediaPart( // --- Inbound message handling --- +// handleBotP2PChatEntered handles user entering a P2P chat with the bot; sends optional welcome and respects allowlist. +func (c *FeishuChannel) handleBotP2PChatEntered(ctx context.Context, event *larkim.P2ChatAccessEventBotP2pChatEnteredV1) error { + if event == nil || event.Event == nil { + return nil + } + data := event.Event + chatID := stringValue(data.ChatId) + if chatID == "" { + return nil + } + + operatorID := "" + if data.OperatorId != nil { + if data.OperatorId.OpenId != nil && *data.OperatorId.OpenId != "" { + operatorID = *data.OperatorId.OpenId + } else if data.OperatorId.UserId != nil && *data.OperatorId.UserId != "" { + operatorID = *data.OperatorId.UserId + } else if data.OperatorId.UnionId != nil && *data.OperatorId.UnionId != "" { + operatorID = *data.OperatorId.UnionId + } + } + if operatorID != "" { + senderInfo := bus.SenderInfo{ + Platform: "feishu", + PlatformID: operatorID, + CanonicalID: identity.BuildCanonicalID("feishu", operatorID), + } + if !c.IsAllowedSender(senderInfo) { + return nil + } + } + + welcome := "你好,有什么可以帮你的?" + if err := c.Send(ctx, bus.OutboundMessage{ + Channel: c.Name(), + ChatID: chatID, + Content: welcome, + }); err != nil { + logger.WarnCF("feishu", "Failed to send P2P welcome", map[string]any{ + "chat_id": chatID, + "error": err.Error(), + }) + } + return nil +} + func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.P2MessageReceiveV1) error { if event == nil || event.Event == nil || event.Event.Message == nil { + logger.DebugCF("feishu", "P2MessageReceiveV1 skipped: nil event or message", nil) return nil } @@ -327,6 +375,9 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim. chatID := stringValue(message.ChatId) if chatID == "" { + logger.WarnCF("feishu", "Dropping message: empty chat_id", map[string]any{ + "message_id": stringValue(message.MessageId), + }) return nil } @@ -347,6 +398,9 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim. CanonicalID: identity.BuildCanonicalID("feishu", senderID), } if !c.IsAllowedSender(senderInfo) { + logger.InfoCF("feishu", "Dropping message: sender not in allow list", map[string]any{ + "sender_id": senderID, "chat_id": chatID, + }) return nil } @@ -382,9 +436,8 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim. } var peer bus.Peer - if chatType == "p2p" { - peer = bus.Peer{Kind: "direct", ID: senderID} - } else { + isGroup := chatType == "group" || chatType == "topic_group" + if isGroup { peer = bus.Peer{Kind: "group", ID: chatID} // Check if bot was mentioned @@ -398,14 +451,21 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim. // In group chats, apply unified group trigger filtering respond, cleaned := c.ShouldRespondInGroup(isMentioned, content) if !respond { + logger.InfoCF("feishu", "Dropping group message: no trigger (mention or prefix)", map[string]any{ + "chat_id": chatID, "is_mentioned": isMentioned, + }) return nil } content = cleaned + } else { + // P2P (chat_type "p2p") or unknown: treat as direct, always respond + peer = bus.Peer{Kind: "direct", ID: senderID} } logger.InfoCF("feishu", "Feishu message received", map[string]any{ "sender_id": senderID, "chat_id": chatID, + "chat_type": chatType, "message_id": messageID, "preview": utils.Truncate(content, 80), })