飞书支持直接与机器人聊天

This commit is contained in:
zhaoguanggang1@tal.com 2026-03-05 21:22:41 +08:00
parent 6f5930624b
commit 463614e53b
2 changed files with 80 additions and 7 deletions

View file

@ -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 与通道的错误日志。

View file

@ -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),
})