From 0dd4ebd6e62592b85ddd0c7f565e8cd49afbcf4e Mon Sep 17 00:00:00 2001 From: Luan Vu Date: Thu, 19 Mar 2026 14:02:19 +0700 Subject: [PATCH] fix(channels): collect contacts for DM and group-mentioned messages (#271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(channels): collect contacts for DM and group-mentioned messages Previously, EnsureContact was only called inside the "not mentioned → record history → return" branch, so contacts were never saved for messages that the bot actually processed (DMs and group @mentions). This caused the contacts list to show empty names ("—") for active users across all channels. Move contact collection to the main processing path (before PublishInbound / HandleMessage) so every processed message upserts the sender's display name. The existing ContactCollector 30-minute cache prevents redundant DB writes. Affected channels: Feishu, Telegram, Discord, Slack, Zalo, WhatsApp. * fix(slack): use clean userID for EnsureContact cache key The senderID param in HandleMessage is the compound "U123|DisplayName" form, which produces a different cache key than the clean "U123" used in the group-history path (handlers.go:178). Use userID (extracted clean ID) to ensure cache dedup works correctly. --------- Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> Co-authored-by: viettranx --- internal/channels/discord/handler.go | 5 +++++ internal/channels/feishu/bot.go | 5 +++++ internal/channels/slack/utils.go | 5 +++++ internal/channels/telegram/handlers.go | 5 +++++ internal/channels/whatsapp/whatsapp.go | 5 +++++ internal/channels/zalo/personal/handlers.go | 10 ++++++++++ 6 files changed, 35 insertions(+) diff --git a/internal/channels/discord/handler.go b/internal/channels/discord/handler.go index bc602a38..6fd7d542 100644 --- a/internal/channels/discord/handler.go +++ b/internal/channels/discord/handler.go @@ -247,6 +247,11 @@ func (c *Channel) handleMessage(_ *discordgo.Session, m *discordgo.MessageCreate } } + // Collect contact for processed messages (DM + group-mentioned). + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(context.Background(), c.Type(), c.Name(), senderID, senderID, senderName, m.Author.Username, peerKind) + } + // Publish directly to bus (to preserve MediaFile MIME types) c.Bus().PublishInbound(bus.InboundMessage{ Channel: c.Name(), diff --git a/internal/channels/feishu/bot.go b/internal/channels/feishu/bot.go index ba4b23ca..9222e066 100644 --- a/internal/channels/feishu/bot.go +++ b/internal/channels/feishu/bot.go @@ -131,6 +131,11 @@ func (c *Channel) handleMessageEvent(ctx context.Context, event *MessageEvent) { peerKind = "group" } + // Collect contact for processed messages (DM + group-mentioned). + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(ctx, c.Type(), c.Name(), mc.SenderID, mc.SenderID, senderName, "", peerKind) + } + metadata := map[string]string{ "message_id": messageID, "chat_type": mc.ChatType, diff --git a/internal/channels/slack/utils.go b/internal/channels/slack/utils.go index a4c66883..307095c0 100644 --- a/internal/channels/slack/utils.go +++ b/internal/channels/slack/utils.go @@ -30,6 +30,11 @@ func (c *Channel) HandleMessage(senderID, chatID, content string, mediaPaths []s mediaFiles = append(mediaFiles, bus.MediaFile{Path: p}) } + // Collect contact for processed messages (DM + group-mentioned). + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(context.Background(), c.Type(), c.Name(), userID, userID, metadata["username"], "", peerKind) + } + c.Bus().PublishInbound(bus.InboundMessage{ Channel: c.Name(), SenderID: senderID, diff --git a/internal/channels/telegram/handlers.go b/internal/channels/telegram/handlers.go index ebd6c5ff..e8b5a0d5 100644 --- a/internal/channels/telegram/handlers.go +++ b/internal/channels/telegram/handlers.go @@ -482,6 +482,11 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) { } } + // Collect contact for processed messages (DM + group-mentioned). + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, userID, user.FirstName, user.Username, peerKind) + } + c.Bus().PublishInbound(bus.InboundMessage{ Channel: c.Name(), SenderID: senderID, diff --git a/internal/channels/whatsapp/whatsapp.go b/internal/channels/whatsapp/whatsapp.go index 981298e3..66e10e10 100644 --- a/internal/channels/whatsapp/whatsapp.go +++ b/internal/channels/whatsapp/whatsapp.go @@ -267,6 +267,11 @@ func (c *Channel) handleIncomingMessage(msg map[string]any) { "preview", channels.Truncate(content, 50), ) + // Collect contact for processed messages. + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(context.Background(), c.Type(), c.Name(), senderID, senderID, metadata["user_name"], "", peerKind) + } + c.HandleMessage(senderID, chatID, content, media, metadata, peerKind) } diff --git a/internal/channels/zalo/personal/handlers.go b/internal/channels/zalo/personal/handlers.go index f068d764..5ecb1614 100644 --- a/internal/channels/zalo/personal/handlers.go +++ b/internal/channels/zalo/personal/handlers.go @@ -59,6 +59,11 @@ func (c *Channel) handleDM(msg protocol.UserMessage) { c.startTyping(threadID, protocol.ThreadTypeUser) + // Collect contact for DM messages. + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(context.Background(), c.Type(), c.Name(), senderID, senderID, senderName, "", "direct") + } + metadata := map[string]string{ "message_id": msg.Data.MsgID, "platform": channels.TypeZaloPersonal, @@ -131,6 +136,11 @@ func (c *Channel) handleGroupMessage(msg protocol.GroupMessage) { histMedia := c.groupHistory.CollectMedia(threadID) allMedia := append(histMedia, media...) + // Collect contact for group-mentioned messages. + if cc := c.ContactCollector(); cc != nil { + cc.EnsureContact(context.Background(), c.Type(), c.Name(), senderID, senderID, senderName, "", "group") + } + metadata := map[string]string{ "message_id": msg.Data.MsgID, "platform": channels.TypeZaloPersonal,