From 230f357b6e26cf9e540d25f98ba89d73bc1ab681 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 20 Apr 2024 12:44:15 +0200 Subject: [PATCH] Fix even more bugs --- config/bridge.go | 1 + config/upgrade.go | 1 + database/emoji.go | 2 +- database/reaction.go | 2 +- emoji.go | 4 +++- example-config.yaml | 3 +++ msgconv/from-matrix.go | 30 +++++++++++++++++++----------- msgconv/from-slack.go | 4 ++++ portal.go | 33 ++++++++++++++++++--------------- userteam.go | 5 +---- 10 files changed, 52 insertions(+), 33 deletions(-) diff --git a/config/bridge.go b/config/bridge.go index 5495984..9f04eaf 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -72,6 +72,7 @@ type BridgeConfig struct { WorkspaceAvatarInRooms bool `yaml:"workspace_avatar_in_rooms"` ParticipantSyncCount int `yaml:"participant_sync_count"` ParticipantSyncOnlyOnCreate bool `yaml:"participant_sync_only_on_create"` + CaptionInMessage bool `yaml:"caption_in_message"` ManagementRoomText bridgeconfig.ManagementRoomTexts `yaml:"management_room_text"` diff --git a/config/upgrade.go b/config/upgrade.go index a0b60e1..4c427e1 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -38,6 +38,7 @@ func DoUpgrade(helper *up.Helper) { helper.Copy(up.Bool, "bridge", "workspace_avatar_in_rooms") helper.Copy(up.Int, "bridge", "participant_sync_count") helper.Copy(up.Bool, "bridge", "participant_sync_only_on_create") + helper.Copy(up.Bool, "bridge", "caption_in_message") helper.Copy(up.Bool, "bridge", "sync_direct_chat_list") helper.Copy(up.Bool, "bridge", "federate_rooms") if legacyPrivateChatPortalMeta, ok := helper.Get(up.Bool, "bridge", "private_chat_portal_meta"); ok { diff --git a/database/emoji.go b/database/emoji.go index 92c6147..51617e5 100644 --- a/database/emoji.go +++ b/database/emoji.go @@ -67,7 +67,7 @@ func (eq *EmojiQuery) GetBySlackID(ctx context.Context, teamID, emojiID string) } func (eq *EmojiQuery) GetByMXC(ctx context.Context, mxc id.ContentURI) (*Emoji, error) { - return eq.QueryOne(ctx, getEmojiByMXCQuery, mxc) + return eq.QueryOne(ctx, getEmojiByMXCQuery, &mxc) } func buildSQLiteEmojiDeleteQuery(baseQuery string, teamID string, emojiIDs ...string) (string, []any) { diff --git a/database/reaction.go b/database/reaction.go index d249c18..32d692e 100644 --- a/database/reaction.go +++ b/database/reaction.go @@ -51,7 +51,7 @@ const ( ) func (rq *ReactionQuery) GetBySlackID(ctx context.Context, key PortalKey, messageID, authorID, emojiID string) (*Reaction, error) { - return rq.QueryOne(ctx, getReactionBySlackIDQuery, key.ChannelID, key.TeamID, messageID, authorID, emojiID) + return rq.QueryOne(ctx, getReactionBySlackIDQuery, key.TeamID, key.ChannelID, messageID, authorID, emojiID) } func (rq *ReactionQuery) GetByMXID(ctx context.Context, eventID id.EventID) (*Reaction, error) { diff --git a/emoji.go b/emoji.go index 18e6a8d..f13b3c4 100644 --- a/emoji.go +++ b/emoji.go @@ -19,12 +19,13 @@ package main import ( "context" _ "embed" + "fmt" "strings" "github.com/rs/zerolog" + "github.com/slack-go/slack" "maunium.net/go/mautrix/id" - "github.com/slack-go/slack" "go.mau.fi/mautrix-slack/database" "go.mau.fi/mautrix-slack/msgconv/emoji" ) @@ -176,6 +177,7 @@ func (ut *UserTeam) syncEmojis(ctx context.Context, onlyIfCountMismatch bool) er dbEmoji := ut.bridge.DB.Emoji.New() dbEmoji.EmojiID = key dbEmoji.TeamID = ut.TeamID + dbEmoji.Value = fmt.Sprintf("alias:%s", alias) if uri, ok := uploaded[alias]; ok { dbEmoji.Alias = alias dbEmoji.ImageMXC = uri diff --git a/example-config.yaml b/example-config.yaml index 928247b..ec922d5 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -144,6 +144,9 @@ bridge: # Should channel participants only be synced when creating the room? # If you want participants to always be accurately synced, set participant_sync_count to a high value and this to false. participant_sync_only_on_create: true + # Should single images with a caption be sent as a single event to Matrix? + # This requires a client with Matrix 1.10 support. + caption_in_message: true # Should the bridge update the m.direct account data event when double puppeting is enabled. # Note that updating the m.direct event is not atomic (except with mautrix-asmux) diff --git a/msgconv/from-matrix.go b/msgconv/from-matrix.go index e9a24da..d60087f 100644 --- a/msgconv/from-matrix.go +++ b/msgconv/from-matrix.go @@ -30,6 +30,8 @@ import ( "maunium.net/go/mautrix/id" "github.com/slack-go/slack" + + "go.mau.fi/mautrix-slack/database" ) var ( @@ -41,11 +43,16 @@ var ( ErrThreadRootNotFound = errors.New("thread root message not found") ) -func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (sendReq slack.MsgOption, fileUpload *slack.FileUploadParameters, threadRootID string, err error) { +func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (sendReq slack.MsgOption, fileUpload *slack.FileUploadParameters, threadRootID string, editTarget *database.Message, err error) { log := zerolog.Ctx(ctx) content, ok := evt.Content.Parsed.(*event.MessageEventContent) if !ok { - return nil, nil, "", ErrUnexpectedParsedContentType + return nil, nil, "", nil, ErrUnexpectedParsedContentType + } + + if evt.Type == event.EventSticker { + // Slack doesn't have stickers, just bridge stickers as images + content.MsgType = event.MsgImage } var editTargetID string @@ -53,11 +60,12 @@ func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (send existing, err := mc.GetMessageInfo(ctx, replaceEventID) if err != nil { log.Err(err).Msg("Failed to get edit target message") - return nil, nil, "", fmt.Errorf("failed to get edit target message: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to get edit target message: %w", err) } else if existing == nil { - return nil, nil, "", ErrEditTargetNotFound + return nil, nil, "", nil, ErrEditTargetNotFound } else { editTargetID = existing.MessageID + editTarget = existing if content.NewContent != nil { content = content.NewContent } @@ -71,9 +79,9 @@ func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (send if threadMXID != "" { rootMessage, err := mc.GetMessageInfo(ctx, threadMXID) if err != nil { - return nil, nil, "", fmt.Errorf("failed to get thread root message: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to get thread root message: %w", err) } else if rootMessage == nil { - return nil, nil, "", ErrThreadRootNotFound + return nil, nil, "", nil, ErrThreadRootNotFound } else if rootMessage.ThreadID != "" { threadRootID = rootMessage.ThreadID } else { @@ -85,7 +93,7 @@ func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (send if editTargetID != "" && isMediaMsgtype(content.MsgType) { content.MsgType = event.MsgText if content.FileName == "" || content.FileName == content.Body { - return nil, nil, "", ErrMediaOnlyEditCaption + return nil, nil, "", editTarget, ErrMediaOnlyEditCaption } } @@ -107,12 +115,12 @@ func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (send if content.MsgType == event.MsgEmote { options = append(options, slack.MsgOptionMeMessage()) } - return slack.MsgOptionCompose(options...), nil, threadRootID, nil + return slack.MsgOptionCompose(options...), nil, threadRootID, editTarget, nil case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo: data, err := mc.downloadMatrixAttachment(ctx, content) if err != nil { log.Err(err).Msg("Failed to download Matrix attachment") - return nil, nil, "", ErrMediaDownloadFailed + return nil, nil, "", editTarget, ErrMediaDownloadFailed } var filename, caption string @@ -132,9 +140,9 @@ func (mc *MessageConverter) ToSlack(ctx context.Context, evt *event.Event) (send if caption != "" { fileUpload.InitialComment = caption } - return nil, fileUpload, threadRootID, nil + return nil, fileUpload, threadRootID, editTarget, nil default: - return nil, nil, "", ErrUnknownMsgType + return nil, nil, "", editTarget, ErrUnknownMsgType } } diff --git a/msgconv/from-slack.go b/msgconv/from-slack.go index b835395..c8166d4 100644 --- a/msgconv/from-slack.go +++ b/msgconv/from-slack.go @@ -107,6 +107,10 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, msg *slack.Msg) *Conve output.Parts = append(output.Parts, textPart) } for i, file := range msg.Files { + // mode=tombstone seems to mean the file was deleted + if file.Mode == "tombstone" { + continue + } partID := database.PartID{ Type: database.PartTypeFile, Index: i, diff --git a/portal.go b/portal.go index 89ce323..037b2db 100644 --- a/portal.go +++ b/portal.go @@ -27,15 +27,11 @@ import ( "time" "github.com/rs/zerolog" + "github.com/slack-go/slack" "go.mau.fi/util/exslices" "golang.org/x/exp/slices" log "maunium.net/go/maulogger/v2" "maunium.net/go/maulogger/v2/maulogadapt" - - "github.com/slack-go/slack" - "go.mau.fi/mautrix-slack/msgconv" - "go.mau.fi/mautrix-slack/msgconv/emoji" - "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/bridge" @@ -45,6 +41,8 @@ import ( "go.mau.fi/mautrix-slack/config" "go.mau.fi/mautrix-slack/database" + "go.mau.fi/mautrix-slack/msgconv" + "go.mau.fi/mautrix-slack/msgconv/emoji" ) type portalMatrixMessage struct { @@ -555,7 +553,7 @@ func (portal *Portal) handleMatrixMessages(msg portalMatrixMessage) { }) switch msg.evt.Type { - case event.EventMessage: + case event.EventMessage, event.EventSticker: portal.handleMatrixMessage(ctx, ut, msg.evt, &ms) case event.EventRedaction: portal.handleMatrixRedaction(ctx, ut, msg.evt) @@ -570,7 +568,7 @@ func (portal *Portal) handleMatrixMessage(ctx context.Context, sender *UserTeam, log := zerolog.Ctx(ctx) ctx = context.WithValue(ctx, convertContextKeySource, sender) start := time.Now() - sendOpts, fileUpload, threadID, err := portal.MsgConv.ToSlack(ctx, evt) + sendOpts, fileUpload, threadID, editTarget, err := portal.MsgConv.ToSlack(ctx, evt) ms.timings.convert = time.Since(start) start = time.Now() @@ -611,7 +609,7 @@ func (portal *Portal) handleMatrixMessage(ctx context.Context, sender *UserTeam, } ms.timings.totalSend = time.Since(start) go ms.sendMessageMetrics(ctx, evt, err, "Error sending", true) - if timestamp != "" { + if timestamp != "" && editTarget == nil { dbMsg := portal.bridge.DB.Message.New() dbMsg.PortalKey = portal.PortalKey dbMsg.MessageID = timestamp @@ -1435,6 +1433,9 @@ func (portal *Portal) HandleSlackMessage(ctx context.Context, source *UserTeam, if slackAuthor == "" { slackAuthor = msg.BotID } + if slackAuthor == "" && msg.SubMessage != nil { + slackAuthor = msg.SubMessage.User + } var sender *Puppet if slackAuthor != "" { sender = portal.Team.GetPuppetByID(slackAuthor) @@ -1512,10 +1513,11 @@ func (portal *Portal) HandleSlackEditMessage(ctx context.Context, source *UserTe ts := parseSlackTimestamp(msg.Timestamp) ctx = context.WithValue(ctx, convertContextKeySource, source) ctx = context.WithValue(ctx, convertContextKeyIntent, intent) + // TODO avoid reuploading files when editing messages converted := portal.MsgConv.ToMatrix(ctx, msg) // Don't merge caption if there's more than one part in the database // (because it means the original didn't have a merged caption) - if len(editTarget) == 1 { + if len(editTarget) == 1 && portal.bridge.Config.Bridge.CaptionInMessage { converted.MergeCaption() } @@ -1610,6 +1612,9 @@ func (portal *Portal) HandleSlackNormalMessage(ctx context.Context, source *User ctx = context.WithValue(ctx, convertContextKeySource, source) ctx = context.WithValue(ctx, convertContextKeyIntent, intent) converted := portal.MsgConv.ToMatrix(ctx, msg) + if portal.bridge.Config.Bridge.CaptionInMessage { + converted.MergeCaption() + } var lastEventID id.EventID for _, part := range converted.Parts { @@ -1673,9 +1678,7 @@ func (portal *Portal) HandleSlackReaction(ctx context.Context, source *UserTeam, return } - slackReaction := strings.Trim(msg.Reaction, ":") - - key := source.GetEmoji(ctx, slackReaction) + key := source.GetEmoji(ctx, msg.Reaction) var content event.ReactionEventContent content.RelatesTo = event.RelatesTo{ @@ -1686,12 +1689,12 @@ func (portal *Portal) HandleSlackReaction(ctx context.Context, source *UserTeam, extraContent := map[string]any{} if strings.HasPrefix(key, "mxc://") { extraContent["fi.mau.slack.reaction"] = map[string]any{ - "name": slackReaction, + "name": msg.Reaction, "mxc": key, } - extraContent["com.beeper.reaction.shortcode"] = msg.Reaction + extraContent["com.beeper.reaction.shortcode"] = fmt.Sprintf(":%s:", msg.Reaction) if !portal.bridge.Config.Bridge.CustomEmojiReactions { - content.RelatesTo.Key = slackReaction + content.RelatesTo.Key = msg.Reaction } } diff --git a/userteam.go b/userteam.go index ef10d86..e179714 100644 --- a/userteam.go +++ b/userteam.go @@ -426,10 +426,7 @@ func (ut *UserTeam) handleSlackEvent(ctx context.Context, rawEvt any) { case *slack.InvalidAuthEvent: ut.Logout(context.TODO(), status.BridgeState{StateEvent: status.StateBadCredentials, Error: "slack-invalid-auth"}) return - case *slack.LatencyReport: - ut.Log.Trace().Dur("latency", evt.Value).Msg("Latency report") case *slack.MessageEvent: - ut.Log.Trace().Any("event_content", evt).Msg("Received Slack message event") ut.pushPortalEvent(evt.Channel, evt) case *slack.ReactionAddedEvent: ut.pushPortalEvent(evt.Item.Channel, evt) @@ -466,7 +463,7 @@ func (ut *UserTeam) handleSlackEvent(ctx context.Context, rawEvt any) { Error: status.BridgeStateErrorCode(fmt.Sprintf("slack-rtm-error-%d", evt.Code)), Message: fmt.Sprintf("%d: %s", evt.Code, evt.Msg), }) - case *slack.FileSharedEvent, *slack.FilePublicEvent, *slack.FilePrivateEvent, *slack.FileCreatedEvent, *slack.FileChangeEvent, *slack.FileDeletedEvent, *slack.DesktopNotificationEvent: + case *slack.FileSharedEvent, *slack.FilePublicEvent, *slack.FilePrivateEvent, *slack.FileCreatedEvent, *slack.FileChangeEvent, *slack.FileDeletedEvent, *slack.DesktopNotificationEvent, *slack.ReconnectUrlEvent, *slack.LatencyReport: // ignored intentionally, these are duplicates or do not contain useful information default: ut.Log.Warn().Any("event_data", evt).Msg("Unrecognized Slack event type")