飞书支持直接与机器人聊天
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/)创建应用程序
|
1. 前往 [飞书开放平台](https://open.feishu.cn/)创建应用程序
|
||||||
2. 获取 App ID 和 App Secret
|
2. 获取 App ID 和 App Secret
|
||||||
3. 配置事件订阅和Webhook URL
|
3. **权限**:在「权限管理」中勾选并发布:
|
||||||
4. 设置加密(可选,生产环境建议启用)
|
- `im:message`(接收与发送消息)
|
||||||
5. 将 App ID、App Secret、Encrypt Key 和 Verification Token(如果启用加密) 填入配置文件中
|
- `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).
|
dispatcher := larkdispatcher.NewEventDispatcher(c.config.VerificationToken, c.config.EncryptKey).
|
||||||
OnP2MessageReceiveV1(c.handleMessageReceive)
|
OnP2MessageReceiveV1(c.handleMessageReceive).
|
||||||
|
OnP2ChatAccessEventBotP2pChatEnteredV1(c.handleBotP2PChatEntered)
|
||||||
|
|
||||||
runCtx, cancel := context.WithCancel(ctx)
|
runCtx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
|
@ -317,8 +318,55 @@ func (c *FeishuChannel) sendMediaPart(
|
||||||
|
|
||||||
// --- Inbound message handling ---
|
// --- 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 {
|
func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
|
||||||
if event == nil || event.Event == nil || event.Event.Message == nil {
|
if event == nil || event.Event == nil || event.Event.Message == nil {
|
||||||
|
logger.DebugCF("feishu", "P2MessageReceiveV1 skipped: nil event or message", nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,6 +375,9 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||||
|
|
||||||
chatID := stringValue(message.ChatId)
|
chatID := stringValue(message.ChatId)
|
||||||
if chatID == "" {
|
if chatID == "" {
|
||||||
|
logger.WarnCF("feishu", "Dropping message: empty chat_id", map[string]any{
|
||||||
|
"message_id": stringValue(message.MessageId),
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,6 +398,9 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||||
CanonicalID: identity.BuildCanonicalID("feishu", senderID),
|
CanonicalID: identity.BuildCanonicalID("feishu", senderID),
|
||||||
}
|
}
|
||||||
if !c.IsAllowedSender(senderInfo) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,9 +436,8 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
||||||
}
|
}
|
||||||
|
|
||||||
var peer bus.Peer
|
var peer bus.Peer
|
||||||
if chatType == "p2p" {
|
isGroup := chatType == "group" || chatType == "topic_group"
|
||||||
peer = bus.Peer{Kind: "direct", ID: senderID}
|
if isGroup {
|
||||||
} else {
|
|
||||||
peer = bus.Peer{Kind: "group", ID: chatID}
|
peer = bus.Peer{Kind: "group", ID: chatID}
|
||||||
|
|
||||||
// Check if bot was mentioned
|
// 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
|
// In group chats, apply unified group trigger filtering
|
||||||
respond, cleaned := c.ShouldRespondInGroup(isMentioned, content)
|
respond, cleaned := c.ShouldRespondInGroup(isMentioned, content)
|
||||||
if !respond {
|
if !respond {
|
||||||
|
logger.InfoCF("feishu", "Dropping group message: no trigger (mention or prefix)", map[string]any{
|
||||||
|
"chat_id": chatID, "is_mentioned": isMentioned,
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
content = cleaned
|
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{
|
logger.InfoCF("feishu", "Feishu message received", map[string]any{
|
||||||
"sender_id": senderID,
|
"sender_id": senderID,
|
||||||
"chat_id": chatID,
|
"chat_id": chatID,
|
||||||
|
"chat_type": chatType,
|
||||||
"message_id": messageID,
|
"message_id": messageID,
|
||||||
"preview": utils.Truncate(content, 80),
|
"preview": utils.Truncate(content, 80),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue