Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions pkg/connector/chatinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ func (s *SlackClient) generateGroupDMName(ctx context.Context, members []string)
return "", err
}
ghost.UpdateInfoIfNecessary(ctx, s.UserLogin, bridgev2.RemoteEventUnknown)
// Use the ghost's name (output of displayname_template) so group DM names
// are consistent with individual DM names and puppet display names.
if ghost.Name != "" {
ghostNames = append(ghostNames, ghost.Name)
}
Expand Down Expand Up @@ -173,6 +175,18 @@ func (s *SlackClient) generateMemberList(ctx context.Context, info *slack.Channe
return
}

func (s *SlackClient) makeChannelNameHashExtraUpdates() func(ctx context.Context, portal *bridgev2.Portal) bool {
currentChannelHash := s.Main.Config.GetChannelNameTemplateHash()
return func(ctx context.Context, portal *bridgev2.Portal) bool {
meta := portal.Metadata.(*slackid.PortalMetadata)
if meta.ChannelNameTemplateHash != currentChannelHash {
meta.ChannelNameTemplateHash = currentChannelHash
return true
}
return false
}
}

func (s *SlackClient) wrapChatInfo(ctx context.Context, info *slack.Channel, isNew bool) (*bridgev2.ChatInfo, error) {
var members bridgev2.ChatMemberList
var avatar *bridgev2.Avatar
Expand All @@ -193,6 +207,7 @@ func (s *SlackClient) wrapChatInfo(ctx context.Context, info *slack.Channel, isN
if err != nil {
return nil, err
}
extraUpdates = s.makeChannelNameHashExtraUpdates()
case info.IsIM:
roomType = database.RoomTypeDM
members.IsFull = true
Expand All @@ -208,7 +223,11 @@ func (s *SlackClient) wrapChatInfo(ctx context.Context, info *slack.Channel, isN
return nil, err
}
ghost.UpdateInfoIfNecessary(ctx, s.UserLogin, bridgev2.RemoteEventUnknown)
// Use the ghost's name (i.e. the output of displayname_template) as the
// channel name input so that channel_name_template receives the same
// formatted name that is displayed on the puppet.
info.Name = ghost.Name
extraUpdates = s.makeChannelNameHashExtraUpdates()
case info.Name != "":
members = s.generateMemberList(ctx, info, !s.Main.Config.ParticipantSyncOnlyOnCreate || isNew)
if isNew && s.Main.Config.MuteChannelsByDefault {
Expand All @@ -228,10 +247,7 @@ func (s *SlackClient) wrapChatInfo(ctx context.Context, info *slack.Channel, isN
}
}
members.TotalMemberCount = info.NumMembers
var name *string
if roomType != database.RoomTypeDM || len(members.MemberMap) == 1 {
name = ptr.Ptr(s.formatChannelName(info))
}
name := ptr.Ptr(s.formatChannelName(info))
return &bridgev2.ChatInfo{
Name: name,
Topic: ptr.Ptr(info.Topic.Value),
Expand Down Expand Up @@ -307,9 +323,19 @@ func (s *SlackClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal)
return nil, fmt.Errorf("invalid portal ID %q", portal.ID)
} else if channelID == "" {
return s.getTeamInfo(), nil
} else {
return s.fetchChatInfo(ctx, channelID, portal.MXID == "")
}
// For DM and group DM portals, invalidate the chat info cache when the
// combined channel name template hash has changed so the portal name gets
// recomputed with the updated templates.
if portal.RoomType == database.RoomTypeDM || portal.RoomType == database.RoomTypeGroupDM {
meta := portal.Metadata.(*slackid.PortalMetadata)
if meta.ChannelNameTemplateHash != s.Main.Config.GetChannelNameTemplateHash() {
s.chatInfoCacheLock.Lock()
delete(s.chatInfoCache, channelID)
s.chatInfoCacheLock.Unlock()
}
}
return s.fetchChatInfo(ctx, channelID, portal.MXID == "")
}

func makeAvatar(avatarURL, slackAvatarHash string) *bridgev2.Avatar {
Expand Down Expand Up @@ -373,6 +399,7 @@ func (s *SlackClient) wrapUserInfo(userID string, info *slack.User, botInfo *sla
ExtraUpdates: func(ctx context.Context, ghost *bridgev2.Ghost) bool {
meta := ghost.Metadata.(*slackid.GhostMetadata)
meta.LastSync = jsontime.UnixNow()
meta.NameTemplateHash = s.Main.Config.GetDisplaynameTemplateHash()
if info != nil {
meta.SlackUpdatedTS = int64(info.Updated)
} else if botInfo != nil {
Expand All @@ -392,10 +419,15 @@ func (s *SlackClient) syncManyUsers(ctx context.Context, ghosts map[string]*brid
IncludeProfileOnlyUsers: true,
UpdatedIDs: make(map[string]int64, len(ghosts)),
}
currentTemplateHash := s.Main.Config.GetDisplaynameTemplateHash()
for _, ghost := range ghosts {
meta := ghost.Metadata.(*slackid.GhostMetadata)
_, userID := slackid.ParseUserID(ghost.ID)
params.UpdatedIDs[userID] = meta.SlackUpdatedTS
lastUpdated := meta.SlackUpdatedTS
if meta.NameTemplateHash != currentTemplateHash {
lastUpdated = 0
}
params.UpdatedIDs[userID] = lastUpdated
}
zerolog.Ctx(ctx).Debug().Any("request_map", params.UpdatedIDs).Msg("Requesting user info")
infos, err := s.Client.GetUsersCacheContext(ctx, s.TeamID, params)
Expand Down Expand Up @@ -465,7 +497,8 @@ func (s *SlackClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*
return nil, nil
}
meta := ghost.Metadata.(*slackid.GhostMetadata)
if time.Since(meta.LastSync.Time) < MinGhostSyncInterval {
templateChanged := meta.NameTemplateHash != s.Main.Config.GetDisplaynameTemplateHash()
if !templateChanged && time.Since(meta.LastSync.Time) < MinGhostSyncInterval {
return nil, nil
}
if s.IsRealUser && (ghost.Name != "" || time.Since(s.initialConnect) < 1*time.Minute) {
Expand Down
24 changes: 21 additions & 3 deletions pkg/connector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package connector

import (
_ "embed"
"crypto/sha256"
"fmt"
"strings"
"text/template"

Expand All @@ -42,9 +44,11 @@ type Config struct {

Backfill BackfillConfig `yaml:"backfill"`

displaynameTemplate *template.Template `yaml:"-"`
channelNameTemplate *template.Template `yaml:"-"`
teamNameTemplate *template.Template `yaml:"-"`
displaynameTemplate *template.Template `yaml:"-"`
channelNameTemplate *template.Template `yaml:"-"`
teamNameTemplate *template.Template `yaml:"-"`
displaynameTemplateHash string `yaml:"-"`
channelNameTemplateHash string `yaml:"-"`
}

type BackfillConfig struct {
Expand All @@ -64,10 +68,16 @@ func (c *Config) UnmarshalYAML(node *yaml.Node) error {
if err != nil {
return err
}
h := sha256.Sum256([]byte(c.DisplaynameTemplate))
c.displaynameTemplateHash = fmt.Sprintf("%x", h[:8])
c.channelNameTemplate, err = template.New("channel_name").Parse(c.ChannelNameTemplate)
if err != nil {
return err
}
// The channel name for DMs depends on both displayname_template (which determines ghost.Name,
// used as .Name input) and channel_name_template (the outer format). Hash both together.
ch := sha256.Sum256([]byte(c.ChannelNameTemplate + "\x00" + c.DisplaynameTemplate))
c.channelNameTemplateHash = fmt.Sprintf("%x", ch[:8])
c.teamNameTemplate, err = template.New("team_name").Parse(c.TeamNameTemplate)
if err != nil {
return err
Expand All @@ -90,6 +100,14 @@ func (c *Config) FormatDisplayname(user *DisplaynameParams) string {
return executeTemplate(c.displaynameTemplate, user)
}

func (c *Config) GetDisplaynameTemplateHash() string {
return c.displaynameTemplateHash
}

func (c *Config) GetChannelNameTemplateHash() string {
return c.channelNameTemplateHash
}

func (c *Config) FormatBotDisplayname(bot *slack.Bot, team *slack.TeamInfo) string {
return c.FormatDisplayname(&DisplaynameParams{
User: &slack.User{
Expand Down
4 changes: 3 additions & 1 deletion pkg/connector/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
# .Profile.Phone - The formatted phone number of the user
displayname_template: '{{or .Profile.DisplayName .Profile.RealName .Name}}{{if .IsBot}} (bot){{end}}'
# Channel name template for Slack channels (all types). Available variables:
# .Name - The name of the channel
# .Name - The name of the channel; for DMs (.IsIM) the other user's name as formatted by
# displayname_template; for group DMs (.IsMpIM) a comma-separated list of members'
# names, each also formatted by displayname_template
# .Team.Name - The name of the team the channel is in
# .Team.Domain - The Slack subdomain of the team the channel is in
# .ID - The internal ID of the channel
Expand Down
8 changes: 6 additions & 2 deletions pkg/slackid/dbmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ type PortalMetadata struct {
TeamDomain string `json:"team_domain,omitempty"`
EditMaxAge *int `json:"edit_max_age,omitempty"`
AllowDelete *bool `json:"allow_delete,omitempty"`
// Hash of channel_name_template + displayname_template; used to detect when
// DM/group DM portal names need to be refreshed after a config change.
ChannelNameTemplateHash string `json:"channel_name_template_hash,omitempty"`
}

type GhostMetadata struct {
SlackUpdatedTS int64 `json:"slack_updated_ts"`
LastSync jsontime.Unix `json:"last_sync"`
SlackUpdatedTS int64 `json:"slack_updated_ts"`
LastSync jsontime.Unix `json:"last_sync"`
NameTemplateHash string `json:"name_template_hash,omitempty"`
}

type UserLoginMetadata struct {
Expand Down