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
3 changes: 3 additions & 0 deletions pkg/connector/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ func (s *SlackConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities
// GetUserInfo has an internal rate limit of 1 fetch per 24 hours,
// so we're fine to tell the bridge to fetch user info all the time.
AggressiveUpdateInfo: true,
// Slack's message retention is mirrored as a disappear-after-send
// timer (read-only, see disappearSettingForChannel).
DisappearingMessages: true,
Provisioning: bridgev2.ProvisioningCapabilities{
ImagePackImport: true,
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
Expand Down
72 changes: 72 additions & 0 deletions pkg/connector/chatinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import (
"context"
"errors"
"fmt"
"net/url"
"slices"
Expand Down Expand Up @@ -271,13 +272,84 @@
Avatar: avatar,
Members: &members,
Type: &roomType,
Disappear: s.disappearSettingForChannel(ctx, info, roomType),
ParentID: ptr.Ptr(slackid.MakeTeamPortalID(s.TeamID)),
ExtraUpdates: extraUpdates,
UserLocal: userLocal,
CanBackfill: true,
}, nil
}

// Slack retention_type values (verified against the workspace data-retention UI):
//
// 0 = inherit/unset (use the workspace default)
// 1 = never delete messages
// 2 = delete after retention_duration days ("keep for N days" presets)
// 3 = delete after retention_duration days (paid "custom timeline")
//
// Only types 2 and 3 delete messages, so only those map to a disappearing timer.
const (
slackRetentionInherit = 0
slackRetentionKeepAll = 1
)

// disappearSettingForChannel maps Slack's message retention to a disappearing
// timer, preferring the per-channel conversations.getRetention (which works with
// the user token, unlike the admin API) and falling back to the workspace-default
// prefs when the channel inherits, the feature is unavailable, or it errors.
func (s *SlackClient) disappearSettingForChannel(ctx context.Context, info *slack.Channel, roomType database.RoomType) *database.DisappearingSetting {
if s.BootResp == nil {
return nil
}
if retentionType, retentionDuration, ok := s.fetchChannelRetention(ctx, info.ID); ok && retentionType != slackRetentionInherit {
return mapSlackRetention(retentionType, retentionDuration)
}
prefs := s.BootResp.Team.Prefs
switch {
case roomType == database.RoomTypeDM || roomType == database.RoomTypeGroupDM:
return mapSlackRetention(prefs.DMRetentionType, prefs.DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionDuration undefined (type slack.TeamPrefs has no field or method DMRetentionDuration)

Check failure on line 310 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.DMRetentionType undefined (type slack.TeamPrefs has no field or method DMRetentionType)
case info.IsPrivate:
return mapSlackRetention(prefs.GroupRetentionType, prefs.GroupRetentionDuration)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionDuration undefined (type slack.TeamPrefs has no field or method GroupRetentionDuration)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionDuration undefined (type slack.TeamPrefs has no field or method GroupRetentionDuration)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionDuration undefined (type slack.TeamPrefs has no field or method GroupRetentionDuration)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionDuration undefined (type slack.TeamPrefs has no field or method GroupRetentionDuration)

Check failure on line 312 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.GroupRetentionType undefined (type slack.TeamPrefs has no field or method GroupRetentionType)
default:
return mapSlackRetention(prefs.RetentionType, prefs.RetentionDuration)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.RetentionDuration undefined (type slack.TeamPrefs has no field or method RetentionDuration)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.RetentionType undefined (type slack.TeamPrefs has no field or method RetentionType)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.RetentionDuration undefined (type slack.TeamPrefs has no field or method RetentionDuration)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.RetentionType undefined (type slack.TeamPrefs has no field or method RetentionType)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.RetentionDuration undefined (type slack.TeamPrefs has no field or method RetentionDuration)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

prefs.RetentionType undefined (type slack.TeamPrefs has no field or method RetentionType)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.RetentionDuration undefined (type slack.TeamPrefs has no field or method RetentionDuration)

Check failure on line 314 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

prefs.RetentionType undefined (type slack.TeamPrefs has no field or method RetentionType)
}
}

// fetchChannelRetention reads a channel's own retention policy. The second
// return value is false when the value should be ignored (feature unavailable or
// error), in which case the caller falls back to workspace defaults.
func (s *SlackClient) fetchChannelRetention(ctx context.Context, channelID string) (retentionType, retentionDuration int, ok bool) {
if s.customRetentionUnsupported.Load() {
return 0, 0, false
}
retention, err := s.Client.GetConversationRetentionContext(ctx, channelID)

Check failure on line 325 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

s.Client.GetConversationRetentionContext undefined (type *slack.Client has no field or method GetConversationRetentionContext)

Check failure on line 325 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

s.Client.GetConversationRetentionContext undefined (type *slack.Client has no field or method GetConversationRetentionContext)

Check failure on line 325 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (latest)

s.Client.GetConversationRetentionContext undefined (type *slack.Client has no field or method GetConversationRetentionContext)

Check failure on line 325 in pkg/connector/chatinfo.go

View workflow job for this annotation

GitHub Actions / Lint (old)

s.Client.GetConversationRetentionContext undefined (type *slack.Client has no field or method GetConversationRetentionContext)
if err != nil {
var slackErr slack.SlackErrorResponse
if errors.As(err, &slackErr) && slackErr.Err == "not_paid" {
// Per-channel retention is a paid feature; stop trying for this team.
s.customRetentionUnsupported.Store(true)
} else {
zerolog.Ctx(ctx).Debug().Err(err).Str("channel_id", channelID).
Msg("Failed to get channel retention policy")
}
return 0, 0, false
}
return retention.Type, retention.Duration, true
}

// mapSlackRetention converts a Slack retention type/duration pair into a
// disappearing-message setting. Non-deleting policies return an empty setting so
// any previously bridged timer gets cleared.
func mapSlackRetention(retentionType, retentionDuration int) *database.DisappearingSetting {
if retentionType == slackRetentionInherit || retentionType == slackRetentionKeepAll || retentionDuration <= 0 {
return &database.DisappearingSetting{}
}
return &database.DisappearingSetting{
Type: event.DisappearingTypeAfterSend,
Timer: time.Duration(retentionDuration) * 24 * time.Hour,
}
}

func (s *SlackClient) selfPowerLevel(info *slack.Channel) int {
if s.BootResp == nil {
return 0
Expand Down
4 changes: 4 additions & 0 deletions pkg/connector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ type SlackClient struct {
userResyncQueue chan *bridgev2.Ghost
initialConnect time.Time

// Set once conversations.getRetention returns not_paid, so we stop calling
// the per-channel API and fall back to workspace-default retention prefs.
customRetentionUnsupported atomic.Bool

chatInfoCache map[string]chatInfoCacheEntry
chatInfoFetchAttempted map[string]bool
chatInfoCacheLock sync.Mutex
Expand Down
6 changes: 5 additions & 1 deletion pkg/msgconv/from-slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ func (mc *MessageConverter) ToMatrix(
ctx = context.WithValue(ctx, contextKeyPortal, portal)
ctx = context.WithValue(ctx, contextKeySource, source)
client := source.Client.(SlackClientProvider).GetClient()
output := &bridgev2.ConvertedMessage{}
output := &bridgev2.ConvertedMessage{
// Mirror the room's retention timer onto each message so the bridge
// redacts it once Slack's retention window elapses.
Disappear: portal.Disappear,
}
if msg.ThreadTimestamp != "" && msg.ThreadTimestamp != msg.Timestamp {
teamID, channelID := slackid.ParsePortalID(portal.ID)
output.ThreadRoot = ptr.Ptr(slackid.MakeMessageID(teamID, channelID, msg.ThreadTimestamp))
Expand Down
Loading