fix(slack): resolve mention race condition and unify chatID logic

This commit is contained in:
Badgerbees 2026-03-27 20:22:13 +07:00
parent 76cd7f8ad5
commit 4079cb9080

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"github.com/slack-go/slack" "github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents" "github.com/slack-go/slack/slackevents"
@ -29,6 +30,7 @@ type SlackChannel struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
pendingAcks sync.Map pendingAcks sync.Map
seenMessages sync.Map // "channel_id:ts" -> true (dedup message/app_mention race)
} }
type slackMessageRef struct { type slackMessageRef struct {
@ -279,6 +281,14 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
return return
} }
// Deduplicate against app_mention events which Slack often sends in parallel.
dedupKey := ev.Channel + ":" + ev.TimeStamp
if _, loaded := c.seenMessages.LoadOrStore(dedupKey, true); loaded {
return
}
// Simple TTL: clean up from map after 1 minute (sufficient for Slack race)
time.AfterFunc(1*time.Minute, func() { c.seenMessages.Delete(dedupKey) })
// check allowlist to avoid downloading attachments for rejected users // check allowlist to avoid downloading attachments for rejected users
sender := bus.SenderInfo{ sender := bus.SenderInfo{
Platform: "slack", Platform: "slack",
@ -307,12 +317,13 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
Timestamp: messageTS, Timestamp: messageTS,
}) })
content := ev.Text rawContent := ev.Text
content = c.stripBotMention(content) content := c.stripBotMention(rawContent)
isMentioned := rawContent != content // Detect if bot was @mentioned
// In non-DM channels, apply group trigger filtering // In non-DM channels, apply group trigger filtering
if !strings.HasPrefix(channelID, "D") { if !strings.HasPrefix(channelID, "D") {
respond, cleaned := c.ShouldRespondInGroup(false, content) respond, cleaned := c.ShouldRespondInGroup(isMentioned, content)
if !respond { if !respond {
return return
} }
@ -385,6 +396,14 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
return return
} }
// Deduplicate against message events which Slack often sends in parallel.
dedupKey := ev.Channel + ":" + ev.TimeStamp
if _, loaded := c.seenMessages.LoadOrStore(dedupKey, true); loaded {
return
}
// Simple TTL: clean up from map after 1 minute (sufficient for Slack race)
time.AfterFunc(1*time.Minute, func() { c.seenMessages.Delete(dedupKey) })
if !c.IsAllowedSender(bus.SenderInfo{ if !c.IsAllowedSender(bus.SenderInfo{
Platform: "slack", Platform: "slack",
PlatformID: ev.User, PlatformID: ev.User,
@ -406,11 +425,9 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
threadTS := ev.ThreadTimeStamp threadTS := ev.ThreadTimeStamp
messageTS := ev.TimeStamp messageTS := ev.TimeStamp
var chatID string chatID := channelID
if threadTS != "" { if threadTS != "" {
chatID = channelID + "/" + threadTS chatID = channelID + "/" + threadTS
} else {
chatID = channelID + "/" + messageTS
} }
c.pendingAcks.Store(chatID, slackMessageRef{ c.pendingAcks.Store(chatID, slackMessageRef{