飞书支持直接与机器人聊天
This commit is contained in:
parent
6f5930624b
commit
463614e53b
2 changed files with 80 additions and 7 deletions
|
|
@ -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 与通道的错误日志。
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue