diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 0b78ed8c..8b6793a5 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -19,7 +19,6 @@ import ( "go.mau.fi/mautrix-telegram/pkg/connector/ids" "go.mau.fi/mautrix-telegram/pkg/connector/media" - "go.mau.fi/mautrix-telegram/pkg/connector/msgconv" "go.mau.fi/mautrix-telegram/pkg/connector/store" "go.mau.fi/mautrix-telegram/pkg/connector/util" ) @@ -33,7 +32,6 @@ type TelegramClient struct { userLogin *bridgev2.UserLogin client *telegram.Client clientCancel context.CancelFunc - msgConv *msgconv.MessageConverter reactionMessageLocks map[int]*sync.Mutex @@ -141,7 +139,6 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge Logger: zaplog, UpdateHandler: updatesManager, }) - client.msgConv = msgconv.NewMessageConverter(client.client, tc.Bridge.Matrix, tc.Store, tc.Config.AnimatedSticker, tc.useDirectMedia) client.clientCancel, err = connectTelegramClient(ctx, client.client) client.reactionMessageLocks = map[int]*sync.Mutex{} go func() { diff --git a/pkg/connector/config.go b/pkg/connector/config.go index ce89b351..5baa5983 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -78,15 +78,11 @@ func (tg *TelegramConnector) ValidateConfig() error { func (tg *TelegramConnector) GetDBMetaTypes() database.MetaTypes { return database.MetaTypes{ - Ghost: func() any { - return &GhostMetadata{} - }, - Portal: nil, - Message: nil, - Reaction: nil, - UserLogin: func() any { - return &UserLoginMetadata{} - }, + Ghost: func() any { return &GhostMetadata{} }, + Portal: nil, + Message: func() any { return &MessageMetadata{} }, + Reaction: nil, + UserLogin: func() any { return &UserLoginMetadata{} }, } } @@ -95,6 +91,10 @@ type GhostMetadata struct { AccessHash int64 `json:"access_hash"` } +type MessageMetadata struct { + ContentHash []byte `json:"content_hash,omitempty"` +} + type UserLoginMetadata struct { Phone string `json:"phone"` } diff --git a/pkg/connector/msgconv/geouri.go b/pkg/connector/geouri.go similarity index 67% rename from pkg/connector/msgconv/geouri.go rename to pkg/connector/geouri.go index d32ec92c..8c4e848b 100644 --- a/pkg/connector/msgconv/geouri.go +++ b/pkg/connector/geouri.go @@ -1,7 +1,6 @@ -package msgconv +package connector import ( - "encoding/json" "fmt" "strconv" "strings" @@ -12,9 +11,6 @@ type GeoURI struct { Long float64 } -var _ json.Unmarshaler = (*GeoURI)(nil) -var _ json.Marshaler = (*GeoURI)(nil) - func GeoURIFromLatLong(lat, long float64) GeoURI { return GeoURI{lat, long} } @@ -42,19 +38,3 @@ func ParseGeoURI(uri string) (g GeoURI, err error) { func (g GeoURI) URI() string { return fmt.Sprintf("geo:%f,%f", g.Lat, g.Long) } - -func (g *GeoURI) UnmarshalJSON(data []byte) (err error) { - var uri string - err = json.Unmarshal(data, &uri) - if err != nil { - return err - } - geo, err := ParseGeoURI(uri) - g.Lat = geo.Lat - g.Long = geo.Long - return -} - -func (g *GeoURI) MarshalJSON() ([]byte, error) { - return json.Marshal(g.URI()) -} diff --git a/pkg/connector/ids/media.go b/pkg/connector/ids/media.go index e5f02ec4..98b14623 100644 --- a/pkg/connector/ids/media.go +++ b/pkg/connector/ids/media.go @@ -13,18 +13,20 @@ import ( // The format of the media ID is as follows (each character represents a single // byte, |'s added for clarity): // -// v|p|cccccccc|mmmmmmmm|T +// v|p|cccccccc|mmmmmmmm|T|MMMMMMMM // // v (int8) = binary encoding format version. Should be 0. // p (byte) = the peer type of the Telegram chat ID // cccccccc (int64) = the Telegram chat ID (big endian) // mmmmmmmm (int64) = the Telegram message ID (big endian) -// T (byte) = 0 or 1 depending on whether it's a thumbnail +// T (byte) = 0 or 1 depending on whether it's a thumbnail (optional) +// MMMMMMMM (int64) = the Telegram media ID (big endian) (optional) type DirectMediaInfo struct { - PeerType PeerType - ChatID int64 - MessageID int64 - Thumbnail bool + PeerType PeerType + ChatID int64 + MessageID int64 + Thumbnail bool + TelegramMediaID int64 } func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) { @@ -39,6 +41,7 @@ func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) { } else { mediaID = append(mediaID, 0x00) } + mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.TelegramMediaID)) // Telegram Message ID return mediaID, nil } @@ -51,7 +54,10 @@ func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err err = fmt.Errorf("invalid version %d", mediaID[0]) return } - if len(mediaID) != 18 && len(mediaID) != 19 { + + // For compatibility with old media IDs that don't have the thumbnail flag + // and the Telegram media ID, we allow media IDs with 18, 19, or 27 bytes. + if len(mediaID) != 18 && len(mediaID) != 19 && len(mediaID) != 27 { err = fmt.Errorf("invalid media ID") return } @@ -61,8 +67,11 @@ func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err } info.ChatID = int64(binary.BigEndian.Uint64(mediaID[2:])) info.MessageID = int64(binary.BigEndian.Uint64(mediaID[10:])) - if len(mediaID) == 19 { + if len(mediaID) >= 19 { info.Thumbnail = mediaID[18] == 1 } + if len(mediaID) >= 20 { + info.TelegramMediaID = int64(binary.BigEndian.Uint64(mediaID[19:])) + } return } diff --git a/pkg/connector/matrix.go b/pkg/connector/matrix.go index 742bad6d..93508c4a 100644 --- a/pkg/connector/matrix.go +++ b/pkg/connector/matrix.go @@ -2,6 +2,7 @@ package connector import ( "context" + "crypto/sha256" "fmt" "strconv" "strings" @@ -21,7 +22,6 @@ import ( "go.mau.fi/mautrix-telegram/pkg/connector/emojis" "go.mau.fi/mautrix-telegram/pkg/connector/ids" - "go.mau.fi/mautrix-telegram/pkg/connector/msgconv" "go.mau.fi/mautrix-telegram/pkg/connector/waveform" ) @@ -105,8 +105,8 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. updates, err = builder.Media(ctx, media) } case event.MsgLocation: - var uri msgconv.GeoURI - uri, err = msgconv.ParseGeoURI(msg.Content.GeoURI) + var uri GeoURI + uri, err = ParseGeoURI(msg.Content.GeoURI) if err != nil { return nil, err } @@ -131,6 +131,9 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. return nil, err } + hasher := sha256.New() + hasher.Write([]byte(msg.Content.Body)) + var tgMessageID, tgDate int switch sentMessage := updates.(type) { case *tg.UpdateShortSentMessage: @@ -139,9 +142,12 @@ func (t *TelegramClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2. case *tg.Updates: tgDate = sentMessage.Date for _, u := range sentMessage.Updates { - if update, ok := u.(*tg.UpdateMessageID); ok { + switch update := u.(type) { + case *tg.UpdateMessageID: tgMessageID = update.ID - break + case *tg.UpdateNewMessage: + msg := update.Message.(*tg.Message) + hasher.Write(mediaHashID(msg.Media)) } } if tgMessageID == 0 { @@ -158,6 +164,7 @@ 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)}, }, } return diff --git a/pkg/connector/media/transfer.go b/pkg/connector/media/transfer.go index 7de07ad1..c827e906 100644 --- a/pkg/connector/media/transfer.go +++ b/pkg/connector/media/transfer.go @@ -326,16 +326,17 @@ func (t *ReadyTransferer) Download(ctx context.Context) ([]byte, *event.FileInfo } // DirectDownloadURL returns the direct download URL for the media. -func (t *ReadyTransferer) DirectDownloadURL(ctx context.Context, portal *bridgev2.Portal, msgID int, thumbnail bool) (id.ContentURIString, *event.FileInfo, error) { +func (t *ReadyTransferer) DirectDownloadURL(ctx context.Context, portal *bridgev2.Portal, msgID int, thumbnail bool, telegramMediaID int64) (id.ContentURIString, *event.FileInfo, error) { peerType, chatID, err := ids.ParsePortalID(portal.ID) if err != nil { return "", nil, err } mediaID, err := ids.DirectMediaInfo{ - PeerType: peerType, - ChatID: chatID, - MessageID: int64(msgID), - Thumbnail: thumbnail, + PeerType: peerType, + ChatID: chatID, + MessageID: int64(msgID), + Thumbnail: thumbnail, + TelegramMediaID: telegramMediaID, }.AsMediaID() if err != nil { return "", nil, err diff --git a/pkg/connector/msgconv/converter.go b/pkg/connector/msgconv/converter.go deleted file mode 100644 index 940a6833..00000000 --- a/pkg/connector/msgconv/converter.go +++ /dev/null @@ -1,28 +0,0 @@ -package msgconv - -import ( - "github.com/gotd/td/telegram" - "maunium.net/go/mautrix/bridgev2" - - "go.mau.fi/mautrix-telegram/pkg/connector/media" - "go.mau.fi/mautrix-telegram/pkg/connector/store" -) - -type MessageConverter struct { - client *telegram.Client - connector bridgev2.MatrixConnector - store *store.Container - animatedStickerConfig media.AnimatedStickerConfig - - useDirectMedia bool -} - -func NewMessageConverter(client *telegram.Client, connector bridgev2.MatrixConnector, store *store.Container, animatedStickerConfig media.AnimatedStickerConfig, useDirectMedia bool) *MessageConverter { - return &MessageConverter{ - client: client, - connector: connector, - store: store, - animatedStickerConfig: animatedStickerConfig, - useDirectMedia: useDirectMedia, - } -} diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 55c045f9..f8fb1c95 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -1,6 +1,7 @@ package connector import ( + "bytes" "context" "fmt" "slices" @@ -58,7 +59,7 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, update IGetMess PortalKey: ids.MakePortalKey(msg.PeerID), Data: msg, CreatePortal: true, - ConvertMessageFunc: t.msgConv.ToMatrix, + ConvertMessageFunc: t.convertToMatrix, Timestamp: time.Unix(int64(msg.Date), 0), }) case *tg.MessageService: @@ -209,30 +210,59 @@ func (t *TelegramClient) onEntityUpdate(ctx context.Context, e tg.Entities) erro } func (t *TelegramClient) onMessageEdit(ctx context.Context, update IGetMessage) error { - // fmt.Printf("message edit %+v\n", update) msg, ok := update.GetMessage().(*tg.Message) if !ok { return fmt.Errorf("edit message is not *tg.Message") } + // TODO do this async if err := t.handleTelegramReactions(ctx, msg); err != nil { return err } - // t.main.Bridge.QueueRemoteEvent(t.userLogin, &bridgev2.SimpleRemoteEvent[*tg.Message]{ - // Type: bridgev2.RemoteEventEdit, - // LogContext: func(c zerolog.Context) zerolog.Context { - // return c. - // Str("action", "edit message"). - // Int("message_id", message.ID) - // }, - // Sender: sender, - // PortalKey: ids.MakePortalID(message.PeerID), - // }) + sender := t.getEventSender(msg) + + t.main.Bridge.QueueRemoteEvent(t.userLogin, &bridgev2.SimpleRemoteEvent[*tg.Message]{ + Type: bridgev2.RemoteEventEdit, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Str("action", "edit_message"). + Str("conversion_direction", "to_matrix"). + Int("message_id", msg.ID) + }, + ID: ids.MakeMessageID(msg.ID), + Sender: sender, + PortalKey: ids.MakePortalKey(msg.PeerID), + TargetMessage: ids.MakeMessageID(msg.ID), + Data: msg, + ConvertEditFunc: t.convertEdit, + Timestamp: time.Unix(int64(msg.EditDate), 0), + }) return nil } +func (t *TelegramClient) convertEdit(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, existing []*database.Message, msg *tg.Message) (*bridgev2.ConvertedEdit, error) { + converted, err := t.convertToMatrix(ctx, portal, intent, msg) + if err != nil { + return nil, err + } + + if len(existing) != len(converted.Parts) { + return nil, fmt.Errorf("parts were added or removed in edit") + } + + var ce bridgev2.ConvertedEdit + for i, part := range converted.Parts { + if bytes.Equal(existing[i].Metadata.(*MessageMetadata).ContentHash, part.DBMetadata.(*MessageMetadata).ContentHash) { + continue + } + + ce.ModifiedParts = append(ce.ModifiedParts, part.ToEditPart(existing[i])) + } + return &ce, nil +} + func (t *TelegramClient) handleTelegramReactions(ctx context.Context, msg *tg.Message) error { if _, set := msg.GetReactions(); !set { // fmt.Printf("no reactions\n") diff --git a/pkg/connector/msgconv/tomatrix.go b/pkg/connector/tomatrix.go similarity index 70% rename from pkg/connector/msgconv/tomatrix.go rename to pkg/connector/tomatrix.go index 40a7b327..b5c1009d 100644 --- a/pkg/connector/msgconv/tomatrix.go +++ b/pkg/connector/tomatrix.go @@ -1,7 +1,9 @@ -package msgconv +package connector import ( "context" + "crypto/sha256" + "encoding/binary" "fmt" "html" "strings" @@ -30,15 +32,81 @@ type ttlable interface { GetTTLSeconds() (value int, ok bool) } -func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *tg.Message) (*bridgev2.ConvertedMessage, error) { +func mediaHashID(media tg.MessageMediaClass) []byte { + switch media := media.(type) { + case *tg.MessageMediaPhoto: + return binary.BigEndian.AppendUint64(nil, uint64(media.Photo.GetID())) + case *tg.MessageMediaDocument: + return binary.BigEndian.AppendUint64(nil, uint64(media.Document.GetID())) + } + return nil +} + +func (c *TelegramClient) mediaToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *tg.Message) (*bridgev2.ConvertedMessagePart, *database.DisappearingSetting, []byte, error) { + media, ok := msg.GetMedia() + if !ok { + return nil, nil, nil, nil + } + + switch media.TypeID() { + case tg.MessageMediaWebPageTypeID: + // Already handled in the message handling + return nil, nil, nil, nil + case tg.MessageMediaUnsupportedTypeID: + return &bridgev2.ConvertedMessagePart{ + ID: networkid.PartID("unsupported_media"), + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: "This message is not supported on your version of Mautrix-Telegram. Please check https://github.com/mautrix/telegram or ask your bridge administrator about possible updates.", + }, + Extra: map[string]any{ + "fi.mau.telegram.unsupported": true, + }, + }, nil, nil, nil + case tg.MessageMediaPhotoTypeID, tg.MessageMediaDocumentTypeID: + converted, disappearingSetting, err := c.convertMediaRequiringUpload(ctx, portal, intent, msg.ID, media) + return converted, disappearingSetting, mediaHashID(media), err + case tg.MessageMediaContactTypeID: + return c.convertContact(media), nil, nil, nil + case tg.MessageMediaGeoTypeID, tg.MessageMediaGeoLiveTypeID, tg.MessageMediaVenueTypeID: + location, err := convertLocation(media) + return location, nil, nil, err + case tg.MessageMediaPollTypeID: + return convertPoll(media), nil, nil, nil + case tg.MessageMediaDiceTypeID: + return convertDice(media), nil, nil, nil + case tg.MessageMediaGameTypeID: + return convertGame(media), nil, nil, nil + case tg.MessageMediaStoryTypeID, tg.MessageMediaInvoiceTypeID, tg.MessageMediaGiveawayTypeID, tg.MessageMediaGiveawayResultsTypeID: + // TODO: support these properly + return &bridgev2.ConvertedMessagePart{ + ID: networkid.PartID("story"), + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: fmt.Sprintf("%s are not yet supported. Open Telegram to view.", media.TypeName()), + }, + Extra: map[string]any{ + "fi.mau.telegram.unsupported": true, + "fi.mau.telegram.type_id": media.TypeID(), + }, + }, nil, nil, nil + default: + return nil, nil, nil, fmt.Errorf("unsupported media type %T", media) + } +} + +func (c *TelegramClient) convertToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *tg.Message) (*bridgev2.ConvertedMessage, error) { log := zerolog.Ctx(ctx).With().Str("conversion_direction", "to_matrix").Logger() ctx = log.WithContext(ctx) cm := &bridgev2.ConvertedMessage{} + hasher := sha256.New() if len(msg.Message) > 0 { var linkPreviews []*event.BeeperLinkPreview if media, ok := msg.GetMedia(); ok && media.TypeID() == tg.MessageMediaWebPageTypeID { - preview, err := mc.webpageToBeeperLinkPreview(ctx, intent, media) + preview, err := c.webpageToBeeperLinkPreview(ctx, intent, media) if err != nil { return nil, err } else if preview != nil { @@ -46,81 +114,40 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta } } + hasher.Write([]byte(msg.Message)) + // TODO formatting - // TODO combine with other media - cm.Parts = append(cm.Parts, &bridgev2.ConvertedMessagePart{ - ID: networkid.PartID("caption"), - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgText, - Body: msg.Message, - BeeperLinkPreviews: linkPreviews, + cm.Parts = []*bridgev2.ConvertedMessagePart{ + { + ID: networkid.PartID("caption"), + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgText, + Body: msg.Message, + BeeperLinkPreviews: linkPreviews, + }, }, - }) - } - - if media, ok := msg.GetMedia(); ok { - switch media.TypeID() { - case tg.MessageMediaWebPageTypeID: - // Already handled above - case tg.MessageMediaUnsupportedTypeID: - cm.Parts = append(cm.Parts, &bridgev2.ConvertedMessagePart{ - ID: networkid.PartID("unsupported_media"), - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: "This message is not supported on your version of Mautrix-Telegram. Please check https://github.com/mautrix/telegram or ask your bridge administrator about possible updates.", - }, - Extra: map[string]any{ - "fi.mau.telegram.unsupported": true, - }, - }) - case tg.MessageMediaPhotoTypeID, tg.MessageMediaDocumentTypeID: - mediaPart, disappearingSetting, err := mc.convertMediaRequiringUpload(ctx, portal, intent, msg.ID, media) - if err != nil { - return nil, err - } - if disappearingSetting != nil { - cm.Disappear = *disappearingSetting - } - cm.Parts = append(cm.Parts, mediaPart) - case tg.MessageMediaContactTypeID: - cm.Parts = append(cm.Parts, mc.convertContact(media)) - case tg.MessageMediaGeoTypeID, tg.MessageMediaGeoLiveTypeID, tg.MessageMediaVenueTypeID: - location, err := mc.convertLocation(media) - if err != nil { - return nil, err - } - cm.Parts = append(cm.Parts, location) - case tg.MessageMediaPollTypeID: - cm.Parts = append(cm.Parts, mc.convertPoll(media)) - case tg.MessageMediaDiceTypeID: - cm.Parts = append(cm.Parts, mc.convertDice(media)) - case tg.MessageMediaGameTypeID: - cm.Parts = append(cm.Parts, mc.convertGame(media)) - - case tg.MessageMediaStoryTypeID, tg.MessageMediaInvoiceTypeID, tg.MessageMediaGiveawayTypeID, tg.MessageMediaGiveawayResultsTypeID: - // TODO: support these properly - cm.Parts = append(cm.Parts, &bridgev2.ConvertedMessagePart{ - ID: networkid.PartID("story"), - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: fmt.Sprintf("%s are not yet supported. Open Telegram to view.", media.TypeName()), - }, - Extra: map[string]any{ - "fi.mau.telegram.unsupported": true, - "fi.mau.telegram.type_id": media.TypeID(), - }, - }) - default: - return nil, fmt.Errorf("unsupported media type %T", media) } } + + mediaPart, disappearingSetting, mediaHashID, err := c.mediaToMatrix(ctx, portal, intent, msg) + if err != nil { + return nil, err + } else if mediaPart != nil { + hasher.Write(mediaHashID) + cm.Parts = append(cm.Parts, mediaPart) + cm.MergeCaption() + + if disappearingSetting != nil { + cm.Disappear = *disappearingSetting + } + } + cm.Parts[0].DBMetadata = &MessageMetadata{ContentHash: hasher.Sum(nil)} + return cm, nil } -func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, intent bridgev2.MatrixAPI, msgMedia tg.MessageMediaClass) (preview *event.BeeperLinkPreview, err error) { +func (c *TelegramClient) webpageToBeeperLinkPreview(ctx context.Context, intent bridgev2.MatrixAPI, msgMedia tg.MessageMediaClass) (preview *event.BeeperLinkPreview, err error) { webpage, ok := msgMedia.(*tg.MessageMediaWebPage).Webpage.(*tg.WebPage) if !ok { return nil, nil @@ -136,9 +163,9 @@ func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, inte if pc, ok := webpage.GetPhoto(); ok && pc.TypeID() == tg.PhotoTypeID { var fileInfo *event.FileInfo - preview.ImageURL, preview.ImageEncryption, fileInfo, err = media.NewTransferer(mc.client.API()). + preview.ImageURL, preview.ImageEncryption, fileInfo, err = media.NewTransferer(c.client.API()). WithPhoto(pc). - Transfer(ctx, mc.store, intent) + Transfer(ctx, c.main.Store, intent) if err != nil { return nil, err } @@ -148,7 +175,7 @@ func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, inte return preview, nil } -func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msgID int, msgMedia tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, *database.DisappearingSetting, error) { +func (c *TelegramClient) convertMediaRequiringUpload(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msgID int, msgMedia tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, *database.DisappearingSetting, error) { log := zerolog.Ctx(ctx).With(). Str("conversion_direction", "to_matrix"). Str("portal_id", string(portal.ID)). @@ -157,10 +184,11 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por eventType := event.EventMessage var partID networkid.PartID var content event.MessageEventContent + var telegramMediaID int64 var isSticker, isAnimatedSticker bool extra := map[string]any{} - transferer := media.NewTransferer(mc.client.API()).WithRoomID(portal.MXID) + transferer := media.NewTransferer(c.client.API()).WithRoomID(portal.MXID) var mediaTransferer *media.ReadyTransferer // Determine the filename and some other information @@ -169,12 +197,14 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por partID = networkid.PartID("photo") content.MsgType = event.MsgImage content.Body = "image" + telegramMediaID = msgMedia.Photo.GetID() mediaTransferer = transferer.WithPhoto(msgMedia.Photo) case *tg.MessageMediaDocument: document, ok := msgMedia.Document.(*tg.Document) if !ok { return nil, nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document) } + telegramMediaID = document.GetID() partID = networkid.PartID("document") content.MsgType = event.MsgFile @@ -208,7 +238,7 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por transferer = transferer.WithImageSize(a) case *tg.DocumentAttributeSticker: isSticker = true - if mc.animatedStickerConfig.Target == "webm" { + if c.main.Config.AnimatedSticker.Target == "webm" { content.MsgType = event.MsgVideo } else { eventType = event.EventSticker @@ -238,13 +268,13 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por extraInfo["fi.mau.autoplay"] = true extraInfo["fi.mau.hide_controls"] = true extraInfo["fi.mau.no_audio"] = true - transferer = transferer.WithStickerConfig(mc.animatedStickerConfig) + transferer = transferer.WithStickerConfig(c.main.Config.AnimatedSticker) case *tg.DocumentAttributeAnimated: isAnimatedSticker = true } } - if isAnimatedSticker || (isSticker && mc.animatedStickerConfig.Target == "webm") { + if isAnimatedSticker || (isSticker && c.main.Config.AnimatedSticker.Target == "webm") { if isAnimatedSticker { extraInfo["fi.mau.telegram.gif"] = true } else { @@ -259,17 +289,17 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por var thumbnailInfo *event.FileInfo var err error - thumbnailTransferer := media.NewTransferer(mc.client.API()). + thumbnailTransferer := media.NewTransferer(c.client.API()). WithRoomID(portal.MXID). WithDocument(document, true) - if mc.useDirectMedia { - thumbnailURL, thumbnailInfo, err = thumbnailTransferer.DirectDownloadURL(ctx, portal, msgID, true) + if c.main.useDirectMedia { + thumbnailURL, thumbnailInfo, err = thumbnailTransferer.DirectDownloadURL(ctx, portal, msgID, true, document.ID) if err != nil { log.Err(err).Msg("error getting direct download URL for thumbnail") } } if thumbnailURL == "" { - thumbnailURL, thumbnailFile, thumbnailInfo, err = thumbnailTransferer.Transfer(ctx, mc.store, intent) + thumbnailURL, thumbnailFile, thumbnailInfo, err = thumbnailTransferer.Transfer(ctx, c.main.Store, intent) if err != nil { return nil, nil, fmt.Errorf("error transferring thumbnail: %w", err) } @@ -286,14 +316,14 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por } var err error - if mc.useDirectMedia && (!isSticker || mc.animatedStickerConfig.Target == "disable") { - content.URL, content.Info, err = mediaTransferer.DirectDownloadURL(ctx, portal, msgID, false) + if c.main.useDirectMedia && (!isSticker || c.main.Config.AnimatedSticker.Target == "disable") { + content.URL, content.Info, err = mediaTransferer.DirectDownloadURL(ctx, portal, msgID, false, telegramMediaID) if err != nil { log.Err(err).Msg("error getting direct download URL for media") } } if content.URL == "" { - content.URL, content.File, content.Info, err = mediaTransferer.Transfer(ctx, mc.store, intent) + content.URL, content.File, content.Info, err = mediaTransferer.Transfer(ctx, c.main.Store, intent) if err != nil { return nil, nil, fmt.Errorf("error transferring media: %w", err) } @@ -337,7 +367,7 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por }, disappearingSetting, nil } -func (mc *MessageConverter) convertContact(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { +func (c *TelegramClient) convertContact(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { contact := media.(*tg.MessageMediaContact) name := util.FormatFullName(contact.FirstName, contact.LastName) formattedPhone := fmt.Sprintf("+%s", strings.TrimPrefix(contact.PhoneNumber, "+")) @@ -350,7 +380,7 @@ func (mc *MessageConverter) convertContact(media tg.MessageMediaClass) *bridgev2 content.Format = event.FormatHTML content.FormattedBody = fmt.Sprintf( `Shared contact info for %s: %s`, - mc.connector.GhostIntent(ids.MakeUserID(contact.UserID)).GetMXID(), + c.main.Bridge.Matrix.GhostIntent(ids.MakeUserID(contact.UserID)).GetMXID(), html.EscapeString(name), html.EscapeString(formattedPhone), ) @@ -376,7 +406,7 @@ type hasGeo interface { GetGeo() tg.GeoPointClass } -func (mc *MessageConverter) convertLocation(media tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, error) { +func convertLocation(media tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, error) { g, ok := media.(hasGeo) if !ok || g.GetGeo().TypeID() != tg.GeoPointTypeID { return nil, fmt.Errorf("location didn't have geo or geo is wrong type") @@ -395,7 +425,7 @@ func (mc *MessageConverter) convertLocation(media tg.MessageMediaClass) (*bridge } geo := fmt.Sprintf("%f,%f", point.Lat, point.Long) - geoURI := GeoURIFromLatLong(point.Lat, point.Long) + geoURI := GeoURIFromLatLong(point.Lat, point.Long).URI() body := fmt.Sprintf("%.4f° %s, %.4f° %s", point.Lat, latChar, point.Long, longChar) url := fmt.Sprintf("https://maps.google.com/?q=%s", geo) @@ -412,7 +442,7 @@ func (mc *MessageConverter) convertLocation(media tg.MessageMediaClass) (*bridge } extra["org.matrix.msc3488.location"] = map[string]any{ - "uri": geoURI.URI(), + "uri": geoURI, "description": note, } @@ -421,7 +451,7 @@ func (mc *MessageConverter) convertLocation(media tg.MessageMediaClass) (*bridge Type: event.EventMessage, Content: &event.MessageEventContent{ MsgType: event.MsgLocation, - GeoURI: geoURI.URI(), + GeoURI: geoURI, Body: fmt.Sprintf("%s: %s\n%s", note, body, url), Format: event.FormatHTML, FormattedBody: fmt.Sprintf(`%s: %s`, note, url, body), @@ -430,7 +460,7 @@ func (mc *MessageConverter) convertLocation(media tg.MessageMediaClass) (*bridge }, nil } -func (mc *MessageConverter) convertPoll(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { +func convertPoll(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { // TODO (PLAT-25224) make this richer in the future once megabridge has support for polls poll := media.(*tg.MessageMediaPoll) @@ -453,7 +483,7 @@ func (mc *MessageConverter) convertPoll(media tg.MessageMediaClass) *bridgev2.Co } } -func (mc *MessageConverter) convertDice(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { +func convertDice(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { roll := media.(*tg.MessageMediaDice) var result string @@ -524,7 +554,7 @@ func (mc *MessageConverter) convertDice(media tg.MessageMediaClass) *bridgev2.Co } } -func (mc *MessageConverter) convertGame(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { +func convertGame(media tg.MessageMediaClass) *bridgev2.ConvertedMessagePart { // TODO (PLAT-25562) provide a richer experience for the game game := media.(*tg.MessageMediaGame) return &bridgev2.ConvertedMessagePart{