From ce1c28832e9b669f7de5aad2c2db84ef5e936ee2 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Wed, 25 Sep 2024 16:43:13 -0600 Subject: [PATCH] reactions: use allowed reactions when possible Signed-off-by: Sumner Evans --- cmd/mautrix-telegram/legacymigrate.go | 1 + pkg/connector/client.go | 6 ++++ pkg/connector/config.go | 3 ++ pkg/connector/example-config.yaml | 6 ++++ pkg/connector/matrix.go | 27 ++++++++++++++--- pkg/connector/telegram.go | 42 +++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 4 deletions(-) diff --git a/cmd/mautrix-telegram/legacymigrate.go b/cmd/mautrix-telegram/legacymigrate.go index 8005fd7f..f2c02a42 100644 --- a/cmd/mautrix-telegram/legacymigrate.go +++ b/cmd/mautrix-telegram/legacymigrate.go @@ -49,4 +49,5 @@ func migrateLegacyConfig(helper up.Helper) { bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "sync_update_limit"}, []string{"network", "sync", "update_limit"}) bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "sync_create_limit"}, []string{"network", "sync", "create_limit"}) bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "sync_direct_chats"}, []string{"network", "sync", "direct_chats"}) + bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "always_custom_emoji_reaction"}, []string{"network", "always_custom_emoji_reaction"}) } diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 6157094e..e14d4b33 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -44,9 +44,15 @@ type TelegramClient struct { updatesManager *updates.Manager clientCancel context.CancelFunc + appConfigLock sync.Mutex appConfig map[string]any appConfigHash int + availableReactionsLock sync.Mutex + availableReactions map[string]struct{} + availableReactionsHash int + availableReactionsFetched time.Time + telegramFmtParams *telegramfmt.FormatParams matrixParser *matrixfmt.HTMLParser diff --git a/pkg/connector/config.go b/pkg/connector/config.go index c2a8bfea..e1f9d163 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -63,6 +63,8 @@ type TelegramConfig struct { CreateLimit int `yaml:"create_limit"` DirectChats bool `yaml:"direct_chats"` } `yaml:"sync"` + + AlwaysCustomEmojiReaction bool `yaml:"always_custom_emoji_reaction"` } func (c TelegramConfig) ShouldBridge(participantCount int) bool { @@ -96,6 +98,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Int, "sync", "update_limit") helper.Copy(up.Int, "sync", "create_limit") helper.Copy(up.Bool, "sync", "direct_chats") + helper.Copy(up.Bool, "always_custom_emoji_reaction") } func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.Upgrader) { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 526834d2..9d5775ac 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -70,3 +70,9 @@ sync: create_limit: 15 # Whether or not to sync and create portals for direct chats at startup. direct_chats: false + + +# Should the bridge send all unicode reactions as custom emoji reactions to +# Telegram? By default, the bridge only uses custom emojis for unicode emojis +# that aren't allowed in reactions. +always_custom_emoji_reaction: false diff --git a/pkg/connector/matrix.go b/pkg/connector/matrix.go index 3d2e3941..7dcf190b 100644 --- a/pkg/connector/matrix.go +++ b/pkg/connector/matrix.go @@ -285,6 +285,11 @@ func (t *TelegramClient) HandleMatrixMessageRemove(ctx context.Context, msg *bri } func (t *TelegramClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) { + log := zerolog.Ctx(ctx).With(). + Str("conversion_direction", "to_telegram"). + Str("handler", "pre_handle_matrix_reaction"). + Str("key", msg.Content.RelatesTo.Key). + Logger() var resp bridgev2.MatrixReactionPreResponse var maxReactions int @@ -293,7 +298,8 @@ func (t *TelegramClient) PreHandleMatrixReaction(ctx context.Context, msg *bridg return resp, err } - var emojiID networkid.EmojiID + keyNoVariation := variationselector.Remove(msg.Content.RelatesTo.Key) + emojiID := ids.MakeEmojiIDFromEmoticon(msg.Content.RelatesTo.Key) if strings.HasPrefix(msg.Content.RelatesTo.Key, "mxc://") { if file, err := t.main.Store.TelegramFile.GetByMXC(ctx, msg.Content.RelatesTo.Key); err != nil { return resp, err @@ -304,12 +310,25 @@ func (t *TelegramClient) PreHandleMatrixReaction(ctx context.Context, msg *bridg } else { emojiID = ids.MakeEmojiIDFromDocumentID(documentID) } - } else if documentID, ok := emojis.GetEmojiDocumentID(msg.Content.RelatesTo.Key); ok { - emojiID = ids.MakeEmojiIDFromDocumentID(documentID) + } else if t.main.Config.AlwaysCustomEmojiReaction { + // Always use the unicodemoji reaction if available + if documentID, ok := emojis.GetEmojiDocumentID(keyNoVariation); ok { + log.Debug().Msg("Using custom emoji reaction") + emojiID = ids.MakeEmojiIDFromDocumentID(documentID) + } + } else if availableReactions, err := t.getAvailableReactions(ctx); err != nil { + return resp, fmt.Errorf("failed to get available reactions: %w", err) + } else if _, ok := availableReactions[keyNoVariation]; ok { + log.Debug().Msg("Not using custom emoji reaction since the emoji is available") } else { - emojiID = ids.MakeEmojiIDFromEmoticon(msg.Content.RelatesTo.Key) + if documentID, ok := emojis.GetEmojiDocumentID(keyNoVariation); ok { + log.Debug().Msg("Using custom emoji reaction") + emojiID = ids.MakeEmojiIDFromDocumentID(documentID) + } } + log.Debug().Str("emoji_id", string(emojiID)).Msg("Pre-handled reaction") + return bridgev2.MatrixReactionPreResponse{ SenderID: t.userID, EmojiID: emojiID, diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 4da17e44..e0285064 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -515,6 +515,8 @@ func (t *TelegramClient) inputPeerForPortalID(ctx context.Context, portalID netw } func (t *TelegramClient) getAppConfigCached(ctx context.Context) (map[string]any, error) { + t.appConfigLock.Lock() + defer t.appConfigLock.Unlock() if t.appConfig == nil { cfg, err := t.client.API().HelpGetAppConfig(ctx, t.appConfigHash) if err != nil { @@ -537,6 +539,46 @@ func (t *TelegramClient) getAppConfigCached(ctx context.Context) (map[string]any return t.appConfig, nil } +func (t *TelegramClient) getAvailableReactions(ctx context.Context) (map[string]struct{}, error) { + log := zerolog.Ctx(ctx).With().Str("handler", "get_available_reactions").Logger() + t.availableReactionsLock.Lock() + defer t.availableReactionsLock.Unlock() + if t.availableReactions == nil || time.Since(t.availableReactionsFetched) > 12*time.Hour { + cfg, err := t.client.API().MessagesGetAvailableReactions(ctx, t.availableReactionsHash) + if err != nil { + return nil, err + } + t.availableReactionsFetched = time.Now() + switch v := cfg.(type) { + case *tg.MessagesAvailableReactions: + availableReactions, ok := cfg.(*tg.MessagesAvailableReactions) + if !ok { + return nil, fmt.Errorf("failed to get app config: unexpected type %T", availableReactions) + } + + log.Debug().Msg("Fetched new available reactions") + + myGhost, err := t.main.Bridge.GetGhostByID(ctx, t.userID) + if err != nil { + log.Err(err).Msg("failed to get own ghost") + } + t.availableReactions = make(map[string]struct{}, len(availableReactions.Reactions)) + for _, reaction := range availableReactions.Reactions { + if !reaction.Inactive && (myGhost.Metadata.(*GhostMetadata).IsPremium || !reaction.Premium) { + t.availableReactions[reaction.Reaction] = struct{}{} + } + } + + t.availableReactionsHash = availableReactions.Hash + case *tg.MessagesAvailableReactionsNotModified: + log.Debug().Msg("Available reactions not modified") + default: + log.Error().Type("reaction_type", v).Msg("failed to get available reactions: unexpected type") + } + } + return t.availableReactions, nil +} + func (t *TelegramClient) transferEmojisToMatrix(ctx context.Context, customEmojiIDs []int64) (result map[networkid.EmojiID]string, err error) { result, customEmojiIDs = emojis.ConvertKnownEmojis(customEmojiIDs)