media: support thumbnails
Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
@@ -358,7 +358,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
|
||||
avatar = &bridgev2.Avatar{
|
||||
ID: ids.MakeAvatarID(photo.ID),
|
||||
Get: func(ctx context.Context) (data []byte, err error) {
|
||||
data, _, err = download.DownloadPhoto(ctx, t.client.API(), photo)
|
||||
data, _, _, _, err = download.DownloadPhoto(ctx, t.client.API(), photo)
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
@@ -84,14 +84,25 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
|
||||
var mimeType string
|
||||
switch media := media.(type) {
|
||||
case *tg.MessageMediaPhoto:
|
||||
data, mimeType, err = download.DownloadPhotoMedia(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 {
|
||||
return nil, fmt.Errorf("unrecognized document type %T", media.Document)
|
||||
}
|
||||
mimeType = document.GetMimeType()
|
||||
data, err = download.DownloadDocument(ctx, client.client.API(), document)
|
||||
|
||||
if info.Thumbnail {
|
||||
_, _, largestThumbnail := download.GetLargestPhotoSize(document.Thumbs)
|
||||
data, mimeType, err = download.DownloadPhotoFileLocation(ctx, client.client.API(), &tg.InputDocumentFileLocation{
|
||||
ID: document.GetID(),
|
||||
AccessHash: document.GetAccessHash(),
|
||||
FileReference: document.GetFileReference(),
|
||||
ThumbSize: largestThumbnail.GetType(),
|
||||
})
|
||||
} else {
|
||||
mimeType = document.GetMimeType()
|
||||
data, err = download.DownloadDocument(ctx, client.client.API(), document)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled media type %T", media)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,16 @@ import (
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
func GetLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) {
|
||||
type dimensionable interface {
|
||||
GetW() int
|
||||
GetH() int
|
||||
}
|
||||
|
||||
func GetLargestPhotoSize(sizes []tg.PhotoSizeClass) (width, height int, largest tg.PhotoSizeClass) {
|
||||
if len(sizes) == 0 {
|
||||
panic("cannot get largest size from empty list of sizes")
|
||||
}
|
||||
|
||||
var maxSize int
|
||||
for _, s := range sizes {
|
||||
var currentSize int
|
||||
@@ -30,6 +39,24 @@ func GetLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass)
|
||||
if currentSize > maxSize {
|
||||
maxSize = currentSize
|
||||
largest = s
|
||||
if d, ok := s.(dimensionable); ok {
|
||||
width = d.GetW()
|
||||
height = d.GetH()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetLargestDimensions(sizes []tg.PhotoSizeClass) (width, height int) {
|
||||
for _, s := range sizes {
|
||||
switch size := s.(type) {
|
||||
case *tg.PhotoCachedSize:
|
||||
width = size.GetW()
|
||||
height = size.GetH()
|
||||
case *tg.PhotoSizeProgressive:
|
||||
width = size.GetW()
|
||||
height = size.GetH()
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -65,23 +92,26 @@ func DownloadPhotoFileLocation(ctx context.Context, client downloader.Client, fi
|
||||
return buf.Bytes(), mimeType, nil
|
||||
}
|
||||
|
||||
func DownloadPhoto(ctx context.Context, client downloader.Client, photo *tg.Photo) (data []byte, mimeType string, err error) {
|
||||
return DownloadPhotoFileLocation(ctx, client, &tg.InputPhotoFileLocation{
|
||||
func DownloadPhoto(ctx context.Context, client downloader.Client, photo *tg.Photo) (data []byte, width, height int, mimeType string, err error) {
|
||||
var largest tg.PhotoSizeClass
|
||||
width, height, largest = GetLargestPhotoSize(photo.GetSizes())
|
||||
data, mimeType, err = DownloadPhotoFileLocation(ctx, client, &tg.InputPhotoFileLocation{
|
||||
ID: photo.GetID(),
|
||||
AccessHash: photo.GetAccessHash(),
|
||||
FileReference: photo.GetFileReference(),
|
||||
ThumbSize: GetLargestPhotoSize(photo.GetSizes()).GetType(),
|
||||
ThumbSize: largest.GetType(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func DownloadPhotoMedia(ctx context.Context, client downloader.Client, media *tg.MessageMediaPhoto) (data []byte, mimeType string, err error) {
|
||||
func DownloadPhotoMedia(ctx context.Context, client downloader.Client, media *tg.MessageMediaPhoto) (data []byte, width, height int, mimeType string, err error) {
|
||||
p, ok := media.GetPhoto()
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("photo message sent without a photo")
|
||||
return nil, 0, 0, "", 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 nil, 0, 0, "", fmt.Errorf("unrecognized photo type %T", p)
|
||||
}
|
||||
return DownloadPhoto(ctx, client, photo)
|
||||
}
|
||||
|
||||
@@ -13,16 +13,18 @@ import (
|
||||
// The format of the media ID is as follows (each character represents a single
|
||||
// byte, |'s added for clarity):
|
||||
//
|
||||
// v|p|CCCCCCCC|TTTTTTTT
|
||||
// v|p|cccccccc|mmmmmmmm|T
|
||||
//
|
||||
// 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)
|
||||
// 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
|
||||
type DirectMediaInfo struct {
|
||||
PeerType PeerType
|
||||
ChatID int64
|
||||
MessageID int64
|
||||
Thumbnail bool
|
||||
}
|
||||
|
||||
func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) {
|
||||
@@ -32,6 +34,11 @@ func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) {
|
||||
}
|
||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.ChatID)) // Telegram Chat ID
|
||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.MessageID)) // Telegram Message ID
|
||||
if m.Thumbnail {
|
||||
mediaID = append(mediaID, 0x01)
|
||||
} else {
|
||||
mediaID = append(mediaID, 0x00)
|
||||
}
|
||||
return mediaID, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +51,7 @@ func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err
|
||||
err = fmt.Errorf("invalid version %d", mediaID[0])
|
||||
return
|
||||
}
|
||||
if len(mediaID) != 18 {
|
||||
if len(mediaID) != 18 && len(mediaID) != 19 {
|
||||
err = fmt.Errorf("invalid media ID")
|
||||
return
|
||||
}
|
||||
@@ -54,5 +61,8 @@ 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 {
|
||||
info.Thumbnail = mediaID[18] == 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -119,12 +119,12 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, intent bridgev2.MatrixAPI, media tg.MessageMediaClass) (*event.BeeperLinkPreview, error) {
|
||||
func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, intent bridgev2.MatrixAPI, media tg.MessageMediaClass) (preview *event.BeeperLinkPreview, err error) {
|
||||
webpage, ok := media.(*tg.MessageMediaWebPage).Webpage.(*tg.WebPage)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
preview := &event.BeeperLinkPreview{
|
||||
preview = &event.BeeperLinkPreview{
|
||||
MatchedURL: webpage.URL,
|
||||
LinkPreview: event.LinkPreview{
|
||||
Title: webpage.Title,
|
||||
@@ -135,25 +135,13 @@ func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, inte
|
||||
|
||||
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)
|
||||
var data []byte
|
||||
data, preview.ImageWidth, preview.ImageHeight, preview.ImageType, 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)
|
||||
preview.ImageURL, preview.ImageEncryption, err = intent.UploadMedia(ctx, "", data, "", preview.ImageType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,12 +150,34 @@ func (mc *MessageConverter) webpageToBeeperLinkPreview(ctx context.Context, inte
|
||||
return preview, nil
|
||||
}
|
||||
|
||||
func (mc *MessageConverter) directMedia(ctx context.Context, portal *bridgev2.Portal, msgID int, thumbnail bool) (uri id.ContentURIString, err error) {
|
||||
if !mc.useDirectMedia {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
peerType, chatID, err := ids.ParsePortalID(portal.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mediaID, err := ids.DirectMediaInfo{
|
||||
PeerType: peerType,
|
||||
ChatID: chatID,
|
||||
MessageID: int64(msgID),
|
||||
Thumbnail: thumbnail,
|
||||
}.AsMediaID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return portal.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||
}
|
||||
|
||||
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
|
||||
var audio *event.MSC1767Audio
|
||||
var voice *event.MSC3245Voice
|
||||
var info event.FileInfo
|
||||
|
||||
// Determine the filename and some other information
|
||||
switch media := media.(type) {
|
||||
@@ -175,18 +185,55 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
partID = networkid.PartID("photo")
|
||||
msgType = event.MsgImage
|
||||
filename = "image"
|
||||
if photo, ok := media.Photo.(*tg.Photo); ok {
|
||||
info.Width, info.Height, _ = download.GetLargestPhotoSize(photo.GetSizes())
|
||||
}
|
||||
case *tg.MessageMediaDocument:
|
||||
partID = networkid.PartID("document")
|
||||
msgType = event.MsgFile
|
||||
document, ok := media.Document.(*tg.Document)
|
||||
info.Size = int(document.Size)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("unrecognized document type %T", media.Document)
|
||||
}
|
||||
|
||||
if thumbSizes, ok := document.GetThumbs(); ok {
|
||||
info.ThumbnailInfo = &event.FileInfo{}
|
||||
var largestThumbnail tg.PhotoSizeClass
|
||||
info.ThumbnailInfo.Width, info.ThumbnailInfo.Height, largestThumbnail = download.GetLargestPhotoSize(thumbSizes)
|
||||
|
||||
var err error
|
||||
info.ThumbnailInfo.ThumbnailURL, err = mc.directMedia(ctx, portal, msgID, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if info.ThumbnailInfo.ThumbnailURL == "" {
|
||||
data, mimeType, err := download.DownloadPhotoFileLocation(ctx, mc.client.API(), &tg.InputDocumentFileLocation{
|
||||
ID: document.GetID(),
|
||||
AccessHash: document.GetAccessHash(),
|
||||
FileReference: document.GetFileReference(),
|
||||
ThumbSize: largestThumbnail.GetType(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("downloading thumbnail failed: %w", err)
|
||||
}
|
||||
|
||||
info.ThumbnailInfo.ThumbnailURL, info.ThumbnailInfo.ThumbnailFile, err = intent.UploadMedia(ctx, "", data, filename, mimeType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, attr := range document.GetAttributes() {
|
||||
switch a := attr.(type) {
|
||||
case *tg.DocumentAttributeFilename:
|
||||
filename = a.GetFileName()
|
||||
case *tg.DocumentAttributeVideo:
|
||||
msgType = event.MsgVideo
|
||||
info.Width, info.Height = a.W, a.H
|
||||
info.Duration = int(a.Duration * 1000)
|
||||
case *tg.DocumentAttributeAudio:
|
||||
msgType = event.MsgAudio
|
||||
audio = &event.MSC1767Audio{
|
||||
@@ -206,27 +253,11 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
return nil, nil, fmt.Errorf("unhandled media type %T", media)
|
||||
}
|
||||
|
||||
var mxcURI id.ContentURIString
|
||||
var encryptedFileInfo *event.EncryptedFileInfo
|
||||
|
||||
if mc.useDirectMedia {
|
||||
var err error
|
||||
peerType, chatID, err := ids.ParsePortalID(portal.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mediaID, err := ids.DirectMediaInfo{
|
||||
PeerType: peerType,
|
||||
ChatID: chatID,
|
||||
MessageID: int64(msgID),
|
||||
}.AsMediaID()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mxcURI, err = portal.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mxcURI, err := mc.directMedia(ctx, portal, msgID, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if mxcURI == "" {
|
||||
@@ -241,7 +272,7 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
filename = "image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
}
|
||||
|
||||
data, mimeType, err = download.DownloadPhotoMedia(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 {
|
||||
@@ -293,6 +324,7 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
Body: filename,
|
||||
URL: mxcURI,
|
||||
File: encryptedFileInfo,
|
||||
Info: &info,
|
||||
MSC1767Audio: audio,
|
||||
MSC3245Voice: voice,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user