fix(feishu): preserve mention identity and sender open_id in messages
- Replace @_user_N placeholders with @Name(open_id:xxx) instead of stripping them, so downstream tools can still extract user IDs - Bot mentions are still stripped for trigger detection - Fix mention handling not applied in p2p (direct) chats - Inject sender open_id into message content and metadata so the LLM knows who sent the message
This commit is contained in:
parent
aaf99d7a30
commit
b989f94122
3 changed files with 80 additions and 19 deletions
|
|
@ -69,18 +69,42 @@ func extractFileKey(content string) string { return extractJSONStringField(conte
|
|||
// extractFileName extracts the file_name from a Feishu file message content JSON.
|
||||
func extractFileName(content string) string { return extractJSONStringField(content, "file_name") }
|
||||
|
||||
// stripMentionPlaceholders removes @_user_N placeholders from the text content.
|
||||
// These are inserted by Feishu when users @mention someone in a message.
|
||||
func stripMentionPlaceholders(content string, mentions []*larkim.MentionEvent) string {
|
||||
// stripMentionPlaceholders replaces @_user_N placeholders in the text content.
|
||||
// Bot mentions are removed; other user mentions are replaced with @Name(open_id:xxx)
|
||||
// so downstream tools can still extract the user ID.
|
||||
func stripMentionPlaceholders(content string, mentions []*larkim.MentionEvent, botOpenID string) string {
|
||||
if len(mentions) == 0 {
|
||||
return content
|
||||
}
|
||||
for _, m := range mentions {
|
||||
if m.Key != nil && *m.Key != "" {
|
||||
content = strings.ReplaceAll(content, *m.Key, "")
|
||||
if m.Key == nil || *m.Key == "" {
|
||||
continue
|
||||
}
|
||||
// If this mention is the bot itself, strip it.
|
||||
if botOpenID != "" && m.Id != nil && m.Id.OpenId != nil && *m.Id.OpenId == botOpenID {
|
||||
content = strings.ReplaceAll(content, *m.Key, "")
|
||||
continue
|
||||
}
|
||||
// Replace with @Name(open_id:xxx) to preserve identity for downstream use.
|
||||
replacement := ""
|
||||
name := ""
|
||||
if m.Name != nil {
|
||||
name = *m.Name
|
||||
}
|
||||
openID := ""
|
||||
if m.Id != nil && m.Id.OpenId != nil {
|
||||
openID = *m.Id.OpenId
|
||||
}
|
||||
if name != "" && openID != "" {
|
||||
replacement = "@" + name + "(open_id:" + openID + ")"
|
||||
} else if name != "" {
|
||||
replacement = "@" + name
|
||||
} else if openID != "" {
|
||||
replacement = "@user(open_id:" + openID + ")"
|
||||
}
|
||||
content = strings.ReplaceAll(content, *m.Key, replacement)
|
||||
}
|
||||
// Also clean up any remaining @_user_N patterns
|
||||
// Clean up any remaining @_user_N patterns not covered by mentions list
|
||||
content = mentionPlaceholderRegex.ReplaceAllString(content, "")
|
||||
return strings.TrimSpace(content)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@ func TestBuildMarkdownCard(t *testing.T) {
|
|||
func TestStripMentionPlaceholders(t *testing.T) {
|
||||
strPtr := func(s string) *string { return &s }
|
||||
|
||||
botOpenID := "ou_bot123"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
|
|
@ -243,21 +245,37 @@ func TestStripMentionPlaceholders(t *testing.T) {
|
|||
want: "Hello world",
|
||||
},
|
||||
{
|
||||
name: "single mention",
|
||||
name: "bot mention is stripped",
|
||||
content: "@_user_1 hello",
|
||||
mentions: []*larkim.MentionEvent{
|
||||
{Key: strPtr("@_user_1")},
|
||||
{Key: strPtr("@_user_1"), Id: &larkim.UserId{OpenId: strPtr(botOpenID)}, Name: strPtr("Bot")},
|
||||
},
|
||||
want: "hello",
|
||||
},
|
||||
{
|
||||
name: "multiple mentions",
|
||||
content: "@_user_1 @_user_2 hey",
|
||||
name: "user mention preserved with name and id",
|
||||
content: "@_user_1 hello",
|
||||
mentions: []*larkim.MentionEvent{
|
||||
{Key: strPtr("@_user_1")},
|
||||
{Key: strPtr("@_user_2")},
|
||||
{Key: strPtr("@_user_1"), Id: &larkim.UserId{OpenId: strPtr("ou_user456")}, Name: strPtr("张三")},
|
||||
},
|
||||
want: "hey",
|
||||
want: "@张三(open_id:ou_user456) hello",
|
||||
},
|
||||
{
|
||||
name: "mixed bot and user mentions",
|
||||
content: "@_user_1 给我和 @_user_2 拉个群",
|
||||
mentions: []*larkim.MentionEvent{
|
||||
{Key: strPtr("@_user_1"), Id: &larkim.UserId{OpenId: strPtr(botOpenID)}, Name: strPtr("Bot")},
|
||||
{Key: strPtr("@_user_2"), Id: &larkim.UserId{OpenId: strPtr("ou_user789")}, Name: strPtr("李四")},
|
||||
},
|
||||
want: "给我和 @李四(open_id:ou_user789) 拉个群",
|
||||
},
|
||||
{
|
||||
name: "user mention with name only",
|
||||
content: "@_user_1 hello",
|
||||
mentions: []*larkim.MentionEvent{
|
||||
{Key: strPtr("@_user_1"), Name: strPtr("张三")},
|
||||
},
|
||||
want: "@张三 hello",
|
||||
},
|
||||
{
|
||||
name: "empty content",
|
||||
|
|
@ -283,7 +301,7 @@ func TestStripMentionPlaceholders(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := stripMentionPlaceholders(tt.content, tt.mentions)
|
||||
got := stripMentionPlaceholders(tt.content, tt.mentions, botOpenID)
|
||||
if got != tt.want {
|
||||
t.Errorf("stripMentionPlaceholders(%q, ...) = %q, want %q", tt.content, got, tt.want)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -400,6 +400,19 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
|||
if sender != nil && sender.TenantKey != nil {
|
||||
metadata["tenant_key"] = *sender.TenantKey
|
||||
}
|
||||
if senderID != "" {
|
||||
metadata["sender_open_id"] = senderID
|
||||
}
|
||||
// Store the full open_id for API use (senderID may be user_id instead)
|
||||
if sender != nil && sender.SenderId != nil && sender.SenderId.OpenId != nil && *sender.SenderId.OpenId != "" {
|
||||
metadata["sender_open_id"] = *sender.SenderId.OpenId
|
||||
}
|
||||
|
||||
// Replace mention placeholders for all chat types: bot mentions are stripped, others become @Name(open_id:xxx)
|
||||
if len(message.Mentions) > 0 {
|
||||
knownBotID, _ := c.botOpenID.Load().(string)
|
||||
content = stripMentionPlaceholders(content, message.Mentions, knownBotID)
|
||||
}
|
||||
|
||||
var peer bus.Peer
|
||||
if chatType == "p2p" {
|
||||
|
|
@ -410,11 +423,6 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
|||
// Check if bot was mentioned
|
||||
isMentioned := c.isBotMentioned(message)
|
||||
|
||||
// Strip mention placeholders from content before group trigger check
|
||||
if len(message.Mentions) > 0 {
|
||||
content = stripMentionPlaceholders(content, message.Mentions)
|
||||
}
|
||||
|
||||
// In group chats, apply unified group trigger filtering
|
||||
respond, cleaned := c.ShouldRespondInGroup(isMentioned, content)
|
||||
if !respond {
|
||||
|
|
@ -423,6 +431,17 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
|
|||
content = cleaned
|
||||
}
|
||||
|
||||
// Prepend sender identity so the LLM knows who sent the message.
|
||||
if sender != nil && sender.SenderId != nil {
|
||||
openID := ""
|
||||
if sender.SenderId.OpenId != nil {
|
||||
openID = *sender.SenderId.OpenId
|
||||
}
|
||||
if openID != "" {
|
||||
content = fmt.Sprintf("[sender: open_id=%s] %s", openID, content)
|
||||
}
|
||||
}
|
||||
|
||||
logger.InfoCF("feishu", "Feishu message received", map[string]any{
|
||||
"sender_id": senderID,
|
||||
"chat_id": chatID,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue