feat(channels): add thread support to all channel handlers

Pass threadID and threadType to EnsureContact across all channel integrations:
- Discord, Feishu, Slack, Telegram, WhatsApp, Zalo
- Include General topic in contact collection
This commit is contained in:
viettranx committed 2026-04-02 08:23:03 +07:00
1 parent ac3af93df5
commit 7d5c670708
9 files changed
+27 -20

No files matched your search

+2 -2
View File
@@ -121,13 +121,13 @@ func processNormalMessage(
}
displayName := sessionMeta["display_name"]
username := sessionMeta["username"]
deps.ContactCollector.EnsureContact(ctx, channelType, msg.Channel, senderNumericID, userID, displayName, username, peerKind, "user")
deps.ContactCollector.EnsureContact(ctx, channelType, msg.Channel, senderNumericID, userID, displayName, username, peerKind, "user", "", "")
// Also collect group chat as a contact (for group permission management / merge).
// Group IDs (e.g., Telegram "-100456") differ from user IDs — no UNIQUE conflict.
if peerKind == string(sessions.PeerGroup) && msg.ChatID != "" {
groupTitle := msg.Metadata["chat_title"] // Telegram: message.Chat.Title
deps.ContactCollector.EnsureContact(ctx, channelType, msg.Channel, msg.ChatID, "", groupTitle, "", "group", "group")
deps.ContactCollector.EnsureContact(ctx, channelType, msg.Channel, msg.ChatID, "", groupTitle, "", "group", "group", "", "")
}
}
+2 -2
View File
@@ -195,7 +195,7 @@ func (c *Channel) handleMessage(_ *discordgo.Session, m *discordgo.MessageCreate
// Collect contact even when bot is not mentioned (cache prevents DB spam).
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, m.Author.Username, "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, m.Author.Username, "group", "user", "", "")
}
slog.Debug("discord group message recorded (no mention)",
@@ -287,7 +287,7 @@ 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(ctx, c.Type(), c.Name(), senderID, senderID, senderName, m.Author.Username, peerKind, "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, m.Author.Username, peerKind, "user", "", "")
}
// Publish directly to bus (to preserve MediaFile MIME types)
+2 -2
View File
@@ -106,7 +106,7 @@ func (c *Channel) handleMessageEvent(ctx context.Context, event *MessageEvent) {
// Collect contact even when bot is not mentioned (cache prevents DB spam).
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), mc.SenderID, mc.SenderID, senderName, "", "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), mc.SenderID, mc.SenderID, senderName, "", "group", "user", "", "")
}
slog.Debug("feishu group message recorded (no mention)",
@@ -162,7 +162,7 @@ func (c *Channel) handleMessageEvent(ctx context.Context, event *MessageEvent) {
// 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, "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), mc.SenderID, mc.SenderID, senderName, "", peerKind, "user", "", "")
}
metadata := map[string]string{
+1 -1
View File
@@ -575,7 +575,7 @@ func (c *Channel) ListGroupMembers(ctx context.Context, chatID string) ([]channe
}
// Auto-sync member into contact store
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, channels.TypeFeishu, c.Name(), m.MemberID, m.MemberID, m.Name, "", "group", "user")
cc.EnsureContact(ctx, channels.TypeFeishu, c.Name(), m.MemberID, m.MemberID, m.Name, "", "group", "user", "", "")
}
}
return result, nil
+1 -1
View File
@@ -200,7 +200,7 @@ func (c *Channel) handleMessage(ev *slackevents.MessageEvent) {
// Collect contact even when bot is not mentioned (cache prevents DB spam).
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, displayName, "", "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, displayName, "", "group", "user", "", "")
}
slog.Debug("slack group message recorded (no mention)",
+1 -1
View File
@@ -34,7 +34,7 @@ func (c *Channel) HandleMessage(senderID, chatID, content string, mediaPaths []s
// Collect contact for processed messages (DM + group-mentioned).
if cc := c.ContactCollector(); cc != nil {
ctx := store.WithTenantID(context.Background(), c.TenantID())
cc.EnsureContact(ctx, c.Type(), c.Name(), userID, userID, metadata["username"], "", peerKind, "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), userID, userID, metadata["username"], "", peerKind, "user", "", "")
}
c.Bus().PublishInbound(bus.InboundMessage{
+14 -7
View File
@@ -41,9 +41,6 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) {
userID := fmt.Sprintf("%d", user.ID)
senderID := userID
if user.Username != "" {
senderID = fmt.Sprintf("%s|%s", userID, user.Username)
}
isGroup := message.Chat.Type == "group" || message.Chat.Type == "supergroup"
@@ -336,9 +333,14 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) {
// Collect contact even when bot is not mentioned (cache prevents DB spam).
if cc := c.ContactCollector(); cc != nil {
contactName := strings.TrimSpace(user.FirstName + " " + user.LastName)
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, userID, contactName, user.Username, "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, userID, contactName, user.Username, "group", "user", "", "")
// Also collect group chat itself as a contact (for group permission / merge).
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "group")
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "group", "", "")
// Collect forum topic as a distinct delivery target (including General).
if isForum && messageThreadID > 0 {
threadStr := fmt.Sprintf("%d", messageThreadID)
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "topic", threadStr, "topic")
}
}
slog.Debug("telegram group message recorded (no mention)",
@@ -591,10 +593,15 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) {
// Collect contact for processed messages (DM + group-mentioned).
if cc := c.ContactCollector(); cc != nil {
contactName := strings.TrimSpace(user.FirstName + " " + user.LastName)
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, userID, contactName, user.Username, peerKind, "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, userID, contactName, user.Username, peerKind, "user", "", "")
// Also collect group chat itself as a contact (for group permission / merge).
if isGroup {
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "group")
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "group", "", "")
// Collect forum topic as a distinct delivery target (including General).
if isForum && messageThreadID > 0 {
threadStr := fmt.Sprintf("%d", messageThreadID)
cc.EnsureContact(ctx, c.Type(), c.Name(), chatIDStr, "", message.Chat.Title, "", "group", "topic", threadStr, "topic")
}
}
}
+1 -1
View File
@@ -271,7 +271,7 @@ func (c *Channel) handleIncomingMessage(msg map[string]any) {
// Collect contact for processed messages.
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, metadata["user_name"], "", peerKind, "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, metadata["user_name"], "", peerKind, "user", "", "")
}
// Annotate with sender identity so the agent knows who is messaging.
+3 -3
View File
@@ -64,7 +64,7 @@ func (c *Channel) handleDM(msg protocol.UserMessage) {
// Collect contact for DM messages.
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "direct", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "direct", "user", "", "")
}
metadata := map[string]string{
@@ -111,7 +111,7 @@ func (c *Channel) handleGroupMessage(msg protocol.GroupMessage) {
// Collect contact even when bot is not mentioned (cache prevents DB spam).
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "group", "user", "", "")
}
slog.Debug("zalo_personal group message recorded (no mention)",
@@ -144,7 +144,7 @@ func (c *Channel) handleGroupMessage(msg protocol.GroupMessage) {
// Collect contact for group-mentioned messages.
if cc := c.ContactCollector(); cc != nil {
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "group", "user")
cc.EnsureContact(ctx, c.Type(), c.Name(), senderID, senderID, senderName, "", "group", "user", "", "")
}
metadata := map[string]string{