From 891750592d0cd96994468ed1613c243cb5af3bef Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Thu, 20 Jun 2024 11:49:16 -0600 Subject: [PATCH] converter: handle link previews TG -> Matrix Signed-off-by: Sumner Evans --- pkg/connector/directdownload.go | 3 +- pkg/connector/download/photo.go | 27 ++++---- pkg/connector/msgconv/tomatrix.go | 101 +++++++++++++++++++++++++----- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/pkg/connector/directdownload.go b/pkg/connector/directdownload.go index 4727882f..d2b15859 100644 --- a/pkg/connector/directdownload.go +++ b/pkg/connector/directdownload.go @@ -84,7 +84,7 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med var mimeType string switch media := media.(type) { case *tg.MessageMediaPhoto: - data, mimeType, err = download.DownloadPhoto(ctx, client.client.API(), media) + data, mimeType, err = download.DownloadPhotoMedia(ctx, client.client.API(), media) case *tg.MessageMediaDocument: document, ok := media.Document.(*tg.Document) if !ok { @@ -98,7 +98,6 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med // case *tg.MessageMediaContact: // messageMediaContact#70322949 // case *tg.MessageMediaUnsupported: // messageMediaUnsupported#9f84f49e // case *tg.MessageMediaDocument: // messageMediaDocument#4cf4d72d - // case *tg.MessageMediaWebPage: // messageMediaWebPage#ddf10c3b // case *tg.MessageMediaVenue: // messageMediaVenue#2ec0533f // case *tg.MessageMediaGame: // messageMediaGame#fdb19008 // case *tg.MessageMediaInvoice: // messageMediaInvoice#f6a548d3 diff --git a/pkg/connector/download/photo.go b/pkg/connector/download/photo.go index ab277d72..775a7c3d 100644 --- a/pkg/connector/download/photo.go +++ b/pkg/connector/download/photo.go @@ -10,7 +10,7 @@ import ( "github.com/gotd/td/tg" ) -func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) { +func GetLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) { var maxSize int for _, s := range sizes { var currentSize int @@ -35,17 +35,8 @@ func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) return } -func DownloadPhoto(ctx context.Context, client downloader.Client, media *tg.MessageMediaPhoto) (data []byte, mimeType string, err error) { - p, ok := media.GetPhoto() - if !ok { - return nil, "", fmt.Errorf("photo message sent without a photo") - } - photo, ok := p.(*tg.Photo) - if !ok { - return nil, "", fmt.Errorf("unrecognized photo type %T", p) - } - - largest := getLargestPhotoSize(photo.GetSizes()) +func DownloadPhoto(ctx context.Context, client downloader.Client, photo *tg.Photo) (data []byte, mimeType string, err error) { + largest := GetLargestPhotoSize(photo.GetSizes()) file := tg.InputPhotoFileLocation{ ID: photo.GetID(), AccessHash: photo.GetAccessHash(), @@ -81,3 +72,15 @@ func DownloadPhoto(ctx context.Context, client downloader.Client, media *tg.Mess } return buf.Bytes(), mimeType, nil } + +func DownloadPhotoMedia(ctx context.Context, client downloader.Client, media *tg.MessageMediaPhoto) (data []byte, mimeType string, err error) { + p, ok := media.GetPhoto() + if !ok { + return nil, "", fmt.Errorf("photo message sent without a photo") + } + photo, ok := p.(*tg.Photo) + if !ok { + return nil, "", fmt.Errorf("unrecognized photo type %T", p) + } + return DownloadPhoto(ctx, client, photo) +} diff --git a/pkg/connector/msgconv/tomatrix.go b/pkg/connector/msgconv/tomatrix.go index edff2a47..8b599867 100644 --- a/pkg/connector/msgconv/tomatrix.go +++ b/pkg/connector/msgconv/tomatrix.go @@ -3,6 +3,7 @@ package msgconv import ( "context" "fmt" + "slices" "time" "github.com/gotd/td/tg" @@ -26,33 +27,105 @@ type ttlable interface { GetTTLSeconds() (value int, ok bool) } +func mediaRequiringUpload(media tg.MessageMediaClass) bool { + allowed := []uint32{ + tg.MessageMediaPhotoTypeID, + tg.MessageMediaGeoTypeID, + tg.MessageMediaContactTypeID, + tg.MessageMediaDocumentTypeID, + tg.MessageMediaStoryTypeID, + } + return slices.Contains(allowed, media.TypeID()) +} + func (mc *MessageConverter) ToMatrix(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{} - if msg.Message != "" { + 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) + if err != nil { + return nil, err + } else if preview != nil { + linkPreviews = append(linkPreviews, preview) + } + } + + // TODO formatting cm.Parts = append(cm.Parts, &bridgev2.ConvertedMessagePart{ - ID: networkid.PartID("caption"), - Type: event.EventMessage, - Content: &event.MessageEventContent{MsgType: event.MsgText, Body: msg.Message}, + ID: networkid.PartID("caption"), + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgText, + Body: msg.Message, + BeeperLinkPreviews: linkPreviews, + }, }) } if media, ok := msg.GetMedia(); ok { - mediaParts, disappearingSetting, err := mc.convertMedia(ctx, portal, intent, msg.ID, media) - if err != nil { - return nil, err + switch { + case mediaRequiringUpload(media): + mediaParts, 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, mediaParts) } - if disappearingSetting != nil { - cm.Disappear = *disappearingSetting - } - cm.Parts = append(cm.Parts, mediaParts) } return cm, nil } -func (mc *MessageConverter) convertMedia(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msgID int, media tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, *database.DisappearingSetting, error) { +func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, intent bridgev2.MatrixAPI, media tg.MessageMediaClass) (*event.BeeperLinkPreview, error) { + webpage, ok := media.(*tg.MessageMediaWebPage).Webpage.(*tg.WebPage) + if !ok { + return nil, nil + } + preview := &event.BeeperLinkPreview{ + MatchedURL: webpage.URL, + LinkPreview: event.LinkPreview{ + Title: webpage.Title, + CanonicalURL: webpage.URL, + Description: webpage.Description, + }, + } + + if pc, ok := webpage.GetPhoto(); ok && pc.TypeID() == tg.PhotoTypeID { + photo := pc.(*tg.Photo) + for _, s := range photo.GetSizes() { + switch size := s.(type) { + case *tg.PhotoCachedSize: + preview.ImageWidth = size.GetW() + preview.ImageHeight = size.GetH() + case *tg.PhotoSizeProgressive: + preview.ImageWidth = size.GetW() + preview.ImageHeight = size.GetH() + } + } + + data, mimeType, err := download.DownloadPhoto(ctx, mc.client.API(), photo) + if err != nil { + return nil, err + } + preview.ImageSize = len(data) + preview.ImageType = mimeType + + preview.ImageURL, preview.ImageEncryption, err = intent.UploadMedia(ctx, "", data, "", mimeType) + if err != nil { + return nil, err + } + } + + return preview, nil +} + +func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msgID int, media tg.MessageMediaClass) (*bridgev2.ConvertedMessagePart, *database.DisappearingSetting, error) { var partID networkid.PartID var msgType event.MessageType var filename string @@ -97,7 +170,6 @@ func (mc *MessageConverter) convertMedia(ctx context.Context, portal *bridgev2.P // case *tg.MessageMediaGeo: // messageMediaGeo#56e0d474 // case *tg.MessageMediaContact: // messageMediaContact#70322949 // case *tg.MessageMediaUnsupported: // messageMediaUnsupported#9f84f49e - // case *tg.MessageMediaWebPage: // messageMediaWebPage#ddf10c3b // case *tg.MessageMediaVenue: // messageMediaVenue#2ec0533f // case *tg.MessageMediaGame: // messageMediaGame#fdb19008 // case *tg.MessageMediaInvoice: // messageMediaInvoice#f6a548d3 @@ -146,7 +218,7 @@ func (mc *MessageConverter) convertMedia(ctx context.Context, portal *bridgev2.P filename = "image" + exmime.ExtensionFromMimetype(mimeType) } - data, mimeType, err = download.DownloadPhoto(ctx, mc.client.API(), media) + data, mimeType, err = download.DownloadPhotoMedia(ctx, mc.client.API(), media) case *tg.MessageMediaDocument: document, ok := media.Document.(*tg.Document) if !ok { @@ -160,7 +232,6 @@ func (mc *MessageConverter) convertMedia(ctx context.Context, portal *bridgev2.P // case *tg.MessageMediaGeo: // messageMediaGeo#56e0d474 // case *tg.MessageMediaContact: // messageMediaContact#70322949 // case *tg.MessageMediaUnsupported: // messageMediaUnsupported#9f84f49e - // case *tg.MessageMediaWebPage: // messageMediaWebPage#ddf10c3b // case *tg.MessageMediaVenue: // messageMediaVenue#2ec0533f // case *tg.MessageMediaGame: // messageMediaGame#fdb19008 // case *tg.MessageMediaInvoice: // messageMediaInvoice#f6a548d3