From 20ac16a501f7b831a1f5f9e8d585317c97aebc66 Mon Sep 17 00:00:00 2001 From: Ping Chen Date: Sun, 31 May 2026 21:27:46 +0900 Subject: [PATCH 1/3] handleslack: handle channel name change --- pkg/connector/chatinfo.go | 6 +++++ pkg/connector/handleslack.go | 50 +++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index f7e69c9..7a98656 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -41,6 +41,12 @@ import ( const ChatInfoCacheExpiry = 1 * time.Hour +func (s *SlackClient) invalidateChatInfoCache(channelID string) { + s.chatInfoCacheLock.Lock() + defer s.chatInfoCacheLock.Unlock() + delete(s.chatInfoCache, channelID) +} + func (s *SlackClient) fetchChatInfoWithCache(ctx context.Context, channelID string, onlyIfNotAttempted bool) (*slack.Channel, error) { s.chatInfoCacheLock.Lock() defer s.chatInfoCacheLock.Unlock() diff --git a/pkg/connector/handleslack.go b/pkg/connector/handleslack.go index e0af9e8..e3885e1 100644 --- a/pkg/connector/handleslack.go +++ b/pkg/connector/handleslack.go @@ -98,7 +98,7 @@ func (s *SlackClient) HandleSlackEvent(rawEvt any) { *slack.UserTypingEvent, *slack.ChannelMarkedEvent, *slack.IMMarkedEvent, *slack.GroupMarkedEvent, *slack.ChannelJoinedEvent, *slack.ChannelLeftEvent, *slack.GroupJoinedEvent, *slack.GroupLeftEvent, *slack.MemberJoinedChannelEvent, *slack.MemberLeftChannelEvent, - *slack.ChannelUpdateEvent: + *slack.ChannelUpdateEvent, *slack.ChannelRenameEvent: wrapped, err := s.wrapEvent(ctx, evt) if err != nil { log.Err(err).Msg("Failed to wrap Slack event") @@ -176,6 +176,16 @@ func (s *SlackClient) handleUserInvalidated(ctx context.Context, userID string) } } +func isChannelInfoChangeSubtype(subType string) bool { + switch subType { + case slack.MsgSubTypeChannelTopic, slack.MsgSubTypeChannelPurpose, slack.MsgSubTypeChannelName, + slack.MsgSubTypeGroupTopic, slack.MsgSubTypeGroupPurpose, slack.MsgSubTypeGroupName: + return true + default: + return false + } +} + func (s *SlackClient) wrapEvent(ctx context.Context, rawEvt any) (bridgev2.RemoteEvent, error) { var meta SlackEventMeta var metaErr error @@ -185,6 +195,18 @@ func (s *SlackClient) wrapEvent(ctx context.Context, rawEvt any) (bridgev2.Remot if evt.SubType == slack.MsgSubTypeMessageChanged && evt.SubMessage.SubType == "huddle_thread" { return nil, nil } + if isChannelInfoChangeSubtype(evt.SubType) { + // Drop the cache so the resync's GetChatInfo fetches the new values. + s.invalidateChatInfoCache(evt.Channel) + meta, metaErr = s.makeEventMeta(ctx, evt.Channel, nil, "", evt.Timestamp) + meta.Type = bridgev2.RemoteEventChatResync + wrapped = &SlackChatResync{ + SlackEventMeta: &meta, + Client: s, + ShouldSyncInfo: true, + } + break + } sender := evt.User if sender == "" { sender = evt.BotID @@ -261,10 +283,23 @@ func (s *SlackClient) wrapEvent(ctx context.Context, rawEvt any) (bridgev2.Remot wrapped = wrapMemberChange(&meta, meta.Sender, event.MembershipLeave, event.MembershipJoin) case *slack.ChannelUpdateEvent: + s.invalidateChatInfoCache(evt.Channel) meta, metaErr = s.makeEventMeta(ctx, evt.Channel, nil, "", evt.Timestamp) meta.Type = bridgev2.RemoteEventChatResync - //meta.CreatePortal = true - wrapped = &meta + wrapped = &SlackChatResync{ + SlackEventMeta: &meta, + Client: s, + ShouldSyncInfo: true, + } + case *slack.ChannelRenameEvent: + s.invalidateChatInfoCache(evt.Channel.ID) + meta, metaErr = s.makeEventMeta(ctx, evt.Channel.ID, nil, "", evt.Timestamp) + meta.Type = bridgev2.RemoteEventChatResync + wrapped = &SlackChatResync{ + SlackEventMeta: &meta, + Client: s, + ShouldSyncInfo: true, + } } return wrapped, metaErr } @@ -545,15 +580,16 @@ var ( ) func (s *SlackMessage) GetType() bridgev2.RemoteEventType { + if isChannelInfoChangeSubtype(s.Data.SubType) { + // wrapEvent turns these into a SlackChatResync; mapped here too so + // they're never bridged as plain messages. + return bridgev2.RemoteEventChatResync + } switch s.Data.SubType { case slack.MsgSubTypeMessageChanged: return bridgev2.RemoteEventEdit case slack.MsgSubTypeMessageDeleted: return bridgev2.RemoteEventMessageRemove - case slack.MsgSubTypeChannelTopic, slack.MsgSubTypeChannelPurpose, slack.MsgSubTypeChannelName, - slack.MsgSubTypeGroupTopic, slack.MsgSubTypeGroupPurpose, slack.MsgSubTypeGroupName: - // TODO implement deltas instead of full resync - return bridgev2.RemoteEventChatResync case slack.MsgSubTypeMessageReplied, slack.MsgSubTypeGroupJoin, slack.MsgSubTypeGroupLeave, slack.MsgSubTypeChannelJoin, slack.MsgSubTypeChannelLeave: return bridgev2.RemoteEventUnknown From dde2daf552e4c3c7bdd65429c7d90dfb119aa26f Mon Sep 17 00:00:00 2001 From: Ping Chen Date: Tue, 2 Jun 2026 14:36:09 +0900 Subject: [PATCH 2/3] Update pkg/connector/handleslack.go Co-authored-by: Tulir Asokan --- pkg/connector/handleslack.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/connector/handleslack.go b/pkg/connector/handleslack.go index e3885e1..160b6ce 100644 --- a/pkg/connector/handleslack.go +++ b/pkg/connector/handleslack.go @@ -581,9 +581,8 @@ var ( func (s *SlackMessage) GetType() bridgev2.RemoteEventType { if isChannelInfoChangeSubtype(s.Data.SubType) { - // wrapEvent turns these into a SlackChatResync; mapped here too so - // they're never bridged as plain messages. - return bridgev2.RemoteEventChatResync + // These shouldn't end up as SlackMessage events + return bridgev2.RemoteEventUnknown } switch s.Data.SubType { case slack.MsgSubTypeMessageChanged: From 46268066f75bdb7e67dea91005f46f0fada340ce Mon Sep 17 00:00:00 2001 From: Ping Chen Date: Tue, 2 Jun 2026 14:48:17 +0900 Subject: [PATCH 3/3] drop ChannelRenameEvent, handle specific subtypes --- pkg/connector/handleslack.go | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/pkg/connector/handleslack.go b/pkg/connector/handleslack.go index 160b6ce..2cfbf57 100644 --- a/pkg/connector/handleslack.go +++ b/pkg/connector/handleslack.go @@ -98,7 +98,7 @@ func (s *SlackClient) HandleSlackEvent(rawEvt any) { *slack.UserTypingEvent, *slack.ChannelMarkedEvent, *slack.IMMarkedEvent, *slack.GroupMarkedEvent, *slack.ChannelJoinedEvent, *slack.ChannelLeftEvent, *slack.GroupJoinedEvent, *slack.GroupLeftEvent, *slack.MemberJoinedChannelEvent, *slack.MemberLeftChannelEvent, - *slack.ChannelUpdateEvent, *slack.ChannelRenameEvent: + *slack.ChannelUpdateEvent: wrapped, err := s.wrapEvent(ctx, evt) if err != nil { log.Err(err).Msg("Failed to wrap Slack event") @@ -195,17 +195,22 @@ func (s *SlackClient) wrapEvent(ctx context.Context, rawEvt any) (bridgev2.Remot if evt.SubType == slack.MsgSubTypeMessageChanged && evt.SubMessage.SubType == "huddle_thread" { return nil, nil } - if isChannelInfoChangeSubtype(evt.SubType) { - // Drop the cache so the resync's GetChatInfo fetches the new values. + switch evt.SubType { + case slack.MsgSubTypeChannelName, slack.MsgSubTypeGroupName: + // The room name is formatted from the channel type/privacy, which + // the rename message doesn't include, so resync the full info. + // Drop the cache first so GetChatInfo fetches the new name. s.invalidateChatInfoCache(evt.Channel) meta, metaErr = s.makeEventMeta(ctx, evt.Channel, nil, "", evt.Timestamp) meta.Type = bridgev2.RemoteEventChatResync - wrapped = &SlackChatResync{ + return &SlackChatResync{ SlackEventMeta: &meta, Client: s, ShouldSyncInfo: true, - } - break + }, metaErr + case slack.MsgSubTypeChannelTopic, slack.MsgSubTypeGroupTopic: + meta, metaErr = s.makeEventMeta(ctx, evt.Channel, nil, "", evt.Timestamp) + return wrapTopicChange(&meta, evt.Topic), metaErr } sender := evt.User if sender == "" { @@ -291,15 +296,6 @@ func (s *SlackClient) wrapEvent(ctx context.Context, rawEvt any) (bridgev2.Remot Client: s, ShouldSyncInfo: true, } - case *slack.ChannelRenameEvent: - s.invalidateChatInfoCache(evt.Channel.ID) - meta, metaErr = s.makeEventMeta(ctx, evt.Channel.ID, nil, "", evt.Timestamp) - meta.Type = bridgev2.RemoteEventChatResync - wrapped = &SlackChatResync{ - SlackEventMeta: &meta, - Client: s, - ShouldSyncInfo: true, - } } return wrapped, metaErr } @@ -350,6 +346,18 @@ func wrapReadReceipt(meta *SlackEventMeta) *SlackReadReceipt { return &SlackReadReceipt{SlackEventMeta: meta} } +func wrapTopicChange(meta *SlackEventMeta, topic string) *SlackChatInfoChange { + meta.Type = bridgev2.RemoteEventChatInfoChange + return &SlackChatInfoChange{ + SlackEventMeta: meta, + Change: &bridgev2.ChatInfoChange{ + ChatInfo: &bridgev2.ChatInfo{ + Topic: &topic, + }, + }, + } +} + func wrapMemberChange(meta *SlackEventMeta, sender bridgev2.EventSender, newMembership, prevMembership event.Membership) *SlackChatInfoChange { meta.Type = bridgev2.RemoteEventChatInfoChange meta.LogContext = func(c zerolog.Context) zerolog.Context {