diff --git a/pkg/connector/client.go b/pkg/connector/client.go index cfef7ed5..4b97bb44 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -17,6 +17,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" + "go.mau.fi/mautrix-telegram/pkg/connector/ids" "go.mau.fi/mautrix-telegram/pkg/connector/msgconv" ) @@ -71,7 +72,7 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge Logger: zaplog, UpdateHandler: updatesManager, }) - client.msgConv = msgconv.NewMessageConverter(client.client) + client.msgConv = msgconv.NewMessageConverter(client.client, tc.useDirectMedia) client.clientCancel, err = connectTelegramClient(ctx, client.client) go func() { err = updatesManager.Run(ctx, client.client.API(), loginID, updates.AuthOptions{}) @@ -127,21 +128,21 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, e tg.Entities, var sender bridgev2.EventSender if msg.Out { sender.IsFromMe = true - sender.SenderLogin = makeUserLoginID(t.loginID) - sender.Sender = makeUserID(t.loginID) + sender.SenderLogin = ids.MakeUserLoginID(t.loginID) + sender.Sender = ids.MakeUserID(t.loginID) } else if msg.FromID != nil { switch from := msg.FromID.(type) { case *tg.PeerUser: - sender.SenderLogin = makeUserLoginID(from.UserID) - sender.Sender = makeUserID(from.UserID) + sender.SenderLogin = ids.MakeUserLoginID(from.UserID) + sender.Sender = ids.MakeUserID(from.UserID) default: fmt.Printf("%+v\n", msg.FromID) fmt.Printf("%T\n", msg.FromID) panic("unimplemented FromID") } } else if peer, ok := msg.PeerID.(*tg.PeerUser); ok { - sender.SenderLogin = makeUserLoginID(peer.UserID) - sender.Sender = makeUserID(peer.UserID) + sender.SenderLogin = ids.MakeUserLoginID(peer.UserID) + sender.Sender = ids.MakeUserID(peer.UserID) } else { panic("not from anyone") } @@ -155,9 +156,9 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, e tg.Entities, Str("sender_login", string(sender.SenderLogin)). Bool("is_from_me", sender.IsFromMe) }, - ID: makeMessageID(msg.ID), + ID: ids.MakeMessageID(msg.ID), Sender: sender, - PortalKey: makePortalID(msg.PeerID), + PortalKey: ids.MakePortalID(msg.PeerID), Data: msg, CreatePortal: true, ConvertMessageFunc: t.msgConv.ToMatrix, @@ -186,7 +187,7 @@ func getFullName(user *tg.User) string { func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.PortalInfo, error) { fmt.Printf("%+v\n", portal) - peerType, id, err := parsePortalID(portal.ID) + peerType, id, err := ids.ParsePortalID(portal.ID) if err != nil { return nil, err } @@ -195,7 +196,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta var isSpace, isDM bool switch peerType { - case peerTypeUser: + case ids.PeerTypeUser: users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{UserID: id}}) if err != nil { return nil, err @@ -207,10 +208,10 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta return nil, fmt.Errorf("returned user is not *tg.User") } else { name = getFullName(user) // TODO gate this behind a config? - members = []networkid.UserID{makeUserID(id), makeUserID(t.loginID)} + members = []networkid.UserID{ids.MakeUserID(id), ids.MakeUserID(t.loginID)} isDM = true } - case peerTypeChat: + case ids.PeerTypeChat: // TODO get name of chat chat, err := t.client.API().MessagesGetFullChat(ctx, id) if err != nil { @@ -220,7 +221,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta return nil, fmt.Errorf("no users found in chat %d", id) } for _, user := range chat.Users { - members = append(members, makeUserID(user.GetID())) + members = append(members, ids.MakeUserID(user.GetID())) } default: fmt.Printf("%s %d\n", peerType, id) @@ -240,7 +241,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) { - id, err := parseUserID(ghost.ID) + id, err := ids.ParseUserID(ghost.ID) if err != nil { return nil, err } diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index e6f03b5a..e98bb865 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -37,7 +37,8 @@ type TelegramConnector struct { Bridge *bridgev2.Bridge Config *TelegramConfig - store *store.Container + store *store.Container + useDirectMedia bool } var _ bridgev2.NetworkConnector = (*TelegramConnector)(nil) diff --git a/pkg/connector/directdownload.go b/pkg/connector/directdownload.go new file mode 100644 index 00000000..2fec06d3 --- /dev/null +++ b/pkg/connector/directdownload.go @@ -0,0 +1,105 @@ +package connector + +import ( + "bytes" + "context" + "fmt" + "io" + + "github.com/gotd/td/tg" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/mediaproxy" + + "go.mau.fi/mautrix-telegram/pkg/connector/ids" + conmedia "go.mau.fi/mautrix-telegram/pkg/connector/media" +) + +var _ bridgev2.DirectMediableNetwork = (*TelegramConnector)(nil) + +type getMessages interface { + GetMessages() []tg.MessageClass +} + +func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.MediaID) (mediaproxy.GetMediaResponse, error) { + info, err := ids.ParseDirectMediaInfo(mediaID) + if err != nil { + return nil, err + } + + logins, err := tc.Bridge.GetUserLoginsInPortal(ctx, info.PeerType.AsPortalKey(info.ChatID)) + if err != nil { + return nil, err + } else if len(logins) == 0 { + return nil, fmt.Errorf("no user logins in the portal (%s %d)", info.PeerType, info.ChatID) + } + + client := logins[0].Client.(*TelegramClient) + var messages tg.MessagesMessagesClass + switch info.PeerType { + case ids.PeerTypeUser, ids.PeerTypeChat: + messages, err = client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{ + &tg.InputMessageID{ID: int(info.MessageID)}, + }) + case ids.PeerTypeChannel: + messages, err = client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{ + Channel: &tg.InputChannel{ChannelID: info.ChatID}, + ID: []tg.InputMessageClass{ + &tg.InputMessageID{ID: int(info.MessageID)}, + }, + }) + default: + return nil, fmt.Errorf("unknown peer type %s", info.PeerType) + } + if err != nil { + return nil, err + } + + var media tg.MessageMediaClass + if m, ok := messages.(getMessages); !ok { + return nil, fmt.Errorf("unknown message type") + } else { + for _, message := range m.GetMessages() { + if msg, ok := message.(*tg.Message); ok && msg.ID == int(info.MessageID) { + media = msg.Media + break + } + } + } + + switch media := media.(type) { + case *tg.MessageMediaPhoto: + data, mimeType, err := conmedia.DownloadPhoto(ctx, client.client.API(), media) + if err != nil { + return nil, err + } + + return &mediaproxy.GetMediaResponseData{ + Reader: io.NopCloser(bytes.NewBuffer(data)), + ContentType: mimeType, + ContentLength: int64(len(data)), + }, nil + + // TODO all of these + // case *tg.MessageMediaGeo: // messageMediaGeo#56e0d474 + // 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 + // case *tg.MessageMediaGeoLive: // messageMediaGeoLive#b940c666 + // case *tg.MessageMediaPoll: // messageMediaPoll#4bd6e798 + // case *tg.MessageMediaDice: // messageMediaDice#3f7ee58b + // case *tg.MessageMediaStory: // messageMediaStory#68cb6283 + // case *tg.MessageMediaGiveaway: // messageMediaGiveaway#daad85b0 + // case *tg.MessageMediaGiveawayResults: // messageMediaGiveawayResults#c6991068 + default: + return nil, fmt.Errorf("unhandled media type %T", media) + } +} + +func (tg *TelegramConnector) SetUseDirectMedia() { + tg.useDirectMedia = true +} diff --git a/pkg/connector/ids.go b/pkg/connector/ids.go deleted file mode 100644 index 5cc8201e..00000000 --- a/pkg/connector/ids.go +++ /dev/null @@ -1,71 +0,0 @@ -package connector - -import ( - "fmt" - "strconv" - "strings" - - "github.com/gotd/td/tg" - "maunium.net/go/mautrix/bridgev2/networkid" -) - -func makeUserID(userID int64) networkid.UserID { - return networkid.UserID(strconv.FormatInt(userID, 10)) -} - -func parseUserID(userID networkid.UserID) (int64, error) { - return strconv.ParseInt(string(userID), 10, 64) -} - -func makeUserLoginID(userID int64) networkid.UserLoginID { - return networkid.UserLoginID(strconv.FormatInt(userID, 10)) -} - -func makeMessageID(messageID int) networkid.MessageID { - return networkid.MessageID(strconv.Itoa(messageID)) -} - -type peerType string - -const ( - peerTypeUser peerType = "user" - peerTypeChat peerType = "chat" - peerTypeChannel peerType = "channel" -) - -func makePortalID(peer tg.PeerClass) networkid.PortalKey { - switch v := peer.(type) { - case *tg.PeerUser: - return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeUser, v.UserID))} - case *tg.PeerChat: - return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeChat, v.ChatID))} - case *tg.PeerChannel: - return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeChannel, v.ChannelID))} - default: - panic(fmt.Errorf("unknown peer class type %T", v)) - } -} - -func parsePortalID(portalID networkid.PortalID) (pt peerType, id int64, err error) { - parts := strings.Split(string(portalID), ":") - pt = peerType(parts[0]) - id, err = strconv.ParseInt(parts[1], 10, 64) - return -} - -func inputPeerForPortalID(portalID networkid.PortalID) (tg.InputPeerClass, error) { - peerType, id, err := parsePortalID(portalID) - if err != nil { - return nil, err - } - switch peerType { - case peerTypeUser: - return &tg.InputPeerUser{UserID: id}, nil - case peerTypeChat: - return &tg.InputPeerChat{ChatID: id}, nil - case peerTypeChannel: - return &tg.InputPeerChannel{ChannelID: id}, nil - default: - panic("invalid peer type") - } -} diff --git a/pkg/connector/ids/ids.go b/pkg/connector/ids/ids.go new file mode 100644 index 00000000..6f57f0de --- /dev/null +++ b/pkg/connector/ids/ids.go @@ -0,0 +1,101 @@ +package ids + +import ( + "fmt" + "strconv" + "strings" + + "github.com/gotd/td/tg" + "maunium.net/go/mautrix/bridgev2/networkid" +) + +func MakeUserID(userID int64) networkid.UserID { + return networkid.UserID(strconv.FormatInt(userID, 10)) +} + +func ParseUserID(userID networkid.UserID) (int64, error) { + return strconv.ParseInt(string(userID), 10, 64) +} + +func MakeUserLoginID(userID int64) networkid.UserLoginID { + return networkid.UserLoginID(strconv.FormatInt(userID, 10)) +} + +func MakeMessageID(messageID int) networkid.MessageID { + return networkid.MessageID(strconv.Itoa(messageID)) +} + +type PeerType string + +const ( + PeerTypeUser PeerType = "user" + PeerTypeChat PeerType = "chat" + PeerTypeChannel PeerType = "channel" +) + +func PeerTypeFromByte(pt byte) (PeerType, error) { + switch pt { + case 0x01: + return PeerTypeUser, nil + case 0x02: + return PeerTypeChat, nil + case 0x03: + return PeerTypeChannel, nil + default: + return "", fmt.Errorf("unknown peer type %d", pt) + } +} + +func (pt PeerType) AsByte() byte { + switch pt { + case PeerTypeUser: + return 0x01 + case PeerTypeChat: + return 0x02 + case PeerTypeChannel: + return 0x03 + default: + panic(fmt.Errorf("unknown peer type %s", pt)) + } +} + +func (pt PeerType) AsPortalKey(chatID int64) networkid.PortalKey { + return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", pt, chatID))} +} + +func MakePortalID(peer tg.PeerClass) networkid.PortalKey { + switch v := peer.(type) { + case *tg.PeerUser: + return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeUser, v.UserID))} + case *tg.PeerChat: + return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeChat, v.ChatID))} + case *tg.PeerChannel: + return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeChannel, v.ChannelID))} + default: + panic(fmt.Errorf("unknown peer class type %T", v)) + } +} + +func ParsePortalID(portalID networkid.PortalID) (pt PeerType, id int64, err error) { + parts := strings.Split(string(portalID), ":") + pt = PeerType(parts[0]) + id, err = strconv.ParseInt(parts[1], 10, 64) + return +} + +func InputPeerForPortalID(portalID networkid.PortalID) (tg.InputPeerClass, error) { + peerType, id, err := ParsePortalID(portalID) + if err != nil { + return nil, err + } + switch peerType { + case PeerTypeUser: + return &tg.InputPeerUser{UserID: id}, nil + case PeerTypeChat: + return &tg.InputPeerChat{ChatID: id}, nil + case PeerTypeChannel: + return &tg.InputPeerChannel{ChannelID: id}, nil + default: + panic("invalid peer type") + } +} diff --git a/pkg/connector/ids/media.go b/pkg/connector/ids/media.go new file mode 100644 index 00000000..70083212 --- /dev/null +++ b/pkg/connector/ids/media.go @@ -0,0 +1,59 @@ +package ids + +import ( + "encoding/binary" + "fmt" + + "maunium.net/go/mautrix/bridgev2/networkid" +) + +// DirectMediaInfo is the information that is encoded in the media ID when +// using direct media. +// +// The format of the media ID is as follows (each character represents a single +// byte, |'s added for clarity): +// +// v|p|CCCCCCCC|TTTTTTTT +// +// 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) +// TTTTTTTT (int64) = the Telegram message ID (big endian) +type DirectMediaInfo struct { + PeerType PeerType + ChatID int64 + MessageID int64 +} + +func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) { + mediaID := []byte{ + 0x00, // Version + m.PeerType.AsByte(), // Peer Type + } + mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.ChatID)) // Telegram Chat ID + mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.MessageID)) // Telegram Message ID + return mediaID, nil +} + +func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err error) { + if len(mediaID) == 0 { + err = fmt.Errorf("empty media ID") + return + } + if mediaID[0] != 0x00 { + err = fmt.Errorf("invalid version %d", mediaID[0]) + return + } + if len(mediaID) != 18 { + err = fmt.Errorf("invalid media ID") + return + } + fmt.Printf("%v\n", mediaID) + info.PeerType, err = PeerTypeFromByte(mediaID[1]) + if err != nil { + return + } + info.ChatID = int64(binary.BigEndian.Uint64(mediaID[2:])) + info.MessageID = int64(binary.BigEndian.Uint64(mediaID[10:])) + return +} diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 55a593be..57171ff2 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -30,6 +30,8 @@ import ( "go.uber.org/zap" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" + + "go.mau.fi/mautrix-telegram/pkg/connector/ids" ) const LoginFlowIDPhone = "phone" @@ -187,7 +189,7 @@ func (p *PhoneLogin) handleAuthSuccess(ctx context.Context, authorization *tg.Au } p.clientCancel() - userLoginID := makeUserLoginID(authorization.User.GetID()) + userLoginID := ids.MakeUserLoginID(authorization.User.GetID()) ul, err := p.user.NewLogin(ctx, &database.UserLogin{ ID: userLoginID, Metadata: database.UserLoginMetadata{ diff --git a/pkg/connector/media/download.go b/pkg/connector/media/download.go new file mode 100644 index 00000000..a3626aba --- /dev/null +++ b/pkg/connector/media/download.go @@ -0,0 +1,83 @@ +package media + +import ( + "bytes" + "context" + "fmt" + "net/http" + + "github.com/gotd/td/telegram/downloader" + "github.com/gotd/td/tg" +) + +func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) { + var maxSize int + for _, s := range sizes { + var currentSize int + switch size := s.(type) { + case *tg.PhotoSize: + currentSize = size.GetSize() + case *tg.PhotoCachedSize: + currentSize = max(size.GetW(), size.GetH()) + case *tg.PhotoSizeProgressive: + currentSize = max(size.GetW(), size.GetH()) + case *tg.PhotoPathSize: + currentSize = len(size.GetBytes()) + case *tg.PhotoStrippedSize: + currentSize = len(size.GetBytes()) + } + + if currentSize > maxSize { + maxSize = currentSize + largest = s + } + } + 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()) + file := tg.InputPhotoFileLocation{ + ID: photo.GetID(), + AccessHash: photo.GetAccessHash(), + FileReference: photo.GetFileReference(), + ThumbSize: largest.GetType(), + } + + // TODO convert to streaming + var buf bytes.Buffer + storageFileTypeClass, err := downloader.NewDownloader().Download(client, &file).Stream(ctx, &buf) + if err != nil { + return nil, "", err + } + switch storageFileTypeClass.(type) { + case *tg.StorageFileJpeg: + mimeType = "image/jpeg" + case *tg.StorageFileGif: + mimeType = "image/gif" + case *tg.StorageFilePng: + mimeType = "image/png" + case *tg.StorageFilePdf: + mimeType = "application/pdf" + case *tg.StorageFileMp3: + mimeType = "audio/mp3" + case *tg.StorageFileMov: + mimeType = "video/quicktime" + case *tg.StorageFileMp4: + mimeType = "video/mp4" + case *tg.StorageFileWebp: + mimeType = "image/webp" + default: + mimeType = http.DetectContentType(buf.Bytes()) + } + return buf.Bytes(), mimeType, nil +} diff --git a/pkg/connector/msgconv/converter.go b/pkg/connector/msgconv/converter.go index 50fcc3a5..c9317cb4 100644 --- a/pkg/connector/msgconv/converter.go +++ b/pkg/connector/msgconv/converter.go @@ -4,8 +4,10 @@ import "github.com/gotd/td/telegram" type MessageConverter struct { client *telegram.Client + + useDirectMedia bool } -func NewMessageConverter(client *telegram.Client) *MessageConverter { - return &MessageConverter{client: client} +func NewMessageConverter(client *telegram.Client, useDirectMedia bool) *MessageConverter { + return &MessageConverter{client: client, useDirectMedia: useDirectMedia} } diff --git a/pkg/connector/msgconv/tomatrix.go b/pkg/connector/msgconv/tomatrix.go index bf50655a..4b70e713 100644 --- a/pkg/connector/msgconv/tomatrix.go +++ b/pkg/connector/msgconv/tomatrix.go @@ -1,18 +1,19 @@ package msgconv import ( - "bytes" "context" "fmt" - "net/http" - "github.com/gotd/td/telegram/downloader" "github.com/gotd/td/tg" "github.com/rs/zerolog" "go.mau.fi/util/exmime" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + + "go.mau.fi/mautrix-telegram/pkg/connector/ids" + conmedia "go.mau.fi/mautrix-telegram/pkg/connector/media" ) func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *tg.Message) (*bridgev2.ConvertedMessage, error) { @@ -27,64 +28,50 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta Content: &event.MessageEventContent{MsgType: event.MsgText, Body: msg.Message}, }) } + if m, ok := msg.GetMedia(); ok { switch media := m.(type) { case *tg.MessageMediaPhoto: - 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()) - file := tg.InputPhotoFileLocation{ - ID: photo.GetID(), - AccessHash: photo.GetAccessHash(), - FileReference: photo.GetFileReference(), - ThumbSize: largest.GetType(), - } - - // TODO convert to streaming directly into UploadMedia - var buf bytes.Buffer - storageFileTypeClass, err := downloader.NewDownloader().Download(mc.client.API(), &file).Stream(ctx, &buf) - if err != nil { - return nil, err - } - var mimeType string - switch storageFileTypeClass.(type) { - case *tg.StorageFileJpeg: - mimeType = "image/jpeg" - case *tg.StorageFileGif: - mimeType = "image/gif" - case *tg.StorageFilePng: - mimeType = "image/png" - case *tg.StorageFilePdf: - mimeType = "application/pdf" - case *tg.StorageFileMp3: - mimeType = "audio/mp3" - case *tg.StorageFileMov: - mimeType = "video/quicktime" - case *tg.StorageFileMp4: - mimeType = "video/mp4" - case *tg.StorageFileWebp: - mimeType = "image/webp" - default: - mimeType = http.DetectContentType(buf.Bytes()) - } var filename string - if _, ok := media.GetTTLSeconds(); ok { - // TODO set the ttl on the converted message - filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType) - } else { - filename = "image" + exmime.ExtensionFromMimetype(mimeType) + var mxcURI id.ContentURIString + var encryptedFileInfo *event.EncryptedFileInfo + if mc.useDirectMedia { + var err error + filename = "image" + peerType, chatID, err := ids.ParsePortalID(portal.ID) + if err != nil { + return nil, err + } + mediaID, err := ids.DirectMediaInfo{ + PeerType: peerType, + ChatID: chatID, + MessageID: int64(msg.ID), + }.AsMediaID() + if err != nil { + return nil, err + } + mxcURI, err = portal.Bridge.Matrix.GenerateContentURI(ctx, mediaID) + if err != nil { + return nil, err + } } - mxcURI, encryptedFileInfo, err := intent.UploadMedia(ctx, "", buf.Bytes(), filename, mimeType) - if err != nil { - return nil, err + if mxcURI == "" { + data, mimeType, err := conmedia.DownloadPhoto(ctx, mc.client.API(), media) + if err != nil { + return nil, err + } + if _, ok := media.GetTTLSeconds(); ok { + // TODO set the ttl on the converted message + filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType) + } else { + filename = "image" + exmime.ExtensionFromMimetype(mimeType) + } + + mxcURI, encryptedFileInfo, err = intent.UploadMedia(ctx, "", data, filename, mimeType) + if err != nil { + return nil, err + } } extra := map[string]any{} @@ -128,28 +115,3 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta } return cm, nil } - -func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) { - var maxSize int - for _, s := range sizes { - var currentSize int - switch size := s.(type) { - case *tg.PhotoSize: - currentSize = size.GetSize() - case *tg.PhotoCachedSize: - currentSize = max(size.GetW(), size.GetH()) - case *tg.PhotoSizeProgressive: - currentSize = max(size.GetW(), size.GetH()) - case *tg.PhotoPathSize: - currentSize = len(size.GetBytes()) - case *tg.PhotoStrippedSize: - currentSize = len(size.GetBytes()) - } - - if currentSize > maxSize { - maxSize = currentSize - largest = s - } - } - return -}