From a38ab078d283a16e85ba1a2be81ca1d8ac0ea701 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 7 Oct 2024 17:13:20 +0300 Subject: [PATCH] msgconv/from-whatsapp: add option to disable bridging view-once media --- pkg/connector/backfill.go | 7 ++++--- pkg/connector/config.go | 2 ++ pkg/connector/connector.go | 1 + pkg/connector/events.go | 16 ++++++++++------ pkg/connector/example-config.yaml | 2 ++ pkg/msgconv/from-whatsapp.go | 13 +++++++------ pkg/msgconv/msgconv.go | 1 + pkg/msgconv/wa-business.go | 6 +++--- pkg/msgconv/wa-media.go | 11 ++++++++++- 9 files changed, 40 insertions(+), 19 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 61dc2cdc..9c35a83c 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -279,7 +279,8 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet return nil, fmt.Errorf("failed to parse info of message %s: %w", msg.GetKey().GetID(), err) } var mediaReq *wadb.MediaRequest - convertedMessages[i], mediaReq = wa.convertHistorySyncMessage(ctx, params.Portal, &evt.Info, evt.Message, msg.Reactions) + isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension + convertedMessages[i], mediaReq = wa.convertHistorySyncMessage(ctx, params.Portal, &evt.Info, evt.Message, isViewOnce, msg.Reactions) if mediaReq != nil { mediaRequests = append(mediaRequests, mediaReq) } @@ -324,12 +325,12 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } func (wa *WhatsAppClient) convertHistorySyncMessage( - ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg *waE2E.Message, reactions []*waWeb.Reaction, + ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, ) (*bridgev2.BackfillMessage, *wadb.MediaRequest) { // TODO use proper intent intent := wa.Main.Bridge.Bot wrapped := &bridgev2.BackfillMessage{ - ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, info), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, info, isViewOnce), Sender: wa.makeEventSender(info.Sender), ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID), TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)), diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 58c1f1cf..29f5eb79 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -43,6 +43,7 @@ type Config struct { WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"` URLPreviews bool `yaml:"url_previews"` ExtEvPolls bool `yaml:"extev_polls"` + DisableViewOnce bool `yaml:"disable_view_once"` ForceActiveDeliveryReceipts bool `yaml:"force_active_delivery_receipts"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -102,6 +103,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "whatsapp_thumbnail") helper.Copy(up.Bool, "url_previews") helper.Copy(up.Bool, "extev_polls") + helper.Copy(up.Bool, "disable_view_once") helper.Copy(up.Bool, "force_active_delivery_receipts") helper.Copy(up.Str, "animated_sticker", "target") diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 8cfebbf9..85f7266b 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -48,6 +48,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { wa.MsgConv = msgconv.New(bridge) wa.MsgConv.AnimatedStickerConfig = wa.Config.AnimatedSticker wa.MsgConv.ExtEvPolls = wa.Config.ExtEvPolls + wa.MsgConv.DisableViewOnce = wa.Config.DisableViewOnce wa.MsgConv.OldMediaSuffix = "Requesting old media is not enabled on this bridge." wa.MsgConv.FetchURLPreviews = wa.Config.URLPreviews if wa.Config.HistorySync.MediaRequests.AutoRequestMedia { diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 698dfe26..2cf61ec8 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -93,10 +93,6 @@ type WAMessageEvent struct { postHandle func() } -func (evt *WAMessageEvent) GetStreamOrder() int64 { - return evt.Info.Timestamp.Unix() -} - var ( _ bridgev2.RemoteMessage = (*WAMessageEvent)(nil) _ bridgev2.RemoteMessageUpsert = (*WAMessageEvent)(nil) @@ -112,6 +108,14 @@ var ( _ bridgev2.RemotePostHandler = (*WAMessageEvent)(nil) ) +func (evt *WAMessageEvent) GetStreamOrder() int64 { + return evt.Info.Timestamp.Unix() +} + +func (evt *WAMessageEvent) isViewOnce() bool { + return evt.MsgEvent.IsViewOnce || evt.MsgEvent.IsViewOnceV2 || evt.MsgEvent.IsViewOnceV2Extension +} + func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context { return evt.MessageInfoWrapper.AddLogContext(c).Str("parsed_message_type", evt.parsedMessageType) } @@ -136,7 +140,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } // TODO edits to media captions may not contain the media - cm := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, editedMsg, &evt.Info) + cm := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, editedMsg, &evt.Info, evt.isViewOnce()) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) @@ -224,7 +228,7 @@ func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2. func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) { evt.wa.EnqueuePortalResync(portal) - converted := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, evt.Message, &evt.Info) + converted := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, evt.Message, &evt.Info, evt.isViewOnce()) if isFailedMedia(converted) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), converted, false) diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 7c2ce08d..31b90b66 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -47,6 +47,8 @@ whatsapp_thumbnail: false url_previews: false # Should polls be sent using unstable MSC3381 event types? extev_polls: false +# Should view-once messages be disabled entirely? +disable_view_once: false # Should the bridge always send "active" delivery receipts (two gray ticks on WhatsApp) # even if the user isn't marked as online (e.g. when presence bridging isn't enabled)? # diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 11f35bb1..126ab445 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -102,6 +102,7 @@ func (mc *MessageConverter) ToMatrix( intent bridgev2.MatrixAPI, waMsg *waE2E.Message, info *types.MessageInfo, + isViewOnce bool, ) *bridgev2.ConvertedMessage { ctx = context.WithValue(ctx, contextKeyClient, client) ctx = context.WithValue(ctx, contextKeyIntent, intent) @@ -132,21 +133,21 @@ func (mc *MessageConverter) ToMatrix( case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.ImageMessage != nil: - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo") + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", isViewOnce) case waMsg.StickerMessage != nil: - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.StickerMessage, "sticker") + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.StickerMessage, "sticker", isViewOnce) case waMsg.VideoMessage != nil: - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.VideoMessage, "video attachment") + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.VideoMessage, "video attachment", isViewOnce) case waMsg.PtvMessage != nil: - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.PtvMessage, "video message") + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.PtvMessage, "video message", isViewOnce) case waMsg.AudioMessage != nil: typeName := "audio attachment" if waMsg.AudioMessage.GetPTT() { typeName = "voice message" } - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.AudioMessage, typeName) + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.AudioMessage, typeName, isViewOnce) case waMsg.DocumentMessage != nil: - part, contextInfo = mc.convertMediaMessage(ctx, waMsg.DocumentMessage, "file attachment") + part, contextInfo = mc.convertMediaMessage(ctx, waMsg.DocumentMessage, "file attachment", isViewOnce) case waMsg.LocationMessage != nil: part, contextInfo = mc.convertLocationMessage(ctx, waMsg.LocationMessage) case waMsg.LiveLocationMessage != nil: diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go index 131581f4..efb7848a 100644 --- a/pkg/msgconv/msgconv.go +++ b/pkg/msgconv/msgconv.go @@ -40,6 +40,7 @@ type MessageConverter struct { AnimatedStickerConfig AnimatedStickerConfig FetchURLPreviews bool ExtEvPolls bool + DisableViewOnce bool OldMediaSuffix string } diff --git a/pkg/msgconv/wa-business.go b/pkg/msgconv/wa-business.go index eca0a4f7..572b3d6c 100644 --- a/pkg/msgconv/wa-business.go +++ b/pkg/msgconv/wa-business.go @@ -70,11 +70,11 @@ func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *ty var convertedTitle *bridgev2.ConvertedMessagePart switch title := tpl.GetTitle().(type) { case *waE2E.TemplateMessage_HydratedFourRowTemplate_DocumentMessage: - convertedTitle, _ = mc.convertMediaMessage(ctx, title.DocumentMessage, "file attachment") + convertedTitle, _ = mc.convertMediaMessage(ctx, title.DocumentMessage, "file attachment", false) case *waE2E.TemplateMessage_HydratedFourRowTemplate_ImageMessage: - convertedTitle, _ = mc.convertMediaMessage(ctx, title.ImageMessage, "photo") + convertedTitle, _ = mc.convertMediaMessage(ctx, title.ImageMessage, "photo", false) case *waE2E.TemplateMessage_HydratedFourRowTemplate_VideoMessage: - convertedTitle, _ = mc.convertMediaMessage(ctx, title.VideoMessage, "video attachment") + convertedTitle, _ = mc.convertMediaMessage(ctx, title.VideoMessage, "video attachment", false) case *waE2E.TemplateMessage_HydratedFourRowTemplate_LocationMessage: content = fmt.Sprintf("Unsupported location message\n\n%s", content) case *waE2E.TemplateMessage_HydratedFourRowTemplate_HydratedTitleText: diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index f3aeadef..71d2d010 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -44,7 +44,16 @@ import ( "maunium.net/go/mautrix-whatsapp/pkg/waid" ) -func (mc *MessageConverter) convertMediaMessage(ctx context.Context, msg MediaMessage, typeName string) (part *bridgev2.ConvertedMessagePart, contextInfo *waE2E.ContextInfo) { +func (mc *MessageConverter) convertMediaMessage(ctx context.Context, msg MediaMessage, typeName string, isViewOnce bool) (part *bridgev2.ConvertedMessagePart, contextInfo *waE2E.ContextInfo) { + if mc.DisableViewOnce && isViewOnce { + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: fmt.Sprintf("You received a view once %s. For added privacy, you can only open it on the WhatsApp app.", typeName), + }, + }, nil + } preparedMedia := prepareMediaMessage(msg) preparedMedia.TypeDescription = typeName if preparedMedia.FileName != "" && preparedMedia.Body != preparedMedia.FileName {