diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 5baa5983..7896ad89 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -8,6 +8,7 @@ import ( up "go.mau.fi/util/configupgrade" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-telegram/pkg/connector/media" ) @@ -92,7 +93,8 @@ type GhostMetadata struct { } type MessageMetadata struct { - ContentHash []byte `json:"content_hash,omitempty"` + ContentHash []byte `json:"content_hash,omitempty"` + ContentURI id.ContentURIString `json:"content_uri,omitempty"` } type UserLoginMetadata struct { diff --git a/pkg/connector/matrix.go b/pkg/connector/matrix.go index 93508c4a..faa27945 100644 --- a/pkg/connector/matrix.go +++ b/pkg/connector/matrix.go @@ -13,10 +13,12 @@ import ( "github.com/gotd/td/telegram/message/styling" "github.com/gotd/td/telegram/uploader" "github.com/gotd/td/tg" + "github.com/rs/zerolog" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" "go.mau.fi/util/variationselector" @@ -45,11 +47,13 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. } builder := message.NewSender(t.client.API()).To(peer) + var contentURI id.ContentURIString // TODO handle sticker var updates tg.UpdatesClass switch msg.Content.MsgType { case event.MsgText: + // TODO unify with edits? if msg.Content.BeeperLinkPreviews != nil && len(msg.Content.BeeperLinkPreviews) == 0 { builder.NoWebpage() } @@ -62,6 +66,11 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. if err != nil { return nil, fmt.Errorf("failed to download media from Matrix: %w", err) } + contentURI = msg.Content.URL + if contentURI == "" { + contentURI = msg.Content.File.URL + } + uploader := uploader.NewUploader(t.client.API()) var upload tg.InputFileClass upload, err = uploader.FromBytes(ctx, filename, fileData) @@ -132,13 +141,13 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. } hasher := sha256.New() - hasher.Write([]byte(msg.Content.Body)) var tgMessageID, tgDate int switch sentMessage := updates.(type) { case *tg.UpdateShortSentMessage: tgMessageID = sentMessage.ID tgDate = sentMessage.Date + hasher.Write([]byte(msg.Content.Body)) case *tg.Updates: tgDate = sentMessage.Date for _, u := range sentMessage.Updates { @@ -147,6 +156,7 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. tgMessageID = update.ID case *tg.UpdateNewMessage: msg := update.Message.(*tg.Message) + hasher.Write([]byte(msg.Message)) hasher.Write(mediaHashID(msg.Media)) } } @@ -164,14 +174,134 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. Room: networkid.PortalKey{ID: msg.Portal.ID}, SenderID: t.userID, Timestamp: time.Unix(int64(tgDate), 0), - Metadata: &MessageMetadata{ContentHash: hasher.Sum(nil)}, + Metadata: &MessageMetadata{ + ContentHash: hasher.Sum(nil), + ContentURI: contentURI, + }, }, } return } func (t *TelegramClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.MatrixEdit) error { - panic("unimplemented edit") + log := zerolog.Ctx(ctx).With(). + Str("conversion_direction", "to_telegram"). + Str("handler", "matrix_edit"). + Logger() + + peer, err := t.inputPeerForPortalID(ctx, msg.Portal.ID) + if err != nil { + return err + } + + b := message.NewSender(t.client.API()).To(peer) + if msg.Content.MsgType == event.MsgText && msg.Content.BeeperLinkPreviews != nil && len(msg.Content.BeeperLinkPreviews) == 0 { + b.NoWebpage() + } + + targetID, err := ids.ParseMessageID(msg.EditTarget.ID) + if err != nil { + return err + } + builder := b.Edit(targetID) + + var newContentURI id.ContentURIString + var updates tg.UpdatesClass + switch msg.Content.MsgType { + case event.MsgText: + updates, err = builder.Text(ctx, msg.Content.Body) + case event.MsgImage, event.MsgFile, event.MsgAudio, event.MsgVideo: + filename, caption := getMediaFilenameAndCaption(msg.Content) + + var styling []styling.StyledTextOption + if caption != "" { + // TODO resolver? + // TODO HTML + styling = append(styling, html.String(nil, caption)) + } + + newContentURI = msg.Content.URL + if newContentURI == "" { + newContentURI = msg.Content.File.URL + } + if msg.EditTarget.Metadata.(*MessageMetadata).ContentURI == newContentURI { + log.Info().Msg("media URI unchanged, skipping re-upload, just editing text") + updates, err = builder.StyledText(ctx, styling...) + break + } + + log.Info().Msg("media URI changed, re-uploading media") + + var fileData []byte + fileData, err = t.main.Bridge.Bot.DownloadMedia(ctx, msg.Content.URL, msg.Content.File) + if err != nil { + return fmt.Errorf("failed to download media from Matrix: %w", err) + } + uploader := uploader.NewUploader(t.client.API()) + var upload tg.InputFileClass + upload, err = uploader.FromBytes(ctx, filename, fileData) + if err != nil { + return fmt.Errorf("failed to upload media to Telegram: %w", err) + } + + if msg.Content.MsgType == event.MsgImage { + updates, err = builder.Media(ctx, message.UploadedPhoto(upload, styling...)) + break + } else { + document := message.UploadedDocument(upload, styling...).Filename(filename) + if msg.Content.Info != nil { + document.MIME(msg.Content.Info.MimeType) + } + + var media message.MediaOption + + switch msg.Content.MsgType { + case event.MsgAudio: + audioBuilder := document.Audio() + if msg.Content.MSC1767Audio != nil { + audioBuilder.Duration(time.Duration(msg.Content.MSC1767Audio.Duration) * time.Millisecond) + if len(msg.Content.MSC1767Audio.Waveform) > 0 { + audioBuilder.Waveform(waveform.Encode(msg.Content.MSC1767Audio.Waveform)) + } + } + if msg.Content.MSC3245Voice != nil { + audioBuilder.Voice() + } + media = audioBuilder + default: + media = document + } + updates, err = builder.Media(ctx, media) + } + default: + return fmt.Errorf("unsupported message type %s", msg.Content.MsgType) + } + if err != nil { + return err + } + + hasher := sha256.New() + + switch sentMessage := updates.(type) { + case *tg.UpdateShortSentMessage: + hasher.Write([]byte(msg.Content.Body)) + case *tg.Updates: + for _, u := range sentMessage.Updates { + switch update := u.(type) { + case *tg.UpdateNewMessage: + msg := update.Message.(*tg.Message) + hasher.Write([]byte(msg.Message)) + hasher.Write(mediaHashID(msg.Media)) + } + } + default: + return fmt.Errorf("unknown update from message response %T", updates) + } + + metadata := msg.EditTarget.Metadata.(*MessageMetadata) + metadata.ContentHash = hasher.Sum(nil) + metadata.ContentURI = newContentURI + return nil } func (t *TelegramClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error { diff --git a/pkg/connector/tomatrix.go b/pkg/connector/tomatrix.go index b5c1009d..816bfc4b 100644 --- a/pkg/connector/tomatrix.go +++ b/pkg/connector/tomatrix.go @@ -130,6 +130,7 @@ func (c *TelegramClient) convertToMatrix(ctx context.Context, portal *bridgev2.P } } + var contentURI id.ContentURIString mediaPart, disappearingSetting, mediaHashID, err := c.mediaToMatrix(ctx, portal, intent, msg) if err != nil { return nil, err @@ -138,11 +139,19 @@ func (c *TelegramClient) convertToMatrix(ctx context.Context, portal *bridgev2.P cm.Parts = append(cm.Parts, mediaPart) cm.MergeCaption() + contentURI = mediaPart.Content.URL + if contentURI == "" { + contentURI = mediaPart.Content.File.URL + } + if disappearingSetting != nil { cm.Disappear = *disappearingSetting } } - cm.Parts[0].DBMetadata = &MessageMetadata{ContentHash: hasher.Sum(nil)} + cm.Parts[0].DBMetadata = &MessageMetadata{ + ContentHash: hasher.Sum(nil), + ContentURI: contentURI, + } return cm, nil }